Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
- Build integration: Expo plugin + `cocoapods/coverage_post_install.rb` + `android/rn-coverage*.gradle` (example applies all three).
- JS/TS: `RN_COVERAGE_JS=1` → babel-plugin-istanbul; `rn-coverage js pull|report` (NYC sourceMap remap). Unit: `yarn test:coverage`.
- CI: `yarn e2e:ios:dynamic` (primary), `yarn e2e:ios:static`, `yarn e2e:android` — see `.github/workflows/ci.yml` and `docs/integration/ci-appium.md`.
- Release: Conventional Commits + semantic-release via `.github/workflows/release.yml` (`workflow_dispatch` only); PR titles via `.github/workflows/pr-title.yml`. Operator docs: `docs/releasing.md`. No `@semantic-release/git`.
- Release: Conventional Commits + semantic-release via `.github/workflows/release.yml` (`workflow_dispatch` only); PR titles via `.github/workflows/pr-title.yml`. Operator docs: `docs/releasing.mdx`. No `@semantic-release/git`.
- Validation: `yarn`, `yarn prepare`, `yarn test` / `yarn test:coverage`, `yarn typecheck`, CLI `--help`.
- Appium e2e for this package is in-scope when the task asks; do not use RNFB slot2/3. Prefer a non-RNFB simulator (e.g. iPhone 17 on Xcode 26).
- Public repository execution and validation guidance lives in `okf-bundle/`;
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Contributions are always welcome, no matter how large or small!

This package targets **dedicated test apps only** (Pattern C). See `docs/pattern-c.md` and `AGENTS.md`.
This package targets **dedicated test apps only** (Pattern C). See `docs/coverage.mdx` and `AGENTS.md`.

License: Apache-2.0. Please follow the [code of conduct](./CODE_OF_CONDUCT.md).

Expand Down Expand Up @@ -125,4 +125,4 @@ Local check: `echo "feat: your subject" | yarn commitlint`

### Releasing

Maintainers: see [docs/releasing.md](./docs/releasing.md). Releases are **manual** (`workflow_dispatch` + semantic-release). Do not publish from a laptop unless performing the one-time human bootstrap (separate from day-to-day CI releases).
Maintainers: see [docs/releasing.mdx](./docs/releasing.mdx). Releases are **manual** (`workflow_dispatch` + semantic-release). Do not publish from a laptop unless performing the one-time human bootstrap (separate from day-to-day CI releases).
126 changes: 96 additions & 30 deletions docs.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
{
"$schema": "https://docs.page/schema.json",
"name": "react-native-coverage",
"description": "Native code coverage for React Native - iOS and Android - without touching the native stuff. TurboModule flush, LCOV/Jacoco reports, assert in CI.",
"favicon": "/assets/brand/invertase-honeycomb.png",
"description": "iOS, Android, and TypeScript code coverage from end-to-end tests in a dedicated React Native test app, with a CI gate for empty coverage.",
"favicon": "/assets/brand/invertase-honeycomb-96x96.png",
"logo": {
"light": "/assets/brand/invertase-honeycomb.png",
"dark": "/assets/brand/invertase-honeycomb.png"
"light": "/assets/brand/invertase-honeycomb-96x96.png",
"dark": "/assets/brand/invertase-honeycomb-96x96.png"
},
"theme": {
"primary": "#C97A2B",
"primaryDark": "#F5B454"
},
"og": {
"logo": "/assets/brand/invertase-honeycomb-256x256.png"
},
"social": {
"x": "@invertaseio",
"github": "invertase/react-native-coverage",
Expand All @@ -25,38 +28,55 @@
"showGitHubCard": true,
"links": [
{
"title": "Codecov",
"title": "Open Codecov",
"href": "https://app.codecov.io/gh/invertase/react-native-coverage"
},
{
"title": "Get started",
"href": "/app-developers",
"cta": true
}
]
},
"agent": {
"placeholder": "Ask about setup, the CLI, or exit codes",
"questions": [
"How do I add native coverage to an Expo test app?",
"How is this different from Codecov's JavaScript coverage?",
"Why does rn-coverage assert exit 2, and how do I wire it into CI?",
"How do library maintainers get iOS native coverage from their test app?"
]
},
"tabs": [
{
"id": "docs",
"title": "Documentation",
"href": "/"
},
{
"id": "reference",
"title": "Reference",
"href": "/reference"
}
],
"sidebar": [
{
"group": "Getting Started",
"tab": "docs",
"group": "Introduction",
"pages": [
{ "title": "Introduction", "href": "/", "icon": "house" },
{ "title": "Why native coverage", "href": "/why", "icon": "lightbulb" },
{ "title": "Pattern C", "href": "/pattern-c", "icon": "shield-halved" }
{
"title": "Overview",
"href": "/",
"icon": "house"
},
{
"title": "Coverage",
"href": "/coverage",
"icon": "lightbulb"
}
]
},
{
"group": "Guides",
"tab": "docs",
"group": "Install",
"pages": [
{
"title": "App developers (Expo & RN CLI)",
"title": "App developers",
"href": "/app-developers",
"icon": "mobile-screen"
},
Expand All @@ -65,42 +85,88 @@
"href": "/library-maintainers",
"icon": "cubes"
},
{ "title": "Agents", "href": "/agents", "icon": "robot" }
{
"title": "Coding agents",
"href": "/agents",
"icon": "robot"
}
]
},
{
"group": "Integration",
"tab": "docs",
"group": "Integrations",
"pages": [
{
"title": "Android",
"href": "/integration/android",
"icon": "android"
},
{ "title": "iOS", "href": "/integration/ios", "icon": "apple" },
{
"title": "JavaScript / TypeScript",
"href": "/integration/js",
"icon": "js"
"title": "iOS",
"href": "/integration/ios",
"icon": "apple"
},
{
"title": "E2E timing",
"href": "/integration/e2e-timing",
"icon": "clock"
"title": "JavaScript and TypeScript",
"href": "/integration/js",
"icon": "js"
},
{
"title": "CI (Appium)",
"title": "CI with Appium",
"href": "/integration/ci-appium",
"icon": "gears"
},
{
"title": "Upload to Codecov",
"href": "/integration/codecov",
"icon": "chart-line"
}
]
},
{
"tab": "docs",
"group": "Troubleshooting",
"pages": [
{
"title": "Empty or missing coverage",
"href": "/troubleshooting/empty-coverage",
"icon": "triangle-exclamation"
}
]
},
{
"tab": "reference",
"group": "Reference",
"pages": [
{ "title": "CLI", "href": "/cli", "icon": "terminal" },
{ "title": "Config", "href": "/config", "icon": "sliders" },
{ "title": "Releasing", "href": "/releasing", "icon": "rocket" }
{
"title": "Overview",
"href": "/reference",
"icon": "book"
},
{
"title": "CLI",
"href": "/reference/cli",
"icon": "terminal"
},
{
"title": "Configuration",
"href": "/reference/config",
"icon": "sliders"
},
{
"title": "Releasing",
"href": "/releasing",
"icon": "rocket"
}
]
}
]
],
"redirects": {
"/cli": "/reference/cli",
"/config": "/reference/config",
"/why": "/coverage",
"/pattern-c": "/coverage#dedicated-test-app",
"/integration/e2e-timing": "/app-developers",
"/test-app": "/coverage#dedicated-test-app"
}
}
105 changes: 39 additions & 66 deletions docs/agents.mdx
Original file line number Diff line number Diff line change
@@ -1,105 +1,78 @@
---
title: Agents
description: A paste-ready prompt to wire react-native-coverage into a repo, plus the exact CI contract an agent must satisfy so native coverage can't silently regress.
title: Coding agents
description: Rules and the CI contract for Cursor, Claude, or Codex setting up react-native-coverage. Install steps live on App developers.
---

