diff --git a/packages/angular_devkit/build_angular/BUILD.bazel b/packages/angular_devkit/build_angular/BUILD.bazel index 5705d91b93..83a948ff3c 100644 --- a/packages/angular_devkit/build_angular/BUILD.bazel +++ b/packages/angular_devkit/build_angular/BUILD.bazel @@ -318,7 +318,7 @@ LARGE_SPECS = { }, "extract-i18n": {}, "karma": { - "shards": 3, + "shards": 6, "size": "large", "flaky": True, "extra_deps": [ diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/code-coverage_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/code-coverage_spec.ts index 0b94de99af..38e93f6dd6 100644 --- a/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/code-coverage_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/code-coverage_spec.ts @@ -10,7 +10,7 @@ import { setTimeout } from 'node:timers/promises'; import { tags } from '@angular-devkit/core'; import { last, tap } from 'rxjs'; import { execute } from '../../index'; -import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; +import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeKarmaBuilder } from '../setup'; // In each of the test below we'll have to call setTimeout to wait for the coverage // analysis to be done. This is because karma-coverage performs the analysis @@ -21,8 +21,12 @@ import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; const coveragePath = 'coverage/lcov.info'; -describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { +describeKarmaBuilder(execute, KARMA_BUILDER_INFO, (harness, setupTarget, isApplicationBuilder) => { describe('Behavior: "codeCoverage"', () => { + beforeEach(() => { + setupTarget(harness); + }); + it('should generate coverage report when file was previously processed by Babel', async () => { // Force Babel transformation. await harness.appendToFile('src/app/app.component.ts', '// async'); diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/errors_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/errors_spec.ts index 437aff26f7..c1ff1a15bd 100644 --- a/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/errors_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/errors_spec.ts @@ -7,10 +7,14 @@ */ import { execute } from '../../index'; -import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; +import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeKarmaBuilder } from '../setup'; -describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { +describeKarmaBuilder(execute, KARMA_BUILDER_INFO, (harness, setupTarget) => { describe('Behavior: "Errors"', () => { + beforeEach(() => { + setupTarget(harness); + }); + it('should fail when there is a TypeScript error', async () => { harness.useTarget('test', { ...BASE_OPTIONS, diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/module-cjs_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/module-cjs_spec.ts index fc6937f09a..3737944c35 100644 --- a/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/module-cjs_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/module-cjs_spec.ts @@ -7,10 +7,14 @@ */ import { execute } from '../../index'; -import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; +import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeKarmaBuilder } from '../setup'; -describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { +describeKarmaBuilder(execute, KARMA_BUILDER_INFO, (harness, setupTarget) => { describe('Behavior: "module commonjs"', () => { + beforeEach(() => { + setupTarget(harness); + }); + it('should work when module is commonjs', async () => { harness.useTarget('test', { ...BASE_OPTIONS, diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/rebuilds_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/rebuilds_spec.ts index 039a3fdbab..d58cf7a851 100644 --- a/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/rebuilds_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/rebuilds_spec.ts @@ -8,10 +8,14 @@ import { concatMap, count, debounceTime, take, timeout } from 'rxjs'; import { execute } from '../../index'; -import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; +import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeKarmaBuilder } from '../setup'; -describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { +describeKarmaBuilder(execute, KARMA_BUILDER_INFO, (harness, setupTarget) => { describe('Behavior: "Rebuilds"', () => { + beforeEach(() => { + setupTarget(harness); + }); + it('recovers from compilation failures in watch mode', async () => { harness.useTarget('test', { ...BASE_OPTIONS, diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/assets_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/assets_spec.ts index 76d4179121..b900dca832 100644 --- a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/assets_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/assets_spec.ts @@ -7,10 +7,14 @@ */ import { execute } from '../../index'; -import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; +import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeKarmaBuilder } from '../setup'; -describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { +describeKarmaBuilder(execute, KARMA_BUILDER_INFO, (harness, setupTarget) => { describe('Option: "assets"', () => { + beforeEach(() => { + setupTarget(harness); + }); + it('includes assets', async () => { await harness.writeFiles({ './src/string-file-asset.txt': 'string-file-asset.txt', diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/code-coverage-exclude_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/code-coverage-exclude_spec.ts index de5b2ff894..d10c6a74b0 100644 --- a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/code-coverage-exclude_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/code-coverage-exclude_spec.ts @@ -7,7 +7,7 @@ */ import { setTimeout } from 'node:timers/promises'; import { execute } from '../../index'; -import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; +import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeKarmaBuilder } from '../setup'; // In each of the test below we'll have to call setTimeout to wait for the coverage // analysis to be done. This is because karma-coverage performs the analysis @@ -18,8 +18,12 @@ import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; const coveragePath = 'coverage/lcov.info'; -describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { +describeKarmaBuilder(execute, KARMA_BUILDER_INFO, (harness, setupTarget) => { describe('Option: "codeCoverageExclude"', () => { + beforeEach(() => { + setupTarget(harness); + }); + it('should exclude file from coverage when set', async () => { harness.useTarget('test', { ...BASE_OPTIONS, diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/code-coverage_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/code-coverage_spec.ts index 8346cb1330..5b8bd9d721 100644 --- a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/code-coverage_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/code-coverage_spec.ts @@ -8,7 +8,7 @@ import { setTimeout } from 'node:timers/promises'; import { execute } from '../../index'; -import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; +import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeKarmaBuilder } from '../setup'; // In each of the test below we'll have to call setTimeout to wait for the coverage // analysis to be done. This is because karma-coverage performs the analysis @@ -19,8 +19,12 @@ import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; const coveragePath = 'coverage/lcov.info'; -describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { +describeKarmaBuilder(execute, KARMA_BUILDER_INFO, (harness, setupTarget) => { describe('Option: "codeCoverage"', () => { + beforeEach(() => { + setupTarget(harness); + }); + it('should generate coverage report when option is set to true', async () => { harness.useTarget('test', { ...BASE_OPTIONS, diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/exclude_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/exclude_spec.ts index bcc7af77d2..e2cc3221a2 100644 --- a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/exclude_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/exclude_spec.ts @@ -7,10 +7,14 @@ */ import { execute } from '../../index'; -import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; +import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeKarmaBuilder } from '../setup'; -describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { +describeKarmaBuilder(execute, KARMA_BUILDER_INFO, (harness, setupTarget) => { describe('Option: "exclude"', () => { + beforeEach(() => { + setupTarget(harness); + }); + beforeEach(async () => { await harness.writeFiles({ 'src/app/error.spec.ts': ` diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/include_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/include_spec.ts index b8402b1879..9dd61aa525 100644 --- a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/include_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/include_spec.ts @@ -7,10 +7,14 @@ */ import { execute } from '../../index'; -import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; +import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeKarmaBuilder } from '../setup'; -describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { +describeKarmaBuilder(execute, KARMA_BUILDER_INFO, (harness, setupTarget) => { describe('Option: "include"', () => { + beforeEach(() => { + setupTarget(harness); + }); + it(`should fail when includes doesn't match any files`, async () => { harness.useTarget('test', { ...BASE_OPTIONS, diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/styles_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/styles_spec.ts index 5d498462c9..37f213cb08 100644 --- a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/styles_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/styles_spec.ts @@ -7,10 +7,14 @@ */ import { execute } from '../../index'; -import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; +import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeKarmaBuilder } from '../setup'; -describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { +describeKarmaBuilder(execute, KARMA_BUILDER_INFO, (harness, setupTarget) => { describe('Option: "styles"', () => { + beforeEach(() => { + setupTarget(harness); + }); + it(`processes 'styles.css' styles`, async () => { await harness.writeFiles({ 'src/styles.css': 'p {display: none}', diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/web-worker-tsconfig_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/web-worker-tsconfig_spec.ts index 22423e2b85..3bc06d86a1 100644 --- a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/web-worker-tsconfig_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/web-worker-tsconfig_spec.ts @@ -7,10 +7,14 @@ */ import { execute } from '../../index'; -import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeBuilder } from '../setup'; +import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeKarmaBuilder } from '../setup'; -describeBuilder(execute, KARMA_BUILDER_INFO, (harness) => { +describeKarmaBuilder(execute, KARMA_BUILDER_INFO, (harness, setupTarget) => { describe('Option: "webWorkerTsConfig"', () => { + beforeEach(() => { + setupTarget(harness); + }); + beforeEach(async () => { await harness.writeFiles({ 'src/tsconfig.worker.json': ` diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/setup.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/setup.ts index eb10590b44..6f0cde5927 100644 --- a/packages/angular_devkit/build_angular/src/builders/karma/tests/setup.ts +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/setup.ts @@ -7,6 +7,21 @@ */ import { Schema } from '../schema'; +import { BuilderHandlerFn } from '@angular-devkit/architect'; +import { json } from '@angular-devkit/core'; +import { ApplicationBuilderOptions as ApplicationSchema, buildApplication } from '@angular/build'; +import * as path from 'node:path'; +import { readFileSync } from 'node:fs'; + +import { JasmineBuilderHarness } from '../../../testing'; +import { host } from '../../../testing/test-utils'; +import { BuilderHarness } from '../../../testing'; +import { buildWebpackBrowser } from '../../browser'; +import { Schema as BrowserSchema } from '../../browser/schema'; +import { + BASE_OPTIONS as BROWSER_BASE_OPTIONS, + BROWSER_BUILDER_INFO, +} from '../../browser/tests/setup'; export { describeBuilder } from '../../../testing'; @@ -27,3 +42,133 @@ export const BASE_OPTIONS = Object.freeze({ progress: false, watch: false, }); + +const optionSchemaCache = new Map(); + +function getCachedSchema(options: { schemaPath: string }): json.schema.JsonSchema { + let optionSchema = optionSchemaCache.get(options.schemaPath); + if (optionSchema === undefined) { + optionSchema = JSON.parse(readFileSync(options.schemaPath, 'utf8')) as json.schema.JsonSchema; + optionSchemaCache.set(options.schemaPath, optionSchema); + } + return optionSchema; +} + +/** + * Adds a `build` target to a builder test harness for the browser builder with the base options + * used by the browser builder tests. + * + * @param harness The builder harness to use when setting up the browser builder target + * @param extraOptions The additional options that should be used when executing the target. + */ +export function setupBrowserTarget( + harness: BuilderHarness, + extraOptions?: Partial, +): void { + const browserSchema = getCachedSchema(BROWSER_BUILDER_INFO); + + harness.withBuilderTarget( + 'build', + buildWebpackBrowser, + { + ...BROWSER_BASE_OPTIONS, + ...extraOptions, + }, + { + builderName: BROWSER_BUILDER_INFO.name, + optionSchema: browserSchema, + }, + ); +} + +/** + * Contains all required application builder fields. + * Also disables progress reporting to minimize logging output. + */ +export const APPLICATION_BASE_OPTIONS = Object.freeze({ + index: 'src/index.html', + browser: 'src/main.ts', + outputPath: 'dist', + tsConfig: 'src/tsconfig.app.json', + progress: false, + + // Disable optimizations + optimization: false, + + // Enable polling (if a test enables watch mode). + // This is a workaround for bazel isolation file watch not triggering in tests. + poll: 100, +}); + +// TODO: Remove and use import after Vite-based dev server is moved to new package +export const APPLICATION_BUILDER_INFO = Object.freeze({ + name: '@angular-devkit/build-angular:application', + schemaPath: path.join( + path.dirname(require.resolve('@angular/build/package.json')), + 'src/builders/application/schema.json', + ), +}); + +/** + * Adds a `build` target to a builder test harness for the application builder with the base options + * used by the application builder tests. + * + * @param harness The builder harness to use when setting up the application builder target + * @param extraOptions The additional options that should be used when executing the target. + */ +export function setupApplicationTarget( + harness: BuilderHarness, + extraOptions?: Partial, +): void { + const applicationSchema = getCachedSchema(APPLICATION_BUILDER_INFO); + + harness.withBuilderTarget( + 'build', + buildApplication, + { + ...APPLICATION_BASE_OPTIONS, + ...extraOptions, + }, + { + builderName: APPLICATION_BUILDER_INFO.name, + optionSchema: applicationSchema, + }, + ); +} + +/** Runs the test against both an application- and a browser-builder context. */ +export function describeKarmaBuilder( + builderHandler: BuilderHandlerFn, + options: { name?: string; schemaPath: string }, + specDefinitions: (( + harness: JasmineBuilderHarness, + setupTarget: typeof setupApplicationTarget, + isApplicationTarget: true, + ) => void) & + (( + harness: JasmineBuilderHarness, + setupTarget: typeof setupBrowserTarget, + isApplicationTarget: false, + ) => void), +) { + const optionSchema = getCachedSchema(options); + const harness = new JasmineBuilderHarness(builderHandler, host, { + builderName: options.name, + optionSchema, + }); + + describe(options.name || builderHandler.name, () => { + for (const isApplicationTarget of [true, false]) { + describe(isApplicationTarget ? 'with application builder' : 'with browser builder', () => { + beforeEach(() => host.initialize().toPromise()); + afterEach(() => host.restore().toPromise()); + + if (isApplicationTarget) { + specDefinitions(harness, setupApplicationTarget, true); + } else { + specDefinitions(harness, setupBrowserTarget, false); + } + }); + } + }); +}