From 35cbf038397855c556b0958b2df3cca50d18a995 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Tue, 21 Apr 2026 12:48:05 -0400 Subject: [PATCH] feat(@angular/build): add quiet option to suppress build noise in unit tests The unit-test builder currently triggers a rebuild of the application on each test run in watch mode. This causes the application builder to print a full list of built files on every rebuild, which can be quite noisy and not useful during test development. This change introduces a `quiet` option to the `@angular/build:unit-test` builder. When enabled, it suppresses the build summary and stats table from the application builder. The option defaults to `true` when running locally to provide a cleaner development experience, and defaults to `false` when running in a CI environment to ensure detailed logs are available for troubleshooting. --- goldens/public-api/angular/build/index.api.md | 1 + .../src/builders/application/execute-build.ts | 2 +- .../build/src/builders/application/options.ts | 6 ++ .../build/src/builders/unit-test/builder.ts | 1 + .../build/src/builders/unit-test/options.ts | 1 + .../build/src/builders/unit-test/schema.json | 4 + .../unit-test/tests/options/quiet_spec.ts | 87 +++++++++++++++++++ 7 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 packages/angular/build/src/builders/unit-test/tests/options/quiet_spec.ts diff --git a/goldens/public-api/angular/build/index.api.md b/goldens/public-api/angular/build/index.api.md index 619482e325be..4a56a0c4b683 100644 --- a/goldens/public-api/angular/build/index.api.md +++ b/goldens/public-api/angular/build/index.api.md @@ -237,6 +237,7 @@ export type UnitTestBuilderOptions = { outputFile?: string; progress?: boolean; providersFile?: string; + quiet?: boolean; reporters?: SchemaReporter[]; runner?: Runner; runnerConfig?: RunnerConfig; diff --git a/packages/angular/build/src/builders/application/execute-build.ts b/packages/angular/build/src/builders/application/execute-build.ts index 3ca4a45f4b9a..5aefadc9d904 100644 --- a/packages/angular/build/src/builders/application/execute-build.ts +++ b/packages/angular/build/src/builders/application/execute-build.ts @@ -331,7 +331,7 @@ export async function executeBuild( ); } - if (!jsonLogs) { + if (!jsonLogs && !options.quiet) { const changedFiles = rebuildState && executionResult.findChangedFiles(rebuildState.previousOutputInfo); executionResult.addLog( diff --git a/packages/angular/build/src/builders/application/options.ts b/packages/angular/build/src/builders/application/options.ts index 99d5d67efbfd..07d805d1cbf9 100644 --- a/packages/angular/build/src/builders/application/options.ts +++ b/packages/angular/build/src/builders/application/options.ts @@ -120,6 +120,11 @@ interface InternalOptions { * Used exclusively for tests and shouldn't be used for other kinds of builds. */ instrumentForCoverage?: (filename: string) => boolean; + + /** + * Suppress build summary and stats table. + */ + quiet?: boolean; } /** Full set of options for `application` builder. */ @@ -502,6 +507,7 @@ export async function normalizeOptions( plugins: extensions?.codePlugins?.length ? extensions?.codePlugins : undefined, loaderExtensions, jsonLogs: useJSONBuildLogs, + quiet: options.quiet, colors: supportColor(), clearScreen, define, diff --git a/packages/angular/build/src/builders/unit-test/builder.ts b/packages/angular/build/src/builders/unit-test/builder.ts index 97c3c0dce055..542f5f978b90 100644 --- a/packages/angular/build/src/builders/unit-test/builder.ts +++ b/packages/angular/build/src/builders/unit-test/builder.ts @@ -325,6 +325,7 @@ export async function* execute( ...runnerBuildOptions, watch: normalizedOptions.watch, progress: normalizedOptions.buildProgress ?? buildTargetOptions.progress, + quiet: normalizedOptions.quiet, ...(normalizedOptions.tsConfig ? { tsConfig: normalizedOptions.tsConfig } : {}), } satisfies ApplicationBuilderInternalOptions; diff --git a/packages/angular/build/src/builders/unit-test/options.ts b/packages/angular/build/src/builders/unit-test/options.ts index 0a71f2d642f1..b2b3d6740ff1 100644 --- a/packages/angular/build/src/builders/unit-test/options.ts +++ b/packages/angular/build/src/builders/unit-test/options.ts @@ -121,6 +121,7 @@ export async function normalizeOptions( watch, debug: options.debug ?? false, ui: process.env['CI'] ? false : ui, + quiet: options.quiet ?? (process.env['CI'] ? false : true), providersFile: options.providersFile && path.join(workspaceRoot, options.providersFile), setupFiles: options.setupFiles ? options.setupFiles.map((setupFile) => path.join(workspaceRoot, setupFile)) diff --git a/packages/angular/build/src/builders/unit-test/schema.json b/packages/angular/build/src/builders/unit-test/schema.json index 46b9b5fb6276..403b61a9009b 100644 --- a/packages/angular/build/src/builders/unit-test/schema.json +++ b/packages/angular/build/src/builders/unit-test/schema.json @@ -73,6 +73,10 @@ "type": "boolean", "description": "Enables the Vitest UI for interactive test execution. This option is only available for the Vitest runner." }, + "quiet": { + "type": "boolean", + "description": "Suppresses the verbose build summary and stats table on each rebuild. Defaults to `true` locally and `false` in CI environments." + }, "coverage": { "type": "boolean", "description": "Enables coverage reporting for tests. If not specified, the coverage configuration from a runner configuration file will be used if present. Otherwise, coverage is disabled by default." diff --git a/packages/angular/build/src/builders/unit-test/tests/options/quiet_spec.ts b/packages/angular/build/src/builders/unit-test/tests/options/quiet_spec.ts new file mode 100644 index 000000000000..1ca8281db8b1 --- /dev/null +++ b/packages/angular/build/src/builders/unit-test/tests/options/quiet_spec.ts @@ -0,0 +1,87 @@ +/** + * @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, + expectLog, + expectNoLog, +} from '../setup'; + +describeBuilder(execute, UNIT_TEST_BUILDER_INFO, (harness) => { + describe('Option: "quiet"', () => { + let originalCI: string | undefined; + + beforeEach(async () => { + setupApplicationTarget(harness); + originalCI = process.env['CI']; + }); + + afterEach(() => { + if (originalCI !== undefined) { + process.env['CI'] = originalCI; + } else { + delete process.env['CI']; + } + }); + + it('should default to true (quiet) when CI is not set', async () => { + delete process.env['CI']; + + harness.useTarget('test', { + ...BASE_OPTIONS, + }); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + // Should not contain the stats table headers + expectNoLog(logs, /Initial chunk files/); + }); + + it('should default to false (verbose) when CI is set', async () => { + process.env['CI'] = 'true'; + + harness.useTarget('test', { + ...BASE_OPTIONS, + }); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + // Should contain the stats table headers or file listing + expectLog(logs, /Application bundle generation complete/); + }); + + it('should respect quiet: true explicitly', async () => { + process.env['CI'] = 'false'; // Ensure CI doesn't interfere if it defaults to false + + harness.useTarget('test', { + ...BASE_OPTIONS, + quiet: true, + }); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + expectNoLog(logs, /Initial chunk files/); + }); + + it('should respect quiet: false explicitly', async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + quiet: false, + }); + + const { result, logs } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + // On initial build, it should print the file list + expectLog(logs, /Initial/); + }); + }); +});