Sets up Jest, checks unit, component and route tests (RNTL 14, renderWithProviders, SDK mocks, fakes, fast-check, Stryker). Use when writing a test or mock, or Jest fails or flakes. Not for goldens (golden-and-snapshot-tests).
3 stars
0 votes
0 copies
0 views
Added October 18, 2026
developmenttypescriptgosqlreactnodetestinggitapi
Works with
api
Security analysis
A92/100
mediumInstalls packages at runtime which could introduce malicious dependencies
Installs into .claude/skills of the current project.
Are you the author of Unit And Component Tests?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/zakariaf-unit-and-component-tests)
---
name: unit-and-component-tests
description: Sets up Jest, checks unit, component and route tests (RNTL 14, renderWithProviders, SDK mocks, fakes, fast-check, Stryker). Use when writing a test or mock, or Jest fails or flakes. Not for goldens (golden-and-snapshot-tests).
---
# Unit and component tests
Every store, service, hook, component, route and pure module of an Expo app is proven in Jest (jest-expo, React Native Testing Library 14, fast-check, Stryker) through one root configuration, with components rendered inside the app's real providers on both iOS and Android presets. This skill builds that set-up, says how each kind of test is written, and its scripts prove the set-up, the test code and the mutation results.
## Rules that must hold
1. **One Jest config at the repo root, run from the root, and every test file runs in exactly one place.** Projects: `unit` (preset `jest-expo/ios`), `android` (`jest-expo/android`, `*.android.test.ts(x)` only) and `golden` (`*.golden.test.ts` only, in every app: data goldens with the unit set-up, and Skia's CanvasKit environment only while the app depends on `@shopify/react-native-skia`). Long-running simulations run only through `jest.sim.config.js`. Every test is a `<unit>.test.ts(x)` file in `src/`, `scripts/`, `plugins/` (local config plugins) or `test/` (no `__tests__/<name>-test.tsx`, no `.spec.ts`), and never inside the routes folder (`src/app/`). No second Jest config (a `package.json` `jest` key, a `jest.config.ts`). Why: Expo Router bundles every file in `src/app/` as a route, so a test there breaks `expo export` while Jest still passes; jest-expo reads Babel options from the working directory; `.android.tsx` files, `Platform.OS` branches and the hardware back button exist only under the Android preset; a CanvasKit environment without Skia fails every Jest run, and a golden project kept behind Skia silently never ran data goldens, and neither do tests in Expo's guide layout under this config; two configs stop Jest with "Multiple configurations found" (all verified).
2. **Exact test-tool pins and one React.** jest 29.7.0 (Expo's line), jest-expo from `expo/bundledNativeModules.json`, `@react-native/jest-preset` equal to `react-native`, RNTL 14.0.1 installed in the same command as test-renderer 1.2.0 (its minor equals React's), fast-check 4, the three Stryker packages on one version; `react` and `react-native` exact (when multi-package: root `overrides`); no `react-test-renderer`. Why: npm alone picked test-renderer 1.3.0 (React 19.3), a monorepo without overrides hoisted a second React for the tests, and React 19 deprecates `react-test-renderer`, which jest-expo still installs, so a test importing it keeps passing unnoticed (verified).
3. **Import through the path aliases**, `@/` to `src/` and `@test/` to `test/`, mirrored in `moduleNameMapper`; when multi-package, another workspace by its package name, mapped to its `src/`. Why: jest-expo derives aliases only from the working directory's `tsconfig.json`, and through workspace symlinks Stryker mutates a copy the tests never run (verified).
4. **Await every RNTL call** (`render`, `renderHook`, `act`, `waitFor`, `waitForElementToBeRemoved`, `fireEvent`, `findBy*`, `user.*`, the render-helper functions, and `rerender`/`unmount` on what a render returned). Query `getByRole` first, `findBy*` for what appears later, `queryBy*` only to prove absence; press through `userEvent.setup()`. Why: RNTL 14 is async; an un-awaited call runs after the assertions.
5. **Render every component and hook through the render helper** (`renderWithProviders`, `renderHookWithProviders`, `createProvidersWrapper` from `@test/render-with-providers`), and every route test through `renderRouterWithProviders` (`@test/render-router-with-providers`), never Expo Router's `renderRouter`. The helpers build the app's real providers fresh for each render from `test/test-app.tsx`: catalogs where a missing message throws, stores seeded through their real factories, only the services the test passes (the others throw and name themselves). They return what the tree holds. A test whose unit reads nothing from context, that supplies the provider under test itself (`<I18nProvider>` around `<T />`), or that proves a hook's no-provider default keeps plain `render`/`renderHook` with `// no-providers: <why>` in the file. Assert visible text, roles and accessible names; assert styles only in the design-system folders or under `// allow-style-assertion: <why>`; no component snapshots. Why: tests see what the app, VoiceOver and TalkBack see, and survive refactors; with RNTL 14, `renderRouter`'s matchers throw and it leaves fake timers on (verified).
6. **Native SDKs only through root `__mocks__/<sdk>.ts`.** An adapter test pairs `jest.mock('<sdk>')` with a locally typed `jest.requireMock`; no test imports the SDK. Skia is mocked once, in `jest.setup.ts`. Why: these SDKs crash on import in Jest, and `requireMock` without `jest.mock` reads a second instance with zero calls (verified).
7. **Everything above an adapter is tested through fakes** (`fake-<port>.ts`: a small real implementation with a shared call log); store state changes only by seeding and through the store's own actions, never by mocking a store module. Why: fakes make service rules millisecond tests and prove cross-service order with one assertion.
8. **Tests are deterministic.** `TZ=UTC` in every Jest config, an injected `ClockPort` and seeded RNG, `flushMicrotasks()` instead of sleeps, fake timers only for UI timers, no retries, and Android behaviour in `<unit>.android.test.ts(x)`, never by overwriting `Platform.OS`. Why: a flaky test is a bug; an overwritten `Platform.OS` leaks into later tests and skips `.android` files.
9. **Pure logic gets examples, fast-check properties and one pinned value**; a counterexample becomes an example test before the fix. Why: properties alone left 3 of 8 mutants of a random-number generator alive; the pinned sequence killed them.
10. **Tests that need Node APIs live under the root `test/`** (`node:sqlite`, `node:fs`, `Buffer`). Why: the app program has only `jest` types and lint bans `node:*` imports in `src/`.
11. **Coverage 90/90/90/85 globally and 95/95/95/90 for the logic keys; mutation score at least 75%, measured on mutants a test really ran**, survivors killed by boundary examples. Never lower a threshold, never `istanbul ignore`, never `// Stryker disable`. Why: one agent writes code and tests; coverage stood at 97.8% while the mutation score was 77.1%, and Stryker passes a run whose every mutant was skipped as static (score NaN, verified); Stryker obeys a `// Stryker disable` comment and reports those mutants as Ignored, outside the score, so quality-gates' bypass check fails both comments.
12. **Every source file ships with a test or starts with `// device-only: covered by <check>`** in its first 6 lines, and only native adapters, Skia and frame-callback modules, composition files and tooling entry points may carry it. `jest.config.js` leaves exactly the marked files out of coverage through the tested helper `scripts/quality/device-only.ts`; fakes and pure helpers always get tests. A route file in `src/app/` meets this through the route test that renders its default export from `src/features/` (never a test beside it) or the marker naming the flow that opens it. Why: untested composition files held a real app at 83.8% statements, and a hand-written exclusion hides real code.
## Workflow
1. **Check the set-up** from the repo root: `node ${CLAUDE_SKILL_DIR}/scripts/check-test-setup.mjs .`. For each missing piece copy the template of the same path from `templates/` (root configs, `scripts/quality/`, `__mocks__/`; `babel.config.js` as it is: its Stryker override compiles only the files Stryker instrumented without the Worklets and Reanimated plugins, see step 8), and merge `assets/package.test-deps.json` into `package.json` (scripts and exact devDependency pins). If the app followed Expo's unit-testing guide, delete its `"jest": { "preset": "jest-expo" }` key from `package.json` and its `"test": "jest --watchAll"` script: `jest.config.js` is the one config. The guide also puts tests in `__tests__/<name>-test.tsx`, which no project runs: move each next to its unit as `<unit>.test.tsx` and remove `react-test-renderer`, as the guide itself says (`jest-orphan-tests` and `test-dep-deprecated` name each one). Install the Testing Library with its companion in one command: `npm install -D --save-exact @testing-library/react-native@14.0.1 test-renderer@1.2.0`. `tsconfig.stryker.json` extends `tsconfig.base.json` from typescript-and-lint-rules' tsconfig set: adopt that set first, or point its `extends` at `./tsconfig.json` (`stryker-tsconfig` names it). Tests another runner owns (Playwright for the web build, Cypress, Deno under `supabase/functions/`) that the checker cannot detect go into `contracts/test-setup.json` `otherRunnerGlobs`. Read [references/jest-config.md](references/jest-config.md) before changing any config; the configs and the device-only helper are gated files (quality-gates).
2. **Wire the render helpers once.** Copy `templates/test/`: `render-with-providers.tsx` with its test, `test-app.tsx`, `flush-microtasks.ts`, `play-actions.ts` with its test, `render-router-with-providers.tsx` with its test when the app uses Expo Router (an app on React Navigation has no `expo-router/testing-library`; it follows the appendix of [references/component-tests.md](references/component-tests.md) instead), and `skia-mock.test.tsx` when the app depends on Skia. `test-app.tsx` already wraps `ServicesProvider` over `createTestServices(options.services)`, the composition root of architecture-and-boundaries, and takes the `language` and `direction` options (text locale, layout direction); each file it imports that the app lacks comes from `templates/src/` (synced copies; never over an existing file such as the app's `languages.ts`). Then grow `test/test-app.tsx` with the app's providers, in the order the root layout uses them, as each lands: catalogs, store factories with a seed, theme. [examples/test-app.tsx](examples/test-app.tsx) shows a Tasks app fully wired.
3. **Pick the kind of test** and read its reference once per session:
- component, screen or hook: [references/component-tests.md](references/component-tests.md); models `templates/test/render-with-providers.test.tsx` and [examples/use-tasks-model.test.tsx](examples/use-tasks-model.test.tsx);
- navigation (a link, a deep link, a redirect, back): the same reference, section "Routes and navigation"; model `templates/test/render-router-with-providers.test.tsx`;
- Android-only behaviour (an `.android.tsx` file, the hardware back button, a `Platform.select` branch): the same reference, section "Android behaviour"; model [examples/task-editor.android.test.tsx](examples/task-editor.android.test.tsx);
- adapter over a native SDK, fake, service or policy: [references/mocks-and-fakes.md](references/mocks-and-fakes.md); models [examples/expo-iap-purchase-adapter.test.ts](examples/expo-iap-purchase-adapter.test.ts), [examples/fake-purchase.ts](examples/fake-purchase.ts), [examples/premium-service.test.ts](examples/premium-service.test.ts);
- reducer, policy or other pure logic: [references/properties.md](references/properties.md); model [examples/task-list-reducer.test.ts](examples/task-list-reducer.test.ts).
4. **Place and name the test**: next to its unit as `<unit>.test.ts(x)` (`<unit>.android.test.tsx` for Android-only behaviour, `test/integration/<area>/` when it needs Node), never inside `src/app/`: a route test goes to `src/features/<feature>/<feature>-navigation.test.tsx`. Titles read as the spec and pass lint (`jest/valid-title` `^(can|[a-z]+s)\b`): `describe('<exported name>')`, optional `describe('when …')`, then `it('clears …')`, never `it('should …')`; no `it.skip`, `it.only`, `xit`.
5. **Run the one file and read the failure**: `npx jest <file> --ci --selectProjects unit` (`--selectProjects android` for an Android test; paths always before the flag). Red must be an assertion diff, not an import or type error.
6. **Make it green, then check the test code**: `node ${CLAUDE_SKILL_DIR}/scripts/check-test-code.mjs .`. Fix each `FAIL` line (file, rule and fix are printed) and rerun until `RESULT: PASS`. A unit that really reads nothing from context (or a test of the provider itself or of a no-provider default), a timer helper, or a style that is the contract gets the reasoned marker the fix names (`no-providers`, `allow-fake-timers`, `allow-style-assertion`), not a rewrite; `--help` lists them.
7. **Run the whole suite with coverage**: `npm run test:coverage`. Below a threshold, add tests for the uncovered behaviour ([references/coverage-and-mutation.md](references/coverage-and-mutation.md)); a native adapter, Skia or frame-callback module, composition file or tooling entry point gets the device-only marker instead, naming the check that covers it. For a subset, put the paths first and switch the thresholds off: `npx jest <paths> --ci --selectProjects unit --coverage --collectCoverageFrom='<path>/**/*.ts' --coverageThreshold='{}'`.
8. **Mutation-test finished logic** (a reducer, policy, repository or `src/core` module): `npx stryker run --mutate <file>`, then `node ${CLAUDE_SKILL_DIR}/scripts/check-mutation-report.mjs . --changed <file>`. If Stryker stops with "There were failed tests in the initial test run" and the Reanimated error "`useAnimatedStyle` was used without a dependency array or Babel plugin" while `npx jest` passes, `babel.config.js` is an old form that compiles every file without the Worklets plugin: copy `templates/babel.config.js`, whose override applies only to the files Stryker instrumented ([references/jest-config.md](references/jest-config.md), section babel.config.js). Kill each survivor with a boundary example and rerun both; an equivalent mutant is accepted only with `--allow <id>` and a sentence in the change report. Nightly and before a release: `npm run test:mutation`, then the same check without `--changed` (it also fails any file none of whose mutants was tested).
9. **Rerun the three checks** until each prints `RESULT: PASS`.
## Definition of done
- [ ] `npx jest --ci` passes with no `act(...)` warnings, and `npm ls react react-native` shows one version of each.
- [ ] Every new test sits next to its unit (an Android-only case in `<unit>.android.test.tsx`, a Node-API test under `test/`, a route test in `src/features/`, none inside `src/app/`) and was seen failing on an assertion first.
- [ ] Components and hooks render through the render helper, routes through the router helper; adapters are tested against the root mocks; services over fakes.
- [ ] `npm run test:coverage` passes (90/90/90/85 global, 95/95/95/90 logic keys).
- [ ] Finished logic modules: `node ${CLAUDE_SKILL_DIR}/scripts/check-mutation-report.mjs . --changed <file>` prints `RESULT: PASS` (score at least 75%, no survivor in the file, its mutants actually tested).
- [ ] `node ${CLAUDE_SKILL_DIR}/scripts/check-test-setup.mjs .` prints `RESULT: PASS` (it prints no SKIP line; `NOT APPLICABLE` only in a repo without `expo`).
- [ ] `node ${CLAUDE_SKILL_DIR}/scripts/check-test-code.mjs .` prints `RESULT: PASS`
## Anti-patterns
- **`render(<X />)` without the providers, or without `await`.** The component misses its catalog, stores and services, and the assertions run before React commits. Use `await renderWithProviders(<X />, options)`.
- **Mocking a store or one of the app's own modules to "make the test easy".** The test then proves the mock. Seed through the render helper's options and call the actions of the returned `stores`.
- **A bag of `jest.fn()` standing in for a port.** It cannot prove order or state. Write or reuse `fake-<port>.ts` with a call log.
- **`import { fetchProducts } from 'expo-iap'` in a test.** It crashes or reads a second instance. `jest.mock('expo-iap')` plus a typed `jest.requireMock`.
- **`await new Promise((r) => setTimeout(r, 50))`.** Slow and flaky. `findBy*` for the screen, `flushMicrotasks()` for services.
- **`Platform.OS = 'android'` in an iOS-project test.** It leaks into the next test, and `.android.tsx` files still resolve to iOS. Write `<unit>.android.test.tsx`.
- **`renderRouter(...)` with `expect(screen).toHavePathname(...)`**, as Expo's SDK 57 testing guide shows it. `renderRouter` returns RNTL's render promise with the route getters attached to the promise, so the awaited result has none; with RNTL 14 its matchers and `testRouter` throw (`getPathname is not a function`), and it leaves fake timers on for the rest of the file (verified with expo-router 57.0.24). `await renderRouterWithProviders(routes)`, then `expect(view.getPathname())`.
- **Tests in `__tests__/<name>-test.tsx` or `*.spec.ts`**, as Expo's guide and Jest's defaults allow. No project runs them, so they pass by never running. Name every test `<unit>.test.ts(x)` beside its unit.
- **`expect(view.toJSON()).toMatchSnapshot()` for a component.** It fails on every harmless change and proves nothing a user sees. Assert roles, names and state; readable data snapshots belong in a golden test.
- **`queryByText(...)` with `toBeOnTheScreen()`, or `getByText(...)` with `.not.toBeOnTheScreen()`.** The first hides a missing element's error message, the second throws before asserting.
- **`const TASKS = sortTasks([...])` above the `describe`.** The unit runs at import, Stryker marks every mutant of it static and skips it, and reports a score of NaN as a pass (verified). Call the unit inside `it()`; module-level fixtures are plain data.
- **Weakening a gate to get green**: lower coverage numbers, a break under 75, `istanbul ignore`, `Stryker disable`, `jest.retryTimes`. This skill's checkers fail the numbers and the retries; quality-gates' bypass check fails both comments. Fix the test or the code; if a gate looks wrong, stop and ask the developer.
## Files in this skill
| File | What it is | Read/run when |
|---|---|---|
| [references/jest-config.md](references/jest-config.md) | Versions and re-verify commands, file layout, Babel, `jest.config.js` part by part, `jest.setup.ts`, the simulation config, scripts, the contract, when multi-package, known failures, flaky tests | Workflow step 1, and whenever a config or pin changes or Jest fails oddly |
| [references/component-tests.md](references/component-tests.md) | RNTL 14 async model, the render helper and `test/test-app.tsx`, queries, matchers, user-event, hooks, stores, gestures, Skia, Android behaviour, styles, what not to test | Workflow steps 2 and 3 for components, screens and hooks |
| [references/mocks-and-fakes.md](references/mocks-and-fakes.md) | Root native-SDK mocks and why each exists, adapter tests with `requireMock`, fakes with call logs, services and policies over fakes | Workflow step 3 for adapters, services, policies |
| [references/properties.md](references/properties.md) | Examples + fast-check properties + pinned values, `playActions`, the property checklist, counterexamples, simulations, `node:sqlite` placement | Workflow step 3 for pure logic |
| [references/coverage-and-mutation.md](references/coverage-and-mutation.md) | Thresholds and key semantics, the device-only marker, subsets, Stryker config, reading survivors, a worked boundary example, equivalent mutants | Workflow steps 7 and 8 |
| `templates/jest.config.js` | The one Jest config: unit, android and golden projects (CanvasKit with Skia), aliases, UTC, transforms, coverage gates (synced from the library; do not edit here) | Copy to the repo root (step 1) |
| `templates/jest.setup.ts` | Gesture Handler, Worklets and Reanimated setup and the one guarded Skia mock, for unit, android and (without Skia) golden (synced; do not edit here) | Copy to the repo root (step 1) |
| `templates/jest.sim.config.js` | Plain-Node config for `*.sim.test.ts` simulations (synced; do not edit here) | Copy only when the app has simulations (game-balance-and-bots) |
| `templates/babel.config.js` | Root Babel config whose Stryker override compiles only the instrumented files without the Worklets and Reanimated plugins (synced; do not edit here) | Copy to the repo root (step 1); replaces an old all-files branch (step 8) |
| `templates/stryker.config.json` | Stryker scope, thresholds, ignore list, type-check scope, reporters (synced; do not edit here) | Copy to the repo root (step 1) |
| `templates/tsconfig.stryker.json` | The jest-only program Stryker's type checker uses (synced; do not edit here) | Copy to the repo root (step 1) |
| `templates/scripts/package.json` | Marks `scripts/` as ES modules so Node loads the helper (synced; do not edit here) | Copy with the helper (step 1) |
| `templates/scripts/quality/device-only.ts` | The one helper that turns `// device-only: covered by ...` markers into coverage exclusions (synced; do not edit here) | Copy with `jest.config.js` (step 1) |
| `templates/scripts/quality/device-only.test.ts` | The helper's test (synced; do not edit here) | Copy with the helper |
| `templates/test/render-with-providers.tsx` | `renderWithProviders`, `renderHookWithProviders`, `createProvidersWrapper` (synced; do not edit here) | Copy once (step 2) |
| `templates/test/render-with-providers.test.tsx` | The helper's own test and the model of every component and hook pattern (synced; do not edit here) | Copy with the helper; read before the first component test |
| `templates/test/render-router-with-providers.tsx` | `renderRouterWithProviders`: Expo Router's in-memory router inside the same providers, with route getters (synced; do not edit here) | Copy once when the app uses Expo Router (step 2) |
| `templates/test/render-router-with-providers.test.tsx` | Its test and the model of a route test: link press, deep link, navigation from outside React (synced; do not edit here) | Copy with the helper; read before the first route test |
| `templates/test/test-app.tsx` | The app's provider tree for tests: `ServicesProvider` over `createTestServices`, the `language` and `direction` options (react-intl that throws on a missing message, the text locale, the layout direction), a fresh TanStack Query cache (synced; do not edit here) | Copy once, then grow it in the app (step 2) |
| `templates/src/` | What `test-app.tsx` imports, synced from their owners (do not edit here): the composition root's test side (`services-context.tsx`, `create-test-services.ts`, the clock, error-log and direction-guard ports, `fake-clock.ts`, `core/dates/date-key.ts`, `core/result.ts`), `i18n/{languages,digits,language-context,direction-context}` and `theme/text-locale.tsx` | Step 2, each file only when the app has none at that path (never over the app's `languages.ts`) |
| `templates/test/flush-microtasks.ts` | Lets queued promise callbacks run without fake timers (synced; do not edit here) | Copy with the helper |
| `templates/test/play-actions.ts` | Turns fast-check integers into legal action sequences for any pure model (synced; do not edit here) | Copy before the first property test of a reducer or policy |
| `templates/test/play-actions.test.ts` | Its test, a compact property-test model (synced; do not edit here) | Copy with `play-actions.ts` |
| `templates/test/skia-mock.test.tsx` | Proves the central Skia mock (synced; do not edit here) | Copy when the app depends on Skia |
| `templates/__mocks__/` | Root manual mocks: `expo-iap`, `expo-tracking-transparency`, `react-native-audio-api`, `react-native-google-mobile-ads`, `react-native-keyboard-controller` (synced; do not edit here) | Copy the mock of each SDK the app depends on |
| `templates/contracts/test-setup.json` | The optional contract with the app's facts (render and router helpers, root mocks, logic folders), filled with the defaults | Copy when the app's logic or helper lives elsewhere |
| `templates/contracts/test-setup.schema.json` | JSON Schema of the contract | With the contract |
| [examples/test-app.tsx](examples/test-app.tsx) | `test/test-app.tsx` fully wired for a Tasks app: services, seeded stores, catalogs, theme, query cache | Workflow step 2 |
| [examples/use-tasks-model.test.tsx](examples/use-tasks-model.test.tsx) | A hook test: seeded through the helper, changed by a store action | To imitate for hooks |
| [examples/task-editor.android.test.tsx](examples/task-editor.android.test.tsx) | An Android-only test: the hardware back button under `jest-expo/android`, on a screen and on the router stack | To imitate for Android behaviour |
| [examples/expo-iap-purchase-adapter.test.ts](examples/expo-iap-purchase-adapter.test.ts) | An adapter test against a root mock with a typed `requireMock` | To imitate for adapters |
| [examples/fake-purchase.ts](examples/fake-purchase.ts) | A fake port: a mutable script, a shared call log, emitted events | To imitate for fakes |
| [examples/premium-service.test.ts](examples/premium-service.test.ts) | A service test over fakes: "save, then finish the transaction", and its failure paths (a refused save, a failed restore sync, the launch re-check, an unfinished purchase) | To imitate for services |
| [examples/task-list-reducer.test.ts](examples/task-list-reducer.test.ts) | Pure logic: examples, invalid input, determinism, invariants over any action sequence, round trip, pinned value | To imitate for reducers and policies |
| `assets/package.test-deps.json` | Test scripts, exact devDependency pins, and the React overrides for a multi-package repo | Workflow step 1 |
| `assets/shared.json` | Declares the shared files this skill copies in | When adding a shared file |
| `scripts/check-test-setup.mjs` | Checks pins and companions, one React and no `react-test-renderer`, scripts, Babel, one Jest config, the Jest projects and that every file Jest or Expo would take for a test runs in exactly one (other runners' folders left out), no test in the routes folder, aliases, transforms, coverage scope and the device-only exclusions, setup, simulations, Stryker and its tsconfig, root mocks, the render helper | Workflow step 1 and the definition of done |
| `scripts/check-test-code.mjs` | Checks every test file of the repo (`__tests__/` and `*.spec` ones too): awaits, the render helpers, queries, styles, snapshots, `react-test-renderer`, SDK imports, `requireMock`, store mocks, timers, sleeps, retries, `Platform.OS`, Expo Router's test API, imports across roots | Workflow step 6 and the definition of done |
| `scripts/check-mutation-report.mjs` | Reads `reports/stryker/mutation.json`: score against the break threshold, survivors in `--changed` files, any file (or the whole report) with no tested mutant (a types-only file, or one whose every mutant failed to compile, needs none) | Workflow step 8 |
| `scripts/lib/test-contract.mjs` | Shared by the two repo checkers: the contract, its defaults, manifests, the known root-mock SDKs, the helper names, the files Jest or Expo would take for tests, and the folders other runners own | Read when changing a checker |
| `scripts/lib/config-lines.mjs` | Finds the line of a key in `package.json`, `stryker.config.json` and `jest.config.js` (per Jest project) for check-test-setup's problem lines | Read when changing check-test-setup |
| `scripts/lib/source-scan.mjs` | Shared lexer; check-test-code uses its `maskCode` to walk call chains without being misled by strings (synced; do not edit here) | Never by hand |
| `scripts/selftest.mjs` | Proves the three checkers pass their good fixtures and catch every planted bug | After changing a checker |
| `scripts/check-lib.mjs` | Shared script helper (synced; do not edit here) | Never by hand |
| `tests/bases/` | Base trees the fixtures start from: a single Expo app, and a layer that turns it into a monorepo | When adding a fixture |
| `tests/fixtures/` | Good, pass, bad and error cases for each checker (overlay format) | When adding a rule to a checker |
## Related skills
- `typescript-and-lint-rules` - the tsconfig set (`tsconfig.base.json`) that `tsconfig.stryker.json` extends, and the lint rules tests obey.
- `tdd-workflow` - the red-green loop, test names and the order of layers.
- `golden-and-snapshot-tests` - data and pixel goldens, the golden project's matcher and when `jest -u` is allowed.
- `e2e-maestro` - what flows prove on simulators and emulators instead of Jest; the device-only reason names a flow.
- `quality-gates` - `check:fast`, `verify` and the guardrail around these configs.
- `architecture-and-boundaries` - ports, adapters, `ServicesProvider` and `createTestServices`.
- `state-management-zustand` - the store factories the test app seeds.
- `persistence-and-migrations` - the `node:sqlite` driver and migration tests under `test/`.
- `i18n-strings-and-catalogs` - the catalogs the test app loads and the bidi isolates in accessible names.
- `seeded-determinism-and-golden-vectors` - pinned sequences and independent oracles.
- `skia-canvas-rendering` - draw-call tests and the frame clock.