Skip to content

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:

  1. Runtime: a "pipeline" test that runs the standalone ishow-validator script against sample_show.ishow, then loadShowFile(), then bindExecutor + fire() for each cue type. Catches schema drift between Studio output and Runtime expectation.
  2. 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.
  3. Studio: a test that loads sample .ishow files 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.