import { beforeEach, describe, expect, it } from 'vitest'; import { CreateAssetCommand } from '../../src/core/commands/assetCommands'; import { AddNodeCommand } from '../../src/core/commands/graphCommands'; import type { BehaviorAsset } from '../../src/core/model/Asset'; import { createEmptyProject } from '../../src/core/model/Project'; import { removeNodes } from '../../src/editor/actions'; import { EditorState, nodeSelection } from '../../src/editor/state'; function setup(): { state: EditorState; behavior: BehaviorAsset; ids: string[] } { const state = new EditorState(); state.loadProject(createEmptyProject('p', 'P'), 1); const create = new CreateAssetCommand('behavior', 'B'); // A rejected command only returns false, so an unasserted setup would leave an empty graph // and let every assertion below pass for the wrong reason. expect(state.execute(create)).toBe(true); const behavior = create.asset as BehaviorAsset; const ids = ['OnUpdate', 'Number', 'Number'].map((type) => { const add = new AddNodeCommand(behavior.id, type, 0, 0); expect(state.execute(add)).toBe(true); return add.node.id; }); return { state, behavior, ids }; } describe('selecting several graph nodes', () => { let f: ReturnType; beforeEach(() => { f = setup(); }); it('keeps the last picked one as the primary, which is what the inspector shows', () => { f.state.select(nodeSelection(f.behavior.id, f.ids)); expect(f.state.selectedNodeIds).toEqual(f.ids); expect(f.state.selection).toMatchObject({ kind: 'node', nodeId: f.ids[2] }); }); it('adds and removes a node on ctrl-click', () => { f.state.select(nodeSelection(f.behavior.id, [f.ids[0]!])); f.state.toggleNode(f.behavior.id, f.ids[1]!); expect(f.state.selectedNodeIds).toEqual([f.ids[0], f.ids[1]]); f.state.toggleNode(f.behavior.id, f.ids[0]!); expect(f.state.selectedNodeIds).toEqual([f.ids[1]]); }); it('falls back to the behavior when the last node is toggled off, so the graph stays open', () => { f.state.select(nodeSelection(f.behavior.id, [f.ids[0]!])); f.state.toggleNode(f.behavior.id, f.ids[0]!); expect(f.state.selection).toEqual({ kind: 'asset', assetId: f.behavior.id }); }); it('accepts the short form a test hook passes, filling in the list', () => { f.state.select({ kind: 'node', assetId: f.behavior.id, nodeId: f.ids[0]! } as never); expect(f.state.selectedNodeIds).toEqual([f.ids[0]]); }); it('deletes the whole selection in one undo step', () => { f.state.select(nodeSelection(f.behavior.id, [f.ids[0]!, f.ids[1]!])); removeNodes(f.state, f.behavior.id, f.state.selectedNodeIds); const graph = () => (f.state.project.assets.find((a) => a.id === f.behavior.id) as BehaviorAsset).graph; expect(graph().nodes.map((n) => n.id)).toEqual([f.ids[2]]); f.state.undo(); expect(graph().nodes).toHaveLength(3); }); it('drops deleted nodes from the selection but keeps the survivors', () => { f.state.select(nodeSelection(f.behavior.id, f.ids)); removeNodes(f.state, f.behavior.id, [f.ids[0]!]); expect(f.state.selectedNodeIds).toEqual([f.ids[1], f.ids[2]]); }); });