test-inventory 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/CHANGELOG.md +180 -0
  2. package/README.md +16 -10
  3. package/dist/case-titles.d.ts +22 -0
  4. package/dist/case-titles.js +184 -0
  5. package/dist/cli.d.ts +1 -1
  6. package/dist/cli.js +5 -5
  7. package/dist/config-dependencies.js +27 -3
  8. package/dist/config-runs.d.ts +117 -3
  9. package/dist/config-runs.js +1015 -19
  10. package/dist/config.d.ts +42 -1
  11. package/dist/config.js +287 -21
  12. package/dist/dependency-hash.d.ts +13 -5
  13. package/dist/dependency-hash.js +56 -12
  14. package/dist/diagnostics.js +16 -1
  15. package/dist/extract.d.ts +2 -0
  16. package/dist/extract.js +89 -29
  17. package/dist/files.js +4 -2
  18. package/dist/filter.js +1 -0
  19. package/dist/framework-version.d.ts +13 -2
  20. package/dist/framework-version.js +207 -7
  21. package/dist/frameworks/bindings.d.ts +14 -0
  22. package/dist/frameworks/bindings.js +99 -6
  23. package/dist/frameworks/bun.d.ts +42 -0
  24. package/dist/frameworks/bun.js +272 -0
  25. package/dist/frameworks/common.d.ts +3 -1
  26. package/dist/frameworks/common.js +18 -9
  27. package/dist/frameworks/cypress.d.ts +31 -0
  28. package/dist/frameworks/cypress.js +70 -0
  29. package/dist/frameworks/jasmine.d.ts +28 -0
  30. package/dist/frameworks/jasmine.js +218 -0
  31. package/dist/frameworks/jest.d.ts +46 -0
  32. package/dist/frameworks/jest.js +442 -0
  33. package/dist/frameworks/mocha.d.ts +50 -0
  34. package/dist/frameworks/mocha.js +306 -0
  35. package/dist/frameworks/node.d.ts +30 -0
  36. package/dist/frameworks/node.js +268 -0
  37. package/dist/frameworks/playwright.d.ts +13 -0
  38. package/dist/frameworks/playwright.js +57 -0
  39. package/dist/frameworks/test-functions.d.ts +194 -0
  40. package/dist/frameworks/test-functions.js +534 -0
  41. package/dist/frameworks/vitest.d.ts +15 -62
  42. package/dist/frameworks/vitest.js +69 -389
  43. package/dist/frameworks/webdriverio.d.ts +27 -0
  44. package/dist/frameworks/webdriverio.js +37 -0
  45. package/dist/imported-values.js +15 -1
  46. package/dist/model.d.ts +9 -2
  47. package/dist/node-scripts.d.ts +6 -0
  48. package/dist/node-scripts.js +85 -0
  49. package/dist/options.d.ts +16 -3
  50. package/dist/options.js +22 -3
  51. package/dist/parse.js +17 -2
  52. package/dist/records.d.ts +8 -2
  53. package/dist/records.js +94 -22
  54. package/dist/resolve.d.ts +21 -4
  55. package/dist/resolve.js +91 -52
  56. package/dist/scan-file.d.ts +8 -0
  57. package/dist/scan-file.js +21 -3
  58. package/dist/scan.js +62 -12
  59. package/dist/toml.d.ts +13 -0
  60. package/dist/toml.js +172 -0
  61. package/dist/types.d.ts +27 -11
  62. package/dist/values.d.ts +15 -0
  63. package/dist/values.js +120 -2
  64. package/dist/yaml.d.ts +12 -0
  65. package/dist/yaml.js +148 -0
  66. package/package.json +7 -6
  67. package/schema/v1.json +30 -8
package/CHANGELOG.md CHANGED
@@ -1,5 +1,185 @@
1
1
  # test-inventory
2
2
 
