Conversation
Angular CLI fills unset array options with [] for every builder outside of @angular/build, and @angular/build treats an empty array as a value the user set. With the unit-test builder, ng test --coverage therefore ran no coverage reporter and filtered every source file out of the report. The existing guard for an empty browsers array now also covers coverageInclude, coverageExclude and coverageReporters. Fixes just-jeb#2420
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The change is narrowly scoped, aligns behavior with the upstream Angular builder, and includes targeted unit tests covering the new normalization logic.
Review effort: Lite
Findings: None
What changed in this PR
Fixes a behavioral mismatch between @angular-builders/custom-esbuild:unit-test and the upstream @angular/build:unit-test builder when running ng test --coverage, where Angular CLI’s defaulting of unset array options to [] causes @angular/build to interpret them as user-specified overrides (disabling Vitest coverage defaults).
Changes:
- Normalize selected array options (
browsers,coverageInclude,coverageExclude,coverageReporters) by deleting them when they are empty, so they delegate asundefinedrather than[]. - Add unit tests validating that empty arrays are stripped, non-empty arrays pass through, and the builder forces the Vitest runner.
- Document the pitfall/behavior in
packages/custom-esbuild/AGENTS.mdfor future maintenance and debugging.
| File | Description |
|---|---|
| packages/custom-esbuild/src/unit-test/index.ts | Deletes specific empty array options before delegating to executeUnitTestBuilder, aligning behavior with @angular/build:unit-test. |
| packages/custom-esbuild/src/unit-test/index.spec.ts | Adds Jest coverage for the new normalization logic and the forced runner: 'vitest' behavior. |
| packages/custom-esbuild/AGENTS.md | Documents the “unset arrays become []” pitfall and the local workaround tied to issue #2420. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Thanks for the bug report and the fix. |
@just-jeb the short answer is that an explicit I think this is OK since the CLI also cannot tell them apart for us. |
The explicit list of coverage options missed reporters: an empty reporters array makes @angular/build's Vitest plugin delete the reporters configured in the runner config file. No array option in the unit-test schema means something different when set to [] than when unset, so strip all of them, matching what the CLI's addUndefinedObjectDefaults path gives @angular/build:unit-test itself.
|
While tracing the other options I found |
| function dropEmptyArrayOptions(options: CustomEsbuildUnitTestSchema) { | ||
| for (const option of Object.keys(options) as (keyof CustomEsbuildUnitTestSchema)[]) { | ||
| const value = options[option]; | ||
|
|
||
| if (Array.isArray(value) && !value.length) { | ||
| delete options[option]; | ||
| } | ||
| } | ||
| } |

PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
What is the current behavior?
With
@angular-builders/custom-esbuild:unit-test,ng test --coveragecollects coverage (Coverage enabled with v8is printed) but prints no coverage table and writes nothing tocoverage/<project>/.Angular CLI only skips its "fill unset array options with
[]" schema transform (addUndefinedDefaults) for builders named@angular/build:*. For this builder every unset array option arrives as[]and is forwarded unchanged.@angular/buildtreats an empty array as a value the user set:coverageReporters: []replaces Vitest's default coverage reporters, so none run.coverageInclude: []becomes['spec-*.js', 'chunk-*.js'], which filters out every source file once coverage is remapped to the original sources.reporters: []makes Angular's Vitest plugindelete testConfig.reporters, so reporters configured in therunnerConfigVitest file are silently discarded.Issue Number: #2420
Reproduction: https://github.com/myabc/custom-esbuild-coverage-repro (
HEAD~1is the control case with the stock@angular/build:unit-testbuilder).What is the new behavior?
The unit-test builder already deleted an empty
browsersarray for the same reason. It now deletes every empty array option before delegating, so they reachexecuteUnitTestBuilderasundefined, exactly as they do for@angular/build:unit-test(whose options go through the CLI'saddUndefinedObjectDefaultspath, which fills no array defaults at all).ng test --coveragethen runs Vitest's default coverage reporters and writes the report tocoverage/<project>/, andreportersfrom the Vitest config file survive. Non-empty arrays are passed through unchanged.An explicit
[]inangular.jsonis dropped as well. The CLI cannot tell it apart from an unset option for this builder, and none of the array options in the unit-test schema mean anything useful as[]:browsers: []is already normalised toundefinedby@angular/build,coverageExclude: []yields the same config as unset because Angular appends Vitest's default excludes anyway,exclude: []andsetupFiles: []are already treated as unset, andinclude/pluginshave schema defaults so never arrive as[]unless written explicitly (where[]would mean "run no tests").Tests: new
src/unit-test/index.spec.tscovers the stripped options (browsers,coverageInclude,coverageExclude,coverageReporters,reporters,setupFiles,exclude), the pass-through of non-empty arrays, and the forcedvitestrunner. Docs: a pitfall row inpackages/custom-esbuild/AGENTS.md; the README needs no change because no option or usage changes.Does this PR introduce a breaking change?
Other information
Verified by hand: the equivalent change applied to
dist/unit-test/index.jsof22.0.1makesng test --coveragewrite a report in the reproduction project and in the project where I first hit this. Locally I ran thecustom-esbuildunit tests (yarn test, 24 passing); I did not run the integration tests, so I am relying on CI for those.