mirror of
https://github.com/ApfelTeeSaft/WebMetal.git
synced 2026-08-26 19:43:24 +00:00
127 lines
3.8 KiB
TypeScript
127 lines
3.8 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import type { WmEdge, WmNode } from '../editor/nodeTypes'
|
|
import { portWidth, validateGraph } from './validateGraph'
|
|
|
|
let counter = 0
|
|
function node(
|
|
type: string,
|
|
name: string,
|
|
params: Record<string, string | number> = {},
|
|
): WmNode {
|
|
return {
|
|
id: `n${++counter}`,
|
|
type,
|
|
position: { x: 0, y: 0 },
|
|
data: { name, doc: '', params },
|
|
}
|
|
}
|
|
|
|
function edge(
|
|
source: WmNode,
|
|
sourceHandle: string,
|
|
target: WmNode,
|
|
targetHandle: string,
|
|
kind: 'data' | 'control' = 'data',
|
|
): WmEdge {
|
|
return {
|
|
id: `e${++counter}`,
|
|
source: source.id,
|
|
sourceHandle,
|
|
target: target.id,
|
|
targetHandle,
|
|
data: { kind },
|
|
}
|
|
}
|
|
|
|
/** Minimal graph that passes all error-level checks. */
|
|
function soundGraph() {
|
|
const pc = node('pc', 'PC1', { width: 16 })
|
|
const mem = node('memory', 'MAIN', { size: 256, width: 8 })
|
|
const nodes = [pc, mem]
|
|
const edges = [edge(pc, 'out', mem, 'addr')]
|
|
return { nodes, edges }
|
|
}
|
|
|
|
const errorsOf = (nodes: WmNode[], edges: WmEdge[]) =>
|
|
validateGraph(nodes, edges).filter((d) => d.severity === 'error')
|
|
|
|
describe('validateGraph', () => {
|
|
it('produces no errors for a sound minimal graph', () => {
|
|
const { nodes, edges } = soundGraph()
|
|
expect(errorsOf(nodes, edges)).toEqual([])
|
|
})
|
|
|
|
it('flags invalid and duplicate names as errors', () => {
|
|
const { nodes, edges } = soundGraph()
|
|
nodes.push(node('register', '2fast'), node('register', 'pc1'))
|
|
const messages = errorsOf(nodes, edges).map((d) => d.message)
|
|
expect(messages.join()).toContain('not a valid identifier')
|
|
expect(messages.join()).toContain('duplicate block name')
|
|
})
|
|
|
|
it('requires a PC and at least one memory', () => {
|
|
expect(
|
|
errorsOf([], [])
|
|
.map((d) => d.message)
|
|
.join(),
|
|
).toMatch(/Program Counter[\s\S]*Memory|Memory[\s\S]*Program Counter/)
|
|
})
|
|
|
|
it('rejects multiple PCs and multiple flags blocks', () => {
|
|
const { nodes, edges } = soundGraph()
|
|
nodes.push(
|
|
node('pc', 'PC2', { width: 16 }),
|
|
node('flags', 'FLAGS1', { flags: 'Z' }),
|
|
node('flags', 'FLAGS2', { flags: 'Z' }),
|
|
)
|
|
const messages = errorsOf(nodes, edges).map((d) => d.message)
|
|
expect(messages.join()).toContain('only one Program Counter')
|
|
expect(messages.join()).toContain('only one Flags block')
|
|
})
|
|
|
|
it('warns about unconnected inputs', () => {
|
|
const { nodes, edges } = soundGraph()
|
|
nodes.push(node('register', 'REG1', { width: 8 }))
|
|
const warnings = validateGraph(nodes, edges).filter(
|
|
(d) => d.severity === 'warning',
|
|
)
|
|
expect(warnings.map((d) => d.message).join()).toContain(
|
|
'"REG1": input "Data in" is not connected',
|
|
)
|
|
})
|
|
|
|
it('warns about data bus width mismatches', () => {
|
|
const { nodes, edges } = soundGraph()
|
|
const reg = node('register', 'REG1', { width: 8 })
|
|
const alu = node('alu', 'ALU1', { width: 16 })
|
|
nodes.push(reg, alu)
|
|
edges.push(edge(reg, 'out', alu, 'a'))
|
|
const warnings = validateGraph(nodes, edges).filter(
|
|
(d) => d.severity === 'warning',
|
|
)
|
|
expect(warnings.map((d) => d.message).join()).toContain(
|
|
'bus width mismatch',
|
|
)
|
|
})
|
|
|
|
it('ignores comment nodes entirely', () => {
|
|
const { nodes, edges } = soundGraph()
|
|
nodes.push(node('comment', 'not an identifier!!', { text: 'hi' }))
|
|
expect(errorsOf(nodes, edges)).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('portWidth', () => {
|
|
it('derives widths from params', () => {
|
|
const reg = node('register', 'R1', { width: 12 })
|
|
expect(portWidth(reg, 'in')).toBe(12)
|
|
expect(portWidth(reg, 'load')).toBeNull() // control port
|
|
const mem = node('memory', 'M1', { size: 256, width: 8 })
|
|
expect(portWidth(mem, 'addr')).toBe(8)
|
|
expect(portWidth(mem, 'dout')).toBe(8)
|
|
const flags = node('flags', 'F1', { flags: 'Z,N,C' })
|
|
expect(portWidth(flags, 'out')).toBe(3)
|
|
})
|
|
})
|