test-inventory 0.6.0 → 0.7.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 +126 -0
- package/dist/case-titles.d.ts +14 -0
- package/dist/case-titles.js +227 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +3 -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/diagnostics.js +1 -0
- package/dist/extract.d.ts +19 -1
- package/dist/extract.js +350 -31
- package/dist/filter.js +1 -0
- package/dist/framework-version.d.ts +4 -2
- package/dist/framework-version.js +17 -3
- package/dist/frameworks/common.d.ts +6 -0
- package/dist/frameworks/common.js +20 -0
- package/dist/frameworks/playwright.d.ts +3 -1
- package/dist/frameworks/playwright.js +14 -5
- package/dist/frameworks/vitest.d.ts +12 -2
- package/dist/frameworks/vitest.js +101 -16
- package/dist/imported-values.d.ts +22 -0
- package/dist/imported-values.js +135 -0
- package/dist/model.d.ts +19 -2
- package/dist/options.d.ts +11 -0
- package/dist/options.js +7 -0
- package/dist/parse.d.ts +6 -0
- package/dist/parse.js +204 -0
- package/dist/records.d.ts +2 -0
- package/dist/records.js +17 -6
- package/dist/resolve.js +28 -8
- package/dist/scan-file.d.ts +5 -0
- package/dist/scan-file.js +8 -5
- package/dist/scan-source.js +3 -1
- package/dist/scan.js +10 -4
- package/dist/types.d.ts +15 -3
- package/dist/values.d.ts +39 -1
- package/dist/values.js +425 -18
- package/package.json +1 -1
- package/schema/v1.json +28 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,131 @@
|
|
|
1
1
|
# test-inventory
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#72](https://github.com/Huldoser/test-inventory/pull/72) [`cf784f0`](https://github.com/Huldoser/test-inventory/commit/cf784f070e66523df1d68c546f033e1f495f0f49) - `--expand-cases` (library: `expandCases: true`) lists each case of a `.each` or `.for` table and each pass of a loop
|
|
8
|
+
whose values the scan can work out as its own record, with the title the runner gives it. Vitest's titles follow the
|
|
9
|
+
rules of the project's Vitest version, 3.0 to 5: `%s`, `%d`, `%i`, `%f`, `%j`, `%o`, `%O`, `%c`, `%#`, `%$` and `$name`,
|
|
10
|
+
with values quoted and cut as that version does; they match the names Vitest 3.0, 3.1, 3.2, 4.0, 4.1 and 5.0 list.
|
|
11
|
+
Each case's values are bound in its callback, so titles, tags and conditions built from them are worked out per case,
|
|
12
|
+
and an `if`, `?:`, `&&`, `||` or `??` that reads nothing but them registers only the branch it takes. Loops in test
|
|
13
|
+
and hook bodies and loops over a `var` are not expanded, and a file starts expansions up to 1,000 loop passes and
|
|
14
|
+
describe cases.
|
|
15
|
+
Suites and tests have a new key, `caseIndex`: the case's index from 0, as `%#` counts, or `null` without the option. A
|
|
16
|
+
table that can't be expanded stays one record with a new `cases-not-expanded` warning. Without the option the output
|
|
17
|
+
is as before, apart from `caseIndex: null` on every suite and test.
|
|
18
|
+
|
|
19
|
+
- [#72](https://github.com/Huldoser/test-inventory/pull/72) [`cf784f0`](https://github.com/Huldoser/test-inventory/commit/cf784f070e66523df1d68c546f033e1f495f0f49) - Values imported from other files of the project are now worked out: a title, tag, annotation, reason, run setting or
|
|
20
|
+
`.each` table built from a constant, enum or default export of another module, through re-exports, `export *`,
|
|
21
|
+
`import * as` and JSON files, found the same way as helper functions (relative paths, tsconfig `paths`, `#imports`,
|
|
22
|
+
workspace packages). Values from installed packages are still not read, and keep their diagnostic.
|
|
23
|
+
|
|
24
|
+
A test in a `for...of`, `for...in`, `forEach` or `map` loop over values the scan can work out now has `caseCount`, the
|
|
25
|
+
number of tests the runner registers, and no `test-in-loop` warning; the warning now marks a loop the scan can't
|
|
26
|
+
count. Values also work out arithmetic on numbers, as in `30 * 1000`, `?:`, `&&`, `||`, `??`, comparisons,
|
|
27
|
+
`Object.keys()`, `Object.values()`, `Object.entries()`, `JSON.stringify()`, `String()`, and a string's `toUpperCase()`,
|
|
28
|
+
`toLowerCase()` and `trim()` and an array's `join()`. A skip condition that works out to a constant, such as
|
|
29
|
+
`side === 'short'` in a helper called with `'buy'`, now sets the state it gives instead of being kept as a condition.
|
|
30
|
+
|
|
31
|
+
Code after an `if` whose branch always returns or throws, as in `if (!strategy.enabled) return;` in a helper, is now
|
|
32
|
+
under the opposite condition, so its tests have `isInCondition` and a loop around them no count. A tagged-template
|
|
33
|
+
table is counted as Vitest splits it: outer pipes are columns, an incomplete last row is left out, and a template
|
|
34
|
+
without values is one case.
|
|
35
|
+
|
|
36
|
+
A test whose title was its source text and is now worked out gets a new `id`, once.
|
|
37
|
+
|
|
38
|
+
### Patch Changes
|
|
39
|
+
|
|
40
|
+
- [#71](https://github.com/Huldoser/test-inventory/pull/71) [`bf8383f`](https://github.com/Huldoser/test-inventory/commit/bf8383f009a73a026d128cd8e050505cdd63cfb5) - A name destructured from a test object, as in `const { describe, beforeEach } = test`, is now read as that member:
|
|
41
|
+
`beforeEach(...)` is a hook, `describe(...)` a describe and `skip(...)` a skipped test, for Playwright and Vitest,
|
|
42
|
+
also nested, as in `const { describe: { serial } } = test`. Every such name was read as `test` itself, so a hook got
|
|
43
|
+
`test-without-body` and every test of the file `notLoaded`, and the tests inside a describe were left out with
|
|
44
|
+
`nested-test` warnings.
|
|
45
|
+
|
|
46
|
+
- [#71](https://github.com/Huldoser/test-inventory/pull/71) [`bf8383f`](https://github.com/Huldoser/test-inventory/commit/bf8383f009a73a026d128cd8e050505cdd63cfb5) - When the root and the folders above it don't have the framework, its version is now read from the shallowest
|
|
47
|
+
`package.json` below the root that declares it and holds scanned files, as `code/package.json` in a monorepo, with
|
|
48
|
+
that package's lockfile or installed copy first. The version was `null` there, so the newest rules applied: Vitest
|
|
49
|
+
5's, which refuse a file that calls `vi.mock` inside a block, made every test of such a file `notLoaded` in a project
|
|
50
|
+
on Vitest 3.
|
|
51
|
+
|
|
52
|
+
## 0.6.1
|
|
53
|
+
|
|
54
|
+
### Patch Changes
|
|
55
|
+
|
|
56
|
+
- [#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
|
|
57
|
+
`"paths": { "*": ["./src/types/*"] }`, now goes on to the usual lookup, as TypeScript does. Such imports were reported
|
|
58
|
+
as unreadable, so every test that used a package or a built-in module such as `node:fs` got
|
|
59
|
+
`hasUnfollowedDependencies`. An `@/` or `~/` alias whose files are missing is still reported.
|
|
60
|
+
|
|
61
|
+
- [#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
|
|
62
|
+
`export { OrderTicket } from './order-ticket'` does, is now in the `dependencies` of the tests that import through it,
|
|
63
|
+
so `--related` keeps them when the file changes. Which module a re-export points at is now part of `dependencyHash`, as
|
|
64
|
+
an import's already was, so the hash of a test that imports through a re-export changes once.
|
|
65
|
+
|
|
66
|
+
- [#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
|
|
67
|
+
`dependencyHash`. It was read as text, where every byte that isn't UTF-8 reads the same, so two different images could
|
|
68
|
+
give the same hash.
|
|
69
|
+
|
|
70
|
+
- [#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
|
|
71
|
+
`<script type="application/ld+json">` in `<svelte:head>`, is no longer read as the component's code. It made the
|
|
72
|
+
component unreadable, so the modules its real script imports were missing from `dependencies`.
|
|
73
|
+
|
|
74
|
+
- [#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
|
|
75
|
+
its template uses, so a change to a child component changes the hash of the tests that mount its parent. A component
|
|
76
|
+
whose script doesn't parse is still taken by its text, and its tests get `hasUnfollowedDependencies`.
|
|
77
|
+
|
|
78
|
+
- [#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
|
|
79
|
+
crashing the scan. oxc-parser overflows the native stack on such a file, about 5,000 deep, and the process dies with a
|
|
80
|
+
segmentation fault that no error handling can catch, so no inventory was written.
|
|
81
|
+
|
|
82
|
+
- [#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
|
|
83
|
+
page only with `await expect.element(locator).toBeVisible()` no longer looks like a test without assertions.
|
|
84
|
+
|
|
85
|
+
- [#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
|
|
86
|
+
`import { OrderHeader } from './OrderHeader.jsx'` is followed instead of marking the test with
|
|
87
|
+
`hasUnfollowedDependencies`.
|
|
88
|
+
|
|
89
|
+
- [#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
|
|
90
|
+
`grepInvert` matches it, so a project with `grepInvert: /@nightly/` in a config with `tag: '@nightly'` no longer runs
|
|
91
|
+
the tests. When the scan can't work out the `tag`, as for `process.env.CI_ENVIRONMENT_NAME`, it gets a
|
|
92
|
+
`config-value-unknown` warning, and the tests of a project with a `grep` get `projects: null`.
|
|
93
|
+
|
|
94
|
+
- [#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
|
|
95
|
+
only with `--project`, unless a project that runs lists it in `dependencies` or `teardown`. A `default` the scan can't
|
|
96
|
+
work out gets a `config-value-unknown` warning, and the tests it may run get `projects: null`.
|
|
97
|
+
|
|
98
|
+
- [#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
|
|
99
|
+
project of `base` with the same name instead of replacing all of `base`'s projects, and `expect`, `use` and `build`
|
|
100
|
+
merge key by key. A setup project from a base config is no longer lost, and an `expect.toHaveScreenshot.pathTemplate`
|
|
101
|
+
from the base still marks the tests that compare screenshots with `hasUnfollowedDependencies`.
|
|
102
|
+
|
|
103
|
+
- [#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
|
|
104
|
+
and `retries: null`. Vitest 4.0 and 3.2 never run such a test; the scan used to ignore the object and give the
|
|
105
|
+
config's retry count instead.
|
|
106
|
+
|
|
107
|
+
- [#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
|
|
108
|
+
`test('title', { tags: ['flaky'], skip: false }, fn)` is active instead of skipped, and a conditional `skip` replaces
|
|
109
|
+
the tag's unconditional one.
|
|
110
|
+
|
|
111
|
+
- [#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
|
|
112
|
+
`hasUnfollowedDependencies` and a `dependency-not-followed` warning when no tsconfig `paths` define it. It was taken for
|
|
113
|
+
an installed package, so the aliased file was missing from `dependencies` without a sign, and `--related` left the test
|
|
114
|
+
out when that file changed.
|
|
115
|
+
|
|
116
|
+
- [#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
|
|
117
|
+
decides its skip, so `test.skip.skipIf(false)` is active, and a `skipIf()` or `runIf()` replaces the skip of a tag or of
|
|
118
|
+
the options a describe passes down. An option set to `undefined`, as in `test.skip('title', { skip: undefined }, fn)`,
|
|
119
|
+
no longer undoes the chain or a tag.
|
|
120
|
+
|
|
121
|
+
- [#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
|
|
122
|
+
Vitest names them: the `unit` project of `apps/web/vitest.config.ts`, named `web`, is `web (unit)`.
|
|
123
|
+
|
|
124
|
+
- [#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
|
|
125
|
+
the package.json of its folder, else the folder's name, so `projects: ['packages/*']` names its projects
|
|
126
|
+
`@trading/pricing` or `ledger` instead of `""`. An inline project without a name is named after its place among the
|
|
127
|
+
inline projects, as `0`, and in Vitest 5 no longer takes the name of the config it extends.
|
|
128
|
+
|
|
3
129
|
## 0.6.0
|
|
4
130
|
|
|
5
131
|
### Minor Changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Value } from './values.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The title Vitest gives one case of a `.each` or `.for` table, read from Vitest's `formatTitle` in 3.0, 3.1, 4.1
|
|
4
|
+
* and 5.0: `%#` and `%$` for the case's index, printf placeholders for its items, and `$name` for a key of its object.
|
|
5
|
+
* Vitest 3 formats the whole title and then replaces `$name`, also inside the values it put in; Vitest 4 and 5 format
|
|
6
|
+
* each placeholder on its own and replace `$name` only in the text between them. Vitest 3.0 has no `%$`, and reads
|
|
7
|
+
* `$name` only when the case is an object. Null when the title shows a value the scan doesn't format the way Vitest
|
|
8
|
+
* does, which is any array or object printed as a whole.
|
|
9
|
+
*
|
|
10
|
+
* `version` is Vitest's `[major, minor]`, null for the newest rules. `truncate` is the length Vitest shortens a value to,
|
|
11
|
+
* 40 unless the config sets `chaiConfig.truncateThreshold` (3 and 4) or `taskTitleValueFormatTruncate` (5); 0 keeps
|
|
12
|
+
* values whole.
|
|
13
|
+
*/
|
|
14
|
+
export declare function vitestCaseTitle(template: string, items: readonly Value[], index: number, version: readonly [number, number] | null, truncate?: number): string | null;
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The title Vitest gives one case of a `.each` or `.for` table, read from Vitest's `formatTitle` in 3.0, 3.1, 4.1
|
|
3
|
+
* and 5.0: `%#` and `%$` for the case's index, printf placeholders for its items, and `$name` for a key of its object.
|
|
4
|
+
* Vitest 3 formats the whole title and then replaces `$name`, also inside the values it put in; Vitest 4 and 5 format
|
|
5
|
+
* each placeholder on its own and replace `$name` only in the text between them. Vitest 3.0 has no `%$`, and reads
|
|
6
|
+
* `$name` only when the case is an object. Null when the title shows a value the scan doesn't format the way Vitest
|
|
7
|
+
* does, which is any array or object printed as a whole.
|
|
8
|
+
*
|
|
9
|
+
* `version` is Vitest's `[major, minor]`, null for the newest rules. `truncate` is the length Vitest shortens a value to,
|
|
10
|
+
* 40 unless the config sets `chaiConfig.truncateThreshold` (3 and 4) or `taskTitleValueFormatTruncate` (5); 0 keeps
|
|
11
|
+
* values whole.
|
|
12
|
+
*/
|
|
13
|
+
export function vitestCaseTitle(template, items, index, version, truncate = 40) {
|
|
14
|
+
const rules = version === null || version[0] >= 5 ? 5 : version[0];
|
|
15
|
+
const isFirst3 = version !== null && version[0] === 3 && version[1] === 0;
|
|
16
|
+
let text = template;
|
|
17
|
+
if (text.includes('%#') || (!isFirst3 && text.includes('%$'))) {
|
|
18
|
+
text = text.replace(/%%/g, ESCAPED).replace(/%#/g, String(index));
|
|
19
|
+
if (!isFirst3)
|
|
20
|
+
text = text.replace(/%\$/g, String(index + 1));
|
|
21
|
+
text = text.replaceAll(ESCAPED, '%%');
|
|
22
|
+
}
|
|
23
|
+
const count = text.split('%').length - 1;
|
|
24
|
+
if (text.includes('%f'))
|
|
25
|
+
text = negativeFloats(text, items);
|
|
26
|
+
const isObjectItem = isObject(items[0]);
|
|
27
|
+
const attribute = (key) => {
|
|
28
|
+
const isArrayKey = !isFirst3 && /^\d+$/.test(key);
|
|
29
|
+
if (!isObjectItem && !isArrayKey)
|
|
30
|
+
return `$${key}`;
|
|
31
|
+
const element = isArrayKey ? objectAttr(items, key) : undefined;
|
|
32
|
+
const value = isObjectItem ? objectAttr(items[0], key, element) : element;
|
|
33
|
+
if (value === NOT_PLAIN)
|
|
34
|
+
return null;
|
|
35
|
+
if (rules === 5)
|
|
36
|
+
return typeof value === 'string' ? shorten(value, truncate) : prettyFormat(value, truncate);
|
|
37
|
+
return loupe(value, truncate === 0 ? Infinity : truncate);
|
|
38
|
+
};
|
|
39
|
+
if (rules === 3) {
|
|
40
|
+
const formatted = printf(text, items.slice(0, count), rules, truncate);
|
|
41
|
+
return formatted === null ? null : replaceAttributes(formatted, ATTRIBUTE_3, attribute);
|
|
42
|
+
}
|
|
43
|
+
let output = '';
|
|
44
|
+
let used = 0;
|
|
45
|
+
let last = 0;
|
|
46
|
+
for (const match of text.matchAll(PLACEHOLDER)) {
|
|
47
|
+
const between = replaceAttributes(text.slice(last, match.index), rules === 5 ? ATTRIBUTE_5 : ATTRIBUTE_3, attribute);
|
|
48
|
+
if (between === null)
|
|
49
|
+
return null;
|
|
50
|
+
output += between;
|
|
51
|
+
// Each placeholder, `%%` included, takes the next item, also one past the end of the case.
|
|
52
|
+
const placeholder = printf(match[0], [items[used++]], rules, truncate);
|
|
53
|
+
if (placeholder === null)
|
|
54
|
+
return null;
|
|
55
|
+
output += placeholder;
|
|
56
|
+
last = match.index + match[0].length;
|
|
57
|
+
}
|
|
58
|
+
const rest = replaceAttributes(text.slice(last), rules === 5 ? ATTRIBUTE_5 : ATTRIBUTE_3, attribute);
|
|
59
|
+
return rest === null ? null : output + rest;
|
|
60
|
+
}
|
|
61
|
+
const ESCAPED = '__vitest_escaped_%__';
|
|
62
|
+
const PLACEHOLDER = /%[sdjifoOc%]/g;
|
|
63
|
+
const ATTRIBUTE_3 = /\$([$\w.]+)/g;
|
|
64
|
+
const ATTRIBUTE_5 = /\$([$\p{ID_Continue}.]+)/gu;
|
|
65
|
+
/** A key that leads to something other than a plain value, such as `toString`, which Vitest would show as a function. */
|
|
66
|
+
const NOT_PLAIN = Symbol('not plain');
|
|
67
|
+
function isPrimitive(value) {
|
|
68
|
+
return typeof value !== 'object' || value === null;
|
|
69
|
+
}
|
|
70
|
+
function isObject(value) {
|
|
71
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
72
|
+
}
|
|
73
|
+
/** Vitest's `objectAttr`: a path such as `order.qty`, `legs[0].side` or `0`, read as `new Object(value)[key]` reads it. */
|
|
74
|
+
function objectAttr(source, path, fallback) {
|
|
75
|
+
let result = source;
|
|
76
|
+
for (const key of path.replace(/\[(\d+)\]/g, '.$1').split('.')) {
|
|
77
|
+
if (result === null || result === undefined)
|
|
78
|
+
return fallback;
|
|
79
|
+
const object = Object(result);
|
|
80
|
+
if (Object.hasOwn(object, key)) {
|
|
81
|
+
result = object[key];
|
|
82
|
+
}
|
|
83
|
+
else if (key in object) {
|
|
84
|
+
return NOT_PLAIN;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
return fallback;
|
|
88
|
+
}
|
|
89
|
+
if (result === undefined)
|
|
90
|
+
return fallback;
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
/** `%f` of `-0` shows a minus, which Vitest adds for the item at the same place among the items. */
|
|
95
|
+
function negativeFloats(template, items) {
|
|
96
|
+
let text = template;
|
|
97
|
+
template.match(/%f/g).forEach((_, index) => {
|
|
98
|
+
if (!Object.is(items[index], -0))
|
|
99
|
+
return;
|
|
100
|
+
let occurrence = 0;
|
|
101
|
+
text = text.replace(/%f/g, (match) => (++occurrence === index + 1 ? '-%f' : match));
|
|
102
|
+
});
|
|
103
|
+
return text;
|
|
104
|
+
}
|
|
105
|
+
function replaceAttributes(text, pattern, attribute) {
|
|
106
|
+
let output = '';
|
|
107
|
+
let last = 0;
|
|
108
|
+
for (const match of text.matchAll(pattern)) {
|
|
109
|
+
const value = attribute(match[1]);
|
|
110
|
+
if (value === null)
|
|
111
|
+
return null;
|
|
112
|
+
output += text.slice(last, match.index) + value;
|
|
113
|
+
last = match.index + match[0].length;
|
|
114
|
+
}
|
|
115
|
+
return output + text.slice(last);
|
|
116
|
+
}
|
|
117
|
+
/** Vitest's `format()` for a template and its arguments, for the values it shows the way the scan does. */
|
|
118
|
+
function printf(template, args, rules, truncate) {
|
|
119
|
+
let used = 0;
|
|
120
|
+
let output = '';
|
|
121
|
+
let last = 0;
|
|
122
|
+
for (const match of template.matchAll(PLACEHOLDER)) {
|
|
123
|
+
const placeholder = match[0];
|
|
124
|
+
const formatted = placeholder === '%%'
|
|
125
|
+
? '%'
|
|
126
|
+
: used >= args.length
|
|
127
|
+
? placeholder
|
|
128
|
+
: formatValue(placeholder, args[used++], rules, truncate);
|
|
129
|
+
if (formatted === null)
|
|
130
|
+
return null;
|
|
131
|
+
output += template.slice(last, match.index) + formatted;
|
|
132
|
+
last = match.index + placeholder.length;
|
|
133
|
+
}
|
|
134
|
+
output += template.slice(last);
|
|
135
|
+
// Arguments beyond the placeholders are appended, as `console.log` does.
|
|
136
|
+
for (const value of args.slice(used)) {
|
|
137
|
+
if (!isPrimitive(value))
|
|
138
|
+
return null;
|
|
139
|
+
output += ` ${String(value)}`;
|
|
140
|
+
}
|
|
141
|
+
return output;
|
|
142
|
+
}
|
|
143
|
+
function formatValue(placeholder, value, rules, truncate) {
|
|
144
|
+
switch (placeholder) {
|
|
145
|
+
case '%s':
|
|
146
|
+
if (!isPrimitive(value))
|
|
147
|
+
return null;
|
|
148
|
+
return Object.is(value, -0) ? '-0' : String(value);
|
|
149
|
+
case '%d':
|
|
150
|
+
return Number(value).toString();
|
|
151
|
+
case '%i':
|
|
152
|
+
return isPrimitive(value) ? Number.parseInt(String(value)).toString() : null;
|
|
153
|
+
case '%f':
|
|
154
|
+
return isPrimitive(value) ? Number.parseFloat(String(value)).toString() : null;
|
|
155
|
+
case '%j':
|
|
156
|
+
// JSON.stringify(undefined) gives undefined, which the title shows as text.
|
|
157
|
+
return value === undefined ? 'undefined' : JSON.stringify(value);
|
|
158
|
+
case '%c':
|
|
159
|
+
return '';
|
|
160
|
+
default:
|
|
161
|
+
// `%o` and `%O` inspect the value: with loupe in Vitest 3 and 4, with pretty-format and the truncate option in 5.
|
|
162
|
+
if (!isPrimitive(value))
|
|
163
|
+
return null;
|
|
164
|
+
return rules === 5 ? prettyFormat(value, truncate) : loupe(value, Infinity);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/** A string cut to `length` with an ellipsis, without splitting a surrogate pair; 0 keeps it whole. */
|
|
168
|
+
function shorten(text, length) {
|
|
169
|
+
if (length === 0 || text.length <= length)
|
|
170
|
+
return text;
|
|
171
|
+
let end = length - 1;
|
|
172
|
+
if (isHighSurrogate(text[end - 1]))
|
|
173
|
+
end -= 1;
|
|
174
|
+
return `${text.slice(0, end)}…`;
|
|
175
|
+
}
|
|
176
|
+
function isHighSurrogate(char) {
|
|
177
|
+
return char !== undefined && char >= '\ud800' && char <= '\udbff';
|
|
178
|
+
}
|
|
179
|
+
/** Vitest 5's `inspect()` of a value, through pretty-format with single quotes. */
|
|
180
|
+
function prettyFormat(value, truncate) {
|
|
181
|
+
if (!isPrimitive(value))
|
|
182
|
+
return null;
|
|
183
|
+
if (typeof value !== 'string')
|
|
184
|
+
return Object.is(value, -0) ? '-0' : String(value);
|
|
185
|
+
const quoted = `'${value}'`;
|
|
186
|
+
if (truncate === 0 || quoted.length <= truncate)
|
|
187
|
+
return quoted;
|
|
188
|
+
let end = truncate - 1;
|
|
189
|
+
if (end > 0 && isHighSurrogate(quoted[end - 1]))
|
|
190
|
+
end -= 1;
|
|
191
|
+
return `'${quoted.slice(1, end)}…'`;
|
|
192
|
+
}
|
|
193
|
+
const LOUPE_ESCAPED = new RegExp("['\\u0000-\\u001f\\u007f-\\u009f\\u00ad\\u0600-\\u0604\\u070f\\u17b4\\u17b5\\u200c-\\u200f\\u2028-\\u202f\\u2060-\\u206f\\ufeff\\ufff0-\\uffff]", 'g');
|
|
194
|
+
const LOUPE_ESCAPES = {
|
|
195
|
+
'\b': '\\b',
|
|
196
|
+
'\t': '\\t',
|
|
197
|
+
'\n': '\\n',
|
|
198
|
+
'\f': '\\f',
|
|
199
|
+
'\r': '\\r',
|
|
200
|
+
"'": "\\'",
|
|
201
|
+
};
|
|
202
|
+
/** loupe's `inspect()` of a value, as Vitest 3 and 4 show it, cut to `truncate`. */
|
|
203
|
+
function loupe(value, truncate) {
|
|
204
|
+
if (!isPrimitive(value))
|
|
205
|
+
return null;
|
|
206
|
+
if (typeof value === 'string') {
|
|
207
|
+
const escaped = value.replace(LOUPE_ESCAPED, (char) => LOUPE_ESCAPES[char] ?? `\\u${`0000${char.charCodeAt(0).toString(16)}`.slice(-4)}`);
|
|
208
|
+
return `'${loupeTruncate(escaped, truncate - 2)}'`;
|
|
209
|
+
}
|
|
210
|
+
if (typeof value !== 'number')
|
|
211
|
+
return String(value);
|
|
212
|
+
if (Number.isNaN(value) || !Number.isFinite(value))
|
|
213
|
+
return String(value);
|
|
214
|
+
if (value === 0)
|
|
215
|
+
return Object.is(value, -0) ? '-0' : '+0';
|
|
216
|
+
return loupeTruncate(String(value), truncate);
|
|
217
|
+
}
|
|
218
|
+
function loupeTruncate(text, length) {
|
|
219
|
+
if (length < 1 && text.length > 1)
|
|
220
|
+
return '…';
|
|
221
|
+
if (text.length <= length || text.length <= 1)
|
|
222
|
+
return text;
|
|
223
|
+
let end = length - 1;
|
|
224
|
+
if (end > 0 && isHighSurrogate(text[end - 1]))
|
|
225
|
+
end -= 1;
|
|
226
|
+
return `${text.slice(0, end)}…`;
|
|
227
|
+
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -6,6 +6,6 @@ export interface Io {
|
|
|
6
6
|
/** Read for `--related -`. */
|
|
7
7
|
stdin?: Readable;
|
|
8
8
|
}
|
|
9
|
-
export declare const HELP = "Usage: test-inventory [glob...] --framework <playwright|vitest> [options]\n\nLists the tests in the matching files as JSON, read from the source code without running them. Without globs, the\ntest files are those the Playwright or Vitest configs in the root and the folders below it run.\n\nOptions:\n --framework <name> playwright or vitest (required)\n --root <dir> project root; paths in the output are relative to it (default: current directory)\n --ignore <glob> leave out matching files; can be repeated (node_modules is always left out)\n --framework-version <x.y.z> apply the rules of this framework version instead of the one in the project\n --comments <mode> comments above tests: all, non-active or none (default: non-active)\n --state <state> keep tests in this state, such as skip or fixme; can be repeated\n --tag <tag> keep tests with this tag, such as @smoke; can be repeated\n --grep <regex> keep tests whose full name matches, ignoring case\n --only keep tests that use .only, or are inside a describe that does\n --without-reason keep tests that don't run and give no reason\n --related <file> keep tests a change to this file can affect; can be repeated; - reads files, one per\n line, from standard input, as git diff --name-only writes them\n --fields <keys> write only these keys of each test, separated by commas; id is always kept\n --format <format> json or jsonl, one record per line (default: json)\n -o, --output <file> write to a file instead of standard output\n --pretty indent the JSON\n -h, --help show this help\n -v, --version show the version\n\nExit codes: 0 when the scan finishes, even with diagnostics; 2 for invalid arguments, an output that cannot be\nwritten, or a crash. 1 is reserved for a future release.\n\nExamples:\n test-inventory \"tests/**/*.spec.ts\" --framework playwright --output inventory.json\n test-inventory \"tests/**/*.spec.ts\" --framework playwright --state skip --fields fullName,reason\n";
|
|
9
|
+
export declare const HELP = "Usage: test-inventory [glob...] --framework <playwright|vitest> [options]\n\nLists the tests in the matching files as JSON, read from the source code without running them. Without globs, the\ntest files are those the Playwright or Vitest configs in the root and the folders below it run.\n\nOptions:\n --framework <name> playwright or vitest (required)\n --root <dir> project root; paths in the output are relative to it (default: current directory)\n --ignore <glob> leave out matching files; can be repeated (node_modules is always left out)\n --framework-version <x.y.z> apply the rules of this framework version instead of the one in the project\n --comments <mode> comments above tests: all, non-active or none (default: non-active)\n --expand-cases one record per case of a .each or .for table and per pass of a loop, with its title\n --state <state> keep tests in this state, such as skip or fixme; can be repeated\n --tag <tag> keep tests with this tag, such as @smoke; can be repeated\n --grep <regex> keep tests whose full name matches, ignoring case\n --only keep tests that use .only, or are inside a describe that does\n --without-reason keep tests that don't run and give no reason\n --related <file> keep tests a change to this file can affect; can be repeated; - reads files, one per\n line, from standard input, as git diff --name-only writes them\n --fields <keys> write only these keys of each test, separated by commas; id is always kept\n --format <format> json or jsonl, one record per line (default: json)\n -o, --output <file> write to a file instead of standard output\n --pretty indent the JSON\n -h, --help show this help\n -v, --version show the version\n\nExit codes: 0 when the scan finishes, even with diagnostics; 2 for invalid arguments, an output that cannot be\nwritten, or a crash. 1 is reserved for a future release.\n\nExamples:\n test-inventory \"tests/**/*.spec.ts\" --framework playwright --output inventory.json\n test-inventory \"tests/**/*.spec.ts\" --framework playwright --state skip --fields fullName,reason\n";
|
|
10
10
|
/** Runs the command line with the given arguments and streams. Resolves to the exit code. */
|
|
11
11
|
export declare function run(argv: string[], io: Io): Promise<number>;
|
package/dist/cli.js
CHANGED
|
@@ -19,6 +19,7 @@ Options:
|
|
|
19
19
|
--ignore <glob> leave out matching files; can be repeated (node_modules is always left out)
|
|
20
20
|
--framework-version <x.y.z> apply the rules of this framework version instead of the one in the project
|
|
21
21
|
--comments <mode> comments above tests: all, non-active or none (default: non-active)
|
|
22
|
+
--expand-cases one record per case of a .each or .for table and per pass of a loop, with its title
|
|
22
23
|
--state <state> keep tests in this state, such as skip or fixme; can be repeated
|
|
23
24
|
--tag <tag> keep tests with this tag, such as @smoke; can be repeated
|
|
24
25
|
--grep <regex> keep tests whose full name matches, ignoring case
|
|
@@ -114,6 +115,7 @@ export async function run(argv, io) {
|
|
|
114
115
|
ignore: { type: 'string', multiple: true },
|
|
115
116
|
'framework-version': { type: 'string' },
|
|
116
117
|
comments: { type: 'string' },
|
|
118
|
+
'expand-cases': { type: 'boolean' },
|
|
117
119
|
state: { type: 'string', multiple: true },
|
|
118
120
|
tag: { type: 'string', multiple: true },
|
|
119
121
|
grep: { type: 'string' },
|
|
@@ -180,6 +182,7 @@ export async function run(argv, io) {
|
|
|
180
182
|
ignore: values.ignore ?? [],
|
|
181
183
|
frameworkVersion: values['framework-version'],
|
|
182
184
|
comments: values.comments,
|
|
185
|
+
expandCases: values['expand-cases'],
|
|
183
186
|
});
|
|
184
187
|
const filtered = filterInventory(inventory, filter);
|
|
185
188
|
result = fields === undefined ? filtered : pickTestFields(filtered, fields);
|
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`. */
|