@describe-me/vitest 0.5.4 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/README.md +10 -8
  2. package/dist/browser-profile.d.ts +3 -0
  3. package/dist/browser-profile.js +27 -0
  4. package/dist/build-live.d.ts +26 -0
  5. package/dist/build-live.js +106 -0
  6. package/dist/check-live-enabled.d.ts +3 -0
  7. package/dist/check-live-enabled.js +10 -0
  8. package/dist/collect-live-suites.d.ts +7 -0
  9. package/dist/collect-live-suites.js +25 -0
  10. package/dist/create-live-server.d.ts +29 -0
  11. package/dist/create-live-server.js +55 -0
  12. package/dist/detect-environment.d.ts +4 -0
  13. package/dist/detect-environment.js +4 -0
  14. package/dist/dom-profile.d.ts +3 -0
  15. package/dist/dom-profile.js +38 -0
  16. package/dist/environment-profile.d.ts +41 -0
  17. package/dist/environment-profile.js +1 -0
  18. package/dist/find-live-config-file.d.ts +2 -0
  19. package/dist/find-live-config-file.js +16 -0
  20. package/dist/find-live-test.d.ts +13 -0
  21. package/dist/find-live-test.js +31 -0
  22. package/dist/free-live-port.d.ts +5 -0
  23. package/dist/free-live-port.js +16 -0
  24. package/dist/import-live-vite.d.ts +7 -0
  25. package/dist/import-live-vite.js +46 -0
  26. package/dist/install-live-globals.d.ts +5 -0
  27. package/dist/install-live-globals.js +33 -0
  28. package/dist/is-adapter-module.d.ts +6 -0
  29. package/dist/is-adapter-module.js +11 -0
  30. package/dist/live-arg-label.d.ts +5 -0
  31. package/dist/live-arg-label.js +38 -0
  32. package/dist/live-build-plugin.d.ts +18 -0
  33. package/dist/live-build-plugin.js +89 -0
  34. package/dist/live-each-name.d.ts +6 -0
  35. package/dist/live-each-name.js +146 -0
  36. package/dist/live-entry-source.d.ts +9 -0
  37. package/dist/live-entry-source.js +46 -0
  38. package/dist/live-env.d.ts +6 -0
  39. package/dist/live-env.js +6 -0
  40. package/dist/live-error-detail.d.ts +2 -0
  41. package/dist/live-error-detail.js +16 -0
  42. package/dist/live-export.d.ts +10 -0
  43. package/dist/live-export.js +48 -0
  44. package/dist/live-fixture-props.d.ts +12 -0
  45. package/dist/live-fixture-props.js +87 -0
  46. package/dist/live-fixture-registrations.d.ts +21 -0
  47. package/dist/live-fixture-registrations.js +69 -0
  48. package/dist/live-fixture-values.d.ts +9 -0
  49. package/dist/live-fixture-values.js +125 -0
  50. package/dist/live-format-placeholder.d.ts +6 -0
  51. package/dist/live-format-placeholder.js +70 -0
  52. package/dist/live-hook.d.ts +8 -0
  53. package/dist/live-hook.js +12 -0
  54. package/dist/live-inspect.d.ts +7 -0
  55. package/dist/live-inspect.js +240 -0
  56. package/dist/live-loupe-inspect.d.ts +6 -0
  57. package/dist/live-loupe-inspect.js +330 -0
  58. package/dist/live-mock.d.ts +18 -0
  59. package/dist/live-mock.js +105 -0
  60. package/dist/live-module-ids.d.ts +8 -0
  61. package/dist/live-module-ids.js +8 -0
  62. package/dist/live-optimize-deps.d.ts +13 -0
  63. package/dist/live-optimize-deps.js +67 -0
  64. package/dist/live-page-html.d.ts +5 -0
  65. package/dist/live-page-html.js +21 -0
  66. package/dist/live-page-middleware.d.ts +6 -0
  67. package/dist/live-page-middleware.js +28 -0
  68. package/dist/live-page-settings.d.ts +20 -0
  69. package/dist/live-page-settings.js +24 -0
  70. package/dist/live-plugin-protocol.d.ts +3 -0
  71. package/dist/live-plugin-protocol.js +13 -0
  72. package/dist/live-plugin.d.ts +13 -0
  73. package/dist/live-plugin.js +148 -0
  74. package/dist/live-project-plugins.d.ts +8 -0
  75. package/dist/live-project-plugins.js +23 -0
  76. package/dist/live-registrar.d.ts +10 -0
  77. package/dist/live-registrar.js +174 -0
  78. package/dist/live-registry.d.ts +14 -0
  79. package/dist/live-registry.js +14 -0
  80. package/dist/live-runtime.d.ts +2 -0
  81. package/dist/live-runtime.js +3 -0
  82. package/dist/live-setup-files.d.ts +15 -0
  83. package/dist/live-setup-files.js +56 -0
  84. package/dist/live-shim-source.d.ts +6 -0
  85. package/dist/live-shim-source.js +9 -0
  86. package/dist/live-spy-on.d.ts +7 -0
  87. package/dist/live-spy-on.js +27 -0
  88. package/dist/live-step.d.ts +2 -0
  89. package/dist/live-step.js +4 -0
  90. package/dist/live-string-literal.d.ts +2 -0
  91. package/dist/live-string-literal.js +7 -0
  92. package/dist/live-suite.d.ts +28 -0
  93. package/dist/live-suite.js +18 -0
  94. package/dist/live-test-context.d.ts +24 -0
  95. package/dist/live-test-context.js +26 -0
  96. package/dist/live-test-files-source.d.ts +6 -0
  97. package/dist/live-test-files-source.js +10 -0
  98. package/dist/live-vi.d.ts +7 -0
  99. package/dist/live-vi.js +50 -0
  100. package/dist/live.d.ts +2 -0
  101. package/dist/live.js +3 -0
  102. package/dist/load-live-config.d.ts +9 -0
  103. package/dist/load-live-config.js +25 -0
  104. package/dist/load-profile.d.ts +4 -0
  105. package/dist/load-profile.js +9 -0
  106. package/dist/notice-live-module-mock.d.ts +5 -0
  107. package/dist/notice-live-module-mock.js +25 -0
  108. package/dist/package-name-of.d.ts +5 -0
  109. package/dist/package-name-of.js +7 -0
  110. package/dist/plugin.d.ts +11 -2
  111. package/dist/plugin.js +40 -86
  112. package/dist/read-live-browser-names.d.ts +5 -0
  113. package/dist/read-live-browser-names.js +19 -0
  114. package/dist/read-live-params.d.ts +18 -0
  115. package/dist/read-live-params.js +63 -0
  116. package/dist/read-live-vitest-major.d.ts +6 -0
  117. package/dist/read-live-vitest-major.js +20 -0
  118. package/dist/read-live-vitest-names.d.ts +5 -0
  119. package/dist/read-live-vitest-names.js +40 -0
  120. package/dist/register-recording-hooks.d.ts +2 -1
  121. package/dist/register-recording-hooks.js +8 -7
  122. package/dist/report-live-errors.d.ts +5 -0
  123. package/dist/report-live-errors.js +24 -0
  124. package/dist/reporter.d.ts +3 -0
  125. package/dist/reporter.js +9 -0
  126. package/dist/run-live-test.d.ts +10 -0
  127. package/dist/run-live-test.js +118 -0
  128. package/dist/set-up-project.d.ts +12 -0
  129. package/dist/set-up-project.js +39 -0
  130. package/dist/start-live-page.d.ts +29 -0
  131. package/dist/start-live-page.js +62 -0
  132. package/dist/validate-live-option.d.ts +11 -0
  133. package/dist/validate-live-option.js +29 -0
  134. package/package.json +11 -3
