# testing-dom ## Purpose The `@suaveplan/testing-dom` package provides DOM- and React-side test helpers with dual-runner support for Vitest and `bun:test`. It ships a `createTestConfig()` factory tuned for WSL2 (forks pool, polyfills, optional `@vitejs/plugin-react`), a `renderComponent` wrapper around `@testing-library/react` with auto-cleanup and `userEvent` integration, side-effect polyfills for `crypto`/`TextEncoder`/`TextDecoder`, and ambient-typed `@testing-library/jest-dom` matcher extensions. The package is a Layer 0 dependency: its `dependencies` and `peerDependencies` MUST NOT contain any `@suaveplan/*` workspace package, and structured errors flow through `TestingDomError` with stable `TESTING_DOM..` codes. ## Requirements ### Requirement: Layer 0 dep-graph isolation The `@suaveplan/testing-dom` `dependencies` and `peerDependencies` MUST NOT contain any `@suaveplan/*` workspace package. `devDependencies` MAY include `@suaveplan/testing` (utilities) for the package's own tests, but the production runtime closure MUST NOT contain it. #### Scenario: Runtime dep-graph guard - **WHEN** `bunx turbo ls --filter=@suaveplan/testing-dom...` is run (forward closure of runtime deps) - **THEN** no `@suaveplan/*` package appears #### Scenario: devDep allowance - **WHEN** `package.json` is parsed - **THEN** `dependencies` and `peerDependencies` are free of `@suaveplan/*` AND `devDependencies` may contain `@suaveplan/testing` (this dep is not visible to consumers) **Verified by:** `tests/dep-graph.test.ts::"no @suaveplan/* in dependencies or peerDependencies"` AND `tests/dep-graph.test.ts::"devDependencies may contain @suaveplan/testing only (utilities for our own tests)"` ### Requirement: Dual-runner conditional exports preserved The package `exports["."]` SHALL route the `vitest` condition and the `default` condition to the vitest barrel and the `bun` condition to the bun-test barrel. The exports MUST expose `describe`, `it`, `test`, `expect`, `vi`, `beforeAll`, `beforeEach`, `afterAll`, `afterEach`, `fn`, `spy`, `stub`, `mock`, `fakeClock`, `restoreMocks`, `restoreTimers` under both conditions. #### Scenario: Vitest consumer - **WHEN** a consumer running under vitest imports `{ describe, it, expect, vi }` from `@suaveplan/testing-dom` - **THEN** the symbols come from `vitest` #### Scenario: bun-test consumer - **WHEN** a consumer running under `bun test` imports the same names - **THEN** the symbols come from `bun:test` **Verified by:** `tests/exports-map.spec-conformance.test.ts::"routes vitest + default to the index barrel"` AND `tests/exports-map.spec-conformance.test.ts::"routes the bun condition to the bun-test barrel"` AND `tests/exports-map.spec-conformance.test.ts::"vitest and default conditions share the same types entry"` AND `tests/exports-map.spec-conformance.test.ts::"bun condition has its own types entry"` AND `src/runner/mocks.test.ts::"creates a mock that records calls"` ### Requirement: `createTestConfig()` produces WSL2-safe configs The factory SHALL emit a vitest config with `pool: "forks"`, `setupFiles: ["@suaveplan/testing-dom/polyfills"]`, `fileParallelism: false` when `environment === "happy-dom"`, and dynamically-loaded `@vitejs/plugin-react` if that optional peer is installed. When the caller's runner is `bun:test`, the factory SHALL return a `BunTestConfig` instead. #### Scenario: Happy-dom config - **WHEN** `createTestConfig({ environment: "happy-dom" })` is awaited and `@vitejs/plugin-react` is present - **THEN** the returned config includes the React plugin, has `pool === "forks"`, `fileParallelism === false`, and `setupFiles` includes the polyfills path #### Scenario: Missing React peer doesn't break non-React consumers - **WHEN** `createTestConfig({ environment: "happy-dom" })` is awaited and the React peer is NOT installed - **THEN** the factory returns the vitest config without the plugin, no error thrown **Verified by:** `src/config/config.test.ts::"returns a vitest config by default"` AND `src/config/config.test.ts::"forces pool=forks and includes polyfill setupFile"` AND `src/config/config.test.ts::"disables fileParallelism for happy-dom by default"` AND `src/config/config.test.ts::"returns a BunTestConfig when runner=bun:test"` AND `src/config/config.test.ts::"throws CONFIG_UNKNOWN_RUNNER for an unknown runner"` ### Requirement: Polyfills are side-effect-only The `@suaveplan/testing-dom/polyfills` subpath MUST resolve to a side-effecting module that installs `globalThis.crypto` (when missing), `globalThis.TextEncoder`, and `globalThis.TextDecoder`. The module MUST export nothing. #### Scenario: Idempotent polyfill install - **WHEN** `import "@suaveplan/testing-dom/polyfills"` runs twice in the same process - **THEN** the second import is a no-op (no errors, no overwrites) **Verified by:** `tests/tree-shake.test.ts::"polyfills subpath has no functional surface"` AND `tests/exports-map.spec-conformance.test.ts::"polyfills + matchers resolve to side-effecting dist files"` ### Requirement: `renderComponent` wraps React render with cleanup and userEvent `renderComponent(ui, options?)` SHALL render the React tree using `@testing-library/react`'s `render`, auto-register cleanup on `afterEach`, and return the standard `RenderResult` extended with a `userEvent` instance. #### Scenario: Render and event dispatch - **WHEN** `renderComponent(