Repository navigation
fix(testing): select the test files jest runs for atomized targets - #37505
Merged
Merged
Conversation
✅ Deploy Preview for nx-dev ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for nx-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Contributor
|
View your CI Pipeline Execution ↗ for commit aefc1bc
☁️ Nx Cloud last updated this comment at |
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
force-pushed
the
fix/jest-atomized-test-matching
branch
from
October 26, 2026 09:25
ab9f19c to
2eaa96e
Compare
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.
Contributor
There was a problem hiding this comment.
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.
View detailed reasoning in Nx Cloud ↗
🎓 Learn more about Self-Healing CI on nx.dev
…[Self-Healing CI Rerun]
jaysoo
approved these changes
Oct 26, 2026
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Current Behavior
With atomized test targets (
ciTargetName) and the defaultdisableJestRuntime: true,@nx/jest/pluginpicks the test files itself instead of loading Jest. Its picks differ from Jest's:test-ci--<file>target, which exits 1 with "No tests found".test-citarget.Configs that Nx generates for root projects and Detox apps hit the second case, because their
testMatchglobs 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
testMatch(<rootDir>and!globs, a string value),testPathIgnorePatterns,testRegex,roots,moduleFileExtensionsandmodulePathIgnorePatterns, set in the config or its preset.testRegexturns regex matching off.modulePathIgnorePatternscombines both lists.findcrawler, used whenfindsupports-inameandhaste.forceNodeFilesystemAPIis off, matches extensions as case-insensitive suffixes. A config or presethaste, like thereact-nativepreset's, replaces Jest's default, which sets the flag. Such configs add a 5 msfindcheck per process. Like the runtime path, this assumes Watchman is off.testMatchglobs go through picomatch with Jest's options and negation rule, because minimatch reads(a|b)groups literally. Jest 29 and 30 both use picomatch fortestMatch. Six other Nx packages already depend on picomatch.testMatchglob starts with**/, as in the Nx preset. Otherwise it adds about 1 ms per 10,000 files.rootDiris resolved through symlinks, as Jest does. Configs withoutrootDirskip that lookup.testMatch, the plugin uses Jest 30's default, as before. Under Jest 29 it can create targets for.mjs,.cjs,.mtsand.ctsspecs that Jest skips.