[Molecule-Platform-Evolvement-Manager]
Continues the #1815 coverage rollup. classNames.ts was at 17%
in the baseline; this PR brings it to full coverage.
16 cases across 3 helpers:
**appendClass (6):**
- undefined / empty existing → just `cls`
- single-class → "a b" join
- DEDUP: existing already contains `cls` → existing unchanged.
This is the load-bearing reason classNames.ts exists. Pre-helper
the call sites inlined `${existing} ${cls}` with no dedup, so a
tick that fired the same class twice produced "a a" and React
Flow's className-equality diff saw it as a change every render.
- whitespace normalization (multi-space, leading/trailing)
**removeClass (7):**
- undefined / empty existing → ""
- removes named class
- exact match only ("spawn" must NOT match "spawn-fast")
- removing the only class → ""
- no-op when class absent
- whitespace normalization
**scheduleNodeClassRemoval (3):**
- after delayMs: calls set() with className-removed on target node;
OTHER nodes untouched (the per-id pruning is the contract — pin
it so a future refactor that maps over all nodes doesn't silently
strip classes from siblings)
- does NOT fire before the delay elapses (vi.useFakeTimers + advance)
- SSR safety: when window is undefined, function is a no-op
(neither get nor set fires)
## Note on test environment
Added `// @vitest-environment jsdom` directive — the file's
default `node` environment leaves `window` undefined, which would
make the SSR-guard happy-path test pass for the wrong reason
(every test would short-circuit). With jsdom, the SSR test
explicitly stubs `window` to undefined to exercise the guard.
## Test plan
- [x] All 16 cases pass locally (~1.1s with jsdom env spin-up)
- [x] No SUT changes
- [ ] CI green
## #1815 progress
- [x] Step 1+2: instrumentation (#2147)
- [x] utils.ts + runtime-names.ts (#2148)
- [x] canvas-actions.ts (#2149)
- [x] store/classNames.ts (this PR)
- [ ] store/canvas.ts (73% — biggest absolute gap; bigger surface,
separate cycle)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>