test-inventory 0.7.0 → 0.9.0
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/CHANGELOG.md +180 -0
- package/README.md +16 -10
- package/dist/case-titles.d.ts +22 -0
- package/dist/case-titles.js +184 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +5 -5
- package/dist/config-dependencies.js +27 -3
- package/dist/config-runs.d.ts +117 -3
- package/dist/config-runs.js +1015 -19
- package/dist/config.d.ts +42 -1
- package/dist/config.js +287 -21
- package/dist/dependency-hash.d.ts +13 -5
- package/dist/dependency-hash.js +56 -12
- package/dist/diagnostics.js +16 -1
- package/dist/extract.d.ts +2 -0
- package/dist/extract.js +89 -29
- package/dist/files.js +4 -2
- package/dist/filter.js +1 -0
- package/dist/framework-version.d.ts +13 -2
- package/dist/framework-version.js +207 -7
- package/dist/frameworks/bindings.d.ts +14 -0
- package/dist/frameworks/bindings.js +99 -6
- package/dist/frameworks/bun.d.ts +42 -0
- package/dist/frameworks/bun.js +272 -0
- package/dist/frameworks/common.d.ts +3 -1
- package/dist/frameworks/common.js +18 -9
- package/dist/frameworks/cypress.d.ts +31 -0
- package/dist/frameworks/cypress.js +70 -0
- package/dist/frameworks/jasmine.d.ts +28 -0
- package/dist/frameworks/jasmine.js +218 -0
- package/dist/frameworks/jest.d.ts +46 -0
- package/dist/frameworks/jest.js +442 -0
- package/dist/frameworks/mocha.d.ts +50 -0
- package/dist/frameworks/mocha.js +306 -0
- package/dist/frameworks/node.d.ts +30 -0
- package/dist/frameworks/node.js +268 -0
- package/dist/frameworks/playwright.d.ts +13 -0
- package/dist/frameworks/playwright.js +57 -0
- package/dist/frameworks/test-functions.d.ts +194 -0
- package/dist/frameworks/test-functions.js +534 -0
- package/dist/frameworks/vitest.d.ts +15 -62
- package/dist/frameworks/vitest.js +69 -389
- package/dist/frameworks/webdriverio.d.ts +27 -0
- package/dist/frameworks/webdriverio.js +37 -0
- package/dist/imported-values.js +15 -1
- package/dist/model.d.ts +9 -2
- package/dist/node-scripts.d.ts +6 -0
- package/dist/node-scripts.js +85 -0
- package/dist/options.d.ts +16 -3
- package/dist/options.js +22 -3
- package/dist/parse.js +17 -2
- package/dist/records.d.ts +8 -2
- package/dist/records.js +94 -22
- package/dist/resolve.d.ts +21 -4
- package/dist/resolve.js +91 -52
- package/dist/scan-file.d.ts +8 -0
- package/dist/scan-file.js +21 -3
- package/dist/scan.js +62 -12
- package/dist/toml.d.ts +13 -0
- package/dist/toml.js +172 -0
- package/dist/types.d.ts +27 -11
- package/dist/values.d.ts +15 -0
- package/dist/values.js +120 -2
- package/dist/yaml.d.ts +12 -0
- package/dist/yaml.js +148 -0
- package/package.json +7 -6
- package/schema/v1.json +30 -8
package/dist/model.d.ts
CHANGED
|
@@ -37,6 +37,8 @@ export interface Problem {
|
|
|
37
37
|
level?: DiagnosticLevel;
|
|
38
38
|
/** Raised by code in a test, hook or helper body, which runs after the file is loaded and so can't stop it loading. */
|
|
39
39
|
afterLoad?: boolean;
|
|
40
|
+
/** The runner still runs the tests the file declared before the call that stops it loading, as WebdriverIO's Mocha. */
|
|
41
|
+
keepsEarlierTests?: boolean;
|
|
40
42
|
}
|
|
41
43
|
export interface Title {
|
|
42
44
|
text: string;
|
|
@@ -87,9 +89,10 @@ interface Declaration {
|
|
|
87
89
|
cases: TableCase[] | null;
|
|
88
90
|
problems: Problem[];
|
|
89
91
|
}
|
|
90
|
-
/** One case of a `.each` or `.for` table: the
|
|
92
|
+
/** One case of a `.each` or `.for` table: the title the runner gives it, and the arguments its callback gets. */
|
|
91
93
|
export interface TableCase {
|
|
92
|
-
|
|
94
|
+
/** Null when the scan can't format it the way the runner does, as for a title that shows an object. */
|
|
95
|
+
title: string | null;
|
|
93
96
|
args: Value[];
|
|
94
97
|
}
|
|
95
98
|
export interface TestDeclaration extends Declaration {
|
|
@@ -139,6 +142,8 @@ export type ApiCall = TestDeclaration | SuiteDeclaration | {
|
|
|
139
142
|
settings: RunSettings;
|
|
140
143
|
/** Source text of the first argument. */ argument: string | null;
|
|
141
144
|
problems: Problem[];
|
|
145
|
+
/** Jest's `jest.retryTimes()`, which applies to every test of the file; a later call replaces it. */
|
|
146
|
+
fileWide?: boolean;
|
|
142
147
|
} | {
|
|
143
148
|
kind: 'hook';
|
|
144
149
|
name: keyof HookCounts;
|
|
@@ -219,6 +224,8 @@ export interface Adapter {
|
|
|
219
224
|
/** Where each of `fileTags` is written. */
|
|
220
225
|
fileTagSpans: Span[];
|
|
221
226
|
usesGlobals: boolean;
|
|
227
|
+
/** Whether a test declared in a test's body is a subtest the runner runs as the test does, as in `node:test`. */
|
|
228
|
+
nestsTests?: boolean;
|
|
222
229
|
classify(call: CallExpression, context: CallContext): ApiCall | null;
|
|
223
230
|
/**
|
|
224
231
|
* The test or describe function a value holds, in the adapter's own terms, as `it.skip` in `return it.skip`; null
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `node --test` commands of a package.json script, each as the files and globs it gives; empty for a command
|
|
3
|
+
* that gives none. A command runs node:test when `--test` comes before any file, as `node build.js --test` passes it on
|
|
4
|
+
* to the script.
|
|
5
|
+
*/
|
|
6
|
+
export declare function nodeTestCommands(script: string): string[][];
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** Node.js options that take their value as the next argument, so it is not a file to test. */
|
|
2
|
+
const VALUE_OPTIONS = new Set([
|
|
3
|
+
'--allow-fs-read',
|
|
4
|
+
'--allow-fs-write',
|
|
5
|
+
'--conditions',
|
|
6
|
+
'-C',
|
|
7
|
+
'--config-file',
|
|
8
|
+
'--cpu-prof-dir',
|
|
9
|
+
'--cpu-prof-name',
|
|
10
|
+
'--diagnostic-dir',
|
|
11
|
+
'--disable-warning',
|
|
12
|
+
'--dns-result-order',
|
|
13
|
+
'--env-file',
|
|
14
|
+
'--env-file-if-exists',
|
|
15
|
+
'--experimental-config-file',
|
|
16
|
+
'--experimental-loader',
|
|
17
|
+
'--heap-prof-dir',
|
|
18
|
+
'--heap-prof-name',
|
|
19
|
+
'--heapsnapshot-signal',
|
|
20
|
+
'--icu-data-dir',
|
|
21
|
+
'--import',
|
|
22
|
+
'--input-type',
|
|
23
|
+
'--loader',
|
|
24
|
+
'--localstorage-file',
|
|
25
|
+
'--max-http-header-size',
|
|
26
|
+
'--openssl-config',
|
|
27
|
+
'--redirect-warnings',
|
|
28
|
+
'--report-dir',
|
|
29
|
+
'--report-filename',
|
|
30
|
+
'--require',
|
|
31
|
+
'-r',
|
|
32
|
+
'--secure-heap',
|
|
33
|
+
'--secure-heap-min',
|
|
34
|
+
'--test-concurrency',
|
|
35
|
+
'--test-coverage-branches',
|
|
36
|
+
'--test-coverage-exclude',
|
|
37
|
+
'--test-coverage-functions',
|
|
38
|
+
'--test-coverage-include',
|
|
39
|
+
'--test-coverage-lines',
|
|
40
|
+
'--test-global-setup',
|
|
41
|
+
'--test-isolation',
|
|
42
|
+
'--test-name-pattern',
|
|
43
|
+
'--test-random-seed',
|
|
44
|
+
'--test-reporter',
|
|
45
|
+
'--test-reporter-destination',
|
|
46
|
+
'--test-rerun-failures',
|
|
47
|
+
'--test-shard',
|
|
48
|
+
'--test-skip-pattern',
|
|
49
|
+
'--test-timeout',
|
|
50
|
+
'--title',
|
|
51
|
+
'--unhandled-rejections',
|
|
52
|
+
'--watch-kill-signal',
|
|
53
|
+
'--watch-path',
|
|
54
|
+
]);
|
|
55
|
+
/**
|
|
56
|
+
* The `node --test` commands of a package.json script, each as the files and globs it gives; empty for a command
|
|
57
|
+
* that gives none. A command runs node:test when `--test` comes before any file, as `node build.js --test` passes it on
|
|
58
|
+
* to the script.
|
|
59
|
+
*/
|
|
60
|
+
export function nodeTestCommands(script) {
|
|
61
|
+
const commands = [];
|
|
62
|
+
for (const command of script.split(/&&|\|\||[;|]/)) {
|
|
63
|
+
const words = [...command.matchAll(/'([^']*)'|"([^"]*)"|(\S+)/g)].map((match) => match[1] ?? match[2] ?? match[3]);
|
|
64
|
+
const node = words.findIndex((word) => /(^|[/\\])node(\.exe)?$/.test(word));
|
|
65
|
+
if (node === -1)
|
|
66
|
+
continue;
|
|
67
|
+
let runsTests = false;
|
|
68
|
+
const files = [];
|
|
69
|
+
for (let index = node + 1; index < words.length; index++) {
|
|
70
|
+
const word = words[index];
|
|
71
|
+
if (word === '--test')
|
|
72
|
+
runsTests = true;
|
|
73
|
+
else if (VALUE_OPTIONS.has(word))
|
|
74
|
+
index++;
|
|
75
|
+
else if (!word.startsWith('-')) {
|
|
76
|
+
if (!runsTests)
|
|
77
|
+
break;
|
|
78
|
+
files.push(word);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (runsTests)
|
|
82
|
+
commands.push(files);
|
|
83
|
+
}
|
|
84
|
+
return commands;
|
|
85
|
+
}
|
package/dist/options.d.ts
CHANGED
|
@@ -4,11 +4,18 @@ export type { CommentsMode };
|
|
|
4
4
|
/** @inline */
|
|
5
5
|
export interface ScanOptions {
|
|
6
6
|
/**
|
|
7
|
-
* Glob patterns for the test files, relative to `root`. As in
|
|
8
|
-
*
|
|
7
|
+
* Glob patterns for the test files, relative to `root`. As in the runners, they match names that start with a dot
|
|
8
|
+
* too. Without them, the test files are those the configs in the root and the folders below it run, or for node:test
|
|
9
|
+
* and Bun those `node --test` and `bun test` run.
|
|
9
10
|
*/
|
|
10
11
|
patterns?: string[];
|
|
11
|
-
|
|
12
|
+
/**
|
|
13
|
+
* The runner whose tests are listed. Without it, the one the project uses: whose package a package.json in the root
|
|
14
|
+
* or below lists, whose config is there, or for node:test and Bun, run by a package.json script, else one a
|
|
15
|
+
* package.json above the root lists. With none of them or
|
|
16
|
+
* several, the scan throws a `TypeError` that names them.
|
|
17
|
+
*/
|
|
18
|
+
framework?: Framework;
|
|
12
19
|
/** The project root. Paths in the output are relative to it. Defaults to the current directory. */
|
|
13
20
|
root?: string;
|
|
14
21
|
/** Glob patterns to leave out, in addition to `node_modules` and `.git`. */
|
|
@@ -22,6 +29,11 @@ export interface ScanOptions {
|
|
|
22
29
|
* out, each with the title the runner gives it. Defaults to `false`: one record per declaration.
|
|
23
30
|
*/
|
|
24
31
|
expandCases?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Stops the scan when it aborts: the scan checks it before it starts and between files, and then rejects with the
|
|
34
|
+
* signal's `reason`, as `fetch` does.
|
|
35
|
+
*/
|
|
36
|
+
signal?: AbortSignal;
|
|
25
37
|
}
|
|
26
38
|
/** @inline */
|
|
27
39
|
export interface ScanSourceOptions {
|
|
@@ -47,6 +59,7 @@ export declare function checkObject(value: unknown, name: string): Record<string
|
|
|
47
59
|
export declare function checkFramework(value: unknown, option: string): Framework;
|
|
48
60
|
export declare function checkComments(value: unknown, option: string): CommentsMode;
|
|
49
61
|
export declare function checkFlag(value: unknown, option: string): boolean;
|
|
62
|
+
export declare function checkSignal(value: unknown, option: string): AbortSignal | null;
|
|
50
63
|
export declare function checkVersion(value: unknown, option: string): string | null;
|
|
51
64
|
export declare function checkString(value: unknown, option: string): string;
|
|
52
65
|
export declare function checkStrings(value: unknown, option: string, required: boolean): string[];
|
package/dist/options.js
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
const FRAMEWORKS = [
|
|
1
|
+
const FRAMEWORKS = [
|
|
2
|
+
'playwright',
|
|
3
|
+
'vitest',
|
|
4
|
+
'jest',
|
|
5
|
+
'mocha',
|
|
6
|
+
'jasmine',
|
|
7
|
+
'cypress',
|
|
8
|
+
'webdriverio',
|
|
9
|
+
'node',
|
|
10
|
+
'bun',
|
|
11
|
+
];
|
|
2
12
|
const COMMENTS = ['all', 'non-active', 'none'];
|
|
3
13
|
// A prerelease and build metadata each appear once, so a long input can't make the match backtrack for long.
|
|
4
14
|
const VERSION = /^v?(\d+)\.(\d+)(?:\.\d+)?(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
@@ -14,8 +24,10 @@ export function checkObject(value, name) {
|
|
|
14
24
|
return value;
|
|
15
25
|
}
|
|
16
26
|
export function checkFramework(value, option) {
|
|
17
|
-
if (!FRAMEWORKS.includes(value))
|
|
18
|
-
|
|
27
|
+
if (!FRAMEWORKS.includes(value)) {
|
|
28
|
+
const names = FRAMEWORKS.map((name) => `"${name}"`);
|
|
29
|
+
throw invalid(option, `${names.slice(0, -1).join(', ')} or ${names.at(-1)}`);
|
|
30
|
+
}
|
|
19
31
|
return value;
|
|
20
32
|
}
|
|
21
33
|
export function checkComments(value, option) {
|
|
@@ -32,6 +44,13 @@ export function checkFlag(value, option) {
|
|
|
32
44
|
throw invalid(option, 'true or false');
|
|
33
45
|
return value;
|
|
34
46
|
}
|
|
47
|
+
export function checkSignal(value, option) {
|
|
48
|
+
if (value === undefined)
|
|
49
|
+
return null;
|
|
50
|
+
if (!(value instanceof AbortSignal))
|
|
51
|
+
throw invalid(option, 'an AbortSignal');
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
35
54
|
export function checkVersion(value, option) {
|
|
36
55
|
if (value === undefined || value === null)
|
|
37
56
|
return null;
|
package/dist/parse.js
CHANGED
|
@@ -222,10 +222,25 @@ export function parseFile(fileName, code) {
|
|
|
222
222
|
error: { message, start: tooDeep, end: tooDeep + 1 },
|
|
223
223
|
};
|
|
224
224
|
}
|
|
225
|
-
|
|
225
|
+
let result;
|
|
226
|
+
let program;
|
|
227
|
+
try {
|
|
228
|
+
result = parseSync(fileName, text, parserOptions(fileName));
|
|
229
|
+
// The tree is handed over when it is first read.
|
|
230
|
+
program = result.program;
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
// The parser can fail to hand over the tree of invalid code, as for a JSX name written as a Unicode escape.
|
|
234
|
+
return {
|
|
235
|
+
source: new SourceText(text),
|
|
236
|
+
program: parseSync(fileName, '', parserOptions(fileName)).program,
|
|
237
|
+
comments: [],
|
|
238
|
+
error: { message: error.message, start: 0, end: 0 },
|
|
239
|
+
};
|
|
240
|
+
}
|
|
226
241
|
return {
|
|
227
242
|
source: new SourceText(text),
|
|
228
|
-
program
|
|
243
|
+
program,
|
|
229
244
|
comments: result.comments,
|
|
230
245
|
error: firstError(result.errors),
|
|
231
246
|
};
|
package/dist/records.d.ts
CHANGED
|
@@ -52,8 +52,14 @@ export interface VitestTags {
|
|
|
52
52
|
}
|
|
53
53
|
/** What the config the file uses gives it. */
|
|
54
54
|
export interface FileConfig {
|
|
55
|
-
/**
|
|
56
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Why a `.only` fails, from Playwright's `forbidOnly` or Vitest's `allowOnly`, and whether it surely does; null when
|
|
57
|
+
* it doesn't.
|
|
58
|
+
*/
|
|
59
|
+
forbidOnly: {
|
|
60
|
+
message: string;
|
|
61
|
+
isSure: boolean;
|
|
62
|
+
} | null;
|
|
57
63
|
/** The names of the projects that run a test, null when unknown, and what they set for it. */
|
|
58
64
|
forTest(test: TestRecord, suites: SuiteRecord[]): {
|
|
59
65
|
projects: string[] | null;
|
package/dist/records.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { REJECTS_FILE, toDiagnostic } from "./diagnostics.js";
|
|
1
|
+
import { LEVELS, REJECTS_FILE, toDiagnostic } from "./diagnostics.js";
|
|
2
2
|
import { anySlow, eitherCondition, noHooks, } from "./extract.js";
|
|
3
3
|
import { problem } from "./frameworks/common.js";
|
|
4
4
|
import { hashNodes, shortHash } from "./hash.js";
|
|
@@ -184,9 +184,16 @@ class RecordBuilder {
|
|
|
184
184
|
fileName;
|
|
185
185
|
ordinals = new Map();
|
|
186
186
|
suiteIds = new Map();
|
|
187
|
+
testIds = new Map();
|
|
187
188
|
diagnostics = [];
|
|
188
189
|
/** The line of the first problem for which the runner refuses the file; null when it loads. */
|
|
189
190
|
rejectedAt = null;
|
|
191
|
+
/** Whether the tests declared before the line that stops the file loading still run. */
|
|
192
|
+
keepsEarlierTests = false;
|
|
193
|
+
/** Jest: the describes with a test of their own that uses `.only`, which then keep their mode from their others. */
|
|
194
|
+
focused = new Set();
|
|
195
|
+
/** Jasmine: scopes with a focused spec or describe below them, whose own focus Jasmine gives up for the inner one. */
|
|
196
|
+
focusedBelow = new Set();
|
|
190
197
|
constructor(parsed, options) {
|
|
191
198
|
this.parsed = parsed;
|
|
192
199
|
this.options = options;
|
|
@@ -203,12 +210,24 @@ class RecordBuilder {
|
|
|
203
210
|
...extraction.problems.filter((item) => tags === null || isTagMissing || item.code !== 'config-may-change-state'),
|
|
204
211
|
...undefinedUses.map((use) => use.problem),
|
|
205
212
|
];
|
|
206
|
-
const rejecting = [...problems, ...testProblems, ...duplicates.map((item) => item.problem)].filter((item) => REJECTS_FILE[this.options.framework].has(item.code) &&
|
|
213
|
+
const rejecting = [...problems, ...testProblems, ...duplicates.map((item) => item.problem)].filter((item) => REJECTS_FILE[this.options.framework].has(item.code) &&
|
|
214
|
+
item.code !== 'undefined-tag' &&
|
|
215
|
+
item.afterLoad !== true &&
|
|
216
|
+
// A warning of a code that rejects files, as for Jest's .each without a body, leaves the file loading.
|
|
217
|
+
(item.level ?? LEVELS[item.code]) === 'error');
|
|
207
218
|
// Each of these problems points at the code that causes it. Vitest meets the tags in the order they are listed.
|
|
208
219
|
const firstTag = undefinedUses.find((use) => use.isSure)?.problem;
|
|
209
220
|
const lines = [...rejecting, ...(firstTag ? [firstTag] : [])].map((item) => this.parsed.source.line(item.node.start));
|
|
210
221
|
if (lines.length > 0)
|
|
211
222
|
this.rejectedAt = Math.min(...lines);
|
|
223
|
+
this.keepsEarlierTests = rejecting.length > 0 && rejecting.every((item) => item.keepsEarlierTests === true);
|
|
224
|
+
this.focused = new Set(extraction.tests.filter((test) => test.declaration.isOnly).map((test) => test.scope));
|
|
225
|
+
const focusedAt = [
|
|
226
|
+
...extraction.tests.filter((test) => test.declaration.isOnly).map((test) => test.scope),
|
|
227
|
+
// A describe's scope is inside the file's, so it has a parent.
|
|
228
|
+
...extraction.suites.filter((suite) => suite.declaration.isOnly).map((suite) => suite.scope.parent),
|
|
229
|
+
];
|
|
230
|
+
this.focusedBelow = new Set(focusedAt.flatMap((scope) => outward(scope)));
|
|
212
231
|
const suites = extraction.suites.map((suite) => this.suite(suite));
|
|
213
232
|
const tests = extraction.tests.map((test) => this.test(test, suites));
|
|
214
233
|
for (const item of problems) {
|
|
@@ -258,15 +277,12 @@ class RecordBuilder {
|
|
|
258
277
|
* warning. `call` is the `.only` call when it is in this file, whose callee the diagnostic shows.
|
|
259
278
|
*/
|
|
260
279
|
forbidOnly(located, call) {
|
|
261
|
-
const forbid = this.options.config?.forbidOnly ??
|
|
262
|
-
if (forbid ===
|
|
280
|
+
const forbid = this.options.config?.forbidOnly ?? null;
|
|
281
|
+
if (forbid === null)
|
|
263
282
|
return;
|
|
264
|
-
const message = forbid === true
|
|
265
|
-
? 'The config sets forbidOnly, so this .only fails the run before any test runs.'
|
|
266
|
-
: `The config sets forbidOnly when ${forbid}, so then this .only fails the run before any test runs.`;
|
|
267
283
|
const callee = call && snippet(this.parsed.source.text.slice(call.callee.start, call.callee.end));
|
|
268
|
-
const found = problem('forbidden-only', located, message, callee);
|
|
269
|
-
this.diagnostics.push(this.diagnostic(forbid
|
|
284
|
+
const found = problem('forbidden-only', located, forbid.message, callee);
|
|
285
|
+
this.diagnostics.push(this.diagnostic(forbid.isSure ? found : { ...found, level: 'warning' }));
|
|
270
286
|
}
|
|
271
287
|
suite(pending) {
|
|
272
288
|
const { scope, declaration, call } = pending;
|
|
@@ -336,6 +352,39 @@ class RecordBuilder {
|
|
|
336
352
|
merged[option] = undefined;
|
|
337
353
|
return merged;
|
|
338
354
|
}
|
|
355
|
+
/**
|
|
356
|
+
* A Jest test's state, as jest-circus decides it: a describe takes its own `.only` or `.skip`, else the mode around
|
|
357
|
+
* it; a test without a mode of its own takes its describe's, unless that describe is `.only` and has a test of its
|
|
358
|
+
* own that uses `.only`. A skipped describe skips every test in it, a todo or a `.only` too, and a `.only` there
|
|
359
|
+
* focuses nothing.
|
|
360
|
+
*/
|
|
361
|
+
jestState(pending, scopes) {
|
|
362
|
+
const { declaration } = pending;
|
|
363
|
+
const marked = scopes.find((scope) => scope.isOnly || scope.states.some((setting) => setting.state === 'skip'));
|
|
364
|
+
const skippedBy = marked && !marked.isOnly ? marked.states.find((setting) => setting.state === 'skip') : undefined;
|
|
365
|
+
const own = (state) => declaration.states.find((setting) => setting.state === state);
|
|
366
|
+
const passesOnly = marked?.isOnly === true && !this.focused.has(pending.scope);
|
|
367
|
+
if (skippedBy) {
|
|
368
|
+
return {
|
|
369
|
+
chosen: own('skip')
|
|
370
|
+
? { setting: own('skip'), source: 'test' }
|
|
371
|
+
: { setting: skippedBy, source: 'suite' },
|
|
372
|
+
isOnly: false,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
const setting = own('skip') ?? own('todo') ?? own('expectedToFail');
|
|
376
|
+
return { chosen: setting ? { setting, source: 'test' } : null, isOnly: declaration.isOnly || passesOnly };
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Whether Jasmine runs a spec as focused: a `fit`, or a spec in an `fdescribe` with no focused spec or describe
|
|
380
|
+
* below it, as Jasmine runs only the innermost focus (checked with Jasmine 4.6.0 and 7.0.0).
|
|
381
|
+
*/
|
|
382
|
+
jasmineFocus(pending, scopes) {
|
|
383
|
+
if (pending.declaration.isOnly)
|
|
384
|
+
return true;
|
|
385
|
+
const nearest = scopes.find((scope) => scope.isOnly);
|
|
386
|
+
return nearest !== undefined && !this.focusedBelow.has(nearest);
|
|
387
|
+
}
|
|
339
388
|
test(pending, suites) {
|
|
340
389
|
const { declaration } = pending;
|
|
341
390
|
const dependencies = this.options.dependencies(pending);
|
|
@@ -344,7 +393,14 @@ class RecordBuilder {
|
|
|
344
393
|
const scopes = outward(pending.scope);
|
|
345
394
|
const describes = scopes.filter((scope) => scope.kind === 'describe').reverse();
|
|
346
395
|
const title = declaration.title;
|
|
347
|
-
const
|
|
396
|
+
const parents = [];
|
|
397
|
+
for (let parent = pending.parent; parent; parent = parent.parent)
|
|
398
|
+
parents.unshift(parent);
|
|
399
|
+
const parentTitles = parents.map((parent) => parent.declaration.title.withoutTags);
|
|
400
|
+
const id = this.id(parents.length > 0
|
|
401
|
+
? [this.describeTitles(describes), parentTitles, title.withoutTags]
|
|
402
|
+
: [this.describeTitles(describes), title.withoutTags]);
|
|
403
|
+
this.testIds.set(pending, id);
|
|
348
404
|
const fileScope = scopes[scopes.length - 1];
|
|
349
405
|
const tags = unique([
|
|
350
406
|
...(this.options.config?.configTags ?? []),
|
|
@@ -356,19 +412,32 @@ class RecordBuilder {
|
|
|
356
412
|
// In Vitest 5 the options of a test's tags win over those the describes pass down; before, the describes' win.
|
|
357
413
|
const tagsFirst = this.options.config?.tags?.beatsDescribes ?? true;
|
|
358
414
|
const tagged = (own, tag, around) => tagsFirst ? [...own, ...tag, ...around] : [...own, ...around, ...tag];
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
415
|
+
const jest = this.options.framework === 'jest' ? this.jestState(pending, scopes) : null;
|
|
416
|
+
const focused = this.options.framework === 'jasmine' ? this.jasmineFocus(pending, scopes) : null;
|
|
417
|
+
// A focused Jasmine spec runs inside an `xdescribe`, whose own skip doesn't reach it; `pending()` still does.
|
|
418
|
+
const declaredSkip = (scope, setting) => focused === true && scope.kind === 'describe' && suiteOf(scope).declaration.states.includes(setting);
|
|
419
|
+
const chosen = jest
|
|
420
|
+
? jest.chosen
|
|
421
|
+
: chooseState([
|
|
422
|
+
...declaredStates(pending.states.slice(0, declaration.states.length), fromTags, declaration),
|
|
423
|
+
...pending.states.slice(declaration.states.length).map((setting) => ({ setting, source: 'test' })),
|
|
424
|
+
// A test the declaration skips never runs its body, so its subtests never run; one its body skips still runs it.
|
|
425
|
+
...parents.flatMap((parent) => parent.declaration.states.map((setting) => ({ setting, source: 'suite' }))),
|
|
426
|
+
...scopes.flatMap((scope) => scope.states
|
|
427
|
+
.filter((setting) => !declaredSkip(scope, setting))
|
|
428
|
+
.map((setting) => ({
|
|
429
|
+
setting,
|
|
430
|
+
source: scope.kind === 'file' ? 'file' : 'suite',
|
|
431
|
+
}))),
|
|
432
|
+
]);
|
|
433
|
+
// When the runner refuses the file, no test in it runs, whatever else is written, or none after the refused call.
|
|
434
|
+
const rejected = this.rejectedAt !== null && !(this.keepsEarlierTests && this.parsed.source.line(located.start) < this.rejectedAt);
|
|
369
435
|
const state = rejected ? 'notLoaded' : (chosen?.setting.state ?? 'active');
|
|
370
436
|
const setting = rejected ? null : (chosen?.setting ?? null);
|
|
371
|
-
const suitePath =
|
|
437
|
+
const suitePath = [
|
|
438
|
+
...describes.flatMap((scope) => suiteOf(scope).declaration.title?.text ?? []),
|
|
439
|
+
...parents.map((parent) => parent.declaration.title.text),
|
|
440
|
+
];
|
|
372
441
|
const tagConcurrent = this.options.config?.tags?.concurrentBeatsTags ? undefined : fromTags?.concurrent;
|
|
373
442
|
const tagMode = tagConcurrent === undefined ? null : tagConcurrent ? 'parallel' : 'default';
|
|
374
443
|
const [mode = null] = tagged([declaration.mode], [tagMode], scopes.map((scope) => scope.mode)).filter((item) => item !== null);
|
|
@@ -376,7 +445,9 @@ class RecordBuilder {
|
|
|
376
445
|
for (const values of [fromTags?.meta, ...describes.map((scope) => scope.details.meta), declaration.details.meta]) {
|
|
377
446
|
Object.assign(meta, values);
|
|
378
447
|
}
|
|
379
|
-
const isOnly =
|
|
448
|
+
const isOnly = jest
|
|
449
|
+
? jest.isOnly
|
|
450
|
+
: (focused ?? (declaration.isOnly || scopes.some((scope) => scope.isOnly) || fromTags?.only === true));
|
|
380
451
|
const comments = this.options.comments === 'all' || (this.options.comments === 'non-active' && state !== 'active')
|
|
381
452
|
? pending.comments
|
|
382
453
|
: [];
|
|
@@ -407,6 +478,7 @@ class RecordBuilder {
|
|
|
407
478
|
dependencies: dependencies.files,
|
|
408
479
|
hasUnfollowedDependencies: !dependencies.isComplete,
|
|
409
480
|
suiteId: describes.length > 0 ? this.suiteIds.get(describes[describes.length - 1]) : null,
|
|
481
|
+
parentTestId: pending.parent ? this.testIds.get(pending.parent) : null,
|
|
410
482
|
suitePath,
|
|
411
483
|
relativeFilePath: this.options.relativeFilePath,
|
|
412
484
|
fileName: this.fileName,
|
package/dist/resolve.d.ts
CHANGED
|
@@ -2,8 +2,25 @@ import type { ModuleReader } from './model.ts';
|
|
|
2
2
|
/** Parses JSON written with comments and trailing commas, as tsconfig files often are; null when it isn't JSON. */
|
|
3
3
|
export declare function parseJsonc(text: string): unknown;
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* How a runner's config maps imports, for every module a test imports: aliases, tried first, as Vite's
|
|
6
|
+
* `resolve.alias` and Jest's `moduleNameMapper`, and folders a bare name is looked for in, as Jest's `modulePaths` and
|
|
7
|
+
* `moduleDirectories`.
|
|
8
8
|
*/
|
|
9
|
-
export
|
|
9
|
+
export interface ImportMap {
|
|
10
|
+
/** A string `find` maps the name itself and the names below it, as `@` maps `@/x`; a regex maps what it matches. */
|
|
11
|
+
aliases: {
|
|
12
|
+
find: string | RegExp;
|
|
13
|
+
replacements: string[];
|
|
14
|
+
}[];
|
|
15
|
+
/** Folders a bare name is looked for in. */
|
|
16
|
+
paths: string[];
|
|
17
|
+
/** Names of folders a bare name is looked for in, in the importing file's folder and each folder above it. */
|
|
18
|
+
directories: string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Returns a reader for the modules a test file imports. It follows the import map of the config the file runs with,
|
|
22
|
+
* relative paths, tsconfig `paths` and `baseUrl` from the tsconfig nearest the importing file, package.json `imports`
|
|
23
|
+
* (`#name`), and workspace packages whose entry is TypeScript source. Each module it reads carries a reader for the
|
|
24
|
+
* modules that module imports in turn, with the same import map.
|
|
25
|
+
*/
|
|
26
|
+
export declare function moduleResolver(root: string): (fromFile: string, map?: ImportMap | null) => ModuleReader;
|