From ca28ec7971c818726166e69256dd4c84e17a9799 Mon Sep 17 00:00:00 2001 From: Jan Martin Date: Mon, 23 Sep 2024 15:49:58 -0700 Subject: [PATCH] test(@angular-devkit/build-angular): add application/browser test runs Runs all existing karma tests twice: Once in an environment that uses the application builder and once in one that uses the browser builder. The general approach is taken from the dev server tests. This is in preparation for supporting the application builder for karma tests. (cherry picked from commit 66c55df46812c2f8d95c1e6eadc75bdc5153c14f) --- .../angular_devkit/build_angular/BUILD.bazel | 2 +- .../tests/behavior/code-coverage_spec.ts | 8 +- .../karma/tests/behavior/errors_spec.ts | 8 +- .../karma/tests/behavior/module-cjs_spec.ts | 8 +- .../karma/tests/behavior/rebuilds_spec.ts | 8 +- .../karma/tests/options/assets_spec.ts | 8 +- .../options/code-coverage-exclude_spec.ts | 8 +- .../karma/tests/options/code-coverage_spec.ts | 8 +- .../karma/tests/options/exclude_spec.ts | 8 +- .../karma/tests/options/include_spec.ts | 8 +- .../karma/tests/options/styles_spec.ts | 8 +- .../tests/options/web-worker-tsconfig_spec.ts | 8 +- .../src/builders/karma/tests/setup.ts | 145 ++++++++++++++++++ 13 files changed, 212 insertions(+), 23 deletions(-) diff --git a/packages/angular_devkit/build_angular/BUILD.bazel b/packages/angular_devkit/build_angular/BUILD.bazel index 4d2e1f7b294b..3a19f71b208c 100644 --- a/packages/angular_devkit/build_angular/BUILD.bazel +++ b/packages/angular_devkit/build_angular/BUILD.bazel @@ -317,7 +317,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 0b94de99af31..38e93f6dd611 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 437aff26f791..c1ff1a15bd06 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 fc6937f09aa1..3737944c351d 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 039a3fdbab3b..d58cf7a851fb 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 76d417912180..b900dca8321d 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 de5b2ff8949d..d10c6a74b0fb 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 8346cb133012..5b8bd9d7217b 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 bcc7af77d296..e2cc3221a2e3 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 b8402b187916..9dd61aa52516 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 5d498462c9f1..37f213cb087c 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 22423e2b8586..3bc06d86a10d 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 eb10590b44c5..6f0cde5927d4 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); + } + }); + } + }); +}