From 4060b6f1cf069c515873a84ff14ed990a1dc2869 Mon Sep 17 00:00:00 2001 From: Alex Duke Date: Fri, 2 Oct 2026 16:46:10 +0100 Subject: [PATCH] docs: update README Co-authored-by: Cursor --- README.md | 255 ++++++++++++++++-------------------------------------- 1 file changed, 75 insertions(+), 180 deletions(-) diff --git a/README.md b/README.md index d0bb866..0bea7c5 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,15 @@

- Invertase + Invertase +

+ Native code coverage for React Native

+ A React Native library for iOS and Android that records which lines of your native code your end-to-end tests ran. Use this package to collect coverage, write LCOV, JaCoCo, and TypeScript reports, and fail CI when a report is empty.

-

Code coverage for React Native — Typescript, iOS, and Android — without touching the native stuff.

-

+ npm downloads + npm version Codecov Docs New Architecture only @@ -14,136 +17,36 @@

- Install it into a dedicated test / e2e harness app (Pattern C) — never your shipping app. -

- -

- A TurboModule flushes real device coverage; the CLI pulls it, merges every framework's .profraw and the app binary into clean LCOV / Jacoco, and remaps your instrumented JS line-for-line back to TypeScript. Use that as a signal in your development loop or CI to gate development iterations or CI pass/fail. -

- -

- - Codecov dashboard for react-native-coverage — overall coverage, 3-month trend, sunburst graph, and the native code tree - + Docs • + Contribute

---- - -## Why this exists - -React Native is cross-platform, but your **coverage tooling stops at the JavaScript bundle**. - -The Objective-C++, Swift, and Kotlin that make your TurboModules actually work? That code -runs on a device during your e2e suite and then vanishes without a trace. **iOS native -coverage in particular is a black box** — LLVM `.profraw` files buried in a simulator -container, `__llvm_profile` counters that never get flushed, dynamic frameworks that hide -their own LINKEDIT sections. Nobody wants to hand-wire that. - -So teams don't. They ship native modules with a green checkmark that only ever proved the -JS ran. In an agentic world where a model can rewrite your `.mm` file and swear it's tested, -**that missing evidence is a real problem.** Coverage is the backpressure. It's how you — or -your agent — prove the native path executed, not just the mock. - -`react-native-coverage` makes that evidence a one-liner: flush from the TurboModule, `rn-coverage pull` -to merge the scattered native counters and remap your JS back to TypeScript, then `rn-coverage assert` -to turn "did the native path actually run?" into a pass/fail signal for your dev loop or CI. - -No Podfile regex. No profraw archaeology. No Gradle spelunking. - ---- - -## Show, don't tell - -This repository **proves its own thesis on Codecov, live on `main`** — including the hard part, iOS. - -| Flag | What ran | Coverage | -|------|----------|---------:| -| [`e2e-ios-dynamic`](https://app.codecov.io/gh/invertase/react-native-coverage) | iOS native, dynamic frameworks (the hard case) | **90.6%** | -| [`e2e-android`](https://app.codecov.io/gh/invertase/react-native-coverage) | Android native (Emma → Jacoco) | **81.7%** | -| [`e2e-ios-static`](https://app.codecov.io/gh/invertase/react-native-coverage) | iOS native, static libraries | **63.1%** | -| [`unit-js`](https://app.codecov.io/gh/invertase/react-native-coverage) | Jest unit (JS/TS) | **52.6%** | - -Those numbers come from an actual iOS Simulator and Android emulator running the harness apps -under Appium — not from a mock. Browse the [`ios/` native directory on Codecov →](https://app.codecov.io/gh/invertase/react-native-coverage/tree/main/ios) yourself. - -**The TurboModule itself, line by line** — [`ios/Coverage.mm` on Codecov →](https://app.codecov.io/gh/invertase/react-native-coverage/blob/main/ios/Coverage.mm) -Real Objective-C++ (`flush()`, `dumpJsCoverage`, `getTurboModule`), green where a device -executed it, at **71.88%**: - -

- Codecov line-by-line view of ios/Coverage.mm at 71.88%, Objective-C++ TurboModule code shown covered and partially covered -

- -### The gap this closes - -[**React Native Firebase**](https://github.com/invertase/react-native-firebase) — one of the -most-installed libraries in the ecosystem — already depends on `react-native-coverage` in its -dedicated `tests/` app. Its Android native coverage is -[live on Codecov](https://app.codecov.io/gh/invertase/react-native-firebase) at **65.9%** (the -`android-native` flag), while the `ios-native` flag still reads **`0.0%`** today. That remaining -zero — the hardest half — is exactly what this package exists to turn into a number. - ---- - -## Used in production by +JavaScript coverage tools only see the JavaScript bundle. The Objective-C++, Swift, and Kotlin in your TurboModules run on the device during end-to-end tests, but no report records them. Unit tests mock those modules, so they do not record them either. A passing build can therefore ship a native change with no evidence that the change ran, including a change a coding agent wrote. -

