Skip to content

Commit 66dd6dd

Browse files
committed
feat(@angular/build): allow options for unit test reporters
This change enhances the `reporters` option in the unit-test builder to support passing an options object, similar to the existing `codeCoverageReporters` option. Users can now specify a reporter as a tuple of `[name, options]`. - The `schema.json` is updated to allow either a string or a `[string, object]` tuple in the `reporters` array. An `enum` is provided for common reporters while still allowing custom string paths. - The option normalization logic in `options.ts` is refactored into a shared helper function to handle both `reporters` and `codeCoverageReporters`, reducing code duplication. - The Karma runner, which does not support reporter options, is updated to safely ignore them and warn the user.
1 parent 69c3b12 commit 66dd6dd

File tree

5 files changed

+106
-24
lines changed

5 files changed

+106
-24
lines changed

goldens/public-api/angular/build/index.api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ export type UnitTestBuilderOptions = {
225225
include?: string[];
226226
progress?: boolean;
227227
providersFile?: string;
228-
reporters?: string[];
228+
reporters?: SchemaReporter[];
229229
runner: Runner;
230230
setupFiles?: string[];
231231
tsConfig: string;

packages/angular/build/src/builders/unit-test/options.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ import type { Schema as UnitTestBuilderOptions } from './schema';
1515

1616
export type NormalizedUnitTestBuilderOptions = Awaited<ReturnType<typeof normalizeOptions>>;
1717

18+
function normalizeReporterOption(
19+
reporters: unknown[] | undefined,
20+
): [string, Record<string, unknown>][] | undefined {
21+
return reporters?.map((entry) =>
22+
typeof entry === 'string'
23+
? ([entry, {}] as [string, Record<string, unknown>])
24+
: (entry as [string, Record<string, unknown>]),
25+
);
26+
}
27+
1828
export async function normalizeOptions(
1929
context: BuilderContext,
2030
projectName: string,
@@ -33,7 +43,7 @@ export async function normalizeOptions(
3343
const buildTargetSpecifier = options.buildTarget ?? `::development`;
3444
const buildTarget = targetFromTargetString(buildTargetSpecifier, projectName, 'build');
3545

36-
const { tsConfig, runner, reporters, browsers, progress } = options;
46+
const { tsConfig, runner, browsers, progress } = options;
3747

3848
return {
3949
// Project/workspace information
@@ -49,16 +59,12 @@ export async function normalizeOptions(
4959
codeCoverage: options.codeCoverage
5060
? {
5161
exclude: options.codeCoverageExclude,
52-
reporters: options.codeCoverageReporters?.map((entry) =>
53-
typeof entry === 'string'
54-
? ([entry, {}] as [string, Record<string, unknown>])
55-
: (entry as [string, Record<string, unknown>]),
56-
),
62+
reporters: normalizeReporterOption(options.codeCoverageReporters),
5763
}
5864
: undefined,
5965
tsConfig,
6066
buildProgress: progress,
61-
reporters,
67+
reporters: normalizeReporterOption(options.reporters),
6268
browsers,
6369
watch: options.watch ?? isTTY(),
6470
debug: options.debug ?? false,

packages/angular/build/src/builders/unit-test/runners/karma/executor.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,16 @@ export class KarmaExecutor implements TestExecutor {
6060
codeCoverage: !!unitTestOptions.codeCoverage,
6161
codeCoverageExclude: unitTestOptions.codeCoverage?.exclude,
6262
fileReplacements: buildTargetOptions.fileReplacements,
63-
reporters: unitTestOptions.reporters,
63+
reporters: unitTestOptions.reporters?.map((reporter) => {
64+
// Karma only supports string reporters.
65+
if (Object.keys(reporter[1]).length > 0) {
66+
context.logger.warn(
67+
`The "karma" test runner does not support options for the "${reporter[0]}" reporter. The options will be ignored.`,
68+
);
69+
}
70+
71+
return reporter[0];
72+
}),
6473
webWorkerTsConfig: buildTargetOptions.webWorkerTsConfig,
6574
aot: buildTargetOptions.aot,
6675
};

packages/angular/build/src/builders/unit-test/schema.json

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,40 @@
8888
},
8989
"reporters": {
9090
"type": "array",
91-
"description": "Test runner reporters to use. Directly passed to the test runner.",
91+
"description": "Specifies the reporters to use during test execution. Each reporter can be a string representing its name, or a tuple containing the name and an options object. Built-in reporters include 'default', 'verbose', 'dots', 'json', 'junit', 'tap', 'tap-flat', and 'html'. You can also provide a path to a custom reporter.",
9292
"items": {
93-
"type": "string"
93+
"oneOf": [
94+
{
95+
"anyOf": [
96+
{
97+
"$ref": "#/definitions/reporters-enum"
98+
},
99+
{
100+
"type": "string"
101+
}
102+
]
103+
},
104+
{
105+
"type": "array",
106+
"minItems": 1,
107+
"maxItems": 2,
108+
"items": [
109+
{
110+
"anyOf": [
111+
{
112+
"$ref": "#/definitions/reporters-enum"
113+
},
114+
{
115+
"type": "string"
116+
}
117+
]
118+
},
119+
{
120+
"type": "object"
121+
}
122+
]
123+
}
124+
]
94125
}
95126
},
96127
"providersFile": {
@@ -124,6 +155,10 @@
124155
"json",
125156
"json-summary"
126157
]
158+
},
159+
"reporters-enum": {
160+
"type": "string",
161+
"enum": ["default", "verbose", "dots", "json", "junit", "tap", "tap-flat", "html"]
127162
}
128163
}
129164
}

packages/angular/build/src/builders/unit-test/tests/options/reporters_spec.ts

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,34 +15,66 @@ import {
1515
} from '../setup';
1616

1717
describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => {
18-
xdescribe('Option: "reporters"', () => {
19-
beforeEach(async () => {
18+
describe('Option: "reporters"', () => {
19+
beforeEach(() => {
2020
setupApplicationTarget(harness);
2121
});
2222

23-
it('should use the default reporter when none is specified', async () => {
23+
it(`should support a single reporter`, async () => {
2424
harness.useTarget('test', {
2525
...BASE_OPTIONS,
26+
reporters: ['json'],
2627
});
2728

28-
const { result, logs } = await harness.executeOnce();
29+
const { result } = await harness.executeOnce();
2930
expect(result?.success).toBeTrue();
30-
expect(logs).toContain(
31-
jasmine.objectContaining({ message: jasmine.stringMatching(/DefaultReporter/) }),
32-
);
3331
});
3432

35-
it('should use a custom reporter when specified', async () => {
33+
it(`should support multiple reporters`, async () => {
3634
harness.useTarget('test', {
3735
...BASE_OPTIONS,
38-
reporters: ['json'],
36+
reporters: ['json', 'verbose'],
37+
});
38+
39+
const { result } = await harness.executeOnce();
40+
expect(result?.success).toBeTrue();
41+
});
42+
43+
it(`should support a single reporter with options`, async () => {
44+
harness.useTarget('test', {
45+
...BASE_OPTIONS,
46+
reporters: [['json', { outputFile: 'a.json' }]],
47+
});
48+
49+
const { result } = await harness.executeOnce();
50+
expect(result?.success).toBeTrue();
51+
harness.expectFile('a.json').toExist();
52+
});
53+
54+
it(`should support multiple reporters with options`, async () => {
55+
harness.useTarget('test', {
56+
...BASE_OPTIONS,
57+
reporters: [
58+
['json', { outputFile: 'a.json' }],
59+
['junit', { outputFile: 'a.xml' }],
60+
],
61+
});
62+
63+
const { result } = await harness.executeOnce();
64+
expect(result?.success).toBeTrue();
65+
harness.expectFile('a.json').toExist();
66+
harness.expectFile('a.xml').toExist();
67+
});
68+
69+
it(`should support multiple reporters with and without options`, async () => {
70+
harness.useTarget('test', {
71+
...BASE_OPTIONS,
72+
reporters: [['json', { outputFile: 'a.json' }], 'verbose', 'default'],
3973
});
4074

41-
const { result, logs } = await harness.executeOnce();
75+
const { result } = await harness.executeOnce();
4276
expect(result?.success).toBeTrue();
43-
expect(logs).toContain(
44-
jasmine.objectContaining({ message: jasmine.stringMatching(/JsonReporter/) }),
45-
);
77+
harness.expectFile('a.json').toExist();
4678
});
4779
});
4880
});

0 commit comments

Comments
 (0)