Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
## Context
`@suaveplan/testing-dom` is the second testing-infrastructure package on genesis. It exists because:
1. Every React component test needs a DOM environment (happy-dom or jsdom under vitest).
2. The genesis testing-package separation rule (CLAUDE.md #9) puts the test-runner adapter here, not in `@suaveplan/testing` (which stays universal/runner-agnostic).
3. The staged runner/config/polyfills code from the `enterprise-testing` split (`packages/universal/testing-dom-staging/`) is its starting material.
Layer 0 dependency profile: external catalog deps only. No `@suaveplan/*` workspace deps. The package's own tests run under vitest with happy-dom.
## Goals / Non-Goals
**Goals:**
- Absorb staged runner/config/polyfills cleanly from `testing-dom-staging/`.
- Preserve the dual-runner conditional export (`vitest` default / `bun` condition) for the runner re-exports.
- Provide thin React/DOM helpers built on `@testing-library/react` + `@testing-library/jest-dom` + `@testing-library/user-event` (v14+).
- Local `TestingDomError` hierarchy with `{ code, cause, context }` shape; codes follow `TESTING_DOM.<DOMAIN>.<REASON>`.
- `createTestConfig()` (moved from testing) absorbs WSL2 pool/polyfill defaults and dynamic React-plugin loading.
- ≥ 95% test coverage; co-located unit tests + cross-cutting `tests/` per the test-placement hard rule.
**Non-Goals:**
- Forking `@testing-library/*` internals — the package is a thin wrapper.
- Legacy `fireEvent` helpers in the default export — `userEvent` v14 only.
- Wrapping `@suaveplan/error` / `@suaveplan/opentelemetry` / `@suaveplan/schema-registry` — these are forbidden workspace deps. Local equivalents.
## Decisions
### D1: Local `TestingDomError` hierarchy
Mirrors `@suaveplan/testing`'s `TestingError` shape but with `TESTING_DOM.<DOMAIN>.<REASON>` codes. Subclasses: `RenderError`, `MatcherError`, `ConfigError`, `AbortedError`. Reason: rule #8/#9 — no `@suaveplan/*` workspace deps. Shape-compatible with future `@suaveplan/error` adapter.
### D2: Runner adapter via conditional exports (preserved from staging)
`package.json` `exports["."]` routes vitest condition → vitest barrel, bun condition → bun-test barrel, default → vitest. `mocks.ts` provides runner-neutral `fn`/`spy`/`stub`/`mock`/`fakeClock` shims. The vitest dynamic-mock bracket-access trick (`vi["mock"]`) carries over from the staging code. **Coverage exception:** `runner/bun-test.ts` is excluded from istanbul coverage because vitest cannot resolve `bun:test` (same exception we already documented for testing).
### D3: `createTestConfig()` factory
Lifts `createTestConfig`, `detectRunner`, `detectEnvironment`, `createVitestConfig`, `createBunTestConfig` verbatim from staging. Hardcodes `pool: "forks"` (WSL2 vmThreads/threads fail), `setupFiles: ["@suaveplan/testing-dom/polyfills"]`, `fileParallelism: false` for happy-dom, optional dynamic `@vitejs/plugin-react` import.
### D4: React render wrapper — `renderComponent`
Thin wrapper over `@testing-library/react`'s `render` that:
- Wraps the tree in any provider stack the caller passes (e.g. router, query client) via a `wrapper` option
- Auto-cleans on `afterEach` via `cleanup()` from `@testing-library/react`
- Returns the standard `RenderResult` extended with a `userEvent` instance for ergonomic event dispatching
- Returns `TestingDomError("TESTING_DOM.RENDER.SETUP_FAILED")` if the render itself throws
### D5: Accessibility-first query aliases
Re-export Testing Library queries verbatim AND add ergonomic aliases:
- `byRole(name, opts)` → `screen.getByRole(name, opts)` (strict get; throws on miss)
- `findRole`, `queryRole` similar variants
- `byLabelText`, `byText`, `byPlaceholderText` similarly
Documentation in `.md` files steers callers toward role/label/text queries; `getByTestId` is re-exported but flagged as a fallback only.
### D6: User events via `userEvent` v14
Single export: `setupUser(options?)` returns a `UserEvent` instance with `pointerEventsCheck: 0` default for happy-dom (which doesn't fully simulate pointer events). When vitest fake timers are active (detected via `vi.isFakeTimers()`), wires `userEvent.setup({ advanceTimers: vi.advanceTimersByTime })` automatically.
### D7: jest-dom matchers extended via vitest's `expect.extend`
A side-effect import path `@suaveplan/testing-dom/matchers` extends vitest's `expect` with the `@testing-library/jest-dom` matcher set. Imported via `setupFiles` or per-test as a side effect.
### D8: Polyfills file
Lifted from staging — installs `globalThis.crypto` (from `node:crypto.webcrypto`), `globalThis.TextEncoder`/`TextDecoder` (from `node:util`) when missing. Side-effecting, exports nothing. Subpath: `@suaveplan/testing-dom/polyfills`.
### D9: OTEL via `@opentelemetry/api` (gated)
Optional debug instrumentation behind `process.env.TESTING_DOM_OTEL_ENABLED === "1"`. Counters: `testing_dom.render.count`, `testing_dom.user.click.count`. Histograms: `testing_dom.render.duration_ms`. Disabled by default — adds zero overhead in normal test runs.
### D10: Tests use `@suaveplan/testing` (utilities) directly
The package's own tests import primitive utilities (FakeClock, Factory, etc.) directly from `@suaveplan/testing`. This is the one allowed cross-genesis-package dep — but only as a `devDependency`, not a runtime dep. Production users of `testing-dom` never transitively pull `@suaveplan/testing`.
## Risks / Trade-offs
- **happy-dom limitations** — pointer events, intersection observer, resize observer aren't fully simulated. Mitigation: document, provide opt-in shims for the most-needed gaps.
- **React 19 act() boundaries** — incorrect handling of async state updates leaks errors. Mitigation: `renderComponent` always awaits `act()` for the initial render.
- **bun-test path** — same untestable-under-vitest issue as the original; excluded from coverage. Mitigation: add a `bun:smoke` script later that runs a tiny test fixture under `bun test` to verify the dual-runner contract works in anger.
- **`@vitejs/plugin-react` optional peer** — missing peer must not break non-React consumers. Mitigation: dynamic import inside `try/catch` in `createTestConfig` (preserved from staging).
- **`devDependency on @suaveplan/testing`** — D10 violates the spirit of "no @suaveplan/* deps in testing-infra packages" but only at devDep level. Tests need `FakeClock`, `Factory`, etc. Documented in CLAUDE.md exception note.