Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion goldens/public-api/angular/build/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ export type UnitTestBuilderOptions = {
include?: string[];
progress?: boolean;
providersFile?: string;
reporters?: string[];
reporters?: SchemaReporter[];
runner: Runner;
setupFiles?: string[];
tsConfig: string;
Expand Down
20 changes: 13 additions & 7 deletions packages/angular/build/src/builders/unit-test/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ import type { Schema as UnitTestBuilderOptions } from './schema';

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

function normalizeReporterOption(
reporters: unknown[] | undefined,
): [string, Record<string, unknown>][] | undefined {
return reporters?.map((entry) =>
typeof entry === 'string'
? ([entry, {}] as [string, Record<string, unknown>])
: (entry as [string, Record<string, unknown>]),
);
}

export async function normalizeOptions(
context: BuilderContext,
projectName: string,
Expand All @@ -33,7 +43,7 @@ export async function normalizeOptions(
const buildTargetSpecifier = options.buildTarget ?? `::development`;
const buildTarget = targetFromTargetString(buildTargetSpecifier, projectName, 'build');

const { tsConfig, runner, reporters, browsers, progress } = options;
const { tsConfig, runner, browsers, progress } = options;

return {
// Project/workspace information
Expand All @@ -49,16 +59,12 @@ export async function normalizeOptions(
codeCoverage: options.codeCoverage
? {
exclude: options.codeCoverageExclude,
reporters: options.codeCoverageReporters?.map((entry) =>
typeof entry === 'string'
? ([entry, {}] as [string, Record<string, unknown>])
: (entry as [string, Record<string, unknown>]),
),
reporters: normalizeReporterOption(options.codeCoverageReporters),
}
: undefined,
tsConfig,
buildProgress: progress,
reporters,
reporters: normalizeReporterOption(options.reporters),
browsers,
watch: options.watch ?? isTTY(),
debug: options.debug ?? false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,16 @@ export class KarmaExecutor implements TestExecutor {
codeCoverage: !!unitTestOptions.codeCoverage,
codeCoverageExclude: unitTestOptions.codeCoverage?.exclude,
fileReplacements: buildTargetOptions.fileReplacements,
reporters: unitTestOptions.reporters,
reporters: unitTestOptions.reporters?.map((reporter) => {
// Karma only supports string reporters.
if (Object.keys(reporter[1]).length > 0) {
context.logger.warn(
`The "karma" test runner does not support options for the "${reporter[0]}" reporter. The options will be ignored.`,
);
}

return reporter[0];
}),
webWorkerTsConfig: buildTargetOptions.webWorkerTsConfig,
aot: buildTargetOptions.aot,
};
Expand Down
39 changes: 37 additions & 2 deletions packages/angular/build/src/builders/unit-test/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,40 @@
},
"reporters": {
"type": "array",
"description": "Test runner reporters to use. Directly passed to the test runner.",
"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.",
"items": {
"type": "string"
"oneOf": [
{
"anyOf": [
{
"$ref": "#/definitions/reporters-enum"
},
{
"type": "string"
}
]
},
{
"type": "array",
"minItems": 1,
"maxItems": 2,
"items": [
{
"anyOf": [
{
"$ref": "#/definitions/reporters-enum"
},
{
"type": "string"
}
]
},
{
"type": "object"
}
]
}
]
}
},
"providersFile": {
Expand Down Expand Up @@ -124,6 +155,10 @@
"json",
"json-summary"
]
},
"reporters-enum": {
"type": "string",
"enum": ["default", "verbose", "dots", "json", "junit", "tap", "tap-flat", "html"]
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,66 @@ import {
} from '../setup';

describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => {
xdescribe('Option: "reporters"', () => {
beforeEach(async () => {
describe('Option: "reporters"', () => {
beforeEach(() => {
setupApplicationTarget(harness);
});

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

const { result, logs } = await harness.executeOnce();
const { result } = await harness.executeOnce();
expect(result?.success).toBeTrue();
expect(logs).toContain(
jasmine.objectContaining({ message: jasmine.stringMatching(/DefaultReporter/) }),
);
});

it('should use a custom reporter when specified', async () => {
it(`should support multiple reporters`, async () => {
harness.useTarget('test', {
...BASE_OPTIONS,
reporters: ['json'],
reporters: ['json', 'verbose'],
});

const { result } = await harness.executeOnce();
expect(result?.success).toBeTrue();
});

it(`should support a single reporter with options`, async () => {
harness.useTarget('test', {
...BASE_OPTIONS,
reporters: [['json', { outputFile: 'a.json' }]],
});

const { result } = await harness.executeOnce();
expect(result?.success).toBeTrue();
harness.expectFile('a.json').toExist();
});

it(`should support multiple reporters with options`, async () => {
harness.useTarget('test', {
...BASE_OPTIONS,
reporters: [
['json', { outputFile: 'a.json' }],
['junit', { outputFile: 'a.xml' }],
],
});

const { result } = await harness.executeOnce();
expect(result?.success).toBeTrue();
harness.expectFile('a.json').toExist();
harness.expectFile('a.xml').toExist();
});

it(`should support multiple reporters with and without options`, async () => {
harness.useTarget('test', {
...BASE_OPTIONS,
reporters: [['json', { outputFile: 'a.json' }], 'verbose', 'default'],
});

const { result, logs } = await harness.executeOnce();
const { result } = await harness.executeOnce();
expect(result?.success).toBeTrue();
expect(logs).toContain(
jasmine.objectContaining({ message: jasmine.stringMatching(/JsonReporter/) }),
);
harness.expectFile('a.json').toExist();
});
});
});