The mobile repo has no Jest, no Vitest and no end-to-end suite. Quality rests on strict TypeScript, ESLint, and a set of plain Node test scripts that exercise pure logic and a few guard rails.

Commands

There is no test script in package.json.

TypeScript

tsconfig.json extends expo/tsconfig.base with strict: true and two path aliases: It includes .expo/types/**/*.ts and expo-env.d.ts. Both are generated and ignored by Git.

Typed routes go stale

.expo/types/router.d.ts holds the union of valid routes. Expo only regenerates it when the dev server starts. After pulling a commit that adds a route, pnpm typecheck can fail on a router.push or href with a “not assignable” error while the route file plainly exists. Check before changing any code:
If the route is missing from the file, start the dev server once, let it write the types, stop it, and run the type-check again. Do not cast the href and do not edit the generated file.

Locale keys

strings is typed as typeof en. A key added to en.ts and not to the other five locale files fails the type-check, which is the only guard against a missing translation.

ESLint

eslint.config.js is a flat config built on eslint-config-expo/flat, ignoring dist/*. no-redeclare is off so a constant object and its derived type can share a name, as AuthStatus and ApiErrorCode do. The React Compiler rules from eslint-plugin-react-hooks 6 are set to warn. The compiler itself is on (experiments.reactCompiler in app.json), so these warnings point at code the compiler may skip optimising.

Prettier

.prettierrc.json: 100 column width, single quotes, trailing commas everywhere, semicolons, 2 space indent, always parenthesise arrow parameters, LF line endings. .prettierignore excludes build output, native folders and one source file: src/features/nutrition/lib/food-units.ts. Its comment in the ignore file calls it a byte-identical mirror of the web app’s food-units.ts. That file is a copy of the web app’s file and is kept identical on purpose. Do not reformat it. Change it in both repositories together.

Git hooks

Husky hooks in .husky/: .lintstagedrc.json runs prettier --write and eslint --fix on staged .ts and .tsx files, and prettier --write on staged JSON and Markdown. mobile/docs/CI_AND_HOOKS.md says pre-push also runs the type-check. The hook file only runs lint. Run pnpm typecheck yourself before pushing.

Commit messages

commitlint.config.cjs extends the conventional config:

CI

.github/workflows/ci.yml runs on every pull request and on pushes to main. One job, on Ubuntu:
  1. Checkout.
  2. Set up pnpm 10.28.2.
  3. Set up Node from .nvmrc.
  4. pnpm install --frozen-lockfile.
  5. pnpm run typecheck.
  6. pnpm run lint.
CI does not run the test scripts and does not build the app.

Test scripts

scripts/test-*.cjs are standalone files that use node:test and node:assert. No package script wraps them and the repository does not document a command. They follow the standard runner, so they are run directly with Node:

How they load app code

The scripts do not use a bundler. Each defines a loadSource helper:
  1. Read one TypeScript source file.
  2. Transpile it to CommonJS with ts.transpileModule.
  3. Run it in a vm context whose require only returns the mocks the test passes in. Any other import throws Unexpected dependency.
That makes each test explicit about what the file depends on. It also means a new import in a tested file breaks its test until the mock list is updated. That is intended: it flags a pure helper that has started pulling in React Native. It is the reason logic is kept in lib/ files with few imports. See Feature modules.

What exists

One more test sits beside its source: src/lib/phone/phoneNumber.test.mjs, which imports phoneNumber.ts directly and checks how Israeli and foreign numbers are joined and split.

Writing a new one

  • Put the logic in a lib/ file that imports as little as possible.
  • Copy the loadSource helper from an existing script.
  • Mock only what the file imports. For React Native, a small object with Platform is usually enough.
  • Name each test as a sentence describing behaviour, as the existing ones do. For example: “a late 401 for a token that was already replaced does not sign the new session out”.

What is not covered

  • No component or screen rendering tests.
  • No end-to-end tests on a device.
  • Native code in modules/, plugins/ and patches/ has no automated test.
  • RTL layout, keyboard behaviour, Live Activity and widgets can only be confirmed on a device or simulator.
When a change touches those areas, say in the pull request what you checked by hand and on which platform.

Before you push

  1. pnpm typecheck
  2. pnpm lint
  3. The test scripts for any lib/ file you changed.
  4. If you added a route, start the dev server once so the typed routes include it.
  5. If you added a native dependency, read Guarding native imports and run pnpm preview:export to confirm the web export still builds.