Unit/integration tests that don't map 1:1 onto a single component (those
live in __tests__/ next to the component instead — see
docs/testing-guide.md for the full picture
of how testing works across this repo). Everything here runs under Jest
(npm run test), same as the co-located component tests.
tests/
├── api/ fetchWithTimeout / retry / cancellation behavior of lib/api/client.ts
├── components/ a reference hook + its test (see note below)
├── contracts/ OpenAPI schema-drift check against lib/api/client-contracts.ts
├── exports/ credential/verification-link export safety (filenames, no leaked PII)
├── fixtures/ shared JSON fixtures, organized by feature (qr/, ...)
├── qr/ VerifyScan component: QR decode states, safety, keyboard recovery
├── security/ CSP/security-header policy and external-URL safety checks
└── proof-input.test.ts proof-ID / verification-link parsing (lib/validation/proof-input.ts)
npm run test # whole repo, including tests/
npm run test -- tests/qr # just one directory
npm run test -- tests/proof-input.test.ts # just one filePlain JSON, imported directly (import x from "@/tests/fixtures/...",
using the @/ path alias — works because jest.config.mjs resolves it
the same way the app does). tests/fixtures/qr/ is the current example:
image-states.json— syntheticBarcodeDetectoroutcomes (blurred, rotated, multiple-codes, oversized payload) since raster QR decoding isn't available under jsdom;tests/qr/verify-scan.test.tsxmockswindow.BarcodeDetectorand feeds it these fixtures to exerciseVerifyScan's error/recovery states deterministically.malicious.json— adversarial QR payloads (e.g. non-httpsschemes, wrong origin) used to assert the scanner rejects them instead of navigating.round-trip.json— a valid payload used to assert the happy path.
Each feature area under tests/ that needs fixtures keeps its own
fixtures/ subdirectory (tests/contracts/fixtures/,
tests/exports/fixtures/, tests/security/fixtures/) rather than
sharing one flat pool — keep that convention: put new fixture JSON next
to the tests that use it, not in the top-level tests/fixtures/, unless
it's genuinely shared across more than one of these directories the way
the QR fixtures currently aren't.
tests/qr/verify-scan.test.tsx doesn't mock Freighter directly (QR
scanning and verification don't require a wallet), but it's the
reference for mocking browser APIs that don't exist in jsdom the same
way you'd mock Freighter: define the missing global right on window
inside beforeEach, and restore the original in afterEach.
function mockDetector(detect: (source: unknown) => Promise<Array<{ rawValue: string }>>) {
Object.defineProperty(window, "BarcodeDetector", {
configurable: true,
writable: true,
value: class {
detect = detect;
},
});
}For a component that actually calls @stellar/freighter-api (i.e.
anything downstream of CreateProofFlow's wallet-connect step), see
"Mocking Freighter (unit-test level)"
in the main testing guide — the short version is: seed
localStorage["earnproof.session"] directly to skip past wallet-connect
UI entirely for tests that only care about what happens after
authentication, and reach for the Playwright freighter fixture
(e2e/fixtures/freighter-mock.ts) for anything that needs to exercise
the actual wallet-connect handshake.
tests/components/use-api-data.ts is a standalone example hook
(useApiData) demonstrating the correct request-cancellation pattern on
top of fetchWithTimeout — it documents itself as "Example hook
demonstrating the correct cancellation pattern" and isn't imported by any
route or component in app/ or components/. Its test,
use-api-data.test.tsx, is really testing the pattern (abort on unmount,
abort on dependency change, timeout handling), not a shipped feature. If
you're building a new data-fetching hook, this is the pattern to copy;
don't be surprised that grepping the app for its usage comes up empty.
Flagging these so they aren't mistaken for something a new test change broke:
tests/api/timeout-retry-cancel.test.tsfails to even parse today — it contains a genuine syntax error (new Promise(() {}), missing the=>) at several call sites, plus it's written againstvitest's API (import { vi } from 'vitest') while everything else in this repo runs under Jest. Both are pre-existing; a fix needs to correct the syntax error(s) and either port thevi.*calls to theirjest.*equivalents or move the file to a vitest-run location — worth its own follow-up rather than folding into an unrelated change.tests/components/use-api-data.test.tsxhas the samevitestimport as above and fails for the same reason.
Run npm run test -- --runInBand (what CI runs) locally before opening a
PR to confirm you haven't added to this list.