test-inventory 0.6.0 → 0.6.1
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 +77 -0
- package/dist/config-runs.d.ts +31 -5
- package/dist/config-runs.js +181 -41
- package/dist/config.js +65 -3
- package/dist/dependency-hash.d.ts +12 -4
- package/dist/dependency-hash.js +85 -10
- package/dist/extract.js +5 -2
- package/dist/frameworks/vitest.d.ts +1 -1
- package/dist/frameworks/vitest.js +33 -5
- package/dist/model.d.ts +11 -2
- package/dist/parse.d.ts +6 -0
- package/dist/parse.js +204 -0
- package/dist/records.d.ts +2 -0
- package/dist/records.js +6 -2
- package/dist/resolve.js +28 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,82 @@
|
|
|
1
1
|
# test-inventory
|
|
2
2
|
|
|
3
|
+
## 0.6.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#69](https://github.com/Huldoser/test-inventory/pull/69) [`41ce74d`](https://github.com/Huldoser/test-inventory/commit/41ce74d6dc5fb413ffcfa789442754a445f5572c) - An import that matches a tsconfig `paths` pattern but none of its files, as an installed package does with
|
|
8
|
+
`"paths": { "*": ["./src/types/*"] }`, now goes on to the usual lookup, as TypeScript does. Such imports were reported
|
|
9
|
+
as unreadable, so every test that used a package or a built-in module such as `node:fs` got
|
|
10
|
+
`hasUnfollowedDependencies`. An `@/` or `~/` alias whose files are missing is still reported.
|
|
11
|
+
|
|
12
|
+
- [#69](https://github.com/Huldoser/test-inventory/pull/69) [`41ce74d`](https://github.com/Huldoser/test-inventory/commit/41ce74d6dc5fb413ffcfa789442754a445f5572c) - A file that only passes names on, as a barrel `index.ts` with `export * from './fees'` or
|
|
13
|
+
`export { OrderTicket } from './order-ticket'` does, is now in the `dependencies` of the tests that import through it,
|
|
14
|
+
so `--related` keeps them when the file changes. Which module a re-export points at is now part of `dependencyHash`, as
|
|
15
|
+
an import's already was, so the hash of a test that imports through a re-export changes once.
|
|
16
|
+
|
|
17
|
+
- [#69](https://github.com/Huldoser/test-inventory/pull/69) [`41ce74d`](https://github.com/Huldoser/test-inventory/commit/41ce74d6dc5fb413ffcfa789442754a445f5572c) - An imported file that isn't text, such as `import logo from './logo.png'`, is now taken by its bytes in
|
|
18
|
+
`dependencyHash`. It was read as text, where every byte that isn't UTF-8 reads the same, so two different images could
|
|
19
|
+
give the same hash.
|
|
20
|
+
|
|
21
|
+
- [#69](https://github.com/Huldoser/test-inventory/pull/69) [`41ce74d`](https://github.com/Huldoser/test-inventory/commit/41ce74d6dc5fb413ffcfa789442754a445f5572c) - A `<script>` block in a Vue or Svelte component whose `type` isn't JavaScript, such as
|
|
22
|
+
`<script type="application/ld+json">` in `<svelte:head>`, is no longer read as the component's code. It made the
|
|
23
|
+
component unreadable, so the modules its real script imports were missing from `dependencies`.
|
|
24
|
+
|
|
25
|
+
- [#68](https://github.com/Huldoser/test-inventory/pull/68) [`fa9b97a`](https://github.com/Huldoser/test-inventory/commit/fa9b97affd5d8598dcb91f11c7fc2c5343ec5b87) - `dependencyHash` now follows what a Vue or Svelte single-file component imports, also the components and helpers only
|
|
26
|
+
its template uses, so a change to a child component changes the hash of the tests that mount its parent. A component
|
|
27
|
+
whose script doesn't parse is still taken by its text, and its tests get `hasUnfollowedDependencies`.
|
|
28
|
+
|
|
29
|
+
- [#69](https://github.com/Huldoser/test-inventory/pull/69) [`41ce74d`](https://github.com/Huldoser/test-inventory/commit/41ce74d6dc5fb413ffcfa789442754a445f5572c) - A file with brackets nested more than 500 deep, such as a generated fixture, now gets a `parse-error` instead of
|
|
30
|
+
crashing the scan. oxc-parser overflows the native stack on such a file, about 5,000 deep, and the process dies with a
|
|
31
|
+
segmentation fault that no error handling can catch, so no inventory was written.
|
|
32
|
+
|
|
33
|
+
- [#69](https://github.com/Huldoser/test-inventory/pull/69) [`41ce74d`](https://github.com/Huldoser/test-inventory/commit/41ce74d6dc5fb413ffcfa789442754a445f5572c) - `expectCount` now counts `expect.element()`, the assertion of Vitest browser mode, so a browser test that checks the
|
|
34
|
+
page only with `await expect.element(locator).toBeVisible()` no longer looks like a test without assertions.
|
|
35
|
+
|
|
36
|
+
- [#69](https://github.com/Huldoser/test-inventory/pull/69) [`41ce74d`](https://github.com/Huldoser/test-inventory/commit/41ce74d6dc5fb413ffcfa789442754a445f5572c) - An import written with a `.jsx` extension now finds the `.tsx` file it refers to, as `.js` already found `.ts`, so
|
|
37
|
+
`import { OrderHeader } from './OrderHeader.jsx'` is followed instead of marking the test with
|
|
38
|
+
`hasUnfollowedDependencies`.
|
|
39
|
+
|
|
40
|
+
- [#69](https://github.com/Huldoser/test-inventory/pull/69) [`41ce74d`](https://github.com/Huldoser/test-inventory/commit/41ce74d6dc5fb413ffcfa789442754a445f5572c) - From Playwright 1.57, the config's `tag` is now read: every test gets it first in its `tags`, and a project's `grep` or
|
|
41
|
+
`grepInvert` matches it, so a project with `grepInvert: /@nightly/` in a config with `tag: '@nightly'` no longer runs
|
|
42
|
+
the tests. When the scan can't work out the `tag`, as for `process.env.CI_ENVIRONMENT_NAME`, it gets a
|
|
43
|
+
`config-value-unknown` warning, and the tests of a project with a `grep` get `projects: null`.
|
|
44
|
+
|
|
45
|
+
- [#69](https://github.com/Huldoser/test-inventory/pull/69) [`41ce74d`](https://github.com/Huldoser/test-inventory/commit/41ce74d6dc5fb413ffcfa789442754a445f5572c) - From Playwright 1.64, a project with `default: false` is no longer listed in a test's `projects`, as Playwright runs it
|
|
46
|
+
only with `--project`, unless a project that runs lists it in `dependencies` or `teardown`. A `default` the scan can't
|
|
47
|
+
work out gets a `config-value-unknown` warning, and the tests it may run get `projects: null`.
|
|
48
|
+
|
|
49
|
+
- [#69](https://github.com/Huldoser/test-inventory/pull/69) [`41ce74d`](https://github.com/Huldoser/test-inventory/commit/41ce74d6dc5fb413ffcfa789442754a445f5572c) - Playwright's `defineConfig(base, config)` is now merged as Playwright merges it: a project of `config` merges into the
|
|
50
|
+
project of `base` with the same name instead of replacing all of `base`'s projects, and `expect`, `use` and `build`
|
|
51
|
+
merge key by key. A setup project from a base config is no longer lost, and an `expect.toHaveScreenshot.pathTemplate`
|
|
52
|
+
from the base still marks the tests that compare screenshots with `hasUnfollowedDependencies`.
|
|
53
|
+
|
|
54
|
+
- [#69](https://github.com/Huldoser/test-inventory/pull/69) [`41ce74d`](https://github.com/Huldoser/test-inventory/commit/41ce74d6dc5fb413ffcfa789442754a445f5572c) - A Vitest `retry` object in a test's or describe's options before Vitest 4.1 now gets an `unresolved-option` warning
|
|
55
|
+
and `retries: null`. Vitest 4.0 and 3.2 never run such a test; the scan used to ignore the object and give the
|
|
56
|
+
config's retry count instead.
|
|
57
|
+
|
|
58
|
+
- [#69](https://github.com/Huldoser/test-inventory/pull/69) [`41ce74d`](https://github.com/Huldoser/test-inventory/commit/41ce74d6dc5fb413ffcfa789442754a445f5572c) - A Vitest test's own `skip`, `only`, `todo` or `fails` option now replaces the one its tags set, as Vitest does, so
|
|
59
|
+
`test('title', { tags: ['flaky'], skip: false }, fn)` is active instead of skipped, and a conditional `skip` replaces
|
|
60
|
+
the tag's unconditional one.
|
|
61
|
+
|
|
62
|
+
- [#69](https://github.com/Huldoser/test-inventory/pull/69) [`41ce74d`](https://github.com/Huldoser/test-inventory/commit/41ce74d6dc5fb413ffcfa789442754a445f5572c) - An import that starts with `@/` or `~/`, as a Vite `resolve.alias` allows, now gives the test
|
|
63
|
+
`hasUnfollowedDependencies` and a `dependency-not-followed` warning when no tsconfig `paths` define it. It was taken for
|
|
64
|
+
an installed package, so the aliased file was missing from `dependencies` without a sign, and `--related` left the test
|
|
65
|
+
out when that file changed.
|
|
66
|
+
|
|
67
|
+
- [#69](https://github.com/Huldoser/test-inventory/pull/69) [`41ce74d`](https://github.com/Huldoser/test-inventory/commit/41ce74d6dc5fb413ffcfa789442754a445f5572c) - Vitest 5 tests follow the option rules of Vitest 5.0.5: the last of `.skip`, `skipIf()` and `runIf()` on a test
|
|
68
|
+
decides its skip, so `test.skip.skipIf(false)` is active, and a `skipIf()` or `runIf()` replaces the skip of a tag or of
|
|
69
|
+
the options a describe passes down. An option set to `undefined`, as in `test.skip('title', { skip: undefined }, fn)`,
|
|
70
|
+
no longer undoes the chain or a tag.
|
|
71
|
+
|
|
72
|
+
- [#69](https://github.com/Huldoser/test-inventory/pull/69) [`41ce74d`](https://github.com/Huldoser/test-inventory/commit/41ce74d6dc5fb413ffcfa789442754a445f5572c) - In Vitest 5, the projects of a config that another config lists in `projects` are named inside that config's name, as
|
|
73
|
+
Vitest names them: the `unit` project of `apps/web/vitest.config.ts`, named `web`, is `web (unit)`.
|
|
74
|
+
|
|
75
|
+
- [#69](https://github.com/Huldoser/test-inventory/pull/69) [`41ce74d`](https://github.com/Huldoser/test-inventory/commit/41ce74d6dc5fb413ffcfa789442754a445f5572c) - A Vitest project without a `test.name` now has the name Vitest gives it, in `projects` and in `configs`: the `name` in
|
|
76
|
+
the package.json of its folder, else the folder's name, so `projects: ['packages/*']` names its projects
|
|
77
|
+
`@trading/pricing` or `ledger` instead of `""`. An inline project without a name is named after its place among the
|
|
78
|
+
inline projects, as `0`, and in Vitest 5 no longer takes the name of the config it extends.
|
|
79
|
+
|
|
3
80
|
## 0.6.0
|
|
4
81
|
|
|
5
82
|
### Minor Changes
|
package/dist/config-runs.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface ProjectAbout {
|
|
|
21
21
|
config: ConfigFile;
|
|
22
22
|
grep: ((title: string) => boolean) | null;
|
|
23
23
|
grepRoot: string;
|
|
24
|
+
/** Whether the runner runs the project without `--project`, as Playwright's `default` decides; null when unknown. */
|
|
25
|
+
runsByDefault: boolean | null;
|
|
24
26
|
defaults: ProjectDefaults;
|
|
25
27
|
}
|
|
26
28
|
/**
|
|
@@ -43,6 +45,8 @@ export interface Placement {
|
|
|
43
45
|
runs: Run | false | null;
|
|
44
46
|
/** The projects of the config that run the file, which a Playwright `grep` may narrow per test; null when unknown. */
|
|
45
47
|
projects: ProjectAbout[] | null;
|
|
48
|
+
/** The tags Playwright's config `tag` gives every test; null when the scan can't work them out. */
|
|
49
|
+
tags: string[] | null;
|
|
46
50
|
}
|
|
47
51
|
/** Finds the configs a file is under and decides which one runs it, as the runner started at the root would. */
|
|
48
52
|
export declare class ConfigRuns {
|
|
@@ -54,7 +58,7 @@ export declare class ConfigRuns {
|
|
|
54
58
|
private readonly projects;
|
|
55
59
|
private readonly ignoreRules;
|
|
56
60
|
private readonly folderStates;
|
|
57
|
-
/** The Vitest configs whose projects are being found, to stop at one whose projects name itself. */
|
|
61
|
+
/** The Vitest configs whose projects are being found, with their names, to stop at one whose projects name itself. */
|
|
58
62
|
private readonly expanding;
|
|
59
63
|
constructor(configs: Configs, framework: Framework, version: [number, number] | null, findsFiles: boolean);
|
|
60
64
|
/**
|
|
@@ -63,6 +67,13 @@ export declare class ConfigRuns {
|
|
|
63
67
|
* nearest config.
|
|
64
68
|
*/
|
|
65
69
|
placementOf(file: string): Placement;
|
|
70
|
+
private place;
|
|
71
|
+
/**
|
|
72
|
+
* Playwright's config `tag`, from 1.57, which it gives every test before its own tags; null when the scan can't work
|
|
73
|
+
* it out. That is reported where the config writes `tag`; a config whose settings come from code the scan can't
|
|
74
|
+
* follow is reported there already, as its projects can't be worked out either.
|
|
75
|
+
*/
|
|
76
|
+
private configTags;
|
|
66
77
|
/** The projects of a config the output lists; null when the scan can't work them out. */
|
|
67
78
|
projectsIn(config: ConfigFile): ProjectRecord[] | null;
|
|
68
79
|
/**
|
|
@@ -79,6 +90,13 @@ export declare class ConfigRuns {
|
|
|
79
90
|
/** A project whose files can't be worked out, because of `setting` under `key`: it may run any file. */
|
|
80
91
|
private unknownProject;
|
|
81
92
|
private playwrightProjects;
|
|
93
|
+
/**
|
|
94
|
+
* Whether Playwright runs each project without `--project`: from 1.64, not with `default: false`, unless a project it
|
|
95
|
+
* runs lists it in `dependencies` or `teardown`, directly or through others. Null when the scan can't tell.
|
|
96
|
+
*/
|
|
97
|
+
private runsByDefault;
|
|
98
|
+
/** The name of a project a Vitest config lists, with a warning when its `test.name` can't be worked out. */
|
|
99
|
+
private listedName;
|
|
82
100
|
/** A Playwright project: its own settings, else the config's, else Playwright's defaults. */
|
|
83
101
|
private playwrightProject;
|
|
84
102
|
/**
|
|
@@ -89,17 +107,25 @@ export declare class ConfigRuns {
|
|
|
89
107
|
/** A folder's status and the rules inside it, worked out once per project folder however many files it holds. */
|
|
90
108
|
private folderState;
|
|
91
109
|
private rulesIn;
|
|
110
|
+
/**
|
|
111
|
+
* The projects of a Vitest config, the one the runner starts with or, in Vitest 5, a config another lists, whose name
|
|
112
|
+
* then goes around the names of its projects.
|
|
113
|
+
*/
|
|
92
114
|
private vitestProjects;
|
|
93
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* A project written in `test.projects`, which inherits the root config as each version of Vitest decides, named
|
|
117
|
+
* after `index` when it has no name of its own.
|
|
118
|
+
*/
|
|
94
119
|
private vitestEntry;
|
|
95
120
|
/**
|
|
96
121
|
* The projects of a config file or folder `test.projects` names: the config's own, also the projects it lists in
|
|
97
|
-
* Vitest 5, or a project with Vitest's defaults for a folder without a config.
|
|
122
|
+
* Vitest 5, or a project with Vitest's defaults for a folder without a config. Without a name, a project is named
|
|
123
|
+
* after the package.json in its folder, else the folder.
|
|
98
124
|
*/
|
|
99
125
|
private vitestPath;
|
|
100
126
|
/**
|
|
101
|
-
* A Vitest project: its files are found from `test.dir`, else `test.root` or `root`,
|
|
102
|
-
* mode on, each of `browser.instances` is a project of its own, named as Vitest names it.
|
|
127
|
+
* A Vitest project, with the name Vitest gives it: its files are found from `test.dir`, else `test.root` or `root`,
|
|
128
|
+
* else its folder. With browser mode on, each of `browser.instances` is a project of its own, named as Vitest names it.
|
|
103
129
|
*/
|
|
104
130
|
private vitestProject;
|
|
105
131
|
/** The files a Vitest project runs and its settings; `ownTags`, when set, in place of the tags in `settings`. */
|
package/dist/config-runs.js
CHANGED
|
@@ -101,8 +101,8 @@ export class ConfigRuns {
|
|
|
101
101
|
projects = new Map();
|
|
102
102
|
ignoreRules = new Map();
|
|
103
103
|
folderStates = new Map();
|
|
104
|
-
/** The Vitest configs whose projects are being found, to stop at one whose projects name itself. */
|
|
105
|
-
expanding = new
|
|
104
|
+
/** The Vitest configs whose projects are being found, with their names, to stop at one whose projects name itself. */
|
|
105
|
+
expanding = new Map();
|
|
106
106
|
constructor(configs, framework, version, findsFiles) {
|
|
107
107
|
this.configs = configs;
|
|
108
108
|
this.framework = framework;
|
|
@@ -115,6 +115,10 @@ export class ConfigRuns {
|
|
|
115
115
|
* nearest config.
|
|
116
116
|
*/
|
|
117
117
|
placementOf(file) {
|
|
118
|
+
const placement = this.place(file);
|
|
119
|
+
return { ...placement, tags: this.configTags(placement.config) };
|
|
120
|
+
}
|
|
121
|
+
place(file) {
|
|
118
122
|
const around = this.configs.around(file);
|
|
119
123
|
for (const config of around) {
|
|
120
124
|
const running = this.projectsOf(config).flatMap((project) => {
|
|
@@ -140,6 +144,29 @@ export class ConfigRuns {
|
|
|
140
144
|
projects: nearest ? [] : null,
|
|
141
145
|
};
|
|
142
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Playwright's config `tag`, from 1.57, which it gives every test before its own tags; null when the scan can't work
|
|
149
|
+
* it out. That is reported where the config writes `tag`; a config whose settings come from code the scan can't
|
|
150
|
+
* follow is reported there already, as its projects can't be worked out either.
|
|
151
|
+
*/
|
|
152
|
+
configTags(config) {
|
|
153
|
+
if (config === null || this.framework !== 'playwright' || !atLeast(this.version, 1, 57))
|
|
154
|
+
return [];
|
|
155
|
+
const setting = settingAt(config.settings, ['tag']);
|
|
156
|
+
if (setting === null || isUnset(setting))
|
|
157
|
+
return [];
|
|
158
|
+
const tags = [];
|
|
159
|
+
for (const item of setting.kind === 'list' ? setting.items : [setting]) {
|
|
160
|
+
if (item.kind !== 'value' || typeof item.value !== 'string') {
|
|
161
|
+
if (config.settings.kind === 'object' && config.settings.keys.has('tag')) {
|
|
162
|
+
this.configs.reportUnknown(unknownAt(item), 'tag', "the tests' tags leave it out");
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
tags.push(item.value);
|
|
167
|
+
}
|
|
168
|
+
return tags;
|
|
169
|
+
}
|
|
143
170
|
/** The projects of a config the output lists; null when the scan can't work them out. */
|
|
144
171
|
projectsIn(config) {
|
|
145
172
|
const projects = this.projectsOf(config);
|
|
@@ -183,6 +210,7 @@ export class ConfigRuns {
|
|
|
183
210
|
config,
|
|
184
211
|
grep: null,
|
|
185
212
|
grepRoot: '',
|
|
213
|
+
runsByDefault: true,
|
|
186
214
|
defaults: NO_DEFAULTS,
|
|
187
215
|
};
|
|
188
216
|
return { config, settings: setting, about, runs: null, unknown: { setting, key } };
|
|
@@ -192,10 +220,68 @@ export class ConfigRuns {
|
|
|
192
220
|
if (list?.kind === 'unknown')
|
|
193
221
|
return [this.unknownProject(config, list, 'projects')];
|
|
194
222
|
const items = list?.kind === 'list' ? list.items : [emptySettings()];
|
|
195
|
-
|
|
223
|
+
const running = this.runsByDefault(config, items);
|
|
224
|
+
return items.map((project) => this.playwrightProject(config, project, running.get(project)));
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Whether Playwright runs each project without `--project`: from 1.64, not with `default: false`, unless a project it
|
|
228
|
+
* runs lists it in `dependencies` or `teardown`, directly or through others. Null when the scan can't tell.
|
|
229
|
+
*/
|
|
230
|
+
runsByDefault(config, projects) {
|
|
231
|
+
const isRead = atLeast(this.version, 1, 64);
|
|
232
|
+
const runs = new Map(projects.map((project) => [project, !isRead]));
|
|
233
|
+
const names = projects.map((project) => playwrightName(config, project));
|
|
234
|
+
const visit = (project, value) => {
|
|
235
|
+
const current = runs.get(project);
|
|
236
|
+
if (current === true || current === value)
|
|
237
|
+
return;
|
|
238
|
+
runs.set(project, value);
|
|
239
|
+
const teardown = settingAt(project, ['teardown']);
|
|
240
|
+
const dependencies = settingAt(project, ['dependencies']);
|
|
241
|
+
const needed = strings(dependencies);
|
|
242
|
+
if (teardown?.kind === 'value' && typeof teardown.value === 'string')
|
|
243
|
+
needed.push(teardown.value);
|
|
244
|
+
// A list the scan can't work out may name any project, which then may run.
|
|
245
|
+
const unknowns = [teardown, dependencies, ...(dependencies?.kind === 'list' ? dependencies.items : [])].filter((setting) => setting?.kind === 'unknown');
|
|
246
|
+
for (const unknown of unknowns) {
|
|
247
|
+
this.configs.reportUnknown(unknown, 'projects dependencies', "the scan can't tell which projects run the tests");
|
|
248
|
+
}
|
|
249
|
+
for (const [index, other] of projects.entries()) {
|
|
250
|
+
if (needed.includes(names[index]))
|
|
251
|
+
visit(other, value);
|
|
252
|
+
else if (unknowns.length > 0)
|
|
253
|
+
visit(other, null);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
// The projects that surely run go first, so that what they pull in is sure to run too.
|
|
257
|
+
for (const value of isRead ? [true, null] : []) {
|
|
258
|
+
for (const project of projects) {
|
|
259
|
+
const setting = settingAt(project, ['default']);
|
|
260
|
+
const seed = setting?.kind === 'unknown' ? null : !(setting?.kind === 'value' && setting.value === false);
|
|
261
|
+
if (seed !== value)
|
|
262
|
+
continue;
|
|
263
|
+
if (seed === null && project.kind !== 'unknown') {
|
|
264
|
+
this.configs.reportUnknown(setting, 'projects default', "the scan can't tell which projects run the tests");
|
|
265
|
+
}
|
|
266
|
+
visit(project, value);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return runs;
|
|
270
|
+
}
|
|
271
|
+
/** The name of a project a Vitest config lists, with a warning when its `test.name` can't be worked out. */
|
|
272
|
+
listedName(settings, fallback, container) {
|
|
273
|
+
const name = settingAt(settings, ['test', 'name']);
|
|
274
|
+
const label = name?.kind === 'object' ? settingAt(name, ['label']) : name;
|
|
275
|
+
// Only a name written in a config the scan reads; a config it can't read is reported as a whole.
|
|
276
|
+
const holder = name?.kind === 'object' ? name : settingAt(settings, ['test']);
|
|
277
|
+
const isWritten = holder?.kind === 'object' && [...holder.keys.values()].includes(label);
|
|
278
|
+
if (label?.kind === 'unknown' && isWritten) {
|
|
279
|
+
this.configs.reportUnknown(label, 'test.name', 'the project is named after its folder or its place in the list');
|
|
280
|
+
}
|
|
281
|
+
return vitestName(settings, fallback, container);
|
|
196
282
|
}
|
|
197
283
|
/** A Playwright project: its own settings, else the config's, else Playwright's defaults. */
|
|
198
|
-
playwrightProject(config, project) {
|
|
284
|
+
playwrightProject(config, project, runsByDefault) {
|
|
199
285
|
if (project.kind === 'unknown')
|
|
200
286
|
return this.unknownProject(config, project, 'projects');
|
|
201
287
|
const take = (key) => {
|
|
@@ -249,12 +335,11 @@ export class ConfigRuns {
|
|
|
249
335
|
greps.push((title) => expressions.some((expression) => matches(expression, title)));
|
|
250
336
|
}
|
|
251
337
|
const [isGrepped, isInverted] = greps;
|
|
252
|
-
const name = take('name');
|
|
253
338
|
const dependencies = settingAt(project, ['dependencies']);
|
|
254
339
|
// Playwright's root folder: the config's testDir, the start of each test's grep title.
|
|
255
340
|
const configDir = settingAt(config.settings, ['testDir']);
|
|
256
341
|
const about = {
|
|
257
|
-
name:
|
|
342
|
+
name: playwrightName(config, project),
|
|
258
343
|
testDir: root,
|
|
259
344
|
dependencies: strings(dependencies),
|
|
260
345
|
// A project's own keys, as `snapshotPathTemplate`; the config's apply to every project, and are read there.
|
|
@@ -268,6 +353,7 @@ export class ConfigRuns {
|
|
|
268
353
|
grepRoot: configDir?.kind === 'value' && typeof configDir.value === 'string'
|
|
269
354
|
? path.resolve(folder, configDir.value)
|
|
270
355
|
: folder,
|
|
356
|
+
runsByDefault,
|
|
271
357
|
defaults: {
|
|
272
358
|
timeout: take('timeout')?.setting ?? null,
|
|
273
359
|
retry: take('retries')?.setting ?? null,
|
|
@@ -283,7 +369,7 @@ export class ConfigRuns {
|
|
|
283
369
|
about,
|
|
284
370
|
unknown: null,
|
|
285
371
|
runs: (file) => {
|
|
286
|
-
if (!isInside(root, file))
|
|
372
|
+
if (runsByDefault === false || !isInside(root, file))
|
|
287
373
|
return false;
|
|
288
374
|
if (isIgnored(file) || !isMatch(file))
|
|
289
375
|
return false;
|
|
@@ -343,7 +429,11 @@ export class ConfigRuns {
|
|
|
343
429
|
}
|
|
344
430
|
return rules;
|
|
345
431
|
}
|
|
346
|
-
|
|
432
|
+
/**
|
|
433
|
+
* The projects of a Vitest config, the one the runner starts with or, in Vitest 5, a config another lists, whose name
|
|
434
|
+
* then goes around the names of its projects.
|
|
435
|
+
*/
|
|
436
|
+
vitestProjects(config, container = null) {
|
|
347
437
|
const folder = path.dirname(config.file);
|
|
348
438
|
const list = settingAt(config.settings, ['test', 'projects']);
|
|
349
439
|
const isVitest3 = this.version !== null && this.version[0] < 4;
|
|
@@ -354,12 +444,15 @@ export class ConfigRuns {
|
|
|
354
444
|
// Vitest 3 takes its projects from a workspace file, which the scan doesn't read.
|
|
355
445
|
return [this.unknownProject(config, { kind: 'unknown', where: null }, 'test.projects')];
|
|
356
446
|
}
|
|
447
|
+
const name = container ?? vitestName(config.settings, '', null);
|
|
357
448
|
if (list === null)
|
|
358
|
-
return this.vitestProject(config, config.settings, folder, null);
|
|
449
|
+
return this.vitestProject(config, config.settings, folder, null, name);
|
|
359
450
|
if (list.kind !== 'list')
|
|
360
451
|
return [this.unknownProject(config, unknownAt(list), 'test.projects')];
|
|
361
|
-
this.expanding.
|
|
452
|
+
this.expanding.set(config, name);
|
|
362
453
|
const globs = [];
|
|
454
|
+
// Vitest names an inline project without a name after its place among the entries that aren't paths.
|
|
455
|
+
let index = 0;
|
|
363
456
|
const projects = list.items.flatMap((item) => {
|
|
364
457
|
if (item.kind === 'value' && typeof item.value === 'string') {
|
|
365
458
|
// `<rootDir>` is the config's folder, written with / as globs are.
|
|
@@ -368,10 +461,10 @@ export class ConfigRuns {
|
|
|
368
461
|
if (pattern.startsWith('!') || picomatch.scan(pattern).isGlob)
|
|
369
462
|
globs.push(pattern);
|
|
370
463
|
else
|
|
371
|
-
return this.vitestPath(config, path.resolve(folder, pattern));
|
|
464
|
+
return this.vitestPath(config, path.resolve(folder, pattern), container);
|
|
372
465
|
return [];
|
|
373
466
|
}
|
|
374
|
-
return this.vitestEntry(config, item, folder);
|
|
467
|
+
return this.vitestEntry(config, item, folder, String(index++), container);
|
|
375
468
|
});
|
|
376
469
|
const matches = globs.length
|
|
377
470
|
? globSync(globs, {
|
|
@@ -383,12 +476,15 @@ export class ConfigRuns {
|
|
|
383
476
|
ignore: ['**/node_modules/**'],
|
|
384
477
|
})
|
|
385
478
|
: [];
|
|
386
|
-
projects.push(...matches.flatMap((match) => this.vitestPath(config, path.resolve(match))));
|
|
479
|
+
projects.push(...matches.flatMap((match) => this.vitestPath(config, path.resolve(match), container)));
|
|
387
480
|
this.expanding.delete(config);
|
|
388
481
|
return projects;
|
|
389
482
|
}
|
|
390
|
-
/**
|
|
391
|
-
|
|
483
|
+
/**
|
|
484
|
+
* A project written in `test.projects`, which inherits the root config as each version of Vitest decides, named
|
|
485
|
+
* after `index` when it has no name of its own.
|
|
486
|
+
*/
|
|
487
|
+
vitestEntry(config, entry, folder, index, container) {
|
|
392
488
|
if (entry.kind !== 'object') {
|
|
393
489
|
// A function or a promise, read by running the config, or a setting of the wrong type.
|
|
394
490
|
return [this.unknownProject(config, unknownAt(entry), 'test.projects')];
|
|
@@ -401,31 +497,40 @@ export class ConfigRuns {
|
|
|
401
497
|
else if (extended?.kind === 'value' ? extended.value === true : this.version === null || this.version[0] >= 5) {
|
|
402
498
|
base = withoutProjects(config.settings);
|
|
403
499
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
500
|
+
const isVitest5 = atLeast(this.version, 5, 0);
|
|
501
|
+
// Vitest 5 takes a project's own tags in place of those it extends, and doesn't pass on the name.
|
|
502
|
+
const ownTags = isVitest5 ? settingAt(entry, ['test', 'tags']) : null;
|
|
503
|
+
const settings = mergeDeep(base, entry);
|
|
504
|
+
const name = this.listedName(isVitest5 ? entry : settings, index, container);
|
|
505
|
+
return this.vitestProject(config, settings, folder, ownTags, name);
|
|
407
506
|
}
|
|
408
507
|
/**
|
|
409
508
|
* The projects of a config file or folder `test.projects` names: the config's own, also the projects it lists in
|
|
410
|
-
* Vitest 5, or a project with Vitest's defaults for a folder without a config.
|
|
509
|
+
* Vitest 5, or a project with Vitest's defaults for a folder without a config. Without a name, a project is named
|
|
510
|
+
* after the package.json in its folder, else the folder.
|
|
411
511
|
*/
|
|
412
|
-
vitestPath(config, target) {
|
|
512
|
+
vitestPath(config, target, container) {
|
|
413
513
|
const nested = VITEST_CONFIG.test(path.basename(target)) ? this.configs.at(target) : this.configs.inFolder(target);
|
|
514
|
+
const folder = nested ? path.dirname(nested.file) : target;
|
|
515
|
+
// A config that lists itself also runs as a project, under its own name.
|
|
516
|
+
const listing = nested && this.expanding.get(nested);
|
|
517
|
+
const fallback = packageName(folder) ?? path.basename(folder);
|
|
518
|
+
const name = listing ?? this.listedName(nested?.settings ?? emptySettings(), fallback, container);
|
|
414
519
|
if (!nested)
|
|
415
|
-
return this.vitestProject(config, emptySettings(), target, null);
|
|
416
|
-
const isContainer = (this.version === null || this.version[0] >= 5) &&
|
|
520
|
+
return this.vitestProject(config, emptySettings(), target, null, name);
|
|
521
|
+
const isContainer = (this.version === null || this.version[0] >= 5) && listing === undefined;
|
|
417
522
|
const projects = isContainer && settingAt(nested.settings, ['test', 'projects'])
|
|
418
|
-
? this.vitestProjects(nested)
|
|
419
|
-
: this.vitestProject(nested, nested.settings,
|
|
523
|
+
? this.vitestProjects(nested, name)
|
|
524
|
+
: this.vitestProject(nested, nested.settings, folder, null, name);
|
|
420
525
|
// The outermost config lists the others, so it is set last.
|
|
421
526
|
return projects.map((project) => ({ ...project, about: { ...project.about, container: config } }));
|
|
422
527
|
}
|
|
423
528
|
/**
|
|
424
|
-
* A Vitest project: its files are found from `test.dir`, else `test.root` or `root`,
|
|
425
|
-
* mode on, each of `browser.instances` is a project of its own, named as Vitest names it.
|
|
529
|
+
* A Vitest project, with the name Vitest gives it: its files are found from `test.dir`, else `test.root` or `root`,
|
|
530
|
+
* else its folder. With browser mode on, each of `browser.instances` is a project of its own, named as Vitest names it.
|
|
426
531
|
*/
|
|
427
|
-
vitestProject(config, settings, folder, ownTags) {
|
|
428
|
-
const project = this.vitestFiles(config, settings, folder, ownTags);
|
|
532
|
+
vitestProject(config, settings, folder, ownTags, name) {
|
|
533
|
+
const project = this.vitestFiles(config, settings, folder, ownTags, name);
|
|
429
534
|
if (project.unknown)
|
|
430
535
|
return [project];
|
|
431
536
|
const enabled = settingAt(settings, ['test', 'browser', 'enabled']);
|
|
@@ -448,7 +553,7 @@ export class ConfigRuns {
|
|
|
448
553
|
});
|
|
449
554
|
}
|
|
450
555
|
/** The files a Vitest project runs and its settings; `ownTags`, when set, in place of the tags in `settings`. */
|
|
451
|
-
vitestFiles(config, settings, folder, ownTags) {
|
|
556
|
+
vitestFiles(config, settings, folder, ownTags, name) {
|
|
452
557
|
let root = folder;
|
|
453
558
|
let dir = null;
|
|
454
559
|
let hasTestRoot = false;
|
|
@@ -500,10 +605,8 @@ export class ConfigRuns {
|
|
|
500
605
|
flags.push(setting?.kind === 'value' && setting.value === true);
|
|
501
606
|
}
|
|
502
607
|
const [checksTypes, onlyTypes] = flags;
|
|
503
|
-
const name = settingAt(settings, ['test', 'name']);
|
|
504
|
-
const label = name?.kind === 'object' ? settingAt(name, ['label']) : name;
|
|
505
608
|
const about = {
|
|
506
|
-
name
|
|
609
|
+
name,
|
|
507
610
|
testDir: base,
|
|
508
611
|
dependencies: [],
|
|
509
612
|
settings,
|
|
@@ -512,6 +615,7 @@ export class ConfigRuns {
|
|
|
512
615
|
config,
|
|
513
616
|
grep: null,
|
|
514
617
|
grepRoot: base,
|
|
618
|
+
runsByDefault: true,
|
|
515
619
|
defaults: {
|
|
516
620
|
timeout: settingAt(settings, ['test', 'testTimeout']),
|
|
517
621
|
retry: this.vitestRetry(settingAt(settings, ['test', 'retry'])),
|
|
@@ -548,12 +652,41 @@ function matches(expression, text) {
|
|
|
548
652
|
expression.lastIndex = 0;
|
|
549
653
|
return expression.test(text);
|
|
550
654
|
}
|
|
655
|
+
/** A Playwright project's name: its own, else the config's, else empty. */
|
|
656
|
+
function playwrightName(config, project) {
|
|
657
|
+
for (const setting of [settingAt(project, ['name']), settingAt(config.settings, ['name'])]) {
|
|
658
|
+
if (setting && !isUnset(setting)) {
|
|
659
|
+
return setting.kind === 'value' && typeof setting.value === 'string' ? setting.value : '';
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return '';
|
|
663
|
+
}
|
|
551
664
|
/** The strings of a list setting, such as Playwright's `dependencies`; none for anything else. */
|
|
665
|
+
/**
|
|
666
|
+
* A Vitest project's name: its `test.name`, or the name's `label`, else `fallback`; in Vitest 5, inside the name of the
|
|
667
|
+
* config that lists it, as `web (unit)`.
|
|
668
|
+
*/
|
|
669
|
+
function vitestName(settings, fallback, container) {
|
|
670
|
+
const name = settingAt(settings, ['test', 'name']);
|
|
671
|
+
const label = name?.kind === 'object' ? settingAt(name, ['label']) : name;
|
|
672
|
+
const own = (label?.kind === 'value' && typeof label.value === 'string' && label.value) || fallback;
|
|
673
|
+
return container === null ? own : `${container} (${own})`;
|
|
674
|
+
}
|
|
552
675
|
function strings(setting) {
|
|
553
676
|
if (setting?.kind !== 'list')
|
|
554
677
|
return [];
|
|
555
678
|
return setting.items.flatMap((item) => (item.kind === 'value' && typeof item.value === 'string' ? [item.value] : []));
|
|
556
679
|
}
|
|
680
|
+
/** The `name` of the package.json in a folder; null without one, or when it has none. */
|
|
681
|
+
function packageName(folder) {
|
|
682
|
+
try {
|
|
683
|
+
const { name } = JSON.parse(readFileSync(path.join(folder, 'package.json'), 'utf8'));
|
|
684
|
+
return typeof name === 'string' && name !== '' ? name : null;
|
|
685
|
+
}
|
|
686
|
+
catch {
|
|
687
|
+
return null;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
557
690
|
/** A setting written as `undefined`, which the runners take as not set. */
|
|
558
691
|
function isUnset(setting) {
|
|
559
692
|
return setting.kind === 'value' && setting.value === undefined;
|
|
@@ -603,20 +736,26 @@ function folders(relative) {
|
|
|
603
736
|
* The projects that run a test: those that run its file, less those whose Playwright `grep` or `grepInvert` leaves its
|
|
604
737
|
* title out, matched as Playwright matches it, against the project's name, the file's path from the config's root
|
|
605
738
|
* folder, the titles of the describes and the test and their tags. Null when a `grep` meets a title the scan can't work
|
|
606
|
-
* out.
|
|
739
|
+
* out, or the test is in a project the scan can't tell runs.
|
|
607
740
|
*/
|
|
608
|
-
function projectsRunning(projects, test, suites, file) {
|
|
741
|
+
function projectsRunning(projects, test, suites, file, configTags) {
|
|
609
742
|
const titles = [...test.suitePath, test.title];
|
|
610
|
-
const isDynamic = test.hasDynamicTitle ||
|
|
611
|
-
|
|
612
|
-
|
|
743
|
+
const isDynamic = test.hasDynamicTitle ||
|
|
744
|
+
configTags === null ||
|
|
745
|
+
suites.some((suite) => suite.hasDynamicTitle && test.suitePath.includes(suite.title));
|
|
746
|
+
// Tags written into a title are in its text already; Playwright adds the config's after the file, the others after it.
|
|
747
|
+
const tags = test.tags.filter((tag) => !configTags?.includes(tag) && !titles.some((title) => title.includes(tag)));
|
|
613
748
|
const running = [];
|
|
614
749
|
for (const project of projects) {
|
|
615
750
|
const { name, grep, grepRoot } = project;
|
|
616
751
|
if (grep && isDynamic)
|
|
617
752
|
return null;
|
|
618
|
-
|
|
619
|
-
|
|
753
|
+
const text = [name, path.relative(grepRoot, file), ...(configTags ?? []), ...titles, ...tags].join(' ');
|
|
754
|
+
if (grep && !grep(text))
|
|
755
|
+
continue;
|
|
756
|
+
if (project.runsByDefault === null)
|
|
757
|
+
return null;
|
|
758
|
+
running.push(project);
|
|
620
759
|
}
|
|
621
760
|
return running;
|
|
622
761
|
}
|
|
@@ -782,7 +921,7 @@ export function fileConfig(placement, framework, version, file) {
|
|
|
782
921
|
if (config === null) {
|
|
783
922
|
// Without a config, Vitest defines no tags and keeps strictTags on.
|
|
784
923
|
const tags = vitestTags(null, null, null, atLeast(version, 5, 0));
|
|
785
|
-
return framework === 'vitest' ? { forbidOnly: false, forTest: () => NO_CONFIG, tags } : null;
|
|
924
|
+
return framework === 'vitest' ? { forbidOnly: false, forTest: () => NO_CONFIG, tags, configTags: [] } : null;
|
|
786
925
|
}
|
|
787
926
|
const forbidOnly = framework === 'playwright' ? settingAt(config.settings, ['forbidOnly']) : null;
|
|
788
927
|
return {
|
|
@@ -792,11 +931,12 @@ export function fileConfig(placement, framework, version, file) {
|
|
|
792
931
|
forTest(test, suites) {
|
|
793
932
|
if (projects === null)
|
|
794
933
|
return { projects: null, defaults: UNKNOWN_DEFAULTS };
|
|
795
|
-
const running = projectsRunning(projects, test, suites, file);
|
|
934
|
+
const running = projectsRunning(projects, test, suites, file, placement.tags);
|
|
796
935
|
// A grep the scan can't match leaves the projects that run the file, which may still agree.
|
|
797
936
|
return { projects: running?.map(({ name }) => name) ?? null, defaults: configDefaults(running ?? projects) };
|
|
798
937
|
},
|
|
799
938
|
tags: agreedTags(projects),
|
|
939
|
+
configTags: placement.tags ?? [],
|
|
800
940
|
};
|
|
801
941
|
}
|
|
802
942
|
/** The tags the projects that run a file define, when they all define the same; null otherwise. */
|