frontend-testing · git:20260916.b334104 · 2026-09-16 · sha256 9ffac160afed856d
frontend-testing git:20260916.b334104A
Immutable. This exact content is served forever at /api/v1/blob/9ffac160afed856d.
---
name: frontend-testing
description: Frontend Vitest browser-mode tests, Testing Library queries, mockSdkService, makeMe builders, deterministic Vue component testing. Use when writing or changing frontend Vitest tests.
paths:
- "frontend/tests/**/*.ts"
- "frontend/src/**/*.spec.ts"
---
# Frontend Testing Rules
**Style ("small test" practice — stable boundary, data over mocks, focused assertions, concise makeMe):** the `unit-testing` skill — follow that first.
## Test Commands
From the repo root:
```bash
CURSOR_DEV=true nix develop -c pnpm frontend:test
```
Run tests with browser rendering UI:
```bash
CURSOR_DEV=true nix develop -c pnpm frontend:test:ui
```
Run tests in browser-rendered watch mode:
```bash
CURSOR_DEV=true nix develop -c pnpm frontend:test:watch
```
Run a single test file from the repo root:
```bash
CURSOR_DEV=true nix develop -c pnpm frontend:test tests/path/to/TestFile.spec.ts
```
Or only the frontend package:
```bash
CURSOR_DEV=true nix develop -c pnpm -C frontend test tests/path/to/TestFile.spec.ts
```
Run a specific test case:
```bash
CURSOR_DEV=true nix develop -c pnpm -C frontend test -t "test name pattern"
```
The test file path is relative to `frontend/`. Do not use `pnpm ... test -- tests/...`; the `--` is forwarded to Vitest and file filtering is skipped.
In most situations, run all unit tests instead of a selected file only. Use a single test file when actively debugging or iterating on a specific component.
For `dough-test-optimization`, `frontend:test` remains the ordinary local
feedback measurement because it runs Chromium browser mode. When per-test JSON
durations are needed for investigation, use
`CURSOR_DEV=true nix develop -c pnpm -C frontend exec vitest run --reporter=json`.
That plain Vitest run is profiling evidence only; verify retained changes with
the normal browser-mode command and do not compare its wall time directly with
`frontend:test`.
## Component Behavior
- Drive the mounted component / page ("small test" style per `unit-testing` skill); cover lower layers with realistic `makeMe` props/state, not by testing internal helpers in isolation.
- Test through user interactions; assert observable DOM outcomes.
- Use `data-testid` for test selectors.
- Use Vitest browser mode and prefer real browser rendering over mocking sibling components or internal modules; stop using jsdom.
## Routing assertions
Navigation assertions use named locations (or helpers that return them).
Rendered-href assertions use `noteShowHref`. Path strings belong only in
`routes.spec.ts` (matching / redirects) and inbound URL classifiers. Test
routers that resolve named screen locations use production `routes` or
`dummyRouteRecordsFromMetadata` (the `routeMetadata` table with dummy
components; no page imports). Catch-all `/` or `/:pathMatch(.*)*` routers
and `useRoute` stubs with `path: "/"` are not a second screen dialect
(ADR 0005).
## Avoid Role Queries
- Do not use `getByRole`, `findByRole`, `queryByRole`, `getAllByRole`, and similar queries; they are slow due to expensive visibility checks.
- Testing Library recommends role queries for accessibility, but this project prioritizes test performance.
- Use faster alternatives: `getByText`, `getByLabelText`, `getByTitle`, or `querySelector` / `querySelectorAll`.
## Mock SDK Services
The backend HTTP API is an **external** dependency from the frontend’s perspective (`unit-testing` skill mocking exception).
- Use `mockSdkService` from `@tests/helpers` for type-safe mocking: pass the generated **controller class** and the **method name** (same static methods as `@generated/donut-backend-api/sdk.gen`).
- It automatically wraps responses in the standard format `{ data, error, request, response }`.
- It returns a spy that can be reconfigured in tests.
- Use `wrapSdkResponse` when updating mock return values.
- Use `mockSdkServiceWithImplementation` only for custom async logic based on options.
- Build response payloads with `makeMe`; do not mock in-process collaborators to “reach” coverage.
```typescript
import { NoteController } from "@generated/donut-backend-api/sdk.gen"
import { mockSdkService } from "@tests/helpers"
beforeEach(() => {
mockSdkService(NoteController, "getRecentNotes", [])
mockSdkService(NoteController, "showNote", makeMe.aNoteRealm.please())
})
```
```typescript
import { NoteController } from "@generated/donut-backend-api/sdk.gen"
import { mockSdkService, wrapSdkResponse } from "@tests/helpers"
const spy = mockSdkService(NoteController, "showNote", makeMe.aNoteRealm.please())
spy.mockResolvedValue(wrapSdkResponse(differentNote))
```
```typescript
import { TextContentController } from "@generated/donut-backend-api/sdk.gen"
import { mockSdkServiceWithImplementation } from "@tests/helpers"
mockSdkServiceWithImplementation(TextContentController, "updateNoteContent", async (options) => {
return await someAsyncOperation(options)
})
```
## Component Props
- Use `helper.component(ComponentName).withStorageProps` if the component requires a `storageAccessor` prop.
- Use `withProps` instead of `withStorageProps` if the component does not require `storageAccessor`.
- Test prop changes and their effects.
```typescript
const wrapper = helper
.component(Component)
.withStorageProps({ value: initialValue })
.mount()
await wrapper.setProps({ value: newValue })
```
## Data Builders
- Use `makeMe` for API-shaped test data; implementation lives in `packages/donut-test-fixtures`.
- Import `donut-test-fixtures/makeMe` only. Do not import the bare package name or deep paths into `src/`.
- Concise setup, defaults, and extending builders: `unit-testing` skill.
```typescript
const note = makeMe.aNoteRealm.title("Dummy Title").content("Description").please()
const answered = makeMe.anAnsweredQuestion
.accidentalMatch("alias", [note.note.noteTopology])
.please()
```
## Browser Mode Mounting
- Use `render()` from `@testing-library/vue` for most tests.
- `render()` encourages user-perspective tests and returns fast queries such as `getByText`, `getByLabelText`, and `data-testid`.
- Use `mount()` from `@vue/test-utils` only when you need direct access to Vue internals, emitted events, slots, or provide/inject edge cases.
- Query the DOM, not Vue components.
Prefer:
```typescript
page.getByText(/submit/i)
screen.getByText("Loading...")
```
Avoid:
```typescript
wrapper.findComponent(MyButton)
wrapper.find("[data-testid='my-component']")
```
## Deterministic Tests
- Tests must always execute the same way.
- Use assertions instead of if-conditions so failures are clear.
- Use sequential async operations instead of loops where possible.
Avoid:
```typescript
if (vm.searchResults) {
const selected = vm.searchResults.find((result) => result.id === wikidataId)
if (selected) {
// do something
}
}
```
Prefer:
```typescript
expect(vm.searchResults).toBeDefined()
expect(vm.searchResults.length).toBeGreaterThan(0)
const selected = vm.searchResults.find((result) => result.id === wikidataId)
expect(selected).toBeDefined()
```