@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.
- package/package.json +8 -8
- package/src/builders/application/index.js +2 -1
- package/src/builders/karma/application_builder.d.ts +0 -2
- package/src/builders/karma/application_builder.js +39 -351
- package/src/builders/karma/assets-middleware.d.ts +26 -0
- package/src/builders/karma/assets-middleware.js +65 -0
- package/src/builders/karma/coverage.d.ts +9 -0
- package/src/builders/karma/coverage.js +31 -0
- package/src/builders/karma/karma-config.d.ts +11 -0
- package/src/builders/karma/karma-config.js +79 -0
- package/src/builders/karma/polyfills-plugin.d.ts +13 -0
- package/src/builders/karma/polyfills-plugin.js +74 -0
- package/src/builders/karma/progress-reporter.d.ts +17 -0
- package/src/builders/karma/progress-reporter.js +73 -0
- package/src/builders/karma/utils.d.ts +17 -0
- package/src/builders/karma/utils.js +66 -0
- package/src/builders/unit-test/builder.js +140 -44
- package/src/builders/unit-test/options.d.ts +4 -1
- package/src/builders/unit-test/options.js +11 -5
- package/src/builders/unit-test/runners/api.d.ts +19 -1
- package/src/builders/unit-test/runners/dependency-checker.d.ts +43 -0
- package/src/builders/unit-test/runners/dependency-checker.js +82 -0
- package/src/builders/unit-test/runners/karma/executor.js +26 -2
- package/src/builders/unit-test/runners/karma/index.js +17 -0
- package/src/builders/unit-test/runners/vitest/browser-provider.d.ts +3 -2
- package/src/builders/unit-test/runners/vitest/build-options.js +1 -0
- package/src/builders/unit-test/runners/vitest/executor.d.ts +4 -5
- package/src/builders/unit-test/runners/vitest/executor.js +20 -133
- package/src/builders/unit-test/runners/vitest/index.js +19 -2
- package/src/builders/unit-test/runners/vitest/plugins.d.ts +23 -0
- package/src/builders/unit-test/runners/vitest/plugins.js +131 -0
- package/src/builders/unit-test/schema.d.ts +54 -30
- package/src/builders/unit-test/schema.js +1 -1
- package/src/builders/unit-test/schema.json +65 -16
- package/src/tools/esbuild/application-code-bundle.js +4 -7
- package/src/tools/vite/middlewares/assets-middleware.d.ts +2 -0
- package/src/tools/vite/middlewares/assets-middleware.js +31 -0
- package/src/tools/vite/utils.js +0 -1
- package/src/utils/normalize-cache.js +1 -1
- package/src/utils/supported-browsers.js +7 -3
- package/src/utils/test-files.d.ts +17 -0
- package/src/utils/test-files.js +82 -0
- 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,
|
|
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
|
|
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
|
|
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;
|
|
@@ -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 =
|
|
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 {};
|