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);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-22 03:31:51 +07:00
|
|
|
// 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([]);
|
|
|
|
|
});
|
|
|
|
|
});
|