velxio/frontend/src/__tests__/ammeter-waveform.test.ts

138 lines
4.7 KiB
TypeScript
Raw Normal View History

/**
* End-to-end test for the `readAmmeter(..., timeWaveforms)` AC path.
*
* Sine source (5 Vpk, 50 Hz) drives a 1 resistor through an inline
* ammeter. Expected RMS current (5/2) / 1000 3.54 mA, peak 5 mA,
* DC 0. The ammeter's SPICE model injects a 0 V sense source named
* `v_<ammeterId>_sense`; `timeWaveforms.branches` reports the current
* through that source sample-by-sample.
*/
import { describe, it, expect } from 'vitest';
import { readAmmeter } from '../simulation/spice/probes';
import type { ComponentForSpice } from '../simulation/spice/types';
import type { Wire } from '../types/wire';
describe('readAmmeter with .tran timeWaveforms', () => {
it(
'reports RMS ≈ Vpk/(√2·R), peak ≈ Vpk/R, DC ≈ 0 for a centered sine',
{ timeout: 30_000 },
async () => {
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 { solveInput } = await import('./helpers/solveInput');
const components: ComponentForSpice[] = [
{
id: 'sg1',
metadataId: 'signal-generator',
properties: { waveform: 'sine', frequency: 50, amplitude: 5, offset: 0 },
},
{ id: 'r1', metadataId: 'resistor', properties: { value: '1k' } },
{ id: 'am', metadataId: 'instr-ammeter', properties: {} },
];
// SIG → R → A+ [ammeter] A- → GND
const wires: Wire[] = [
{
id: 'w1',
start: { componentId: 'sg1', pinName: 'SIG' },
end: { componentId: 'r1', pinName: '1' },
} as Wire,
{
id: 'w2',
start: { componentId: 'r1', pinName: '2' },
end: { componentId: 'am', pinName: 'A+' },
} as Wire,
{
id: 'w3',
start: { componentId: 'am', pinName: 'A-' },
end: { componentId: 'sg1', pinName: 'GND' },
} as Wire,
];
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 solve = await solveInput({
components,
wires: wires.map((w) => ({ id: w.id, start: w.start, end: w.end })),
boards: [],
analysis: { kind: 'tran', step: '1e-3', stop: '8e-2' },
});
expect(solve.analysisMode).toBe('tran');
expect(solve.timeWaveforms).toBeDefined();
const am = components.find((c) => c.id === 'am')!;
const reading = readAmmeter(am, solve, solve.timeWaveforms);
expect(reading.kind).toBe('ammeter');
expect(reading.stale).toBe(false);
expect(reading.ac).toBeDefined();
if (!reading.ac) return;
// 5 V peak across 1 kΩ → 5 mA peak, 5/√2 mA RMS. Allow ±15 %.
const idealRmsA = 5 / Math.sqrt(2) / 1000;
const idealPeakA = 5 / 1000;
expect(reading.ac.rms).toBeGreaterThan(idealRmsA * 0.85);
expect(reading.ac.rms).toBeLessThan(idealRmsA * 1.15);
expect(reading.ac.peak).toBeGreaterThan(idealPeakA * 0.9);
expect(reading.ac.peak).toBeLessThan(idealPeakA * 1.1);
expect(Math.abs(reading.ac.dc)).toBeLessThan(idealRmsA * 0.25);
expect(reading.ac.rmsDisplay).toMatch(/^RMS /);
expect(reading.ac.peakDisplay).toMatch(/^pk /);
expect(reading.ac.dcDisplay).toMatch(/^DC /);
// mA formatting (0.001 ≤ |i| < 1).
expect(reading.ac.rmsDisplay).toMatch(/mA/);
expect(reading.display).toBe(reading.ac.rmsDisplay);
},
);
it('leaves `ac` undefined for a pure DC circuit (.op path)', { timeout: 30_000 }, async () => {
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 { solveInput } = await import('./helpers/solveInput');
const components: ComponentForSpice[] = [
{ id: 'r1', metadataId: 'resistor', properties: { value: '220' } },
{ id: 'am', metadataId: 'instr-ammeter', properties: {} },
];
const wires: Wire[] = [
{
id: 'w1',
start: { componentId: 'uno', pinName: '5V' },
end: { componentId: 'r1', pinName: '1' },
} as Wire,
{
id: 'w2',
start: { componentId: 'r1', pinName: '2' },
end: { componentId: 'am', pinName: 'A+' },
} as Wire,
{
id: 'w3',
start: { componentId: 'am', pinName: 'A-' },
end: { componentId: 'uno', pinName: 'GND' },
} as Wire,
];
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 solve = await solveInput({
components,
wires: wires.map((w) => ({ id: w.id, start: w.start, end: w.end })),
boards: [
{
id: 'uno',
vcc: 5,
pins: {},
groundPinNames: ['GND'],
vccPinNames: ['5V'],
},
],
analysis: { kind: 'op' },
});
expect(solve.analysisMode).toBe('op');
expect(solve.timeWaveforms).toBeUndefined();
const am = components.find((c) => c.id === 'am')!;
const reading = readAmmeter(am, solve, solve.timeWaveforms);
expect(reading.ac).toBeUndefined();
// 5V / 220Ω = 22.7 mA.
expect(Math.abs(reading.value)).toBeCloseTo(22.7, 0);
expect(reading.display).toMatch(/mA/);
});
});