@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.
- package/README.md +10 -8
- package/dist/browser-profile.d.ts +3 -0
- package/dist/browser-profile.js +27 -0
- package/dist/build-live.d.ts +26 -0
- package/dist/build-live.js +106 -0
- package/dist/check-live-enabled.d.ts +3 -0
- package/dist/check-live-enabled.js +10 -0
- package/dist/collect-live-suites.d.ts +7 -0
- package/dist/collect-live-suites.js +25 -0
- package/dist/create-live-server.d.ts +29 -0
- package/dist/create-live-server.js +55 -0
- package/dist/detect-environment.d.ts +4 -0
- package/dist/detect-environment.js +4 -0
- package/dist/dom-profile.d.ts +3 -0
- package/dist/dom-profile.js +38 -0
- package/dist/environment-profile.d.ts +41 -0
- package/dist/environment-profile.js +1 -0
- package/dist/find-live-config-file.d.ts +2 -0
- package/dist/find-live-config-file.js +16 -0
- package/dist/find-live-test.d.ts +13 -0
- package/dist/find-live-test.js +31 -0
- package/dist/free-live-port.d.ts +5 -0
- package/dist/free-live-port.js +16 -0
- package/dist/import-live-vite.d.ts +7 -0
- package/dist/import-live-vite.js +46 -0
- package/dist/install-live-globals.d.ts +5 -0
- package/dist/install-live-globals.js +33 -0
- package/dist/is-adapter-module.d.ts +6 -0
- package/dist/is-adapter-module.js +11 -0
- package/dist/live-arg-label.d.ts +5 -0
- package/dist/live-arg-label.js +38 -0
- package/dist/live-build-plugin.d.ts +18 -0
- package/dist/live-build-plugin.js +89 -0
- package/dist/live-each-name.d.ts +6 -0
- package/dist/live-each-name.js +146 -0
- package/dist/live-entry-source.d.ts +9 -0
- package/dist/live-entry-source.js +46 -0
- package/dist/live-env.d.ts +6 -0
- package/dist/live-env.js +6 -0
- package/dist/live-error-detail.d.ts +2 -0
- package/dist/live-error-detail.js +16 -0
- package/dist/live-export.d.ts +10 -0
- package/dist/live-export.js +48 -0
- package/dist/live-fixture-props.d.ts +12 -0
- package/dist/live-fixture-props.js +87 -0
- package/dist/live-fixture-registrations.d.ts +21 -0
- package/dist/live-fixture-registrations.js +69 -0
- package/dist/live-fixture-values.d.ts +9 -0
- package/dist/live-fixture-values.js +125 -0
- package/dist/live-format-placeholder.d.ts +6 -0
- package/dist/live-format-placeholder.js +70 -0
- package/dist/live-hook.d.ts +8 -0
- package/dist/live-hook.js +12 -0
- package/dist/live-inspect.d.ts +7 -0
- package/dist/live-inspect.js +240 -0
- package/dist/live-loupe-inspect.d.ts +6 -0
- package/dist/live-loupe-inspect.js +330 -0
- package/dist/live-mock.d.ts +18 -0
- package/dist/live-mock.js +105 -0
- package/dist/live-module-ids.d.ts +8 -0
- package/dist/live-module-ids.js +8 -0
- package/dist/live-optimize-deps.d.ts +13 -0
- package/dist/live-optimize-deps.js +67 -0
- package/dist/live-page-html.d.ts +5 -0
- package/dist/live-page-html.js +21 -0
- package/dist/live-page-middleware.d.ts +6 -0
- package/dist/live-page-middleware.js +28 -0
- package/dist/live-page-settings.d.ts +20 -0
- package/dist/live-page-settings.js +24 -0
- package/dist/live-plugin-protocol.d.ts +3 -0
- package/dist/live-plugin-protocol.js +13 -0
- package/dist/live-plugin.d.ts +13 -0
- package/dist/live-plugin.js +148 -0
- package/dist/live-project-plugins.d.ts +8 -0
- package/dist/live-project-plugins.js +23 -0
- package/dist/live-registrar.d.ts +10 -0
- package/dist/live-registrar.js +174 -0
- package/dist/live-registry.d.ts +14 -0
- package/dist/live-registry.js +14 -0
- package/dist/live-runtime.d.ts +2 -0
- package/dist/live-runtime.js +3 -0
- package/dist/live-setup-files.d.ts +15 -0
- package/dist/live-setup-files.js +56 -0
- package/dist/live-shim-source.d.ts +6 -0
- package/dist/live-shim-source.js +9 -0
- package/dist/live-spy-on.d.ts +7 -0
- package/dist/live-spy-on.js +27 -0
- package/dist/live-step.d.ts +2 -0
- package/dist/live-step.js +4 -0
- package/dist/live-string-literal.d.ts +2 -0
- package/dist/live-string-literal.js +7 -0
- package/dist/live-suite.d.ts +28 -0
- package/dist/live-suite.js +18 -0
- package/dist/live-test-context.d.ts +24 -0
- package/dist/live-test-context.js +26 -0
- package/dist/live-test-files-source.d.ts +6 -0
- package/dist/live-test-files-source.js +10 -0
- package/dist/live-vi.d.ts +7 -0
- package/dist/live-vi.js +50 -0
- package/dist/live.d.ts +2 -0
- package/dist/live.js +3 -0
- package/dist/load-live-config.d.ts +9 -0
- package/dist/load-live-config.js +25 -0
- package/dist/load-profile.d.ts +4 -0
- package/dist/load-profile.js +9 -0
- package/dist/notice-live-module-mock.d.ts +5 -0
- package/dist/notice-live-module-mock.js +25 -0
- package/dist/package-name-of.d.ts +5 -0
- package/dist/package-name-of.js +7 -0
- package/dist/plugin.d.ts +11 -2
- package/dist/plugin.js +40 -86
- package/dist/read-live-browser-names.d.ts +5 -0
- package/dist/read-live-browser-names.js +19 -0
- package/dist/read-live-params.d.ts +18 -0
- package/dist/read-live-params.js +63 -0
- package/dist/read-live-vitest-major.d.ts +6 -0
- package/dist/read-live-vitest-major.js +20 -0
- package/dist/read-live-vitest-names.d.ts +5 -0
- package/dist/read-live-vitest-names.js +40 -0
- package/dist/register-recording-hooks.d.ts +2 -1
- package/dist/register-recording-hooks.js +8 -7
- package/dist/report-live-errors.d.ts +5 -0
- package/dist/report-live-errors.js +24 -0
- package/dist/reporter.d.ts +3 -0
- package/dist/reporter.js +9 -0
- package/dist/run-live-test.d.ts +10 -0
- package/dist/run-live-test.js +118 -0
- package/dist/set-up-project.d.ts +12 -0
- package/dist/set-up-project.js +39 -0
- package/dist/start-live-page.d.ts +29 -0
- package/dist/start-live-page.js +62 -0
- package/dist/validate-live-option.d.ts +11 -0
- package/dist/validate-live-option.js +29 -0
- package/package.json +11 -3
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const MAX_LENGTH = 80;
|
|
2
|
+
function cut(text) {
|
|
3
|
+
return text.length > MAX_LENGTH ? `${text.slice(0, MAX_LENGTH)}…` : text;
|
|
4
|
+
}
|
|
5
|
+
/** A DOM event, or one of React's synthetic events, which carry the native one. */
|
|
6
|
+
function isEvent(value) {
|
|
7
|
+
if (value instanceof Event) {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
return 'nativeEvent' in value && typeof value.type === 'string';
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A short label for one argument of a mock call, such as `"Rename"`,
|
|
14
|
+
* `click event`, `<button>` or `ƒ onClose`, for the viewer's list of actions.
|
|
15
|
+
*/
|
|
16
|
+
export function liveArgLabel(value) {
|
|
17
|
+
if (typeof value === 'string') {
|
|
18
|
+
return JSON.stringify(cut(value));
|
|
19
|
+
}
|
|
20
|
+
if (typeof value === 'function') {
|
|
21
|
+
return `ƒ ${value.name || 'anonymous'}`;
|
|
22
|
+
}
|
|
23
|
+
if (typeof value !== 'object' || value === null) {
|
|
24
|
+
return String(value);
|
|
25
|
+
}
|
|
26
|
+
if (isEvent(value)) {
|
|
27
|
+
return `${value.type} event`;
|
|
28
|
+
}
|
|
29
|
+
if (value instanceof Element) {
|
|
30
|
+
return `<${value.tagName.toLowerCase()}>`;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
return cut(JSON.stringify(value));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return Array.isArray(value) ? '[…]' : '{…}';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Vite } from 'vitest/node';
|
|
2
|
+
/** What the plugin of a live build takes from `buildLive()`. */
|
|
3
|
+
export interface LiveBuildPluginOptions {
|
|
4
|
+
/** How messages name the config file. */
|
|
5
|
+
configName: string;
|
|
6
|
+
/** The test files of the map, as paths from the root with `/`. */
|
|
7
|
+
testFiles: string[];
|
|
8
|
+
/** Takes the live protocol of the project's describeMe() plugin, once the config is resolved. */
|
|
9
|
+
onProtocol(protocol: number): void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The plugin `buildLive()` adds after the project's own. It runs the checks
|
|
13
|
+
* of `createLiveServer()` before anything is bundled, loads the map of test
|
|
14
|
+
* files, and stands in for each import of a project file that does not load
|
|
15
|
+
* in the browser, such as `node:fs`. A stand-in throws when it runs, so only
|
|
16
|
+
* the test files and setup files that import it fail, and the build goes on.
|
|
17
|
+
*/
|
|
18
|
+
export declare function liveBuildPlugin(options: LiveBuildPluginOptions): Vite.Plugin;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { relative } from 'node:path';
|
|
2
|
+
import { checkLiveEnabled } from './check-live-enabled.js';
|
|
3
|
+
import { LIVE_MODULE_IDS } from './live-module-ids.js';
|
|
4
|
+
import { livePluginProtocol } from './live-plugin-protocol.js';
|
|
5
|
+
import { liveStringLiteral } from './live-string-literal.js';
|
|
6
|
+
import { liveTestFilesSource } from './live-test-files-source.js';
|
|
7
|
+
// The specifier stays in the id and the reason does not, so chunk names stay plain.
|
|
8
|
+
const UNLOADABLE = '\0describe-me-live:unloadable:';
|
|
9
|
+
// Vite's stand-in for a Node module in a browser build.
|
|
10
|
+
const BROWSER_EXTERNAL = '__vite-browser-external';
|
|
11
|
+
const NODE_MODULES = /[\\/]node_modules[\\/]/;
|
|
12
|
+
function isProjectModule(importer) {
|
|
13
|
+
return importer !== undefined && !importer.startsWith('\0') && !NODE_MODULES.test(importer);
|
|
14
|
+
}
|
|
15
|
+
function firstLine(error) {
|
|
16
|
+
return (error instanceof Error ? error.message : String(error)).split('\n')[0];
|
|
17
|
+
}
|
|
18
|
+
/** Whether a log is the bundler's note that it shimmed an export of a stand-in. */
|
|
19
|
+
function isShimmedStandIn(log) {
|
|
20
|
+
const text = `${log.message} ${log.exporter ?? ''} ${log.id ?? ''}`;
|
|
21
|
+
return /shimmed/i.test(log.message) && text.includes(UNLOADABLE.slice(1));
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The plugin `buildLive()` adds after the project's own. It runs the checks
|
|
25
|
+
* of `createLiveServer()` before anything is bundled, loads the map of test
|
|
26
|
+
* files, and stands in for each import of a project file that does not load
|
|
27
|
+
* in the browser, such as `node:fs`. A stand-in throws when it runs, so only
|
|
28
|
+
* the test files and setup files that import it fail, and the build goes on.
|
|
29
|
+
*/
|
|
30
|
+
export function liveBuildPlugin(options) {
|
|
31
|
+
const reasons = new Map();
|
|
32
|
+
const warned = new Set();
|
|
33
|
+
let root = '';
|
|
34
|
+
let logger = null;
|
|
35
|
+
function warnOnce(importer, specifier, reason) {
|
|
36
|
+
const key = JSON.stringify([importer, specifier]);
|
|
37
|
+
if (warned.has(key)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
warned.add(key);
|
|
41
|
+
logger?.warn(`describe-me: ${relative(root, importer)} imports ${specifier}, which does not load in the browser: ${reason}`);
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
name: 'describe-me:live-build',
|
|
45
|
+
enforce: 'pre',
|
|
46
|
+
configResolved(config) {
|
|
47
|
+
options.onProtocol(livePluginProtocol(config.plugins, options.configName));
|
|
48
|
+
checkLiveEnabled(config.plugins, options.configName);
|
|
49
|
+
root = config.root;
|
|
50
|
+
logger = config.logger;
|
|
51
|
+
},
|
|
52
|
+
async resolveId(source, importer, resolveOptions) {
|
|
53
|
+
if (!isProjectModule(importer)) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
let reason;
|
|
57
|
+
try {
|
|
58
|
+
const resolved = await this.resolve(source, importer, { ...resolveOptions, skipSelf: true });
|
|
59
|
+
if (resolved !== null && !resolved.id.startsWith(BROWSER_EXTERNAL)) {
|
|
60
|
+
return resolved;
|
|
61
|
+
}
|
|
62
|
+
reason = resolved === null ? 'it cannot be resolved' : 'it is a Node module';
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
reason = firstLine(error);
|
|
66
|
+
}
|
|
67
|
+
const id = `${UNLOADABLE}${source}`;
|
|
68
|
+
reasons.set(id, reason);
|
|
69
|
+
warnOnce(importer, source, reason);
|
|
70
|
+
return id;
|
|
71
|
+
},
|
|
72
|
+
load(id) {
|
|
73
|
+
if (id === LIVE_MODULE_IDS.testFiles) {
|
|
74
|
+
return liveTestFilesSource(options.testFiles);
|
|
75
|
+
}
|
|
76
|
+
const reason = reasons.get(id);
|
|
77
|
+
if (reason === undefined) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const message = `${id.slice(UNLOADABLE.length)} does not load in the browser: ${reason}`;
|
|
81
|
+
return { code: `throw new Error(${liveStringLiteral(message)})\n`, moduleSideEffects: true };
|
|
82
|
+
},
|
|
83
|
+
onLog(level, log) {
|
|
84
|
+
if (level === 'warn' && isShimmedStandIn(log)) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The name of one row of `each` or `for`, a port of Vitest's `formatTitle`
|
|
3
|
+
* with its quirks, because the manifest holds the names Vitest gave. Vitest 5
|
|
4
|
+
* and 4 differ in how they print values, so `vitestMajor` picks the rules.
|
|
5
|
+
*/
|
|
6
|
+
export declare function liveEachName(template: string, items: unknown[], index: number, vitestMajor: number): string;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { liveFormatPlaceholder } from './live-format-placeholder.js';
|
|
2
|
+
import { liveInspect } from './live-inspect.js';
|
|
3
|
+
import { liveLoupeInspect } from './live-loupe-inspect.js';
|
|
4
|
+
const PLACEHOLDER = /%[sdjifoOc%]/g;
|
|
5
|
+
const ARRAY_KEY = /^\d+$/;
|
|
6
|
+
// `taskTitleValueFormatTruncate` in Vitest 5, `chaiConfig.truncateThreshold` in Vitest 4.
|
|
7
|
+
const TRUNCATE = 40;
|
|
8
|
+
function isObject(item) {
|
|
9
|
+
return item !== null && item !== undefined && typeof item === 'object' && !Array.isArray(item);
|
|
10
|
+
}
|
|
11
|
+
function isNegativeNaN(value) {
|
|
12
|
+
if (!Number.isNaN(value)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
const float = new Float64Array(1);
|
|
16
|
+
float[0] = value;
|
|
17
|
+
return new Uint32Array(float.buffer)[1] >>> 31 === 1;
|
|
18
|
+
}
|
|
19
|
+
/** Vitest's `objectAttr`: a property path such as `a.b` or `a[0]`, or `fallback` where it ends. */
|
|
20
|
+
function objectAttr(source, path, fallback) {
|
|
21
|
+
const parts = path.replace(/\[(\d+)\]/g, '.$1').split('.');
|
|
22
|
+
let result = source;
|
|
23
|
+
for (const part of parts) {
|
|
24
|
+
result = Object(result)[part];
|
|
25
|
+
if (result === undefined) {
|
|
26
|
+
return fallback;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
/** Vitest 5's `truncateString`. */
|
|
32
|
+
function truncateString(text, maxLength) {
|
|
33
|
+
if (text.length <= maxLength) {
|
|
34
|
+
return text;
|
|
35
|
+
}
|
|
36
|
+
let end = maxLength - 1;
|
|
37
|
+
const before = text[end - 1];
|
|
38
|
+
if (before >= '\ud800' && before <= '\udbff') {
|
|
39
|
+
end -= 1;
|
|
40
|
+
}
|
|
41
|
+
return `${text.slice(0, end)}…`;
|
|
42
|
+
}
|
|
43
|
+
/** Vitest 4's `objDisplay`: loupe's output, or a summary once it reaches the threshold. */
|
|
44
|
+
function objDisplay(value) {
|
|
45
|
+
const text = liveLoupeInspect(value, TRUNCATE);
|
|
46
|
+
if (text.length < TRUNCATE) {
|
|
47
|
+
return text;
|
|
48
|
+
}
|
|
49
|
+
const tag = Object.prototype.toString.call(value);
|
|
50
|
+
if (tag === '[object Function]') {
|
|
51
|
+
const { name } = value;
|
|
52
|
+
return name ? `[Function: ${name}]` : '[Function]';
|
|
53
|
+
}
|
|
54
|
+
if (tag === '[object Array]') {
|
|
55
|
+
return `[ Array(${value.length}) ]`;
|
|
56
|
+
}
|
|
57
|
+
if (tag === '[object Object]') {
|
|
58
|
+
const keys = Object.keys(value);
|
|
59
|
+
const shown = keys.length > 2 ? `${keys.splice(0, 2).join(', ')}, ...` : keys.join(', ');
|
|
60
|
+
return `{ Object (${shown}) }`;
|
|
61
|
+
}
|
|
62
|
+
return text;
|
|
63
|
+
}
|
|
64
|
+
const VITEST_5 = {
|
|
65
|
+
key: /\$([$\p{ID_Continue}.]+)/gu,
|
|
66
|
+
display: (value) => typeof value === 'string' ? truncateString(value, TRUNCATE) : liveInspect(value, TRUNCATE),
|
|
67
|
+
inspect: (value) => liveInspect(value, TRUNCATE),
|
|
68
|
+
};
|
|
69
|
+
const VITEST_4 = {
|
|
70
|
+
key: /\$([$\w.]+)/g,
|
|
71
|
+
display: objDisplay,
|
|
72
|
+
inspect: (value) => liveLoupeInspect(value, Number.POSITIVE_INFINITY),
|
|
73
|
+
};
|
|
74
|
+
/** `%#` and `%$`, the 0-based and 1-based row index, go in before any value. */
|
|
75
|
+
function withIndex(template, index) {
|
|
76
|
+
if (!template.includes('%#') && !template.includes('%$')) {
|
|
77
|
+
return template;
|
|
78
|
+
}
|
|
79
|
+
return template
|
|
80
|
+
.replace(/%%/g, '__vitest_escaped_%__')
|
|
81
|
+
.replace(/%#/g, `${index}`)
|
|
82
|
+
.replace(/%\$/g, `${index + 1}`)
|
|
83
|
+
.replace(/__vitest_escaped_%__/g, '%%');
|
|
84
|
+
}
|
|
85
|
+
/** Vitest puts a `-` before the i-th `%f` when the i-th value, not that `%f`'s own, is -0 or a negative NaN. */
|
|
86
|
+
function withNegativeFloats(template, items) {
|
|
87
|
+
if (!template.includes('%f')) {
|
|
88
|
+
return template;
|
|
89
|
+
}
|
|
90
|
+
let result = template;
|
|
91
|
+
const placeholders = template.match(/%f/g) ?? [];
|
|
92
|
+
placeholders.forEach((_, i) => {
|
|
93
|
+
if (isNegativeNaN(items[i]) || Object.is(items[i], -0)) {
|
|
94
|
+
let occurrence = 0;
|
|
95
|
+
result = result.replace(/%f/g, (match) => {
|
|
96
|
+
occurrence += 1;
|
|
97
|
+
return occurrence === i + 1 ? '-%f' : match;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
/** `$key`, `$a.b` and `$0` outside the placeholders. */
|
|
104
|
+
function withKeys(text, items, rules) {
|
|
105
|
+
const isObjectItem = isObject(items[0]);
|
|
106
|
+
return text.replace(rules.key, (_, key) => {
|
|
107
|
+
const isArrayKey = ARRAY_KEY.test(key);
|
|
108
|
+
if (!isObjectItem && !isArrayKey) {
|
|
109
|
+
return `$${key}`;
|
|
110
|
+
}
|
|
111
|
+
const arrayElement = isArrayKey ? objectAttr(items, key) : undefined;
|
|
112
|
+
const value = isObjectItem ? objectAttr(items[0], key, arrayElement) : arrayElement;
|
|
113
|
+
return rules.display(value);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The name of one row of `each` or `for`, a port of Vitest's `formatTitle`
|
|
118
|
+
* with its quirks, because the manifest holds the names Vitest gave. Vitest 5
|
|
119
|
+
* and 4 differ in how they print values, so `vitestMajor` picks the rules.
|
|
120
|
+
*/
|
|
121
|
+
export function liveEachName(template, items, index, vitestMajor) {
|
|
122
|
+
const rules = vitestMajor >= 5 ? VITEST_5 : VITEST_4;
|
|
123
|
+
const indexed = withIndex(template, index);
|
|
124
|
+
const count = indexed.split('%').length - 1;
|
|
125
|
+
const named = withNegativeFloats(indexed, items);
|
|
126
|
+
let output = '';
|
|
127
|
+
let next = 0;
|
|
128
|
+
let end = 0;
|
|
129
|
+
for (const match of named.matchAll(PLACEHOLDER)) {
|
|
130
|
+
if (end < match.index) {
|
|
131
|
+
output += withKeys(named.slice(end, match.index), items, rules);
|
|
132
|
+
}
|
|
133
|
+
if (next < count) {
|
|
134
|
+
output += liveFormatPlaceholder(match[0], items[next], rules.inspect);
|
|
135
|
+
next += 1;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
output += match[0];
|
|
139
|
+
}
|
|
140
|
+
end = match.index + match[0].length;
|
|
141
|
+
}
|
|
142
|
+
if (end < named.length) {
|
|
143
|
+
output += withKeys(named.slice(end), items, rules);
|
|
144
|
+
}
|
|
145
|
+
return output;
|
|
146
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LivePageSettings } from './live-page-settings.js';
|
|
2
|
+
/**
|
|
3
|
+
* The source of the live page's entry. On the dev server the `import()` of
|
|
4
|
+
* the test file stays here, in a module of the project's server, so it loads
|
|
5
|
+
* from the server. A build imports it from the map of lazy imports, so the
|
|
6
|
+
* bundler sees every test file. Setup files are literal imports, so Vite
|
|
7
|
+
* resolves them and a build bundles them.
|
|
8
|
+
*/
|
|
9
|
+
export declare function liveEntrySource(settings: Pick<LivePageSettings, 'globals' | 'testTimeout' | 'setupFiles' | 'vitestMajor'>, command: 'serve' | 'build'): string;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { LIVE_MODULE_IDS } from './live-module-ids.js';
|
|
2
|
+
import { liveStringLiteral } from './live-string-literal.js';
|
|
3
|
+
/**
|
|
4
|
+
* The `importTest` of the dev server's entry. It imports an absolute URL:
|
|
5
|
+
* Vite adds `?import` to a path from the root, and the static self-import
|
|
6
|
+
* that `@vitejs/plugin-react` adds to a file with a component would then load
|
|
7
|
+
* the test file a second time.
|
|
8
|
+
*/
|
|
9
|
+
const SERVE_IMPORT_TEST = [
|
|
10
|
+
` importTest: (file) => import(/* @vite-ignore */ new URL('/' + file, location.origin).href),`,
|
|
11
|
+
];
|
|
12
|
+
/** The `importTest` of a build, which holds only the test files of its map. */
|
|
13
|
+
const BUILD_IMPORT_TEST = [
|
|
14
|
+
' importTest: (file) =>',
|
|
15
|
+
' Object.hasOwn(testFiles, file)',
|
|
16
|
+
' ? testFiles[file]()',
|
|
17
|
+
' : Promise.reject(new Error(`${file} is not in this build`)),',
|
|
18
|
+
];
|
|
19
|
+
/**
|
|
20
|
+
* The source of the live page's entry. On the dev server the `import()` of
|
|
21
|
+
* the test file stays here, in a module of the project's server, so it loads
|
|
22
|
+
* from the server. A build imports it from the map of lazy imports, so the
|
|
23
|
+
* bundler sees every test file. Setup files are literal imports, so Vite
|
|
24
|
+
* resolves them and a build bundles them.
|
|
25
|
+
*/
|
|
26
|
+
export function liveEntrySource(settings, command) {
|
|
27
|
+
const setupFiles = settings.setupFiles.map((setupFile) => ` { file: ${liveStringLiteral(setupFile.name)}, load: () => import(${liveStringLiteral(setupFile.specifier)}) },`);
|
|
28
|
+
const testFilesImport = command === 'build'
|
|
29
|
+
? [`import testFiles from ${liveStringLiteral(LIVE_MODULE_IDS.testFiles)}`]
|
|
30
|
+
: [];
|
|
31
|
+
return [
|
|
32
|
+
`import { startLivePage } from '@describe-me/vitest/live-runtime'`,
|
|
33
|
+
...testFilesImport,
|
|
34
|
+
'',
|
|
35
|
+
'startLivePage({',
|
|
36
|
+
...(command === 'build' ? BUILD_IMPORT_TEST : SERVE_IMPORT_TEST),
|
|
37
|
+
` globals: ${String(settings.globals)},`,
|
|
38
|
+
` testTimeout: ${String(settings.testTimeout)},`,
|
|
39
|
+
` vitestMajor: ${String(settings.vitestMajor)},`,
|
|
40
|
+
' setupFiles: [',
|
|
41
|
+
...setupFiles,
|
|
42
|
+
' ],',
|
|
43
|
+
'})',
|
|
44
|
+
'',
|
|
45
|
+
].join('\n');
|
|
46
|
+
}
|
package/dist/live-env.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const STACK_LINES = 4;
|
|
2
|
+
/** An error's message and the first lines of its stack, for the detail of a status. */
|
|
3
|
+
export function liveErrorDetail(error) {
|
|
4
|
+
if (!(error instanceof Error)) {
|
|
5
|
+
return String(error);
|
|
6
|
+
}
|
|
7
|
+
const heading = String(error);
|
|
8
|
+
const stack = error.stack ?? '';
|
|
9
|
+
// V8 starts the stack with the heading. Firefox and Safari do not.
|
|
10
|
+
const frames = (stack.startsWith(heading) ? stack.slice(heading.length) : stack)
|
|
11
|
+
.split('\n')
|
|
12
|
+
.map((line) => line.trim())
|
|
13
|
+
.filter((line) => line !== '')
|
|
14
|
+
.slice(0, STACK_LINES);
|
|
15
|
+
return [heading, ...frames].join('\n');
|
|
16
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The modules that the live page swaps for shims. */
|
|
2
|
+
type LiveModule = 'vitest' | 'vitest/browser' | '@describe-me/vitest';
|
|
3
|
+
/**
|
|
4
|
+
* The live page's implementation of one export of a module it swaps for a
|
|
5
|
+
* shim. The plugin's generated modules call it once per name. A name the shim
|
|
6
|
+
* does not implement, such as `expect` or one that a newer Vitest adds, gets an
|
|
7
|
+
* inert value.
|
|
8
|
+
*/
|
|
9
|
+
export declare function liveExport(module: LiveModule, name: string): unknown;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { liveInert } from '@describe-me/core/live';
|
|
2
|
+
import { liveHook } from './live-hook.js';
|
|
3
|
+
import { liveRegistrar } from './live-registrar.js';
|
|
4
|
+
import { liveStep } from './live-step.js';
|
|
5
|
+
import { liveVi } from './live-vi.js';
|
|
6
|
+
let shims;
|
|
7
|
+
function ignore() {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
function createShims() {
|
|
11
|
+
const describe = liveRegistrar('suite');
|
|
12
|
+
const it = liveRegistrar('test');
|
|
13
|
+
const vi = liveVi();
|
|
14
|
+
return {
|
|
15
|
+
vitest: {
|
|
16
|
+
describe,
|
|
17
|
+
suite: describe,
|
|
18
|
+
it,
|
|
19
|
+
test: it,
|
|
20
|
+
beforeAll: liveHook('beforeAll'),
|
|
21
|
+
beforeEach: liveHook('beforeEach'),
|
|
22
|
+
afterEach: liveHook('afterEach'),
|
|
23
|
+
afterAll: liveHook('afterAll'),
|
|
24
|
+
aroundEach: liveHook('aroundEach'),
|
|
25
|
+
aroundAll: liveHook('aroundAll'),
|
|
26
|
+
onTestFinished: ignore,
|
|
27
|
+
onTestFailed: ignore,
|
|
28
|
+
vi,
|
|
29
|
+
vitest: vi,
|
|
30
|
+
assertType: ignore,
|
|
31
|
+
inject: ignore,
|
|
32
|
+
},
|
|
33
|
+
// The page never interacts, so all of `vitest/browser` is inert.
|
|
34
|
+
'vitest/browser': {},
|
|
35
|
+
'@describe-me/vitest': { step: liveStep },
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The live page's implementation of one export of a module it swaps for a
|
|
40
|
+
* shim. The plugin's generated modules call it once per name. A name the shim
|
|
41
|
+
* does not implement, such as `expect` or one that a newer Vitest adds, gets an
|
|
42
|
+
* inert value.
|
|
43
|
+
*/
|
|
44
|
+
export function liveExport(module, name) {
|
|
45
|
+
shims ??= createShims();
|
|
46
|
+
const shim = shims[module];
|
|
47
|
+
return Object.hasOwn(shim, name) ? shim[name] : liveInert();
|
|
48
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** A callback as written, and which of its parameters destructures the fixtures it uses. */
|
|
2
|
+
export interface LiveFixtureUse {
|
|
3
|
+
callback: (...args: never[]) => unknown;
|
|
4
|
+
/** 0 in general, 1 for an `aroundEach` hook and a `for` body. */
|
|
5
|
+
index: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The fixture names a callback destructures, read from its source the way
|
|
9
|
+
* Vitest's `getUsedProps` reads them. Throws when the parameter exists but
|
|
10
|
+
* does not destructure, or uses a rest element, as Vitest does.
|
|
11
|
+
*/
|
|
12
|
+
export declare function liveFixtureProps({ callback, index }: LiveFixtureUse): Set<string>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// esbuild's lowered async functions, which Vitest strips the same way.
|
|
2
|
+
const LOWERED_ASYNC = /__async\((?:this|null), (?:null|arguments|\[[_0-9, ]*\]), function\*/;
|
|
3
|
+
const LOWERED_ASYNC_HEAD = /__async\((?:this|null),/;
|
|
4
|
+
const PARAMETERS = /[^(]*\(([^)]*)/;
|
|
5
|
+
const POSITIONS = ['first', 'second'];
|
|
6
|
+
/** The source without its comments, as Vitest's `filterOutComments` reads it. */
|
|
7
|
+
function withoutComments(source) {
|
|
8
|
+
const kept = [];
|
|
9
|
+
let state = 'none';
|
|
10
|
+
for (let i = 0; i < source.length; ++i) {
|
|
11
|
+
if (state === 'line') {
|
|
12
|
+
if (source[i] === '\n') {
|
|
13
|
+
state = 'none';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
else if (state === 'block') {
|
|
17
|
+
if (source[i - 1] === '*' && source[i] === '/') {
|
|
18
|
+
state = 'none';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
else if (source[i] === '/' && source[i + 1] === '/') {
|
|
22
|
+
state = 'line';
|
|
23
|
+
}
|
|
24
|
+
else if (source[i] === '/' && source[i + 1] === '*') {
|
|
25
|
+
state = 'block';
|
|
26
|
+
i += 2;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
kept.push(source[i]);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return kept.join('');
|
|
33
|
+
}
|
|
34
|
+
/** Splits on the commas outside `{}` and `[]`, as Vitest's `splitByComma` does. */
|
|
35
|
+
function splitByComma(text) {
|
|
36
|
+
const parts = [];
|
|
37
|
+
const closers = [];
|
|
38
|
+
let start = 0;
|
|
39
|
+
for (let i = 0; i < text.length; i++) {
|
|
40
|
+
if (text[i] === '{' || text[i] === '[') {
|
|
41
|
+
closers.push(text[i] === '{' ? '}' : ']');
|
|
42
|
+
}
|
|
43
|
+
else if (text[i] === closers.at(-1)) {
|
|
44
|
+
closers.pop();
|
|
45
|
+
}
|
|
46
|
+
else if (closers.length === 0 && text[i] === ',') {
|
|
47
|
+
const part = text.substring(start, i).trim();
|
|
48
|
+
if (part) {
|
|
49
|
+
parts.push(part);
|
|
50
|
+
}
|
|
51
|
+
start = i + 1;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const last = text.substring(start).trim();
|
|
55
|
+
if (last) {
|
|
56
|
+
parts.push(last);
|
|
57
|
+
}
|
|
58
|
+
return parts;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The fixture names a callback destructures, read from its source the way
|
|
62
|
+
* Vitest's `getUsedProps` reads them. Throws when the parameter exists but
|
|
63
|
+
* does not destructure, or uses a rest element, as Vitest does.
|
|
64
|
+
*/
|
|
65
|
+
export function liveFixtureProps({ callback, index }) {
|
|
66
|
+
let source = withoutComments(callback.toString());
|
|
67
|
+
if (LOWERED_ASYNC.test(source)) {
|
|
68
|
+
source = source.split(LOWERED_ASYNC_HEAD)[1];
|
|
69
|
+
}
|
|
70
|
+
const match = PARAMETERS.exec(source);
|
|
71
|
+
if (!match) {
|
|
72
|
+
return new Set();
|
|
73
|
+
}
|
|
74
|
+
const parameter = splitByComma(match[1])[index];
|
|
75
|
+
if (!parameter) {
|
|
76
|
+
return new Set();
|
|
77
|
+
}
|
|
78
|
+
if (!(parameter.startsWith('{') && parameter.endsWith('}'))) {
|
|
79
|
+
throw new Error(`the ${POSITIONS[index] ?? `#${index + 1}`} parameter must destructure the fixtures it uses, such as ({ user }) => …, not "${parameter}"`);
|
|
80
|
+
}
|
|
81
|
+
const props = splitByComma(parameter.slice(1, -1).replace(/\s/g, '')).map((prop) => prop.replace(/:.*|=.*/g, ''));
|
|
82
|
+
const last = props.at(-1);
|
|
83
|
+
if (last?.startsWith('...')) {
|
|
84
|
+
throw new Error(`fixtures cannot be read with a rest element, got "${last}"`);
|
|
85
|
+
}
|
|
86
|
+
return new Set(props);
|
|
87
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { LiveCallback } from './live-suite.js';
|
|
2
|
+
/** One fixture of `test.extend`, as the live page registers it. */
|
|
3
|
+
export interface LiveFixture {
|
|
4
|
+
name: string;
|
|
5
|
+
/** The value, or the function `({ deps }, use) => …` that computes it. */
|
|
6
|
+
value: unknown;
|
|
7
|
+
auto: boolean;
|
|
8
|
+
/** The function whose first parameter names the dependencies: the builder function for the builder syntax. */
|
|
9
|
+
source?: LiveCallback;
|
|
10
|
+
/** The definition this one redefines, which a fixture that depends on its own name gets. */
|
|
11
|
+
parent?: LiveFixture;
|
|
12
|
+
}
|
|
13
|
+
/** The fixtures of one chain, by name. */
|
|
14
|
+
export type LiveFixtures = ReadonlyMap<string, LiveFixture>;
|
|
15
|
+
/**
|
|
16
|
+
* The fixtures of a chain after `extend(…)`, in the object syntax of Vitest
|
|
17
|
+
* 4.1 and 5 or their builder syntax `extend(name, options?, value)`. A
|
|
18
|
+
* redefined fixture keeps the earlier one as its parent. An `injected`
|
|
19
|
+
* fixture keeps its default value, and every scope counts as the test's.
|
|
20
|
+
*/
|
|
21
|
+
export declare function liveFixtureRegistrations(fixtures: LiveFixtures, args: unknown[]): LiveFixtures;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const OPTION_KEYS = ['auto', 'injected', 'scope'];
|
|
2
|
+
function isObject(value) {
|
|
3
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
function isFixtureOptions(value) {
|
|
6
|
+
return isObject(value) && Object.keys(value).some((key) => OPTION_KEYS.includes(key));
|
|
7
|
+
}
|
|
8
|
+
function ignore() {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
/** A builder function `(context, { onCleanup }) => value` as a fixture function. Cleanups never run. */
|
|
12
|
+
function builderFixture(builder) {
|
|
13
|
+
return async (context, use) => {
|
|
14
|
+
await use(await builder(context, { onCleanup: ignore }));
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** The arguments of `extend`, in the object syntax, as Vitest's `parseBuilderFixtures` reads them. */
|
|
18
|
+
function definitionsOf(args) {
|
|
19
|
+
const [first, second, third] = args;
|
|
20
|
+
if (typeof first !== 'string') {
|
|
21
|
+
return { definitions: isObject(first) ? first : {} };
|
|
22
|
+
}
|
|
23
|
+
let options;
|
|
24
|
+
let value;
|
|
25
|
+
if (third !== undefined) {
|
|
26
|
+
options = second;
|
|
27
|
+
value = third;
|
|
28
|
+
}
|
|
29
|
+
else if (isFixtureOptions(second)) {
|
|
30
|
+
options = second;
|
|
31
|
+
value = {};
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
value = second;
|
|
35
|
+
}
|
|
36
|
+
const builder = typeof value === 'function' ? value : undefined;
|
|
37
|
+
const fixture = builder === undefined ? value : builderFixture(builder);
|
|
38
|
+
return { definitions: { [first]: options ? [fixture, options] : fixture }, source: builder };
|
|
39
|
+
}
|
|
40
|
+
function register(registrations, name, definition, source) {
|
|
41
|
+
const hasOptions = Array.isArray(definition) && definition.length >= 2 && isFixtureOptions(definition[1]);
|
|
42
|
+
const value = hasOptions ? definition[0] : definition;
|
|
43
|
+
const parent = registrations.get(name);
|
|
44
|
+
let auto = parent?.auto ?? false;
|
|
45
|
+
if (hasOptions) {
|
|
46
|
+
auto = Boolean(definition[1].auto);
|
|
47
|
+
}
|
|
48
|
+
registrations.set(name, {
|
|
49
|
+
name,
|
|
50
|
+
value,
|
|
51
|
+
auto,
|
|
52
|
+
source: source ?? (typeof value === 'function' ? value : undefined),
|
|
53
|
+
parent,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The fixtures of a chain after `extend(…)`, in the object syntax of Vitest
|
|
58
|
+
* 4.1 and 5 or their builder syntax `extend(name, options?, value)`. A
|
|
59
|
+
* redefined fixture keeps the earlier one as its parent. An `injected`
|
|
60
|
+
* fixture keeps its default value, and every scope counts as the test's.
|
|
61
|
+
*/
|
|
62
|
+
export function liveFixtureRegistrations(fixtures, args) {
|
|
63
|
+
const registrations = new Map(fixtures);
|
|
64
|
+
const { definitions, source } = definitionsOf(args);
|
|
65
|
+
for (const [name, definition] of Object.entries(definitions)) {
|
|
66
|
+
register(registrations, name, definition, source);
|
|
67
|
+
}
|
|
68
|
+
return registrations;
|
|
69
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type LiveFixtureUse } from './live-fixture-props.js';
|
|
2
|
+
import type { LiveFixture, LiveFixtures } from './live-fixture-registrations.js';
|
|
3
|
+
/**
|
|
4
|
+
* Computes the fixtures that one test-scope callback uses before it runs:
|
|
5
|
+
* those its parameter destructures and every `auto` fixture, dependencies
|
|
6
|
+
* first. Each fixture runs once per page, and its value becomes a property of
|
|
7
|
+
* the context. A fixture that fails throws an error that names it.
|
|
8
|
+
*/
|
|
9
|
+
export declare function liveFixtureValues(fixtures: LiveFixtures, use: LiveFixtureUse | null, context: Record<string, unknown>, computed: Set<LiveFixture>): Promise<void>;
|