diff --git a/tests/editor/editor-run.test.ts b/tests/editor/editor-run.test.ts index 4f17072d..62944c3c 100644 --- a/tests/editor/editor-run.test.ts +++ b/tests/editor/editor-run.test.ts @@ -5,6 +5,12 @@ import { describe, expect, it } from 'vitest'; import { CreateAssetCommand, SetAssetPropertyCommand } from '../../src/core/commands/assetCommands'; import { CreateContainerCommand } from '../../src/core/commands/containerCommands'; +import { + AddNodeCommand, + ConnectCommand, + RemoveNodeCommand, + SetNodeParamCommand, +} from '../../src/core/commands/graphCommands'; import { instanceChildId } from '../../src/core/model/instance'; import { ClearOverrideCommand, SetOverrideCommand } from '../../src/core/commands/sceneObjectCommands'; import type { BehaviorAsset, ContainerAsset, VariablesAsset } from '../../src/core/model/Asset'; @@ -316,6 +322,77 @@ describe('shared variables of an editor run', () => { }); }); +/** The point of the switch is changing a graph and watching, so an edit must not wait for a restart (#216). */ +describe('a graph edited while its behavior runs in the editor (#216)', () => { + function running() { + const f = scene(); + f.state.execute(new SetOverrideCommand(f.sceneId, f.logic, 'runInEditor', true)); + f.frame(0.1); + const nodeOf = (type: string) => f.behavior.graph.nodes.find((n) => n.type === type)!; + return { ...f, nodeOf, boxY: () => f.object(f.box).position.y }; + } + + it('runs with a changed node setting from the next frame on', () => { + const f = running(); + expect(f.boxX()).toBeCloseTo(10); + + f.state.execute(new SetNodeParamCommand(f.behavior.id, f.nodeOf('Move').id, 'scale', 1000)); + f.frame(0.1); + expect(f.boxX()).toBeCloseTo(110); + }); + + it('runs a node wired in while it runs, and no longer one that was cut off', () => { + const f = running(); + const down = new AddNodeCommand(f.behavior.id, 'Move', 0, 0, { dy: 1, scale: 50 }); + f.state.execute(down); + const port = (node: { id: string }, key: string) => ({ node: node.id, port: key }); + const [update, owner, move] = [f.nodeOf('OnUpdate'), f.nodeOf('Owner'), f.nodeOf('Move')]; + f.state.execute(new ConnectCommand(f.behavior.id, port(move, 'exec'), port(down.node, 'exec'))); + f.state.execute(new ConnectCommand(f.behavior.id, port(update, 'dt'), port(down.node, 'dt'))); + f.state.execute(new ConnectCommand(f.behavior.id, port(owner, 'object'), port(down.node, 'object'))); + f.frame(0.1); + expect(f.boxX()).toBeCloseTo(20); + expect(f.boxY()).toBeCloseTo(5); + + f.state.execute(new RemoveNodeCommand(f.behavior.id, move.id)); + f.frame(0.1); + expect(f.boxX()).toBeCloseTo(20); + expect(f.boxY()).toBeCloseTo(5); + }); + + /** Decided in #216: the instance carries on, so what `On start` writes is not written a second time. */ + it('carries on rather than starting over: a flow hung on On start afterwards does not fire', () => { + const f = running(); + const start = new AddNodeCommand(f.behavior.id, 'OnStart', 0, 0); + const down = new AddNodeCommand(f.behavior.id, 'Move', 0, 0, { dy: 1, scale: 50 }); + f.state.execute(start); + f.state.execute(down); + const port = (node: { id: string }, key: string) => ({ node: node.id, port: key }); + f.state.execute(new ConnectCommand(f.behavior.id, port(start.node, 'exec'), port(down.node, 'exec'))); + f.state.execute( + new ConnectCommand(f.behavior.id, port(f.nodeOf('Owner'), 'object'), port(down.node, 'object')), + ); + f.frame(0.1); + expect(f.boxX()).toBeCloseTo(20); + expect(f.boxY()).toBe(0); + + // Off and on is what starts it over: the world is built anew, and `On start` runs. + f.state.execute(new ClearOverrideCommand(f.sceneId, f.logic, 'runInEditor')); + f.frame(0.1); + f.state.execute(new SetOverrideCommand(f.sceneId, f.logic, 'runInEditor', true)); + f.frame(0.1); + expect(f.boxY()).not.toBe(0); + }); + + it('takes an undo of the edit back the same way', () => { + const f = running(); + f.state.execute(new SetNodeParamCommand(f.behavior.id, f.nodeOf('Move').id, 'scale', 1000)); + f.state.undo(); + f.frame(0.1); + expect(f.boxX()).toBeCloseTo(20); + }); +}); + describe('the game nodes in the editor run (#167)', () => { it('ignores Pause game and says so: the scene keeps moving', () => { const f = scene(); diff --git a/tools/e2e/43-queries.mjs b/tools/e2e/43-queries.mjs index 325718a7..aca84337 100644 --- a/tools/e2e/43-queries.mjs +++ b/tools/e2e/43-queries.mjs @@ -382,15 +382,6 @@ export async function queries(t) { settings.includes(`Layers: ${names[0]}`), JSON.stringify(settings), ); - // A running instance keeps the graph it started with – measured here: the setting stands in the - // project at once, what the node casts does not. So the behavior is switched off and on, which - // ends the instance and builds a new one on the graph as it now stands. - await select({ kind: 'object', holderId: 'scene1', objectId: 'q-eyebox-go', objectIds: ['q-eyebox-go'] }); - for (let i = 0; i < 2; i++) { - const runRow = await inView(t, '__gm.inspectorRect()', "__gm.inspectorField('Run in editor')"); - await click(runRow.x, runRow.y); - await settle(); - } await sleep(600); const through = reached(); check(