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