import { describe, expect, it } from 'vitest' import type { WmEdge, WmNode } from '../editor/nodeTypes' import { compileGraph } from './compileGraph' let counter = 0 function node( type: string, name: string, params: Record = {}, ): WmNode { return { id: `n${++counter}`, type, position: { x: 0, y: 0 }, data: { name, doc: '', params }, } } function pcToMemEdge(pc: WmNode, mem: WmNode): WmEdge { return { id: `e${++counter}`, source: pc.id, sourceHandle: 'out', target: mem.id, targetHandle: 'addr', data: { kind: 'data' }, } } function fullGraph() { const pc = node('pc', 'PC1', { width: 12 }) const mem = node('memory', 'MAIN', { size: 4096, width: 8 }) const acc = node('register', 'ACC', { width: 8 }) const rf = node('registerFile', 'R', { count: 4, width: 8 }) const flags = node('flags', 'FLAGS1', { flags: 'Z, N, C' }) const alu = node('alu', 'ALU1', { width: 8 }) const note = node('comment', 'a note!', { text: 'hello' }) return { nodes: [pc, mem, acc, rf, flags, alu, note], edges: [pcToMemEdge(pc, mem)], } } describe('compileGraph', () => { it('compiles a full graph into a valid machine model', () => { const { nodes, edges } = fullGraph() const { model, diagnostics } = compileGraph(nodes, edges) expect(diagnostics.filter((d) => d.severity === 'error')).toEqual([]) expect(model).not.toBeNull() if (!model) return expect(model.banks).toEqual([ { name: 'ACC', width: 8, count: 1, sourceNodeId: expect.any(String) }, { name: 'R', width: 8, count: 4, sourceNodeId: expect.any(String) }, ]) expect(model.memories).toEqual([ { name: 'MAIN', size: 4096, width: 8, sourceNodeId: expect.any(String), }, ]) expect(model.flags).toEqual(['Z', 'N', 'C']) expect(model.pc).toEqual({ width: 12 }) expect(model.programMemory).toBe('MAIN') }) it('is deterministic regardless of node order', () => { const { nodes, edges } = fullGraph() const forward = compileGraph(nodes, edges).model const backward = compileGraph([...nodes].reverse(), edges).model // sourceNodeIds differ per construction run, so compare shapes without them const strip = (m: typeof forward) => JSON.parse( JSON.stringify(m, (k, v: unknown) => k === 'sourceNodeId' ? undefined : v, ), ) as unknown expect(strip(backward)).toEqual(strip(forward)) }) it('returns no model when validation errors exist', () => { const { model, diagnostics } = compileGraph([], []) expect(model).toBeNull() expect(diagnostics.some((d) => d.severity === 'error')).toBe(true) }) it('resolves the program memory via the PC wire when there are several memories', () => { const { nodes, edges } = fullGraph() nodes.push(node('memory', 'DATA', { size: 256, width: 8 })) const { model } = compileGraph(nodes, edges) expect(model?.programMemory).toBe('MAIN') }) it('errors when the program memory is ambiguous', () => { const { nodes } = fullGraph() nodes.push(node('memory', 'DATA', { size: 256, width: 8 })) const { model, diagnostics } = compileGraph(nodes, []) // no PC wire expect(model).toBeNull() expect(diagnostics.map((d) => d.message).join()).toContain( 'cannot determine the program memory', ) }) it('rejects bad flag names', () => { const { nodes, edges } = fullGraph() const flags = nodes.find((n) => n.type === 'flags') if (flags) flags.data.params = { flags: 'Z,9bad,Z' } const { model, diagnostics } = compileGraph(nodes, edges) expect(model).toBeNull() const text = diagnostics.map((d) => d.message).join() expect(text).toContain('not a valid identifier') expect(text).toContain('duplicate flag name') }) it('clamps out-of-range params into the schema range', () => { const { nodes, edges } = fullGraph() const acc = nodes.find((n) => n.data.name === 'ACC') if (acc) acc.data.params = { width: 99 } const { model } = compileGraph(nodes, edges) expect(model?.banks.find((b) => b.name === 'ACC')?.width).toBe(32) }) })