Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .nx/version-plans/version-plan-1768982019500.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
__default__: prerelease
---

Enables collection of coverage data in monorepository scenarios through the new coverageRoot configuration option.
14 changes: 13 additions & 1 deletion actions/shared/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4209,16 +4209,28 @@ var coerce = {
var NEVER = INVALID;

// ../config/dist/types.js
var RunnerSchema = external_exports.object({
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"),
config: external_exports.record(external_exports.any()),
runner: external_exports.string()
});
var ConfigSchema = external_exports.object({
entryPoint: external_exports.string().min(1, "Entry point is required"),
appRegistryComponentName: external_exports.string().min(1, "App registry component name is required"),
runners: external_exports.array(external_exports.any()).min(1, "At least one runner is required"),
runners: external_exports.array(RunnerSchema).min(1, "At least one runner is required"),
defaultRunner: external_exports.string().optional(),
webSocketPort: external_exports.number().optional().default(3001),
bridgeTimeout: external_exports.number().min(1e3, "Bridge timeout must be at least 1 second").default(6e4),
bundleStartTimeout: external_exports.number().min(1e3, "Bundle start timeout must be at least 1 second").default(15e3),
maxAppRestarts: external_exports.number().min(0, "Max app restarts must be non-negative").default(2),
resetEnvironmentBetweenTestFiles: external_exports.boolean().optional().default(true),
unstable__skipAlreadyIncludedModules: external_exports.boolean().optional().default(false),
unstable__enableMetroCache: external_exports.boolean().optional().default(false),
detectNativeCrashes: external_exports.boolean().optional().default(true),
crashDetectionInterval: external_exports.number().min(100, "Crash detection interval must be at least 100ms").default(500),
coverage: external_exports.object({
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.`)
}).optional(),
// Deprecated property - used for migration detection
include: external_exports.array(external_exports.string()).optional()
}).refine((config) => {
Expand Down
21 changes: 19 additions & 2 deletions packages/babel-preset/src/preset.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
import resolveWeakPlugin from './resolve-weak-plugin';
import path from 'path';

const getIstanbulPlugin = (): string | [string, object] | null => {
if (!process.env.RN_HARNESS_COLLECT_COVERAGE) {
return null;
}

const coverageRoot = process.env.RN_HARNESS_COVERAGE_ROOT;
if (coverageRoot) {
return [
'babel-plugin-istanbul',
{ cwd: path.resolve(process.cwd(), coverageRoot) },
];
}

return 'babel-plugin-istanbul';
};

export const rnHarnessPlugins = [
'@babel/plugin-transform-class-static-block',
resolveWeakPlugin,
process.env.RN_HARNESS_COLLECT_COVERAGE ? 'babel-plugin-istanbul' : null,
].filter((plugin): plugin is string => plugin !== null);
getIstanbulPlugin(),
].filter((plugin) => plugin !== null);

export const rnHarnessPreset = () => {
if (!process.env.RN_HARNESS) {
Expand Down
14 changes: 14 additions & 0 deletions packages/config/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@ export const ConfigSchema = z
.min(100, 'Crash detection interval must be at least 100ms')
.default(500),

coverage: z
.object({
root: z
.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.'
),
})
.optional(),

// Deprecated property - used for migration detection
include: z.array(z.string()).optional(),
})
Expand Down
4 changes: 4 additions & 0 deletions packages/jest/src/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ export const setup = async (globalConfig: JestConfig.GlobalConfig) => {
// This is going to be used by @react-native-harness/babel-preset
// to enable instrumentation of test files.
process.env.RN_HARNESS_COLLECT_COVERAGE = 'true';

if (harnessConfig.coverage?.root) {
process.env.RN_HARNESS_COVERAGE_ROOT = harnessConfig.coverage.root;
}
}

logTestRunHeader(selectedRunner);
Expand Down
27 changes: 27 additions & 0 deletions website/src/docs/getting-started/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ For Expo projects, the `entryPoint` should be set to the path specified in the `

// Optional: Reset environment between test files (default: true)
resetEnvironmentBetweenTestFiles?: boolean;

// Optional: Root directory for coverage instrumentation in monorepos (default: process.cwd())
coverageRoot?: string;
}
```

Expand Down Expand Up @@ -324,6 +327,30 @@ const config = {
export default config;
```

## Coverage Root

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.

```javascript
{
coverageRoot: '../', // Use parent directory as coverage root
}
```

**Default:** `process.cwd()` (current working directory)

This option is passed to babel-plugin-istanbul's `cwd` option and ensures that coverage data is collected correctly in monorepo scenarios where:

- Tests run from an `example/` directory but source files are in `../src/`
- Libraries are structured with separate test and source directories
- Projects have nested directory structures that don't align with the current working directory

Without specifying `coverageRoot`, babel-plugin-istanbul may skip instrumenting files outside the current working directory, resulting in incomplete coverage reports.

:::tip When to use coverageRoot
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.
:::

## Finding Device and Simulator IDs

### Android Emulators
Expand Down