test-inventory 0.4.0 → 0.5.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 (45) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +4 -1
  3. package/dist/ast.d.ts +3 -3
  4. package/dist/ast.js +4 -4
  5. package/dist/cli.d.ts +1 -1
  6. package/dist/cli.js +4 -5
  7. package/dist/config-runs.d.ts +97 -0
  8. package/dist/config-runs.js +784 -0
  9. package/dist/config.d.ts +90 -0
  10. package/dist/config.js +652 -0
  11. package/dist/diagnostics.js +5 -1
  12. package/dist/extract.d.ts +8 -0
  13. package/dist/extract.js +14 -1
  14. package/dist/filter.js +7 -0
  15. package/dist/frameworks/bindings.d.ts +22 -0
  16. package/dist/frameworks/bindings.js +19 -9
  17. package/dist/frameworks/common.d.ts +2 -0
  18. package/dist/frameworks/common.js +4 -0
  19. package/dist/frameworks/playwright.d.ts +2 -1
  20. package/dist/frameworks/playwright.js +3 -2
  21. package/dist/frameworks/vitest.d.ts +2 -1
  22. package/dist/frameworks/vitest.js +11 -7
  23. package/dist/index.d.ts +1 -1
  24. package/dist/inventory.d.ts +2 -2
  25. package/dist/inventory.js +2 -1
  26. package/dist/json.js +2 -0
  27. package/dist/model.d.ts +6 -1
  28. package/dist/options.d.ts +2 -2
  29. package/dist/records.d.ts +54 -1
  30. package/dist/records.js +192 -28
  31. package/dist/resolve.js +3 -3
  32. package/dist/scan-file.d.ts +5 -1
  33. package/dist/scan-file.js +5 -2
  34. package/dist/scan-source.js +1 -1
  35. package/dist/scan.d.ts +2 -1
  36. package/dist/scan.js +63 -17
  37. package/dist/types.d.ts +73 -7
  38. package/dist/values.d.ts +5 -0
  39. package/dist/values.js +5 -0
  40. package/dist/vitest-config.d.ts +6 -4
  41. package/dist/vitest-config.js +32 -28
  42. package/dist/vitest-defaults.d.ts +6 -0
  43. package/dist/vitest-defaults.js +16 -0
  44. package/package.json +10 -8
  45. package/schema/v1.json +180 -6
package/dist/types.d.ts CHANGED
@@ -20,18 +20,25 @@ export type Framework = 'playwright' | 'vitest';
20
20
  */
21
21
  export type TestState = 'active' | 'skip' | 'fixme' | 'todo' | 'expectedToFail' | 'notLoaded';
22
22
  /**
23
- * Where a test's state is set: on the test, on a describe around it, or at file level.
23
+ * Where a test's state is set: on the test, on a describe around it, at file level, or by a Vitest tag the config
24
+ * defines.
24
25
  *
25
26
  * @inline
26
27
  */
27
- export type StateSource = 'test' | 'suite' | 'file';
28
+ export type StateSource = 'test' | 'suite' | 'file' | 'config';
29
+ /**
30
+ * Where a test's run setting comes from: the test, a describe around it, the file, or the config its file uses.
31
+ *
32
+ * @inline
33
+ */
34
+ export type SettingSource = 'test' | 'suite' | 'file' | 'config';
28
35
  /**
29
36
  * `error`: tests may be missing from the output. `warning`: a value is missing or approximate.
30
37
  *
31
38
  * @inline
32
39
  */
33
40
  export type DiagnosticLevel = 'error' | 'warning';
