velxio/frontend/src/__tests__/spice-rectifier-integration...

157 lines
5.4 KiB
TypeScript
Raw Normal View History

/**
* Integration test for the Half-Wave Rectifier example.
*
* Exercises the full pipeline:
* storeAdapter picks `.tran` because `signal-generator` is non-DC
* NetlistBuilder emits SIN source + diode + load
* ngspice returns v(out) waveform with clipped negative half
* CircuitScheduler populates timeWaveforms in the result
*
* Bug this test guards: before the Phase 1-2 fix, `.op` evaluated SIN at t=0
* and returned 0 V, so `analogRead(A0)` read 0 in a loop the user symptom.
*/
import { describe, it, expect } from 'vitest';
import { buildInputFromStore } from '../simulation/spice/storeAdapter';
feat(sim): Phase 1c G+F3 — retire legacy CircuitScheduler / eecircuit-engine The mixed-mode migration's endgame. After this commit there is ONE SPICE solver path in the codebase — the vendored ngspice WASM via SolverPort, behind both NgSpiceWorkerAdapter (production browser) and NgSpiceNodeAdapter (Vitest Node). Zero hybrids; zero legacy left to maintain. Deleted production files: • simulation/spice/CircuitScheduler.ts (200ms-poll legacy) • simulation/spice/SpiceEngine.ts (eecircuit-engine wrap) • simulation/spice/SpiceEngine.lazy.ts (lazy code-split) • simulation/spice/subscribeToStore.ts (legacy solve loop) • simulation/spice/connectLegacySolverToMixedMode.ts (bridge) • simulation/spice/connectMixedModeSchedulerToStore.ts (feature flag) Deleted tests (no longer cover any live code): • connect-legacy-solver-to-mixed-mode.test.ts • connect-mixed-mode-scheduler-to-store.test.ts • spice-rectifier-live-bootstrap.test.ts Migrated 6 tests off the deleted `circuitScheduler.solveNow` API to the new `__tests__/helpers/solveInput.ts` (same shape, backed by NgSpiceNodeAdapter). `useElectricalStore` rewritten as a pure state container: • setSolveResult(snapshot) — atomic publish from the service • paused / setPaused — UI control unchanged • reset — project unload • REMOVED: triggerSolve, solveNow, setDebounceMs, scheduler hook • REMOVED: dependency on SpiceEngine.lazy preload EditorPage now mounts a single `startSimulation()` from `simulation/spice/start.ts`, which constructs CircuitSimulationService + ADC bridge + MCU edge bridge. Four useEffect calls collapsed to one. `circuitVerifier.ts` (production) and `runNetlist.ts` use an environment-aware factory: Web Worker in browser, in-proc WASM in Node tests. `/* @vite-ignore */` keeps the Node adapter chain (node:fs, node:url) out of the browser bundle while still letting Node resolve it dynamically. Removed `eecircuit-engine` from package.json dependencies. `collectPinStates` extracted to its own module so the service doesn't depend on the (now deleted) subscribeToStore.ts. Verification: • 1392/1392 tests pass across 103 files (28 pre-existing skips). • `tsc --noEmit` clean. • `vite build` succeeds (27 s, only the existing chunk-size warning that pre-dates this work). Phase 1c — COMPLETE. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 02:46:34 +07:00
import { solveInput } from './helpers/solveInput';
import { buildNetlist } from '../simulation/spice/NetlistBuilder';
function rectifierSnapshot() {
return {
components: [
{
id: 'sg1',
metadataId: 'signal-generator',
properties: { waveform: 'sine', frequency: 50, amplitude: 5, offset: 0 },
},
{ id: 'd1', metadataId: 'diode-1n4007', properties: {} },
{ id: 'rl', metadataId: 'resistor', properties: { value: '1000' } },
],
wires: [
{
id: 'w1',
start: { componentId: 'sg1', pinName: 'SIG' },
end: { componentId: 'd1', pinName: 'A' },
},
{
id: 'w2',
start: { componentId: 'd1', pinName: 'C' },
end: { componentId: 'rl', pinName: '1' },
},
{
id: 'w3',
start: { componentId: 'rl', pinName: '2' },
end: { componentId: 'arduino-uno', pinName: 'GND' },
},
{
id: 'w4',
start: { componentId: 'sg1', pinName: 'GND' },
end: { componentId: 'arduino-uno', pinName: 'GND' },
},
{
id: 'w5',
start: { componentId: 'd1', pinName: 'C' },
end: { componentId: 'arduino-uno', pinName: 'A0' },
},
],
boards: [
{
id: 'arduino-uno',
boardKind: 'arduino-uno' as const,
pinStates: {},
},
],
};
}
describe('half-wave rectifier — end-to-end pipeline', () => {
it('storeAdapter picks .tran and the netlist contains SIN + .tran cards', () => {
const input = buildInputFromStore(rectifierSnapshot());
expect(input.analysis.kind).toBe('tran');
const { netlist } = buildNetlist(input);
expect(netlist).toMatch(/SIN\(/);
expect(netlist).toMatch(/\.tran\s/);
});
it('CircuitScheduler returns a v(A0-net) waveform with positive peaks and clipped trough', async () => {
const input = buildInputFromStore(rectifierSnapshot());
feat(sim): Phase 1c G+F3 — retire legacy CircuitScheduler / eecircuit-engine The mixed-mode migration's endgame. After this commit there is ONE SPICE solver path in the codebase — the vendored ngspice WASM via SolverPort, behind both NgSpiceWorkerAdapter (production browser) and NgSpiceNodeAdapter (Vitest Node). Zero hybrids; zero legacy left to maintain. Deleted production files: • simulation/spice/CircuitScheduler.ts (200ms-poll legacy) • simulation/spice/SpiceEngine.ts (eecircuit-engine wrap) • simulation/spice/SpiceEngine.lazy.ts (lazy code-split) • simulation/spice/subscribeToStore.ts (legacy solve loop) • simulation/spice/connectLegacySolverToMixedMode.ts (bridge) • simulation/spice/connectMixedModeSchedulerToStore.ts (feature flag) Deleted tests (no longer cover any live code): • connect-legacy-solver-to-mixed-mode.test.ts • connect-mixed-mode-scheduler-to-store.test.ts • spice-rectifier-live-bootstrap.test.ts Migrated 6 tests off the deleted `circuitScheduler.solveNow` API to the new `__tests__/helpers/solveInput.ts` (same shape, backed by NgSpiceNodeAdapter). `useElectricalStore` rewritten as a pure state container: • setSolveResult(snapshot) — atomic publish from the service • paused / setPaused — UI control unchanged • reset — project unload • REMOVED: triggerSolve, solveNow, setDebounceMs, scheduler hook • REMOVED: dependency on SpiceEngine.lazy preload EditorPage now mounts a single `startSimulation()` from `simulation/spice/start.ts`, which constructs CircuitSimulationService + ADC bridge + MCU edge bridge. Four useEffect calls collapsed to one. `circuitVerifier.ts` (production) and `runNetlist.ts` use an environment-aware factory: Web Worker in browser, in-proc WASM in Node tests. `/* @vite-ignore */` keeps the Node adapter chain (node:fs, node:url) out of the browser bundle while still letting Node resolve it dynamically. Removed `eecircuit-engine` from package.json dependencies. `collectPinStates` extracted to its own module so the service doesn't depend on the (now deleted) subscribeToStore.ts. Verification: • 1392/1392 tests pass across 103 files (28 pre-existing skips). • `tsc --noEmit` clean. • `vite build` succeeds (27 s, only the existing chunk-size warning that pre-dates this work). Phase 1c — COMPLETE. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 02:46:34 +07:00
const result = await solveInput(input);
expect(result.converged).toBe(true);
expect(result.analysisMode).toBe('tran');
expect(result.timeWaveforms).toBeDefined();
const { timeWaveforms, pinNetMap } = result;
if (!timeWaveforms) return;
// The A0 pin maps to the diode cathode / resistor 1 net (through the
// Union-Find). Look it up through pinNetMap rather than guessing.
const a0Net = pinNetMap.get('arduino-uno:A0');
expect(a0Net).toBeDefined();
if (!a0Net) return;
const samples = timeWaveforms.nodes.get(a0Net);
expect(samples).toBeDefined();
if (!samples) return;
// Drop the first period so the RC/decoupling transient doesn't bias the
// statistics — we want steady-state behavior.
const periodSamples = Math.floor(samples.length / 4); // ≈ one 50 Hz period
const steady = samples.slice(periodSamples);
const vMax = Math.max(...steady);
const vMin = Math.min(...steady);
// A real 1N4007 drops ~0.7-0.9 V, so the peak is around 5 0.8 ≈ 4.2 V.
// SPICE's default D model may give slightly different Vf; allow a loose
// range but demand we see a real positive swing.
expect(vMax).toBeGreaterThan(3.0);
// Reverse bias: the diode blocks, so the load voltage stays at/near 0.
expect(vMin).toBeGreaterThan(-0.2);
// The rectified waveform must have *both* positive excursions and near-
// zero valleys within one steady-state window — otherwise we're back to
// the `.op` bug where everything sits at one DC value.
const highs = steady.filter((v) => v > 1.5).length;
const lows = steady.filter((v) => v < 0.2).length;
expect(highs).toBeGreaterThanOrEqual(5);
expect(lows).toBeGreaterThanOrEqual(5);
}, 30_000);
it('all-DC circuit → .op, no timeWaveforms (regression guard for the >20 DC examples)', async () => {
const input = buildInputFromStore({
components: [
{ id: 'r1', metadataId: 'resistor', properties: { value: '1000' } },
{ id: 'led', metadataId: 'led', properties: { color: 'red' } },
],
wires: [
{
id: 'w1',
start: { componentId: 'arduino-uno', pinName: '13' },
end: { componentId: 'r1', pinName: '1' },
},
{
id: 'w2',
start: { componentId: 'r1', pinName: '2' },
end: { componentId: 'led', pinName: 'A' },
},
{
id: 'w3',
start: { componentId: 'led', pinName: 'C' },
end: { componentId: 'arduino-uno', pinName: 'GND' },
},
],
boards: [
{
id: 'arduino-uno',
boardKind: 'arduino-uno' as const,
pinStates: { '13': { type: 'digital', v: 5 } },
},
],
});
expect(input.analysis.kind).toBe('op');
feat(sim): Phase 1c G+F3 — retire legacy CircuitScheduler / eecircuit-engine The mixed-mode migration's endgame. After this commit there is ONE SPICE solver path in the codebase — the vendored ngspice WASM via SolverPort, behind both NgSpiceWorkerAdapter (production browser) and NgSpiceNodeAdapter (Vitest Node). Zero hybrids; zero legacy left to maintain. Deleted production files: • simulation/spice/CircuitScheduler.ts (200ms-poll legacy) • simulation/spice/SpiceEngine.ts (eecircuit-engine wrap) • simulation/spice/SpiceEngine.lazy.ts (lazy code-split) • simulation/spice/subscribeToStore.ts (legacy solve loop) • simulation/spice/connectLegacySolverToMixedMode.ts (bridge) • simulation/spice/connectMixedModeSchedulerToStore.ts (feature flag) Deleted tests (no longer cover any live code): • connect-legacy-solver-to-mixed-mode.test.ts • connect-mixed-mode-scheduler-to-store.test.ts • spice-rectifier-live-bootstrap.test.ts Migrated 6 tests off the deleted `circuitScheduler.solveNow` API to the new `__tests__/helpers/solveInput.ts` (same shape, backed by NgSpiceNodeAdapter). `useElectricalStore` rewritten as a pure state container: • setSolveResult(snapshot) — atomic publish from the service • paused / setPaused — UI control unchanged • reset — project unload • REMOVED: triggerSolve, solveNow, setDebounceMs, scheduler hook • REMOVED: dependency on SpiceEngine.lazy preload EditorPage now mounts a single `startSimulation()` from `simulation/spice/start.ts`, which constructs CircuitSimulationService + ADC bridge + MCU edge bridge. Four useEffect calls collapsed to one. `circuitVerifier.ts` (production) and `runNetlist.ts` use an environment-aware factory: Web Worker in browser, in-proc WASM in Node tests. `/* @vite-ignore */` keeps the Node adapter chain (node:fs, node:url) out of the browser bundle while still letting Node resolve it dynamically. Removed `eecircuit-engine` from package.json dependencies. `collectPinStates` extracted to its own module so the service doesn't depend on the (now deleted) subscribeToStore.ts. Verification: • 1392/1392 tests pass across 103 files (28 pre-existing skips). • `tsc --noEmit` clean. • `vite build` succeeds (27 s, only the existing chunk-size warning that pre-dates this work). Phase 1c — COMPLETE. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 02:46:34 +07:00
const result = await solveInput(input);
expect(result.analysisMode).toBe('op');
expect(result.timeWaveforms).toBeUndefined();
}, 30_000);
});