Skip to content

fix(testing): select the test files jest runs for atomized targets - #37505

Merged
leosvelperez merged 4 commits into
masterfrom
fix/jest-atomized-test-matching
Oct 26, 2026
Merged

leosvelperez merged 4 commits into
masterfrom
fix/jest-atomized-test-matching

Conversation

@leosvelperez

@leosvelperez leosvelperez commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Current Behavior

With atomized test targets (ciTargetName) and the default disableJestRuntime: true, @nx/jest/plugin picks the test files itself instead of loading Jest. Its picks differ from Jest's:

  • A file Jest skips gets a test-ci--<file> target, which exits 1 with "No tests found".
  • A file Jest runs gets no target, so CI never runs it. When no file matches, the project gets no test-ci target.

Configs that Nx generates for root projects and Detox apps hit the second case, because their testMatch globs start with <rootDir>. The other differences need hand-written Jest options.

Expected Behavior

Atomized targets cover the files Jest runs, using the options from the config, its preset or Jest's defaults.

Related Issue(s)

NXC-5148

Implementation Notes

  • The plugin still doesn't load Jest. It follows how jest-haste-map and Jest's SearchSource select files, and checks absolute paths the way they do.
  • That covers testMatch (<rootDir> and ! globs, a string value), testPathIgnorePatterns, testRegex, roots, moduleFileExtensions and modulePathIgnorePatterns, set in the config or its preset.
  • As in Jest, an option the config sets replaces the preset's, even when empty. An empty testRegex turns regex matching off. modulePathIgnorePatterns combines both lists.
  • Jest's find crawler, used when find supports -iname and haste.forceNodeFilesystemAPI is off, matches extensions as case-insensitive suffixes. A config or preset haste, like the react-native preset's, replaces Jest's default, which sets the flag. Such configs add a 5 ms find check per process. Like the runtime path, this assumes Watchman is off.
  • testMatch globs go through picomatch with Jest's options and negation rule, because minimatch reads (a|b) groups literally. Jest 29 and 30 both use picomatch for testMatch. Six other Nx packages already depend on picomatch.
  • Measured in isolation, the per-file checks take about 3 ms per 12,000 specs. The picomatch check is skipped when every testMatch glob starts with **/, as in the Nx preset. Otherwise it adds about 1 ms per 10,000 files.
  • An explicit rootDir is resolved through symlinks, as Jest does. Configs without rootDir skip that lookup.
  • Not handled: without a testMatch, the plugin uses Jest 30's default, as before. Under Jest 29 it can create targets for .mjs, .cjs, .mts and .cts specs that Jest skips.

@leosvelperez leosvelperez self-assigned this Oct 20, 2026
@netlify

netlify Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for nx-dev ready!

Name Link
🔨 Latest commit aefc1bc
🔍 Latest deploy log https://app.netlify.com/projects/nx-dev/deploys/6adf426f28f658b466559722
😎 Deploy Preview https://deploy-preview-37505--nx-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for nx-docs ready!

Name Link
🔨 Latest commit aefc1bc
🔍 Latest deploy log https://app.netlify.com/projects/nx-docs/deploys/6adf426f3d9621e588f2cdfe
😎 Deploy Preview https://deploy-preview-37505--nx-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@nx-cloud

nx-cloud Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit aefc1bc

Command Status Duration Result
nx affected --targets=lint,oxlint,test,build,e2... ✅ Succeeded 50m 11s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 2s View ↗
nx-cloud record -- pnpm nx-cloud conformance:check ✅ Succeeded 1m 3s View ↗
nx build workspace-plugin ✅ Succeeded <1s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded 33s View ↗
nx-cloud record -- nx format:check ✅ Succeeded <1s View ↗

☁️ Nx Cloud last updated this comment at 2026-10-26 13:02:31 UTC

With the default `disableJestRuntime: true`, @nx/jest/plugin chose the
files for atomized test targets with its own matcher, which diverged
from Jest. A file Jest skips got a target that failed with "No tests
found", and a file Jest runs got no target, so CI never ran it.

The divergences:
- testMatch: `<rootDir>` globs matched no file, `!` globs excluded no
  file, and a string value threw.
- testPathIgnorePatterns: matched against the project-relative path, so
  entries that need the absolute path (`<rootDir>/...`, `/src/...`)
  never matched, and a preset value was ignored.
- testRegex: matched against the workspace-relative path, a preset value
  was ignored, and the default testMatch still applied, which Jest drops.
- roots, moduleFileExtensions and modulePathIgnorePatterns were ignored.

