mirror of
https://gitee.com/pnoker/iot-dc3.git
synced 2026-11-04 04:02:30 +08:00
Adds shared fixtures/stubs/templates, 15 mechanically-enforced guardrails (filename casing, ban wrapper.vm direct calls, double assertions, weak matchers, tautologies, debug residue), splits grab-bag util test files by source module, promotes Vue warnings to test failures, fills logout and login-validation gaps in e2e, and replaces networkidle+hard-wait recipes with web-first assertions. Net: 355 unit/component/api/guardrail tests (+98), 36 playwright tests (+11), 80.55% line coverage with 2-3% buffer above tightened thresholds (65/75/78/78).
132 lines
5.0 KiB
Markdown
132 lines
5.0 KiB
Markdown
# Test Debugging FAQ
|
|
|
|
Quick answers to the failures that recur most often when writing or running
|
|
the frontend tests. Pair this with [tests/README.md](../tests/README.md) for
|
|
the conventions, and `tests/guardrails/ai-guardrails.test.ts` for the
|
|
mechanically-enforced rules.
|
|
|
|
## "Unexpected Vue warning: ..." thrown from a test
|
|
|
|
The setup file in `tests/setup/vitest.setup.ts` promotes `[Vue warn]` /
|
|
`[Vue error]` to thrown errors. The most common causes:
|
|
|
|
- **Failed to resolve component: el-xxx** — the component template uses an
|
|
Element Plus component you haven't stubbed. Add it to `layoutStubs` in
|
|
`tests/setup/stubs/element-plus.ts` (preferred) or pass it under
|
|
`global.stubs` in the mount call.
|
|
- **injection "Symbol(router)" not found** — the component calls
|
|
`useRouter()` / `useRoute()` but the test didn't mount with a router.
|
|
Use a memory router:
|
|
```ts
|
|
import { createMemoryHistory, createRouter } from 'vue-router';
|
|
const router = createRouter({ history: createMemoryHistory(), routes: [...] });
|
|
mount(Comp, { global: { plugins: [i18n, router] } });
|
|
```
|
|
- **Failed setting prop "modelValue"** — already in the allowlist; if you
|
|
see a different prop, add the regex to `VUE_WARN_ALLOWLIST` with a comment
|
|
explaining why it can't be fixed at the source.
|
|
|
|
To temporarily bypass while debugging locally:
|
|
|
|
```bash
|
|
VITEST_ALLOW_VUE_WARN=1 pnpm test
|
|
```
|
|
|
|
Don't commit code that needs the bypass — the warning indicates a real
|
|
contract gap.
|
|
|
|
## "Cannot access X before initialization" in vi.mock factory
|
|
|
|
Symptom: `ReferenceError: Cannot access 'someMock' before initialization`
|
|
pointing at a `vi.mock(...)` call. Root cause: the factory references a
|
|
top-level `const`, but `vi.mock` is hoisted to the top of the file by
|
|
Vitest, so the const isn't initialized yet.
|
|
|
|
Fix: wrap the spies in `vi.hoisted`:
|
|
|
|
```ts
|
|
const apiMocks = vi.hoisted(() => ({
|
|
doSomething: vi.fn(),
|
|
}));
|
|
vi.mock('@/api/foo', () => apiMocks);
|
|
```
|
|
|
|
The shared-mocks guardrail enforces this for files with more than one
|
|
`vi.mock`.
|
|
|
|
## Test passes locally but fails on CI for "leftover state"
|
|
|
|
Look for module-level state that survives between tests:
|
|
|
|
- **Module-level reactive cache** (e.g. `useEntityNames`'s `cache` /
|
|
`inflight` objects). Reset by `vi.resetModules()` + re-importing in
|
|
`beforeEach`.
|
|
- **Pinia store persisted across tests**. Always
|
|
`setActivePinia(createPinia())` in `beforeEach`.
|
|
- **localStorage / sessionStorage**. The global setup clears these in
|
|
`afterEach`, but if your test relies on data being absent at the start
|
|
of the run, clear in `beforeEach` too.
|
|
|
|
## "as unknown as T" or "as never" guardrail failure
|
|
|
|
The guardrail forbids erasing types via double assertion. Replace with:
|
|
|
|
- A typed fixture builder: `function makeRequest(): Request { return { … }; }`
|
|
- `// @ts-expect-error — intentionally invalid input for whitelist test`
|
|
on the single line that violates the contract.
|
|
|
|
If the test really needs `as never` to satisfy a generic constraint, that
|
|
usually means the production code is mistyped; fix it at the source.
|
|
|
|
## "tests/component/foo.test.ts contains wrapper.vm.x()" guardrail failure
|
|
|
|
Drive the component through its public surface — props, slots, emits, DOM
|
|
events. Calling `wrapper.vm.someMethod()` couples the test to internals
|
|
that change whenever the component is refactored. If you genuinely need
|
|
an escape hatch, use `defineExpose` in the component and call through the
|
|
expose surface (still `wrapper.vm.someMethod()`, but tests survive the
|
|
refactor because expose is part of the contract).
|
|
|
|
## "describe must use lowercase verb (no should...)" guardrail failure
|
|
|
|
Rephrase. The describe block names the subject, the it block names the
|
|
behaviour:
|
|
|
|
```ts
|
|
// ❌
|
|
it('should validate before searching', …);
|
|
|
|
// ✅
|
|
it('validates before searching', …);
|
|
```
|
|
|
|
## Coverage drops below threshold
|
|
|
|
Run `pnpm test:coverage` locally. The console report shows per-file gaps.
|
|
If you genuinely added new untested code, add the test; if you removed
|
|
covered code (intentional refactor), the percentage may rise enough to
|
|
hide the drop, otherwise lower the threshold in `vitest.config.ts` and
|
|
flag it in the PR description.
|
|
|
|
Don't game the threshold by writing tautological tests — the
|
|
"forbids tautological assertions" guardrail catches `expect(true).toBe(true)`.
|
|
|
|
## Snapshot diff is huge after a small API change
|
|
|
|
The `tests/api/api-contracts.test.ts.snap` file is ~2800 lines because it
|
|
covers every API wrapper. A small change to a wrapper triggers a small
|
|
focused diff inside it; a giant diff means many wrappers shifted. Ask:
|
|
|
|
- Did the auth headers change shape? (storage-format change)
|
|
- Did the URL prefix move? (proxy / version bump)
|
|
- Was a new wrapper added to a tracked module?
|
|
|
|
Run `pnpm test:api -u` to update only after manually reading the diff.
|
|
|
|
## Where do new fixtures go?
|
|
|
|
`tests/fixtures/`. Don't inline a 30-line sample tree in a test if a
|
|
sibling test could use the same shape. The guardrail checks the directory
|
|
exists; convention asks you to keep adding files (`auth.ts`, `menu.ts`,
|
|
`rows.ts` as starting points).
|