@describe-me/vitest 0.5.3 → 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.
- package/README.md +61 -92
- package/dist/browser-profile.d.ts +3 -0
- package/dist/browser-profile.js +26 -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 +39 -0
- package/dist/environment-profile.d.ts +35 -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 +29 -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/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 +16 -5
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createServer } from 'node:net';
|
|
2
|
+
/**
|
|
3
|
+
* A port that is free on localhost right now. Older Vite majors keep `port: 0`
|
|
4
|
+
* in their resolved config, which breaks the address HMR falls back to.
|
|
5
|
+
*/
|
|
6
|
+
export function freeLivePort() {
|
|
7
|
+
return new Promise((resolvePort, reject) => {
|
|
8
|
+
const probe = createServer();
|
|
9
|
+
probe.once('error', reject);
|
|
10
|
+
probe.listen(0, 'localhost', () => {
|
|
11
|
+
const address = probe.address();
|
|
12
|
+
const port = typeof address === 'object' && address !== null ? address.port : 0;
|
|
13
|
+
probe.close(() => resolvePort(port));
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Vite } from 'vitest/node';
|
|
2
|
+
/**
|
|
3
|
+
* The project's own Vite, so its plugins run on the version they were made
|
|
4
|
+
* for. Always the ES module build: `require.resolve('vite')` can pick the
|
|
5
|
+
* CommonJS one of older majors.
|
|
6
|
+
*/
|
|
7
|
+
export declare function importLiveVite(root: string): Promise<typeof Vite>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
/** The `package.json` of the project's Vite, or of the Vite its Vitest runs on. */
|
|
6
|
+
function vitePackageJson(root) {
|
|
7
|
+
const projectRequire = createRequire(join(root, 'package.json'));
|
|
8
|
+
try {
|
|
9
|
+
return projectRequire.resolve('vite/package.json');
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
// Not a direct dependency of the project. Vitest's own Vite runs the tests.
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const vitest = projectRequire.resolve('vitest/package.json');
|
|
16
|
+
return createRequire(vitest).resolve('vite/package.json');
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw new Error(`describe-me: cannot find vite from ${root}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** The ES module entry of `exports['.']`: its `import` condition, or the string itself. */
|
|
23
|
+
function esmEntry(target) {
|
|
24
|
+
if (typeof target === 'string') {
|
|
25
|
+
return target;
|
|
26
|
+
}
|
|
27
|
+
if (typeof target !== 'object' || target === null) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const conditions = target;
|
|
31
|
+
return esmEntry(conditions.import ?? conditions.default);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The project's own Vite, so its plugins run on the version they were made
|
|
35
|
+
* for. Always the ES module build: `require.resolve('vite')` can pick the
|
|
36
|
+
* CommonJS one of older majors.
|
|
37
|
+
*/
|
|
38
|
+
export async function importLiveVite(root) {
|
|
39
|
+
const packageJson = vitePackageJson(root);
|
|
40
|
+
const { exports } = JSON.parse(readFileSync(packageJson, 'utf8'));
|
|
41
|
+
const entry = esmEntry(exports?.['.']);
|
|
42
|
+
if (entry === null) {
|
|
43
|
+
throw new Error(`describe-me: cannot find the ES module entry of vite in ${packageJson}`);
|
|
44
|
+
}
|
|
45
|
+
return (await import(pathToFileURL(join(dirname(packageJson), entry)).href));
|
|
46
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { liveExport } from './live-export.js';
|
|
2
|
+
/** The names Vitest puts on `globalThis` with `test.globals`. */
|
|
3
|
+
const GLOBALS = [
|
|
4
|
+
'suite',
|
|
5
|
+
'test',
|
|
6
|
+
'describe',
|
|
7
|
+
'it',
|
|
8
|
+
'chai',
|
|
9
|
+
'expect',
|
|
10
|
+
'assert',
|
|
11
|
+
'expectTypeOf',
|
|
12
|
+
'assertType',
|
|
13
|
+
'vitest',
|
|
14
|
+
'vi',
|
|
15
|
+
'beforeAll',
|
|
16
|
+
'afterAll',
|
|
17
|
+
'beforeEach',
|
|
18
|
+
'afterEach',
|
|
19
|
+
'onTestFinished',
|
|
20
|
+
'onTestFailed',
|
|
21
|
+
'aroundEach',
|
|
22
|
+
'aroundAll',
|
|
23
|
+
];
|
|
24
|
+
/**
|
|
25
|
+
* Puts the shimmed globals on `globalThis`, as `test.globals` does. It runs
|
|
26
|
+
* before the test file imports, so Testing Library finds the hooks too.
|
|
27
|
+
*/
|
|
28
|
+
export function installLiveGlobals() {
|
|
29
|
+
const scope = globalThis;
|
|
30
|
+
for (const name of GLOBALS) {
|
|
31
|
+
scope[name] = liveExport('vitest', name);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a module is one of the adapter's own files, whose imports of the
|
|
3
|
+
* original module must stay unredirected. `/node_modules/<package>/` is the
|
|
4
|
+
* fallback for when the project root could not resolve the package.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isAdapterModule(id: string, adapterRoot: string | null, adapterPackage: string): boolean;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a module is one of the adapter's own files, whose imports of the
|
|
3
|
+
* original module must stay unredirected. `/node_modules/<package>/` is the
|
|
4
|
+
* fallback for when the project root could not resolve the package.
|
|
5
|
+
*/
|
|
6
|
+
export function isAdapterModule(id, adapterRoot, adapterPackage) {
|
|
7
|
+
if (adapterRoot !== null && id.startsWith(adapterRoot)) {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
return id.includes(`/node_modules/${adapterPackage}/`);
|
|
11
|
+
}
|
|
@@ -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>;
|