aspect-build / aspect-build/rules_jest
[Bug]: cannot test minimum coverage levels under `bazel test`
- Dominant language
- Starlark
- Stars
- 24
- Forks
- 31
- PR merge metrics
- No merged PRs in 30d
Description
### What happened?
- A `jest_test` with user config file setting `collectCoverage: true` reports no coverage under `bazel test` (0% or not reported).
- If the config file also has `coverageThreshold` then `bazel test` passes even if the coverage threshold would normally not be met.
- If the config file also has `coverageThreshold` then `bazel test` fails even if the coverage threshold would normally be met.
Expected: coverage is collected correctly under `bazel test` if `collectCoverage` is true, including correctly checking against `coverageThreshold`.
### Version
Development (host) and target OS/architectures:
- macOS 26.6.2 / arm64 (Darwin 25.6.0)
- linux/amd64 (self-hosted GitHub Actions runners in a Linux container)
Host and target are the same in both cases.
Output of bazel --version: bazel 9.2.0
Version of the Aspect rules:
- aspect_rules_jest 0.26.0
- aspect_rules_js 3.4.0
- aspect_rules_ts 3.10.0
Language(s) and/or frameworks involved: TypeScript compiled to CommonJS by ts_project (source maps on, so coverage remaps to src/*.ts), jest 30.0.4, jest-junit 16, Node 24.14.1.
However, the minimal repro below uses jest-cli 30.2.0 and rules_js 3.2.2 as pinned in the e2e/coverage fixture in this repo.
### How to reproduce
Minimal demo using the e2e/coverage fixture in this repo.
- Add `collectCoverage: true` to `e2e/coverage/jest.config.js`
- `cd e2e/coverage`
```
bazel test //:cov_jest --test_output=all --nocache_test_results
```
Expected: `foo.js | 100 | 50 | 100 | 100`
Actual: `All files | 0 | 0 | 0 | 0`
```
bazel coverage //:cov_jest --instrument_test_targets --nocache_test_results --test_output=all
```
Expected & Actual: `foo.js | 100 | 50 | 100 | 100`
Confirmed on main (0bbc22e) on macOS/arm64. Ran with bazel 9.2.0 rather than the pinned 7.7.1.
### Any other information?
### Cause
In `jest/private/jest_config_template.mjs`, there is a `symlinks: false` resolver wrapper. It was added in [aspect-build/rules_jest#366](https://github.com/aspect-build/rules_jest/pull/366). This is a workaround for
[aspect-build/rules_js#2901](https://github.com/aspect-build/rules_js/issues/2901).
This is guarded by `coverageEnabled`, which is set to `!!process.env.COVERAGE_DIR`. So it is only applied under `bazel coverage`. Under `bazel test` the workaround is disabled.
### Suggested fix
Apply the custom resolver unconditionally.
### Workarounds
For anyone else experiencing this issue.
#### Add a custom resolver
```
module.exports = (request, options) =>
request.startsWith('.')
? options.defaultResolver(request, { ...options, symlinks: false })
: options.defaultResolver(request, options);
```
#### Add the coverage env on the jest_test
```
env = {
"COVERAGE_DIR": "$${COVERAGE_DIR:-$$TEST_TMPDIR/cov}",
"COVERAGE_OUTPUT_FILE": "$${COVERAGE_OUTPUT_FILE:-$$TEST_TMPDIR/cov/coverage.dat}",
}
```
Contributor guide
Research direction
Start in jest/private/jest_config_template.mjs, then inspect the e2e/coverage fixture and its jest.config.js. Run bazel test //:cov_jest --test_output=all --nocache_test_results from e2e/coverage and compare it with bazel coverage. Done means bazel test collects coverage and enforces coverageThreshold with the expected foo.js results.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js, typescript
- Domain
- build-system, testing
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100