test-inventory 0.6.1 → 0.8.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 +100 -0
- package/README.md +4 -3
- package/dist/case-titles.d.ts +27 -0
- package/dist/case-titles.js +329 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +6 -5
- package/dist/config-dependencies.js +8 -3
- package/dist/config-runs.d.ts +22 -0
- package/dist/config-runs.js +263 -3
- package/dist/config.d.ts +18 -1
- package/dist/config.js +160 -14
- package/dist/dependency-hash.d.ts +13 -5
- package/dist/dependency-hash.js +56 -12
- package/dist/diagnostics.js +3 -0
- package/dist/extract.d.ts +19 -1
- package/dist/extract.js +391 -41
- package/dist/files.js +3 -2
- package/dist/filter.js +1 -0
- package/dist/framework-version.d.ts +13 -2
- package/dist/framework-version.js +64 -5
- package/dist/frameworks/bindings.d.ts +14 -0
- package/dist/frameworks/bindings.js +99 -6
- package/dist/frameworks/common.d.ts +6 -0
- package/dist/frameworks/common.js +27 -0
- package/dist/frameworks/jest.d.ts +46 -0
- package/dist/frameworks/jest.js +442 -0
- package/dist/frameworks/playwright.d.ts +3 -1
- package/dist/frameworks/playwright.js +14 -5
- package/dist/frameworks/test-functions.d.ts +169 -0
- package/dist/frameworks/test-functions.js +492 -0
- package/dist/frameworks/vitest.d.ts +13 -58
- package/dist/frameworks/vitest.js +84 -378
- package/dist/imported-values.d.ts +22 -0
- package/dist/imported-values.js +149 -0
- package/dist/model.d.ts +11 -0
- package/dist/options.d.ts +16 -1
- package/dist/options.js +9 -2
- package/dist/parse.js +17 -2
- package/dist/records.js +57 -15
- package/dist/resolve.js +39 -36
- package/dist/scan-file.d.ts +5 -0
- package/dist/scan-file.js +15 -8
- package/dist/scan-source.js +3 -1
- package/dist/scan.js +31 -10
- package/dist/types.d.ts +24 -6
- package/dist/values.d.ts +49 -1
- package/dist/values.js +531 -19
- package/package.json +1 -1
- package/schema/v1.json +33 -4
|
@@ -3,7 +3,7 @@ import type { ConfigGap, RunsWith } from './config-dependencies.ts';
|
|
|
3
3
|
import type { Extraction, Frame, PendingTest } from './extract.ts';
|
|
4
4
|
import type { ModuleReader, ModuleSource, Problem, Span } from './model.ts';
|
|
5
5
|
import { type ParsedFile } from './parse.ts';
|
|
6
|
-
import {
|
|
6
|
+
import { SourceText } from './source.ts';
|
|
7
7
|
import type { Framework } from './types.ts';
|
|
8
8
|
import { Values } from './values.ts';
|
|
9
9
|
/** A file whose code a test can run: the scanned file, a module it imports, or a data file such as JSON. */
|
|
@@ -96,6 +96,14 @@ interface Reach {
|
|
|
96
96
|
asks: string[] | null;
|
|
97
97
|
snapshots: SnapshotUse[];
|
|
98
98
|
}
|
|
99
|
+
/** What several reaches cover together, by file. */
|
|
100
|
+
interface Merged {
|
|
101
|
+
files: string[];
|
|
102
|
+
/** Each file's hashes, sorted. */
|
|
103
|
+
byFile: Map<string, string[]>;
|
|
104
|
+
/** Each file's part of the text the dependency hash is taken of. */
|
|
105
|
+
parts: Map<string, string>;
|
|
106
|
+
}
|
|
99
107
|
/** A fixture one `extend()` call defines, and the fixtures it asks for; null when it takes them whole. */
|
|
100
108
|
interface Fixture {
|
|
101
109
|
unit: Unit;
|
|
@@ -145,11 +153,11 @@ export declare class DependencyGraph {
|
|
|
145
153
|
/** The units a set of roots reaches, leaving out those `skip` reached, with their hashes by file. */
|
|
146
154
|
reach(roots: Unit[], skip?: Reach[]): Reach;
|
|
147
155
|
/**
|
|
148
|
-
* Everything a test's roots reach on top of `
|
|
149
|
-
* with the fixtures it needs of each test object reached: the hash, the files, and whether all of it could be
|
|
150
|
-
* followed.
|
|
156
|
+
* Everything a test's roots reach on top of `bases`, such as the code its file runs when loaded, which all its tests
|
|
157
|
+
* share, with the fixtures it needs of each test object reached: the hash, the files, and whether all of it could be
|
|
158
|
+
* followed. `shared` is what the bases cover, put together once for the many tests that share them.
|
|
151
159
|
*/
|
|
152
|
-
dependencies(roots: Unit[], bases: Reach[], needs: FixtureNeeds, snapshots: (uses: SnapshotUse[]) => Unit[], report: (place: Place) => void): Dependencies;
|
|
160
|
+
dependencies(roots: Unit[], bases: Reach[], shared: Merged, needs: FixtureNeeds, snapshots: (uses: SnapshotUse[]) => Unit[], report: (place: Place) => void): Dependencies;
|
|
153
161
|
private newModule;
|
|
154
162
|
private newUnit;
|
|
155
163
|
private hashOf;
|
package/dist/dependency-hash.js
CHANGED
|
@@ -5,8 +5,9 @@ import { exportName, parseModule } from "./frameworks/bindings.js";
|
|
|
5
5
|
import { destructuredNames, problem, takesWhole } from "./frameworks/common.js";
|
|
6
6
|
import { shortHash, hashNodes } from "./hash.js";
|
|
7
7
|
import { parseFile } from "./parse.js";
|
|
8
|
-
import { snippet } from "./source.js";
|
|
8
|
+
import { snippet, SourceText } from "./source.js";
|
|
9
9
|
import { bindingNames, isScopeNode, Values } from "./values.js";
|
|
10
|
+
const NOTHING_MERGED = { files: [], byFile: new Map(), parts: new Map() };
|
|
10
11
|
/** Statements that only declare something, unless code in them runs while they are evaluated. */
|
|
11
12
|
const DECLARATIONS = new Set([
|
|
12
13
|
'ImportDeclaration',
|
|
@@ -386,11 +387,11 @@ export class DependencyGraph {
|
|
|
386
387
|
return { units, byFile, places, fixtures, asks, snapshots };
|
|
387
388
|
}
|
|
388
389
|
/**
|
|
389
|
-
* Everything a test's roots reach on top of `
|
|
390
|
-
* with the fixtures it needs of each test object reached: the hash, the files, and whether all of it could be
|
|
391
|
-
* followed.
|
|
390
|
+
* Everything a test's roots reach on top of `bases`, such as the code its file runs when loaded, which all its tests
|
|
391
|
+
* share, with the fixtures it needs of each test object reached: the hash, the files, and whether all of it could be
|
|
392
|
+
* followed. `shared` is what the bases cover, put together once for the many tests that share them.
|
|
392
393
|
*/
|
|
393
|
-
dependencies(roots, bases, needs, snapshots, report) {
|
|
394
|
+
dependencies(roots, bases, shared, needs, snapshots, report) {
|
|
394
395
|
const reached = [...bases, this.reach(roots, bases)];
|
|
395
396
|
// A fixture can ask for one an object it extends defines, or use another test object, so names are shared by
|
|
396
397
|
// every test object reached, and each name and object is read once.
|
|
@@ -476,11 +477,13 @@ export class DependencyGraph {
|
|
|
476
477
|
this.reported.add(key);
|
|
477
478
|
report(place);
|
|
478
479
|
}
|
|
479
|
-
const
|
|
480
|
+
const added = hashesByFile(reached.slice(bases.length));
|
|
481
|
+
const files = [...new Set([...shared.files, ...added.keys()])].sort();
|
|
480
482
|
const parts = files.map((file) => {
|
|
481
|
-
|
|
483
|
+
const own = added.get(file);
|
|
484
|
+
return own ? part(file, [...(shared.byFile.get(file) ?? []), ...own]) : shared.parts.get(file);
|
|
482
485
|
});
|
|
483
|
-
return { hash: shortHash(
|
|
486
|
+
return { hash: shortHash(`[${parts.join(',')}]`), files, isComplete: places.length === 0 };
|
|
484
487
|
}
|
|
485
488
|
newModule(relativeFilePath, parsed, text, values, readModule) {
|
|
486
489
|
return {
|
|
@@ -632,7 +635,8 @@ export class DependencyGraph {
|
|
|
632
635
|
}
|
|
633
636
|
/** A place in a module the scan can't follow, as a unit that marks the tests that reach it. */
|
|
634
637
|
placeUnit(module, at, message, isQuiet = false) {
|
|
635
|
-
|
|
638
|
+
// A JSON config, such as Jest's package.json, is taken by its text.
|
|
639
|
+
const source = module.parsed?.source ?? new SourceText(module.text);
|
|
636
640
|
const unit = this.newUnit(module, [], new Map(), false);
|
|
637
641
|
unit.edges = new Set();
|
|
638
642
|
const text = snippet(source.slice(at.start, at.end)) || null;
|
|
@@ -665,7 +669,11 @@ export class DependencyGraph {
|
|
|
665
669
|
if ('module' in gap) {
|
|
666
670
|
const module = this.moduleOf(gap.module);
|
|
667
671
|
// The path a setup key names, where the config writes it.
|
|
668
|
-
|
|
672
|
+
let at = null;
|
|
673
|
+
if (gap.text !== null && module.parsed)
|
|
674
|
+
at = literalIn(module.parsed.program, gap.text);
|
|
675
|
+
else if (gap.text !== null)
|
|
676
|
+
at = jsonStringIn(module.text, gap.text);
|
|
669
677
|
return this.placeUnit(module, at ?? { start: 0, end: 0 }, gap.message, gap.isQuiet);
|
|
670
678
|
}
|
|
671
679
|
const module = this.newModule(gap.relativeFilePath, null, null, new Values(), null);
|
|
@@ -1300,6 +1308,33 @@ export class DependencyGraph {
|
|
|
1300
1308
|
return edges;
|
|
1301
1309
|
}
|
|
1302
1310
|
}
|
|
1311
|
+
/** The hashes of several reaches by file, in the order the reaches come. */
|
|
1312
|
+
function hashesByFile(reaches) {
|
|
1313
|
+
const byFile = new Map();
|
|
1314
|
+
for (const item of reaches) {
|
|
1315
|
+
for (const [file, hashes] of item.byFile)
|
|
1316
|
+
byFile.set(file, [...(byFile.get(file) ?? []), ...hashes]);
|
|
1317
|
+
}
|
|
1318
|
+
return byFile;
|
|
1319
|
+
}
|
|
1320
|
+
/** A file and its hashes, sorted, as text: the hash is taken of these parts, in the order of the files. */
|
|
1321
|
+
function part(file, hashes) {
|
|
1322
|
+
return JSON.stringify([file, hashes.sort()]);
|
|
1323
|
+
}
|
|
1324
|
+
/** What `merged` covers with what one more reach adds, the parts of the files it adds hashes to written again. */
|
|
1325
|
+
function combine(merged, reach) {
|
|
1326
|
+
if (reach.byFile.size === 0)
|
|
1327
|
+
return merged;
|
|
1328
|
+
const byFile = new Map(merged.byFile);
|
|
1329
|
+
const parts = new Map(merged.parts);
|
|
1330
|
+
for (const [file, hashes] of reach.byFile) {
|
|
1331
|
+
const all = [...(merged.byFile.get(file) ?? []), ...hashes];
|
|
1332
|
+
parts.set(file, part(file, all));
|
|
1333
|
+
byFile.set(file, all);
|
|
1334
|
+
}
|
|
1335
|
+
const files = [...new Set([...merged.files, ...reach.byFile.keys()])].sort();
|
|
1336
|
+
return { files, byFile, parts };
|
|
1337
|
+
}
|
|
1303
1338
|
/** Whether an identifier refers to a binding, rather than naming a property, a label or a member. */
|
|
1304
1339
|
function isReference(key, parent) {
|
|
1305
1340
|
switch (parent?.type) {
|
|
@@ -1360,6 +1395,12 @@ function literalIn(node, value) {
|
|
|
1360
1395
|
}
|
|
1361
1396
|
return null;
|
|
1362
1397
|
}
|
|
1398
|
+
/** Where a JSON file writes a string, as `"./setup.js"`; null when it doesn't. */
|
|
1399
|
+
function jsonStringIn(text, value) {
|
|
1400
|
+
const written = JSON.stringify(value);
|
|
1401
|
+
const start = text.indexOf(written);
|
|
1402
|
+
return start === -1 ? null : { start, end: start + written.length };
|
|
1403
|
+
}
|
|
1363
1404
|
/**
|
|
1364
1405
|
* The snapshot files the matchers of a test compare against, from the project root, at the runners' default places:
|
|
1365
1406
|
* Vitest's `__snapshots__/<file>.snap`, paths given to `toMatchFileSnapshot()`, and browser screenshots in
|
|
@@ -1468,6 +1509,7 @@ export function fileDependencies(graph, main, extraction, types, report, runsWit
|
|
|
1468
1509
|
...runsWith.gaps.map((gap) => graph.gapUnit(gap)),
|
|
1469
1510
|
];
|
|
1470
1511
|
const base = graph.reach([load, ...config]);
|
|
1512
|
+
const baseMerged = combine(NOTHING_MERGED, base);
|
|
1471
1513
|
// The describes around a test and the helper call that registers it are shared by the tests there, often thousands
|
|
1472
1514
|
// for one helper call, so what they reach is walked once for all of them.
|
|
1473
1515
|
const contexts = new Map();
|
|
@@ -1481,7 +1523,8 @@ export function fileDependencies(graph, main, extraction, types, report, runsWit
|
|
|
1481
1523
|
if (around.suite)
|
|
1482
1524
|
roots.push(...suiteUnit(around.suite));
|
|
1483
1525
|
}
|
|
1484
|
-
|
|
1526
|
+
const reach = graph.reach(roots, [base]);
|
|
1527
|
+
context = { reach, merged: combine(baseMerged, reach) };
|
|
1485
1528
|
byCall.set(registeredBy, context);
|
|
1486
1529
|
}
|
|
1487
1530
|
return context;
|
|
@@ -1528,6 +1571,7 @@ export function fileDependencies(graph, main, extraction, types, report, runsWit
|
|
|
1528
1571
|
needs.names.push(...scope.setupFixtures);
|
|
1529
1572
|
needs.all ||= scope.takesAllFixtures;
|
|
1530
1573
|
}
|
|
1531
|
-
|
|
1574
|
+
const context = contextOf(test.scope, test.registeredBy);
|
|
1575
|
+
return graph.dependencies(roots, [base, context.reach], context.merged, needs, snapshots, report);
|
|
1532
1576
|
};
|
|
1533
1577
|
}
|
package/dist/diagnostics.js
CHANGED
|
@@ -7,6 +7,7 @@ export const LEVELS = {
|
|
|
7
7
|
'unresolved-tag': 'warning',
|
|
8
8
|
'unresolved-annotation': 'warning',
|
|
9
9
|
'unresolved-cases': 'warning',
|
|
10
|
+
'cases-not-expanded': 'warning',
|
|
10
11
|
'test-in-loop': 'warning',
|
|
11
12
|
'dynamic-title': 'warning',
|
|
12
13
|
'duplicate-title': 'warning',
|
|
@@ -17,6 +18,7 @@ export const LEVELS = {
|
|
|
17
18
|
'test-without-body': 'error',
|
|
18
19
|
'describe-not-called': 'error',
|
|
19
20
|
'async-describe': 'error',
|
|
21
|
+
'describe-throws': 'error',
|
|
20
22
|
'nested-test': 'warning',
|
|
21
23
|
'tag-without-at': 'error',
|
|
22
24
|
'removed-api': 'error',
|
|
@@ -50,6 +52,7 @@ export const LEVELS = {
|
|
|
50
52
|
export const REJECTS_FILE = {
|
|
51
53
|
playwright: new Set(['test-without-body', 'tag-without-at', 'invalid-chain', 'duplicate-title']),
|
|
52
54
|
vitest: new Set(['invalid-chain', 'removed-api', 'undefined-tag']),
|
|
55
|
+
jest: new Set(['invalid-chain', 'test-without-body', 'async-describe', 'describe-throws']),
|
|
53
56
|
};
|
|
54
57
|
export function toDiagnostic(problem, relativeFilePath, source, testId = null) {
|
|
55
58
|
const position = problem.node && source ? source.position(problem.node.start) : null;
|
package/dist/extract.d.ts
CHANGED
|
@@ -48,6 +48,8 @@ export interface PendingSuite {
|
|
|
48
48
|
/** Declared under a condition, or in a function: it may not run while the file loads. */
|
|
49
49
|
isInCondition: boolean;
|
|
50
50
|
isInFunction: boolean;
|
|
51
|
+
/** With `expandCases`, the index of the case of a table or pass of a loop this describe is; null otherwise. */
|
|
52
|
+
caseIndex: number | null;
|
|
51
53
|
}
|
|
52
54
|
export interface PendingTest {
|
|
53
55
|
scope: Scope;
|
|
@@ -66,8 +68,17 @@ export interface PendingTest {
|
|
|
66
68
|
states: StateSetting[];
|
|
67
69
|
annotations: Annotation[];
|
|
68
70
|
isInLoop: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* How many times each loop around the test registers it, outermost first; null for a loop the scan can't count, or
|
|
73
|
+
* one that registers it under a condition.
|
|
74
|
+
*/
|
|
75
|
+
loops: (number | null)[];
|
|
76
|
+
/** How many passes each loop around the test makes, whether or not it registers the test on each. */
|
|
77
|
+
passes: (number | null)[];
|
|
69
78
|
isInCondition: boolean;
|
|
70
79
|
isInFunction: boolean;
|
|
80
|
+
/** With `expandCases`, the index of the case of a table or pass of a loop this test is; null otherwise. */
|
|
81
|
+
caseIndex: number | null;
|
|
71
82
|
comments: string[];
|
|
72
83
|
/** Run settings from calls in the body, which win over everything around the test. */
|
|
73
84
|
run: RunSettings;
|
|
@@ -85,6 +96,13 @@ export interface Frame {
|
|
|
85
96
|
readModule: ModuleReader | null;
|
|
86
97
|
relativeFilePath: string;
|
|
87
98
|
}
|
|
99
|
+
/** How the walk lists tables and loops. */
|
|
100
|
+
export interface ExtractOptions {
|
|
101
|
+
/** One test or describe per case of a table and per pass of a loop whose values the scan can work out. */
|
|
102
|
+
expandCases: boolean;
|
|
103
|
+
/** Vitest's `[major, minor]`, whose rules give each case its title; null for the newest rules. */
|
|
104
|
+
version: readonly [number, number] | null;
|
|
105
|
+
}
|
|
88
106
|
/** Reads a module a helper is imported from as a frame; null when it can't be parsed. */
|
|
89
107
|
export type OpenModule = (module: ModuleSource) => Frame | null;
|
|
90
108
|
export interface Extraction {
|
|
@@ -112,4 +130,4 @@ export declare function anySlow(slows: NonNullable<RunSettings['slow']>[]): NonN
|
|
|
112
130
|
export declare function eitherCondition(parts: string[]): string;
|
|
113
131
|
export declare function noHooks(): HookCounts;
|
|
114
132
|
/** Walks the scanned file; `openModule` reads the modules of imported helpers that the file calls. */
|
|
115
|
-
export declare function extract(main: Frame, openModule: OpenModule): Extraction;
|
|
133
|
+
export declare function extract(main: Frame, openModule: OpenModule, options?: ExtractOptions): Extraction;
|