Files
Nitro-V3/tests/navigatorRoomCreatorStore.test.ts
T
simoleo89 6793de2106 Set up Vitest + 22 smoke tests on pure modules (proposal #6)
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
2026-05-11 16:31:53 +00:00

57 lines
1.8 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { useRoomCreatorStore } from '../src/components/navigator/views/navigatorRoomCreatorStore';
describe('useRoomCreatorStore', () =>
{
beforeEach(() =>
{
vi.useFakeTimers();
useRoomCreatorStore.setState({ isCreating: false });
});
afterEach(() =>
{
vi.useRealTimers();
});
it('starts with isCreating === false', () =>
{
expect(useRoomCreatorStore.getState().isCreating).toBe(false);
});
it('beginCreate() latches isCreating to true', () =>
{
useRoomCreatorStore.getState().beginCreate();
expect(useRoomCreatorStore.getState().isCreating).toBe(true);
});
it('isCreating auto-resets to false after the 5s lockout', () =>
{
useRoomCreatorStore.getState().beginCreate();
expect(useRoomCreatorStore.getState().isCreating).toBe(true);
vi.advanceTimersByTime(4999);
expect(useRoomCreatorStore.getState().isCreating).toBe(true);
vi.advanceTimersByTime(1);
expect(useRoomCreatorStore.getState().isCreating).toBe(false);
});
it('a second beginCreate() resets the lockout timer (no double-fire)', () =>
{
useRoomCreatorStore.getState().beginCreate();
vi.advanceTimersByTime(4000);
// Re-entry restarts the 5s window
useRoomCreatorStore.getState().beginCreate();
// At t=4500 (500ms past the second call), we should still be locked
vi.advanceTimersByTime(500);
expect(useRoomCreatorStore.getState().isCreating).toBe(true);
// Only after another 4500ms (total 5000 since the second call)
vi.advanceTimersByTime(4500);
expect(useRoomCreatorStore.getState().isCreating).toBe(false);
});
});