@angular/build 21.0.0-next.3 → 21.0.0-next.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/package.json +8 -8
  2. package/src/builders/application/index.js +2 -1
  3. package/src/builders/karma/application_builder.d.ts +0 -2
  4. package/src/builders/karma/application_builder.js +39 -351
  5. package/src/builders/karma/assets-middleware.d.ts +26 -0
  6. package/src/builders/karma/assets-middleware.js +65 -0
  7. package/src/builders/karma/coverage.d.ts +9 -0
  8. package/src/builders/karma/coverage.js +31 -0
  9. package/src/builders/karma/karma-config.d.ts +11 -0
  10. package/src/builders/karma/karma-config.js +79 -0
  11. package/src/builders/karma/polyfills-plugin.d.ts +13 -0
  12. package/src/builders/karma/polyfills-plugin.js +74 -0
  13. package/src/builders/karma/progress-reporter.d.ts +17 -0
  14. package/src/builders/karma/progress-reporter.js +73 -0
  15. package/src/builders/karma/utils.d.ts +17 -0
  16. package/src/builders/karma/utils.js +66 -0
  17. package/src/builders/unit-test/builder.js +140 -44
  18. package/src/builders/unit-test/options.d.ts +4 -1
  19. package/src/builders/unit-test/options.js +11 -5
  20. package/src/builders/unit-test/runners/api.d.ts +19 -1
  21. package/src/builders/unit-test/runners/dependency-checker.d.ts +43 -0
  22. package/src/builders/unit-test/runners/dependency-checker.js +82 -0
  23. package/src/builders/unit-test/runners/karma/executor.js +26 -2
  24. package/src/builders/unit-test/runners/karma/index.js +17 -0
  25. package/src/builders/unit-test/runners/vitest/browser-provider.d.ts +3 -2
  26. package/src/builders/unit-test/runners/vitest/build-options.js +1 -0
  27. package/src/builders/unit-test/runners/vitest/executor.d.ts +4 -5
  28. package/src/builders/unit-test/runners/vitest/executor.js +20 -133
  29. package/src/builders/unit-test/runners/vitest/index.js +19 -2
  30. package/src/builders/unit-test/runners/vitest/plugins.d.ts +23 -0
  31. package/src/builders/unit-test/runners/vitest/plugins.js +131 -0
  32. package/src/builders/unit-test/schema.d.ts +54 -30
  33. package/src/builders/unit-test/schema.js +1 -1
  34. package/src/builders/unit-test/schema.json +65 -16
  35. package/src/tools/esbuild/application-code-bundle.js +4 -7
  36. package/src/tools/vite/middlewares/assets-middleware.d.ts +2 -0
  37. package/src/tools/vite/middlewares/assets-middleware.js +31 -0
  38. package/src/tools/vite/utils.js +0 -1
  39. package/src/utils/normalize-cache.js +1 -1
  40. package/src/utils/supported-browsers.js +7 -3
  41. package/src/utils/test-files.d.ts +17 -0
  42. package/src/utils/test-files.js +82 -0
  43. package/.browserslistrc +0 -7
@@ -17,6 +17,11 @@ const node_path_1 = __importDefault(require("node:path"));
17
17
  const normalize_cache_1 = require("../../utils/normalize-cache");
18
18
  const project_metadata_1 = require("../../utils/project-metadata");
19
19
  const tty_1 = require("../../utils/tty");
