import { describe, expect, it } from 'vitest'; import { type Graph, GraphInterpreter } from '../../src/core/graph'; import { fakeContext, fakeEntity, graphEditor, makeBehavior, makeSpriteScene, place, startWorld, } from '../helpers'; // Node-by-node behaviour lives in node-catalog.test.ts; what is left here needs a running // world (On click's hit test) or a whole flow (Spawn feeding its object downstream). describe('game-flow nodes', () => { it('fires On click when the mouse is pressed on the owner', () => { const f = makeSpriteScene('Button'); const behavior = makeBehavior(f, 'OnPress', ['clicks']); // On click → clicks = clicks + 1 const edit = graphEditor(f, behavior); const click = edit.addNode('OnClick'); const get = edit.addNode('GetVariable', { variable: 'clicks' }); const one = edit.addNode('Number', { value: 1 }); const add = edit.addNode('Add'); const set = edit.addNode('SetVariable', { variable: 'clicks' }); edit.wire([ [click, 'exec', set, 'exec'], [get, 'value', add, 'a'], [one, 'value', add, 'b'], [add, 'result', set, 'value'], ]); const button = place(f, f.spriteId, { x: 0, y: 0 }); const logic = place(f, behavior.id, { x: 0, y: 0 }, button); const { world } = startWorld(f, { behaviors: true }); const entity = world.findByObjectId(logic)!; world.movePointer(500, 500, true); // beside the button world.setPointerDown(true); world.tick(0.1); expect(entity.props.clicks).toBe(0); world.setPointerDown(false); world.movePointer(10, 10, true); // on the button world.setPointerDown(true); world.tick(0.1); expect(entity.props.clicks).toBe(1); world.tick(0.1); // still held – the press was one frame expect(entity.props.clicks).toBe(1); }); }); describe('Spawn node', () => { // On start → Spawn(crate at (5,7)) → Log(the spawned object) const graph: Graph = { nodes: [ { id: 'start', type: 'OnStart', x: 0, y: 0, params: {} }, { id: 'spawn', type: 'Spawn', x: 0, y: 0, params: { asset: 'crate', x: 5, y: 7 } }, { id: 'log', type: 'Log', x: 0, y: 0, params: {} }, ], edges: [ { id: 'e1', from: { node: 'start', port: 'exec' }, to: { node: 'spawn', port: 'exec' } }, { id: 'e2', from: { node: 'spawn', port: 'exec' }, to: { node: 'log', port: 'exec' } }, { id: 'e3', from: { node: 'spawn', port: 'object' }, to: { node: 'log', port: 'message' } }, ], }; it('asks the context for a new instance at the inline position and outputs it', () => { const calls: unknown[] = []; const ctx = fakeContext({ spawn: (assetId, position, parent) => { calls.push({ assetId, position, parent }); return fakeEntity('Crate', position.x); }, }); new GraphInterpreter(graph).fire('OnStart', ctx); expect(calls).toEqual([{ assetId: 'crate', position: { x: 5, y: 7, z: 0 }, parent: null }]); expect(ctx.logs).toEqual(['Crate']); // the spawned object flowed into Log }); it('logs a hint and continues when no asset is selected or the asset is unknown', () => { const ctx = fakeContext({ spawn: () => null }); const noAsset: Graph = { ...graph, nodes: graph.nodes.map((n) => (n.id === 'spawn' ? { ...n, params: {} } : n)), }; new GraphInterpreter(noAsset).fire('OnStart', ctx); expect(ctx.logs).toEqual(['Spawn: no asset selected', '']); const ctx2 = fakeContext({ spawn: () => null }); new GraphInterpreter(graph).fire('OnStart', ctx2); expect(ctx2.logs[0]).toBe('Spawn: asset crate not found'); }); });