diff --git a/goldens/public-api/angular/build/index.api.md b/goldens/public-api/angular/build/index.api.md index e88df0d8f87c..916f224897b3 100644 --- a/goldens/public-api/angular/build/index.api.md +++ b/goldens/public-api/angular/build/index.api.md @@ -223,6 +223,7 @@ export type UnitTestBuilderOptions = { debug?: boolean; exclude?: string[]; include?: string[]; + outputFile?: string; progress?: boolean; providersFile?: string; reporters?: string[]; diff --git a/packages/angular/build/src/builders/unit-test/options.ts b/packages/angular/build/src/builders/unit-test/options.ts index 3ba298f4bf9e..c2645eaac2e5 100644 --- a/packages/angular/build/src/builders/unit-test/options.ts +++ b/packages/angular/build/src/builders/unit-test/options.ts @@ -33,7 +33,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, reporters, outputFile, browsers, progress } = options; return { // Project/workspace information @@ -59,6 +59,7 @@ export async function normalizeOptions( tsConfig, buildProgress: progress, reporters, + outputFile, browsers, watch: options.watch ?? isTTY(), debug: options.debug ?? false, diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts index 4f23c960f1ae..b4356437f111 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts @@ -53,8 +53,16 @@ export class VitestExecutor implements TestExecutor { } private async initializeVitest(): Promise { - const { codeCoverage, reporters, watch, workspaceRoot, setupFiles, browsers, debug } = - this.options; + const { + codeCoverage, + reporters, + outputFile, + watch, + workspaceRoot, + setupFiles, + browsers, + debug, + } = this.options; const { outputPath, projectName, latestBuildResult } = this; let vitestNodeModule; @@ -110,6 +118,7 @@ export class VitestExecutor implements TestExecutor { name: 'base', include: [], reporters: reporters ?? ['default'], + outputFile, watch, coverage: generateCoverageOption(codeCoverage, workspaceRoot, this.outputPath), ...debugOptions, diff --git a/packages/angular/build/src/builders/unit-test/schema.json b/packages/angular/build/src/builders/unit-test/schema.json index 79185218dee2..5d09e9d6640a 100644 --- a/packages/angular/build/src/builders/unit-test/schema.json +++ b/packages/angular/build/src/builders/unit-test/schema.json @@ -95,6 +95,10 @@ "type": "string" } }, + "outputFile": { + "type": "string", + "description": "The file to output the test report to. If not specified, the test runner will output to the console." + }, "providersFile": { "type": "string", "description": "TypeScript file that exports an array of Angular providers to use during test execution. The array must be a default export.", diff --git a/packages/angular/build/src/builders/unit-test/tests/options/reporter_and_output_file_spec.ts b/packages/angular/build/src/builders/unit-test/tests/options/reporter_and_output_file_spec.ts new file mode 100644 index 000000000000..31fc1a7bfe95 --- /dev/null +++ b/packages/angular/build/src/builders/unit-test/tests/options/reporter_and_output_file_spec.ts @@ -0,0 +1,46 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { execute } from '../../index'; +import { + BASE_OPTIONS, + describeBuilder, + UNIT_TEST_BUILDER_INFO, + setupApplicationTarget, +} from '../setup'; + +describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { + describe('Options: "reporter" and "outputFile"', () => { + beforeEach(async () => { + setupApplicationTarget(harness); + }); + + it(`should output a JSON report`, async () => { + await harness.removeFile('src/app/app.component.spec.ts'); + await harness.writeFiles({ + 'src/app/services/test.service.spec.ts': ` + describe('TestService', () => { + it('should succeed', () => { + expect(true).toBe(true); + }); + });`, + }); + + harness.useTarget('test', { + ...BASE_OPTIONS, + reporters: ['json'], + outputFile: 'test-report.json', + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + const reportContent = await harness.readFile('test-report.json'); + expect(reportContent).toContain('TestService'); + }); + }); +});