34
- export type DiagnosticCode = 'unresolved-test-import' | 'parse-error' | 'read-error' | 'framework-mismatch' | 'unresolved-tag' | 'unresolved-annotation' | 'unresolved-cases' | 'test-in-loop' | 'dynamic-title' | 'duplicate-title' | 'duplicate-id' | 'internal-error' | 'invalid-chain' | 'skip-without-body' | 'test-without-body' | 'describe-not-called' | 'async-describe' | 'nested-test' | 'tag-without-at' | 'removed-api' | 'only-in-skipped-describe' | 'config-may-change-state' | 'symlink-ignored' | 'invalid-import' | 'globals-not-enabled' | 'unknown-framework-version' | 'unsupported-framework-version' | 'local-test-object' | 'state-in-helper' | 'unresolved-option' | 'dynamic-reason' | 'no-files-matched' | 'unsupported-file' | 'approximate-framework-version' | 'test-in-function' | 'empty-title' | 'bench-outside-benchmark-file';
41
+ export type DiagnosticCode = 'unresolved-test-import' | 'parse-error' | 'read-error' | 'framework-mismatch' | 'unresolved-tag' | 'unresolved-annotation' | 'unresolved-cases' | 'test-in-loop' | 'dynamic-title' | 'duplicate-title' | 'duplicate-id' | 'internal-error' | 'invalid-chain' | 'skip-without-body' | 'test-without-body' | 'describe-not-called' | 'async-describe' | 'nested-test' | 'tag-without-at' | 'removed-api' | 'only-in-skipped-describe' | 'config-may-change-state' | 'undefined-tag' | 'symlink-ignored' | 'invalid-import' | 'globals-not-enabled' | 'unknown-framework-version' | 'unsupported-framework-version' | 'local-test-object' | 'state-in-helper' | 'unresolved-option' | 'dynamic-reason' | 'no-files-matched' | 'unsupported-file' | 'approximate-framework-version' | 'test-in-function' | 'empty-title' | 'bench-outside-benchmark-file' | 'config-value-unknown' | 'file-not-run' | 'forbidden-only';
35
42
  export type MetaValue = string | number | boolean | null | MetaValue[] | {
36
43
  [key: string]: MetaValue;
37
44
  };
