Test Strategy¶
What's tested, what isn't, and why.
This isn't aspirational — it documents what actually exists today (v0.1.0) so future developers know where the gaps are.
Test totals at v0.1.0¶
| Product | Test files | Pass count | Coverage focus |
|---|---|---|---|
| Studio | 13 | 790 | Logic, rendering, schema, drag/drop, validators, undo, transport |
| Runtime | 1 | 55 | Validator, loader, executor, HTTP API, dashboard serving |
| Node | 1 | 29 | Config, GPIO fallback, media path security, handler routing |
| Total | 15 | 874 |
Run all tests:
# Studio
cd immerse-designer && for f in *_test.js; do node $f; done
# Runtime
cd immerse-runtime && node test/run-all.js
# Node
cd immerse-node && node test/run-all.js
Test philosophy¶
Three principles guide what we test:
1. Test the business logic, not the framework¶
We test:
- Cue firing branches by type
- Validator catching real broken references
- Schema migration from old .ishow versions
- The cue pointer (next/back/wrap)
- Path traversal rejection
- Logic graph walk
We don't test:
- That Object.keys() works
- That setTimeout fires
- That HTTP servers can listen on a port
2. Use real code paths, fake the I/O¶
Tests run the actual validate(), bindExecutor(), loadConfig() functions from production code. Only the boundaries — DMX socket, GPIO pins, HTTP responses — are stubbed.
This means a test passing is decent evidence the production code path works. It's not great evidence the integration with real hardware works. That's covered by smoke tests.
3. No test infrastructure¶
Plain Node.js scripts. No Jest, no Mocha, no Vitest. A simple ok(condition, msg) function that increments a pass/fail counter.
Why? Three reasons: - Adds zero deps to the project - Tests run in <2 seconds - Future developers can copy-paste a test file as a template; no learning curve
What's covered¶
Studio (immerse-designer/)¶
| File | Tests | Covers |
|---|---|---|
render_test.js |
19 | Module HTML rendering, key panels, no fake content |
device_test.js |
36 | Device library, instances, patches, port resolution |
gpio_dir_test.js |
50 | GPIO direction inference from device patches |
looks_test.js |
106 | Lighting look authoring, keyframe interpolation, validator integration |
transport_test.js |
31 | Timecode start/stop/goto, frame counter |
shapes_test.js |
77 | Room canvas shapes (rect, circle, polygon) |
undo_test.js |
53 | 50-deep undo ring, debouncing, redo invalidation |
wires_test.js |
33 | Logic wire add/remove/validate |
cuelists_test.js |
42 | Multiple cue lists per room, schema migration |
timeline_test.js |
71 | Track add/remove, clip move/resize, drag-on-grid-to-draw |
cuelib_test.js |
154 | All 10 cue types: defaults, inspectors, fire branches, round-trip |
dragdrop_test.js |
67 | Library tile → timeline grid + cue list drops, auto-track creation |
v1polish_test.js |
51 | Validator (real checks), cueGo sequence advance, timeline crossing detection, help/about overlays |
Runtime (immerse-runtime/)¶
test/run-all.js, 55 tests, sections:
- validator — clean show, broken refs, all 8 logic kinds accepted, unknown rejected
- loader — refuses unsupported schema, refuses validation errors, accepts clean shows
- cue pointer — initial, advance, wrap, retreat, reset
- executor — DMX cue applies look, trigger cue walks logic, TC cue starts transport, unknown cue → not ok, go() advances
- dmx interpolation — linear math, hold-last-value, partial keyframes
- status — all snapshot fields populated
- http api + dashboard — static file serving (HTML/CSS/JS), /status, /cuelist, all POST endpoints, 4xx/5xx error paths, path traversal blocked
Node (immerse-node/)¶
test/run-all.js, 29 tests, sections:
- config — missing file, bad JSON, missing nodeId, bad role, defaults applied per role, channel overrides
- gpio fallback — set/pulse/clear functions exposed even without onoff
- media path security — absolute path rejected, ../ rejected, missing file 404, empty filename 400
- http routing — health endpoint, gpio pulse, unknown action 400, role rejection (gpio→audio = 404, audio→gpio = 404), audio play missing file = 400, audio status on idle
What's NOT covered (deliberate gaps)¶
These are explicitly not tested by the automated suite, because they require real hardware or live network. Smoke tests in docs/SMOKE_TEST.md files document how to verify each manually.
DMX over real network¶
The dmx.js module is tested for interpolation math (pure function), not for actual sACN packet emission. Verification: run Runtime with a real .ishow, watch a fixture or sACNView receive packets.
GPIO toggling real pins¶
GPIO library isn't loaded in CI. Tests cover the API surface and fallback path. Verification is performed on real Node hardware before each release.
Audio/video playback¶
mpv subprocess is started in production but never spawned in tests. Verification: drop a WAV in the Node's media directory, POST to /audio/play, hear it.
End-to-end pipeline¶
There's no test that does "Studio saves → Runtime loads → Node receives HTTP → real hardware fires." The components are tested in isolation. End-to-end is verified manually before each release per the smoke test docs.
Long-running stability¶
No test runs Runtime for hours, no memory-leak test, no thermal stress on a Node. Production verification is the 24-hour run from immerse-node/docs/SMOKE_TEST.md.
Network failure modes¶
What happens if Runtime loses network mid-show? Today: untested. Production behavior: sACN stops, GPIO calls time out, dashboard hangs. Reconnect logic isn't tested because it isn't implemented.
Concurrent operators¶
What if two tablets press GO at the same millisecond? Today: untested. Node.js's single-threaded event loop serializes them, but the visible result depends on order. If a customer reports a race, write the test then.
Visual regression¶
No screenshot tests for Studio's UI or Runtime's dashboard. Visual changes are caught by "looks wrong when I run it" only.
When to add a test¶
Add a test when:
1. A bug is fixed. The test demonstrates the bug pre-fix and prevents regression. The schema-drift test added in v1polish_test.js after the condition logic kind bug is the right pattern.
2. A new module is added. Cover its public API, not implementation details.
3. A subtle invariant matters. Cue pointer wrap-around, path traversal, look interpolation — these are non-obvious; tests serve as docs.
Don't add a test for: - "Just to bump coverage" - Boilerplate getters - Tests that test the test framework
Running tests in CI¶
There's no CI yet. When you set one up:
# .github/workflows/test.yml or equivalent
- run: cd immerse-designer && for f in *_test.js; do node $f || exit 1; done
- run: cd immerse-runtime && node test/run-all.js
- run: cd immerse-node && node test/run-all.js
Total runtime: ~10 seconds across all three suites.
Don't add Electron-headless tests for Studio's GUI in CI — they're flaky and slow. Studio's GUI is verified by humans.
Test debt to fix¶
Pragmatic candidates if someone has a free afternoon:
- Runtime: a "pipeline" test that runs the standalone
ishow-validatorscript againstsample_show.ishow, thenloadShowFile(), thenbindExecutor+fire()for each cue type. Catches schema drift between Studio output and Runtime expectation. - Node: integration test that boots the HTTP server and curls it. Most of the routing already gets tested directly through
makeHandlers, but a real network round-trip would catch stuff like trailing-slash bugs or content-type mismatches. - Studio: a test that loads sample
.ishowfiles from previous schema versions and verifies they migrate forward correctly. Migration logic exists but only the migration we built when introducing 0.5.0 is exercised.