Frontend Testing Rules
SkillWeb & browsingFrontend Vitest browser-mode tests, Testing Library queries, mockSdkService, makeMe builders, deterministic Vue component testing. Use when writing or changing frontend Vitest tests.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Frontend Testing Rules skill
What this skill tells your AI
The instructions your AI receives, as published by nerds-odd-e/doughnut in .agents/skills/frontend-testing/SKILL.md and read by ahel’s review.
Style ("small test" practice — stable boundary, data over mocks, focused assertions, concise makeMe): the unit-testing skill — follow that first.
These commands cover behavioral testing only. Before accepting frontend proof,
also pass the typecheck required by the frontend skill's "Frontend proof" rule.
Test Commands
From the repo root:
CURSOR_DEV=true nix develop -c pnpm frontend:test
Run tests with browser rendering UI:
CURSOR_DEV=true nix develop -c pnpm frontend:test:ui
Run tests in browser-rendered watch mode:
CURSOR_DEV=true nix develop -c pnpm frontend:test:watch
Run a single test file from the repo root:
CURSOR_DEV=true nix develop -c pnpm frontend:test tests/path/to/TestFile.spec.ts
Or only the frontend package:
CURSOR_DEV=true nix develop -c pnpm -C frontend test tests/path/to/TestFile.spec.ts
Run a specific test case:
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-testingskill); cover lower layers with realisticmakeMeprops/state, not by testing internal helpers in isolation. - Test through user interactions; assert observable DOM outcomes.
- Use
data-testidfor 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, orquerySelector/querySelectorAll.
Mock SDK Services
The backend HTTP API is an external dependency from the frontend’s perspective (unit-testing skill mocking exception).
- Use
mockSdkServicefrom@tests/helpersfor 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
wrapSdkResponsewhen updating mock return values. - Use
mockSdkServiceWithImplementationonly for custom async logic based on options. - Build response payloads with
makeMe; do not mock in-process collaborators to “reach” coverage.
import { NoteController } from "@generated/donut-backend-api/sdk.gen"
import { mockSdkService } from "@tests/helpers"
beforeEach(() => {
mockSdkService(NoteController, "getRecentNotes", [])
mockSdkService(NoteController, "showNote", makeMe.aNoteRealm.please())
})
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))
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).withStoragePropsif the component requires astorageAccessorprop. - Use
withPropsinstead ofwithStoragePropsif the component does not requirestorageAccessor. - Test prop changes and their effects.
const wrapper = helper
.component(Component)
.withStorageProps({ value: initialValue })
.mount()
await wrapper.setProps({ value: newValue })
Data Builders
- Use
makeMefor API-shaped test data; implementation lives inpackages/donut-test-fixtures. - Import
donut-test-fixtures/makeMeonly. Do not import the bare package name or deep paths intosrc/. - Concise setup, defaults, and extending builders:
unit-testingskill.
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/vuefor most tests. render()encourages user-perspective tests and returns fast queries such asgetByText,getByLabelText, anddata-testid.- Use
mount()from@vue/test-utilsonly when you need direct access to Vue internals, emitted events, slots, or provide/inject edge cases. - Query the DOM, not Vue components.
Prefer:
page.getByText(/submit/i)
screen.getByText("Loading...")
Avoid:
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:
if (vm.searchResults) {
const selected = vm.searchResults.find((result) => result.id === wikidataId)
if (selected) {
// do something
}
}
Prefer:
expect(vm.searchResults).toBeDefined()
expect(vm.searchResults.length).toBeGreaterThan(0)
const selected = vm.searchResults.find((result) => result.id === wikidataId)
expect(selected).toBeDefined()
Signals
- GitHub stars
- 49
- Forks
- 72
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
frontend-testing-nerds-odd-e- Source
- github.com/nerds-odd-e/doughnut