write-tests
GitHub提供Web UI单元测试编写指南,涵盖Vitest+RTL配置、文件布局及针对上下文依赖代码的测试模式。
Trigger Scenarios
Install
npx skills add eclipse-openvsx/openvsx --skill write-tests -g -y
SKILL.md
Frontmatter
{
"name": "write-tests",
"description": "How to write unit tests for the webui — the vitest + React Testing Library setup, file layout, harness patterns for context\/router-dependent code, and the fail-first check that proves a test actually guards its target. Use when adding or updating tests under test\/unit."
}
Writing unit tests (webui)
Stack
- vitest (
yarn testrunsvitest run), jsdom environment, React Testing Library (@testing-library/react,@testing-library/user-event,@testing-library/jest-dom). - Config:
vite.config.mts→test.include: ['test/unit/**/*.spec.{ts,tsx}'],setupFiles: ['./test/setup.ts']. test/setup.tsregisters jest-dom matchers and anafterEach(cleanup). Vitest globals are off: importdescribe/it/expect/vifrom'vitest', and don't add your own RTL cleanup — setup.ts owns it.
Layout
- Mirror the source path under
test/unit/(e.g.src/context/search/…→test/unit/context/…). - Reusable harnesses/mocks go in
test/unit/support/.
Test what you own, not your dependencies
Assert the logic and wiring that belong to this codebase — not behavior a dependency already guarantees. A test whose only assertion is that MUI Tabs switch tabs, react-router navigates, or TanStack caches is testing someone else's library; it earns nothing and breaks on their upgrades.
- Exercising that behavior incidentally, as a step in a flow that asserts your own logic, is good — click the tab, then assert the panel your code renders. What you don't do is write a dedicated test for the third-party behavior alone.
- When you catch yourself asserting a library did its job, move the assertion to the part you own: the props you passed it, the state/callback you wired, the value you derived from its output.
Patterns, simplest first
- Pure functions → call and assert. See
test/unit/utils.spec.ts. - Components →
render+screen+ jest-dom matchers (toBeInTheDocument, …). Seetest/unit/components/kbd-key.spec.tsx. Prefer@testing-library/user-eventfor real interaction over synthetic events. - Anything that needs the app's providers (a TanStack hook reading
servicefromMainContext, a component using search, keyboard shortcuts, the theme, or routing) → render it through the shared harnesstest/unit/support/test-providers.tsx:renderWithProviders(ui, { route, mainContext: { service } })for components,renderHookWithProviders(hook, …)for hooks (the idiomatic way to read a hook's return viaresult.current), orwithProviders(Component, opts)for the HOC form. The harness reuses the app's realAppProviders(so it tracks the app automatically — query client,MainContext, keyboard shortcuts, search) and adds the entry-shell bitsAppProvidersdoesn't own (a fresh no-retry QueryClient, the MUI theme, aMemoryRouter). Reach for this first — don't hand-roll a provider stack per spec. - Routing / history → don't mock it.
test-providersmounts a realMemoryRouter; drive back/forward with theuseNavigate()you pull from the hook under test (navigate(-1)/navigate(1)are POPs), and pass the starting URL viaroute. - An external dependency whose timing or return you must control (a slow client, a flaky global) →
vi.mock('pkg', …)it with a small controllable stand-in, kept generic and reusable intest/unit/support/. Reach for this only when the real thing genuinely can't be driven — prefer the real router/providers first.
Grow the harness, don't duplicate it
The suite gets easier to extend only if shared test code is treated like production code.
- Before writing a mock, stub, or render helper, check
test/unit/support/for one that already fits and reuse it. - If an existing helper almost fits, extend or refactor it — kept generic and dependency-agnostic — rather than forking a near-duplicate. A second caller is the signal to generalize, not to copy.
- When an inline mock or helper in a spec would help another test, promote it to
test/unit/support/before it gets copied. - Refactoring a shared harness is part of adding the test that needs it: update every call site and leave the whole suite green. Never let two helpers do the same job.
- Don't write tests for the test helpers themselves. A harness or mock in
test/unit/support/is exercised by the specs that use it; it never gets its own spec.
The fail-first check (required for regression tests)
A test that passes tells you nothing until you've seen it fail. After writing a test that guards a fix, temporarily break the code it protects, run the test, confirm it goes red, then restore. This is the only thing that proves the test is wired to the behavior — it matters most for timing/race fixes, where a naive test passes with or without the fix.
Run
- One file while iterating:
yarn vitest run test/unit/<path>.spec.tsx. - Whole suite:
yarn test. Iterate on test or implementation until green, thenyarn lint.
Version History
- 4c33308 Current 2026-08-20 15:10


