@describe-me/vitest 0.5.4 → 0.6.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 (132) hide show
  1. package/README.md +10 -8
  2. package/dist/browser-profile.d.ts +3 -0
  3. package/dist/browser-profile.js +26 -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 +39 -0
  16. package/dist/environment-profile.d.ts +35 -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 +29 -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/start-live-page.d.ts +29 -0
  129. package/dist/start-live-page.js +62 -0
  130. package/dist/validate-live-option.d.ts +11 -0
  131. package/dist/validate-live-option.js +29 -0
  132. package/package.json +11 -3
@@ -0,0 +1,330 @@
1
+ const TRUNCATOR = '…';
2
+ const IDENTIFIER = /^[a-zA-Z_][a-zA-Z_0-9]*$/;
3
+ const ESCAPES = {
4
+ '\b': '\\b',
5
+ '\t': '\\t',
6
+ '\n': '\\n',
7
+ '\f': '\\f',
8
+ '\r': '\\r',
9
+ "'": "\\'",
10
+ };
11
+ // The code units loupe escapes in strings: control and invisible characters, and `'`.
12
+ const ESCAPED_RANGES = [
13
+ [0x27, 0x27],
14
+ [0x00, 0x1f],
15
+ [0x7f, 0x9f],
16
+ [0xad, 0xad],
17
+ [0x600, 0x604],
18
+ [0x70f, 0x70f],
19
+ [0x17b4, 0x17b5],
20
+ [0x200c, 0x200f],
21
+ [0x2028, 0x202f],
22
+ [0x2060, 0x206f],
23
+ [0xfeff, 0xfeff],
24
+ [0xfff0, 0xffff],
25
+ ];
26
+ function isHighSurrogate(character) {
27
+ return character !== undefined && character >= '\ud800' && character <= '\udbff';
28
+ }
29
+ function truncate(value, length, tail = TRUNCATOR) {
30
+ const text = String(value);
31
+ if (tail.length > length && text.length > tail.length) {
32
+ return tail;
33
+ }
34
+ if (text.length > length && text.length > tail.length) {
35
+ let end = length - tail.length;
36
+ if (end > 0 && isHighSurrogate(text[end - 1])) {
37
+ end -= 1;
38
+ }
39
+ return `${text.slice(0, end)}${tail}`;
40
+ }
41
+ return text;
42
+ }
43
+ function escaped(text) {
44
+ let result = '';
45
+ for (let i = 0; i < text.length; i++) {
46
+ const unit = text.charCodeAt(i);
47
+ const escape = ESCAPED_RANGES.some(([from, to]) => unit >= from && unit <= to);
48
+ result += escape ? (ESCAPES[text[i]] ?? `\\u${`0000${unit.toString(16)}`.slice(-4)}`) : text[i];
49
+ }
50
+ return result;
51
+ }
52
+ /** loupe's `inspectList`: items until the length runs out, then `…(n)` for the rest. */
53
+ function inspectList(list, options, inspectItem = inspectValue, separator = ', ') {
54
+ const size = list.length;
55
+ if (size === 0) {
56
+ return '';
57
+ }
58
+ const originalLength = options.truncate;
59
+ let output = '';
60
+ let peek = '';
61
+ let truncated = '';
62
+ for (let i = 0; i < size; i += 1) {
63
+ const last = i + 1 === list.length;
64
+ const secondToLast = i + 2 === list.length;
65
+ truncated = `${TRUNCATOR}(${list.length - i})`;
66
+ options.truncate = originalLength - output.length - (last ? 0 : separator.length);
67
+ const text = peek || inspectItem(list[i], options) + (last ? '' : separator);
68
+ const nextLength = output.length + text.length;
69
+ const truncatedLength = nextLength + truncated.length;
70
+ if (last && nextLength > originalLength && output.length + truncated.length <= originalLength) {
71
+ break;
72
+ }
73
+ if (!last && !secondToLast && truncatedLength > originalLength) {
74
+ break;
75
+ }
76
+ peek = last ? '' : inspectItem(list[i + 1], options) + (secondToLast ? '' : separator);
77
+ if (!last &&
78
+ secondToLast &&
79
+ truncatedLength > originalLength &&
80
+ nextLength + peek.length > originalLength) {
81
+ break;
82
+ }
83
+ output += text;
84
+ if (!last && !secondToLast && nextLength + peek.length >= originalLength) {
85
+ truncated = `${TRUNCATOR}(${list.length - i - 1})`;
86
+ break;
87
+ }
88
+ truncated = '';
89
+ }
90
+ return `${output}${truncated}`;
91
+ }
92
+ function quoteComplexKey(key) {
93
+ if (IDENTIFIER.test(key)) {
94
+ return key;
95
+ }
96
+ const inner = JSON.stringify(key).slice(1, -1).split("'").join("\\'").split('\\"').join('"');
97
+ return `'${inner}'`;
98
+ }
99
+ function inspectProperty(entry, options) {
100
+ const [key, value] = entry;
101
+ options.truncate -= 2;
102
+ let name;
103
+ if (typeof key === 'string') {
104
+ name = quoteComplexKey(key);
105
+ }
106
+ else if (typeof key === 'number') {
107
+ name = String(key);
108
+ }
109
+ else {
110
+ name = `[${inspectValue(key, options)}]`;
111
+ }
112
+ options.truncate -= name.length;
113
+ return `${name}: ${inspectValue(value, options)}`;
114
+ }
115
+ function inspectArray(value, options) {
116
+ const array = value;
117
+ const nonIndexProperties = Object.keys(array).slice(array.length);
118
+ if (!array.length && !nonIndexProperties.length) {
119
+ return '[]';
120
+ }
121
+ options.truncate -= 4;
122
+ const listContents = inspectList(array, options);
123
+ options.truncate -= listContents.length;
124
+ const propertyContents = nonIndexProperties.length
125
+ ? inspectList(nonIndexProperties.map((key) => [key, array[key]]), options, inspectProperty)
126
+ : '';
127
+ return `[ ${listContents}${propertyContents ? `, ${propertyContents}` : ''} ]`;
128
+ }
129
+ function inspectObject(value, options) {
130
+ const object = value;
131
+ const properties = Object.getOwnPropertyNames(object);
132
+ const symbols = Object.getOwnPropertySymbols(object);
133
+ if (properties.length === 0 && symbols.length === 0) {
134
+ return '{}';
135
+ }
136
+ options.truncate -= 4;
137
+ if (options.seen.includes(object)) {
138
+ return '[Circular]';
139
+ }
140
+ options.seen.push(object);
141
+ const propertyContents = inspectList(properties.map((key) => [key, object[key]]), options, inspectProperty);
142
+ const symbolContents = inspectList(symbols.map((key) => [key, object[key]]), options, inspectProperty);
143
+ options.seen.pop();
144
+ const separator = propertyContents && symbolContents ? ', ' : '';
145
+ return `{ ${propertyContents}${separator}${symbolContents} }`;
146
+ }
147
+ const ERROR_KEYS = new Set([
148
+ 'stack',
149
+ 'line',
150
+ 'column',
151
+ 'name',
152
+ 'message',
153
+ 'fileName',
154
+ 'lineNumber',
155
+ 'columnNumber',
156
+ 'number',
157
+ 'description',
158
+ 'cause',
159
+ ]);
160
+ /** loupe's error inspector, which never takes the error off `seen` again. */
161
+ function inspectError(value, options) {
162
+ const error = value;
163
+ const properties = Object.getOwnPropertyNames(error).filter((key) => !ERROR_KEYS.has(key));
164
+ const name = String(error.name);
165
+ options.truncate -= name.length;
166
+ let message = '';
167
+ if (typeof error.message === 'string') {
168
+ message = truncate(error.message, options.truncate);
169
+ }
170
+ else {
171
+ properties.unshift('message');
172
+ }
173
+ message = message ? `: ${message}` : '';
174
+ options.truncate -= message.length + 5;
175
+ if (options.seen.includes(error)) {
176
+ return '[Circular]';
177
+ }
178
+ options.seen.push(error);
179
+ const propertyContents = inspectList(properties.map((key) => [key, error[key]]), options, inspectProperty);
180
+ return `${name}${message}${propertyContents ? ` { ${propertyContents} }` : ''}`;
181
+ }
182
+ function inspectClass(value, options) {
183
+ const object = value;
184
+ let name = Symbol.toStringTag in object ? String(object[Symbol.toStringTag]) : '';
185
+ name = name || object.constructor?.name || '';
186
+ if (!name || name === '_class') {
187
+ name = '<Anonymous Class>';
188
+ }
189
+ options.truncate -= name.length;
190
+ return `${name}${inspectObject(value, options)}`;
191
+ }
192
+ function inspectNumber(value, options) {
193
+ const number = value;
194
+ if (Number.isNaN(number)) {
195
+ return 'NaN';
196
+ }
197
+ if (number === Number.POSITIVE_INFINITY) {
198
+ return 'Infinity';
199
+ }
200
+ if (number === Number.NEGATIVE_INFINITY) {
201
+ return '-Infinity';
202
+ }
203
+ if (number === 0) {
204
+ return 1 / number === Number.POSITIVE_INFINITY ? '+0' : '-0';
205
+ }
206
+ return truncate(String(number), options.truncate);
207
+ }
208
+ function inspectBigInt(value, options) {
209
+ const digits = truncate(String(value), options.truncate - 1);
210
+ return digits === TRUNCATOR ? digits : `${digits}n`;
211
+ }
212
+ function inspectString(value, options) {
213
+ return `'${truncate(escaped(String(value)), options.truncate - 2)}'`;
214
+ }
215
+ function inspectSymbol(value) {
216
+ const { description } = value;
217
+ return description ? `Symbol(${description})` : 'Symbol()';
218
+ }
219
+ function inspectFunction(value, options) {
220
+ const fn = value;
221
+ const type = fn[Symbol.toStringTag] || 'Function';
222
+ if (!fn.name) {
223
+ return `[${type}]`;
224
+ }
225
+ return `[${type} ${truncate(fn.name, options.truncate - 11)}]`;
226
+ }
227
+ function inspectDate(value, options) {
228
+ const json = value.toJSON();
229
+ if (json === null) {
230
+ return 'Invalid Date';
231
+ }
232
+ const [date, time] = json.split('T');
233
+ return `${date}T${truncate(time, options.truncate - date.length - 1)}`;
234
+ }
235
+ function inspectRegExp(value, options) {
236
+ const pattern = value;
237
+ const flags = pattern.toString().split('/')[2];
238
+ return `/${truncate(pattern.source, options.truncate - (2 + flags.length))}/${flags}`;
239
+ }
240
+ function inspectMapEntry(entry, options) {
241
+ const [key, value] = entry;
242
+ options.truncate -= 4;
243
+ const keyText = inspectValue(key, options);
244
+ options.truncate -= keyText.length;
245
+ return `${keyText} => ${inspectValue(value, options)}`;
246
+ }
247
+ function inspectMap(value, options) {
248
+ const map = value;
249
+ if (map.size === 0) {
250
+ return 'Map{}';
251
+ }
252
+ options.truncate -= 7;
253
+ return `Map{ ${inspectList([...map.entries()], options, inspectMapEntry)} }`;
254
+ }
255
+ function inspectSet(value, options) {
256
+ const set = value;
257
+ if (set.size === 0) {
258
+ return 'Set{}';
259
+ }
260
+ options.truncate -= 7;
261
+ return `Set{ ${inspectList([...set.values()], options)} }`;
262
+ }
263
+ function inspectArguments(value, options) {
264
+ const args = Array.from(value);
265
+ if (args.length === 0) {
266
+ return 'Arguments[]';
267
+ }
268
+ options.truncate -= 13;
269
+ return `Arguments[ ${inspectList(args, options)} ]`;
270
+ }
271
+ const BASE_TYPES = {
272
+ undefined: () => 'undefined',
273
+ null: () => 'null',
274
+ boolean: (value) => String(value),
275
+ Boolean: (value) => String(value),
276
+ number: inspectNumber,
277
+ Number: inspectNumber,
278
+ bigint: inspectBigInt,
279
+ BigInt: inspectBigInt,
280
+ string: inspectString,
281
+ String: inspectString,
282
+ function: inspectFunction,
283
+ Function: inspectFunction,
284
+ symbol: inspectSymbol,
285
+ Symbol: inspectSymbol,
286
+ Array: inspectArray,
287
+ Date: inspectDate,
288
+ Map: inspectMap,
289
+ Set: inspectSet,
290
+ RegExp: inspectRegExp,
291
+ Promise: () => 'Promise{…}',
292
+ WeakSet: () => 'WeakSet{…}',
293
+ WeakMap: () => 'WeakMap{…}',
294
+ Arguments: inspectArguments,
295
+ Generator: () => '',
296
+ DataView: () => '',
297
+ ArrayBuffer: () => '',
298
+ Error: inspectError,
299
+ };
300
+ /** loupe's `inspect`. Each call works on a copy of the options, as loupe's `normaliseOptions` makes one. */
301
+ function inspectValue(value, parent) {
302
+ const options = { truncate: Number(parent.truncate), seen: parent.seen };
303
+ let type = value === null ? 'null' : typeof value;
304
+ if (type === 'object') {
305
+ type = Object.prototype.toString.call(value).slice(8, -1);
306
+ }
307
+ const inspector = Object.hasOwn(BASE_TYPES, type) ? BASE_TYPES[type] : undefined;
308
+ if (inspector !== undefined) {
309
+ return inspector(value, options);
310
+ }
311
+ const proto = Object.getPrototypeOf(value);
312
+ if (proto === Object.prototype || proto === null) {
313
+ return inspectObject(value, options);
314
+ }
315
+ if (value.constructor !== Object) {
316
+ return inspectClass(value, options);
317
+ }
318
+ return inspectObject(value, options);
319
+ }
320
+ /**
321
+ * A port of `inspect` from Vitest 4's display utilities, which prints values
322
+ * in test names with loupe: `'x'`, `{ a: 1 }`, `[ 1, 2 ]`, and `+0` for zero.
323
+ * Custom inspectors, typed arrays and DOM nodes print as objects.
324
+ */
325
+ export function liveLoupeInspect(value, truncateAt) {
326
+ return inspectValue(value, {
327
+ truncate: truncateAt === 0 ? Number.POSITIVE_INFINITY : truncateAt,
328
+ seen: [],
329
+ });
330
+ }
@@ -0,0 +1,18 @@
1
+ /** What a mock runs when it is called. */
2
+ export type LiveImplementation = (...args: unknown[]) => unknown;
3
+ /** How `vi.fn()` and `vi.spyOn()` set up a mock. */
4
+ export interface LiveMockOptions {
5
+ implementation?: LiveImplementation;
6
+ /** Default: `vi.fn()`, the name Vitest gives an unnamed mock. */
7
+ name?: string;
8
+ /** Puts back what `vi.spyOn()` replaced. */
9
+ restore?: () => void;
10
+ }
11
+ /** A mock as tests see it: callable, with Vitest's `mock*` methods. */
12
+ export type LiveMock = LiveImplementation & Record<string, unknown>;
13
+ /**
14
+ * Enough of Vitest's mock for an arrange phase. Every call posts an action to
15
+ * the viewer under the mock's current name, so a click in the live component
16
+ * shows as `onSelect("Rename")`.
17
+ */
18
+ export declare function liveMock(options?: LiveMockOptions): LiveMock;
@@ -0,0 +1,105 @@
1
+ import { postLiveMessage } from '@describe-me/core/live';
2
+ import { liveArgLabel } from './live-arg-label.js';
3
+ function recordResult(state, run) {
4
+ try {
5
+ const value = run();
6
+ state.results.push({ type: 'return', value });
7
+ return value;
8
+ }
9
+ catch (error) {
10
+ state.results.push({ type: 'throw', value: error });
11
+ throw error;
12
+ }
13
+ }
14
+ /** The `mock*` methods. Those that change the mock return it, as in Vitest. */
15
+ function mockMethods(state, options, self) {
16
+ const clear = () => {
17
+ state.calls.length = 0;
18
+ state.results.length = 0;
19
+ };
20
+ const reset = () => {
21
+ clear();
22
+ state.implementation = options.implementation;
23
+ state.once.length = 0;
24
+ };
25
+ const implement = (next) => {
26
+ state.implementation = next;
27
+ return self();
28
+ };
29
+ const implementOnce = (next) => {
30
+ state.once.push(next);
31
+ return self();
32
+ };
33
+ return {
34
+ mockImplementation: implement,
35
+ mockImplementationOnce: implementOnce,
36
+ mockReturnValue: (value) => implement(() => value),
37
+ mockReturnValueOnce: (value) => implementOnce(() => value),
38
+ mockResolvedValue: (value) => implement(() => Promise.resolve(value)),
39
+ mockResolvedValueOnce: (value) => implementOnce(() => Promise.resolve(value)),
40
+ mockRejectedValue: (error) => implement(() => Promise.reject(error)),
41
+ mockRejectedValueOnce: (error) => implementOnce(() => Promise.reject(error)),
42
+ mockName: (name) => {
43
+ state.name = String(name);
44
+ return self();
45
+ },
46
+ getMockName: () => state.name,
47
+ mockClear: () => {
48
+ clear();
49
+ return self();
50
+ },
51
+ mockReset: () => {
52
+ reset();
53
+ return self();
54
+ },
55
+ mockRestore: () => {
56
+ reset();
57
+ options.restore?.();
58
+ return self();
59
+ },
60
+ };
61
+ }
62
+ /**
63
+ * Enough of Vitest's mock for an arrange phase. Every call posts an action to
64
+ * the viewer under the mock's current name, so a click in the live component
65
+ * shows as `onSelect("Rename")`.
66
+ */
67
+ export function liveMock(options = {}) {
68
+ const state = {
69
+ name: options.name ?? 'vi.fn()',
70
+ implementation: options.implementation,
71
+ once: [],
72
+ calls: [],
73
+ results: [],
74
+ };
75
+ function mock(...args) {
76
+ state.calls.push(args);
77
+ postLiveMessage({ type: 'action', name: state.name, args: args.map(liveArgLabel) });
78
+ const next = state.once.shift() ?? state.implementation;
79
+ const constructing = new.target !== undefined;
80
+ if (next === undefined) {
81
+ return recordResult(state, () => undefined);
82
+ }
83
+ return recordResult(state, () => constructing ? Reflect.construct(next, args) : next.apply(this, args));
84
+ }
85
+ Object.assign(mock, mockMethods(state, options, () => proxy), {
86
+ _isMockFunction: true,
87
+ mock: {
88
+ calls: state.calls,
89
+ results: state.results,
90
+ get lastCall() {
91
+ return state.calls.at(-1);
92
+ },
93
+ },
94
+ });
95
+ // Vitest keeps adding `mock*` methods. Unknown ones return the mock, so a chain still runs.
96
+ const proxy = new Proxy(mock, {
97
+ get(target, key, receiver) {
98
+ if (typeof key === 'string' && key.startsWith('mock') && !(key in target)) {
99
+ return () => proxy;
100
+ }
101
+ return Reflect.get(target, key, receiver);
102
+ },
103
+ });
104
+ return proxy;
105
+ }
@@ -0,0 +1,8 @@
1
+ /** The ids of the modules that live mode generates. The `\0` keeps other plugins off them. */
2
+ export declare const LIVE_MODULE_IDS: {
3
+ readonly entry: "\0describe-me-live:entry";
4
+ readonly vitest: "\0describe-me-live:vitest";
5
+ readonly vitestBrowser: "\0describe-me-live:vitest-browser";
6
+ readonly describeMeVitest: "\0describe-me-live:describe-me-vitest";
7
+ readonly testFiles: "\0describe-me-live:test-files";
8
+ };
@@ -0,0 +1,8 @@
1
+ /** The ids of the modules that live mode generates. The `\0` keeps other plugins off them. */
2
+ export const LIVE_MODULE_IDS = {
3
+ entry: '\0describe-me-live:entry',
4
+ vitest: '\0describe-me-live:vitest',
5
+ vitestBrowser: '\0describe-me-live:vitest-browser',
6
+ describeMeVitest: '\0describe-me-live:describe-me-vitest',
7
+ testFiles: '\0describe-me-live:test-files',
8
+ };
@@ -0,0 +1,13 @@
1
+ import type { LiveSetupFile } from './live-setup-files.js';
2
+ /**
3
+ * Vite's `optimizeDeps` for the live page. Vite bundles every dependency
4
+ * before the first page loads, so it never finds one late and reloads the
5
+ * page. Its scanner starts from the test files of the manifest and the setup
6
+ * files, and the page's own imports and the package setup files are listed
7
+ * outright.
8
+ */
9
+ export declare function liveOptimizeDeps(root: string, outDir: string | undefined, live: string, setupFiles?: LiveSetupFile[]): {
10
+ entries: string[];
11
+ include: string[];
12
+ exclude: string[];
13
+ };
@@ -0,0 +1,67 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import { join, relative, sep } from 'node:path';
4
+ /**
5
+ * Modules the optimizer must leave to the shims. A package that imports
6
+ * `vitest`, such as `@testing-library/jest-dom/vitest`, would otherwise carry
7
+ * the real Vitest in its bundle. `vitest` covers `vitest/browser` too.
8
+ */
9
+ const SHIMMED = ['vitest', '@vitest/browser/context'];
10
+ /** What the live page imports that no test file does, so Vite's scanner cannot find it. */
11
+ function pageDependencies(live) {
12
+ return [
13
+ '@describe-me/vitest/live-runtime',
14
+ live,
15
+ 'react',
16
+ 'react-dom/client',
17
+ 'react/jsx-dev-runtime',
18
+ '@testing-library/react',
19
+ ];
20
+ }
21
+ function resolvable(projectRequire, specifier) {
22
+ try {
23
+ projectRequire.resolve(specifier);
24
+ return true;
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ }
30
+ /** The test files of the plugin's own manifest, with the `/` separators that Vite's globs expect. */
31
+ function testFiles(root, outDir) {
32
+ try {
33
+ const manifest = JSON.parse(readFileSync(join(root, outDir, 'manifest.json'), 'utf8'));
34
+ return (manifest.modules ?? [])
35
+ .map((module) => module.id)
36
+ .filter((id) => typeof id === 'string')
37
+ .map((id) => id.replaceAll('\\', '/'));
38
+ }
39
+ catch {
40
+ return [];
41
+ }
42
+ }
43
+ /** The setup files that are paths, relative to the root, like the test files. */
44
+ function setupFileEntries(root, setupFiles) {
45
+ return setupFiles
46
+ .map((setupFile) => setupFile.file)
47
+ .filter((file) => file !== undefined)
48
+ .map((file) => relative(root, file).split(sep).join('/'));
49
+ }
50
+ /**
51
+ * Vite's `optimizeDeps` for the live page. Vite bundles every dependency
52
+ * before the first page loads, so it never finds one late and reloads the
53
+ * page. Its scanner starts from the test files of the manifest and the setup
54
+ * files, and the page's own imports and the package setup files are listed
55
+ * outright.
56
+ */
57
+ export function liveOptimizeDeps(root, outDir, live, setupFiles = []) {
58
+ const projectRequire = createRequire(join(root, 'package.json'));
59
+ const packages = setupFiles
60
+ .filter((setupFile) => setupFile.file === undefined)
61
+ .map((setupFile) => setupFile.specifier);
62
+ return {
63
+ entries: [...testFiles(root, outDir ?? '.describe-me'), ...setupFileEntries(root, setupFiles)],
64
+ include: [...pageDependencies(live), ...packages].filter((specifier) => resolvable(projectRequire, specifier)),
65
+ exclude: [...SHIMMED],
66
+ };
67
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * The live page before Vite's HTML transforms: the preview head, when it is
3
+ * not blank, and the generated entry at `entrySrc`, which runs the test.
4
+ */
5
+ export declare function livePageHtml(previewHead: string | undefined, entrySrc: string): string;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The live page before Vite's HTML transforms: the preview head, when it is
3
+ * not blank, and the generated entry at `entrySrc`, which runs the test.
4
+ */
5
+ export function livePageHtml(previewHead, entrySrc) {
6
+ const head = previewHead?.trim() ? [` ${previewHead.trim()}`] : [];
7
+ return [
8
+ '<!doctype html>',
9
+ '<html lang="en">',
10
+ ' <head>',
11
+ ' <meta charset="utf-8">',
12
+ ...head,
13
+ ' <title>describe-me live preview</title>',
14
+ ' </head>',
15
+ ' <body>',
16
+ ` <script type="module" src="${entrySrc}"></script>`,
17
+ ' </body>',
18
+ '</html>',
19
+ '',
20
+ ].join('\n');
21
+ }
@@ -0,0 +1,6 @@
1
+ import type { Vite } from 'vitest/node';
2
+ /**
3
+ * Serves the live page. It goes through Vite's HTML transforms, so that
4
+ * `@vitejs/plugin-react` adds its React Refresh preamble.
5
+ */
6
+ export declare function livePageMiddleware(server: Vite.ViteDevServer, previewHead: string | undefined): Vite.Connect.NextHandleFunction;
@@ -0,0 +1,28 @@
1
+ import { LIVE_MODULE_IDS } from './live-module-ids.js';
2
+ import { livePageHtml } from './live-page-html.js';
3
+ const PAGE_PATH = '/__describe-me/live.html';
4
+ /** How Vite serves a module id that starts with `\0`. */
5
+ const ENTRY_URL = `/@id/__x00__${LIVE_MODULE_IDS.entry.slice(1)}`;
6
+ /**
7
+ * Serves the live page. It goes through Vite's HTML transforms, so that
8
+ * `@vitejs/plugin-react` adds its React Refresh preamble.
9
+ */
10
+ export function livePageMiddleware(server, previewHead) {
11
+ const html = livePageHtml(previewHead, ENTRY_URL);
12
+ return (request, response, next) => {
13
+ const url = new URL(request.url ?? '/', 'http://localhost');
14
+ const method = request.method ?? 'GET';
15
+ const isPage = url.pathname === PAGE_PATH && (method === 'GET' || method === 'HEAD');
16
+ // Vite serves the page's inline module scripts, such as React Refresh's, under the same path.
17
+ if (!isPage || url.searchParams.has('html-proxy')) {
18
+ next();
19
+ return;
20
+ }
21
+ server.transformIndexHtml(url.pathname, html).then((page) => {
22
+ response.statusCode = 200;
23
+ response.setHeader('Content-Type', 'text/html; charset=utf-8');
24
+ response.setHeader('Cache-Control', 'no-store');
25
+ response.end(method === 'HEAD' ? undefined : page);
26
+ }, (error) => next(error));
27
+ };
28
+ }
@@ -0,0 +1,20 @@
1
+ import type { ViteUserConfig } from 'vitest/config';
2
+ import { type LiveSetupFile } from './live-setup-files.js';
3
+ import type { LiveOption } from './validate-live-option.js';
4
+ /** What the generated modules of the live page take from the project. */
5
+ export interface LivePageSettings {
6
+ /** `test.globals`. */
7
+ globals: boolean;
8
+ /** `test.testTimeout`, or Vitest's default for the environment. */
9
+ testTimeout: number;
10
+ /** Every name the installed `vitest` exports. */
11
+ vitestNames: string[];
12
+ /** Every name the installed `vitest/browser` declares. */
13
+ browserNames: string[];
14
+ /** `live.setupFiles`, or else `test.setupFiles`, without describe-me's own. */
15
+ setupFiles: LiveSetupFile[];
16
+ /** The major version of the project's Vitest. */
17
+ vitestMajor: number;
18
+ }
19
+ /** Reads the settings of the live page from the project's config and its Vitest. */
20
+ export declare function livePageSettings(userConfig: ViteUserConfig, root: string, live: LiveOption): Promise<LivePageSettings>;
@@ -0,0 +1,24 @@
1
+ import { liveSetupFiles } from './live-setup-files.js';
2
+ import { readLiveBrowserNames } from './read-live-browser-names.js';
3
+ import { readLiveVitestMajor } from './read-live-vitest-major.js';
4
+ import { readLiveVitestNames } from './read-live-vitest-names.js';
5
+ function listOf(entries) {
6
+ if (entries === undefined) {
7
+ return [];
8
+ }
9
+ return Array.isArray(entries) ? entries : [entries];
10
+ }
11
+ /** Reads the settings of the live page from the project's config and its Vitest. */
12
+ export async function livePageSettings(userConfig, root, live) {
13
+ const test = userConfig.test;
14
+ // Vitest's own defaults.
15
+ const defaultTimeout = test?.browser?.enabled ? 15_000 : 5_000;
16
+ return {
17
+ globals: test?.globals === true,
18
+ testTimeout: test?.testTimeout ?? defaultTimeout,
19
+ vitestNames: await readLiveVitestNames(),
20
+ browserNames: readLiveBrowserNames(root),
21
+ setupFiles: liveSetupFiles(live.setupFiles ?? listOf(test?.setupFiles), root),
22
+ vitestMajor: readLiveVitestMajor(root),
23
+ };
24
+ }
@@ -0,0 +1,3 @@
1
+ import type { Vite } from 'vitest/node';
2
+ /** The live protocol of the project's describeMe() plugin, which must be this package's. */
3
+ export declare function livePluginProtocol(plugins: readonly Vite.Plugin[], configName: string): number;