Files
pnokerandClaude Code 3fb43c4239 feat(web): unify three-terminal control density and close audit defects
Land the v7 three-terminal UX decision: drop the width/pointer-based
44px touch enlargement so one 32px density holds across desktop,
tablet, and mobile, and rebuild entity pages on a responsive record
list (ResponsiveRecordList + EntityMobileCard + per-entity mobile
summaries). Tighten the nginx security headers (CSP frame-ancestors
'none', X-Frame-Options DENY) to match the static e2e server.

Complete the dc3-web audit remediation, including the remaining LOW
items from docs/dc3-web-low-defects-handoff.md: streaming-safe
message loads in the agentic store, mock menu reparent cycle
rejection, paged-list page clamping, fetch/axios mock request
normalization, per-table attribute catalogs, post-flush alarm
watchers, rowKey-agnostic delete state, localized card text
fallbacks, and e2e-server port fail-fast.

Gate: vue-tsc, eslint, vitest 687 passed, responsive e2e 64 passed.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-07 19:33:17 +08:00

161 lines
8.3 KiB
Markdown

# Test Strategy
The frontend test suite is intentionally layered so failures point at the right level of the application.
## Commands
- `pnpm test`: run all Vitest suites.
- `pnpm run test:unit`: run utility, store, and infrastructure unit tests.
- `pnpm run test:api`: run API wrapper contract tests.
- `pnpm run test:component`: run Vue component contract tests.
- `pnpm run test:views`: run route-level view tests (mounted views with mocked APIs).
- `pnpm run test:guard`: run meta tests that keep the test suite itself safe.
- `pnpm run test:impact`: print the recommended checks for the current change.
- `pnpm run test:ci`: run Vitest with coverage thresholds.
- `pnpm run test:coverage`: run Vitest with coverage.
- `pnpm run test:e2e`: run Playwright Test specs.
- `pnpm run test:e2e:responsive`: run the three-terminal gate against the self-contained mock build.
- `pnpm run test:e2e:headed`: run Playwright Test specs in a visible browser.
- `pnpm run test:e2e:sweep`: run the deep browser sweep.
- `pnpm run test:e2e:sweep:headed`: run the deep browser sweep in a visible browser.
## Verification
Run the checks yourself instead of trusting recorded results, which go stale as the suite changes.
- `pnpm check`, `pnpm lint:check`, `pnpm build`, and `pnpm test:ci` cover types, lint, the build, and coverage-gated
Vitest (thresholds stay enforced by `vitest.config.ts`).
- `pnpm run test:e2e:responsive` runs the three-terminal gate against the self-contained mock build, so it needs no
backend.
- `pnpm run test:e2e` runs the full Playwright suite; it needs a reachable gateway unless you set `E2E_SERVER_MODE=mock`.
The automated suites do not replace release gating: full real-backend business E2E, Lighthouse budgets, full `axe-core`
scans, and a full-route manual visual review must still run against a disposable backend dataset before release.
## Layers
- `tests/unit`: deterministic unit tests for utils, stores, and shared infrastructure such as Axios interceptors.
- `tests/api`: frontend API wrapper contracts. These tests mock the transport layer and snapshot the method, URL, body,
and config used by every exported wrapper.
- `tests/component`: Vue component contracts using `@vue/test-utils`. Reuse the shared Element Plus stubs in
`tests/setup/stubs/element-plus.ts` instead of redefining them per file.
- `tests/views`: route-level view contracts. Same toolchain as `tests/component`
but the subject is a `src/views/**` page mounted with mocked APIs. Use this layer when the unit-of-behaviour is a
routed page rather than a reusable building block.
- `tests/e2e`: Playwright browser tests. The specs cover authentication, route availability, page health, and safe UI
interactions.
- `tests/e2e/browser-sweep.mjs`: entrypoint for the deep browser sweep. The actual runner lives under
`tests/e2e/browser-sweep`; keep new browser scenarios in Playwright specs unless they specifically need the sweep
runner.
- `tests/guardrails`: meta tests for AI-assisted development. These tests block focused or disabled tests, fixed E2E
business IDs, missing guard scripts, and missing testing policy documentation.
## Adding a new test
1. Pick the right layer (`tests/{unit,component,views,api,e2e}`).
2. Copy the matching template from `tests/_templates/`:
- `store.test.template.ts` for Pinia stores
- `composable.test.template.ts` for `src/composables/*`
- `component.test.template.ts` for `src/components/*` (and views)
- `api.test.template.ts` for new API wrappers (which actually means extending `tests/api/api-contracts.test.ts`)
3. Reuse fixtures from `tests/fixtures/` when the data shape already exists there — don't inline a duplicate
`sampleMenuTree` / credentials block.
4. Reuse stubs from `tests/setup/stubs/element-plus.ts` for any Element Plus components.
5. Run `pnpm test` locally before committing. If it fails with a Vue warn,
see [test-debugging](https://docs.dc3.site/en/frontend/test-debugging).
## Conventions
These rules are mechanically enforced by `tests/guardrails/ai-guardrails.test.ts`. Style points without a guardrail are
listed for reviewer reference.
### File naming (enforced)
- `tests/{unit,component,views}/<kebab-case>.test.ts`. The base name should mirror the source module under test
(`auth.ts` → `auth-store.test.ts`,
`usePagedList.ts` → `use-paged-list.test.ts`,
`validationUtil.ts` → `validation-util.test.ts`).
- Avoid suffixes that describe the test itself (`-coverage`, `-bonus`); name for the _subject_, not the goal.
### Mocking (enforced for multi-mock files)
- More than one `vi.mock` in a file → bundle the spies in one `vi.hoisted(() => ({…}))`
block at the top, conventionally named `xxxMocks` (`tokenMocks`, `apiMocks`). Keeps top-level state minimal and avoids
"Cannot access X before initialization"
hoisting errors.
- A single `vi.mock` may inline its factory.
### Element Plus stubs (enforced)
- Reuse `tests/setup/stubs/element-plus.ts`. Do not redefine `ElButton`,
`ElForm`, `ElPagination`, `ElInput`, `ElSelect`, etc. inline.
- Add new layout/decorator stubs to `layoutStubs` in that file when a new Element Plus component is needed.
### Type assertions (enforced)
- No `as unknown as T` double assertions. Use a type-correct fixture builder or `// @ts-expect-error` for the one line
that intentionally violates the contract.
- No `as never`. Build a properly-typed fixture instead.
### `wrapper.vm` is forbidden in component/view tests (enforced)
- Drive the component through props, slots, emits, and DOM events — the public contract. Calling
`wrapper.vm.someInternalMethod()` couples tests to the component's internal API, which churns whenever
`<script setup>`
details change.
### Describe nesting (style)
- One top-level `describe` per file, matching the subject (`describe('auth store', …)`).
- Multi-action subjects use a second-level `describe` per action (`describe('login', …)`, `describe('logout', …)`).
- Single-purpose subjects (e.g. `interval store` with one method) stay flat.
### Naming inside files (style)
- Stores: lowercase prose — `describe('auth store')`, `describe('menu store')`.
- Components/views: PascalCase symbol — `describe('ToolCard')`,
`describe('AlarmNotify view')`.
- Utility describe blocks mirror the `src/utils/<file>` base — `describe('storageUtil')`.
### Vue warnings are errors (enforced via `tests/setup/vitest.setup.ts`)
- `[Vue warn]` and `[Vue error]` messages are promoted to thrown errors during tests. If you genuinely need to silence
one, add a regex to
`VUE_WARN_ALLOWLIST` with a comment explaining the source — don't broaden the check.
- Set `VITEST_ALLOW_VUE_WARN=1` to bypass while debugging locally.
## AI Guardrails
The mandatory policy lives in `tests/frontend-testing-guardrails.md`. In short:
- API wrapper changes require `tests/api` coverage.
- Shared utility, store, composable, and Axios changes require `tests/unit`.
- Reusable component changes require `tests/component`.
- Route, page, menu, and permission changes require Playwright coverage.
- New test data must be created dynamically and cleaned up; do not hard-code business IDs.
## E2E Environment
Playwright defaults to `http://127.0.0.1:4174`, runs with one worker, and starts
`pnpm run serve:e2e` unless `E2E_START_SERVER=0` is set. The full E2E suite uses the development build and proxies
`/api` to `http://localhost:8000` by default. The test server listens on the dedicated
`http://127.0.0.1:4174` endpoint so it cannot silently reuse an unrelated service on the development UI port.
`pnpm run test:e2e:responsive` switches the local server to the
self-contained mock build, so three-terminal checks do not require a backend. Use `E2E_BASE_URL` with
`E2E_START_SERVER=0` to test an already running environment, `E2E_API_TARGET` to select another gateway, or
`E2E_WORKERS=N` to opt into parallelism against an isolated backend dataset.
Use the headed scripts when you want to watch the browser operate:
- `pnpm run test:e2e:headed`
- `pnpm run test:e2e:sweep:headed`
The `tests/e2e/browser-sweep.mjs` script is kept for deeper manual/full-environment sweeps. It performs destructive
delete checks against seeded data, so run it only against a disposable test dataset.
Both the Playwright specs and the browser sweep discover required route IDs at runtime. If a required entity does not
exist, the test creates
`e2e_*` fixture data through the backend API and deletes those records at the end of the run.