Phase 3 of the refactor plan in docs/ARCHITECTURE.md — the foundation
that unblocks every safe refactor below.
Install
- yarn add -D vitest@3 jsdom @testing-library/dom @testing-library/react
@testing-library/jest-dom
Note: pinned to vitest@3 (not the latest 4.x) because yarn 1's peer
resolution breaks on vitest@4's peer link to vite. With vitest@3 the
existing Vite 8 install resolves cleanly.
Configuration
- vitest.config.mts (new): separate from vite.config.mjs because the
dev/build config wires up renderer SDK aliases that point at sibling
working trees (../renderer, ../Nitro_Render_V3). Tests are written
against pure modules that don't pull in the renderer, so the test
runner uses a smaller alias set.
- tests/setup.ts (new): imports @testing-library/jest-dom/vitest so
custom matchers (toBeInTheDocument, etc.) are available without
per-file imports.
- tsconfig.json: include "tests" so eslint stops complaining about
unparseable files; also makes the IDE see the test files.
- package.json scripts: "test" (one-shot) and "test:watch".
Tests
- tests/WiredCreatorTools.helpers.test.ts (18 cases): covers the pure
helpers extracted in 3c68d97 — createEmptyMonitorSnapshot,
formatMonitorLatestOccurrence (5 time-bucket branches),
formatMonitorHistoryOccurrence, formatVariableTimestamp,
formatMonitorSource (4 branches), normalizeMonitorReason. These are
the most boring-but-easy-to-break functions; locking them down first
is high value, near-zero risk.
- tests/navigatorRoomCreatorStore.test.ts (4 cases): exercises the
Zustand store added in the previous commit — initial state, latch
semantics, 5s auto-reset (with fake timers), and the
"second beginCreate restarts the lockout" invariant. Validates that
the store-based replacement of the let-singleton has the same
observable behavior, plus the new invariant that wasn't possible
before (timer composition under StrictMode double-mount).
Side effect: two non-test source files were converted to `import type`
to keep the test bundle from accidentally pulling in the renderer SDK
transitively:
- src/components/wired-tools/WiredCreatorTools.types.ts
(`import type { AvatarInfoFurni }`)
- src/components/wired-tools/WiredCreatorTools.helpers.ts
(`import type { HotelDateTimeParts, MonitorSnapshot }`)
This is harmless — TypeScript already treated them as type-only —
and improves tree-shaking on build as a side benefit.
Verification
- yarn test -> 2 files, 22 tests passing in ~1.0s.
- yarn eslint on tests/ + the two type-only-import files: 0 errors,
0 warnings.
Migration path
- Next adoption targets: cover useDoorbellState reducer (data hook
split), the new useNitroQuery adapter (timeout/cleanup behavior),
and the smaller pure formatters under src/api/.
- React component tests (via @testing-library/react) deferred until
there's a small mock layer for the renderer SDK. The
@testing-library/* deps are already installed so that PR is
unblocked.
https://claude.ai/code/session_01GrR87LAqnAEyKG2ZbmQt5Q
The single-file WiredCreatorToolsView.tsx was 4493 lines, which is one
of the main reasons the React Compiler reports
"Compilation Skipped: Existing memoization could not be preserved" on
this module. Split is conservative — only the pure leading sections
move out, the component itself is untouched (state, effects, JSX all
stay in place).
New files (sibling to the view):
- WiredCreatorTools.types.ts (~233 lines): every interface and type
alias declared at the top of the original file.
- WiredCreatorTools.constants.ts (~225 lines): TABS, MONITOR_LOG_ORDER,
poll constants, MONITOR_ERROR_INFO, INSPECTION_ELEMENTS,
VARIABLES_ELEMENTS, EDITABLE_*, VARIABLE_DEFINITIONS,
WIRED_FREEZE_EFFECT_IDS, TEAM_COLOR_NAMES, WEEKDAY/MONTH/DIRECTION
names. The createVariableDefinition factory is kept as a local helper
in this file (only used to build VARIABLE_DEFINITIONS).
- WiredCreatorTools.helpers.ts (~147 lines): createEmptyMonitorSnapshot,
getHotelTimeFormatter (with its module-private cache map),
getHotelDateTimeParts, formatMonitorLatestOccurrence,
formatMonitorHistoryOccurrence, formatVariableTimestamp,
formatMonitorSource, normalizeMonitorReason. All pure (or
cache-stable), no closure on component state.
WiredCreatorToolsView.tsx changes:
- 4493 -> 3901 lines (-592, ~13% reduction).
- The four inspection-icon asset imports (furni/global/user/context)
move to the constants file alongside the only consumers
(INSPECTION_ELEMENTS / VARIABLES_ELEMENTS).
- AvatarInfoFurni was only referenced by the extracted
InspectionFurniSelection interface and is removed from the main
file's api import.
- New import block at the top pulls back the symbols actually used by
the component body.
Verification:
- yarn eslint on the three new files: 0 errors / 0 warnings.
- yarn eslint on WiredCreatorToolsView.tsx: 26 errors before split,
26 errors after split (identical pre-existing set; nothing new
introduced).
- yarn tsc --noEmit on the four files: clean (only the project-wide
pre-existing TS2307 about @nitrots/nitro-renderer not being
installed locally remains, same as before).
This unblocks future per-tab splits (Monitor / Inspection / Variables
JSX panels are still inline in the view and represent the next ~1600
lines that could move out, but require introducing a shared state
context first since the current setState chain is intertwined).
https://claude.ai/code/session_01GrR87LAqnAEyKG2ZbmQt5Q