20
+ function normalizeReporterOption(reporters) {
21
+ return reporters?.map((entry) => typeof entry === 'string'
22
+ ? [entry, {}]
23
+ : entry);
24
+ }
20
25
  async function normalizeOptions(context, projectName, options) {
21
26
  // Setup base paths based on workspace root and project information
22
27
  const workspaceRoot = context.workspaceRoot;
@@ -28,7 +33,7 @@ async function normalizeOptions(context, projectName, options) {
28
33
  // Target specifier defaults to the current project's build target using a development configuration
29
34
  const buildTargetSpecifier = options.buildTarget ?? `::development`;
30
35
  const buildTarget = (0, architect_1.targetFromTargetString)(buildTargetSpecifier, projectName, 'build');
31
- const { tsConfig, runner, reporters, browsers, progress } = options;
36
+ const { tsConfig, runner, browsers, progress, filter } = options;
32
37
  return {
33
38
  // Project/workspace information
34
39
  workspaceRoot,
@@ -39,18 +44,18 @@ async function normalizeOptions(context, projectName, options) {
39
44
  buildTarget,
40
45
  include: options.include ?? ['**/*.spec.ts'],
41
46
  exclude: options.exclude,
47
+ filter,
42
48
  runnerName: runner,
43
49
  codeCoverage: options.codeCoverage
44
50
  ? {
45
51
  exclude: options.codeCoverageExclude,
46
- reporters: options.codeCoverageReporters?.map((entry) => typeof entry === 'string'
47
- ? [entry, {}]
48
- : entry),
52
+ reporters: normalizeReporterOption(options.codeCoverageReporters),
49
53
  }
50
54
  : undefined,
51
55
  tsConfig,
52
56
  buildProgress: progress,
53
- reporters,
57
+ reporters: normalizeReporterOption(options.reporters),
58
+ outputFile: options.outputFile,
54
59
  browsers,
55
60
  watch: options.watch ?? (0, tty_1.isTTY)(),
56
61
  debug: options.debug ?? false,
@@ -58,6 +63,7 @@ async function normalizeOptions(context, projectName, options) {
58
63
  setupFiles: options.setupFiles
59
64
  ? options.setupFiles.map((setupFile) => node_path_1.default.join(workspaceRoot, setupFile))
60
65
  : [],
66
+ dumpVirtualFiles: options.dumpVirtualFiles,
61
67
  };
62
68
  }
63
69
  function injectTestingPolyfills(polyfills = []) {
@@ -9,9 +9,25 @@ import type { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
9
9
  import type { ApplicationBuilderInternalOptions } from '../../application/options';
10
10
  import type { FullResult, IncrementalResult } from '../../application/results';
11
11
  import type { NormalizedUnitTestBuilderOptions } from '../options';
12
+ /**
13
+ * Represents the options for a test runner.
14
+ */
12
15
  export interface RunnerOptions {
16
+ /**
17
+ * Partial options for the application builder.
18
+ * These will be merged with the options from the build target.
19
+ */
13
20
  buildOptions: Partial<ApplicationBuilderInternalOptions>;
21
+ /**
22
+ * A record of virtual files to be added to the build.
23
+ * The key is the file path and the value is the file content.
24
+ */
14
25
  virtualFiles?: Record<string, string>;
26
+ /**
27
+ * A map of test entry points to their corresponding test files.
28
+ * This is used to avoid re-discovering the test files in the executor.
29
+ */
30
+ testEntryPointMappings?: Map<string, string>;
15
31
  }
16
32
  /**
17
33
  * Represents a stateful test execution session.
@@ -34,6 +50,7 @@ export interface TestExecutor {
34
50
  export interface TestRunner {
35
51
  readonly name: string;
36
52
  readonly isStandalone?: boolean;
53
+ validateDependencies?(options: NormalizedUnitTestBuilderOptions): void | Promise<void>;
37
54
  getBuildOptions(options: NormalizedUnitTestBuilderOptions, baseBuildOptions: Partial<ApplicationBuilderInternalOptions>): RunnerOptions | Promise<RunnerOptions>;
38
55
  /**
39
56
  * Creates a stateful executor for a test session.
@@ -41,7 +58,8 @@ export interface TestRunner {
41
58
  *
42
59
  * @param context The Architect builder context.
43
60
  * @param options The normalized unit test options.
61
+ * @param testEntryPointMappings A map of test entry points to their corresponding test files.
44
62
  * @returns A TestExecutor instance that will handle the test runs.
45
63
  */
46
- createExecutor(context: BuilderContext, options: NormalizedUnitTestBuilderOptions): Promise<TestExecutor>;
64
+ createExecutor(context: BuilderContext, options: NormalizedUnitTestBuilderOptions, testEntryPointMappings: Map<string, string> | undefined): Promise<TestExecutor>;
47
65
  }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.dev/license
7
+ */
8
+ /**
9
+ * A custom error class to represent missing dependency errors.
10
+ * This is used to avoid printing a stack trace for this expected error.
11
+ */
12
+ export declare class MissingDependenciesError extends Error {
13
+ constructor(message: string);
14
+ }
15
+ export declare class DependencyChecker {
16
+ private readonly resolver;
17
+ private readonly missingDependencies;
18
+ constructor(projectSourceRoot: string);
19
+ /**
20
+ * Checks if a package is installed.
21
+ * @param packageName The name of the package to check.
22
+ * @returns True if the package is found, false otherwise.
23
+ */
24
+ private isInstalled;
25
+ /**
26
+ * Verifies that a package is installed and adds it to a list of missing
27
+ * dependencies if it is not.
28
+ * @param packageName The name of the package to check.
29
+ */
30
+ check(packageName: string): void;
31
+ /**
32
+ * Verifies that at least one of a list of packages is installed. If none are
33
+ * installed, a custom error message is added to the list of errors.
34
+ * @param packageNames An array of package names to check.
35
+ * @param customErrorMessage The error message to use if none of the packages are found.
36
+ */
37
+ checkAny(packageNames: string[], customErrorMessage: string): void;
38
+ /**
39
+ * Throws a `MissingDependenciesError` if any dependencies were found to be missing.
40
+ * The error message is a formatted list of all missing packages.
41
+ */
42
+ report(): void;
43
+ }
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.dev/license
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.DependencyChecker = exports.MissingDependenciesError = void 0;
11
+ const node_module_1 = require("node:module");
12
+ /**
13
+ * A custom error class to represent missing dependency errors.
14
+ * This is used to avoid printing a stack trace for this expected error.
15
+ */
16
+ class MissingDependenciesError extends Error {
17
+ constructor(message) {
18
+ super(message);
19
+ this.name = 'MissingDependenciesError';
20
+ }
21
+ }
22
+ exports.MissingDependenciesError = MissingDependenciesError;
23
+ class DependencyChecker {
24
+ resolver;
25
+ missingDependencies = new Set();
26
+ constructor(projectSourceRoot) {
27
+ this.resolver = (0, node_module_1.createRequire)(projectSourceRoot + '/').resolve;
28
+ }
29
+ /**
30
+ * Checks if a package is installed.
31
+ * @param packageName The name of the package to check.
32
+ * @returns True if the package is found, false otherwise.
33
+ */
34
+ isInstalled(packageName) {
35
+ try {
36
+ this.resolver(packageName);
37
+ return true;
38
+ }
39
+ catch {
40
+ return false;
41
+ }
42
+ }
43
+ /**
44
+ * Verifies that a package is installed and adds it to a list of missing
45
+ * dependencies if it is not.
46
+ * @param packageName The name of the package to check.
47
+ */
48
+ check(packageName) {
49
+ if (!this.isInstalled(packageName)) {
50
+ this.missingDependencies.add(packageName);
51
+ }
52
+ }
53
+ /**
54
+ * Verifies that at least one of a list of packages is installed. If none are
55
+ * installed, a custom error message is added to the list of errors.
56
+ * @param packageNames An array of package names to check.
57
+ * @param customErrorMessage The error message to use if none of the packages are found.
58
+ */
59
+ checkAny(packageNames, customErrorMessage) {
60
+ if (packageNames.every((name) => !this.isInstalled(name))) {
61
+ // This is a custom error, so we add it directly.
62
+ // Using a Set avoids duplicate custom messages.
63
+ this.missingDependencies.add(customErrorMessage);
64
+ }
65
+ }
66
+ /**
67
+ * Throws a `MissingDependenciesError` if any dependencies were found to be missing.
68
+ * The error message is a formatted list of all missing packages.
69
+ */
70
+ report() {
71
+ if (this.missingDependencies.size === 0) {
72
+ return;
73
+ }
74
+ let message = 'The following packages are required but were not found:\n';
75
+ for (const name of this.missingDependencies) {
76
+ message += ` - ${name}\n`;
77
+ }
78
+ message += 'Please install the missing packages and rerun the test command.';
79
+ throw new MissingDependenciesError(message);
80
+ }
81
+ }
82
+ exports.DependencyChecker = DependencyChecker;
@@ -79,12 +79,36 @@ class KarmaExecutor {
79
79
  codeCoverage: !!unitTestOptions.codeCoverage,
80
80
  codeCoverageExclude: unitTestOptions.codeCoverage?.exclude,
81
81
  fileReplacements: buildTargetOptions.fileReplacements,
82
- reporters: unitTestOptions.reporters,
82
+ reporters: unitTestOptions.reporters?.map((reporter) => {
83
+ // Karma only supports string reporters.
84
+ if (Object.keys(reporter[1]).length > 0) {
85
+ context.logger.warn(`The "karma" test runner does not support options for the "${reporter[0]}" reporter. The options will be ignored.`);
86
+ }
87
+ return reporter[0];
88
+ }),
83
89
  webWorkerTsConfig: buildTargetOptions.webWorkerTsConfig,
84
90
  aot: buildTargetOptions.aot,
85
91
  };
92
+ const transformOptions = {
93
+ karmaOptions: (options) => {
94
+ if (unitTestOptions.filter) {
95
+ let filter = unitTestOptions.filter;
96
+ if (filter[0] === '/' && filter.at(-1) === '/') {
97
+ this.context.logger.warn('The `--filter` option is always a regular expression.' +
98
+ 'Leading and trailing `/` are not required and will be ignored.');
99
+ }
100
+ else {
101
+ filter = `/${filter}/`;
102
+ }
103
+ options.client ??= {};
104
+ options.client.args ??= [];
105
+ options.client.args.push('--grep', filter);
106
+ }
107
+ return options;
108
+ },
109
+ };
86
110
  const { execute } = await Promise.resolve().then(() => __importStar(require('../../../karma')));
87
- yield* execute(karmaOptions, context);
111
+ yield* execute(karmaOptions, context, transformOptions);
88
112
  }
89
113
  async [Symbol.asyncDispose]() {
90
114
  // The Karma builder handles its own teardown
@@ -7,6 +7,7 @@
7
7
  * found in the LICENSE file at https://angular.dev/license
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
+ const dependency_checker_1 = require("../dependency-checker");
10
11
  const executor_1 = require("./executor");
11
12
  /**
12
13
  * A declarative definition of the Karma test runner.
@@ -14,6 +15,22 @@ const executor_1 = require("./executor");
14
15
  const KarmaTestRunner = {
15
16
  name: 'karma',
16
17
  isStandalone: true,
18
+ validateDependencies(options) {
19
+ const checker = new dependency_checker_1.DependencyChecker(options.projectSourceRoot);
20
+ checker.check('karma');
21
+ checker.check('karma-jasmine');
22
+ // Check for browser launchers
23
+ if (options.browsers?.length) {
24
+ for (const browser of options.browsers) {
25
+ const launcherName = `karma-${browser.toLowerCase().split('headless')[0]}-launcher`;
26
+ checker.check(launcherName);
27
+ }
28
+ }
29
+ if (options.codeCoverage) {
30
+ checker.check('karma-coverage');
31
+ }
32
+ checker.report();
33
+ },
17
34
  getBuildOptions() {
18
35
  return {
19
36
  buildOptions: {},
@@ -5,7 +5,8 @@
5
5
  * Use of this source code is governed by an MIT-style license that can be
6
6
  * found in the LICENSE file at https://angular.dev/license
7
7
  */
8
- export declare function setupBrowserConfiguration(browsers: string[] | undefined, debug: boolean, projectSourceRoot: string): {
8
+ export interface BrowserConfiguration {
9
9
  browser?: import('vitest/node').BrowserConfigOptions;
10
10
  errors?: string[];
11
- };
11
+ }
12
+ export declare function setupBrowserConfiguration(browsers: string[] | undefined, debug: boolean, projectSourceRoot: string): BrowserConfiguration;
@@ -97,5 +97,6 @@ async function getVitestBuildOptions(options, baseBuildOptions) {
97
97
  virtualFiles: {
98
98
  'angular:test-bed-init': testBedInitContents,
99
99
  },
100
+ testEntryPointMappings: entryPoints,
100
101
  };
101
102
  }
@@ -13,13 +13,12 @@ export declare class VitestExecutor implements TestExecutor {
13
13
  private vitest;
14
14
  private readonly projectName;
15
15
  private readonly options;
16
- private buildResultFiles;
17
- private testFileToEntryPoint;
18
- private entryPointToTestFile;
19
- constructor(projectName: string, options: NormalizedUnitTestBuilderOptions);
16
+ private readonly buildResultFiles;
17
+ private readonly testFileToEntryPoint;
18
+ private readonly entryPointToTestFile;
19
+ constructor(projectName: string, options: NormalizedUnitTestBuilderOptions, testEntryPointMappings: Map<string, string> | undefined);
20
20
  execute(buildResult: FullResult | IncrementalResult): AsyncIterable<BuilderOutput>;
21
21
  [Symbol.asyncDispose](): Promise<void>;
22
22
  private prepareSetupFiles;
23
- private createVitestPlugins;
24
23
  private initializeVitest;
25
24
  }
@@ -12,14 +12,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  exports.VitestExecutor = void 0;
14
14
  const node_assert_1 = __importDefault(require("node:assert"));
15
- const promises_1 = require("node:fs/promises");
16
15
  const node_path_1 = __importDefault(require("node:path"));
17
16
  const error_1 = require("../../../../utils/error");
18
17
  const load_esm_1 = require("../../../../utils/load-esm");
19
18
  const path_1 = require("../../../../utils/path");
20
19
  const results_1 = require("../../../application/results");
21
- const test_discovery_1 = require("../../test-discovery");
22
20
  const browser_provider_1 = require("./browser-provider");
21
+ const plugins_1 = require("./plugins");
23
22
  class VitestExecutor {
24
23
  vitest;
25
24
  projectName;
@@ -31,9 +30,15 @@ class VitestExecutor {
31
30
  // Example: `Map<'/path/to/src/app.spec.ts', 'spec-src-app-spec'>`
32
31
  testFileToEntryPoint = new Map();
33
32
  entryPointToTestFile = new Map();
34
- constructor(projectName, options) {
33
+ constructor(projectName, options, testEntryPointMappings) {
35
34
  this.projectName = projectName;
36
35
  this.options = options;
36
+ if (testEntryPointMappings) {
37
+ for (const [entryPoint, testFile] of testEntryPointMappings) {
38
+ this.testFileToEntryPoint.set(testFile, entryPoint);
39
+ this.entryPointToTestFile.set(entryPoint + '.js', testFile);
40
+ }
41
+ }
37
42
  }
38
43
  async *execute(buildResult) {
39
44
  if (buildResult.kind === results_1.ResultKind.Full) {
@@ -50,24 +55,6 @@ class VitestExecutor {
50
55
  this.buildResultFiles.set(path, file);
51
56
  }
52
57
  }
53
- // The `getTestEntrypoints` function is used here to create the same mapping
54
- // that was used in `build-options.ts` to generate the build entry points.
55
- // This is a deliberate duplication to avoid a larger refactoring of the
56
- // builder's core interfaces to pass the entry points from the build setup
57
- // phase to the execution phase.
58
- if (this.testFileToEntryPoint.size === 0) {
59
- const { include, exclude = [], workspaceRoot, projectSourceRoot } = this.options;
60
- const testFiles = await (0, test_discovery_1.findTests)(include, exclude, workspaceRoot, projectSourceRoot);
61
- const entryPoints = (0, test_discovery_1.getTestEntrypoints)(testFiles, {
62
- projectSourceRoot,
63
- workspaceRoot,
64
- removeTestExtension: true,
65
- });
66
- for (const [entryPoint, testFile] of entryPoints) {
67
- this.testFileToEntryPoint.set(testFile, entryPoint);
68
- this.entryPointToTestFile.set(entryPoint + '.js', testFile);
69
- }
70
- }
71
58
  // Initialize Vitest if not already present.
72
59
  this.vitest ??= await this.initializeVitest();
73
60
  const vitest = this.vitest;
@@ -113,118 +100,8 @@ class VitestExecutor {
113
100
  }
114
101
  return testSetupFiles;
115
102
  }
116
- createVitestPlugins(testSetupFiles, browserOptions) {
117
- const { workspaceRoot } = this.options;
118
- return [
119
- {
120
- name: 'angular:project-init',
121
- // Type is incorrect. This allows a Promise<void>.
122
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
123
- configureVitest: async (context) => {
124
- // Create a subproject that can be configured with plugins for browser mode.
125
- // Plugins defined directly in the vite overrides will not be present in the
126
- // browser specific Vite instance.
127
- await context.injectTestProjects({
128
- test: {
129
- name: this.projectName,
130
- root: workspaceRoot,
131
- globals: true,
132
- setupFiles: testSetupFiles,
133
- // Use `jsdom` if no browsers are explicitly configured.
134
- // `node` is effectively no "environment" and the default.
135
- environment: browserOptions.browser ? 'node' : 'jsdom',
136
- browser: browserOptions.browser,
137
- include: this.options.include,
138
- ...(this.options.exclude ? { exclude: this.options.exclude } : {}),
139
- },
140
- plugins: [
141
- {
142
- name: 'angular:test-in-memory-provider',
143
- enforce: 'pre',
144
- resolveId: (id, importer) => {
145
- if (importer && (id[0] === '.' || id[0] === '/')) {
146
- let fullPath;
147
- if (this.testFileToEntryPoint.has(importer)) {
148
- fullPath = (0, path_1.toPosixPath)(node_path_1.default.join(this.options.workspaceRoot, id));
149
- }
150
- else {
151
- fullPath = (0, path_1.toPosixPath)(node_path_1.default.join(node_path_1.default.dirname(importer), id));
152
- }
153
- const relativePath = node_path_1.default.relative(this.options.workspaceRoot, fullPath);
154
- if (this.buildResultFiles.has((0, path_1.toPosixPath)(relativePath))) {
155
- return fullPath;
156
- }
157
- }
158
- if (this.testFileToEntryPoint.has(id)) {
159
- return id;
160
- }
161
- (0, node_assert_1.default)(this.buildResultFiles.size > 0, 'buildResult must be available for resolving.');
162
- const relativePath = node_path_1.default.relative(this.options.workspaceRoot, id);
163
- if (this.buildResultFiles.has((0, path_1.toPosixPath)(relativePath))) {
164
- return id;
165
- }
166
- },
167
- load: async (id) => {
168
- (0, node_assert_1.default)(this.buildResultFiles.size > 0, 'buildResult must be available for in-memory loading.');
169
- // Attempt to load as a source test file.
170
- const entryPoint = this.testFileToEntryPoint.get(id);
171
- let outputPath;
172
- if (entryPoint) {
173
- outputPath = entryPoint + '.js';
174
- // To support coverage exclusion of the actual test file, the virtual
175
- // test entry point only references the built and bundled intermediate file.
176
- return {
177
- code: `import "./${outputPath}";`,
178
- };
179
- }
180
- else {
181
- // Attempt to load as a built artifact.
182
- const relativePath = node_path_1.default.relative(this.options.workspaceRoot, id);
183
- outputPath = (0, path_1.toPosixPath)(relativePath);
184
- }
185
- const outputFile = this.buildResultFiles.get(outputPath);
186
- if (outputFile) {
187
- const sourceMapPath = outputPath + '.map';
188
- const sourceMapFile = this.buildResultFiles.get(sourceMapPath);
189
- const code = outputFile.origin === 'memory'
190
- ? Buffer.from(outputFile.contents).toString('utf-8')
191
- : await (0, promises_1.readFile)(outputFile.inputPath, 'utf-8');
192
- const map = sourceMapFile
193
- ? sourceMapFile.origin === 'memory'
194
- ? Buffer.from(sourceMapFile.contents).toString('utf-8')
195
- : await (0, promises_1.readFile)(sourceMapFile.inputPath, 'utf-8')
196
- : undefined;
197
- return {
198
- code,
199
- map: map ? JSON.parse(map) : undefined,
200
- };
201
- }
202
- },
203
- },
204
- {
205
- name: 'angular:html-index',
206
- transformIndexHtml: () => {
207
- // Add all global stylesheets
208
- if (this.buildResultFiles.has('styles.css')) {
209
- return [
210
- {
211
- tag: 'link',
212
- attrs: { href: 'styles.css', rel: 'stylesheet' },
213
- injectTo: 'head',
214
- },
215
- ];
216
- }
217
- return [];
218
- },
219
- },
220
- ],
221
- });
222
- },
223
- },
224
- ];
225
- }
226
103
  async initializeVitest() {
227
- const { codeCoverage, reporters, workspaceRoot, browsers, debug, watch } = this.options;
104
+ const { codeCoverage, reporters, outputFile, workspaceRoot, browsers, debug, watch } = this.options;
228
105
  let vitestNodeModule;
229
106
  try {
230
107
  vitestNodeModule = await (0, load_esm_1.loadEsmModule)('vitest/node');
@@ -244,7 +121,15 @@ class VitestExecutor {
244
121
  }
245
122
  (0, node_assert_1.default)(this.buildResultFiles.size > 0, 'buildResult must be available before initializing vitest');
246
123
  const testSetupFiles = this.prepareSetupFiles();
247
- const plugins = this.createVitestPlugins(testSetupFiles, browserOptions);
124
+ const plugins = (0, plugins_1.createVitestPlugins)(this.options, testSetupFiles, browserOptions, {
125
+ workspaceRoot,
126
+ projectSourceRoot: this.options.projectSourceRoot,
127
+ projectName: this.projectName,
128
+ include: this.options.include,
129
+ exclude: this.options.exclude,
130
+ buildResultFiles: this.buildResultFiles,
131
+ testFileToEntryPoint: this.testFileToEntryPoint,
132
+ });
248
133
  const debugOptions = debug
249
134
  ? {
250
135
  inspectBrk: true,
@@ -259,7 +144,9 @@ class VitestExecutor {
259
144
  project: ['base', this.projectName],
260
145
  name: 'base',
261
146
  include: [],
147
+ testNamePattern: this.options.filter,
262
148
  reporters: reporters ?? ['default'],
149
+ outputFile,
263
150
  watch,
264
151
  coverage: generateCoverageOption(codeCoverage),
265
152
  ...debugOptions,
@@ -11,6 +11,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
11
11
  };
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
13
  const node_assert_1 = __importDefault(require("node:assert"));
14
+ const dependency_checker_1 = require("../dependency-checker");
14
15
  const build_options_1 = require("./build-options");
15
16
  const executor_1 = require("./executor");
16
17
  /**
@@ -18,13 +19,29 @@ const executor_1 = require("./executor");
18
19
  */
19
20
  const VitestTestRunner = {
20
21
  name: 'vitest',
22
+ validateDependencies(options) {
23
+ const checker = new dependency_checker_1.DependencyChecker(options.projectSourceRoot);
24
+ checker.check('vitest');
25
+ if (options.browsers?.length) {
26
+ checker.check('@vitest/browser');
27
+ checker.checkAny(['playwright', 'webdriverio'], 'The "browsers" option requires either "playwright" or "webdriverio" to be installed.');
28
+ }
29
+ else {
30
+ // JSDOM is used when no browsers are specified
31
+ checker.check('jsdom');
32
+ }
33
+ if (options.codeCoverage) {
34
+ checker.check('@vitest/coverage-v8');
35
+ }
36
+ checker.report();
37
+ },
21
38
  getBuildOptions(options, baseBuildOptions) {
22
39
  return (0, build_options_1.getVitestBuildOptions)(options, baseBuildOptions);
23
40
  },
24
- async createExecutor(context, options) {
41
+ async createExecutor(context, options, testEntryPointMappings) {
25
42
  const projectName = context.target?.project;
26
43
  (0, node_assert_1.default)(projectName, 'The builder requires a target.');
27
- return new executor_1.VitestExecutor(projectName, options);
44
+ return new executor_1.VitestExecutor(projectName, options, testEntryPointMappings);
28
45
  },
29
46
  };
30
47
  exports.default = VitestTestRunner;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.dev/license
7
+ */
8
+ import type { VitestPlugin } from 'vitest/node';
9
+ import type { ResultFile } from '../../../application/results';
10
+ import type { NormalizedUnitTestBuilderOptions } from '../../options';
11
+ import type { BrowserConfiguration } from './browser-provider';
12
+ type VitestPlugins = Awaited<ReturnType<typeof VitestPlugin>>;
13
+ interface PluginOptions {
14
+ workspaceRoot: string;
15
+ projectSourceRoot: string;
16
+ projectName: string;
17
+ include?: string[];
18
+ exclude?: string[];
19
+ buildResultFiles: ReadonlyMap<string, ResultFile>;
20
+ testFileToEntryPoint: ReadonlyMap<string, string>;
21
+ }
22
+ export declare function createVitestPlugins(options: NormalizedUnitTestBuilderOptions, testSetupFiles: string[], browserOptions: BrowserConfiguration, pluginOptions: PluginOptions): VitestPlugins;
23
+ export {};