velxio/frontend/src/__tests__/breadboard-nets.test.ts

125 lines
4.8 KiB
TypeScript

/**
* Breadboard internal-connectivity tests.
*
* A breadboard joins holes in fixed groups (5-hole column strips, power
* rails). The joining is modelled at the union-find level in NetlistBuilder
* (see unionBreadboardGroups) and keyed by utils/breadboardNets.ts — these
* tests pin the group semantics and the resulting nets end-to-end through
* buildNetlist / buildWireNetMap.
*/
import { describe, it, expect } from 'vitest';
import { breadboardGroupKey, isBreadboard } from '../utils/breadboardNets';
import { buildNetlist, buildWireNetMap } from '../simulation/spice/NetlistBuilder';
describe('breadboardGroupKey', () => {
it('groups the 5 holes of a column strip', () => {
const keys = ['a', 'b', 'c', 'd', 'e'].map((r) => breadboardGroupKey('breadboard', `18t.${r}`));
expect(new Set(keys).size).toBe(1);
expect(keys[0]).toBe('col18t');
});
it('separates top and bottom banks of the same column', () => {
expect(breadboardGroupKey('breadboard', '18t.a')).not.toBe(
breadboardGroupKey('breadboard', '18b.f'),
);
});
it('separates different columns', () => {
expect(breadboardGroupKey('breadboard', '17t.a')).not.toBe(
breadboardGroupKey('breadboard', '18t.a'),
);
});
it('groups a whole power rail as one net', () => {
expect(breadboardGroupKey('breadboard', 'bn.1')).toBe('railbn');
expect(breadboardGroupKey('breadboard', 'bn.50')).toBe('railbn');
expect(breadboardGroupKey('breadboard', 'bp.3')).toBe('railbp');
expect(breadboardGroupKey('breadboard', 'tp.9')).toBe('railtp');
});
it('works for the mini breadboard and rejects non-breadboards', () => {
expect(breadboardGroupKey('breadboard-mini', '5t.c')).toBe('col5t');
expect(breadboardGroupKey('resistor', '1')).toBeNull();
expect(breadboardGroupKey('breadboard', 'not-a-hole')).toBeNull();
expect(isBreadboard('breadboard')).toBe(true);
expect(isBreadboard('breadboard-mini')).toBe(true);
expect(isBreadboard('pushbutton')).toBe(false);
});
});
describe('breadboard nets in NetlistBuilder', () => {
const BOARD = {
id: 'arduino',
vcc: 5,
pins: { '5V': { type: 'digital', v: 5 }, GND: { type: 'digital', v: 0 } },
groundPinNames: ['GND'],
vccPinNames: ['5V'],
};
it('buildWireNetMap: two wires into the same column strip share a net', () => {
const map = buildWireNetMap({
components: [
{ id: 'bb1', metadataId: 'breadboard', properties: {} },
{ id: 'r1', metadataId: 'resistor', properties: { value: '1k' } },
],
wires: [
{
id: 'w-in',
start: { componentId: 'arduino', pinName: '5V' },
end: { componentId: 'bb1', pinName: '10t.a' },
},
{
id: 'w-out',
start: { componentId: 'bb1', pinName: '10t.e' },
end: { componentId: 'r1', pinName: '1' },
},
],
boards: [BOARD],
} as never);
expect(map.get('w-in')).toBeDefined();
expect(map.get('w-in')).toBe(map.get('w-out'));
});
it('buildWireNetMap: different columns stay separate nets', () => {
const map = buildWireNetMap({
components: [{ id: 'bb1', metadataId: 'breadboard', properties: {} }],
wires: [
{
id: 'w-a',
start: { componentId: 'arduino', pinName: '5V' },
end: { componentId: 'bb1', pinName: '10t.a' },
},
{
id: 'w-b',
start: { componentId: 'bb1', pinName: '11t.a' },
end: { componentId: 'arduino', pinName: 'GND' },
},
],
boards: [BOARD],
} as never);
expect(map.get('w-a')).not.toBe(map.get('w-b'));
});
it('buildNetlist: a resistor fed through breadboard strips + rail resolves to vcc/gnd nets', () => {
// 5V → rail tp.1 | rail tp.30 → column 5t.a | 5t.c → R1.1 | R1.2 → 20b.f | 20b.j → GND
const { netlist } = buildNetlist({
components: [
{ id: 'bb1', metadataId: 'breadboard', properties: {} },
{ id: 'r1', metadataId: 'resistor', properties: { value: '1k' } },
],
wires: [
{ id: 'w1', start: { componentId: 'arduino', pinName: '5V' }, end: { componentId: 'bb1', pinName: 'tp.1' } },
{ id: 'w2', start: { componentId: 'bb1', pinName: 'tp.30' }, end: { componentId: 'bb1', pinName: '5t.a' } },
{ id: 'w3', start: { componentId: 'bb1', pinName: '5t.c' }, end: { componentId: 'r1', pinName: '1' } },
{ id: 'w4', start: { componentId: 'r1', pinName: '2' }, end: { componentId: 'bb1', pinName: '20b.f' } },
{ id: 'w5', start: { componentId: 'bb1', pinName: '20b.j' }, end: { componentId: 'arduino', pinName: 'GND' } },
],
boards: [BOARD],
analysis: { kind: 'op' },
} as never);
// The resistor's card must connect vcc_rail directly to ground — every
// breadboard hop collapsed into the two canonical nets.
expect(netlist).toMatch(/R_r1 (vcc_rail 0|0 vcc_rail) 1000/);
});
});