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:
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:
- Checkout.
- Set up pnpm
10.28.2. - Set up Node from
.nvmrc. pnpm install --frozen-lockfile.pnpm run typecheck.pnpm run lint.
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 aloadSource helper:
- Read one TypeScript source file.
- Transpile it to CommonJS with
ts.transpileModule. - Run it in a
vmcontext whoserequireonly returns the mocks the test passes in. Any other import throwsUnexpected dependency.
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
loadSourcehelper from an existing script. - Mock only what the file imports. For React Native, a small object with
Platformis 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/andpatches/has no automated test. - RTL layout, keyboard behaviour, Live Activity and widgets can only be confirmed on a device or simulator.
Before you push
pnpm typecheckpnpm lint- The test scripts for any
lib/file you changed. - If you added a route, start the dev server once so the typed routes include it.
- If you added a native dependency, read Guarding native imports and run
pnpm preview:exportto confirm the web export still builds.