#include "zeroerr/assert.h" #include "zeroerr/unittest.h" #include "common/Module.h" #include #include #include #include #include TEST_CASE("editor.actionTimeline.scriptUsesCanonicalTransactionsAndWorkspace") { ssq::VM vm(2048, ssq::Libs::ALL); eve::ModuleManager::expose(vm); vm.run(vm.compileSource(R"( editor <- eve.Editor(); workspace <- editor.newWorkspace("test.combat", "Test Combat Editor"); timelineAsset <- { schema="eve.action.timeline", schemaVersion=1, actionId="test:light-attack", durationNs=1000000000, animationUri="asset://test/attack.glb#Attack", metadata={}, tracks=[{ id="test-track:gameplay", label="Gameplay", kind="gameplay", muted=false, locked=false, notifies=[], states=[{ id="test-state:hitbox", type="combat:hitbox-window", startNs=200000000, endNs=500000000, payload={hitbox="weapon.main"} }] }] }; created <- eve.ActionEditorModule().create("test.asset.light-attack", timelineAsset); actionEditor <- created.value; externalDamage <- actionEditor.setRuntimeBlockExternallyHandled("combat:damage", true); externalUnknown <- actionEditor.setRuntimeBlockExternallyHandled("test:unknown", true); previewHostAvailable <- actionEditor.hasPreviewHost(); previewRefresh <- actionEditor.refreshPreview(); runtimeRateBeforeStart <- actionEditor.setRuntimeRate(1.5); sectionProgressBeforeStart <- actionEditor.evaluateRuntimeSectionProgress(0, 0.5); sectionSyncJumpBeforeStart <- actionEditor.syncRuntimeSectionAndJump(0, 0.25); clearSectionSyncBeforeStart <- actionEditor.clearRuntimeSectionSync(0); testAnimation <- eve.Animation(); testSkeleton <- testAnimation.newSkeleton(); testBoneMask <- testAnimation.newBoneMask(testSkeleton); testPose <- testAnimation.newPose(testSkeleton.getBoneCount()); setBoneMaskBeforeStart <- actionEditor.setRuntimeBoneMask(testBoneMask); clearBoneMaskBeforeStart <- actionEditor.clearRuntimeBoneMask(); setLayerWeightBeforeStart <- actionEditor.setRuntimeLayerWeight(0.5); setLayerAdditiveBeforeStart <- actionEditor.setRuntimeLayerAdditive(true); runtimeLayerWeightBeforeStart <- actionEditor.getRuntimeLayerWeight(); runtimeLayerAdditiveBeforeStart <- actionEditor.getRuntimeLayerAdditive(); runtimePoseOverBaseBeforeStart <- actionEditor.getRuntimePoseOverBase(testPose); runtimeStateBeforeStart <- actionEditor.getRuntimeState(); runtimeElapsedBeforeStart <- actionEditor.getRuntimeElapsedSeconds(); runtimeDurationBeforeStart <- actionEditor.getRuntimeDurationSeconds(); runtimeLayerBeforeStart <- actionEditor.getRuntimeLayer(); runtimeSlotBeforeStart <- actionEditor.getRuntimeSlot(); configured <- actionEditor.configureWorkspace(workspace); workspacePanelCount <- workspace.getPanelCount(); viewport <- actionEditor.setViewport(800.0, 36.0, 120.0); beforeStart <- actionEditor.getStateStart("test-state:hitbox"); itemCenterX <- (actionEditor.getItemMinX(0) + actionEditor.getItemMaxX(0)) * 0.5; itemCenterY <- (actionEditor.getItemMinY(0) + actionEditor.getItemMaxY(0)) * 0.5; pointerDownResult <- actionEditor.pointerDown(itemCenterX, itemCenterY, false); pointerMoveResult <- actionEditor.pointerMove(itemCenterX + 80.0); pointerUpResult <- actionEditor.pointerUp(itemCenterX + 80.0); movedStart <- actionEditor.getStateStart("test-state:hitbox"); revisionAfterDrag <- actionEditor.getRevision(); undoResult <- actionEditor.undo(); restoredStart <- actionEditor.getStateStart("test-state:hitbox"); inspectorTiming <- actionEditor.setItemTiming("test-state:hitbox", 0.1, 0.6); inspectorStart <- actionEditor.getItemStart("test-state:hitbox"); inspectorEnd <- actionEditor.getItemEnd("test-state:hitbox"); invalidInspectorTiming <- actionEditor.setItemTiming("test-state:hitbox", 0.7, 0.2); detailsResult <- actionEditor.editItemDetails( "test-state:hitbox", "combat:hitbox-window", "{\"hitbox\":\"weapon.alt\"}"); payloadAfterDetails <- actionEditor.getItemPayloadJson("test-state:hitbox"); payloadTextPatch <- actionEditor.setItemPayloadText( "test-state:hitbox", "hitbox", "weapon.typed"); typedHitbox <- actionEditor.getItemPayloadText( "test-state:hitbox", "hitbox", "missing"); payloadVectorPatch <- actionEditor.setItemPayloadVector3( "test-state:hitbox", "positionOffset", 1.0, 2.0, 3.0); typedPositionY <- actionEditor.getItemPayloadVector( "test-state:hitbox", "positionOffset", 1, -1.0); payloadTextListPatch <- actionEditor.setItemPayloadTextList( "test-state:hitbox", "relatedAssets", "asset://audio/a.wav; asset://audio/b.wav"); typedTextList <- actionEditor.getItemPayloadTextList("test-state:hitbox", "relatedAssets"); invalidPayloadPatch <- actionEditor.setItemPayloadText( "test-state:hitbox", "hitbox", ""); shapeMismatch <- actionEditor.editItemDetails( "test-state:hitbox", "combat:damage", "{\"damageType\":\"slash\",\"amount\":2}"); invalidPayload <- actionEditor.editItemDetails( "test-state:hitbox", "combat:hitbox-window", "{}"); disableResult <- actionEditor.setItemEnabled("test-state:hitbox", false); disabledValue <- actionEditor.getItemEnabled("test-state:hitbox"); restoreEnabledResult <- actionEditor.undo(); restoredEnabledValue <- actionEditor.getItemEnabled("test-state:hitbox"); removeItemResult <- actionEditor.removeItem("test-state:hitbox"); itemCountAfterRemove <- actionEditor.getItemCount(); restoreRemovedResult <- actionEditor.undo(); itemCountAfterRestore <- actionEditor.getItemCount(); missResult <- actionEditor.pointerDown(700.0, 20.0, false); draggingAfterMiss <- actionEditor.isDragging(); seekResult <- actionEditor.seekX(700.0); emptySpaceSeekTime <- actionEditor.getPreviewTime(); actionEditor.seekSeconds(0.0); actionEditor.play(); advanced <- actionEditor.update(1.0); previewEventCount <- actionEditor.getEventCount(); snapshotResult <- actionEditor.snapshot(); timelineAbilitySplitA <- actionEditor.addSectionSplit(0.2); timelineAbilitySplitB <- actionEditor.addSectionSplit(0.6); snapshotJsonResult <- actionEditor.snapshotJson(); combatRuntime <- eve.Combat().newRuntime().value; timelineAbility <- combatRuntime.registerTimelineAbility( "combat-ability:authored-light", snapshotJsonResult.value, 0.25, "per-execution", "exclusive-replaceable", "Ability.Combat.Attack.Light"); duplicateTimelineAbility <- combatRuntime.registerTimelineAbility( "combat-ability:authored-light", snapshotJsonResult.value, 0.25, "per-execution", "exclusive-replaceable", "Ability.Combat.Attack.Light"); invalidTimelineAbility <- combatRuntime.registerTimelineAbility( "combat-ability:invalid-policy", snapshotJsonResult.value, 0.25, "per-execution", "invalid", "Ability.Combat.Attack.Light"); invalidTimelineGrant <- combatRuntime.grantAbility("fighter:player", "combat-ability:invalid-policy"); timelineGrant <- combatRuntime.grantAbility("fighter:player", "combat-ability:authored-light"); timelineMatches <- combatRuntime.matchingAbilities("fighter:player", "Ability.Combat.Attack.Light"); timelineActivation <- combatRuntime.activateAbility(timelineGrant.value.grantId, 1); actionEditor.setSectionSplit(0, 0.3); actionEditor.setSectionSplit(1, 0.8); replacementJson <- actionEditor.snapshotJson(); replacedTimelineAbility <- combatRuntime.replaceTimelineAbility( "combat-ability:authored-light", replacementJson.value, 0.0, "per-execution", "exclusive-replaceable", "Ability.Combat.Attack.Light"); timelineAbilityAdvance <- combatRuntime.advanceAbilities(2, 1.0); timelineActiveAfterAdvance <- timelineAbilityAdvance.value.activeCount; replacementActivation <- combatRuntime.activateAbility(timelineGrant.value.grantId, 3); replacementAdvance <- combatRuntime.advanceAbilities(4, 0.5); timelineAbilityAction <- timelineAbility.value.actionId; timelineAbilityDuration <- timelineAbility.value.durationSeconds; timelineAbilitySections <- timelineAbility.value.sectionCount; timelineMatchedGrant <- timelineMatches.value[0]; timelineGrantedId <- timelineGrant.value.grantId; replacementAbilityAction <- replacedTimelineAbility.value.actionId; replacementStillActive <- replacementAdvance.value.activeCount; actionEditor.removeSectionSplit(1); actionEditor.removeSectionSplit(0); instantTypeCount <- actionEditor.getInsertableTypeCount(false); stateTypeCount <- actionEditor.getInsertableTypeCount(true); damageTypeIndex <- -1; invulnerabilityTypeIndex <- -1; for (local i = 0; i < instantTypeCount; ++i) if (actionEditor.getInsertableType(false, i) == "combat:damage") damageTypeIndex = i; for (local i = 0; i < stateTypeCount; ++i) if (actionEditor.getInsertableType(true, i) == "combat:invulnerability-window") invulnerabilityTypeIndex = i; damageTypeLabel <- actionEditor.getInsertableTypeLabel(false, damageTypeIndex); actionEditor.seekSeconds(0.7); insertedNotify <- actionEditor.addNotifyAtCursor( "test-track:gameplay", "combat:damage", "{\"damageType\":\"slash\",\"amount\":3}"); insertedState <- actionEditor.addStateAtCursor( "test-track:gameplay", "combat:invulnerability-window", 0.1, "{}"); invalidInsertedNotify <- actionEditor.addNotifyAtCursor( "test-track:gameplay", "combat:damage", "{}"); itemCountAfterInsert <- actionEditor.getItemCount(); initialTrackLocked <- actionEditor.getTrackLocked(0); lockTrack <- actionEditor.setTrackLocked("test-track:gameplay", true); lockedTrackValue <- actionEditor.getTrackLocked(0); unlockTrack <- actionEditor.setTrackLocked("test-track:gameplay", false); renameTrack <- actionEditor.renameTrack("test-track:gameplay", "Combat Events"); renamedTrackLabel <- actionEditor.getTrackLabel(0); copyTrackResult <- actionEditor.copyTrack("test-track:gameplay"); pasteTrackResult <- actionEditor.pasteTrack(); trackCountAfterPaste <- actionEditor.getTrackCount(); removePastedTrack <- actionEditor.removeTrack(pasteTrackResult.value); trackCountAfterTrackCleanup <- actionEditor.getTrackCount(); addSectionResult <- actionEditor.addAnimationSection( "test-section:attack", "asset://test/attack.glb#Attack", 0.0, 0.4, 0.1); editSectionResult <- actionEditor.editAnimationSection( "test-section:attack", 0.05, 0.45, 0.08, "asset://test/attack.glb#AttackEdited"); editSectionSourceResult <- actionEditor.editAnimationSectionSource( "test-section:attack", 0.1, 0.3, "linear"); sectionUri <- actionEditor.getAnimationSectionUri(0); sectionStart <- actionEditor.getAnimationSectionStart(0); sectionBlend <- actionEditor.getAnimationSectionBlendIn(0); sectionSourceStart <- actionEditor.getAnimationSectionSourceStart(0); sectionCurve <- actionEditor.getAnimationSectionBlendCurve(0); removeSectionResult <- actionEditor.removeAnimationSection("test-section:attack"); sectionCountAfterCleanup <- actionEditor.getAnimationSectionCount(); montageSettingsResult <- actionEditor.setMontageSettings( 1.5, true, true, 2, 0.12, 0.18, -0.03, true, false, false); montageRate <- actionEditor.getMontageBasePlayRate(); montageLooping <- actionEditor.getMontageLooping(); montageFootIk <- actionEditor.getMontageFootIk(); montageLayer <- actionEditor.getMontageAnimationLayer(); montageBlendOffset <- actionEditor.getMontageBlendOutOffset(); montageRootRotation <- actionEditor.getMontageRootMotionRotation(); invalidMontageSettings <- actionEditor.setMontageSettings( 0.0, true, true, 2, 0.12, 0.18, -0.03, true, false, false); undoMontageSettings <- actionEditor.undo(); restoredMontageRate <- actionEditor.getMontageBasePlayRate(); splitCountBefore <- actionEditor.getSectionSplitCount(); addSplit <- actionEditor.addSectionSplit(0.25); splitTimeAfterAdd <- actionEditor.getSectionSplitTime(0); moveSplit <- actionEditor.setSectionSplit(0, 0.35); splitTimeAfterMove <- actionEditor.getSectionSplitTime(0); duplicateSplit <- actionEditor.addSectionSplit(0.35); removeSplit <- actionEditor.removeSectionSplit(0); splitCountAfterRemove <- actionEditor.getSectionSplitCount(); invalidResult <- eve.ActionEditorModule().create("test.asset.invalid", { schema="eve.action.timeline", schemaVersion=1, actionId="test:invalid", durationNs=-1, animationUri="", metadata={}, tracks=[] }); )")); CHECK(vm.find("created").toTable().get("ok")); CHECK_EQ(vm.find("created").toTable().get("ownership"), std::string("owned")); CHECK(vm.find("externalDamage").toTable().get("ok")); CHECK(!vm.find("externalUnknown").toTable().get("ok")); CHECK(!vm.find("previewHostAvailable").toBool()); CHECK(vm.find("previewRefresh").toTable().get("ok")); CHECK(!vm.find("runtimeRateBeforeStart").toTable().get("ok")); CHECK(!vm.find("sectionProgressBeforeStart").toTable().get("ok")); CHECK(!vm.find("sectionSyncJumpBeforeStart").toTable().get("ok")); CHECK(!vm.find("clearSectionSyncBeforeStart").toTable().get("ok")); CHECK(!vm.find("setBoneMaskBeforeStart").toTable().get("ok")); CHECK(!vm.find("clearBoneMaskBeforeStart").toTable().get("ok")); CHECK_EQ(vm.find("runtimeStateBeforeStart").toString(), std::string("not-started")); CHECK_EQ(vm.find("runtimeElapsedBeforeStart").toFloat(), 0.0f); CHECK_EQ(vm.find("runtimeDurationBeforeStart").toFloat(), 0.0f); CHECK_EQ(vm.find("runtimeLayerBeforeStart").toInt(), -1); CHECK_EQ(vm.find("runtimeSlotBeforeStart").toInt(), -1); CHECK(vm.find("configured").toTable().get("ok")); CHECK_EQ(vm.find("workspacePanelCount").toInt(), 4); CHECK(vm.find("viewport").toTable().get("ok")); CHECK(vm.find("pointerDownResult").toTable().get("ok")); CHECK(vm.find("pointerMoveResult").toTable().get("ok")); CHECK(vm.find("pointerUpResult").toTable().get("ok")); CHECK(vm.find("movedStart").toFloat() > vm.find("beforeStart").toFloat()); CHECK_EQ(vm.find("revisionAfterDrag").toInt(), 1); CHECK(vm.find("undoResult").toTable().get("ok")); CHECK_LT(std::abs(vm.find("restoredStart").toFloat() - vm.find("beforeStart").toFloat()), 0.0001F); CHECK(vm.find("inspectorTiming").toTable().get("ok")); CHECK_LT(std::abs(vm.find("inspectorStart").toFloat() - 0.1F), 0.0001F); CHECK_LT(std::abs(vm.find("inspectorEnd").toFloat() - 0.6F), 0.0001F); CHECK(!vm.find("invalidInspectorTiming").toTable().get("ok")); CHECK(vm.find("detailsResult").toTable().get("ok")); CHECK_NE(vm.find("payloadAfterDetails").toString().find("weapon.alt"), std::string::npos); CHECK(vm.find("payloadTextPatch").toTable().get("ok")); CHECK_EQ(vm.find("typedHitbox").toString(), std::string("weapon.typed")); CHECK(vm.find("payloadVectorPatch").toTable().get("ok")); CHECK(std::fabs(vm.find("typedPositionY").toFloat() - 2.0f) < 1e-5f); CHECK(vm.find("payloadTextListPatch").toTable().get("ok")); CHECK_EQ(vm.find("typedTextList").toString(), std::string("asset://audio/a.wav; asset://audio/b.wav")); CHECK(!vm.find("invalidPayloadPatch").toTable().get("ok")); CHECK(!vm.find("shapeMismatch").toTable().get("ok")); CHECK(!vm.find("invalidPayload").toTable().get("ok")); CHECK(vm.find("disableResult").toTable().get("ok")); CHECK(!vm.find("disabledValue").toBool()); CHECK(vm.find("restoreEnabledResult").toTable().get("ok")); CHECK(vm.find("restoredEnabledValue").toBool()); CHECK(vm.find("removeItemResult").toTable().get("ok")); CHECK_EQ(vm.find("itemCountAfterRemove").toInt(), 0); CHECK(vm.find("restoreRemovedResult").toTable().get("ok")); CHECK_EQ(vm.find("itemCountAfterRestore").toInt(), 1); CHECK(vm.find("missResult").toTable().get("ok")); CHECK(!vm.find("draggingAfterMiss").toBool()); CHECK(vm.find("seekResult").toTable().get("ok")); CHECK(vm.find("emptySpaceSeekTime").toFloat() > 0.8F); CHECK(vm.find("advanced").toTable().get("ok")); CHECK_EQ(vm.find("previewEventCount").toInt(), 2); CHECK(vm.find("snapshotResult").toTable().get("ok")); CHECK(vm.find("snapshotJsonResult").toTable().get("ok")); CHECK(vm.find("timelineAbilitySplitA").toTable().get("ok")); CHECK(vm.find("timelineAbilitySplitB").toTable().get("ok")); CHECK(vm.find("timelineAbility").toTable().get("ok")); CHECK(!vm.find("duplicateTimelineAbility").toTable().get("ok")); CHECK(!vm.find("invalidTimelineAbility").toTable().get("ok")); CHECK(!vm.find("invalidTimelineGrant").toTable().get("ok")); CHECK_EQ(vm.find("timelineAbilityAction").toString(), std::string("test:light-attack")); CHECK_LT(std::abs(vm.find("timelineAbilityDuration").toFloat() - 1.0f), 1e-5f); CHECK_EQ(vm.find("timelineAbilitySections").toInt(), 3); CHECK_EQ(vm.find("timelineMatchedGrant").toInt(), vm.find("timelineGrantedId").toInt()); CHECK(vm.find("timelineActivation").toTable().get("ok")); CHECK_EQ(vm.find("timelineActiveAfterAdvance").toInt(), 0); CHECK(vm.find("replacementJson").toTable().get("ok")); CHECK(vm.find("replacedTimelineAbility").toTable().get("ok")); CHECK(vm.find("replacementActivation").toTable().get("ok")); CHECK_EQ(vm.find("replacementAbilityAction").toString(), std::string("test:light-attack")); CHECK_EQ(vm.find("replacementStillActive").toInt(), 1); CHECK(vm.find("instantTypeCount").toInt() > 0); CHECK(vm.find("stateTypeCount").toInt() > 0); CHECK(vm.find("damageTypeIndex").toInt() >= 0); CHECK(vm.find("invulnerabilityTypeIndex").toInt() >= 0); CHECK_NE(vm.find("damageTypeLabel").toString().find("Apply Damage"), std::string::npos); CHECK(vm.find("insertedNotify").toTable().get("ok")); CHECK(vm.find("insertedState").toTable().get("ok")); CHECK(!vm.find("invalidInsertedNotify").toTable().get("ok")); CHECK_EQ(vm.find("itemCountAfterInsert").toInt(), 3); CHECK(!vm.find("initialTrackLocked").toBool()); CHECK(vm.find("lockTrack").toTable().get("ok")); CHECK(vm.find("lockedTrackValue").toBool()); CHECK(vm.find("unlockTrack").toTable().get("ok")); CHECK(vm.find("renameTrack").toTable().get("ok")); CHECK_EQ(vm.find("renamedTrackLabel").toString(), std::string("Combat Events")); CHECK(vm.find("copyTrackResult").toTable().get("ok")); CHECK(vm.find("pasteTrackResult").toTable().get("ok")); CHECK_EQ(vm.find("trackCountAfterPaste").toInt(), 2); CHECK(vm.find("removePastedTrack").toTable().get("ok")); CHECK_EQ(vm.find("trackCountAfterTrackCleanup").toInt(), 1); CHECK(vm.find("addSectionResult").toTable().get("ok")); CHECK(vm.find("editSectionResult").toTable().get("ok")); CHECK(vm.find("editSectionSourceResult").toTable().get("ok")); CHECK_EQ(vm.find("sectionUri").toString(), std::string("asset://test/attack.glb#AttackEdited")); CHECK(std::fabs(vm.find("sectionStart").toFloat() - 0.05f) < 1e-5f); CHECK(std::fabs(vm.find("sectionBlend").toFloat() - 0.08f) < 1e-5f); CHECK(std::fabs(vm.find("sectionSourceStart").toFloat() - 0.1f) < 1e-5f); CHECK_EQ(vm.find("sectionCurve").toString(), std::string("linear")); CHECK(vm.find("removeSectionResult").toTable().get("ok")); CHECK_EQ(vm.find("sectionCountAfterCleanup").toInt(), 0); CHECK(vm.find("montageSettingsResult").toTable().get("ok")); CHECK(std::fabs(vm.find("montageRate").toFloat() - 1.5f) < 1e-5f); CHECK(vm.find("montageLooping").toBool()); CHECK(vm.find("montageFootIk").toBool()); CHECK_EQ(vm.find("montageLayer").toInt(), 2); CHECK(std::fabs(vm.find("montageBlendOffset").toFloat() + 0.03f) < 1e-5f); CHECK(!vm.find("montageRootRotation").toBool()); CHECK_EQ(vm.find("splitCountBefore").toInt(), 0); CHECK(vm.find("addSplit").toTable().get("ok")); CHECK(std::fabs(vm.find("splitTimeAfterAdd").toFloat() - 0.25f) < 1e-5f); CHECK(vm.find("moveSplit").toTable().get("ok")); CHECK(std::fabs(vm.find("splitTimeAfterMove").toFloat() - 0.35f) < 1e-5f); CHECK(!vm.find("duplicateSplit").toTable().get("ok")); CHECK(vm.find("removeSplit").toTable().get("ok")); CHECK_EQ(vm.find("splitCountAfterRemove").toInt(), 0); CHECK(!vm.find("invalidMontageSettings").toTable().get("ok")); CHECK(vm.find("undoMontageSettings").toTable().get("ok")); CHECK(std::fabs(vm.find("restoredMontageRate").toFloat() - 1.0f) < 1e-5f); CHECK(!vm.find("invalidResult").toTable().get("ok")); } TEST_CASE("editor.actionTimeline.scriptPersistsDocumentAndReportsDirtyState") { const auto root = std::filesystem::temp_directory_path() / ("eve_action_script_document_" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); std::error_code cleanupError; std::filesystem::remove_all(root, cleanupError); { ssq::VM vm(2048, ssq::Libs::ALL); eve::ModuleManager::expose(vm); const std::string source = std::string(R"( timelineAsset <- { schema="eve.action.timeline", schemaVersion=1, actionId="test:persistent", durationNs=1000000000, animationUri="asset://test/attack.glb#Attack", metadata={}, tracks=[{ id="test-track:gameplay", label="Gameplay", kind="gameplay", muted=false, locked=false, notifies=[], states=[] }] }; editor <- eve.Editor(); module <- eve.ActionEditorModule(); opened <- module.openDocument(")") + root.generic_string() + R"(", "test.asset.persistent", "Persistent Action", "content://Actions/Persistent.action", timelineAsset); actionEditor <- opened.value; backed <- actionEditor.isDocumentBacked(); initiallyDirty <- actionEditor.isDirty(); renameResult <- actionEditor.renameTrack("test-track:gameplay", "Gameplay Saved"); dirtyAfterEdit <- actionEditor.isDirty(); saveResult <- actionEditor.saveDocument(); dirtyAfterSave <- actionEditor.isDirty(); reopened <- module.openDocument(")" + root.generic_string() + R"(", "test.asset.reopened", "Reopened Action", "content://Actions/Persistent.action", timelineAsset); reopenedLabel <- reopened.value.getTrackLabel(0); )"; vm.run(vm.compileSource(source.c_str())); CHECK(vm.find("opened").toTable().get("ok")); CHECK(vm.find("backed").toBool()); CHECK(!vm.find("initiallyDirty").toBool()); CHECK(vm.find("renameResult").toTable().get("ok")); CHECK(vm.find("dirtyAfterEdit").toBool()); CHECK(vm.find("saveResult").toTable().get("ok")); CHECK(!vm.find("dirtyAfterSave").toBool()); CHECK(vm.find("reopened").toTable().get("ok")); CHECK_EQ(vm.find("reopenedLabel").toString(), std::string("Gameplay Saved")); const std::string catalogSource = std::string(R"( catalogCreated <- module.createAssetCatalog(")") + root.generic_string() + R"("); catalog <- catalogCreated.value; catalogRegister <- catalog.registerDocument( "test.asset.persistent", "content://Actions/Persistent.action"); catalogRefresh <- catalog.refresh("persistent"); catalogCount <- catalog.getAssetCount(); catalogGuid <- catalog.getAssetGuid(0); catalogUri <- catalog.getAssetUri(0); catalogTitle <- catalog.getAssetTitle(0); catalogGeneration <- catalog.getGeneration(); catalogEmptySearch <- catalog.refresh("missing-query"); catalogEmptyCount <- catalog.getAssetCount(); )"; vm.run(vm.compileSource(catalogSource.c_str())); CHECK(vm.find("catalogCreated").toTable().get("ok")); CHECK(vm.find("catalogRegister").toTable().get("ok")); CHECK(vm.find("catalogRefresh").toTable().get("ok")); CHECK_EQ(vm.find("catalogCount").toInt(), 1); CHECK_EQ(vm.find("catalogGuid").toString(), std::string("test.asset.persistent")); CHECK_EQ(vm.find("catalogUri").toString(), std::string("content://Actions/Persistent.action")); CHECK_EQ(vm.find("catalogTitle").toString(), std::string("Persistent")); CHECK(vm.find("catalogGeneration").toInt() > 0); CHECK(vm.find("catalogEmptySearch").toTable().get("ok")); CHECK_EQ(vm.find("catalogEmptyCount").toInt(), 0); } CHECK(std::filesystem::is_regular_file(root / "Content" / "Actions" / "Persistent.action")); std::filesystem::remove_all(root, cleanupError); }