diff --git a/packages/angular_devkit/build_angular/src/builders/karma/application_builder.ts b/packages/angular_devkit/build_angular/src/builders/karma/application_builder.ts index 5c59ae05375b..f6b2e8b944cd 100644 --- a/packages/angular_devkit/build_angular/src/builders/karma/application_builder.ts +++ b/packages/angular_devkit/build_angular/src/builders/karma/application_builder.ts @@ -186,12 +186,7 @@ async function collectEntrypoints( projectSourceRoot, ); - const entryPoints = new Set([ - ...testFiles, - '@angular-devkit/build-angular/src/builders/karma/init_test_bed.js', - ]); - - return entryPoints; + return new Set(testFiles); } async function initializeApplication( @@ -218,6 +213,14 @@ async function initializeApplication( fs.rm(outputPath, { recursive: true, force: true }), ]); + let mainName = 'init_test_bed'; + if (options.main) { + entryPoints.add(options.main); + mainName = path.basename(options.main, path.extname(options.main)); + } else { + entryPoints.add('@angular-devkit/build-angular/src/builders/karma/init_test_bed.js'); + } + const instrumentForCoverage = options.codeCoverage ? createInstrumentationFilter( projectSourceRoot, @@ -261,10 +264,19 @@ async function initializeApplication( karmaOptions.files.push( // Serve polyfills first. { pattern: `${outputPath}/polyfills.js`, type: 'module' }, - // Allow loading of chunk-* files but don't include them all on load. - { pattern: `${outputPath}/{chunk,worker}-*.js`, type: 'module', included: false }, + // Serve global setup script. + { pattern: `${outputPath}/${mainName}.js`, type: 'module' }, + // Serve all source maps. + { pattern: `${outputPath}/*.map`, included: false }, ); + if (hasChunkOrWorkerFiles(buildOutput.files)) { + karmaOptions.files.push( + // Allow loading of chunk-* files but don't include them all on load. + { pattern: `${outputPath}/{chunk,worker}-*.js`, type: 'module', included: false }, + ); + } + karmaOptions.files.push( // Serve remaining JS on page load, these are the test entrypoints. { pattern: `${outputPath}/*.js`, type: 'module' }, @@ -316,6 +328,12 @@ async function initializeApplication( return [karma, parsedKarmaConfig, buildOptions]; } +function hasChunkOrWorkerFiles(files: Record): boolean { + return Object.keys(files).some((filename) => { + return /(?:^|\/)(?:worker|chunk)[^/]+\.js$/.test(filename); + }); +} + export async function writeTestFiles(files: Record, testDir: string) { const directoryExists = new Set(); // Writes the test related output files to disk and ensures the containing directories are present diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/fake-async_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/fake-async_spec.ts index a6cde25eb435..463dd625ac7f 100644 --- a/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/fake-async_spec.ts +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/fake-async_spec.ts @@ -9,7 +9,7 @@ import { execute } from '../../index'; import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeKarmaBuilder } from '../setup'; -describeKarmaBuilder(execute, KARMA_BUILDER_INFO, (harness, setupTarget, isApp) => { +describeKarmaBuilder(execute, KARMA_BUILDER_INFO, (harness, setupTarget) => { describe('Behavior: "fakeAsync"', () => { beforeEach(async () => { await setupTarget(harness); diff --git a/packages/angular_devkit/build_angular/src/builders/karma/tests/options/main_spec.ts b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/main_spec.ts new file mode 100644 index 000000000000..104b15cec31c --- /dev/null +++ b/packages/angular_devkit/build_angular/src/builders/karma/tests/options/main_spec.ts @@ -0,0 +1,51 @@ +/** + * @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, KARMA_BUILDER_INFO, describeKarmaBuilder } from '../setup'; + +describeKarmaBuilder(execute, KARMA_BUILDER_INFO, (harness, setupTarget) => { + describe('Option: "main"', () => { + beforeEach(async () => { + await setupTarget(harness); + }); + + beforeEach(async () => { + await harness.writeFiles({ + 'src/magic.ts': `Object.assign(globalThis, {MAGIC_IS_REAL: true});`, + 'src/magic.spec.ts': ` + declare const MAGIC_IS_REAL: boolean; + describe('Magic', () => { + it('can be scientificially proven to be true', () => { + expect(typeof MAGIC_IS_REAL).toBe('boolean'); + }); + });`, + }); + // Remove this test, we don't expect it to pass with our setup script. + await harness.removeFile('src/app/app.component.spec.ts'); + + // Add src/magic.ts to tsconfig. + interface TsConfig { + files: string[]; + } + const tsConfig = JSON.parse(harness.readFile('src/tsconfig.spec.json')) as TsConfig; + tsConfig.files.push('magic.ts'); + await harness.writeFile('src/tsconfig.spec.json', JSON.stringify(tsConfig)); + }); + + it('uses custom setup file', async () => { + harness.useTarget('test', { + ...BASE_OPTIONS, + main: './src/magic.ts', + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + }); + }); +});