velxio/frontend/src/__tests__/piSlaveScanner.test.ts

156 lines
5.3 KiB
TypeScript

/**
* Tests for piSlaveScanner — verifies that canvas wires from a Pi
* board's protocol pins to a known I2C/SPI/UART component result in
* the right pi_attach_slave frames.
*/
import { describe, it, expect, vi } from 'vitest';
import { attachSlavesFromCanvas } from '../simulation/piSlaveScanner';
function makeBridge() {
return { attachSlave: vi.fn() };
}
const PI_ID = 'raspberry-pi-3';
describe('attachSlavesFromCanvas', () => {
it('attaches a BMP280 wired via SDA/SCL on bus 1', () => {
const bridge = makeBridge();
const components = [
{ id: 'bmp1', metadataId: 'wokwi-bmp280', properties: {} },
];
const wires = [
{ start: { componentId: PI_ID, pinName: '3' },
end: { componentId: 'bmp1', pinName: 'SDA' } },
{ start: { componentId: PI_ID, pinName: '5' },
end: { componentId: 'bmp1', pinName: 'SCL' } },
];
const emitted = attachSlavesFromCanvas(PI_ID, bridge, components, wires);
expect(emitted).toHaveLength(1);
expect(bridge.attachSlave).toHaveBeenCalledTimes(1);
expect(emitted[0]).toMatchObject({
bus_kind: 'i2c',
bus_num: 1,
address: 0x76,
model_id: 'bme280',
});
});
it('uses the address property when set', () => {
const bridge = makeBridge();
const components = [
{ id: 'bmp1', metadataId: 'wokwi-bmp280',
properties: { address: 0x77 } },
];
const wires = [
{ start: { componentId: PI_ID, pinName: '3' },
end: { componentId: 'bmp1', pinName: 'SDA' } },
];
attachSlavesFromCanvas(PI_ID, bridge, components, wires);
expect(bridge.attachSlave).toHaveBeenCalledWith(
expect.objectContaining({ address: 0x77 }),
);
});
it('skips unknown components silently', () => {
const bridge = makeBridge();
const components = [
{ id: 'led1', metadataId: 'wokwi-led', properties: {} },
];
const wires = [
{ start: { componentId: PI_ID, pinName: '3' },
end: { componentId: 'led1', pinName: 'A' } },
];
attachSlavesFromCanvas(PI_ID, bridge, components, wires);
expect(bridge.attachSlave).not.toHaveBeenCalled();
});
it('skips wires that do not touch the Pi', () => {
const bridge = makeBridge();
const components = [
{ id: 'bmp1', metadataId: 'wokwi-bmp280', properties: {} },
{ id: 'arduino', metadataId: 'wokwi-arduino-uno', properties: {} },
];
const wires = [
{ start: { componentId: 'arduino', pinName: 'A4' },
end: { componentId: 'bmp1', pinName: 'SDA' } },
];
attachSlavesFromCanvas(PI_ID, bridge, components, wires);
expect(bridge.attachSlave).not.toHaveBeenCalled();
});
it('dedupes when multiple wires hit the same slave', () => {
const bridge = makeBridge();
const components = [
{ id: 'bmp1', metadataId: 'wokwi-bmp280', properties: {} },
];
// SDA + SCL both wired — should still produce a single attach.
const wires = [
{ start: { componentId: PI_ID, pinName: '3' },
end: { componentId: 'bmp1', pinName: 'SDA' } },
{ start: { componentId: 'bmp1', pinName: 'SCL' },
end: { componentId: PI_ID, pinName: '5' } },
];
attachSlavesFromCanvas(PI_ID, bridge, components, wires);
expect(bridge.attachSlave).toHaveBeenCalledTimes(1);
});
it('forwards temperature/humidity/pressure props as config', () => {
const bridge = makeBridge();
const components = [
{ id: 'bmp1', metadataId: 'wokwi-bmp280',
properties: {
temperature_c: 21.5,
humidity_pct: 55,
pressure_pa: 99000,
color: 'red', // unrelated key, dropped
} },
];
const wires = [
{ start: { componentId: PI_ID, pinName: '3' },
end: { componentId: 'bmp1', pinName: 'SDA' } },
];
attachSlavesFromCanvas(PI_ID, bridge, components, wires);
expect(bridge.attachSlave).toHaveBeenCalledWith(
expect.objectContaining({
config: { temperature_c: 21.5, humidity_pct: 55, pressure_pa: 99000 },
}),
);
});
it('attaches SPI peer on CE0 when present', () => {
const bridge = makeBridge();
// Only CE0 wire is enough to identify the slave; MOSI/MISO/SCLK
// wires are informational. (Re-uses BMP280 mapping since we
// don't have a real SPI model in the registry yet — adapt this
// test when an actual SPI slave model is added.)
const components = [
{ id: 'flash', metadataId: 'wokwi-bmp280', properties: {} },
];
const wires = [
{ start: { componentId: PI_ID, pinName: '24' }, // CE0
end: { componentId: 'flash', pinName: 'CS' } },
];
attachSlavesFromCanvas(PI_ID, bridge, components, wires);
expect(bridge.attachSlave).toHaveBeenCalledTimes(1);
expect(bridge.attachSlave).toHaveBeenCalledWith(
expect.objectContaining({ bus_kind: 'spi', bus_num: 0, cs: 0 }),
);
});
it('does not attach SPI peer when only data lines (no CE) are wired', () => {
const bridge = makeBridge();
const components = [
{ id: 'flash', metadataId: 'wokwi-bmp280', properties: {} },
];
const wires = [
// MOSI only — no CE — should not attach.
{ start: { componentId: PI_ID, pinName: '19' },
end: { componentId: 'flash', pinName: 'MOSI' } },
];
attachSlavesFromCanvas(PI_ID, bridge, components, wires);
expect(bridge.attachSlave).not.toHaveBeenCalled();
});
});