@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
@@ -0,0 +1,13 @@
1
+ import { LIVE_PROTOCOL } from '@describe-me/core/types';
2
+ /** The live protocol of the project's describeMe() plugin, which must be this package's. */
3
+ export function livePluginProtocol(plugins, configName) {
4
+ const plugin = plugins.find((candidate) => candidate.name === 'describe-me');
5
+ if (plugin === undefined) {
6
+ throw new Error(`describe-me: ${configName} has no describeMe() plugin`);
7
+ }
8
+ const protocol = plugin.api?.live?.protocol;
9
+ if (protocol !== LIVE_PROTOCOL) {
10
+ throw new Error(`describe-me: all describe-me packages must be on the same version. The describeMe() plugin in ${configName} has live protocol ${String(protocol ?? 'none')}, expected ${LIVE_PROTOCOL}. Install @describe-me/core, @describe-me/react, @describe-me/vitest and describe-me at one version.`);
11
+ }
12
+ return protocol;
13
+ }
@@ -0,0 +1,13 @@
1
+ import type { ViteUserConfig } from 'vitest/config';
2
+ import type { DescribeMeOptions } from './plugin.js';
3
+ type VitePlugin = NonNullable<ViteUserConfig['plugins']>[number];
4
+ /**
5
+ * `describeMe()` while `createLiveServer()` or `buildLive()` loads the
6
+ * project's config. It serves the live page, or gives a build `live.html` as
7
+ * its input, and swaps `vitest`, `vitest/browser`, `@describe-me/vitest` and
8
+ * the render module for the live page's shims. It adds no setup file,
9
+ * reporter or recording redirect, and ignores `enabled`. With `live: false`
10
+ * it does nothing, and `createLiveServer()` and `buildLive()` stop.
11
+ */
12
+ export declare function livePlugin(options: DescribeMeOptions): VitePlugin;
13
+ export {};
@@ -0,0 +1,148 @@
1
+ import { join, resolve } from 'node:path';
2
+ import { LIVE_PROTOCOL } from '@describe-me/core/types';
3
+ import { adapterPackageRoot } from './adapter-package-root.js';
4
+ import { detectEnvironment } from './detect-environment.js';
5
+ import { isAdapterModule } from './is-adapter-module.js';
6
+ import { liveEntrySource } from './live-entry-source.js';
7
+ import { LIVE_MODULE_IDS } from './live-module-ids.js';
8
+ import { liveOptimizeDeps } from './live-optimize-deps.js';
9
+ import { livePageHtml } from './live-page-html.js';
10
+ import { livePageMiddleware } from './live-page-middleware.js';
11
+ import { livePageSettings } from './live-page-settings.js';
12
+ import { liveShimSource } from './live-shim-source.js';
13
+ import { loadProfile } from './load-profile.js';
14
+ import { packageNameOf } from './package-name-of.js';
15
+ import { validateLiveOption } from './validate-live-option.js';
16
+ /** What the live page imports instead of each module, by the specifier tests use. */
17
+ const SHIMS = new Map([
18
+ ['vitest', LIVE_MODULE_IDS.vitest],
19
+ ['vitest/browser', LIVE_MODULE_IDS.vitestBrowser],
20
+ ['@vitest/browser/context', LIVE_MODULE_IDS.vitestBrowser],
21
+ ['@describe-me/vitest', LIVE_MODULE_IDS.describeMeVitest],
22
+ ]);
23
+ const GENERATED = new Set(Object.values(LIVE_MODULE_IDS));
24
+ /** Where the live page of a build imports its entry from. Only a build resolves it. */
25
+ const BUILD_ENTRY_SRC = '/@describe-me-live/entry';
26
+ /** The source of a generated module. */
27
+ function generatedSource(id, settings, command) {
28
+ if (id === LIVE_MODULE_IDS.entry) {
29
+ return liveEntrySource(settings, command);
30
+ }
31
+ if (id === LIVE_MODULE_IDS.vitest) {
32
+ return liveShimSource('vitest', settings.vitestNames);
33
+ }
34
+ if (id === LIVE_MODULE_IDS.vitestBrowser) {
35
+ return liveShimSource('vitest/browser', settings.browserNames);
36
+ }
37
+ if (id === LIVE_MODULE_IDS.describeMeVitest) {
38
+ return liveShimSource('@describe-me/vitest', ['step', 'recorder']);
39
+ }
40
+ return null;
41
+ }
42
+ /**
43
+ * All the config that live mode adds: the dependencies to bundle up front and
44
+ * `test.alias`, which Vitest's own `config` hook moves to `resolve.alias`.
45
+ */
46
+ function liveConfig(userConfig, root, outDir, live, setupFiles) {
47
+ const config = {
48
+ optimizeDeps: liveOptimizeDeps(root, outDir, live, setupFiles),
49
+ };
50
+ const alias = userConfig.test?.alias;
51
+ if (alias !== undefined) {
52
+ config.resolve = { alias };
53
+ }
54
+ return config;
55
+ }
56
+ /**
57
+ * `describeMe()` while `createLiveServer()` or `buildLive()` loads the
58
+ * project's config. It serves the live page, or gives a build `live.html` as
59
+ * its input, and swaps `vitest`, `vitest/browser`, `@describe-me/vitest` and
60
+ * the render module for the live page's shims. It adds no setup file,
61
+ * reporter or recording redirect, and ignores `enabled`. With `live: false`
62
+ * it does nothing, and `createLiveServer()` and `buildLive()` stop.
63
+ */
64
+ export function livePlugin(options) {
65
+ const { framework = 'react' } = options;
66
+ const enabled = options.live !== false;
67
+ // Set by `config()`, which Vite runs before `configResolved`, `resolveId` and `load`.
68
+ let renderModule = null;
69
+ let settings = null;
70
+ // Where the installed live module lives. Its own import of the original stays unredirected.
71
+ let adapterRoot = null;
72
+ let command = 'serve';
73
+ // The id of `live.html` in a build, which the bundler takes as its input.
74
+ let pageId = null;
75
+ return {
76
+ name: 'describe-me',
77
+ enforce: 'pre',
78
+ api: { live: { protocol: LIVE_PROTOCOL, enabled } },
79
+ async config(userConfig) {
80
+ const live = validateLiveOption(options.live);
81
+ if (!live.enabled) {
82
+ return {};
83
+ }
84
+ const root = resolve(userConfig.root ?? process.cwd());
85
+ const environment = options.environment ?? detectEnvironment(userConfig);
86
+ const profile = await loadProfile(environment);
87
+ renderModule = profile.renderModules[framework];
88
+ settings = await livePageSettings(userConfig, root, live);
89
+ return liveConfig(userConfig, root, options.outDir, renderModule.live, settings.setupFiles);
90
+ },
91
+ configResolved(config) {
92
+ command = config.command;
93
+ if (command === 'build') {
94
+ pageId = join(config.root, 'live.html');
95
+ }
96
+ if (renderModule !== null) {
97
+ adapterRoot = adapterPackageRoot(config.root, packageNameOf(renderModule.live));
98
+ }
99
+ },
100
+ async resolveId(source, importer) {
101
+ if (!enabled) {
102
+ return null;
103
+ }
104
+ if (GENERATED.has(source)) {
105
+ return source;
106
+ }
107
+ if (source === pageId) {
108
+ return source;
109
+ }
110
+ if (command === 'build' && source === BUILD_ENTRY_SRC) {
111
+ return LIVE_MODULE_IDS.entry;
112
+ }
113
+ const shim = SHIMS.get(source);
114
+ if (shim !== undefined) {
115
+ return shim;
116
+ }
117
+ if (renderModule === null || source !== renderModule.original) {
118
+ return null;
119
+ }
120
+ const fromAdapter = importer !== undefined &&
121
+ isAdapterModule(importer, adapterRoot, packageNameOf(renderModule.live));
122
+ if (fromAdapter) {
123
+ return null;
124
+ }
125
+ const resolved = await this.resolve(renderModule.live, importer, { skipSelf: true });
126
+ if (!resolved) {
127
+ throw new Error(`describe-me: cannot resolve ${renderModule.live} (the live preview's stand-in for ${renderModule.original}). Install ${packageNameOf(renderModule.live)}.`);
128
+ }
129
+ return resolved;
130
+ },
131
+ load(id) {
132
+ if (settings === null) {
133
+ return null;
134
+ }
135
+ // Only this plugin knows the preview head, so it owns the page.
136
+ if (id === pageId) {
137
+ return livePageHtml(options.previewHead, BUILD_ENTRY_SRC);
138
+ }
139
+ return generatedSource(id, settings, command);
140
+ },
141
+ configureServer(server) {
142
+ if (!enabled) {
143
+ return;
144
+ }
145
+ server.middlewares.use(livePageMiddleware(server, options.previewHead));
146
+ },
147
+ };
148
+ }
@@ -0,0 +1,8 @@
1
+ import type { Vite } from 'vitest/node';
2
+ /**
3
+ * The project's plugins for the live build, flattened the way Vite flattens
4
+ * them: nested arrays and promises, without falsy entries. Those whose
5
+ * `apply` is `'build'` are left out, because the dev server never runs them.
6
+ * Vite itself drops those whose `apply` is `'serve'`.
7
+ */
8
+ export declare function liveProjectPlugins(plugins: Vite.PluginOption[] | undefined): Promise<Vite.PluginOption[]>;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The project's plugins for the live build, flattened the way Vite flattens
3
+ * them: nested arrays and promises, without falsy entries. Those whose
4
+ * `apply` is `'build'` are left out, because the dev server never runs them.
5
+ * Vite itself drops those whose `apply` is `'serve'`.
6
+ */
7
+ export async function liveProjectPlugins(plugins) {
8
+ const flat = [];
9
+ for (const option of plugins ?? []) {
10
+ const plugin = await option;
11
+ if (!plugin) {
12
+ continue;
13
+ }
14
+ if (Array.isArray(plugin)) {
15
+ flat.push(...(await liveProjectPlugins(plugin)));
16
+ continue;
17
+ }
18
+ if (!('apply' in plugin) || plugin.apply !== 'build') {
19
+ flat.push(plugin);
20
+ }
21
+ }
22
+ return flat;
23
+ }
@@ -0,0 +1,10 @@
1
+ type LiveKind = 'suite' | 'test';
2
+ /**
3
+ * `describe`, `suite`, `it` or `test` on the live page. A call registers
4
+ * whatever modifiers come before it, because a skipped test keeps its entry
5
+ * in the manifest. `test.extend(…)` returns a chain whose tests keep the
6
+ * merged fixtures. Properties the page does not know, such as `override`,
7
+ * are inert.
8
+ */
9
+ export declare function liveRegistrar(kind: LiveKind): unknown;
10
+ export {};
@@ -0,0 +1,174 @@
1
+ import { liveInert } from '@describe-me/core/live';
2
+ import { liveEachName } from './live-each-name.js';
3
+ import { liveFixtureRegistrations } from './live-fixture-registrations.js';
4
+ import { liveHook } from './live-hook.js';
5
+ import { liveRegistry } from './live-registry.js';
6
+ import { liveSuite } from './live-suite.js';
7
+ /** Modifiers that change how Vitest runs a suite or a test, not whether it is registered. */
8
+ const RUN_MODIFIERS = {
9
+ suite: new Set(['skip', 'only', 'concurrent', 'sequential', 'shuffle']),
10
+ test: new Set(['skip', 'only', 'concurrent', 'sequential', 'fails']),
11
+ };
12
+ /** The hooks that Vitest's `test` carries as members, so `test.beforeEach(…)` works. */
13
+ const HOOKS = new Set([
14
+ 'beforeAll',
15
+ 'beforeEach',
16
+ 'afterEach',
17
+ 'afterAll',
18
+ 'aroundEach',
19
+ 'aroundAll',
20
+ ]);
21
+ const NO_FIXTURES = new Map();
22
+ function nameOf(name) {
23
+ if (typeof name === 'string') {
24
+ return name;
25
+ }
26
+ if (typeof name === 'function') {
27
+ return name.name || '<anonymous>';
28
+ }
29
+ return String(name);
30
+ }
31
+ function timeoutOf(options) {
32
+ if (typeof options === 'number') {
33
+ return options;
34
+ }
35
+ if (typeof options !== 'object' || options === null) {
36
+ return undefined;
37
+ }
38
+ const { timeout } = options;
39
+ return typeof timeout === 'number' ? timeout : undefined;
40
+ }
41
+ /** The body and timeout of `(name, fn)`, `(name, options, fn)` or `(name, fn, options | timeout)`. */
42
+ function bodyAndTimeout(second, third) {
43
+ if (typeof second === 'function') {
44
+ return { body: second, timeout: timeoutOf(third) };
45
+ }
46
+ return {
47
+ body: typeof third === 'function' ? third : undefined,
48
+ timeout: timeoutOf(second),
49
+ };
50
+ }
51
+ function add(chain, name, body, timeout, uses) {
52
+ const suite = liveRegistry().current;
53
+ const kept = chain.todo ? undefined : body;
54
+ if (chain.kind === 'suite') {
55
+ suite.children.push(liveSuite(name, kept, timeout));
56
+ return;
57
+ }
58
+ suite.children.push({ kind: 'test', name, body: kept, timeout, fixtures: chain.fixtures, uses });
59
+ }
60
+ /** Like Vitest's `toArray`: no value is no item. */
61
+ function toArray(row) {
62
+ if (row === undefined || row === null) {
63
+ return [];
64
+ }
65
+ return Array.isArray(row) ? row : [row];
66
+ }
67
+ function rowName(name, items, index) {
68
+ return liveEachName(nameOf(name), items, index, liveRegistry().vitestMajor);
69
+ }
70
+ /** The rows of a table, or of a tagged template such as `it.each\`a | b\``, as Vitest reads them. */
71
+ function rowsOf(table, values) {
72
+ if (!Array.isArray(table)) {
73
+ return [];
74
+ }
75
+ if (values.length === 0) {
76
+ return table;
77
+ }
78
+ const header = table.join('').trim().replace(/ /g, '').split('\n')[0].split('|');
79
+ const rows = [];
80
+ for (let i = 0; i + header.length <= values.length; i += header.length) {
81
+ rows.push(Object.fromEntries(header.map((key, j) => [key, values[i + j]])));
82
+ }
83
+ return rows;
84
+ }
85
+ /**
86
+ * `each(table)`: one suite or test per row. Like Vitest, it spreads rows when
87
+ * every row is an array, and the body gets no context.
88
+ */
89
+ function each(chain) {
90
+ return (table, ...values) => (name, second, third) => {
91
+ const rows = rowsOf(table, values);
92
+ const spread = rows.every((row) => Array.isArray(row));
93
+ const { body, timeout } = bodyAndTimeout(second, third);
94
+ rows.forEach((row, index) => {
95
+ const items = Array.isArray(row) ? row : [row];
96
+ const args = spread ? items : [row];
97
+ const rowBody = body === undefined ? undefined : () => body(...args);
98
+ add(chain, rowName(name, items, index), rowBody, timeout, null);
99
+ });
100
+ };
101
+ }
102
+ /**
103
+ * `for(table)`: one suite or test per row, whose body gets the row and the
104
+ * test context. The body names its fixtures in its second parameter.
105
+ */
106
+ function forEachRow(chain) {
107
+ return (table, ...values) => (name, second, third) => {
108
+ const { body, timeout } = bodyAndTimeout(second, third);
109
+ const uses = body === undefined ? null : { callback: body, index: 1 };
110
+ rowsOf(table, values).forEach((row, index) => {
111
+ const rowBody = body === undefined ? undefined : (context) => body(row, context);
112
+ add(chain, rowName(name, toArray(row), index), rowBody, timeout, uses);
113
+ });
114
+ };
115
+ }
116
+ /** What Vitest's `test` carries besides modifiers: `extend`, `describe`, `suite` and the hooks. */
117
+ function testMember(chain, key) {
118
+ if (key === 'extend') {
119
+ return (...args) => chained({
120
+ kind: 'test',
121
+ todo: false,
122
+ fixtures: liveFixtureRegistrations(chain.fixtures, args),
123
+ });
124
+ }
125
+ if (key === 'describe' || key === 'suite') {
126
+ return liveRegistrar('suite');
127
+ }
128
+ if (HOOKS.has(key)) {
129
+ return liveHook(key);
130
+ }
131
+ return undefined;
132
+ }
133
+ function member(chain, key, self) {
134
+ if (typeof key === 'string' && RUN_MODIFIERS[chain.kind].has(key)) {
135
+ return self;
136
+ }
137
+ if (key === 'skipIf' || key === 'runIf') {
138
+ return () => self;
139
+ }
140
+ if (key === 'todo') {
141
+ return chained({ ...chain, todo: true });
142
+ }
143
+ if (key === 'each') {
144
+ return each(chain);
145
+ }
146
+ if (key === 'for') {
147
+ return forEachRow(chain);
148
+ }
149
+ const own = chain.kind === 'test' && typeof key === 'string' ? testMember(chain, key) : undefined;
150
+ return own ?? Reflect.get(liveInert(), key);
151
+ }
152
+ function chained(chain) {
153
+ const register = (name, second, third) => {
154
+ const { body, timeout } = bodyAndTimeout(second, third);
155
+ const uses = body === undefined ? null : { callback: body, index: 0 };
156
+ add(chain, nameOf(name), body, timeout, uses);
157
+ };
158
+ const self = new Proxy(register, {
159
+ get(_target, key) {
160
+ return member(chain, key, self);
161
+ },
162
+ });
163
+ return self;
164
+ }
165
+ /**
166
+ * `describe`, `suite`, `it` or `test` on the live page. A call registers
167
+ * whatever modifiers come before it, because a skipped test keeps its entry
168
+ * in the manifest. `test.extend(…)` returns a chain whose tests keep the
169
+ * merged fixtures. Properties the page does not know, such as `override`,
170
+ * are inert.
171
+ */
172
+ export function liveRegistrar(kind) {
173
+ return chained({ kind, todo: false, fixtures: NO_FIXTURES });
174
+ }
@@ -0,0 +1,14 @@
1
+ import { type LiveSuite } from './live-suite.js';
2
+ /** The suites, tests and hooks of the test file on the live page. */
3
+ export interface LiveRegistry {
4
+ root: LiveSuite;
5
+ /** Where suites, tests and hooks register: the suite whose factory runs, or the root during import. */
6
+ current: LiveSuite;
7
+ /** The major version of the project's Vitest, whose rules name `each` and `for` rows. */
8
+ vitestMajor: number;
9
+ }
10
+ /**
11
+ * The registry of the live page. The shimmed `vitest` and the runner may come
12
+ * from two copies of this package, so it lives on `globalThis`.
13
+ */
14
+ export declare function liveRegistry(): LiveRegistry;
@@ -0,0 +1,14 @@
1
+ import { liveSuite } from './live-suite.js';
2
+ const REGISTRY_KEY = Symbol.for('describe-me.live-registry');
3
+ /**
4
+ * The registry of the live page. The shimmed `vitest` and the runner may come
5
+ * from two copies of this package, so it lives on `globalThis`.
6
+ */
7
+ export function liveRegistry() {
8
+ const scope = globalThis;
9
+ if (scope[REGISTRY_KEY] === undefined) {
10
+ const root = liveSuite('');
11
+ scope[REGISTRY_KEY] = { root, current: root, vitestMajor: 5 };
12
+ }
13
+ return scope[REGISTRY_KEY];
14
+ }
@@ -0,0 +1,2 @@
1
+ export { liveExport } from './live-export.js';
2
+ export { startLivePage, type LivePageOptions } from './start-live-page.js';
@@ -0,0 +1,3 @@
1
+ // Used by the live preview page that the plugin generates, not for import in tests.
2
+ export { liveExport } from './live-export.js';
3
+ export { startLivePage } from './start-live-page.js';
@@ -0,0 +1,15 @@
1
+ /** A setup file of the live page, as the generated entry imports it. */
2
+ export interface LiveSetupFile {
3
+ /** How messages name it: its path from the root for a file inside the root, else the entry as written. */
4
+ name: string;
5
+ /** What the entry imports: `/<path from the root>`, an absolute path, or a package specifier. */
6
+ specifier: string;
7
+ /** The file, with `/` separators. Absent for a package. */
8
+ file?: string;
9
+ }
10
+ /**
11
+ * The setup files of the live page, in the order given. Paths resolve
12
+ * against the root, package specifiers stay bare. describe-me's own setup
13
+ * files are left out, because they record and the live page does not.
14
+ */
15
+ export declare function liveSetupFiles(entries: string[], root: string): LiveSetupFile[];
@@ -0,0 +1,56 @@
1
+ import { existsSync, realpathSync } from 'node:fs';
2
+ import { isAbsolute, relative, resolve, sep } from 'node:path';
3
+ import { adapterPackageRoot } from './adapter-package-root.js';
4
+ const OWN_PACKAGE = '@describe-me/vitest';
5
+ function posix(path) {
6
+ return path.split(sep).join('/');
7
+ }
8
+ /** Vitest takes `vitest.setup.ts` without `./`, so a file under the root counts as a path too. */
9
+ function isPath(entry, root) {
10
+ return (entry.startsWith('.') ||
11
+ entry.startsWith('/') ||
12
+ isAbsolute(entry) ||
13
+ existsSync(resolve(root, entry)));
14
+ }
15
+ function isOwnPackage(specifier) {
16
+ return specifier === OWN_PACKAGE || specifier.startsWith(`${OWN_PACKAGE}/`);
17
+ }
18
+ /** Whether a file lies in the installed `@describe-me/vitest`, through a link or not. */
19
+ function isOwnFile(file, ownRoot) {
20
+ if (ownRoot === null) {
21
+ return false;
22
+ }
23
+ const real = existsSync(file) ? posix(realpathSync(file)) : file;
24
+ return file.startsWith(ownRoot) || real.startsWith(ownRoot);
25
+ }
26
+ function fileEntry(entry, root) {
27
+ const file = resolve(root, entry);
28
+ const fromRoot = relative(root, file);
29
+ const inRoot = fromRoot !== '' && !fromRoot.startsWith('..') && !isAbsolute(fromRoot);
30
+ if (inRoot) {
31
+ return { name: posix(fromRoot), specifier: `/${posix(fromRoot)}`, file: posix(file) };
32
+ }
33
+ return { name: entry, specifier: posix(file), file: posix(file) };
34
+ }
35
+ /**
36
+ * The setup files of the live page, in the order given. Paths resolve
37
+ * against the root, package specifiers stay bare. describe-me's own setup
38
+ * files are left out, because they record and the live page does not.
39
+ */
40
+ export function liveSetupFiles(entries, root) {
41
+ const ownRoot = adapterPackageRoot(root, OWN_PACKAGE);
42
+ const setupFiles = [];
43
+ for (const entry of entries) {
44
+ if (!isPath(entry, root)) {
45
+ if (!isOwnPackage(entry)) {
46
+ setupFiles.push({ name: entry, specifier: entry });
47
+ }
48
+ continue;
49
+ }
50
+ const setupFile = fileEntry(entry, root);
51
+ if (!isOwnFile(setupFile.file ?? '', ownRoot)) {
52
+ setupFiles.push(setupFile);
53
+ }
54
+ }
55
+ return setupFiles;
56
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The source of a module that the live page imports instead of `vitest`,
3
+ * `vitest/browser` or `@describe-me/vitest`. It exports every name the real
4
+ * module has, so a test that imports one still links.
5
+ */
6
+ export declare function liveShimSource(module: string, names: string[]): string;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The source of a module that the live page imports instead of `vitest`,
3
+ * `vitest/browser` or `@describe-me/vitest`. It exports every name the real
4
+ * module has, so a test that imports one still links.
5
+ */
6
+ export function liveShimSource(module, names) {
7
+ const exports = names.map((name) => `export const ${name} = liveExport('${module}', '${name}')`);
8
+ return [`import { liveExport } from '@describe-me/vitest/live-runtime'`, '', ...exports, ''].join('\n');
9
+ }
@@ -0,0 +1,7 @@
1
+ import { type LiveMock } from './live-mock.js';
2
+ /**
3
+ * `vi.spyOn()` on the live page. It replaces a method with a mock named after
4
+ * the key, which calls the original. With an access type it leaves the
5
+ * property alone and returns a mock.
6
+ */
7
+ export declare function liveSpyOn(object: object, key: PropertyKey, accessType?: 'get' | 'set'): LiveMock;
@@ -0,0 +1,27 @@
1
+ import { liveMock } from './live-mock.js';
2
+ /**
3
+ * `vi.spyOn()` on the live page. It replaces a method with a mock named after
4
+ * the key, which calls the original. With an access type it leaves the
5
+ * property alone and returns a mock.
6
+ */
7
+ export function liveSpyOn(object, key, accessType) {
8
+ if (accessType !== undefined) {
9
+ return liveMock({ name: String(key) });
10
+ }
11
+ const target = object;
12
+ const original = target[key];
13
+ const spy = liveMock({
14
+ name: String(key),
15
+ implementation: typeof original === 'function' ? original : undefined,
16
+ restore: () => {
17
+ target[key] = original;
18
+ },
19
+ });
20
+ try {
21
+ target[key] = spy;
22
+ }
23
+ catch {
24
+ // A frozen object, such as a module namespace, keeps its method, and the arrange phase goes on.
25
+ }
26
+ return spy;
27
+ }
@@ -0,0 +1,2 @@
1
+ /** `step()` on the live page: it runs the body and records nothing. */
2
+ export declare function liveStep<T>(_label: string, body: () => T | Promise<T>): Promise<T>;
@@ -0,0 +1,4 @@
1
+ /** `step()` on the live page: it runs the body and records nothing. */
2
+ export async function liveStep(_label, body) {
3
+ return await body();
4
+ }
@@ -0,0 +1,2 @@
1
+ /** A string literal for the modules that live mode generates. */
2
+ export declare function liveStringLiteral(value: string): string;
@@ -0,0 +1,7 @@
1
+ // JSON.stringify leaves these as they are. Escaped, a file name cannot close a
2
+ // script tag that inlines the entry, nor end a line for an older parser.
3
+ const UNSAFE_IN_CODE = /[<>\u2028\u2029]/g;
4
+ /** A string literal for the modules that live mode generates. */
5
+ export function liveStringLiteral(value) {
6
+ return JSON.stringify(value).replace(UNSAFE_IN_CODE, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`);
7
+ }
@@ -0,0 +1,28 @@
1
+ import type { LiveFixtureUse } from './live-fixture-props.js';
2
+ import type { LiveFixtures } from './live-fixture-registrations.js';
3
+ /** When a hook runs, relative to the tests of its suite. */
4
+ export type LiveHookKind = 'beforeAll' | 'beforeEach' | 'afterEach' | 'afterAll' | 'aroundEach' | 'aroundAll';
5
+ /** A hook, a test body or a suite factory, as the test file wrote it. */
6
+ export type LiveCallback = (...args: unknown[]) => unknown;
7
+ /** A test as the live page registers it. `it.todo` registers one without a body. */
8
+ export interface LiveTest {
9
+ kind: 'test';
10
+ name: string;
11
+ body?: LiveCallback;
12
+ timeout?: number;
13
+ /** The `test.extend` fixtures of the chain that registered it. */
14
+ fixtures: LiveFixtures;
15
+ /** Where the body names the fixtures it uses, or null when it gets no context, as an `each` row. */
16
+ uses: LiveFixtureUse | null;
17
+ }
18
+ /** A suite as the live page registers it, with its factory until the runner collects it. */
19
+ export interface LiveSuite {
20
+ kind: 'suite';
21
+ name: string;
22
+ factory?: LiveCallback;
23
+ timeout?: number;
24
+ children: (LiveSuite | LiveTest)[];
25
+ hooks: Record<LiveHookKind, LiveCallback[]>;
26
+ }
27
+ /** A suite with nothing registered on it yet. */
28
+ export declare function liveSuite(name: string, factory?: LiveCallback, timeout?: number): LiveSuite;
@@ -0,0 +1,18 @@
1
+ /** A suite with nothing registered on it yet. */
2
+ export function liveSuite(name, factory, timeout) {
3
+ return {
4
+ kind: 'suite',
5
+ name,
6
+ factory,
7
+ timeout,
8
+ children: [],
9
+ hooks: {
10
+ beforeAll: [],
11
+ beforeEach: [],
12
+ afterEach: [],
13
+ afterAll: [],
14
+ aroundEach: [],
15
+ aroundAll: [],
16
+ },
17
+ };
18
+ }
@@ -0,0 +1,24 @@
1
+ import type { LiveTestMatch } from './find-live-test.js';
2
+ /**
3
+ * The parts of Vitest's test context that the live page offers. The runner
4
+ * adds each `test.extend` fixture it computes as a property.
5
+ */
6
+ export interface LiveTestContext {
7
+ task: {
8
+ name: string;
9
+ fullName: string;
10
+ };
11
+ expect: unknown;
12
+ skip(condition?: unknown): void;
13
+ signal: AbortSignal;
14
+ onTestFinished(): void;
15
+ onTestFailed(): void;
16
+ annotate(): Promise<void>;
17
+ [fixture: string]: unknown;
18
+ }
19
+ /**
20
+ * The one context that the hooks and the body of a live test get, with the
21
+ * fixtures they use once the runner computed them. `skip()` stops the test
22
+ * without a render, so the page reports `no-render`.
23
+ */
24
+ export declare function liveTestContext(match: LiveTestMatch, signal: AbortSignal): LiveTestContext;