|
6 | 6 | * found in the LICENSE file at https://angular.dev/license |
7 | 7 | */ |
8 | 8 |
|
9 | | -import { PathLike, constants, promises as fs } from 'node:fs'; |
10 | | -import { basename, dirname, extname, join, relative } from 'node:path'; |
11 | | -import { glob, isDynamicPattern } from 'tinyglobby'; |
12 | | -import { toPosixPath } from '../../utils/path'; |
13 | | - |
14 | | -/* Go through all patterns and find unique list of files */ |
15 | | -export async function findTests( |
16 | | - include: string[], |
17 | | - exclude: string[], |
18 | | - workspaceRoot: string, |
19 | | - projectSourceRoot: string, |
20 | | -): Promise<string[]> { |
21 | | - const matchingTestsPromises = include.map((pattern) => |
22 | | - findMatchingTests(pattern, exclude, workspaceRoot, projectSourceRoot), |
23 | | - ); |
24 | | - const files = await Promise.all(matchingTestsPromises); |
25 | | - |
26 | | - // Unique file names |
27 | | - return [...new Set(files.flat())]; |
28 | | -} |
29 | | - |
30 | | -interface TestEntrypointsOptions { |
31 | | - projectSourceRoot: string; |
32 | | - workspaceRoot: string; |
33 | | - removeTestExtension?: boolean; |
34 | | -} |
35 | | - |
36 | | -/** Generate unique bundle names for a set of test files. */ |
37 | | -export function getTestEntrypoints( |
38 | | - testFiles: string[], |
39 | | - { projectSourceRoot, workspaceRoot, removeTestExtension }: TestEntrypointsOptions, |
40 | | -): Map<string, string> { |
41 | | - const seen = new Set<string>(); |
42 | | - |
43 | | - return new Map( |
44 | | - Array.from(testFiles, (testFile) => { |
45 | | - const relativePath = removeRoots(testFile, [projectSourceRoot, workspaceRoot]) |
46 | | - // Strip leading dots and path separators. |
47 | | - .replace(/^[./\\]+/, '') |
48 | | - // Replace any path separators with dashes. |
49 | | - .replace(/[/\\]/g, '-'); |
50 | | - |
51 | | - let fileName = basename(relativePath, extname(relativePath)); |
52 | | - if (removeTestExtension) { |
53 | | - fileName = fileName.replace(/\.(spec|test)$/, ''); |
54 | | - } |
55 | | - |
56 | | - const baseName = `spec-${fileName}`; |
57 | | - let uniqueName = baseName; |
58 | | - let suffix = 2; |
59 | | - while (seen.has(uniqueName)) { |
60 | | - uniqueName = `${baseName}-${suffix}`.replace(/([^\w](?:spec|test))-([\d]+)$/, '-$2$1'); |
61 | | - ++suffix; |
62 | | - } |
63 | | - seen.add(uniqueName); |
64 | | - |
65 | | - return [uniqueName, testFile]; |
66 | | - }), |
67 | | - ); |
68 | | -} |
69 | | - |
70 | | -const removeLeadingSlash = (pattern: string): string => { |
71 | | - if (pattern.charAt(0) === '/') { |
72 | | - return pattern.substring(1); |
73 | | - } |
74 | | - |
75 | | - return pattern; |
76 | | -}; |
77 | | - |
78 | | -const removeRelativeRoot = (path: string, root: string): string => { |
79 | | - if (path.startsWith(root)) { |
80 | | - return path.substring(root.length); |
81 | | - } |
82 | | - |
83 | | - return path; |
84 | | -}; |
85 | | - |
86 | | -function removeRoots(path: string, roots: string[]): string { |
87 | | - for (const root of roots) { |
88 | | - if (path.startsWith(root)) { |
89 | | - return path.substring(root.length); |
90 | | - } |
91 | | - } |
92 | | - |
93 | | - return basename(path); |
94 | | -} |
95 | | - |
96 | | -async function findMatchingTests( |
97 | | - pattern: string, |
98 | | - ignore: string[], |
99 | | - workspaceRoot: string, |
100 | | - projectSourceRoot: string, |
101 | | -): Promise<string[]> { |
102 | | - // normalize pattern, glob lib only accepts forward slashes |
103 | | - let normalizedPattern = toPosixPath(pattern); |
104 | | - normalizedPattern = removeLeadingSlash(normalizedPattern); |
105 | | - |
106 | | - const relativeProjectRoot = toPosixPath(relative(workspaceRoot, projectSourceRoot) + '/'); |
107 | | - |
108 | | - // remove relativeProjectRoot to support relative paths from root |
109 | | - // such paths are easy to get when running scripts via IDEs |
110 | | - normalizedPattern = removeRelativeRoot(normalizedPattern, relativeProjectRoot); |
111 | | - |
112 | | - // special logic when pattern does not look like a glob |
113 | | - if (!isDynamicPattern(normalizedPattern)) { |
114 | | - if (await isDirectory(join(projectSourceRoot, normalizedPattern))) { |
115 | | - normalizedPattern = `${normalizedPattern}/**/*.spec.@(ts|tsx)`; |
116 | | - } else { |
117 | | - // see if matching spec file exists |
118 | | - const fileExt = extname(normalizedPattern); |
119 | | - // Replace extension to `.spec.ext`. Example: `src/app/app.component.ts`-> `src/app/app.component.spec.ts` |
120 | | - const potentialSpec = join( |
121 | | - projectSourceRoot, |
122 | | - dirname(normalizedPattern), |
123 | | - `${basename(normalizedPattern, fileExt)}.spec${fileExt}`, |
124 | | - ); |
125 | | - |
126 | | - if (await exists(potentialSpec)) { |
127 | | - return [potentialSpec]; |
128 | | - } |
129 | | - } |
130 | | - } |
131 | | - |
132 | | - // normalize the patterns in the ignore list |
133 | | - const normalizedIgnorePatternList = ignore.map((pattern: string) => |
134 | | - removeRelativeRoot(removeLeadingSlash(toPosixPath(pattern)), relativeProjectRoot), |
135 | | - ); |
136 | | - |
137 | | - return glob(normalizedPattern, { |
138 | | - cwd: projectSourceRoot, |
139 | | - absolute: true, |
140 | | - ignore: ['**/node_modules/**', ...normalizedIgnorePatternList], |
141 | | - }); |
142 | | -} |
143 | | - |
144 | | -async function isDirectory(path: PathLike): Promise<boolean> { |
145 | | - try { |
146 | | - const stats = await fs.stat(path); |
147 | | - |
148 | | - return stats.isDirectory(); |
149 | | - } catch { |
150 | | - return false; |
151 | | - } |
152 | | -} |
153 | | - |
154 | | -async function exists(path: PathLike): Promise<boolean> { |
155 | | - try { |
156 | | - await fs.access(path, constants.F_OK); |
157 | | - |
158 | | - return true; |
159 | | - } catch { |
160 | | - return false; |
161 | | - } |
162 | | -} |
| 9 | +// This file is a compatibility layer that re-exports the test discovery logic from its new location. |
| 10 | +// This is necessary to avoid breaking the Karma builder, which still depends on this file. |
| 11 | +export { findTests, getTestEntrypoints } from '../unit-test/test-discovery'; |
0 commit comments