vercel/next.js
Jest 29 is the single test runner for JS, configured through `next/jest` itself (dogfooding) in the root `jest.config.js`, with `rootDir: 'test'` plus extra roots into `packages/next/src`, `packages/next-codemod`, `packages/font/src`, `packages/next-routing`, and `packages/eslint-plugin-internal`. Integration coverage is very large — roughly 1183 test files in `test/e2e`, 375 in `test/production`, 314 in `test/development`, 116 in `test/unit` — and every one of them runs under a mode × bundler × experimental-axis matrix selected by env vars in `scripts/run-jest.sh`. Rust has its own lane (`test-cargo-unit`, `test-cargo-unit-miri`, `rust-check`, `rustdoc-check`) in CI. The notable gap is that the harness itself, the CI orchestration in `run-tests.js`, and the deploy mode are lightly covered by anything other than running them.
- `jest.config.js` is built from `nextJest()` — the framework tests itself through its own `next/jest` transformer — and then wrapped by `withGateTransformer(await createConfig())` so the `@gate` pragma rewrite chains in front of next/jest's SWC transform rather than replacing it.
- The test matrix is expressed purely as environment variables: `scripts/run-jest.sh` maps `--mode` to `NEXT_TEST_MODE`, `--bundler` to `IS_WEBPACK_TEST` / `IS_TURBOPACK_TEST` / `NEXT_RSPACK`+`NEXT_TEST_USE_RSPACK`, `--experimental` to `__NEXT_CACHE_COMPONENTS=true`, and `--headless` to `HEADLESS=true`, then `exec jest --runInBand`.
- `__NEXT_TEST_AXIS` implements a lettered flag-matrix dimension: setting the axis implies `__NEXT_CACHE_COMPONENTS=true` and vice-versa, and a fixture opts in by keying a flag on it (`concurrentRouterQueue: process.env.__NEXT_TEST_AXIS !== 'A'`) paired with a `// @gate concurrentRouterQueue` on affected tests.
- The `@gate` registry has two tiers: `staticCondition` (run mode, bundler, React version — known at collection time and the only kind `@force-gate` accepts) and `lazyCondition` (a predicate over the fixture's *resolved* `next.config` read inside the test body); an undeclared condition name fails the whole suite at collection time, so typos cannot silently disable a gate.
- `nextTestSetup()` in `test/lib/e2e-utils/index.ts` is the mandated entry point; it internally calls `createNext()` (explicitly documented as not for direct test use), builds a `NextInstance` from one of `NextDevInstance` / `NextStartInstance` / `NextDeployInstance`, and returns a `Proxy<NextInstance>` so the instance can be referenced before it exists.
- Test timeouts are enforced by proxying jest's `it`/`test`: `wrapJestTestFn` injects a 60s `individualTestTimeout` when a test supplies no explicit timeout, while setup hooks get a longer 120s (240s on win32) budget overridable by `NEXT_E2E_TEST_TIMEOUT`.
- `retry()` in `test/lib/next-test-utils.ts` is the sanctioned wait primitive and `check()` still exists at line 844 but is deprecated by project convention — new tests are required to use `retry()` + `expect()`.
- Integration tests install a real tarball rather than symlinking: `NextInstance` calls `createNextInstall(...)` from `test/lib/create-next-install.js`, and `test/lib/pnpm-security-settings.js` copies `blockExoticSubdeps` / `minimumReleaseAge` from `pnpm-workspace.yaml` into the isolated fixture dir.
- Deploy mode shells out: `NextDeployInstance` runs `execa(process.env.NEXT_TEST_DEPLOY_SCRIPT_PATH)` and expects the deployment URL on stdout, with separate logs and cleanup scripts — so `pnpm test-deploy` needs external credentials and cannot run hermetically.
- `run-tests.js` (1164 lines) is the CI orchestrator, not jest: it reads/writes test timings to Vercel KV (`@vercel/kv`, `KV_TIMINGS_KEY = 'test-timings'`), applies `getTestFilter()`, defaults to `DEFAULT_CONCURRENCY = 2` and `DEFAULT_NUM_RETRIES = 2`, and emits `##[group]` markers when `CI` is set.
- Bundler-specific expected-failure lists are checked-in manifests — `test/rspack-build-tests-manifest.json`, `test/rspack-dev-tests-manifest.json`, `test/cache-components-tests-manifest.json`, `test/deploy-tests-manifest.json` — maintained by `test/update-bundler-manifest.js`, so 'known failing under rspack' is data, not a skip.
- JUnit reporting is opt-in via `NEXT_JUNIT_TEST_REPORT` and routes output to a bundler-specific directory (`turbopack-test-junit-report`, `rspack-test-junit-report`, or `test-junit-report`), deliberately not counting retries so flaky-but-passing tests still report as failed.
- Unit tests are colocated with source and run separately: `pnpm test-unit` targets `test/unit/`, `packages/next/`, and `packages/font`, picking up files like `packages/next/src/server/is-private-ip.test.ts`, `config.test.ts`, `load-manifest.test.ts`, and `patch-fetch.test.ts`.
- CI runs a distinct lint/type lane separate from tests: `pnpm lint` parallelises `test-types`, `lint-typescript`, `prettier-check`, `lint-eslint`, `lint-ast-grep`, `lint-language` (alex), and `check-unused-turbo-tasks`, plus a `types-and-precompiled` job that also type-checks every `crates/next-custom-transforms/tests/fixture/**/tsconfig.json`.
- `createRouterAct` / `LinkAccordion` exist as a dedicated harness for deterministically controlling prefetch timing in router tests (`test/lib/router-act.ts`), which is the documented way to avoid flakiness from BFCache-triggered re-prefetches.