@@ -7,25 +7,26 @@ import { fileFilter } from './file-filter.js';
7
7
  * reporter via task.meta, and only then let adapters unmount. Teardown runs
8
8
  * for unrecorded tests too, because Testing Library's auto-cleanup is off in
9
9
  * jsdom. Concurrent tests would share the one recorder, so they are not
10
- * recorded.
10
+ * recorded. The hooks destructure the context, because Vitest rejects any
11
+ * other first parameter in a test that has `test.extend` fixtures.
11
12
  */
12
13
  export function registerRecordingHooks(options, aroundWait) {
13
14
  const isRecordedFile = fileFilter(options);
14
15
  let warnedConcurrent = false;
15
16
  recorder.configure({ renderFrame: options.renderFrame, aroundWait });
16
- beforeEach((context) => {
17
- if (context.task.concurrent) {
17
+ beforeEach(({ task }) => {
18
+ if (task.concurrent) {
18
19
  if (!warnedConcurrent) {
19
20
  warnedConcurrent = true;
20
- console.warn(`describe-me: concurrent tests are not recorded (${context.task.file.name})`);
21
+ console.warn(`describe-me: concurrent tests are not recorded (${task.file.name})`);
21
22
  }
22
23
  return;
23
24
  }
24
- if (isRecordedFile(context.task.file.name)) {
25
+ if (isRecordedFile(task.file.name)) {
25
26
  recorder.begin();
26
27
  }
27
28
  });
28
- afterEach(async (context) => {
29
+ afterEach(async ({ task }) => {
29
30
  try {
30
31
  if (recorder.isActive) {
31
32
  const tookRender = await recorder.flush();
@@ -36,7 +37,7 @@ export function registerRecordingHooks(options, aroundWait) {
36
37
  const record = recorder.end();
37
38
  if (record.frames.length > 0) {
38
39
  ;
39
- context.task.meta[META_KEY] = record;
40
+ task.meta[META_KEY] = record;
40
41
  }
41
42
  }
42
43
  }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Reports what the live component throws while someone uses it. The stop
3
+ * signal is no error, so it is neither reported nor logged.
4
+ */
5
+ export declare function reportLiveErrors(): void;
@@ -0,0 +1,24 @@
1
+ import { isLiveStop, postLiveMessage } from '@describe-me/core/live';
2
+ function messageOf(reason) {
3
+ return reason instanceof Error ? String(reason) : `Unhandled rejection: ${String(reason)}`;
4
+ }
5
+ /**
6
+ * Reports what the live component throws while someone uses it. The stop
7
+ * signal is no error, so it is neither reported nor logged.
8
+ */
9
+ export function reportLiveErrors() {
10
+ window.addEventListener('error', (event) => {
11
+ if (isLiveStop(event.error)) {
12
+ event.preventDefault();
13
+ return;
14
+ }
15
+ postLiveMessage({ type: 'error', message: event.message || String(event.error) });
16
+ });
17
+ window.addEventListener('unhandledrejection', (event) => {
18
+ if (isLiveStop(event.reason)) {
19
+ event.preventDefault();
20
+ return;
21
+ }
22
+ postLiveMessage({ type: 'error', message: messageOf(event.reason) });
23
+ });
24
+ }
@@ -30,6 +30,7 @@ export default class DescribeMeReporter implements Reporter {
30
30
  private readonly generator;
31
31
  private readonly setupWarnings;
32
32
  private root;
33
+ private configFile;
33
34
  private outDir;
34
35
  private snapshots;
35
36
  private assets;
@@ -60,6 +61,8 @@ export default class DescribeMeReporter implements Reporter {
60
61
  onTestRunEnd(): Promise<void>;
61
62
  /** A diagnostic must never break the manifest, so a failing audit writes empty lists. */
62
63
  private auditFonts;
64
+ /** The config file of the run, relative to the root, so the live preview loads the same one. */
65
+ private relativeConfigFile;
63
66
  /** Module ids use the platform's separators. Globs expect posix ones. */
64
67
  private isRecordedModule;
65
68
  /** Decided before any snapshot is written, so a dropped test writes no file. */
package/dist/reporter.js CHANGED
@@ -33,6 +33,7 @@ export default class DescribeMeReporter {
33
33
  generator;
34
34
  setupWarnings;
35
35
  root = process.cwd();
36
+ configFile;
36
37
  outDir = '';
37
38
  snapshots;
38
39
  assets;
@@ -51,6 +52,8 @@ export default class DescribeMeReporter {
51
52
  }
52
53
  onInit(vitest) {
53
54
  this.root = vitest.config.root;
55
+ // A script that calls the reporter directly passes no Vite server, so there is no file to name.
56
+ this.configFile = this.relativeConfigFile(vitest.vite?.config.configFile);
54
57
  this.outDir = resolve(this.root, this.outDirOption);
55
58
  this.snapshots = new SnapshotStore(this.outDir);
56
59
  this.assets = new AssetStore(this.outDir, this.root);
@@ -150,6 +153,8 @@ export default class DescribeMeReporter {
150
153
  generator: this.generator,
151
154
  generatedAt: new Date().toISOString(),
152
155
  root: this.root,
156
+ // Always this run's, never the previous manifest's. `undefined` leaves it out of the JSON.
157
+ configFile: this.configFile,
153
158
  modules,
154
159
  components,
155
160
  assetsMissing,
@@ -183,6 +188,10 @@ export default class DescribeMeReporter {
183
188
  manifest.remoteStylesheets = [];
184
189
  }
185
190
  }
191
+ /** The config file of the run, relative to the root, so the live preview loads the same one. */
192
+ relativeConfigFile(configFile) {
193
+ return configFile === undefined ? undefined : toPosix(relative(this.root, configFile));
194
+ }
186
195
  /** Module ids use the platform's separators. Globs expect posix ones. */
187
196
  isRecordedModule(id) {
188
197
  return this.isRecordedFile(toPosix(id));
@@ -0,0 +1,10 @@
1
+ import type { LiveTestMatch } from './find-live-test.js';
2
+ /**
3
+ * Runs a test's arrange phase the way Vitest runs a test: for each suite from
4
+ * the root down, its `aroundAll` hooks around its `beforeAll` hooks, then the
5
+ * `aroundEach` hooks of the path around the `beforeEach` hooks and the body,
6
+ * each test-scope callback after the fixtures it uses. It stops when the
7
+ * first render throws the stop signal, so no teardown runs. Unless the render
8
+ * reported `mounted`, it reports why the page shows nothing.
9
+ */
10
+ export declare function runLiveTest(match: LiveTestMatch, testTimeout: number): Promise<void>;
@@ -0,0 +1,118 @@
1
+ import { isLiveStop, liveStatus, reportLiveStatus } from '@describe-me/core/live';
2
+ import { liveErrorDetail } from './live-error-detail.js';
3
+ import { liveFixtureValues } from './live-fixture-values.js';
4
+ import { liveTestContext } from './live-test-context.js';
5
+ const RUN_FUNCTIONS = { aroundAll: 'runSuite', aroundEach: 'runTest' };
6
+ /** The test's own timeout, or the nearest suite's, which Vitest passes on to its tests. */
7
+ function timeoutOf(match, testTimeout) {
8
+ const innermostFirst = [...match.suites].reverse();
9
+ const suiteTimeout = innermostFirst.find((suite) => suite.timeout !== undefined)?.timeout;
10
+ return match.test.timeout ?? suiteTimeout ?? testTimeout;
11
+ }
12
+ /** Computes the fixtures that a test-scope callback uses, then calls it. */
13
+ async function withFixtures(arrange, use, call) {
14
+ await liveFixtureValues(arrange.match.test.fixtures, use, arrange.context, arrange.computed);
15
+ await call();
16
+ }
17
+ /**
18
+ * Runs one around hook. Its run function starts `inner` and returns a
19
+ * promise that never settles, so the hook's teardown never runs. Settles as
20
+ * `inner` does, or rejects when the hook fails or settles without calling
21
+ * its run function.
22
+ */
23
+ function around(kind, call, inner) {
24
+ return new Promise((resolve, reject) => {
25
+ let started = false;
26
+ const run = () => {
27
+ if (!started) {
28
+ started = true;
29
+ inner().then(resolve, reject);
30
+ }
31
+ return new Promise(() => undefined);
32
+ };
33
+ new Promise((settle) => settle(call(run))).then(() => {
34
+ if (!started) {
35
+ reject(new Error(`${kind} did not call ${RUN_FUNCTIONS[kind]}()`));
36
+ }
37
+ }, reject);
38
+ });
39
+ }
40
+ /** The around hooks nested around `inner`, the first one outermost, as in Vitest. */
41
+ function nested(kind, calls, inner) {
42
+ return calls.reduceRight((next, call) => () => around(kind, call, next), inner);
43
+ }
44
+ /** The `aroundEach` hooks of every suite on the path, then the `beforeEach` hooks and the body. */
45
+ function testStep(arrange) {
46
+ const { match, context } = arrange;
47
+ const inner = async () => {
48
+ for (const suite of match.suites) {
49
+ for (const hook of suite.hooks.beforeEach) {
50
+ await withFixtures(arrange, { callback: hook, index: 0 }, () => hook(context));
51
+ }
52
+ }
53
+ const { body, uses } = match.test;
54
+ if (body !== undefined) {
55
+ await withFixtures(arrange, uses, () => body(context));
56
+ }
57
+ };
58
+ const calls = match.suites.flatMap((suite) => suite.hooks.aroundEach.map((hook) => (run) => withFixtures(arrange, { callback: hook, index: 1 }, () => hook(run, context, suite))));
59
+ return nested('aroundEach', calls, inner);
60
+ }
61
+ /** One suite on the path: its `aroundAll` hooks around its `beforeAll` hooks and everything below. */
62
+ function suiteStep(arrange, depth) {
63
+ const { match, context } = arrange;
64
+ const suite = match.suites[depth];
65
+ if (suite === undefined) {
66
+ return testStep(arrange);
67
+ }
68
+ const inner = async () => {
69
+ for (const hook of suite.hooks.beforeAll) {
70
+ await hook(context);
71
+ }
72
+ await suiteStep(arrange, depth + 1)();
73
+ };
74
+ const calls = suite.hooks.aroundAll.map((hook) => (run) => hook(run, context, suite));
75
+ return nested('aroundAll', calls, inner);
76
+ }
77
+ function withTimeout(run, timeout, onTimeout) {
78
+ return new Promise((resolve) => {
79
+ const timer = setTimeout(() => {
80
+ onTimeout();
81
+ resolve({ kind: 'timed-out' });
82
+ }, timeout);
83
+ run.then(() => {
84
+ clearTimeout(timer);
85
+ resolve({ kind: 'ended' });
86
+ }, (error) => {
87
+ clearTimeout(timer);
88
+ resolve(isLiveStop(error) ? { kind: 'ended' } : { kind: 'failed', error });
89
+ });
90
+ });
91
+ }
92
+ /**
93
+ * Runs a test's arrange phase the way Vitest runs a test: for each suite from
94
+ * the root down, its `aroundAll` hooks around its `beforeAll` hooks, then the
95
+ * `aroundEach` hooks of the path around the `beforeEach` hooks and the body,
96
+ * each test-scope callback after the fixtures it uses. It stops when the
97
+ * first render throws the stop signal, so no teardown runs. Unless the render
98
+ * reported `mounted`, it reports why the page shows nothing.
99
+ */
100
+ export async function runLiveTest(match, testTimeout) {
101
+ const timeout = timeoutOf(match, testTimeout);
102
+ const controller = new AbortController();
103
+ const context = liveTestContext(match, controller.signal);
104
+ const arrange = { match, context, computed: new Set() };
105
+ const outcome = await withTimeout(suiteStep(arrange, 0)(), timeout, () => controller.abort());
106
+ if (liveStatus() !== null) {
107
+ return;
108
+ }
109
+ if (outcome.kind === 'timed-out') {
110
+ reportLiveStatus('failed', `did not reach its first render within ${timeout} ms`);
111
+ return;
112
+ }
113
+ if (outcome.kind === 'failed') {
114
+ reportLiveStatus('failed', liveErrorDetail(outcome.error));
115
+ return;
116
+ }
117
+ reportLiveStatus('no-render');
118
+ }
@@ -0,0 +1,12 @@
1
+ import type { TestProject } from 'vitest/node';
2
+ import type { EnvironmentProfile } from './environment-profile.js';
3
+ /**
4
+ * Gives a project the profile's setup file, when it lacks it, and the
5
+ * profile's variables. In Vitest 5 a project that shares the root's Vite
6
+ * server inherits the root config as it was before the plugin's `config`
7
+ * hook, so it gets the render redirect but not the setup file, and records
8
+ * nothing. The file goes last, as in the root config. Only projects with the
9
+ * setup file get the variables, so one without describe-me keeps Testing
10
+ * Library's own cleanup.
11
+ */
12
+ export declare function setUpProject(project: TestProject, profile: EnvironmentProfile): void;
@@ -0,0 +1,39 @@
1
+ import { realpathSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import { join } from 'node:path';
4
+ function resolveFrom(root, specifier) {
5
+ try {
6
+ return createRequire(join(root, 'package.json')).resolve(specifier);
7
+ }
8
+ catch {
9
+ return null;
10
+ }
11
+ }
12
+ function realPathOf(file) {
13
+ try {
14
+ return realpathSync(file);
15
+ }
16
+ catch {
17
+ return file;
18
+ }
19
+ }
20
+ /**
21
+ * Gives a project the profile's setup file, when it lacks it, and the
22
+ * profile's variables. In Vitest 5 a project that shares the root's Vite
23
+ * server inherits the root config as it was before the plugin's `config`
24
+ * hook, so it gets the render redirect but not the setup file, and records
25
+ * nothing. The file goes last, as in the root config. Only projects with the
26
+ * setup file get the variables, so one without describe-me keeps Testing
27
+ * Library's own cleanup.
28
+ */
29
+ export function setUpProject(project, profile) {
30
+ const file = resolveFrom(project.config.root, profile.setupFile);
31
+ if (file === null) {
32
+ return;
33
+ }
34
+ const real = realPathOf(file);
35
+ if (!project.config.setupFiles.some((entry) => realPathOf(entry) === real)) {
36
+ project.config.setupFiles.push(file);
37
+ }
38
+ project.config.env = { ...project.config.env, ...profile.env };
39
+ }
@@ -0,0 +1,29 @@
1
+ /** What the plugin's generated entry hands the live page. */
2
+ export interface LivePageOptions {
3
+ /**
4
+ * Imports a test file by its path from the root. The dev server's entry
5
+ * imports it from the page's own origin, and a static build can pass a map
6
+ * of lazy imports.
7
+ */
8
+ importTest(file: string): Promise<unknown>;
9
+ /** `test.globals` of the project's config. */
10
+ globals: boolean;
11
+ /** How long the arrange phase may take, in ms, when the test sets no timeout of its own. */
12
+ testTimeout: number;
13
+ /**
14
+ * The setup files to import before the test file, in order: each one's
15
+ * name for messages and its import. Default: none.
16
+ */
17
+ setupFiles?: {
18
+ file: string;
19
+ load(): Promise<unknown>;
20
+ }[];
21
+ /** The major version of the project's Vitest, whose rules name `each` and `for` rows. Default: 5. */
22
+ vitestMajor?: number;
23
+ }
24
+ /**
25
+ * Runs the live page: imports the setup files and then the test file that
26
+ * the URL names, with the shims in place, runs the test's arrange phase and
27
+ * stops at its first render. The component stays mounted and live.
28
+ */
29
+ export declare function startLivePage(options: LivePageOptions): Promise<void>;
@@ -0,0 +1,62 @@
1
+ import { reportLiveStatus } from '@describe-me/core/live';
2
+ import { collectLiveSuites } from './collect-live-suites.js';
3
+ import { findLiveTest } from './find-live-test.js';
4
+ import { installLiveGlobals } from './install-live-globals.js';
5
+ import { liveErrorDetail } from './live-error-detail.js';
6
+ import { liveRegistry } from './live-registry.js';
7
+ import { readLiveParams } from './read-live-params.js';
8
+ import { reportLiveErrors } from './report-live-errors.js';
9
+ import { runLiveTest } from './run-live-test.js';
10
+ /**
11
+ * Imports the setup files one at a time, as one of the orders Vitest's
12
+ * parallel default can produce. Reports `setup-failed` and returns false at
13
+ * the first one that does not load, link or run.
14
+ */
15
+ async function importSetupFiles(setupFiles) {
16
+ for (const setupFile of setupFiles ?? []) {
17
+ try {
18
+ await setupFile.load();
19
+ }
20
+ catch (error) {
21
+ reportLiveStatus('setup-failed', `${setupFile.file}: ${liveErrorDetail(error)}`);
22
+ return false;
23
+ }
24
+ }
25
+ return true;
26
+ }
27
+ /**
28
+ * Runs the live page: imports the setup files and then the test file that
29
+ * the URL names, with the shims in place, runs the test's arrange phase and
30
+ * stops at its first render. The component stays mounted and live.
31
+ */
32
+ export async function startLivePage(options) {
33
+ if (options.globals) {
34
+ installLiveGlobals();
35
+ }
36
+ reportLiveErrors();
37
+ // Names form while tests register, so the rules must be known before any import.
38
+ liveRegistry().vitestMajor = options.vitestMajor ?? 5;
39
+ const params = readLiveParams(window.location);
40
+ if ('problem' in params) {
41
+ reportLiveStatus('not-found', params.problem);
42
+ return;
43
+ }
44
+ if (!(await importSetupFiles(options.setupFiles))) {
45
+ return;
46
+ }
47
+ try {
48
+ await options.importTest(params.file);
49
+ await collectLiveSuites();
50
+ }
51
+ catch (error) {
52
+ reportLiveStatus('import-failed', liveErrorDetail(error));
53
+ return;
54
+ }
55
+ const match = findLiveTest(params.path, params.occurrence);
56
+ if (match === null) {
57
+ const name = params.path.join(' > ');
58
+ reportLiveStatus('not-found', `no test ${name} (#${params.occurrence}) in ${params.file}`);
59
+ return;
60
+ }
61
+ await runLiveTest(match, options.testTimeout);
62
+ }
@@ -0,0 +1,11 @@
1
+ /** The `live` option of `describeMe()`, checked. */
2
+ export interface LiveOption {
3
+ enabled: boolean;
4
+ /** `live.setupFiles` as a list, or undefined when the option leaves `test.setupFiles` in place. */
5
+ setupFiles?: string[];
6
+ }
7
+ /**
8
+ * Checks the `live` option when the live preview starts, so a typo fails
9
+ * there instead of falling back to `test.setupFiles` without a word.
10
+ */
11
+ export declare function validateLiveOption(live: unknown): LiveOption;
@@ -0,0 +1,29 @@
1
+ function isPlainObject(value) {
2
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
3
+ }
4
+ /**
5
+ * Checks the `live` option when the live preview starts, so a typo fails
6
+ * there instead of falling back to `test.setupFiles` without a word.
7
+ */
8
+ export function validateLiveOption(live) {
9
+ if (live === undefined) {
10
+ return { enabled: true };
11
+ }
12
+ if (live === false) {
13
+ return { enabled: false };
14
+ }
15
+ if (!isPlainObject(live)) {
16
+ throw new Error('describe-me: live must be false or { setupFiles }');
17
+ }
18
+ const { setupFiles } = live;
19
+ if (setupFiles === undefined) {
20
+ return { enabled: true };
21
+ }
22
+ if (typeof setupFiles === 'string') {
23
+ return { enabled: true, setupFiles: [setupFiles] };
24
+ }
25
+ if (Array.isArray(setupFiles) && setupFiles.every((entry) => typeof entry === 'string')) {
26
+ return { enabled: true, setupFiles: [...setupFiles] };
27
+ }
28
+ throw new Error('describe-me: live.setupFiles must be a string or an array of strings');
29
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@describe-me/vitest",
3
- "version": "0.5.4",
3
+ "version": "0.6.1",
4
4
  "description": "Living component documentation from your Vitest tests: the Vitest plugin, setup files and reporter of describe-me",
5
5
  "keywords": [
6
6
  "vitest",
@@ -49,6 +49,14 @@
49
49
  "./plugin": {
50
50
  "types": "./dist/plugin.d.ts",
51
51
  "default": "./dist/plugin.js"
52
+ },
53
+ "./live": {
54
+ "types": "./dist/live.d.ts",
55
+ "default": "./dist/live.js"
56
+ },
57
+ "./live-runtime": {
58
+ "types": "./dist/live-runtime.d.ts",
59
+ "default": "./dist/live-runtime.js"
52
60
  }
53
61
  },
54
62
  "files": [
@@ -63,13 +71,13 @@
63
71
  "dependencies": {
64
72
  "picomatch": "^4.0.3",
65
73
  "std-env": "^4.0.0",
66
- "@describe-me/core": "^0.5.4"
74
+ "@describe-me/core": "^0.6.1"
67
75
  },
68
76
  "peerDependencies": {
69
77
  "vitest": "^4.0.0 || ^5.0.0",
70
78
  "typescript": "^5.0.0 || ^6.0.0",
71
79
  "@testing-library/user-event": "^14.0.0",
72
- "describe-me": "^0.5.4"
80
+ "describe-me": "^0.6.1"
73
81
  },
74
82
  "peerDependenciesMeta": {
75
83
  "typescript": {