diff --git a/packages/angular/build/src/builders/unit-test/options.ts b/packages/angular/build/src/builders/unit-test/options.ts index 0a71f2d642f1..f69044b2c852 100644 --- a/packages/angular/build/src/builders/unit-test/options.ts +++ b/packages/angular/build/src/builders/unit-test/options.ts @@ -127,6 +127,7 @@ export async function normalizeOptions( : [], dumpVirtualFiles: options.dumpVirtualFiles, listTests: options.listTests, + update: options.update ?? false, runnerConfig: typeof runnerConfig === 'string' ? runnerConfig.length === 0 diff --git a/packages/angular/build/src/builders/unit-test/options_spec.ts b/packages/angular/build/src/builders/unit-test/options_spec.ts new file mode 100644 index 000000000000..a1890c5e5d91 --- /dev/null +++ b/packages/angular/build/src/builders/unit-test/options_spec.ts @@ -0,0 +1,85 @@ +/** + * @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 { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { normalizeOptions } from './options'; +import type { Schema as UnitTestBuilderOptions } from './schema'; + +function createMockContext(workspaceRoot: string) { + return { + workspaceRoot, + target: { project: 'test-project', target: 'test', configuration: '' }, + logger: { warn: () => {}, info: () => {}, error: () => {}, debug: () => {} }, + getProjectMetadata: async (_projectName: string) => ({ + root: '.', + sourceRoot: 'src', + }), + getBuilderNameForTarget: async () => '@angular/build:application', + }; +} + +describe('normalizeOptions', () => { + let workspaceRoot: string; + + beforeEach(async () => { + workspaceRoot = await mkdtemp(join(tmpdir(), 'angular-cli-options-spec-')); + // Write a minimal package.json so cache normalization works + await writeFile(join(workspaceRoot, 'package.json'), '{}'); + // Create a tsconfig.spec.json so tsConfig resolution succeeds + await mkdir(join(workspaceRoot, 'src'), { recursive: true }); + await writeFile(join(workspaceRoot, 'tsconfig.spec.json'), '{}'); + }); + + afterEach(async () => { + await rm(workspaceRoot, { recursive: true, force: true }); + }); + + it('should set update to false by default', async () => { + const options: UnitTestBuilderOptions = {}; + const context = createMockContext(workspaceRoot); + + const normalized = await normalizeOptions( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + context as any, + 'test-project', + options, + ); + + expect(normalized.update).toBeFalse(); + }); + + it('should set update to true when update option is true', async () => { + const options: UnitTestBuilderOptions = { update: true }; + const context = createMockContext(workspaceRoot); + + const normalized = await normalizeOptions( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + context as any, + 'test-project', + options, + ); + + expect(normalized.update).toBeTrue(); + }); + + it('should set update to false when update option is explicitly false', async () => { + const options: UnitTestBuilderOptions = { update: false }; + const context = createMockContext(workspaceRoot); + + const normalized = await normalizeOptions( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + context as any, + 'test-project', + options, + ); + + expect(normalized.update).toBeFalse(); + }); +}); 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 c5b70e9a2487..2d7fed32e351 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 @@ -240,6 +240,7 @@ export class VitestExecutor implements TestExecutor { runnerConfig, projectSourceRoot, cacheOptions, + update, } = this.options; const projectName = this.projectName; @@ -355,6 +356,7 @@ export class VitestExecutor implements TestExecutor { cache: cacheOptions.enabled ? undefined : (false as const), testNamePattern: this.options.filter, watch, + update, ...(typeof ui === 'boolean' ? { ui } : {}), ...debugOptions, }; diff --git a/packages/angular/build/src/builders/unit-test/schema.json b/packages/angular/build/src/builders/unit-test/schema.json index 46b9b5fb6276..c68a2ff0661d 100644 --- a/packages/angular/build/src/builders/unit-test/schema.json +++ b/packages/angular/build/src/builders/unit-test/schema.json @@ -262,6 +262,11 @@ "type": "boolean", "description": "Shows build progress information in the console. Defaults to the `progress` setting of the specified `buildTarget`." }, + "update": { + "type": "boolean", + "description": "Updates existing snapshots when they fail instead of failing the test. Only available for the Vitest runner.", + "default": false + }, "listTests": { "type": "boolean", "description": "Lists all discovered test files and exits the process without building or executing the tests.",