velxio/test/test_circuit/test/metadata_drift.test.js

79 lines
3.3 KiB
JavaScript
Raw Normal View History

feat: expand SPICE component catalog (fases 9 + 10) Adds 44 SPICE mappers, 58 custom metadata entries, and 12 visual Web Components covering logic gates, transistors, op-amps, regulators, sources, electromechanical parts and integrated-circuit packaging. Fase 9 — component catalog expansion ------------------------------------ - 7 logic gates (AND/OR/NAND/NOR/XOR/XNOR + NOT) as SPICE B-sources - 8 multi-input gates (AND/OR/NAND/NOR with 3 and 4 inputs) - 9 transistors: 5 BJTs (incl. PNP 2N3906/BC557) + 4 MOSFETs (incl. P-channel IRF9540/FQP27P06). NMOS refactored from Level=3 W=0.1 (hangs ngspice) to Level=1 with sane W/L - 5 op-amps: LM358, LM741, TL072, LM324 with per-chip saturation rails + opamp-ideal - 4 linear regulators (7805, 7812, 7905, LM317) with dropout - 3 batteries (9V, AA, coin-cell) with realistic ESR - Signal generator (sine / square / DC) - 2 Schottky diodes (1N5817, 1N5819) + photodiode (lux-driven current source) Fase 10 — electromechanical + ICs --------------------------------- - Relay (SPDT): coil + L + S-switch with native hysteresis + flyback diode, inverted-control trick for the NC contact - Optocouplers 4N25 and PC817 (LED + CCCS with CTR=0.5 / 1.0) - 7 74HC ICs as DIP-14 packages emitting 4 or 6 B-sources per component (first mapper pattern emitting multiple device cards) - 3 flip-flops (D, T, JK) — digital-sim only (edge detection is not representable in ngspice .op) - L293D dual H-bridge motor driver Infrastructure -------------- - scripts/component-overrides.json gains a _customComponents[] array that lets new Velxio-only parts survive metadata regeneration (previously applyOverrides() could only patch wokwi-elements components that had already been scanned) - scripts/generate-component-metadata.ts injects custom entries before the patch loop - New ComponentCategory values: 'logic', 'analog', 'electromech' - frontend/src/components/DynamicComponent.tsx PASSIVE tracing extended from just ['resistor','resistor-us'] to 9 two-terminal passives with per-part pin name maps - New CI workflow test-circuit.yml runs the sandbox on push/PR - frontend-tests.yml regenerates metadata and fails if committed JSON is stale - Documented 2 new ngspice gotchas in circuit-emulation-gotchas.md: unicode in netlist titles silently hangs the parser, and MOSFET Level=3 + W=0.1m causes .op to hang - 164/164 sandbox tests passing in ~9 s (was 88 pre-fase-9) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 06:44:18 +07:00
import { describe, it, expect } from 'vitest';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';
/**
* Metadata drift detector.
*
* Verifies that every component declared in `scripts/component-overrides.json`
* under `_customComponents[]` is also present in
* `frontend/public/components-metadata.json`.
*
* If this test fails, run `npm run generate:metadata` in the frontend folder
* (or whatever equivalent your build uses) to refresh the JSON. The committed
* metadata.json must always contain the latest custom components so that the
* picker works in production builds that skip the generator step.
*/
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../../..');
const OVERRIDES_PATH = resolve(ROOT, 'scripts/component-overrides.json');
const METADATA_PATH = resolve(ROOT, 'frontend/public/components-metadata.json');
function readJson(path) {
return JSON.parse(readFileSync(path, 'utf8'));
}
describe('component-overrides → components-metadata drift detector', () => {
const overrides = readJson(OVERRIDES_PATH);
const metadata = readJson(METADATA_PATH);
const custom = overrides._customComponents ?? [];
const metadataIds = new Set(metadata.components.map(c => c.id));
it('overrides file declares at least one custom component', () => {
expect(custom.length).toBeGreaterThan(0);
});
it('every _customComponents entry has the required fields', () => {
for (const c of custom) {
expect(c.id, `custom component missing 'id': ${JSON.stringify(c)}`).toBeTypeOf('string');
expect(c.tagName, `${c.id} missing 'tagName'`).toBeTypeOf('string');
expect(c.name, `${c.id} missing 'name'`).toBeTypeOf('string');
expect(c.category, `${c.id} missing 'category'`).toBeTypeOf('string');
expect(c.pinCount, `${c.id} missing 'pinCount'`).toBeTypeOf('number');
expect(Array.isArray(c.tags), `${c.id} tags must be an array`).toBe(true);
}
});
it('every _customComponents id is present in components-metadata.json', () => {
const missing = custom
.map(c => c.id)
.filter(id => !metadataIds.has(id));
expect(
missing,
`Stale metadata. Run 'npm run generate:metadata'. Missing: ${missing.join(', ')}`,
).toEqual([]);
});
it('every _customComponents entry is reflected faithfully in metadata', () => {
for (const c of custom) {
const meta = metadata.components.find(m => m.id === c.id);
expect(meta, `${c.id} missing in metadata`).toBeDefined();
expect(meta.tagName, `${c.id}: tagName drift`).toBe(c.tagName);
expect(meta.category, `${c.id}: category drift`).toBe(c.category);
expect(meta.pinCount, `${c.id}: pinCount drift`).toBe(c.pinCount);
}
});
// NOTE: tagName is intentionally NOT required to be unique — multiple value
// variants (e.g. cap-10p, cap-22p, cap-100n) share the same Web Component tag
// (`wokwi-capacitor`) but differ in id and default attribute values. The id
// uniqueness check below is the actual collision guard.
feat: expand SPICE component catalog (fases 9 + 10) Adds 44 SPICE mappers, 58 custom metadata entries, and 12 visual Web Components covering logic gates, transistors, op-amps, regulators, sources, electromechanical parts and integrated-circuit packaging. Fase 9 — component catalog expansion ------------------------------------ - 7 logic gates (AND/OR/NAND/NOR/XOR/XNOR + NOT) as SPICE B-sources - 8 multi-input gates (AND/OR/NAND/NOR with 3 and 4 inputs) - 9 transistors: 5 BJTs (incl. PNP 2N3906/BC557) + 4 MOSFETs (incl. P-channel IRF9540/FQP27P06). NMOS refactored from Level=3 W=0.1 (hangs ngspice) to Level=1 with sane W/L - 5 op-amps: LM358, LM741, TL072, LM324 with per-chip saturation rails + opamp-ideal - 4 linear regulators (7805, 7812, 7905, LM317) with dropout - 3 batteries (9V, AA, coin-cell) with realistic ESR - Signal generator (sine / square / DC) - 2 Schottky diodes (1N5817, 1N5819) + photodiode (lux-driven current source) Fase 10 — electromechanical + ICs --------------------------------- - Relay (SPDT): coil + L + S-switch with native hysteresis + flyback diode, inverted-control trick for the NC contact - Optocouplers 4N25 and PC817 (LED + CCCS with CTR=0.5 / 1.0) - 7 74HC ICs as DIP-14 packages emitting 4 or 6 B-sources per component (first mapper pattern emitting multiple device cards) - 3 flip-flops (D, T, JK) — digital-sim only (edge detection is not representable in ngspice .op) - L293D dual H-bridge motor driver Infrastructure -------------- - scripts/component-overrides.json gains a _customComponents[] array that lets new Velxio-only parts survive metadata regeneration (previously applyOverrides() could only patch wokwi-elements components that had already been scanned) - scripts/generate-component-metadata.ts injects custom entries before the patch loop - New ComponentCategory values: 'logic', 'analog', 'electromech' - frontend/src/components/DynamicComponent.tsx PASSIVE tracing extended from just ['resistor','resistor-us'] to 9 two-terminal passives with per-part pin name maps - New CI workflow test-circuit.yml runs the sandbox on push/PR - frontend-tests.yml regenerates metadata and fails if committed JSON is stale - Documented 2 new ngspice gotchas in circuit-emulation-gotchas.md: unicode in netlist titles silently hangs the parser, and MOSFET Level=3 + W=0.1m causes .op to hang - 164/164 sandbox tests passing in ~9 s (was 88 pre-fase-9) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 06:44:18 +07:00
it('all _customComponents ids are unique', () => {
const ids = custom.map(c => c.id);
const dupes = ids.filter((t, i) => ids.indexOf(t) !== i);
expect(dupes, `Duplicate ids: ${dupes.join(', ')}`).toEqual([]);
});
});