This page is written for coding agents (Cursor, Claude, Codex, …) and the humans directing them.
It gives a paste-ready integration prompt and the non-negotiable contract that makes the result
trustworthy.
## Prompt

## Paste-ready prompt
Give your agent one line. It reads [App developers](/app-developers), the same page a person uses to get started:

Give this to your agent **before** it edits Gradle files or Podfiles by hand:

```text
Integrate react-native-coverage into this repo's dedicated React Native test /
e2e harness app only (Pattern C — never the production app package.json).

Constraints:
- New Architecture / TurboModule only
- Follow https://docs.page/invertase/react-native-coverage
- Prefer the Expo config plugin when the harness is Expo; otherwise use the bare
Gradle + CocoaPods Ruby helpers from the integration docs
- Wire libraryProjectMatchers / frameworkNamePrefixes for every native library
we need hits from
- Add CI steps that pull coverage and fail with rn-coverage assert (exit 2)
when hits are empty
- Do not invent product-app install paths; keep the package out of the shipping app

After install: yarn/npm add react-native-coverage in the harness, apply the plugin or
manual hooks, prebuild / pod install as needed, then show me the exact CI commands
to run and what green looks like.
```txt
Read https://docs.page/invertase/react-native-coverage/app-developers.md, and set up react-native-coverage in this project.
```

## The rules an agent must not break
Every page on this site is also available as Markdown by adding `.md` to its URL, and the site publishes an [`llms.txt`](https://docs.page/invertase/react-native-coverage/llms.txt).

## Rules

<Warning>
**Pattern C is load-bearing.** `react-native-coverage` goes into a dedicated test/e2e harness
app only. Never add it to a production app's `package.json`. Autolinking scans dependencies, so
`devDependencies` alone does not keep the TurboModule out of a shipped app.
`react-native-coverage` goes in a [dedicated test app](/coverage#dedicated-test-app) only. Never add it to the shipping app's `package.json` file. Autolinking scans dependencies, so `devDependencies` does not keep the TurboModule out.
</Warning>

- **New Architecture only.** Do not attempt Old-Architecture bridging shims.
- **Do not regex-edit the Podfile for LLVM flags.** Use the shipped
`cocoapods/coverage_post_install.rb` helper (`apply_post_install!`). Under Expo keep
`forceDynamicFrameworks: false`; set it `true` only on bare RN hosts whose React builds as
dynamic frameworks.
- **Land Android `.ec` under the app `buildDir`.** `rn-coverage android pull` stages it where
Jacoco's `fileTree` can see it; pulling only into `artifacts/` leaves `jacocoTestReport` empty.
- **Pin CI Actions by full commit SHA.**
- Use the New Architecture only. Do not write old-architecture bridging shims.
- Do not edit the `Podfile` with a regular expression to add LLVM flags. Use the CocoaPods helper, `apply_post_install!` from the `cocoapods/coverage_post_install.rb` file. Under Expo keep `forceDynamicFrameworks: false`. Only on a React Native CLI app whose React builds as dynamic frameworks, set it `true`.
- Put the Android `.ec` file under the app `buildDir`. `rn-coverage android pull` stages it where JaCoCo's `fileTree` includes it. Pulling only into the `artifacts/` directory leaves `jacocoTestReport` empty.
- Pin GitHub Actions by full commit SHA.

## The CI contract
## CI contract

The whole point is a pipeline that **cannot pass with empty native coverage**. The sequence:
The pipeline must not pass with empty native coverage. The sequence is:

<Steps>
<Step title="Run instrumented e2e, then flush">
Start Metro with `RN_COVERAGE_JS=1` for JS coverage, run the suite, and call
`Coverage.flush()` once at teardown.
<Step title="Run the suite">
Launch the test app and run the suite. When you also want JavaScript coverage, start Metro with `RN_COVERAGE_JS=1`.
</Step>
<Step title="Pull + report">
<Step title="Flush">
When the suite ends, call `Coverage.flush()`.
</Step>
<Step title="Pull and report">
```sh
rn-coverage android pull && rn-coverage android report
rn-coverage ios pull && rn-coverage ios export && rn-coverage ios report
```
</Step>
<Step title="Assert (the gate)">
<Step title="Assert">
```sh
rn-coverage assert
```
</Step>
</Steps>

### Exit codes are the contract
### Exit codes

`rn-coverage assert` uses these exit codes.

| Code | Meaning |
|------|---------|
| **0** | Success (or soft-mode empty artifact with a warning) |
| **1** | Unexpected error / bad invocation / tooling failure |
| **2** | Strict empty-hit or missing artifact — the CI presence guard tripped |
| 0 | Success. With `--no-strict`, an empty report also exits 0 with a warning. |
| 1 | Error: bad invocation or a tool failure. |
| 2 | No hits, or a report file is missing, in strict mode. |

<Error>
In strict mode (the CI default), missing or empty expected hits **exit 2**. An agent must treat
exit 2 as a hard failure to fix — never suppress it, never fall back to `--no-strict` in CI to
"make it green." A green pipeline that produced no native hits is a false negative.
</Error>
<Warning>
Exit 2 is a failure to fix, never one to suppress. Do not switch CI to `--no-strict` to pass the job. A passing job with no native hits means coverage failed without failing the job.
</Warning>

Configure which paths/packages must be non-empty via `assert.lcovPathIncludes` and
`assert.jacocoPackageIncludes` in [config](/config). Wire the real CLI into CI rather than
maintaining a bespoke "did anything get covered?" shell script.
Set which paths and packages must have hits with `assert.lcovPathIncludes` and `assert.jacocoPackageIncludes` on [Configuration](/reference/config). For more information about fixing an exit 2, see [Empty or missing coverage](/troubleshooting/empty-coverage).

## Verify like the reference repo
## Check the result

The canonical, working example is this repository itself — its
[Codecov dashboard](https://app.codecov.io/gh/invertase/react-native-coverage) carries separate
`e2e-ios-dynamic`, `e2e-ios-static`, and `e2e-android` flags with real device hits. Reproduce that
shape and you're done.
The working example is this repository. Its [Codecov dashboard](https://app.codecov.io/gh/invertase/react-native-coverage) carries separate [`e2e-ios-dynamic`](https://app.codecov.io/gh/invertase/react-native-coverage/flags/e2e-ios-dynamic), [`e2e-ios-static`](https://app.codecov.io/gh/invertase/react-native-coverage/flags/e2e-ios-static), and [`e2e-android`](https://app.codecov.io/gh/invertase/react-native-coverage/flags/e2e-android) flags with hits from iOS Simulator and Android emulator runs. Match that pattern: one flag per platform, each with non-zero hits.

<CardGroup cols={2}>
<Card title="CLI reference" icon="terminal" href="/cli">
Commands, flags, and the strict/assert contract in full.
<Card title="CLI" icon="terminal" href="/reference/cli">
Commands, flags, and the strict and assert contract in full.
</Card>
<Card title="Config reference" icon="sliders" href="/config">
`assert.*` matchers and default artifact paths.
<Card title="Configuration" icon="sliders" href="/reference/config">
The `assert.*` matchers and default report paths.
</Card>
</CardGroup>
Loading
Loading