Skip to main content

Testing Strategy

This document covers the actual test organization, harness patterns, and coverage priorities in this repository.

Test infrastructure

LayerFrameworkLocationRun command
Unit / componentJest + React Testing Librarytest/unit/CI=true npm test -- --watchAll=false
End-to-endPlaywright (chromium + smoke)test/e2e/npm run test:e2e
Build verificationVite production buildnpm run build

Jest configuration

  • Test root: test/unit/ (configured in jest.config.cjs)
  • Pattern: **/*.test.{ts,tsx}
  • Transformer: ts-jest via jest.config.cjs
  • Setup: src/setupTests.ts provides:
    • @testing-library/jest-dom matchers
    • window.matchMedia polyfill for responsive/media-query code
    • Custom IntersectionObserver stub that synchronously reports elements as intersecting (so animated content is visible during tests)

Playwright configuration

  • Test directory: test/e2e/
  • Projects: chromium for route/browser coverage and smoke for production smoke coverage
  • Serves production build on port 3100 via serve -s build -l 3100
  • Shared worker count is owned by playwright.config.ts (currently 4); docs, workflows, and nested instructions should rely on that default unless a job intentionally overrides it
  • Retries: 2 in CI, 0 locally
  • Build variant: npm run build:e2e sets REACT_APP_RUNTIME_ENV=test for the Chromium Playwright bundle and any test-runtime-specific behavior
  • Standard command shapes:
    • full local suite: npm run test:e2e
    • chromium project: npm run build:e2e && npm run test:e2e:chromium
    • smoke project: npm run build && npm run test:e2e:smoke
    • headed debugging for chromium coverage: npm run test:e2e:headed
    • UI runner for chromium coverage: npm run test:e2e:ui
    • specific chromium spec: npm run build:e2e && npm run test:e2e:chromium -- test/e2e/<spec>.ts
    • specific smoke spec: npm run build && npm run test:e2e:smoke -- test/e2e/smoke.spec.ts

Validation ownership

This document is the canonical source for:

  • repo-standard Playwright, Jest, and build command shapes
  • build-variant requirements such as npm run build:e2e
  • change-type validation expectations referenced by AGENTS.md, CONTRIBUTING.md, and scoped instruction files
  • browser-validation expectations for UI-affecting changes

When another instruction file says to validate a change, it should point here rather than restating the full matrix or command list.

Validation matrix

Change typeRequired validation
Data module onlynpm run build + targeted route or consumer validation
Page-level UInpm run build + browser validation on the changed route
Shared componentnpm run build + browser validation on a primary consumer and at least one additional consumer when reuse is clear
Motion/animationnpm run build + validation with motion intensity off + browser validation
Theme/stylingnpm run build + browser validation in both light and dark modes; when shared appearance treatment changes, check at least two presets
Route/navigation/not-foundnpm run build + direct-navigation check + relevant Playwright route coverage when present
Runtime-environment-sensitive behaviornpm run build:e2e + relevant Playwright or targeted validation
GitHub-backed CV/fallback datanpm run build + mocked /cv success/failure coverage when present + browser validation
Asset-path or media-path changenpm run build + direct-navigation check + PUBLIC_URL compatibility check

Browser validation expectations

  • Use the webdev browser tooling for route rendering, layout inspection, and screenshots when UI is affected.
  • Validate the smallest set of affected routes first, then expand to another consumer when a shared component or layout primitive changed.
  • Check at least one narrow/mobile viewport and one desktop viewport for layout-affecting edits.
  • When Playwright E2E coverage exists for the touched behavior, run the narrowest relevant spec after the appropriate build variant.
  • Use npm run build:e2e before Playwright Chromium coverage when the touched behavior depends on the test runtime.
  • Prefer mocked /cv coverage over live GitHub API-dependent validation when that workflow is available.
  • If browser tooling is unavailable, run the narrowest fallback validation and report browser validation as deferred.
  • Close browser sessions when validation is finished.

Test organization

Unit test patterns

Component tests

Components are tested with render() + screen from React Testing Library. Key patterns:

  • Mock dependencies via jest.mock() for child components, hooks, and imports
  • ThemeProvider wrapper for any component that needs theme context
  • Data attributes (data-testid, data-delay, data-component-is-tilt) for prop assertions in mocked children
  • Proxy render in mocked components to capture and assert on passed props

Provider tests

Root providers are tested for:

  • Context value defaults
  • State update behavior (toggle, set)
  • localStorage persistence (read on mount, write on change)
  • Child rendering

Hook tests

Data hooks are tested for:

  • Correct data transformation from source modules
  • Sorting, filtering, and lookup behavior
  • Edge cases (empty data, missing slugs)
  • Deterministic ordering (e.g., blog tags sorted by frequency with alphabetical tie-break)

Utility tests

Pure functions in src/utils/ have straightforward input → output test suites.

E2E test patterns

Route specs

Each route spec covers:

  • Page render and basic content visibility
  • Navigation from the route
  • Key interactive behavior (if applicable)
  • Screenshot comparison (home page)

GitHub API mocking

test/e2e/helpers/github.ts provides page.route() intercepts for GitHub API responses:

  • Success state (mocked profile + contributions data)
  • Error state (simulated API failure)
  • Validates that fallback content renders when the API fails

Route readiness

test/e2e/helpers/routeReadiness.ts provides helpers to wait for route-specific signals before asserting (e.g., waiting for data to load, animations to settle).

What kinds of behavior are protected

Currently tested

CategoryWhat's coveredTest layer
Provider stateTheme toggle, appearance, motion intensity, audio consent, palette stateUnit
Route renderingAll 6 routes render expected contentUnit + E2E
Component APIsProps, conditional rendering, data-driven contentUnit
Data hooksSorting, lookup, transformation, edge casesUnit
GitHub fallbackSuccess and error API states, fallback renderingE2E
Runtime environment resolutionREACT_APP_RUNTIME_ENV override and NODE_ENV fallback behaviorUnit (constants)
CV story modeStory mode activation, scroll progress, active-section tracking, exit controlsUnit
Not-found recoveryRecovery panel renders with contextual suggestionsUnit + E2E
Animation component contractsDelay props, tilt flag, visibility callbacksUnit
Style builder outputsBuilder functions execute without error against themeUnit

Not currently tested (gaps)

CategoryWhy it mattersRecommended approach
Motion intensity visual regression by levelVerifies off/subtle/default/expressive remain visually correct across routesE2E screenshot comparison per intensity level
Theme appearance preset visual regressionVerifies all 6 presets keep rendering quality across routes and color modesE2E screenshot comparison across presets and mode
Full cross-route accessibility auditCatches keyboard/focus/semantics issues outside the currently targeted helpersPeriodic manual or automated a11y sweeps
Performance budget enforcementPrevents route and shared-bundle growth from regressing initial-load experienceCI size budget or Lighthouse/WebPageTest gate

Testing motion-heavy features

Motion is the highest regression risk in this codebase. Key principles:

Test behavior, not cosmetics

  • Assert that animated components become visible (the IntersectionObserver stub ensures this in unit tests)
  • Assert that delayMs and stagger props are passed correctly
  • Assert that skipEntranceAnimation and visible overrides work
  • Do not assert on specific pixel positions or keyframe values

Test the scaling contract

  • Verify that components respect useMotionScale() — when motion is off, elements should render instantly
  • Verify that prefers-reduced-motion forces the off scale

Test interaction state machines

  • Tab open/close transitions
  • Accordion expand/collapse
  • Story mode scroll progress + active-section transitions
  • IDE window state changes (normal → minimized → expanded)

Use the IntersectionObserver stub

src/setupTests.ts stubs IntersectionObserver to synchronously report intersection. This means:

  • Viewport-triggered animations fire immediately in tests
  • You don't need to scroll or wait for intersection
  • Tests see the final visible state, not the hidden initial state
  • Story-mode tests can simulate multiple intersecting sections in one callback to verify deterministic active-section selection

Testing boundaries

Regression risks specific to this codebase

RiskWhat breaksHow to catch it
Broken motion handoffsTypewriter doesn't start, sections never revealE2E route specs + unit delay prop tests
Tab/drawer lifecycleContent stays mounted when it should unmount, re-render flickerUnit tests with state toggling
Route transition issuesBlank pages during navigation, stale contentE2E navigation tests
Theme driftHardcoded colors/spacing bypass theme, look broken on preset switchUnit style builder tests + E2E visual regression
Component API breaksChanged prop names or defaults affect multiple consumersUnit tests per component + consumer integration tests
Composition breakageShared primitives render incorrectly when composed togetherUnit render tests with full ThemeProvider wrapper
Route exposure driftHeader, command palette, and smoke expectations disagree on public routesUnit route-registry tests + production smoke coverage

Running tests

# Unit tests (all)
CI=true npm test -- --watchAll=false

# Unit tests (specific file)
CI=true npm test -- --watchAll=false --testPathPattern=AnimatedContentList

# Build verification
npm run build

# E2E tests (full local suite; rebuilds between chromium and smoke)
npm run test:e2e

# E2E tests (chromium project)
npm run build:e2e
npm run test:e2e:chromium

# E2E tests (production smoke project)
npm run build
npm run test:e2e:smoke

# E2E tests (headed chromium debugging)
npm run test:e2e:headed

# E2E tests (specific chromium spec)
npm run build:e2e
npm run test:e2e:chromium -- test/e2e/cv.github.spec.ts

# E2E tests (specific smoke run)
npm run build
npm run test:e2e:smoke

Note: CI=true npm test -- --watchAll=false may show baseline failures in existing CV tests unrelated to your changes. Focus on regressions in the files you changed.

Further reading