# Testing Blit-Tech uses three primary testing tiers (unit, integration, visual) plus a benchmark tier for CPU performance regression tracking. ## Architecture ### Tier 1: Unit Tests (Vitest, Node.js) Pure logic with no GPU dependencies. Tests run in Node.js for maximum speed. - **Vector2i** - all arithmetic, distance, comparison, factory methods - **Rect2i** - intersection, containment, computed properties - **Color32** - color math, conversions, blending - **Palette** - construction, set/get, named aliases, serialization, GPU float layout, preset factories - **PaletteEffect** - manager lifecycle, CycleEffect rotation, FadeEffect/FadeRangeEffect lerp, FlashEffect, paletteSwap - **Easing** - boundary values, monotonicity, midpoint checks for all easing curves - **GameLoop** - constructor validation, tick counter - **IBlitTechDemo** - `defaultConfig()` and optional `configure()` ### Tier 2: Integration Tests (Vitest, Node + GPU mocks) Code that requires WebGPU objects or DOM APIs. Most tests run in Node with `src/__test__/webgpu-mock.ts` for GPU stubs and `vi` for browser API stubs. Tests that need a full DOM (Bootstrap, BootstrapHelpers) opt into `happy-dom` via the `// @vitest-environment happy-dom` directive. - **AssetLoader** - image caching and deduplication (Node + vi stubs) - **SpriteSheet** - UV calculation, lazy texture creation, indexization, `loadIndexed()` convenience flow, and `getIndexedPixels()` defensive-copy semantics (Node + GPU mocks) - **BitmapFont** - glyph lookup, text measurement (Node + vi stubs) - **BootstrapHelpers** - WebGPU support detection, canvas lookup (happy-dom) - **Bootstrap** - full bootstrap lifecycle, including `?renderer=software` WebGPU-skip path (happy-dom) - **WebGpuRenderer** - frame lifecycle, camera, pipeline delegation (Node + GPU mocks) - **SoftwareRenderer** - frame lifecycle, palette enforcement, camera offsets, indexed sprite blits, bitmap text, `captureFrame()` semantics, and unsupported-effects assertions (Node + 2D canvas mocks) - **PrimitivePipeline** - vertex buffer math, line algorithm (Node + GPU mocks) - **SpritePipeline** - texture batching, UV coordinates (Node + GPU mocks) - **WebGPUContext** - initialization with mock adapter/device (Node + GPU mocks) - **BTAPI** - singleton coordinator; includes software-mode init, `?renderer=software` URL override, and `captureFrame()` in software mode (Node + GPU mocks + 2D canvas mocks) - **FrameCapture** - GPU readback, PNG conversion (Node + GPU mocks + browser stubs) ### Tier 3: Visual Regression (Playwright, Chromium) Actual GPU rendering verified via screenshot comparison. Requires Chrome with WebGPU flags enabled. Each spec covers both the default WebGPU backend and the `?renderer=software` software backend, producing separate baseline screenshots for each. - **Primitives** - pixel, line, and rectangle rendering - **Sprites** - palette-indexed sprite rendering, palette offsets, batching - **Camera** - camera offset transforms applied to all geometry - **Fonts** - placeholder text rendering at known positions - **Mixed** - primitives and sprites combined with correct layering - **Post-process** - baseline scene, pixel/display-tier effects, CRT preset stacks, upscale filters ### Tier 4: CPU Benchmarks (Vitest bench) Hot-path performance checks for methods and allocation patterns. Benchmarks are tracked in CI benchmark jobs and documented in [Performance Testing](performance-testing.md). ## Declaration tooling checks Public types are rolled up during `pnpm build` via `vite-plugin-dts` and API Extractor. The workspace pins TypeScript to the same version API Extractor bundles (see `docs/developer-experience-guide.md`). - **`pnpm test:declarations`** - Node test runner for `scripts/check-declaration-tooling.mjs` (drift patterns and alignment log parsing). Included in `pnpm preflight`. - **CI** - after `pnpm build`, `node scripts/check-declaration-tooling.mjs build.log` runs in both `.github/workflows/ci.yml` (build-library job) and `.github/workflows/pr-checks.yml` (bundle-size job) to fail on drift warnings and version mismatch. - **Manual** - `pnpm build 2>&1 | tee build.log && node scripts/check-declaration-tooling.mjs build.log` ## Commands ```bash pnpm test # Run all unit tests (alias for test:unit) pnpm test:unit # Run all unit tests pnpm test:unit:watch # Watch mode for development pnpm test:unit:coverage # Coverage report (80% minimum threshold) pnpm test:declarations # Declaration tooling log checker (Node test) pnpm test:visual # Playwright visual regression (requires Chrome) pnpm test:visual:update # Update visual test baselines pnpm test:visual:coverage # Visual tests with Istanbul coverage report pnpm bench # Run CPU benchmarks (Vitest bench) pnpm bench:json # Run CPU benchmarks and write benchmark-results.json ``` ## Test File Location Tests are colocated next to their source files: ```text src/utils/Vector2i.ts # Source src/utils/Vector2i.test.ts # Test ``` Visual regression tests live in a separate directory: ```text tests/visual/ fixtures/ # HTML pages and Vite config __snapshots__/ # Git-tracked reference screenshots coverage-fixture.ts # Playwright fixture for Istanbul coverage primitives.spec.ts # Primitive rendering visual test sprites.spec.ts # Sprite rendering visual test camera.spec.ts # Camera transform visual test fonts.spec.ts # Text rendering visual test mixed.spec.ts # Combined rendering visual test post-process.spec.ts # Post-process effect chain visual test ``` ## Writing a New Test ```ts import { describe, expect, it } from 'vitest'; import { MyClass } from './MyClass'; describe('MyClass', () => { describe('myMethod', () => { it('should do something', () => { const instance = new MyClass(); expect(instance.myMethod()).toBe(expected); }); }); }); ``` Conventions: - Use `describe`/`it` (not `test`) - Use `// #region` / `// #endregion` for files over ~100 lines - Follow source code style (4-space indent, single quotes, semicolons) - No emoji in test descriptions - No JSDoc required in test files ## Palette Testing Patterns Use these patterns when adding or reviewing palette-related features: - **Slot-zero invariants**: assert slot `0` remains transparent and draw paths treat it as discarded/clear. - **Preset correctness**: verify preset factories (`Palette.vga`, `cga`, `c64`, `gameboy`, `pico8`, `nes`) and `palette.applyHUD()` produce expected hex values at known slots. - **Effect determinism**: for `paletteCycle`, `paletteFade`, `paletteFadeRange`, `paletteFlash`, and `paletteSwap`, assert both mid-effect and completion states under fixed timestep progression. - **Dirty flag contract**: assert `set()` / `copyFrom()` / effect updates mark palettes dirty and renderer upload paths clear the flag after upload. - **Offset rendering behavior**: add visual assertions that `BT.drawSprite(..., paletteOffset)` remaps the same indexed sprite data to different slot ranges without duplicating textures. - **Layout-swap safety**: after swapping to a different index layout, assert `BT.spritesRefresh()` re-indexes tracked sheets and catches missing-color cases. ## WebGPU Mock Usage For tests that need GPU objects, import from the mock factory: ```ts import { createMockGPUDevice, createMockGPUTexture } from '../__test__/webgpu-mock'; const device = createMockGPUDevice(); const texture = createMockGPUTexture(256, 256); ``` Available mocks: - `createMockGPUDevice()` - returns a stub GPUDevice - `createMockGPUTexture(width, height)` - returns a stub GPUTexture - `createMockGPUCanvasContext()` - returns a stub GPUCanvasContext - `createMockRenderPassEncoder()` - returns a stub GPURenderPassEncoder - `createMockPaletteBuffer()` - returns a 4096-byte stub GPUBuffer for palette uniform tests - `installMockNavigatorGPU()` / `uninstallMockNavigatorGPU()` - install/remove global navigator.gpu For software-renderer tests that need a Canvas 2D context, use the `makeMock2DCanvas()` helper defined in `src/core/BTAPI.test.ts`. It returns an `HTMLCanvasElement` stub whose `getContext('2d')` yields a minimal `CanvasRenderingContext2D` mock (with `createImageData`, `putImageData`, `drawImage`, and `toBlob` stubs). `src/__test__/setup.ts` also installs a global `OffscreenCanvas` stub in Node.js (returns zero-filled pixel data from `getImageData`). This is needed by `SpriteSheet.indexize()` tests that run outside a browser environment. ## Coverage Minimum thresholds enforced in `vitest.config.ts`: - Statements: 80% - Branches: 80% - Functions: 80% - Lines: 80% Run `pnpm test:unit:coverage` to check. Coverage reports are generated in `coverage/`. ### Visual Test Coverage Visual test coverage uses Istanbul instrumentation via `vite-plugin-istanbul`. It is separate from unit test coverage (different provider: Istanbul vs V8) and generates reports in `coverage-visual/`. ```bash pnpm test:visual:coverage # Runs visual tests with instrumented code, then generates lcov report ``` Coverage is collected via a custom Playwright fixture (`tests/visual/coverage-fixture.ts`) that captures `window.__coverage__` from each page after tests complete. The `VISUAL_COVERAGE=1` env var activates instrumentation. ## Visual Test Workflow 1. Create a fixture HTML page in `tests/visual/fixtures/` 2. Write a Playwright spec in `tests/visual/` 3. Run `pnpm test:visual` to generate baseline screenshots 4. Screenshots are committed to `tests/visual/__snapshots__/` 5. After intentional rendering changes, run `pnpm test:visual:update` ## IDE Setup ### Cursor / VS Code Install the recommended extensions (prompted on first open): - **Vitest** (`vitest.explorer`) - inline test running, debugging, coverage gutter - **Playwright** (`ms-playwright.playwright`) - visual test runner ### WebStorm Built-in Vitest support since 2023.3. Auto-detects `vitest.config.ts` and shows run gutters next to each test. ### Zed Use the Tasks system. Create `.zed/tasks.json`: ```json [ { "label": "Test: Run All", "command": "pnpm test", "cwd": "$ZED_WORKTREE_ROOT" }, { "label": "Test: Current File", "command": "pnpm exec vitest run $ZED_FILE", "cwd": "$ZED_WORKTREE_ROOT" } ] ``` ## CI Integration GitHub Actions runs unit tests and quality gates in CI. Visual regression is local-only today (see below). ### `ci.yml` (`CI` workflow) Triggers on push to `main` and on pull requests targeting `main`. `labeled` / `unlabeled` PR events skip the `quality`, `build-library`, and `test` jobs unless the added label is `perf` (which enables the benchmark job). | Job | What it runs | | --------------- | ---------------------------------------------------------------------------------------------- | | `quality` | `pnpm format:check`, `pnpm lint`, `pnpm typecheck`, `pnpm spellcheck` | | `build-library` | `pnpm build`, declaration tooling check, uploads `dist/` artifact | | `test` | `pnpm test:unit:coverage`, Codecov upload | | `benchmark` | On `main` push or PRs labeled `perf`: `pnpm bench:json`, PR regression compare (25% threshold) | ### `pr-checks.yml` (`PR Checks` workflow) Runs only on pull requests to `main`. Complements `ci.yml` with commit linting, bundle size limits, knip, and doc link checks. It does **not** run unit or visual tests. | Job | What it runs | | ------------- | --------------------------------------------------------------------------- | | `quality` | `pnpm format:check`, `pnpm lint`, `pnpm typecheck`, `pnpm spellcheck`, knip | | `commitlint` | Conventional Commits validation for PR commits | | `bundle-size` | `pnpm build`, declaration tooling check, gzipped ESM size gate | | `docs-links` | Markdown link check for `docs/` and `README.md` | ### Visual regression (not in CI) `pnpm test:visual` requires Chrome with WebGPU and is **not** executed in GitHub Actions. Run it locally before merging renderer, palette, or post-process changes; use `pnpm test:visual:update` when baselines change intentionally. `pnpm preflight` does not include visual tests. --- ## See Also | Guide | What it covers | | ------------------------------------- | ------------------------------------------ | | [API: Palette](api-palette.md) | palette APIs and effect signatures | | [Palette Guide](palette-guide.md) | palette-first workflow and refresh rules | | [Palette Presets](palette-presets.md) | exact built-in preset and HUD color values | | [API: Assets](api-assets.md) | indexed sprite setup and palette offsets |