@angular/build 21.0.0-next.4 → 21.0.0-next.6
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/karma/application_builder.js +5 -1
- package/src/builders/karma/coverage.js +1 -1
- package/src/builders/karma/find-tests.d.ts +1 -9
- package/src/builders/karma/find-tests.js +6 -106
- package/src/builders/unit-test/builder.js +20 -2
- package/src/builders/unit-test/options.d.ts +16 -2
- package/src/builders/unit-test/options.js +37 -4
- package/src/builders/unit-test/runners/karma/executor.js +26 -3
- package/src/builders/unit-test/runners/karma/index.js +1 -1
- package/src/builders/unit-test/runners/vitest/browser-provider.d.ts +4 -1
- package/src/builders/unit-test/runners/vitest/browser-provider.js +6 -2
- package/src/builders/unit-test/runners/vitest/build-options.js +6 -6
- package/src/builders/unit-test/runners/vitest/executor.js +30 -8
- package/src/builders/unit-test/runners/vitest/index.js +1 -1
- package/src/builders/unit-test/schema.d.ts +93 -13
- package/src/builders/unit-test/schema.js +12 -12
- package/src/builders/unit-test/schema.json +126 -33
- package/src/builders/unit-test/test-discovery.d.ts +25 -1
- package/src/builders/unit-test/test-discovery.js +194 -5
- package/src/tools/angular/transformers/jit-bootstrap-transformer.js +1 -1
- package/src/tools/angular/transformers/jit-resource-transformer.js +1 -1
- package/src/tools/babel/plugins/pure-toplevel-functions.d.ts +0 -1
- package/src/tools/babel/plugins/pure-toplevel-functions.js +21 -5
- package/src/tools/esbuild/angular/compiler-plugin.js +38 -14
- package/src/tools/esbuild/javascript-transformer-worker.js +2 -8
- package/src/tools/esbuild/stylesheets/less-language.js +2 -26
- package/src/tools/esbuild/stylesheets/stylesheet-plugin-factory.js +2 -1
- package/src/utils/normalize-cache.js +1 -1
- package/src/utils/server-rendering/utils.d.ts +1 -1
- package/src/utils/service-worker.d.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular/build",
|
|
3
|
-
"version": "21.0.0-next.
|
|
3
|
+
"version": "21.0.0-next.6",
|
|
4
4
|
"description": "Official build system for Angular",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Angular CLI",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"builders": "builders.json",
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@ampproject/remapping": "2.3.0",
|
|
26
|
-
"@angular-devkit/architect": "0.2100.0-next.
|
|
26
|
+
"@angular-devkit/architect": "0.2100.0-next.6",
|
|
27
27
|
"@babel/core": "7.28.4",
|
|
28
28
|
"@babel/helper-annotate-as-pure": "7.27.3",
|
|
29
29
|
"@babel/helper-split-export-declaration": "7.24.7",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@vitejs/plugin-basic-ssl": "2.1.0",
|
|
32
32
|
"beasties": "0.3.5",
|
|
33
33
|
"browserslist": "^4.26.0",
|
|
34
|
-
"esbuild": "0.25.
|
|
34
|
+
"esbuild": "0.25.10",
|
|
35
35
|
"https-proxy-agent": "7.0.6",
|
|
36
36
|
"istanbul-lib-instrument": "6.0.3",
|
|
37
37
|
"jsonc-parser": "3.3.1",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
"parse5-html-rewriting-stream": "8.0.0",
|
|
42
42
|
"picomatch": "4.0.3",
|
|
43
43
|
"piscina": "5.1.3",
|
|
44
|
-
"rolldown": "1.0.0-beta.
|
|
45
|
-
"sass": "1.
|
|
44
|
+
"rolldown": "1.0.0-beta.41",
|
|
45
|
+
"sass": "1.93.2",
|
|
46
46
|
"semver": "7.7.2",
|
|
47
47
|
"source-map-support": "0.5.21",
|
|
48
48
|
"tinyglobby": "0.2.15",
|
|
49
|
-
"vite": "7.1.
|
|
49
|
+
"vite": "7.1.7",
|
|
50
50
|
"watchpack": "2.4.4"
|
|
51
51
|
},
|
|
52
52
|
"optionalDependencies": {
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"@angular/platform-browser": "^21.0.0-next.0",
|
|
61
61
|
"@angular/platform-server": "^21.0.0-next.0",
|
|
62
62
|
"@angular/service-worker": "^21.0.0-next.0",
|
|
63
|
-
"@angular/ssr": "^21.0.0-next.
|
|
63
|
+
"@angular/ssr": "^21.0.0-next.6",
|
|
64
64
|
"karma": "^6.4.0",
|
|
65
65
|
"less": "^4.2.0",
|
|
66
66
|
"ng-packagr": "^21.0.0-next.0",
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"type": "git",
|
|
113
113
|
"url": "https://github.com/angular/angular-cli.git"
|
|
114
114
|
},
|
|
115
|
-
"packageManager": "pnpm@10.17.
|
|
115
|
+
"packageManager": "pnpm@10.17.1",
|
|
116
116
|
"engines": {
|
|
117
117
|
"node": "^20.19.0 || ^22.12.0 || >=24.0.0",
|
|
118
118
|
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
|
|
@@ -155,14 +155,18 @@ async function setupBuildOptions(options, context, projectSourceRoot, outputPath
|
|
|
155
155
|
return { buildOptions, mainName };
|
|
156
156
|
}
|
|
157
157
|
async function runEsbuild(buildOptions, context, projectSourceRoot) {
|
|
158
|
+
const usesZoneJS = buildOptions.polyfills?.includes('zone.js');
|
|
158
159
|
const virtualTestBedInit = (0, virtual_module_plugin_1.createVirtualModulePlugin)({
|
|
159
160
|
namespace: 'angular:test-bed-init',
|
|
160
161
|
loadContent: async () => {
|
|
161
162
|
const contents = [
|
|
162
163
|
// Initialize the Angular testing environment
|
|
164
|
+
`import { NgModule${usesZoneJS ? ', provideZoneChangeDetection' : ''} } from '@angular/core';`,
|
|
163
165
|
`import { getTestBed } from '@angular/core/testing';`,
|
|
164
166
|
`import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';`,
|
|
165
|
-
|
|
167
|
+
`@NgModule({ providers: [${usesZoneJS ? 'provideZoneChangeDetection(), ' : ''}], })`,
|
|
168
|
+
`export class TestModule {}`,
|
|
169
|
+
`getTestBed().initTestEnvironment([BrowserTestingModule, TestModule], platformBrowserTesting(), {`,
|
|
166
170
|
` errorOnUnknownElements: true,`,
|
|
167
171
|
` errorOnUnknownProperties: true,`,
|
|
168
172
|
`});`,
|
|
@@ -17,7 +17,7 @@ const tinyglobby_1 = require("tinyglobby");
|
|
|
17
17
|
function createInstrumentationFilter(includedBasePath, excludedPaths) {
|
|
18
18
|
return (request) => {
|
|
19
19
|
return (!excludedPaths.has(request) &&
|
|
20
|
-
!/\.(e2e|spec)\.tsx?$|[\\/]node_modules[\\/]/.test(request) &&
|
|
20
|
+
!/\.(e2e|spec)\.tsx?$|[\\/]node_modules[\\/]|[\\/]\.angular[\\/]/.test(request) &&
|
|
21
21
|
request.startsWith(includedBasePath));
|
|
22
22
|
};
|
|
23
23
|
}
|
|
@@ -5,12 +5,4 @@
|
|
|
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
|
|
9
|
-
interface TestEntrypointsOptions {
|
|
10
|
-
projectSourceRoot: string;
|
|
11
|
-
workspaceRoot: string;
|
|
12
|
-
removeTestExtension?: boolean;
|
|
13
|
-
}
|
|
14
|
-
/** Generate unique bundle names for a set of test files. */
|
|
15
|
-
export declare function getTestEntrypoints(testFiles: string[], { projectSourceRoot, workspaceRoot, removeTestExtension }: TestEntrypointsOptions): Map<string, string>;
|
|
16
|
-
export {};
|
|
8
|
+
export { findTests, getTestEntrypoints } from '../unit-test/test-discovery';
|
|
@@ -7,109 +7,9 @@
|
|
|
7
7
|
* found in the LICENSE file at https://angular.dev/license
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.findTests =
|
|
11
|
-
exports
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
/* Go through all patterns and find unique list of files */
|
|
17
|
-
async function findTests(include, exclude, workspaceRoot, projectSourceRoot) {
|
|
18
|
-
const matchingTestsPromises = include.map((pattern) => findMatchingTests(pattern, exclude, workspaceRoot, projectSourceRoot));
|
|
19
|
-
const files = await Promise.all(matchingTestsPromises);
|
|
20
|
-
// Unique file names
|
|
21
|
-
return [...new Set(files.flat())];
|
|
22
|
-
}
|
|
23
|
-
/** Generate unique bundle names for a set of test files. */
|
|
24
|
-
function getTestEntrypoints(testFiles, { projectSourceRoot, workspaceRoot, removeTestExtension }) {
|
|
25
|
-
const seen = new Set();
|
|
26
|
-
return new Map(Array.from(testFiles, (testFile) => {
|
|
27
|
-
const relativePath = removeRoots(testFile, [projectSourceRoot, workspaceRoot])
|
|
28
|
-
// Strip leading dots and path separators.
|
|
29
|
-
.replace(/^[./\\]+/, '')
|
|
30
|
-
// Replace any path separators with dashes.
|
|
31
|
-
.replace(/[/\\]/g, '-');
|
|
32
|
-
let fileName = (0, node_path_1.basename)(relativePath, (0, node_path_1.extname)(relativePath));
|
|
33
|
-
if (removeTestExtension) {
|
|
34
|
-
fileName = fileName.replace(/\.(spec|test)$/, '');
|
|
35
|
-
}
|
|
36
|
-
const baseName = `spec-${fileName}`;
|
|
37
|
-
let uniqueName = baseName;
|
|
38
|
-
let suffix = 2;
|
|
39
|
-
while (seen.has(uniqueName)) {
|
|
40
|
-
uniqueName = `${baseName}-${suffix}`.replace(/([^\w](?:spec|test))-([\d]+)$/, '-$2$1');
|
|
41
|
-
++suffix;
|
|
42
|
-
}
|
|
43
|
-
seen.add(uniqueName);
|
|
44
|
-
return [uniqueName, testFile];
|
|
45
|
-
}));
|
|
46
|
-
}
|
|
47
|
-
const removeLeadingSlash = (pattern) => {
|
|
48
|
-
if (pattern.charAt(0) === '/') {
|
|
49
|
-
return pattern.substring(1);
|
|
50
|
-
}
|
|
51
|
-
return pattern;
|
|
52
|
-
};
|
|
53
|
-
const removeRelativeRoot = (path, root) => {
|
|
54
|
-
if (path.startsWith(root)) {
|
|
55
|
-
return path.substring(root.length);
|
|
56
|
-
}
|
|
57
|
-
return path;
|
|
58
|
-
};
|
|
59
|
-
function removeRoots(path, roots) {
|
|
60
|
-
for (const root of roots) {
|
|
61
|
-
if (path.startsWith(root)) {
|
|
62
|
-
return path.substring(root.length);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return (0, node_path_1.basename)(path);
|
|
66
|
-
}
|
|
67
|
-
async function findMatchingTests(pattern, ignore, workspaceRoot, projectSourceRoot) {
|
|
68
|
-
// normalize pattern, glob lib only accepts forward slashes
|
|
69
|
-
let normalizedPattern = (0, path_1.toPosixPath)(pattern);
|
|
70
|
-
normalizedPattern = removeLeadingSlash(normalizedPattern);
|
|
71
|
-
const relativeProjectRoot = (0, path_1.toPosixPath)((0, node_path_1.relative)(workspaceRoot, projectSourceRoot) + '/');
|
|
72
|
-
// remove relativeProjectRoot to support relative paths from root
|
|
73
|
-
// such paths are easy to get when running scripts via IDEs
|
|
74
|
-
normalizedPattern = removeRelativeRoot(normalizedPattern, relativeProjectRoot);
|
|
75
|
-
// special logic when pattern does not look like a glob
|
|
76
|
-
if (!(0, tinyglobby_1.isDynamicPattern)(normalizedPattern)) {
|
|
77
|
-
if (await isDirectory((0, node_path_1.join)(projectSourceRoot, normalizedPattern))) {
|
|
78
|
-
normalizedPattern = `${normalizedPattern}/**/*.spec.@(ts|tsx)`;
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
// see if matching spec file exists
|
|
82
|
-
const fileExt = (0, node_path_1.extname)(normalizedPattern);
|
|
83
|
-
// Replace extension to `.spec.ext`. Example: `src/app/app.component.ts`-> `src/app/app.component.spec.ts`
|
|
84
|
-
const potentialSpec = (0, node_path_1.join)(projectSourceRoot, (0, node_path_1.dirname)(normalizedPattern), `${(0, node_path_1.basename)(normalizedPattern, fileExt)}.spec${fileExt}`);
|
|
85
|
-
if (await exists(potentialSpec)) {
|
|
86
|
-
return [potentialSpec];
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
// normalize the patterns in the ignore list
|
|
91
|
-
const normalizedIgnorePatternList = ignore.map((pattern) => removeRelativeRoot(removeLeadingSlash((0, path_1.toPosixPath)(pattern)), relativeProjectRoot));
|
|
92
|
-
return (0, tinyglobby_1.glob)(normalizedPattern, {
|
|
93
|
-
cwd: projectSourceRoot,
|
|
94
|
-
absolute: true,
|
|
95
|
-
ignore: ['**/node_modules/**', ...normalizedIgnorePatternList],
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
async function isDirectory(path) {
|
|
99
|
-
try {
|
|
100
|
-
const stats = await node_fs_1.promises.stat(path);
|
|
101
|
-
return stats.isDirectory();
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
async function exists(path) {
|
|
108
|
-
try {
|
|
109
|
-
await node_fs_1.promises.access(path, node_fs_1.constants.F_OK);
|
|
110
|
-
return true;
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
10
|
+
exports.getTestEntrypoints = exports.findTests = void 0;
|
|
11
|
+
// This file is a compatibility layer that re-exports the test discovery logic from its new location.
|
|
12
|
+
// This is necessary to avoid breaking the Karma builder, which still depends on this file.
|
|
13
|
+
var test_discovery_1 = require("../unit-test/test-discovery");
|
|
14
|
+
Object.defineProperty(exports, "findTests", { enumerable: true, get: function () { return test_discovery_1.findTests; } });
|
|
15
|
+
Object.defineProperty(exports, "getTestEntrypoints", { enumerable: true, get: function () { return test_discovery_1.getTestEntrypoints; } });
|
|
@@ -107,6 +107,7 @@ const application_1 = require("../application");
|
|
|
107
107
|
const results_1 = require("../application/results");
|
|
108
108
|
const options_1 = require("./options");
|
|
109
109
|
const dependency_checker_1 = require("./runners/dependency-checker");
|
|
110
|
+
const test_discovery_1 = require("./test-discovery");
|
|
110
111
|
async function loadTestRunner(runnerName) {
|
|
111
112
|
// Harden against directory traversal
|
|
112
113
|
if (!/^[a-zA-Z0-9-]+$/.test(runnerName)) {
|
|
@@ -226,6 +227,15 @@ async function* execute(options, context, extensions) {
|
|
|
226
227
|
yield { success: false };
|
|
227
228
|
return;
|
|
228
229
|
}
|
|
230
|
+
if (normalizedOptions.listTests) {
|
|
231
|
+
const testFiles = await (0, test_discovery_1.findTests)(normalizedOptions.include, normalizedOptions.exclude ?? [], normalizedOptions.workspaceRoot, normalizedOptions.projectSourceRoot);
|
|
232
|
+
context.logger.info('Discovered test files:');
|
|
233
|
+
for (const file of testFiles) {
|
|
234
|
+
context.logger.info(` ${node_path_1.default.relative(normalizedOptions.workspaceRoot, file)}`);
|
|
235
|
+
}
|
|
236
|
+
yield { success: true };
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
229
239
|
if (runner.isStandalone) {
|
|
230
240
|
try {
|
|
231
241
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
@@ -256,7 +266,15 @@ async function* execute(options, context, extensions) {
|
|
|
256
266
|
// Get base build options from the buildTarget
|
|
257
267
|
let buildTargetOptions;
|
|
258
268
|
try {
|
|
259
|
-
|
|
269
|
+
const builderName = await context.getBuilderNameForTarget(normalizedOptions.buildTarget);
|
|
270
|
+
if (builderName !== '@angular/build:application' &&
|
|
271
|
+
// TODO: Add comprehensive support for ng-packagr.
|
|
272
|
+
builderName !== '@angular/build:ng-packagr') {
|
|
273
|
+
context.logger.warn(`The 'buildTarget' is configured to use '${builderName}', which is not supported. ` +
|
|
274
|
+
`The 'unit-test' builder is designed to work with '@angular/build:application'. ` +
|
|
275
|
+
'Unexpected behavior or build failures may occur.');
|
|
276
|
+
}
|
|
277
|
+
buildTargetOptions = (await context.validateOptions(await context.getTargetOptions(normalizedOptions.buildTarget), builderName));
|
|
260
278
|
}
|
|
261
279
|
catch (e) {
|
|
262
280
|
(0, error_1.assertIsError)(e);
|
|
@@ -293,8 +311,8 @@ async function* execute(options, context, extensions) {
|
|
|
293
311
|
...buildTargetOptions,
|
|
294
312
|
...runnerBuildOptions,
|
|
295
313
|
watch: normalizedOptions.watch,
|
|
296
|
-
tsConfig: normalizedOptions.tsConfig,
|
|
297
314
|
progress: normalizedOptions.buildProgress ?? buildTargetOptions.progress,
|
|
315
|
+
...(normalizedOptions.tsConfig ? { tsConfig: normalizedOptions.tsConfig } : {}),
|
|
298
316
|
};
|
|
299
317
|
const dumpDirectory = normalizedOptions.dumpVirtualFiles
|
|
300
318
|
? node_path_1.default.join(normalizedOptions.cacheOptions.path, 'unit-test', 'output-files')
|
|
@@ -18,19 +18,33 @@ export declare function normalizeOptions(context: BuilderContext, projectName: s
|
|
|
18
18
|
exclude: string[] | undefined;
|
|
19
19
|
filter: string | undefined;
|
|
20
20
|
runnerName: import("./schema").Runner;
|
|
21
|
-
|
|
21
|
+
coverage: {
|
|
22
|
+
all: boolean | undefined;
|
|
22
23
|
exclude: string[] | undefined;
|
|
24
|
+
include: string[] | undefined;
|
|
23
25
|
reporters: [string, Record<string, unknown>][] | undefined;
|
|
26
|
+
thresholds: import("./schema").CoverageThresholds | undefined;
|
|
27
|
+
watermarks: {
|
|
28
|
+
statements?: [number, number];
|
|
29
|
+
branches?: [number, number];
|
|
30
|
+
functions?: [number, number];
|
|
31
|
+
lines?: [number, number];
|
|
32
|
+
};
|
|
24
33
|
} | undefined;
|
|
25
|
-
tsConfig: string;
|
|
34
|
+
tsConfig: string | undefined;
|
|
26
35
|
buildProgress: boolean | undefined;
|
|
27
36
|
reporters: [string, Record<string, unknown>][] | undefined;
|
|
28
37
|
outputFile: string | undefined;
|
|
29
38
|
browsers: string[] | undefined;
|
|
39
|
+
browserViewport: {
|
|
40
|
+
width: number;
|
|
41
|
+
height: number;
|
|
42
|
+
} | undefined;
|
|
30
43
|
watch: boolean;
|
|
31
44
|
debug: boolean;
|
|
32
45
|
providersFile: string | undefined;
|
|
33
46
|
setupFiles: string[];
|
|
34
47
|
dumpVirtualFiles: boolean | undefined;
|
|
48
|
+
listTests: boolean | undefined;
|
|
35
49
|
}>;
|
|
36
50
|
export declare function injectTestingPolyfills(polyfills?: string[]): string[];
|
|
@@ -13,10 +13,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
13
13
|
exports.normalizeOptions = normalizeOptions;
|
|
14
14
|
exports.injectTestingPolyfills = injectTestingPolyfills;
|
|
15
15
|
const architect_1 = require("@angular-devkit/architect");
|
|
16
|
+
const node_fs_1 = require("node:fs");
|
|
16
17
|
const node_path_1 = __importDefault(require("node:path"));
|
|
17
18
|
const normalize_cache_1 = require("../../utils/normalize-cache");
|
|
18
19
|
const project_metadata_1 = require("../../utils/project-metadata");
|
|
19
20
|
const tty_1 = require("../../utils/tty");
|
|
21
|
+
async function exists(path) {
|
|
22
|
+
try {
|
|
23
|
+
await node_fs_1.promises.access(path, node_fs_1.constants.F_OK);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
20
30
|
function normalizeReporterOption(reporters) {
|
|
21
31
|
return reporters?.map((entry) => typeof entry === 'string'
|
|
22
32
|
? [entry, {}]
|
|
@@ -33,7 +43,22 @@ async function normalizeOptions(context, projectName, options) {
|
|
|
33
43
|
// Target specifier defaults to the current project's build target using a development configuration
|
|
34
44
|
const buildTargetSpecifier = options.buildTarget ?? `::development`;
|
|
35
45
|
const buildTarget = (0, architect_1.targetFromTargetString)(buildTargetSpecifier, projectName, 'build');
|
|
36
|
-
const {
|
|
46
|
+
const { runner, browsers, progress, filter, browserViewport } = options;
|
|
47
|
+
const [width, height] = browserViewport?.split('x').map(Number) ?? [];
|
|
48
|
+
let tsConfig = options.tsConfig;
|
|
49
|
+
if (tsConfig) {
|
|
50
|
+
const fullTsConfigPath = node_path_1.default.join(workspaceRoot, tsConfig);
|
|
51
|
+
if (!(await exists(fullTsConfigPath))) {
|
|
52
|
+
throw new Error(`The specified tsConfig file '${tsConfig}' does not exist.`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const tsconfigSpecPath = node_path_1.default.join(projectRoot, 'tsconfig.spec.json');
|
|
57
|
+
if (await exists(tsconfigSpecPath)) {
|
|
58
|
+
// The application builder expects a path relative to the workspace root.
|
|
59
|
+
tsConfig = node_path_1.default.relative(workspaceRoot, tsconfigSpecPath);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
37
62
|
return {
|
|
38
63
|
// Project/workspace information
|
|
39
64
|
workspaceRoot,
|
|
@@ -46,10 +71,16 @@ async function normalizeOptions(context, projectName, options) {
|
|
|
46
71
|
exclude: options.exclude,
|
|
47
72
|
filter,
|
|
48
73
|
runnerName: runner,
|
|
49
|
-
|
|
74
|
+
coverage: options.coverage
|
|
50
75
|
? {
|
|
51
|
-
|
|
52
|
-
|
|
76
|
+
all: options.coverageAll,
|
|
77
|
+
exclude: options.coverageExclude,
|
|
78
|
+
include: options.coverageInclude,
|
|
79
|
+
reporters: normalizeReporterOption(options.coverageReporters),
|
|
80
|
+
thresholds: options.coverageThresholds,
|
|
81
|
+
// The schema generation tool doesn't support tuple types for items, but the schema validation
|
|
82
|
+
// does ensure that the array has exactly two numbers.
|
|
83
|
+
watermarks: options.coverageWatermarks,
|
|
53
84
|
}
|
|
54
85
|
: undefined,
|
|
55
86
|
tsConfig,
|
|
@@ -57,6 +88,7 @@ async function normalizeOptions(context, projectName, options) {
|
|
|
57
88
|
reporters: normalizeReporterOption(options.reporters),
|
|
58
89
|
outputFile: options.outputFile,
|
|
59
90
|
browsers,
|
|
91
|
+
browserViewport: width && height ? { width, height } : undefined,
|
|
60
92
|
watch: options.watch ?? (0, tty_1.isTTY)(),
|
|
61
93
|
debug: options.debug ?? false,
|
|
62
94
|
providersFile: options.providersFile && node_path_1.default.join(workspaceRoot, options.providersFile),
|
|
@@ -64,6 +96,7 @@ async function normalizeOptions(context, projectName, options) {
|
|
|
64
96
|
? options.setupFiles.map((setupFile) => node_path_1.default.join(workspaceRoot, setupFile))
|
|
65
97
|
: [],
|
|
66
98
|
dumpVirtualFiles: options.dumpVirtualFiles,
|
|
99
|
+
listTests: options.listTests,
|
|
67
100
|
};
|
|
68
101
|
}
|
|
69
102
|
function injectTestingPolyfills(polyfills = []) {
|
|
@@ -50,15 +50,24 @@ class KarmaExecutor {
|
|
|
50
50
|
}
|
|
51
51
|
async *execute() {
|
|
52
52
|
const { context, options: unitTestOptions } = this;
|
|
53
|
+
if (unitTestOptions.browserViewport) {
|
|
54
|
+
context.logger.warn('The "karma" test runner does not support the "browserViewport" option. The option will be ignored.');
|
|
55
|
+
}
|
|
53
56
|
if (unitTestOptions.debug) {
|
|
54
57
|
context.logger.warn('The "karma" test runner does not support the "debug" option. The option will be ignored.');
|
|
55
58
|
}
|
|
56
59
|
if (unitTestOptions.setupFiles.length) {
|
|
57
60
|
context.logger.warn('The "karma" test runner does not support the "setupFiles" option. The option will be ignored.');
|
|
58
61
|
}
|
|
62
|
+
if (unitTestOptions.coverage?.all) {
|
|
63
|
+
context.logger.warn('The "karma" test runner does not support the "coverageAll" option. The option will be ignored.');
|
|
64
|
+
}
|
|
65
|
+
if (unitTestOptions.coverage?.include) {
|
|
66
|
+
context.logger.warn('The "karma" test runner does not support the "coverageInclude" option. The option will be ignored.');
|
|
67
|
+
}
|
|
59
68
|
const buildTargetOptions = (await context.validateOptions(await context.getTargetOptions(unitTestOptions.buildTarget), await context.getBuilderNameForTarget(unitTestOptions.buildTarget)));
|
|
60
69
|
const karmaOptions = {
|
|
61
|
-
tsConfig: unitTestOptions.tsConfig,
|
|
70
|
+
tsConfig: unitTestOptions.tsConfig ?? buildTargetOptions.tsConfig,
|
|
62
71
|
polyfills: buildTargetOptions.polyfills,
|
|
63
72
|
assets: buildTargetOptions.assets,
|
|
64
73
|
scripts: buildTargetOptions.scripts,
|
|
@@ -76,8 +85,8 @@ class KarmaExecutor {
|
|
|
76
85
|
poll: buildTargetOptions.poll,
|
|
77
86
|
preserveSymlinks: buildTargetOptions.preserveSymlinks,
|
|
78
87
|
browsers: unitTestOptions.browsers?.join(','),
|
|
79
|
-
codeCoverage: !!unitTestOptions.
|
|
80
|
-
codeCoverageExclude: unitTestOptions.
|
|
88
|
+
codeCoverage: !!unitTestOptions.coverage,
|
|
89
|
+
codeCoverageExclude: unitTestOptions.coverage?.exclude,
|
|
81
90
|
fileReplacements: buildTargetOptions.fileReplacements,
|
|
82
91
|
reporters: unitTestOptions.reporters?.map((reporter) => {
|
|
83
92
|
// Karma only supports string reporters.
|
|
@@ -104,6 +113,20 @@ class KarmaExecutor {
|
|
|
104
113
|
options.client.args ??= [];
|
|
105
114
|
options.client.args.push('--grep', filter);
|
|
106
115
|
}
|
|
116
|
+
// Add coverage options
|
|
117
|
+
if (unitTestOptions.coverage) {
|
|
118
|
+
const { thresholds, watermarks } = unitTestOptions.coverage;
|
|
119
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
120
|
+
const coverageReporter = (options.coverageReporter ??= {});
|
|
121
|
+
if (thresholds) {
|
|
122
|
+
coverageReporter.check = thresholds.perFile
|
|
123
|
+
? { each: thresholds }
|
|
124
|
+
: { global: thresholds };
|
|
125
|
+
}
|
|
126
|
+
if (watermarks) {
|
|
127
|
+
coverageReporter.watermarks = watermarks;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
107
130
|
return options;
|
|
108
131
|
},
|
|
109
132
|
};
|
|
@@ -9,4 +9,7 @@ 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
|
|
12
|
+
export declare function setupBrowserConfiguration(browsers: string[] | undefined, debug: boolean, projectSourceRoot: string, viewport: {
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
} | undefined): BrowserConfiguration;
|
|
@@ -28,7 +28,7 @@ function normalizeBrowserName(browserName) {
|
|
|
28
28
|
const normalized = browserName.toLowerCase();
|
|
29
29
|
return normalized.replace(/headless$/, '');
|
|
30
30
|
}
|
|
31
|
-
function setupBrowserConfiguration(browsers, debug, projectSourceRoot) {
|
|
31
|
+
function setupBrowserConfiguration(browsers, debug, projectSourceRoot, viewport) {
|
|
32
32
|
if (browsers === undefined) {
|
|
33
33
|
return {};
|
|
34
34
|
}
|
|
@@ -57,10 +57,14 @@ function setupBrowserConfiguration(browsers, debug, projectSourceRoot) {
|
|
|
57
57
|
if (errors) {
|
|
58
58
|
return { errors };
|
|
59
59
|
}
|
|
60
|
+
const isCI = !!process.env['CI'];
|
|
61
|
+
const headless = isCI || browsers.some((name) => name.toLowerCase().includes('headless'));
|
|
60
62
|
const browser = {
|
|
61
63
|
enabled: true,
|
|
62
64
|
provider,
|
|
63
|
-
headless
|
|
65
|
+
headless,
|
|
66
|
+
ui: !headless,
|
|
67
|
+
viewport,
|
|
64
68
|
instances: browsers.map((browserName) => ({
|
|
65
69
|
browser: normalizeBrowserName(browserName),
|
|
66
70
|
})),
|
|
@@ -16,7 +16,8 @@ const path_1 = require("../../../../utils/path");
|
|
|
16
16
|
const schema_1 = require("../../../application/schema");
|
|
17
17
|
const options_1 = require("../../options");
|
|
18
18
|
const test_discovery_1 = require("../../test-discovery");
|
|
19
|
-
function createTestBedInitVirtualFile(providersFile, projectSourceRoot) {
|
|
19
|
+
function createTestBedInitVirtualFile(providersFile, projectSourceRoot, polyfills = []) {
|
|
20
|
+
const usesZoneJS = polyfills.includes('zone.js');
|
|
20
21
|
let providersImport = 'const providers = [];';
|
|
21
22
|
if (providersFile) {
|
|
22
23
|
const relativePath = node_path_1.default.relative(projectSourceRoot, providersFile);
|
|
@@ -26,7 +27,7 @@ function createTestBedInitVirtualFile(providersFile, projectSourceRoot) {
|
|
|
26
27
|
}
|
|
27
28
|
return `
|
|
28
29
|
// Initialize the Angular testing environment
|
|
29
|
-
import { NgModule } from '@angular/core';
|
|
30
|
+
import { NgModule${usesZoneJS ? ', provideZoneChangeDetection' : ''} } from '@angular/core';
|
|
30
31
|
import { getTestBed, ɵgetCleanupHook as getCleanupHook } from '@angular/core/testing';
|
|
31
32
|
import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';
|
|
32
33
|
${providersImport}
|
|
@@ -34,7 +35,7 @@ function createTestBedInitVirtualFile(providersFile, projectSourceRoot) {
|
|
|
34
35
|
beforeEach(getCleanupHook(false));
|
|
35
36
|
afterEach(getCleanupHook(true));
|
|
36
37
|
@NgModule({
|
|
37
|
-
providers,
|
|
38
|
+
providers: [${usesZoneJS ? 'provideZoneChangeDetection(), ' : ''}...providers],
|
|
38
39
|
})
|
|
39
40
|
export class TestModule {}
|
|
40
41
|
getTestBed().initTestEnvironment([BrowserTestingModule, TestModule], platformBrowserTesting(), {
|
|
@@ -54,7 +55,7 @@ function adjustOutputHashing(hashing) {
|
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
async function getVitestBuildOptions(options, baseBuildOptions) {
|
|
57
|
-
const { workspaceRoot, projectSourceRoot, include, exclude = [], watch,
|
|
58
|
+
const { workspaceRoot, projectSourceRoot, include, exclude = [], watch, providersFile } = options;
|
|
58
59
|
// Find test files
|
|
59
60
|
const testFiles = await (0, test_discovery_1.findTests)(include, exclude, workspaceRoot, projectSourceRoot);
|
|
60
61
|
if (testFiles.length === 0) {
|
|
@@ -86,12 +87,11 @@ async function getVitestBuildOptions(options, baseBuildOptions) {
|
|
|
86
87
|
sourceMap: { scripts: true, vendor: false, styles: false },
|
|
87
88
|
outputHashing: adjustOutputHashing(baseBuildOptions.outputHashing),
|
|
88
89
|
optimization: false,
|
|
89
|
-
tsConfig,
|
|
90
90
|
entryPoints,
|
|
91
91
|
externalDependencies: ['vitest', '@vitest/browser/context'],
|
|
92
92
|
};
|
|
93
93
|
buildOptions.polyfills = (0, options_1.injectTestingPolyfills)(buildOptions.polyfills);
|
|
94
|
-
const testBedInitContents = createTestBedInitVirtualFile(providersFile, projectSourceRoot);
|
|
94
|
+
const testBedInitContents = createTestBedInitVirtualFile(providersFile, projectSourceRoot, buildOptions.polyfills);
|
|
95
95
|
return {
|
|
96
96
|
buildOptions,
|
|
97
97
|
virtualFiles: {
|
|
@@ -101,7 +101,7 @@ class VitestExecutor {
|
|
|
101
101
|
return testSetupFiles;
|
|
102
102
|
}
|
|
103
103
|
async initializeVitest() {
|
|
104
|
-
const {
|
|
104
|
+
const { coverage, reporters, outputFile, workspaceRoot, browsers, debug, watch, browserViewport, } = this.options;
|
|
105
105
|
let vitestNodeModule;
|
|
106
106
|
try {
|
|
107
107
|
vitestNodeModule = await (0, load_esm_1.loadEsmModule)('vitest/node');
|
|
@@ -115,7 +115,7 @@ class VitestExecutor {
|
|
|
115
115
|
}
|
|
116
116
|
const { startVitest } = vitestNodeModule;
|
|
117
117
|
// Setup vitest browser options if configured
|
|
118
|
-
const browserOptions = (0, browser_provider_1.setupBrowserConfiguration)(browsers, debug, this.options.projectSourceRoot);
|
|
118
|
+
const browserOptions = (0, browser_provider_1.setupBrowserConfiguration)(browsers, debug, this.options.projectSourceRoot, browserViewport);
|
|
119
119
|
if (browserOptions.errors?.length) {
|
|
120
120
|
throw new Error(browserOptions.errors.join('\n'));
|
|
121
121
|
}
|
|
@@ -148,7 +148,7 @@ class VitestExecutor {
|
|
|
148
148
|
reporters: reporters ?? ['default'],
|
|
149
149
|
outputFile,
|
|
150
150
|
watch,
|
|
151
|
-
coverage: generateCoverageOption(
|
|
151
|
+
coverage: await generateCoverageOption(coverage, this.projectName),
|
|
152
152
|
...debugOptions,
|
|
153
153
|
}, {
|
|
154
154
|
server: {
|
|
@@ -161,19 +161,41 @@ class VitestExecutor {
|
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
exports.VitestExecutor = VitestExecutor;
|
|
164
|
-
function generateCoverageOption(
|
|
165
|
-
if (!
|
|
164
|
+
async function generateCoverageOption(coverage, projectName) {
|
|
165
|
+
if (!coverage) {
|
|
166
166
|
return {
|
|
167
167
|
enabled: false,
|
|
168
168
|
};
|
|
169
169
|
}
|
|
170
|
+
let defaultExcludes = [];
|
|
171
|
+
if (coverage.exclude) {
|
|
172
|
+
try {
|
|
173
|
+
const vitestConfig = await (0, load_esm_1.loadEsmModule)('vitest/config');
|
|
174
|
+
defaultExcludes = vitestConfig.coverageConfigDefaults.exclude;
|
|
175
|
+
}
|
|
176
|
+
catch { }
|
|
177
|
+
}
|
|
170
178
|
return {
|
|
171
179
|
enabled: true,
|
|
180
|
+
all: coverage.all,
|
|
172
181
|
excludeAfterRemap: true,
|
|
182
|
+
include: coverage.include,
|
|
183
|
+
reportsDirectory: (0, path_1.toPosixPath)(node_path_1.default.join('coverage', projectName)),
|
|
184
|
+
thresholds: coverage.thresholds,
|
|
185
|
+
watermarks: coverage.watermarks,
|
|
173
186
|
// Special handling for `exclude`/`reporters` due to an undefined value causing upstream failures
|
|
174
|
-
...(
|
|
175
|
-
|
|
176
|
-
|
|
187
|
+
...(coverage.exclude
|
|
188
|
+
? {
|
|
189
|
+
exclude: [
|
|
190
|
+
// Augment the default exclude https://vitest.dev/config/#coverage-exclude
|
|
191
|
+
// with the user defined exclusions
|
|
192
|
+
...coverage.exclude,
|
|
193
|
+
...defaultExcludes,
|
|
194
|
+
],
|
|
195
|
+
}
|
|
196
|
+
: {}),
|
|
197
|
+
...(coverage.reporters
|
|
198
|
+
? { reporter: coverage.reporters }
|
|
177
199
|
: {}),
|
|
178
200
|
};
|
|
179
201
|
}
|