Frontend test infrastructure
Shared helpers so tests don't re-roll the same boilerplate. Reuse these instead
of redefining a local makeNode, sonner mock, or provider wrapper per file.
factories.ts — fixture builders
import { makeNode, makeEdge, makeNodeData, makeDesign } from '@/test/factories'
makeNode('n1', { type: 'router', ip: '10.0.0.1' }) // React Flow node
makeNode({ data: makeNodeData({ label: 'X' }) }) // partial-node form
makeEdge('e1', 'n1', 'n2', { type: 'wifi' }) // React Flow edge
makeDesign({ name: 'Home' }) // Design row
Prefer these over an inline const makeNode = …. They track the domain types in
@/types, so a type change surfaces in one place.
mocks/ — reusable mock builders
Consume from an async vi.mock factory via dynamic import. vi.mock
factories are hoisted above the file's imports, so a plain top-level
import { mockSonner } referenced inside the factory throws "cannot access
before initialization". The dynamic import() resolves lazily and sidesteps it:
vi.mock('sonner', async () => (await import('@/test/mocks')).mockSonner())
vi.mock('@xyflow/react', async () => (await import('@/test/mocks')).mockReactFlow())
import { toast } from 'sonner'
// …assert vi.mocked(toast.success) etc.
mockSonner()— fulltoaststub (success/error/info/warning/…).mockReactFlow(extra?)— the@xyflow/reactexports most node/edge tests need; passextrato add or override an export.makeUseCanvasStore(state)— auseCanvasStorereplacement for component tests that read a selected slice.
render.tsx — provider-wrapped render
import { renderWithProviders, screen } from '@/test/render'
renderWithProviders(<NodeDetail … />) // wraps TooltipProvider + ReactFlowProvider
Use the plain render from @testing-library/react for presentational
components that need no context.
Conventions
- Store tests are split by concern under
stores/__tests__/canvasStore/(nodes, containers, sizing, edges, selection, grouping, history, clipboard, customStyle, floorMap). Add new store tests to the matching file; don't grow a monolith. - When a component test full-mocks a store (
vi.mock('@/stores/canvasStore')), still assert the state-driven UI branches (disabled buttons, indicators) — don't let the mock hide that wiring.