@@ -45,6 +52,8 @@ export interface Inventory {
45
52
  /** Read from the project, or given with `--framework-version`; null when unknown. */
46
53
  frameworkVersion: string | null;
47
54
  summary: Summary;
55
+ /** The configs the scanned files use, sorted by path. */
56
+ configs: ConfigRecord[];
48
57
  /** One per scanned file, in the order the files were scanned. */
49
58
  files: FileRecord[];
50
59
  suites: SuiteRecord[];
@@ -53,12 +62,14 @@ export interface Inventory {
53
62
  }
54
63
  /**
55
64
  * One line of the JSON Lines output (`--format jsonl`): the inventory with each record on its own line, marked by
56
- * `type`. The `inventory` line comes first, then the files, suites, tests and diagnostics in the order of the JSON
57
- * output, and the `summary` line last.
65
+ * `type`. The `inventory` line comes first, then the configs, files, suites, tests and diagnostics in the order of the
66
+ * JSON output, and the `summary` line last.
58
67
  */
59
68
  export type InventoryLine = ({
60
69
  type: 'inventory';
61
70
  } & Pick<Inventory, 'schemaVersion' | 'tool' | 'framework' | 'frameworkVersion'>) | ({
71
+ type: 'config';
72
+ } & ConfigRecord) | ({
62
73
  type: 'file';
63
74
  } & FileRecord) | ({
64
75
  type: 'suite';
@@ -103,11 +114,40 @@ export interface Summary {
103
114
  /** Tests per annotation type. */
104
115
  annotationCounts: Record<string, Count>;
105
116
  }
117
+ /**
118
+ * A Playwright or Vitest config that scanned files use: the nearest one in a file's folder or a folder above it, up to
119
+ * the root, as the runner started in that folder would. Read as code, never run.
120
+ */
121
+ export interface ConfigRecord {
122
+ /** Path from the project root, with `/` separators. */
123
+ relativeFilePath: string;
124
+ /** Files the config takes settings from through imports, such as a shared base config, in the order read. */
125
+ extends: string[];
126
+ /**
127
+ * The projects the config runs tests in: Playwright's `projects`, or one for a config without them; Vitest's, with
128
+ * each browser instance as one. Null when the scan can't work them out.
129
+ */
130
+ projects: ProjectRecord[] | null;
131
+ }
132
+ /** @inline */
133
+ export interface ProjectRecord {
134
+ /** As the runner names it: `''` for a project without a name. */
135
+ name: string;
136
+ /** The folder the project's test files are found in, from the project root: Playwright's `testDir`, Vitest's `dir`. */
137
+ testDir: string;
138
+ /** Playwright's projects that run before this one; `[]` for Vitest. */
139
+ dependencies: string[];
140
+ }
106
141
  /** A scanned file, also one that has no tests or could not be read. */
107
142
  export interface FileRecord {
108
143
  /** Path from the project root, with `/` separators. */
109
144
  relativeFilePath: string;
110
145
  fileName: string;
146
+ /**
147
+ * The config the file uses, from the project root: the outermost config at or above it that runs it, as the runner
148
+ * started at the root would, else the nearest. Null when there is none.
149
+ */
150
+ config: string | null;
111
151
  /** Hooks at the top level of the file, which run around every test in it. */
112
152
  hooks: HookCounts;
113
153
  suiteCount: Count;
@@ -275,7 +315,7 @@ export interface TestRecord {
275
315
  hooks: HookCounts;
276
316
  /**
277
317
  * In milliseconds: the last `test.setTimeout()` in the body or a `beforeEach`, otherwise the nearest describe or file
278
- * that sets one. Null when the file sets none, so the config's timeout applies.
318
+ * that sets one, otherwise the config. Null when none sets it, so the runner's default applies.
279
319
  */
280
320
  timeout: Count | null;
281
321
  /** Playwright `test.slow()` applies: the runner triples the timeout. */
@@ -288,8 +328,29 @@ export interface TestRecord {
288
328
  retryDelay: Count | null;
289
329
  /** Vitest only: source text of the `retry` object's `condition`, a RegExp or a function. */
290
330
  retryCondition: string | null;
291
- /** Vitest only: how many times the runner runs the test again after the first run. */
331
+ /**
332
+ * How many times the runner runs the test again after the first run: Vitest's `repeats`, or Playwright's `repeatEach`
333
+ * from the config less one.
334
+ */
292
335
  repeats: Count | null;
336
+ /**
337
+ * Where `timeout` comes from; null when nothing sets it. A null `timeout` from the config means the config sets it, but
338
+ * its projects set different values or the value depends on a condition, as in `process.env.CI ? 2 : 0`.
339
+ */
340
+ timeoutSource: SettingSource | null;
341
+ /** Where `retries`, `retryDelay` and `retryCondition` come from, read as `timeoutSource` is. */
342
+ retriesSource: SettingSource | null;
343
+ /** Where `repeats` comes from, read as `timeoutSource` is. */
344
+ repeatsSource: SettingSource | null;
345
+ /**
346
+ * The config's source text for `timeout` when the config sets it under a condition, as in
347
+ * `process.env.CI ? 60_000 : 30_000`, shortened when long; null otherwise.
348
+ */
349
+ timeoutConfigText: string | null;
350
+ /** The config's source text for `retries`, read as `timeoutConfigText` is. */
351
+ retriesConfigText: string | null;
352
+ /** The config's source text for `repeats`, Playwright's `repeatEach`, read as `timeoutConfigText` is. */
353
+ repeatsConfigText: string | null;
293
354
  /**
294
355
  * Names the test function takes from its context, then those of the `beforeEach`, `afterEach` and `aroundEach` hooks
295
356
  * around it, once each. Vitest's own context keys, such as `expect` and `task`, are left out.
@@ -320,6 +381,11 @@ export interface TestRecord {
320
381
  isInCondition: boolean;
321
382
  /** Declared inside a plain function. */
322
383
  isInFunction: boolean;
384
+ /**
385
+ * Names of the projects that run the test, of the config its file uses, after each project's files and Playwright's
386
+ * `grep` and `grepInvert`. `[]` when no project runs it; null when there is no config, or the scan can't tell.
387
+ */
388
+ projects: string[] | null;
323
389
  }
324
390
  /** @inline */
325
391
  export interface Step {
package/dist/values.d.ts CHANGED
@@ -36,6 +36,11 @@ export declare class Values {
36
36
  * the file does not declare it, as with globals.
37
37
  */
38
38
  declarationOf(name: string, scopes: readonly Node[]): Node | null;
39
+ /** The value a `const` name is declared with, and the scopes around that declaration; null for any other name. */
40
+ constantOf(name: string, scopes: readonly Node[]): {
41
+ init: Expression;
42
+ scopes: readonly Node[];
43
+ } | null;
39
44
  /** The module a name is imported from where it is used; null when it isn't an import. */
40
45
  importSource(name: string, scopes: readonly Node[]): string | null;
41
46
  /** Whether a name refers to a function or class declared in the file. */
package/dist/values.js CHANGED
@@ -208,6 +208,11 @@ export class Values {
208
208
  declarationOf(name, scopes) {
209
209
  return this.lookup(name, scopes)?.binding.declaration ?? null;
210
210
  }
211
+ /** The value a `const` name is declared with, and the scopes around that declaration; null for any other name. */
212
+ constantOf(name, scopes) {
213
+ const binding = this.lookup(name, scopes)?.binding;
214
+ return binding?.kind === 'const' ? { init: binding.init, scopes: binding.scopes } : null;
215
+ }
211
216
  /** The module a name is imported from where it is used; null when it isn't an import. */
212
217
  importSource(name, scopes) {
213
218
  const binding = this.lookup(name, scopes)?.binding;
@@ -1,6 +1,8 @@
1
+ import type { Placement } from './config-runs.ts';
2
+ import { type Configs } from './config.ts';
1
3
  /**
2
- * Returns a check of whether Vitest's globals are turned on for a file, by a config in its folder or any folder
3
- * above it up to the root, as in a monorepo where each package has its own config. Config files are read as code and
4
- * never run.
4
+ * Returns a check of whether Vitest's globals are turned on for a file: by the project of the config it uses, or by a
5
+ * tsconfig in its folder or any folder above it up to the root. The check gives the message to report when they are
6
+ * not, and null when they are or may be. Config files are read as code, never run.
5
7
  */
6
- export declare function globalsCheck(root: string): (file: string) => boolean;
8
+ export declare function globalsCheck(root: string, configs: Configs, placementOf: (file: string) => Placement): (file: string) => string | null;
@@ -1,19 +1,6 @@
1
1
  import { readdirSync, readFileSync } from 'node:fs';
2
2
  import path from 'node:path';
3
- import { childNodes } from "./ast.js";
4
- import { parseFile } from "./parse.js";
5
- const CONFIG_FILES = ['vitest.config', 'vite.config'].flatMap((name) => ['.ts', '.mts', '.cts', '.js', '.mjs', '.cjs'].map((extension) => name + extension));
6
- function setsGlobals(node) {
7
- if (node.type === 'Property' &&
8
- !node.computed &&
9
- ((node.key.type === 'Identifier' && node.key.name === 'globals') ||
10
- (node.key.type === 'Literal' && node.key.value === 'globals')) &&
11
- node.value.type === 'Literal' &&
12
- node.value.value === true) {
13
- return true;
14
- }
15
- return childNodes(node).some(setsGlobals);
16
- }
3
+ import { settingAt } from "./config.js";
17
4
  /** A file's text, or null when it is missing or can't be read. */
18
5
  function readText(file) {
19
6
  try {
@@ -23,13 +10,8 @@ function readText(file) {
23
10
  return null;
24
11
  }
25
12
  }
26
- /** Whether a folder has a Vitest or Vite config with `globals: true`, or a tsconfig with `vitest/globals`. */
27
- function enablesGlobals(directory) {
28
- for (const name of CONFIG_FILES) {
29
- const code = readText(path.join(directory, name));
30
- if (code !== null && setsGlobals(parseFile(name, code).program))
31
- return true;
32
- }
13
+ /** Whether a folder has a tsconfig with `vitest/globals` in its types. */
14
+ function typesGlobals(directory) {
33
15
  let names;
34
16
  try {
35
17
  names = readdirSync(directory);
@@ -39,21 +21,43 @@ function enablesGlobals(directory) {
39
21
  }
40
22
  return names.some((name) => /^tsconfig.*\.json$/.test(name) && (readText(path.join(directory, name)) ?? '').includes('"vitest/globals"'));
41
23
  }
24
+ const ASSUMED = "files that use Vitest's global test functions are taken to have them";
25
+ /** Whether a project's settings turn Vitest's globals on; null when the scan can't tell. */
26
+ function turnsOnGlobals(settings, configs) {
27
+ // A project the scan can't work out was reported where its files were asked for.
28
+ if (settings.kind === 'unknown')
29
+ return null;
30
+ const globals = settingAt(settings, ['test', 'globals']);
31
+ if (globals?.kind !== 'unknown')
32
+ return globals?.kind === 'value' && globals.value === true;
33
+ configs.reportUnknown(globals, 'test.globals', ASSUMED);
34
+ return null;
35
+ }
42
36
  /**
43
- * Returns a check of whether Vitest's globals are turned on for a file, by a config in its folder or any folder
44
- * above it up to the root, as in a monorepo where each package has its own config. Config files are read as code and
45
- * never run.
37
+ * Returns a check of whether Vitest's globals are turned on for a file: by the project of the config it uses, or by a
38
+ * tsconfig in its folder or any folder above it up to the root. The check gives the message to report when they are
39
+ * not, and null when they are or may be. Config files are read as code, never run.
46
40
  */
47
- export function globalsCheck(root) {
41
+ export function globalsCheck(root, configs, placementOf) {
48
42
  const known = new Map();
49
- const enabled = (directory) => {
43
+ const typed = (directory) => {
50
44
  let result = known.get(directory);
51
45
  if (result === undefined) {
52
46
  const parent = path.dirname(directory);
53
- result = enablesGlobals(directory) || (directory !== root && parent !== directory && enabled(parent));
47
+ result = typesGlobals(directory) || (directory !== root && parent !== directory && typed(parent));
54
48
  known.set(directory, result);
55
49
  }
56
50
  return result;
57
51
  };
58
- return (file) => enabled(path.dirname(file));
52
+ return (file) => {
53
+ if (typed(path.dirname(file)))
54
+ return null;
55
+ const { config, settings } = placementOf(file);
56
+ if (config && settings && turnsOnGlobals(settings, configs) !== false)
57
+ return null;
58
+ const why = config
59
+ ? `${config.record.relativeFilePath} doesn't set test.globals to true for it`
60
+ : 'no Vitest or Vite config was found';
61
+ return `The file uses Vitest's global test functions, but ${why} and no tsconfig has vitest/globals in its types.`;
62
+ };
59
63
  }
@@ -0,0 +1,6 @@
1
+ /** Vitest's default file settings, as `configDefaults` in `vitest/config` holds them. */
2
+ export declare const VITEST_INCLUDE: string[];
3
+ export declare const VITEST_TYPECHECK_INCLUDE: string[];
4
+ export declare const VITEST_BENCHMARK_INCLUDE: string[];
5
+ /** Vitest's default `exclude`: Vitest 4 left out the folders and tool configs that Vitest 3 excludes. */
6
+ export declare function vitestExclude(version: [number, number] | null): string[];
@@ -0,0 +1,16 @@
1
+ /** Vitest's default file settings, as `configDefaults` in `vitest/config` holds them. */
2
+ export const VITEST_INCLUDE = ['**/*.{test,spec}.?(c|m)[jt]s?(x)'];
3
+ export const VITEST_TYPECHECK_INCLUDE = ['**/*.{test,spec}-d.?(c|m)[jt]s?(x)'];
4
+ export const VITEST_BENCHMARK_INCLUDE = ['**/*.{bench,benchmark}.?(c|m)[jt]s?(x)'];
5
+ /** Vitest's default `exclude`: Vitest 4 left out the folders and tool configs that Vitest 3 excludes. */
6
+ export function vitestExclude(version) {
7
+ if (version === null || version[0] >= 4)
8
+ return ['**/node_modules/**', '**/.git/**'];
9
+ return [
10
+ '**/node_modules/**',
11
+ '**/dist/**',
12
+ '**/cypress/**',
13
+ '**/.{idea,git,cache,output,temp}/**',
14
+ '**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier}.config.*',
15
+ ];
16
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "test-inventory",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Test metadata from source code, not from test results.",
5
5
  "keywords": [
6
6
  "testing",
@@ -79,7 +79,8 @@
79
79
  "changeset": "changeset"
80
80
  },
81
81
  "dependencies": {
82
- "oxc-parser": "0.154.0",
82
+ "oxc-parser": "0.156.0",
83
+ "picomatch": "^4.0.7",
83
84
  "tinyglobby": "^0.2.17"
84
85
  },
85
86
  "devDependencies": {
@@ -87,19 +88,20 @@
87
88
  "@changesets/changelog-github": "^1.0.1",
88
89
  "@changesets/cli": "^3.0.3",
89
90
  "@types/node": "~22.12.0",
90
- "@vitest/coverage-v8": "5.0.4",
91
+ "@types/picomatch": "^4.0.3",
92
+ "@vitest/coverage-v8": "5.0.5",
91
93
  "ajv": "^8.20.0",
92
- "eslint": "~10.12.0",
94
+ "eslint": "~10.13.0",
93
95
  "prettier": "3.9.10",
94
96
  "publint": "^0.3.25",
95
97
  "ts-json-schema-generator": "2.9.0",
96
98
  "typedoc": "^0.28.20",
97
- "typedoc-plugin-markdown": "^4.13.1",
99
+ "typedoc-plugin-markdown": "^4.14.1",
98
100
  "typedoc-vitepress-theme": "^1.1.4",
99
101
  "typescript": "~6.0.3",
100
- "typescript-eslint": "~8.71.2",
101
- "vitepress": "2.0.0-alpha.20",
102
+ "typescript-eslint": "~8.72.1",
103
+ "vitepress": "2.0.0-alpha.21",
102
104
  "vitepress-plugin-llms": "^1.14.0",
103
- "vitest": "5.0.4"
105
+ "vitest": "5.0.5"
104
106
  }
105
107
  }
package/schema/v1.json CHANGED
@@ -28,6 +28,13 @@
28
28
  "summary": {
29
29
  "$ref": "#/definitions/Summary"
30
30
  },
31
+ "configs": {
32
+ "type": "array",
33
+ "items": {
34
+ "$ref": "#/definitions/ConfigRecord"
35
+ },
36
+ "description": "The configs the scanned files use, sorted by path."
37
+ },
31
38
  "files": {
32
39
  "type": "array",
33
40
  "items": {
@@ -60,6 +67,7 @@
60
67
  "framework",
61
68
  "frameworkVersion",
62
69
  "summary",
70
+ "configs",
63
71
  "files",
64
72
  "suites",
65
73
  "tests",
@@ -225,6 +233,67 @@
225
233
  ],
226
234
  "description": "Counts over the whole scan. Each test count uses the same definition as the matching test key."
227
235
  },
236
+ "ConfigRecord": {
237
+ "type": "object",
238
+ "properties": {
239
+ "relativeFilePath": {
240
+ "type": "string",
241
+ "description": "Path from the project root, with `/` separators."
242
+ },
243
+ "extends": {
244
+ "type": "array",
245
+ "items": {
246
+ "type": "string"
247
+ },
248
+ "description": "Files the config takes settings from through imports, such as a shared base config, in the order read."
249
+ },
250
+ "projects": {
251
+ "anyOf": [
252
+ {
253
+ "type": "array",
254
+ "items": {
255
+ "$ref": "#/definitions/ProjectRecord"
256
+ }
257
+ },
258
+ {
259
+ "type": "null"
260
+ }
261
+ ],
262
+ "description": "The projects the config runs tests in: Playwright's `projects`, or one for a config without them; Vitest's, with each browser instance as one. Null when the scan can't work them out."
263
+ }
264
+ },
265
+ "required": [
266
+ "relativeFilePath",
267
+ "extends",
268
+ "projects"
269
+ ],
270
+ "description": "A Playwright or Vitest config that scanned files use: the nearest one in a file's folder or a folder above it, up to the root, as the runner started in that folder would. Read as code, never run."
271
+ },
272
+ "ProjectRecord": {
273
+ "type": "object",
274
+ "properties": {
275
+ "name": {
276
+ "type": "string",
277
+ "description": "As the runner names it: `''` for a project without a name."
278
+ },
279
+ "testDir": {
280
+ "type": "string",
281
+ "description": "The folder the project's test files are found in, from the project root: Playwright's `testDir`, Vitest's `dir`."
282
+ },
283
+ "dependencies": {
284
+ "type": "array",
285
+ "items": {
286
+ "type": "string"
287
+ },
288
+ "description": "Playwright's projects that run before this one; `[]` for Vitest."
289
+ }
290
+ },
291
+ "required": [
292
+ "name",
293
+ "testDir",
294
+ "dependencies"
295
+ ]
296
+ },
228
297
  "FileRecord": {
229
298
  "type": "object",
230
299
  "properties": {
@@ -235,6 +304,13 @@
235
304
  "fileName": {
236
305
  "type": "string"
237
306
  },
307
+ "config": {
308
+ "type": [
309
+ "string",
310
+ "null"
311
+ ],
312
+ "description": "The config the file uses, from the project root: the outermost config at or above it that runs it, as the runner started at the root would, else the nearest. Null when there is none."
313
+ },
238
314
  "hooks": {
239
315
  "$ref": "#/definitions/HookCounts",
240
316
  "description": "Hooks at the top level of the file, which run around every test in it."
@@ -251,6 +327,7 @@
251
327
  "required": [
252
328
  "relativeFilePath",
253
329
  "fileName",
330
+ "config",
254
331
  "hooks",
255
332
  "suiteCount",
256
333
  "testCount"
@@ -884,7 +961,7 @@
884
961
  "type": "null"
885
962
  }
886
963
  ],
887
- "description": "In milliseconds: the last `test.setTimeout()` in the body or a `beforeEach`, otherwise the nearest describe or file that sets one. Null when the file sets none, so the config's timeout applies."
964
+ "description": "In milliseconds: the last `test.setTimeout()` in the body or a `beforeEach`, otherwise the nearest describe or file that sets one, otherwise the config. Null when none sets it, so the runner's default applies."
888
965
  },
889
966
  "isSlow": {
890
967
  "type": "boolean",
@@ -938,7 +1015,61 @@
938
1015
  "type": "null"
939
1016
  }
940
1017
  ],
941
- "description": "Vitest only: how many times the runner runs the test again after the first run."
1018
+ "description": "How many times the runner runs the test again after the first run: Vitest's `repeats`, or Playwright's `repeatEach` from the config less one."
1019
+ },
1020
+ "timeoutSource": {
1021
+ "anyOf": [
1022
+ {
1023
+ "$ref": "#/definitions/SettingSource"
1024
+ },
1025
+ {
1026
+ "type": "null"
1027
+ }
1028
+ ],
1029
+ "description": "Where `timeout` comes from; null when nothing sets it. A null `timeout` from the config means the config sets it, but its projects set different values or the value depends on a condition, as in `process.env.CI ? 2 : 0`."
1030
+ },
1031
+ "retriesSource": {
1032
+ "anyOf": [
1033
+ {
1034
+ "$ref": "#/definitions/SettingSource"
1035
+ },
1036
+ {
1037
+ "type": "null"
1038
+ }
1039
+ ],
1040
+ "description": "Where `retries`, `retryDelay` and `retryCondition` come from, read as `timeoutSource` is."
1041
+ },
1042
+ "repeatsSource": {
1043
+ "anyOf": [
1044
+ {
1045
+ "$ref": "#/definitions/SettingSource"
1046
+ },
1047
+ {
1048
+ "type": "null"
1049
+ }
1050
+ ],
1051
+ "description": "Where `repeats` comes from, read as `timeoutSource` is."
1052
+ },
1053
+ "timeoutConfigText": {
1054
+ "type": [
1055
+ "string",
1056
+ "null"
1057
+ ],
1058
+ "description": "The config's source text for `timeout` when the config sets it under a condition, as in `process.env.CI ? 60_000 : 30_000`, shortened when long; null otherwise."
1059
+ },
1060
+ "retriesConfigText": {
1061
+ "type": [
1062
+ "string",
1063
+ "null"
1064
+ ],
1065
+ "description": "The config's source text for `retries`, read as `timeoutConfigText` is."
1066
+ },
1067
+ "repeatsConfigText": {
1068
+ "type": [
1069
+ "string",
1070
+ "null"
1071
+ ],
1072
+ "description": "The config's source text for `repeats`, Playwright's `repeatEach`, read as `timeoutConfigText` is."
942
1073
  },
943
1074
  "fixtures": {
944
1075
  "type": "array",
@@ -1010,6 +1141,20 @@
1010
1141
  "isInFunction": {
1011
1142
  "type": "boolean",
1012
1143
  "description": "Declared inside a plain function."
1144
+ },
1145
+ "projects": {
1146
+ "anyOf": [
1147
+ {
1148
+ "type": "array",
1149
+ "items": {
1150
+ "type": "string"
1151
+ }
1152
+ },
1153
+ {
1154
+ "type": "null"
1155
+ }
1156
+ ],
1157
+ "description": "Names of the projects that run the test, of the config its file uses, after each project's files and Playwright's `grep` and `grepInvert`. `[]` when no project runs it; null when there is no config, or the scan can't tell."
1013
1158
  }
1014
1159
  },
1015
1160
  "required": [
@@ -1058,6 +1203,12 @@
1058
1203
  "retryDelay",
1059
1204
  "retryCondition",
1060
1205
  "repeats",
1206
+ "timeoutSource",
1207
+ "retriesSource",
1208
+ "repeatsSource",
1209
+ "timeoutConfigText",
1210
+ "retriesConfigText",
1211
+ "repeatsConfigText",
1061
1212
  "fixtures",
1062
1213
  "expectCount",
1063
1214
  "expectedAssertions",
@@ -1069,7 +1220,8 @@
1069
1220
  "caseCount",
1070
1221
  "isInLoop",
1071
1222
  "isInCondition",
1072
- "isInFunction"
1223
+ "isInFunction",
1224
+ "projects"
1073
1225
  ],
1074
1226
  "description": "A test, with the effective values the runner applies, including those inherited from its describes."
1075
1227
  },
@@ -1093,14 +1245,32 @@
1093
1245
  ]
1094
1246
  },
1095
1247
  "StateSource": {
1096
- "description": "Where a test's state is set: on the test, on a describe around it, or at file level. Other values may be added in later releases.",
1248
+ "description": "Where a test's state is set: on the test, on a describe around it, at file level, or by a Vitest tag the config defines. Other values may be added in later releases.",
1249
+ "anyOf": [
1250
+ {
1251
+ "type": "string",
1252
+ "enum": [
1253
+ "test",
1254
+ "suite",
1255
+ "file",
1256
+ "config"
1257
+ ]
1258
+ },
1259
+ {
1260
+ "type": "string"
1261
+ }
1262
+ ]
1263
+ },
1264
+ "SettingSource": {
1265
+ "description": "Where a test's run setting comes from: the test, a describe around it, the file, or the config its file uses. Other values may be added in later releases.",
1097
1266
  "anyOf": [
1098
1267
  {
1099
1268
  "type": "string",
1100
1269
  "enum": [
1101
1270
  "test",
1102
1271
  "suite",
1103
- "file"
1272
+ "file",
1273
+ "config"
1104
1274
  ]
1105
1275
  },
1106
1276
  {
@@ -1238,6 +1408,7 @@
1238
1408
  "removed-api",
1239
1409
  "only-in-skipped-describe",
1240
1410
  "config-may-change-state",
1411
+ "undefined-tag",
1241
1412
  "symlink-ignored",
1242
1413
  "invalid-import",
1243
1414
  "globals-not-enabled",
@@ -1252,7 +1423,10 @@
1252
1423
  "approximate-framework-version",
1253
1424
  "test-in-function",
1254
1425
  "empty-title",
1255
- "bench-outside-benchmark-file"
1426
+ "bench-outside-benchmark-file",
1427
+ "config-value-unknown",
1428
+ "file-not-run",
1429
+ "forbidden-only"
1256
1430
  ]
1257
1431
  },
1258
1432
  {