- - React Native Firebase - -        - - React Native Google Mobile Ads - -

+> **Important:** Add this package only to a [dedicated test app](https://docs.page/invertase/react-native-coverage/coverage#dedicated-test-app), never to the app you ship. Listing it as a `devDependency` does not keep it out, because autolinking still includes the native module. That test app must run React Native's New Architecture. -Both [**React Native Firebase**](https://github.com/invertase/react-native-firebase) and -[**React Native Google Mobile Ads**](https://github.com/invertase/react-native-google-mobile-ads) -flush real device coverage through the `react-native-coverage` TurboModule from their dedicated -[Pattern C](https://docs.page/invertase/react-native-coverage/pattern-c) test apps — the same -pattern this README describes. +## Install ---- +**Prompt your agent:** -## Have your agent wire it up - -Paste this into your coding agent (Cursor, Claude, Codex, …) **before** you touch Gradle 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. ``` ---- - -## Install +**Or set it up yourself** in three steps. -Install in the **harness** (your dedicated test/e2e app), never the product app: +### 1. Add the package to the test app ```sh yarn add react-native-coverage # or: npm install react-native-coverage ``` -### Expo (recommended) +### 2. Turn coverage on in the test app's build -Add the config plugin, then prebuild: +Follow the path that matches the test app. + +**If the test app uses Expo:** add the config plugin to its `app.json` file, then generate the native projects. ```json { @@ -167,19 +70,11 @@ Add the config plugin, then prebuild: npx expo prebuild ``` -### Bare React Native - -Apply the shipped `android/rn-coverage*.gradle` helpers and the -`cocoapods/coverage_post_install.rb` Ruby helper as documented in -[Android](https://docs.page/invertase/react-native-coverage/integration/android) and -[iOS](https://docs.page/invertase/react-native-coverage/integration/ios). Copy -`react-native-coverage.config.js.example` if you need host-specific paths. +**If the test app does not use Expo:** apply the shipped `android/rn-coverage*.gradle` helpers and the `cocoapods/coverage_post_install.rb` helper. Use the [Android](https://docs.page/invertase/react-native-coverage/integration/android) and [iOS](https://docs.page/invertase/react-native-coverage/integration/ios) pages for where each file goes. Copy `react-native-coverage.config.js.example` if your paths differ from the defaults. ---- - -## Prove it in CI +### 3. Flush, pull, report, and assert -Run your e2e suite, call `Coverage.flush()` at teardown, then: +After your tests, call `Coverage.flush()` once, then build the reports: ```sh # Android @@ -188,80 +83,80 @@ rn-coverage android pull && rn-coverage android report # iOS rn-coverage ios pull && rn-coverage ios export && rn-coverage ios report -# The point: empty hits must fail the job -rn-coverage assert # exit 2 when coverage is empty +rn-coverage assert ``` -`rn-coverage assert` is the package-owned replacement for one-off "did anything get covered?" -shell scripts. Wire it into CI and a sabotaged or silently-broken pipeline fails loudly. -Full CLI surface: [docs → CLI](https://docs.page/invertase/react-native-coverage/cli). +`rn-coverage assert` exits 2 when a report is missing or empty, which fails the job. ---- +Use the [App developers](https://docs.page/invertase/react-native-coverage/app-developers) guide for both setups in full, and the [CLI](https://docs.page/invertase/react-native-coverage/reference/cli) page for every other command. -## What you get +## How coverage works -| Piece | Role | -|-------|------| -| **TurboModule** | `flush()` — iOS LINKEDIT LLVM flush + Android Emma dump from the running app (also dumps Istanbul `global.__coverage__` when present) | -| **CLI (`rn-coverage`)** | `android pull\|report`, `ios pull\|export\|report\|summary`, `js pull\|report`, `assert` | -| **Expo config plugin** | Wires the Gradle helpers + the Podfile helper call (safe split) | -| **CocoaPods Ruby helper** | Pod LLVM flags + optional dynamic-framework restore | -| **Gradle Jacoco helpers** | `android/rn-coverage.gradle` + `android/rn-coverage-jacoco.gradle` | -| **JS/TS coverage** | `babel-plugin-istanbul` + NYC source-map remap → TypeScript-accurate LCOV | +Build the test app with coverage turned on, using the Expo config plugin or the Gradle and CocoaPods helpers. When the tests finish, call `Coverage.flush()` to write coverage out of the running app. Then use `rn-coverage` to build the reports and fail the job when a report is empty. ---- +| Feature | What it does | +|---------|--------------| +| **Expo config plugin**, or the Gradle and CocoaPods helpers | Turns native coverage on when the test app is built | +| **TurboModule** | `flush()` writes the iOS LLVM counters and the Android Emma dump, plus Istanbul `global.__coverage__` when that data is present | +| **CLI** (`rn-coverage`) | `pull` and `report` build the LCOV and JaCoCo files. `assert` exits 2 when those files are missing or empty | +| **JavaScript and TypeScript coverage** | `babel-plugin-istanbul` and NYC map the instrumented bundle back to the TypeScript you edit | -## Documentation +## Coverage results -Full docs live at **[docs.page/invertase/react-native-coverage](https://docs.page/invertase/react-native-coverage)**: +This repository runs the same setup on every pull request, with its example apps on an iOS Simulator and an Android emulator under Appium. Find the reports on [Codecov](https://app.codecov.io/gh/invertase/react-native-coverage). -- [Why native coverage](https://docs.page/invertase/react-native-coverage/why) — the problem, in full -- [Pattern C](https://docs.page/invertase/react-native-coverage/pattern-c) — dedicated test apps only -- **App developers:** [Expo & RN CLI integration](https://docs.page/invertase/react-native-coverage/app-developers) -- **Library maintainers:** [unit tests + test app](https://docs.page/invertase/react-native-coverage/library-maintainers) -- **Agents:** [quick-wire guide](https://docs.page/invertase/react-native-coverage/agents) -- Reference: [CLI](https://docs.page/invertase/react-native-coverage/cli) · [Config](https://docs.page/invertase/react-native-coverage/config) +

+ + Codecov dashboard for react-native-coverage, showing overall coverage, the three-month trend, the sunburst graph, and the native code tree + +

---- +Each flag is one run of the example apps: -## Example / CI cells +| Flag | Example app | What ran | Coverage | +|------|-------------|----------|---------:| +| `e2e-ios-dynamic` | `example-dynamic/` | iOS native, dynamic frameworks | 90.6% | +| `e2e-ios-static` | `example/` | iOS native, static libraries | 63.1% | +| `e2e-android` | `example/` | Android native (Emma to JaCoCo) | 81.7% | +| `unit-js` | This repository | Jest unit tests, JavaScript and TypeScript | 52.6% | -This repo's `example/` (Expo) and `example-dynamic/` (bare RN, dynamic frameworks) are the -harness. **Appium** (WebDriverIO) drives them on every PR: +The package also measures its own native code. Its iOS source file, [`ios/Coverage.mm`](https://app.codecov.io/gh/invertase/react-native-coverage/blob/main/ios/Coverage.mm), has 71.88% line coverage. The screenshot shows which of its lines ran. Use the [Contributing guide](./CONTRIBUTING.md) to run the example apps locally. -| Cell | Path | Proves | -|------|------|--------| -| iOS **dynamic** (primary) | `example-dynamic/` | Non-zero LCOV with a real dynamic `CoverageFixture.framework` | -| iOS **static** | `example/` | Expo staticlib merge; fixture hits still asserted | -| Android | `example/` | Emma `.ec` → Jacoco → assert | +

+ Codecov line-by-line view of ios/Coverage.mm at 71.88%, Objective-C++ TurboModule code shown covered and partially covered +

-```sh -yarn -yarn prepare -yarn test # or: yarn test:coverage -yarn e2e:ios:dynamic -yarn e2e:ios:static -yarn e2e:android -node bin/rn-coverage.js --help -``` +## Used in production ---- +- [invertase/react-native-firebase](https://github.com/invertase/react-native-firebase) +- [invertase/react-native-google-mobile-ads](https://github.com/invertase/react-native-google-mobile-ads) + +## More information -## Releasing +- [Coverage](https://docs.page/invertase/react-native-coverage/coverage) — what it measures and how the pipeline works +- Install guides for [app developers](https://docs.page/invertase/react-native-coverage/app-developers), [library maintainers](https://docs.page/invertase/react-native-coverage/library-maintainers), and [coding agents](https://docs.page/invertase/react-native-coverage/agents) +- [CLI](https://docs.page/invertase/react-native-coverage/reference/cli) and [Configuration](https://docs.page/invertase/react-native-coverage/reference/config) +- [Empty or missing coverage](https://docs.page/invertase/react-native-coverage/troubleshooting/empty-coverage) -Conventional Commits + semantic-release, **manual `workflow_dispatch` only** (no -push-to-main publish). Operator steps: -[docs → Releasing](https://docs.page/invertase/react-native-coverage/releasing). +## Contributing + +- Questions: [Discord](https://invertase.link/discord) +- Bugs & feature requests: [Open an issue](https://github.com/invertase/react-native-coverage/issues/new/choose) +- [Pull requests](https://github.com/invertase/react-native-coverage/pulls) +- [Contributing guide](./CONTRIBUTING.md) +- [Releasing](https://docs.page/invertase/react-native-coverage/releasing) +- [Code of Conduct](https://github.com/invertase/.github/blob/main/CODE_OF_CONDUCT.md) ## License Apache-2.0 — see [LICENSE](./LICENSE). +--- +

-
- - Invertase + + Invertase -
- Built and maintained by Invertase. +
+ Built and maintained by Invertase.