3
+ ## 0.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#88](https://github.com/Huldoser/test-inventory/pull/88) [`2d370c9`](https://github.com/Huldoser/test-inventory/commit/2d370c9030c5fafae900d2442d75e70781a5b938) - Bun 1.2.23 and newer: `--framework bun`, also found from a package.json script that runs `bun test` or a
8
+ `bunfig.toml`. Its version comes from `.bun-version`, `packageManager` or `engines.bun`, else `@types/bun` or
9
+ `bun-types`. The scan reads Bun's globals and imports of `bun:test`, `@jest/globals` and `vitest`, which Bun rewrites;
10
+ chains of `.skip`, `.todo`, `.failing`, `.only`, `.concurrent`, `.serial` and `.each` in any order, refusing the
11
+ combinations Bun refuses; the conditions `.if`, `.skipIf`, `.todoIf`, `.failingIf`, `.concurrentIf` and `.serialIf`;
12
+ options second or third, a number as the timeout or `{ timeout, retry, repeats }`; and `.each` titles as Bun formats
13
+ them. The files are Bun's `*.test.*`, `*_test.*`, `*.spec.*` and `*_spec.*` under `bunfig.toml`'s `[test] root`, less
14
+ `pathIgnorePatterns`, with its `retry` and `concurrentTestGlob` applied. A small TOML reader reads `bunfig.toml`.
15
+
16
+ The oldest version a framework supports may now name a patch, as Bun's 1.2.23 does.
17
+
18
+ - [#87](https://github.com/Huldoser/test-inventory/pull/87) [`ae2bd90`](https://github.com/Huldoser/test-inventory/commit/ae2bd909d769ea5e21200fceb3d9f081de7661db) - `dependencyHash` follows imports through the config a file runs with: Vite's `resolve.alias` and Vitest's
19
+ `test.alias`, as an object or a list of `{ find, replacement }` with a name or a regex, and Jest's `moduleNameMapper`,
20
+ `modulePaths` and `moduleDirectories`, with `<rootDir>`. They were read from tsconfig `paths` only, so tests that
21
+ imported through such aliases had `hasUnfollowedDependencies` and a hash that missed those files. A mapping to no file
22
+ is a `dependency-not-followed` warning that names it.
23
+
24
+ - [#83](https://github.com/Huldoser/test-inventory/pull/83) [`9ef9c59`](https://github.com/Huldoser/test-inventory/commit/9ef9c5959b908191e8015c8c880a3c476f43fccd) - Cypress 13 and newer: `--framework cypress`, also found from a `cypress` package or a `cypress.config.*` file. Tests are
25
+ read as Mocha's, with Cypress's test config before the function: `retries`, of which `cypress run` takes `runMode`,
26
+ `browser` as a skip condition, `@cypress/grep`'s `tags`, and from Cypress 16 an error for `env`, which Cypress refuses
27
+ there. `this.timeout()` is not read, as Cypress has only command timeouts; `this.retries()` in a describe and
28
+ `.retries()` after `it()` or `describe()` are errors, as Cypress throws on them while it loads the spec. The config's
29
+ testing types are the projects, `e2e` and `component`, each with its `specPattern`, `excludeSpecPattern` and `retries`.
30
+
31
+ A project whose package.json lists `cypress` next to another runner, as Jest or Vitest for unit tests, now needs
32
+ `--framework`, as the scan asks for one whenever it finds several runners.
33
+
34
+ - [#82](https://github.com/Huldoser/test-inventory/pull/82) [`04e7328`](https://github.com/Huldoser/test-inventory/commit/04e73289dd946bd4c92ec774510ebc369ee80699) - Jasmine 4 and newer: `--framework jasmine`, also found from a `jasmine` or `karma-jasmine` package or a
35
+ `spec/support/jasmine.*` config. The scan reads `describe`, `it` and their `x` and `f` forms with Jasmine's focus, a
36
+ spec without a function as pending, `pending(reason)` in a spec or its `beforeEach` or `afterEach`, from Jasmine 7
37
+ `notApplicable(reason)`, the hooks, and a spec's timeout. The config's `spec_dir` and `spec_files`, with `!`
38
+ exclusions, decide the files; without a config, as in Karma and Angular projects, the files given by glob run. A call
39
+ Jasmine refuses stops the whole run at the top level of a file, and is an error that leaves the file's tests as they
40
+ are inside a describe, which Jasmine catches.
41
+
42
+ - [#81](https://github.com/Huldoser/test-inventory/pull/81) [`85fb8a9`](https://github.com/Huldoser/test-inventory/commit/85fb8a90998f88641400ebb8a6db3b392e2b54d2) - Mocha 10 and newer: `--framework mocha`, also found from a `mocha` package, a `.mocharc.*` file or a `mocha` key in
43
+ `package.json`. The scan reads `describe`, `context`, `it`, `specify`, their `x` forms, `.only` and `.skip`, a test
44
+ without a function as pending, `ui: tdd`'s `suite` and `test`, the hooks, `this.timeout()`, `this.retries()` and
45
+ `this.skip()` where `this` is Mocha's, and `.timeout()` and `.retries()` after `it()` and `describe()`. The config's
46
+ `spec`, `extension`, `recursive`, `ignore` and `file` decide the files, and its `timeout` and `retries` apply to every
47
+ test; a YAML config is read too. A describe without a function, `it()` or `describe()` without a title, and an
48
+ unknown chain such as `it.concurrent` are errors, as Mocha then runs no test.
49
+
50
+ A project whose package.json lists `mocha` next to another runner now needs `--framework`, as the scan asks for one
51
+ whenever it finds several runners.
52
+
53
+ - [#86](https://github.com/Huldoser/test-inventory/pull/86) [`1085fb7`](https://github.com/Huldoser/test-inventory/commit/1085fb74b85eefe64c6bdd217b2c19c2946441d4) - node:test on Node.js 22 and newer: `--framework node`, also found from a package.json script that runs `node --test`
54
+ or a `node.config.json`. Its version is the Node.js version the project pins in `.node-version`, `.nvmrc`, Volta's
55
+ `node` or `engines.node`. The scan reads `test`, `it`, `describe` and `suite` from `node:test`, imported or required,
56
+ with `.skip`, `.todo`, `.only` and from 24.14 `.expectFailure`, and the options `skip`, `todo`, `only`, `timeout`,
57
+ `concurrency`, `expectFailure` and from 24.19 and 26.2 `tags`; `t.skip()` and `t.todo()` in a test's body; and the
58
+ hooks. The files are those the script gives `node --test`, or Node.js's default patterns, with TypeScript files from
59
+ 22.18.
60
+
61
+ A test declared in a test's body, with `t.test()` or `test()`, is a subtest that node:test runs while it runs the test:
62
+ it is listed with the test's title in `suitePath` and the test's id in the new key `parentTestId`, which is `null` for
63
+ every other test.
64
+
65
+ - [#80](https://github.com/Huldoser/test-inventory/pull/80) [`cae53c1`](https://github.com/Huldoser/test-inventory/commit/cae53c107fce5a3024f7666c83d9b2582bb9017b) - A new `invalid-fixture-argument` error: a rest property among a Playwright function's fixture arguments, as
66
+ `async ({ page, ...fixtures }) => {}` in a test, hook, `test.skip()` condition or `test.extend()` fixture written in
67
+ the test file. Playwright refuses it and runs no tests at all, so the file's tests are now `notLoaded` instead of
68
+ `active`. A nested rest, as `({ page: { mouse, ...pointer } })`, and a `test.step()` callback are allowed, as
69
+ Playwright allows them.
70
+
71
+ - [#85](https://github.com/Huldoser/test-inventory/pull/85) [`90df9aa`](https://github.com/Huldoser/test-inventory/commit/90df9aa07741fab403470e653595b1b644b35dc3) - `scan()` takes a `signal`, an `AbortSignal`: the scan checks it before it starts and between files, and rejects with
72
+ the signal's `reason` once it aborts, so a server can stop a scan its client no longer waits for.
73
+
74
+ - [#85](https://github.com/Huldoser/test-inventory/pull/85) [`90df9aa`](https://github.com/Huldoser/test-inventory/commit/90df9aa07741fab403470e653595b1b644b35dc3) - A Vitest `.only` that `allowOnly` makes fail is a `forbidden-only` diagnostic, as for Playwright's `forbidOnly`: an
75
+ error when the config sets `test.allowOnly` to `false`, and a warning when it is unset, as Vitest then turns it off in
76
+ CI, or depends on a condition. Vitest marks such a test failed instead of running it.
77
+
78
+ - [#84](https://github.com/Huldoser/test-inventory/pull/84) [`c68bac7`](https://github.com/Huldoser/test-inventory/commit/c68bac7442f7d5197c8f67bfbec66a8c9ee3f4d5) - WebdriverIO 9 and newer: `--framework webdriverio`, also found from a `@wdio/cli` package or a `wdio.conf.*` file. The
79
+ config's `config` export, ES or CommonJS, decides the files: `specs`, with the nested lists that group files into one
80
+ worker, less `exclude`, matched as WebdriverIO matches them. Its `framework` decides how the tests are read, Mocha by
81
+ default or Jasmine, with `mochaOpts.timeout`, `retries` and `ui`, or `jasmineOpts.defaultTimeoutInterval`, on every
82
+ test; Cucumber is reported, as its feature files aren't read. A number after a test's function is WebdriverIO's retry
83
+ count, and with Jasmine a second one, after the timeout. Mocha's and Jasmine's version gates follow the versions
84
+ WebdriverIO depends on. With Mocha, a call the runner refuses stops the file loading there, and the tests declared
85
+ above it still run. The config's `rootDir` and each capability's own `specs` and `exclude` are read too.
86
+
87
+ A project whose package.json lists `@wdio/cli` next to another runner now needs `--framework`, as the scan asks for
88
+ one whenever it finds several runners.
89
+
90
+ ### Patch Changes
91
+
92
+ - [#85](https://github.com/Huldoser/test-inventory/pull/85) [`90df9aa`](https://github.com/Huldoser/test-inventory/commit/90df9aa07741fab403470e653595b1b644b35dc3) - `expectCount` counts Playwright's `expect.configure(...)`, called in place as `expect.configure({ timeout: 5_000 })(locator)`
93
+ or kept in a variable as `const slowExpect = expect.configure(...)`; it missed both.
94
+
95
+ - [#83](https://github.com/Huldoser/test-inventory/pull/83) [`9ef9c59`](https://github.com/Huldoser/test-inventory/commit/9ef9c5959b908191e8015c8c880a3c476f43fccd) - The error for an unknown `framework` option or `--framework` value names every runner the scan reads; it still named
96
+ only Playwright, Vitest and Jest.
97
+
98
+ - [#89](https://github.com/Huldoser/test-inventory/pull/89) [`fd4b13a`](https://github.com/Huldoser/test-inventory/commit/fd4b13a45ec2349319b5edaec4dd0cbe1170d663) - `--help` names all nine runners `--framework` takes, and says which files node:test and Bun run without globs.
99
+
100
+ - [#80](https://github.com/Huldoser/test-inventory/pull/80) [`cae53c1`](https://github.com/Huldoser/test-inventory/commit/cae53c107fce5a3024f7666c83d9b2582bb9017b) - A helper that takes a test's function as a parameter, as `function settlementTest(desk, body) { describe(desk, () =>
101
+ test('settles', body)); }`, no longer gets a false `nested-test` warning. The scan read the parameter as the helper
102
+ itself, and so read the helper's own describe as declared inside the test.
103
+
104
+ - [#78](https://github.com/Huldoser/test-inventory/pull/78) [`1aea8a7`](https://github.com/Huldoser/test-inventory/commit/1aea8a744c955a01f1918447e048046a4eb022b4) - oxc-parser is updated from 0.156.0 to 0.157.0. The scan output doesn't change, except that a `.js` file with
105
+ TypeScript-only syntax, such as `export default abstract class` or an optional class member, now gets a `parse-error`, as
106
+ Node.js would refuse to load it.
107
+
108
+ - [#80](https://github.com/Huldoser/test-inventory/pull/80) [`cae53c1`](https://github.com/Huldoser/test-inventory/commit/cae53c107fce5a3024f7666c83d9b2582bb9017b) - A test or describe call with a title the runner refuses, written inside a test or hook body, is now a `nested-test`
109
+ warning, not an error that says the runner fails the file. Jest's own suite calls `it('test1')` inside
110
+ `expect(() => ...)` to test that error; the call runs in the test, so the file loads and its tests run.
111
+
112
+ - [#80](https://github.com/Huldoser/test-inventory/pull/80) [`cae53c1`](https://github.com/Huldoser/test-inventory/commit/cae53c107fce5a3024f7666c83d9b2582bb9017b) - Before Vitest 4.1, a `retry` object in the config, as `test: { retry: { count: 2 } }`, now gets a
113
+ `config-value-unknown` warning that Vitest runs no test with it. The tests already had `retries: null`, but nothing
114
+ said why.
115
+
116
+ - [#80](https://github.com/Huldoser/test-inventory/pull/80) [`cae53c1`](https://github.com/Huldoser/test-inventory/commit/cae53c107fce5a3024f7666c83d9b2582bb9017b) - A call through a Vitest member that reads the function it is called on, destructured as in
117
+ `const { each } = test; each([1, 2])('sizes lot %i', fn)`, is now an `invalid-chain` error, and the file's tests are
118
+ `notLoaded`: Vitest fails the file. That holds for `each`, `for`, `skipIf` and `runIf` of `test` and `each` of
119
+ `describe` in every version, `extend`, `scoped` and `override` from Vitest 4.1, and `for` of `describe` from Vitest 5.
120
+ Before, the scan said nothing and listed the file's other tests as active.
121
+
122
+ - [#80](https://github.com/Huldoser/test-inventory/pull/80) [`cae53c1`](https://github.com/Huldoser/test-inventory/commit/cae53c107fce5a3024f7666c83d9b2582bb9017b) - An inline Vitest project in `test.projects` now runs from the config's `test.root` or `root` when it sets none of its
123
+ own, as every Vitest version does; the scan used the config's folder before Vitest 5. A root the project sets is
124
+ resolved from the config's root in Vitest 5, as Vitest 5 does, and from the config's folder before.
125
+
126
+ - [#85](https://github.com/Huldoser/test-inventory/pull/85) [`90df9aa`](https://github.com/Huldoser/test-inventory/commit/90df9aa07741fab403470e653595b1b644b35dc3) - With `expandCases`, a Vitest case title cuts long values at the length the config sets, `test.taskTitleValueFormatTruncate`
127
+ from Vitest 5 and `test.chaiConfig.truncateThreshold` before, as Vitest does; it cut them at Vitest's default, 40, always.
128
+
129
+ - [#80](https://github.com/Huldoser/test-inventory/pull/80) [`cae53c1`](https://github.com/Huldoser/test-inventory/commit/cae53c107fce5a3024f7666c83d9b2582bb9017b) - From Vitest 5, `timeout: undefined`, `retry: undefined` and `repeats: undefined` in a test's options are left out, as
130
+ Vitest leaves them out, instead of giving an `unresolved-option` warning that `undefined` is not a whole number.
131
+
132
+ ## 0.8.0
133
+
134
+ ### Minor Changes
135
+
136
+ - [#75](https://github.com/Huldoser/test-inventory/pull/75) [`f6690bb`](https://github.com/Huldoser/test-inventory/commit/f6690bbe5e49ce048ec4169f21bed23f702942d1) - `--framework` (library: `framework`) is now optional. Without it, the scan takes the runner the project uses: the one
137
+ whose package a `package.json` in the root or below it lists, or whose config file is there, else one a `package.json`
138
+ above the root lists. A project with several runners, or none, gets a usage error (a `TypeError` in the library) that
139
+ names what it found.
140
+
141
+ - [#75](https://github.com/Huldoser/test-inventory/pull/75) [`f6690bb`](https://github.com/Huldoser/test-inventory/commit/f6690bbe5e49ce048ec4169f21bed23f702942d1) - Jest 29 and 30: `--framework jest` (library: `framework: 'jest'`) lists the tests of Jest files with the same keys as
142
+ Playwright and Vitest tests. It reads Jest's globals, `fit`, `xit`, `xtest`, `fdescribe` and `xdescribe` among them,
143
+ imports and `require()` of `@jest/globals`, the chains Jest has, such as `test.concurrent.only.each`, `.failing` as
144
+ `expectedToFail`, the timeout passed third, `jest.retryTimes()` as `retries` for every test of the file (from 30.5,
145
+ with `entireDescribe`, of its describe) and `waitBeforeRetry` as `retryDelay`, and `@group` pragmas as tags, as
146
+ jest-runner-groups reads them. `--expand-cases` titles each case as jest-each 29 and 30 do. Calls Jest refuses, such
147
+ as `test.only.concurrent`, `test('title')` without a function or an async describe, mark the file's tests `notLoaded`.
148
+
149
+ Without globs, the test files come from the Jest config, read as Jest reads it: `jest.config.*`, else `package.json`,
150
+ with `roots`, `testMatch`, `testRegex`, the ignore patterns, `moduleFileExtensions`, `projects` named by
151
+ `displayName`, `testTimeout` and `injectGlobals`, also through Jest 30.3's `defineConfig()` and `mergeConfig()`;
152
+ without any config, Jest's defaults apply. `framework` in the output
153
+ can now be `jest`, an open set, so `schemaVersion` stays 1.
154
+
155
+ The `cases-not-expanded` message no longer names Vitest, as Jest tables can get it too.
156
+
157
+ ### Patch Changes
158
+
159
+ - [#75](https://github.com/Huldoser/test-inventory/pull/75) [`f6690bb`](https://github.com/Huldoser/test-inventory/commit/f6690bbe5e49ce048ec4169f21bed23f702942d1) - `require()` of a local file is followed as an import is: tables, titles and other values it exports, with
160
+ `module.exports` or `exports.name`, are read, and helper functions it exports that declare tests are followed. A
161
+ helper function a shared file imports and exports again, as an `index.js` of helpers does, is followed to where it is
162
+ written, instead of getting an `unresolved-test-import` error. A helpers file may export a test function it takes from
163
+ the runner's globals, as `export const pendingTest = xit`, or from the framework's module, with its modifiers.
164
+
165
+ - [#75](https://github.com/Huldoser/test-inventory/pull/75) [`f6690bb`](https://github.com/Huldoser/test-inventory/commit/f6690bbe5e49ce048ec4169f21bed23f702942d1) - A call the runner refuses, such as `test.only.skip(...)`, that is only reached under a condition no longer marks every
166
+ test of its file `notLoaded`, as the file loads unless the condition holds; the call is still reported. A test function
167
+ chosen with `&&`, `||` or `??`, as in `const onCi = process.env.CI && test.skip || test`, is read like one chosen with
168
+ `?:`. In a config, a list with a spread the scan can't work out keeps the items it can.
169
+
170
+ - [#75](https://github.com/Huldoser/test-inventory/pull/75) [`f6690bb`](https://github.com/Huldoser/test-inventory/commit/f6690bbe5e49ce048ec4169f21bed23f702942d1) - A config that `require()`s a local file, as a CommonJS config does with a shared base, now reads what that file
171
+ exports, as an `import` of it already did, and a JSON file, required or imported, is read as its value.
172
+
173
+ - [#75](https://github.com/Huldoser/test-inventory/pull/75) [`f6690bb`](https://github.com/Huldoser/test-inventory/commit/f6690bbe5e49ce048ec4169f21bed23f702942d1) - A table or loop gets its `caseCount` when its length is known even if one of its values isn't, as in
174
+ `[NaN, Infinity].forEach(...)` or a table with a call in a row. A test or describe titled by a function or class
175
+ written in place, as `describe(function OrderBook() {}, ...)`, takes its name, as the runners do. Code the parser
176
+ fails to hand over, such as a JSX name written as a Unicode escape, gets a `parse-error` instead of an
177
+ `internal-error`.
178
+
179
+ - [#75](https://github.com/Huldoser/test-inventory/pull/75) [`f6690bb`](https://github.com/Huldoser/test-inventory/commit/f6690bbe5e49ce048ec4169f21bed23f702942d1) - `dependencyHash` is faster on large projects: the files a test file and its describes reach are put together once for
180
+ all their tests, and a workspace's packages are found once, not once per folder that imports one. A scan of the Nx
181
+ repository with 17,571 tests went from 19.4 to 11.9 seconds. Hashes are unchanged.
182
+
3
183
  ## 0.7.0
4
184
 
5
185
  ### Minor Changes
package/README.md CHANGED
@@ -2,10 +2,12 @@
2
2
 
3
3
  Test metadata from source code, not from test results.
4
4
 
5
- test-inventory reads Playwright and Vitest test files and lists every test declaration as JSON: its title, file and
6
- lines, the describes around it, its tags and annotations, and whether it is active, skipped, fixme, todo, expected
7
- to fail or in a file the runner refuses to load, with the condition when the state only applies sometimes. It parses
8
- the code and runs nothing, so it needs no browsers, no build and no runner configuration.
5
+ test-inventory reads Playwright, Vitest, Jest, Mocha, Jasmine, Cypress, WebdriverIO, node:test and Bun test files and
6
+ lists every test declaration as JSON: its title, file and lines, the describes around it, its tags and annotations, and
7
+ whether it is active, skipped, fixme, todo, expected to fail or in a file the runner refuses to load, with the condition
8
+ when the state only applies sometimes. Each test has an id that survives a move to another line and a hash of everything
9
+ it runs, from its hooks and fixtures to its page objects, test data, snapshots and config, so it can also list the tests
10
+ a change can affect. It parses the code and runs nothing, so it needs no browsers, no build and no runner configuration.
9
11
 
10
12
  Documentation: [huldoser.github.io/test-inventory](https://huldoser.github.io/test-inventory/)
11
13
 
@@ -15,7 +17,9 @@ Documentation: [huldoser.github.io/test-inventory](https://huldoser.github.io/te
15
17
  npm install --save-dev test-inventory
16
18
  ```
17
19
 
18
- Node.js 22.12 or newer, on Linux, macOS or Windows. Playwright 1.42 or newer, Vitest 3.0 or newer.
20
+ Node.js 22.12 or newer, on Linux, macOS or Windows. Playwright 1.42 or newer, Vitest 3.0 or newer, Jest 29.0 or newer,
21
+ Mocha 10.0 or newer, Jasmine 4.0 or newer, Cypress 13.0 or newer, WebdriverIO 9.0 or newer, node:test 22.0 or newer,
22
+ Bun 1.2.23 or newer.
19
23
 
20
24
  ## Command line
21
25
 
@@ -25,7 +29,9 @@ Node.js 22.12 or newer, on Linux, macOS or Windows. Playwright 1.42 or newer, Vi
25
29
  npx test-inventory "tests/**/*.spec.ts" --framework playwright --output inventory.json
26
30
  ```
27
31
 
28
- Without the glob, the test files are those the Playwright or Vitest configs in the project run.
32
+ Without the glob, the test files are those the project's Playwright, Vitest, Jest, Mocha, Jasmine, Cypress or
33
+ WebdriverIO configs run, or for node:test and Bun the files `node --test` and `bun test` run. Without `--framework`, the
34
+ scan takes the runner the project uses, and asks for `--framework` when it uses several.
29
35
 
30
36
  For this spec from a trading app's UI tests, `tests/alerts.spec.ts`:
31
37
 
@@ -98,12 +104,12 @@ in Claude Code, Cursor, VS Code and other MCP clients ask it which tests a proje
98
104
 
99
105
  - Config files are read as code, never run, and only in part: settings computed at run time, as from `process.env`,
100
106
  can't be worked out, and settings such as `workers` or `use` are not read.
101
- - Values imported from other files are not worked out. A tag or title that comes from an import gets a diagnostic
102
- that names the module. A custom `test` object is followed through as many files as it takes, through relative
103
- paths, tsconfig path aliases, `#` imports in package.json and workspace packages; one that can't be followed gets
107
+ - Values from installed packages are not worked out; values from the project's own files are. A tag or title that
108
+ comes from a package gets a diagnostic that names the module. A custom `test` object is followed through as many files as it takes, through relative
109
+ paths, config and tsconfig path aliases, `#` imports in package.json and workspace packages; one that can't be followed gets
104
110
  an error. Tests declared in helper functions are listed at each call, also for helpers imported from other files.
105
111
  - A test in a loop or a `.each` table is one record, with `isInLoop` or `caseCount`; the runner creates one test per
106
- iteration or case.
112
+ iteration or case. `--expand-cases` lists each case whose values the scan can work out as its own record.
107
113
  - Conditions such as `browserName === 'webkit'` are kept as source text, not evaluated.
108
114
 
109
115
  ## Support
@@ -12,3 +12,25 @@ import type { Value } from './values.ts';
12
12
  * values whole.
13
13
  */
14
14
  export declare function vitestCaseTitle(template: string, items: readonly Value[], index: number, version: readonly [number, number] | null, truncate?: number): string | null;
15
+ /**
16
+ * The title Jest gives one row of a `.each` table of arrays, or of single values, read from `jest-each` 29.0, 29.7,
17
+ * 30.0 and 30.5: `%#` and from 30.0 `%$` for the row's index, then one placeholder per item, in order. `%p` shows the
18
+ * item with pretty-format and the others with Node's `util.format`, which Jest calls too. Items left over are dropped.
19
+ * Null when an item is an array or object that `util.format` would show, which differs between Node versions.
20
+ */
21
+ export declare function jestRowTitle(template: string, row: readonly Value[], index: number, version: readonly [number, number] | null): string | null;
22
+ /**
23
+ * The title Jest gives one case of a `.each` table of objects, or one row of a tagged-template table: `$name` and
24
+ * `$name.path` for the case's values, and `$#` for its index. `keepsEscapes` is set for a tagged template, where `%%`
25
+ * stays as written. Null when Jest fails the table, as when a path goes through `null` before 30.0.
26
+ */
27
+ export declare function jestKeysTitle(template: string, item: Record<string, Value>, index: number, version: readonly [number, number] | null, keepsEscapes: boolean): string | null;
28
+ /**
29
+ * The title Bun gives one case of `.each`, read from Bun 1.2.23, 1.3.14 and 1.4.4: for a case whose first value is an
30
+ * object, first `$key` and `$key.path` for its values, then for every case the placeholders, left to right. `%s` takes
31
+ * a string, `%d` and `%f` a number, `%i` an integer, `%j` and `%o` any value as JSON; a value of another type leaves
32
+ * the placeholder as written, but is used up. `%#` is the index, `%%` a `%`, and `%` before any other character is
33
+ * dropped. Once the values are used up, the rest stays as written. Null where Bun pretty-prints: `%p`, and `$key`
34
+ * of an object, `null` or `undefined`.
35
+ */
36
+ export declare function bunCaseTitle(template: string, args: readonly Value[], index: number, version: readonly [number, number] | null): string | null;
@@ -1,3 +1,4 @@
1
+ import { format } from 'node:util';
1
2
  /**
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
3
4
  * and 5.0: `%#` and `%$` for the case's index, printf placeholders for its items, and `$name` for a key of its object.
@@ -225,3 +226,186 @@ function loupeTruncate(text, length) {
225
226
  end -= 1;
226
227
  return `${text.slice(0, end)}…`;
227
228
  }
229
+ /** The text Jest puts in place of `%%` and of `%` in string values while it formats a title. */
230
+ const JEST_ESCAPE = '@@__JEST_EACH_PLACEHOLDER_ESCAPE__@@';
231
+ const JEST_PLACEHOLDER = /%[#Odfijops]/;
232
+ /** The placeholders `util.format` takes an argument for. */
233
+ const NODE_PLACEHOLDER = /%[sdifjoOc]/;
234
+ /**
235
+ * The title Jest gives one row of a `.each` table of arrays, or of single values, read from `jest-each` 29.0, 29.7,
236
+ * 30.0 and 30.5: `%#` and from 30.0 `%$` for the row's index, then one placeholder per item, in order. `%p` shows the
237
+ * item with pretty-format and the others with Node's `util.format`, which Jest calls too. Items left over are dropped.
238
+ * Null when an item is an array or object that `util.format` would show, which differs between Node versions.
239
+ */
240
+ export function jestRowTitle(template, row, index, version) {
241
+ const isJest30 = version === null || version[0] >= 30;
242
+ const replacesWithFunction = version === null || version[0] > 30 || (version[0] === 30 && version[1] >= 5);
243
+ let text = template.replaceAll('%%', JEST_ESCAPE).replace('%#', String(index));
244
+ if (isJest30)
245
+ text = text.replace('%$', String(index + 1));
246
+ for (const item of row) {
247
+ const placeholder = JEST_PLACEHOLDER.exec(text);
248
+ if (placeholder === null)
249
+ continue;
250
+ const value = typeof item === 'string' ? item.replaceAll('%', JEST_ESCAPE) : item;
251
+ if (placeholder[0] === '%p') {
252
+ const pretty = prettyFormatMin(value);
253
+ // Before 30.5, `$&` and the like in the value are read as replacement patterns, as JavaScript's `replace` does.
254
+ text = replacesWithFunction ? text.replace('%p', () => pretty) : text.replace('%p', pretty);
255
+ }
256
+ else if (placeholder[0] === '%j' && replacesWithFunction) {
257
+ const json = value === undefined ? 'undefined' : JSON.stringify(value);
258
+ text = text.replace('%j', () => json);
259
+ }
260
+ else if (isPrimitive(value) || (placeholder[0] === '%j' && NODE_PLACEHOLDER.exec(text)?.[0] === '%j')) {
261
+ text = format(text, value);
262
+ }
263
+ else {
264
+ return null;
265
+ }
266
+ }
267
+ return text.replaceAll(JEST_ESCAPE, '%');
268
+ }
269
+ /**
270
+ * The title Jest gives one case of a `.each` table of objects, or one row of a tagged-template table: `$name` and
271
+ * `$name.path` for the case's values, and `$#` for its index. `keepsEscapes` is set for a tagged template, where `%%`
272
+ * stays as written. Null when Jest fails the table, as when a path goes through `null` before 30.0.
273
+ */
274
+ export function jestKeysTitle(template, item, index, version, keepsEscapes) {
275
+ let text;
276
+ try {
277
+ text = interpolateKeys(template, item, version);
278
+ }
279
+ catch {
280
+ return null;
281
+ }
282
+ const title = text.replace('$#', String(index));
283
+ return keepsEscapes ? title : title.replaceAll('%%', '%');
284
+ }
285
+ /** Jest's `interpolateVariables`, which throws where Jest's does. */
286
+ function interpolateKeys(template, item, version) {
287
+ const [major, minor] = version ?? [Infinity, 0];
288
+ const show = (value) => (isPrimitive(value) ? String(value) : prettyFormatMin(value));
289
+ if (major === 29 && minor < 4) {
290
+ // Each match of each key is replaced in turn, as text, so a value can hold the next key's match.
291
+ const matches = Object.keys(item).flatMap((key) => template.match(new RegExp(`\\$${key}[\\.\\w]*`, 'g')) ?? []);
292
+ return matches.reduce((title, match) => title.replace(match, show(keyPath(item, match, false))), template);
293
+ }
294
+ let keys = Object.keys(item);
295
+ if (major > 30 || (major === 30 && minor >= 5)) {
296
+ keys = keys.filter((key) => key.length > 0).sort((a, b) => b.length - a.length);
297
+ if (keys.length === 0)
298
+ return template;
299
+ keys = keys.map((key) => key.replaceAll(/[$()*+.?[\\\]^{|}]/g, '\\$&'));
300
+ }
301
+ const pattern = new RegExp(`\\$(${keys.join('|')})[.\\w]*`, 'g');
302
+ return template.replaceAll(pattern, (match) => show(keyPath(item, match, major >= 30)));
303
+ }
304
+ /** The value a `$name.path` match leads to, as Jest's `getPath` reads it; from 30.0 `null` and `undefined` end it. */
305
+ function keyPath(item, match, endsAtNull) {
306
+ let value = item;
307
+ for (const key of match.slice(1).split('.')) {
308
+ if (endsAtNull && (value === null || value === undefined))
309
+ return String(value);
310
+ // Throws on `null` and `undefined` before 30.0, as Jest's does.
311
+ if (!key || !Object.prototype.hasOwnProperty.call(value, key))
312
+ return value;
313
+ value = value[key];
314
+ }
315
+ return endsAtNull && (value === null || value === undefined) ? String(value) : value;
316
+ }
317
+ /** pretty-format with `{ min: true, maxDepth: 1 }`, as Jest shows a value in a title. */
318
+ function prettyFormatMin(value, depth = 0) {
319
+ if (typeof value === 'string')
320
+ return `"${value.replaceAll(/"|\\/g, '\\$&')}"`;
321
+ if (isPrimitive(value))
322
+ return Object.is(value, -0) ? '-0' : String(value);
323
+ if (depth >= 1)
324
+ return Array.isArray(value) ? '[Array]' : '[Object]';
325
+ if (Array.isArray(value))
326
+ return `[${value.map((item) => prettyFormatMin(item, depth + 1)).join(', ')}]`;
327
+ const keys = Object.keys(value).sort();
328
+ return `{${keys.map((key) => `${prettyFormatMin(key)}: ${prettyFormatMin(value[key], depth + 1)}`).join(', ')}}`;
329
+ }
330
+ /**
331
+ * The title Bun gives one case of `.each`, read from Bun 1.2.23, 1.3.14 and 1.4.4: for a case whose first value is an
332
+ * object, first `$key` and `$key.path` for its values, then for every case the placeholders, left to right. `%s` takes
333
+ * a string, `%d` and `%f` a number, `%i` an integer, `%j` and `%o` any value as JSON; a value of another type leaves
334
+ * the placeholder as written, but is used up. `%#` is the index, `%%` a `%`, and `%` before any other character is
335
+ * dropped. Once the values are used up, the rest stays as written. Null where Bun pretty-prints: `%p`, and `$key`
336
+ * of an object, `null` or `undefined`.
337
+ */
338
+ export function bunCaseTitle(template, args, index, version) {
339
+ let text = template;
340
+ const [first] = args;
341
+ if (first !== null && typeof first === 'object' && !Array.isArray(first)) {
342
+ const quotes = version !== null && (version[0] < 1 || (version[0] === 1 && version[1] < 3));
343
+ const shown = { all: true };
344
+ text = text.replace(/\$([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)/g, (written, path) => {
345
+ let value = first;
346
+ for (const key of path.split('.')) {
347
+ value = value !== null && typeof value === 'object' && !Array.isArray(value) ? value[key] : undefined;
348
+ }
349
+ if (value === undefined && !hasPath(first, path))
350
+ return written;
351
+ if (typeof value === 'string')
352
+ return quotes ? JSON.stringify(value) : value;
353
+ if (typeof value === 'number')
354
+ return Object.is(value, -0) ? '-0' : String(value);
355
+ if (typeof value === 'boolean')
356
+ return String(value);
357
+ shown.all = false;
358
+ return written;
359
+ });
360
+ if (!shown.all)
361
+ return null;
362
+ }
363
+ let out = '';
364
+ let used = 0;
365
+ for (let at = 0; at < text.length; at++) {
366
+ const char = text[at];
367
+ if (char !== '%' || at === text.length - 1) {
368
+ out += char;
369
+ continue;
370
+ }
371
+ if (used >= args.length)
372
+ return out + text.slice(at);
373
+ const placeholder = text[++at];
374
+ if (placeholder === '#')
375
+ out += String(index);
376
+ else if (placeholder === '%')
377
+ out += '%';
378
+ else if (placeholder === 'p')
379
+ return null;
380
+ else if ('sdfijo'.includes(placeholder)) {
381
+ const formatted = bunValue(placeholder, args[used++]);
382
+ if (formatted === null)
383
+ return null;
384
+ out += formatted;
385
+ }
386
+ else
387
+ out += placeholder;
388
+ }
389
+ return out;
390
+ }
391
+ /** Whether an object has a value at a dotted path, `undefined` included. */
392
+ function hasPath(item, path) {
393
+ let value = item;
394
+ for (const key of path.split('.')) {
395
+ if (value === null || typeof value !== 'object' || Array.isArray(value) || !(key in value))
396
+ return false;
397
+ value = value[key];
398
+ }
399
+ return true;
400
+ }
401
+ /** One placeholder of a Bun title, or the placeholder as written for a value of another type; null for a JSON Bun shows otherwise. */
402
+ function bunValue(placeholder, value) {
403
+ const written = `%${placeholder}`;
404
+ if (placeholder === 's')
405
+ return typeof value === 'string' ? value : written;
406
+ if (placeholder === 'd' || placeholder === 'f')
407
+ return typeof value === 'number' ? String(value) : written;
408
+ if (placeholder === 'i')
409
+ return typeof value === 'number' && Number.isInteger(value) ? String(value) : written;
410
+ return value === undefined ? null : JSON.stringify(value);
411
+ }
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 --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";
9
+ export declare const HELP = "Usage: test-inventory [glob...] [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 runner's configs in the root and the folders below it run, or for node:test and Bun those\nnode --test and bun test run.\n\nOptions:\n --framework <name> playwright, vitest, jest, mocha, jasmine, cypress, webdriverio, node or bun\n (default: the one the project uses)\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
@@ -8,13 +8,15 @@ import { writeInventory, writeInventoryLines } from "./json.js";
8
8
  import { OptionsError, toPosixPath } from "./options.js";
9
9
  import { scan } from "./scan.js";
10
10
  import { toolVersion } from "./version.js";
11
- export const HELP = `Usage: test-inventory [glob...] --framework <playwright|vitest> [options]
11
+ export const HELP = `Usage: test-inventory [glob...] [options]
12
12
 
13
13
  Lists the tests in the matching files as JSON, read from the source code without running them. Without globs, the
14
- test files are those the Playwright or Vitest configs in the root and the folders below it run.
14
+ test files are those the runner's configs in the root and the folders below it run, or for node:test and Bun those
15
+ node --test and bun test run.
15
16
 
16
17
  Options:
17
- --framework <name> playwright or vitest (required)
18
+ --framework <name> playwright, vitest, jest, mocha, jasmine, cypress, webdriverio, node or bun
19
+ (default: the one the project uses)
18
20
  --root <dir> project root; paths in the output are relative to it (default: current directory)
19
21
  --ignore <glob> leave out matching files; can be repeated (node_modules is always left out)
20
22
  --framework-version <x.y.z> apply the rules of this framework version instead of the one in the project
@@ -143,8 +145,6 @@ export async function run(argv, io) {
143
145
  io.stdout.write(`${toolVersion}\n`);
144
146
  return FINISHED;
145
147
  }
146
- if (values.framework === undefined)
147
- return fail('--framework is required: playwright or vitest.');
148
148
  const format = values.format ?? 'json';
149
149
  if (!FORMATS.includes(format))
150
150
  return fail('--format must be json or jsonl.');
@@ -16,6 +16,13 @@ const SNAPSHOT_KEYS = {
16
16
  ['test', 'browser', 'expect', 'toMatchScreenshot', 'resolveScreenshotPath'],
17
17
  ['test', 'browser', 'expect', 'toMatchScreenshot', 'screenshotDirectory'],
18
18
  ],
19
+ jest: [['snapshotResolver']],
20
+ mocha: [],
21
+ jasmine: [],
22
+ cypress: [],
23
+ webdriverio: [],
24
+ node: [],
25
+ bun: [],
19
26
  };
20
27
  /** The keys of a config that name setup files, by framework; Vitest's are a project's own. */
21
28
  const SETUP_KEYS = {
@@ -24,6 +31,20 @@ const SETUP_KEYS = {
24
31
  ['test', 'setupFiles'],
25
32
  ['test', 'globalSetup'],
26
33
  ],
34
+ jest: [['setupFiles'], ['setupFilesAfterEnv'], ['globalSetup'], ['globalTeardown']],
35
+ // Mocha loads both before the tests: `require` for root hooks and global fixtures, `file` as test files.
36
+ mocha: [['require'], ['file']],
37
+ // Jasmine's `helpers` are globs from `spec_dir`, which these paths from the config's folder don't cover.
38
+ jasmine: [],
39
+ // The support file Cypress loads before every spec.
40
+ cypress: [
41
+ ['e2e', 'supportFile'],
42
+ ['component', 'supportFile'],
43
+ ],
44
+ // Mocha's `require` runs before the specs, as root hooks may.
45
+ webdriverio: [['mochaOpts', 'require']],
46
+ node: [],
47
+ bun: [],
27
48
  };
28
49
  const UNKNOWN_PROJECTS = "The projects that run the file can't be worked out, so dependencyHash doesn't cover their setup.";
29
50
  /**
@@ -86,7 +107,9 @@ export class ConfigDependencies {
86
107
  const found = written(settings, keys);
87
108
  for (const item of found ? listOf(found.setting) : []) {
88
109
  if (item.kind === 'value' && typeof item.value === 'string') {
89
- const read = this.setupFile(root, item.value);
110
+ // Jest's `<rootDir>` at the start of a path is the project's root.
111
+ const file = item.value.startsWith('<rootDir>') ? `.${item.value.slice(9)}` : item.value;
112
+ const read = this.setupFile(root, file);
90
113
  if (read && !('reason' in read))
91
114
  modules.add(read);
92
115
  else if (read) {
@@ -124,13 +147,14 @@ export class ConfigDependencies {
124
147
  ? projects
125
148
  : [{ settings: placement.settings, root: folder, container: null }];
126
149
  // Vitest runs the global setup of the config it starts with for every project, extending it or not.
127
- setup(own, config.settings, folder, ['test', 'globalSetup']);
150
+ if (this.framework === 'vitest')
151
+ setup(own, config.settings, folder, ['test', 'globalSetup']);
128
152
  for (const project of running) {
129
153
  // A file several projects run may be run by one from another config, which the file runs with too; the config
130
154
  // reader has read that config, so it can be read.
131
155
  const written = 'config' in project ? this.module(project.config.file) : own;
132
156
  modules.add(written);
133
- for (const keys of SETUP_KEYS.vitest)
157
+ for (const keys of SETUP_KEYS[this.framework])
134
158
  setup(written, project.settings, project.root, keys);
135
159
  const { container } = project;
136
160
  const outer = container && this.module(container.file);