Testing Guide
Framework
Vitest across all packages — TypeScript-native, ESM-first, fast.
| Package | Environment | Key Libraries |
|---|---|---|
workers/api | node | Vitest, Hono app.request() |
apps/web | jsdom | Vitest, React Testing Library, @testing-library/jest-dom |
packages/shared | node | Vitest |
Directory Structure
workers/api/src/__tests__/ __mocks__/env.ts # KV, R2, Env factories __fixtures__/html-samples.ts middleware/auth.test.ts routes/auth.test.ts routes/files.test.ts routes/convert.test.ts routes/export.test.ts routes/benchmark.test.ts services/wcag-validator.test.ts services/html-analyzer.test.ts services/ux-optimizer.test.ts services/math-detector.test.ts services/claude-converter.test.ts services/marker-converter.test.ts services/unpdf-claude-converter.test.ts services/mathpix-pdf.test.ts services/image-enhancer.test.ts services/benchmark-orchestrator.test.ts services/axe-validator.test.ts services/pdf-generator.test.ts utils/crypto.test.ts utils/response.test.ts index.test.ts
apps/web/src/__tests__/ setup.ts # RTL + jsdom + matchMedia mock lib/utils.test.ts lib/mathpix-storage.test.ts lib/api.test.ts lib/auth-context.test.tsx components/upload/DropZone.test.tsx
packages/shared/src/__tests__/ constants.test.ts benchmark-types.test.tsRunning Tests
# All packages (via Turborepo)npm run test:run
# With coveragenpm run test:coverage
# Watch modenpm test
# Single packagecd workers/api && npx vitest runcd apps/web && npx vitest runcd packages/shared && npx vitest run
# Browser integration tests (real Puppeteer, requires internet for axe-core CDN)cd workers/api && npm run test:browserBrowser Integration Tests
Browser tests live in workers/api/src/__tests__/integration/ and are excluded from the default vitest run. They use real Puppeteer (bundled Chromium, installed automatically with npm install) and axe-core loaded from CDN.
Run them explicitly before a deployment or major release:
cd workers/api && npm run test:browserWhat they test:
- 10 axe-core end-to-end scenarios (real violations detected in a real browser)
- 2 resize text checks — 1.4.4 (responsive passes, fixed-width fails)
- 2 text spacing checks — 1.4.12 (normal HTML passes, fixed-height clip fails)
- 2 focus order checks — 2.4.3 (natural order passes, positive tabindex disruption warned)
Requirements:
- Puppeteer Chromium binary (auto-installed with
npm install) - Internet access (axe-core loaded from
cdnjs.cloudflare.com) - ~30 seconds to run all 16 tests
Config: workers/api/vitest.browser.config.ts — 60 second test timeout.
Mock Strategy
| Dependency | Approach |
|---|---|
| Cloudflare KV | In-memory Map-backed mock (createMockKV()) |
| Cloudflare R2 | In-memory Map-backed mock (createMockR2()) |
| Cloudflare Puppeteer | vi.mock('@cloudflare/puppeteer') with mock page/browser |
| Anthropic SDK | vi.mock('@anthropic-ai/sdk') with mock messages.create |
| unpdf | vi.mock('unpdf') |
| External fetch (Mathpix, Marker) | vi.stubGlobal('fetch', mockFetch) |
| Supabase client | vi.mock('@/lib/supabase') |
| localStorage | jsdom’s built-in window.localStorage |
| Auth (JWTs) | Real HMAC-SHA256 signed JWTs via createTestJwt() helper |
Adding New Tests
- Create a
.test.ts(or.test.tsx) file in the appropriate__tests__/directory - Import from
vitest:{ describe, it, expect, vi, beforeEach } - Use existing mock factories from
__mocks__/env.tsfor API tests - For route tests, use the
createTestJwtpattern from existing route tests - Run
npx vitest runin the package to verify
Coverage Targets
| Area | Target |
|---|---|
workers/api/src/services/ | 80% |
workers/api/src/routes/ | 70% |
workers/api/src/middleware/ | 85% |
workers/api/src/utils/ | 90% |
packages/shared/ | 95% |
apps/web/src/lib/ | 70% |
apps/web/src/components/ | 60% |
| Overall | 75% |