Skip to content

Commit 0f0ca1b

Browse files
authored
fix(coverage): add coverageRoot option for monorepo setups (#59)
Enables collection of coverage data in monorepository scenarios through the new coverageRoot configuration option.
1 parent d4978b9 commit 0f0ca1b

File tree

6 files changed

+82
-3
lines changed

6 files changed

+82
-3
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
__default__: prerelease
3+
---
4+
5+
Enables collection of coverage data in monorepository scenarios through the new coverageRoot configuration option.

actions/shared/index.cjs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4209,16 +4209,28 @@ var coerce = {
42094209
var NEVER = INVALID;
42104210

42114211
// ../config/dist/types.js
4212+
var RunnerSchema = external_exports.object({
4213+
name: external_exports.string().min(1, "Runner name is required").regex(/^[a-zA-Z0-9._-]+$/, "Runner name can only contain alphanumeric characters, dots, underscores, and hyphens"),
4214+
config: external_exports.record(external_exports.any()),
4215+
runner: external_exports.string()
4216+
});
42124217
var ConfigSchema = external_exports.object({
42134218
entryPoint: external_exports.string().min(1, "Entry point is required"),
42144219
appRegistryComponentName: external_exports.string().min(1, "App registry component name is required"),
4215-
runners: external_exports.array(external_exports.any()).min(1, "At least one runner is required"),
4220+
runners: external_exports.array(RunnerSchema).min(1, "At least one runner is required"),
42164221
defaultRunner: external_exports.string().optional(),
42174222
webSocketPort: external_exports.number().optional().default(3001),
42184223
bridgeTimeout: external_exports.number().min(1e3, "Bridge timeout must be at least 1 second").default(6e4),
4224+
bundleStartTimeout: external_exports.number().min(1e3, "Bundle start timeout must be at least 1 second").default(15e3),
4225+
maxAppRestarts: external_exports.number().min(0, "Max app restarts must be non-negative").default(2),
42194226
resetEnvironmentBetweenTestFiles: external_exports.boolean().optional().default(true),
42204227
unstable__skipAlreadyIncludedModules: external_exports.boolean().optional().default(false),
42214228
unstable__enableMetroCache: external_exports.boolean().optional().default(false),
4229+
detectNativeCrashes: external_exports.boolean().optional().default(true),
4230+
crashDetectionInterval: external_exports.number().min(100, "Crash detection interval must be at least 100ms").default(500),
4231+
coverage: external_exports.object({
4232+
root: external_exports.string().optional().describe(`Root directory for coverage instrumentation in monorepo setups. Specifies the directory from which coverage data should be collected. Use ".." for create-react-native-library projects where tests run from example/ but source files are in parent directory. Passed to babel-plugin-istanbul's cwd option.`)
4233+
}).optional(),
42224234
// Deprecated property - used for migration detection
42234235
include: external_exports.array(external_exports.string()).optional()
42244236
}).refine((config) => {

packages/babel-preset/src/preset.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,27 @@
11
import resolveWeakPlugin from './resolve-weak-plugin';
2+
import path from 'path';
3+
4+
const getIstanbulPlugin = (): string | [string, object] | null => {
5+
if (!process.env.RN_HARNESS_COLLECT_COVERAGE) {
6+
return null;
7+
}
8+
9+
const coverageRoot = process.env.RN_HARNESS_COVERAGE_ROOT;
10+
if (coverageRoot) {
11+
return [
12+
'babel-plugin-istanbul',
13+
{ cwd: path.resolve(process.cwd(), coverageRoot) },
14+
];
15+
}
16+
17+
return 'babel-plugin-istanbul';
18+
};
219

320
export const rnHarnessPlugins = [
421
'@babel/plugin-transform-class-static-block',
522
resolveWeakPlugin,
6-
process.env.RN_HARNESS_COLLECT_COVERAGE ? 'babel-plugin-istanbul' : null,
7-
].filter((plugin): plugin is string => plugin !== null);
23+
getIstanbulPlugin(),
24+
].filter((plugin) => plugin !== null);
825

926
export const rnHarnessPreset = () => {
1027
if (!process.env.RN_HARNESS) {

packages/config/src/types.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,20 @@ export const ConfigSchema = z
4646
.min(100, 'Crash detection interval must be at least 100ms')
4747
.default(500),
4848

49+
coverage: z
50+
.object({
51+
root: z
52+
.string()
53+
.optional()
54+
.describe(
55+
'Root directory for coverage instrumentation in monorepo setups. ' +
56+
'Specifies the directory from which coverage data should be collected. ' +
57+
'Use ".." for create-react-native-library projects where tests run from example/ ' +
58+
'but source files are in parent directory. Passed to babel-plugin-istanbul\'s cwd option.'
59+
),
60+
})
61+
.optional(),
62+
4963
// Deprecated property - used for migration detection
5064
include: z.array(z.string()).optional(),
5165
})

packages/jest/src/setup.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ export const setup = async (globalConfig: JestConfig.GlobalConfig) => {
6868
// This is going to be used by @react-native-harness/babel-preset
6969
// to enable instrumentation of test files.
7070
process.env.RN_HARNESS_COLLECT_COVERAGE = 'true';
71+
72+
if (harnessConfig.coverage?.root) {
73+
process.env.RN_HARNESS_COVERAGE_ROOT = harnessConfig.coverage.root;
74+
}
7175
}
7276

7377
logTestRunHeader(selectedRunner);

website/src/docs/getting-started/configuration.mdx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ For Expo projects, the `entryPoint` should be set to the path specified in the `
103103

104104
// Optional: Reset environment between test files (default: true)
105105
resetEnvironmentBetweenTestFiles?: boolean;
106+
107+
// Optional: Root directory for coverage instrumentation in monorepos (default: process.cwd())
108+
coverageRoot?: string;
106109
}
107110
```
108111

@@ -324,6 +327,30 @@ const config = {
324327
export default config;
325328
```
326329

330+
## Coverage Root
331+
332+
The coverage root option specifies the root directory for coverage instrumentation in monorepository setups. This is particularly important when your tests run from a different directory than where your source files are located.
333+
334+
```javascript
335+
{
336+
coverageRoot: '../', // Use parent directory as coverage root
337+
}
338+
```
339+
340+
**Default:** `process.cwd()` (current working directory)
341+
342+
This option is passed to babel-plugin-istanbul's `cwd` option and ensures that coverage data is collected correctly in monorepo scenarios where:
343+
344+
- Tests run from an `example/` directory but source files are in `../src/`
345+
- Libraries are structured with separate test and source directories
346+
- Projects have nested directory structures that don't align with the current working directory
347+
348+
Without specifying `coverageRoot`, babel-plugin-istanbul may skip instrumenting files outside the current working directory, resulting in incomplete coverage reports.
349+
350+
:::tip When to use coverageRoot
351+
Set `coverageRoot` when you notice 0% coverage in your reports or when source files are not being instrumented for coverage. This commonly occurs in create-react-native-library projects and other monorepo setups.
352+
:::
353+
327354
## Finding Device and Simulator IDs
328355

329356
### Android Emulators

0 commit comments

Comments
 (0)