The matcher now resolves these options from the config, its preset or
Jest's defaults, and checks each file's absolute path the way
jest-haste-map and Jest's SearchSource do.
Without the Jest runtime, atomized test file selection fell back to the
preset whenever an option in the config was falsy. A config that sets
`testRegex: ''` or `undefined` to clear the preset's regex got the
preset's regex instead. Its specs then got no atomized targets, and the
project lost its `test-ci` target. Jest lets every key the config sets
override the preset, and it reads a falsy `testRegex` as none.

Selection now takes an option from the config whenever the config sets
the key, and reads a falsy `testRegex` as none. `getJestOption` had no
other caller and is removed. A new table row also checks that an
explicit `moduleFileExtensions` replaces Jest's default list.
@leosvelperez
leosvelperez force-pushed the fix/jest-atomized-test-matching branch from ab9f19c to 2eaa96e Compare October 26, 2026 09:25
When a config or preset sets `haste` without `forceNodeFilesystemAPI`,
as the `react-native` preset does, jest-haste-map lists files with the
system `find` wherever it supports `-iname`. That crawler matches each
module file extension as a case-insensitive suffix. It keeps
`unit.spec.JS`, and `flow.spec.test.ts` when `test.ts` is a module file
extension. Without the Jest runtime, atomized test file selection always
required an exact match of the last extension, so specs like these got
no atomized targets.

Selection now reads `haste` as jest-config does: the config's, else the
preset's, else Jest's default, which forces the Node API crawler. When
`forceNodeFilesystemAPI` is off and the same `find -iname` probe
succeeds, extensions match as case-insensitive suffixes. The probe runs
once per process. Like the runtime path, selection assumes Watchman is
off.
@leosvelperez
leosvelperez marked this pull request as ready for review October 26, 2026 11:44
@leosvelperez
leosvelperez requested a review from a team as a code owner October 26, 2026 11:44

@nx-cloud nx-cloud Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nx Cloud has identified a flaky task in your failed CI:

🔂 Since the failure was identified as flaky, we triggered a CI rerun by adding an empty commit to this branch.

Nx Cloud View detailed reasoning in Nx Cloud ↗


🎓 Learn more about Self-Healing CI on nx.dev

@leosvelperez
leosvelperez merged commit 35dc43a into master Oct 26, 2026
25 checks passed
@leosvelperez
leosvelperez deleted the fix/jest-atomized-test-matching branch October 26, 2026 16:05
jaysoo pushed a commit that referenced this pull request Oct 26, 2026
…37505)

## Current Behavior

With atomized test targets (`ciTargetName`) and the default
`disableJestRuntime: true`, `@nx/jest/plugin` picks the test files
itself instead of loading Jest. Its picks differ from Jest's:

- A file Jest skips gets a `test-ci--<file>` target, which exits 1 with
"No tests found".
- A file Jest runs gets no target, so CI never runs it. When no file
matches, the project gets no `test-ci` target.

Configs that Nx generates for root projects and Detox apps hit the
second case, because their `testMatch` globs start with `<rootDir>`. The
other differences need hand-written Jest options.

## Expected Behavior

Atomized targets cover the files Jest runs, using the options from the
config, its preset or Jest's defaults.

## Related Issue(s)

NXC-5148

## Implementation Notes

- The plugin still doesn't load Jest. It follows how jest-haste-map and
Jest's SearchSource select files, and checks absolute paths the way they
do.
- That covers `testMatch` (`<rootDir>` and `!` globs, a string value),
`testPathIgnorePatterns`, `testRegex`, `roots`, `moduleFileExtensions`
and `modulePathIgnorePatterns`, set in the config or its preset.
- As in Jest, an option the config sets replaces the preset's, even when
empty. An empty `testRegex` turns regex matching off.
`modulePathIgnorePatterns` combines both lists.
- Jest's `find` crawler, used when `find` supports `-iname` and
`haste.forceNodeFilesystemAPI` is off, matches extensions as
case-insensitive suffixes. A config or preset `haste`, like the
`react-native` preset's, replaces Jest's default, which sets the flag.
Such configs add a 5 ms `find` check per process. Like the runtime path,
this assumes Watchman is off.
- `testMatch` globs go through picomatch with Jest's options and
negation rule, because minimatch reads `(a|b)` groups literally. Jest 29
and 30 both use picomatch for `testMatch`. Six other Nx packages already
depend on picomatch.
- Measured in isolation, the per-file checks take about 3 ms per 12,000
specs. The picomatch check is skipped when every `testMatch` glob starts
with `**/`, as in the Nx preset. Otherwise it adds about 1 ms per 10,000
files.
- An explicit `rootDir` is resolved through symlinks, as Jest does.
Configs without `rootDir` skip that lookup.
- Not handled: without a `testMatch`, the plugin uses Jest 30's default,
as before. Under Jest 29 it can create targets for `.mjs`, `.cjs`,
`.mts` and `.cts` specs that Jest skips.

---------

Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
(cherry picked from commit 35dc43a)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants