@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.
- package/README.md +10 -8
- 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 +11 -3
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { liveStop } from '@describe-me/core/live';
|
|
2
|
+
import { liveExport } from './live-export.js';
|
|
3
|
+
function ignore() {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The one context that the hooks and the body of a live test get, with the
|
|
8
|
+
* fixtures they use once the runner computed them. `skip()` stops the test
|
|
9
|
+
* without a render, so the page reports `no-render`.
|
|
10
|
+
*/
|
|
11
|
+
export function liveTestContext(match, signal) {
|
|
12
|
+
return {
|
|
13
|
+
task: { name: match.test.name, fullName: match.path.join(' > ') },
|
|
14
|
+
expect: liveExport('vitest', 'expect'),
|
|
15
|
+
skip(condition) {
|
|
16
|
+
if (condition === false) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
throw liveStop();
|
|
20
|
+
},
|
|
21
|
+
signal,
|
|
22
|
+
onTestFinished: ignore,
|
|
23
|
+
onTestFailed: ignore,
|
|
24
|
+
annotate: () => Promise.resolve(),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The source of a build's map of test files: each path from the root to a
|
|
3
|
+
* lazy import of the file, so the bundler gives every test file a chunk of
|
|
4
|
+
* its own and the page loads only the one it shows.
|
|
5
|
+
*/
|
|
6
|
+
export declare function liveTestFilesSource(testFiles: string[]): string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { liveStringLiteral } from './live-string-literal.js';
|
|
2
|
+
/**
|
|
3
|
+
* The source of a build's map of test files: each path from the root to a
|
|
4
|
+
* lazy import of the file, so the bundler gives every test file a chunk of
|
|
5
|
+
* its own and the page loads only the one it shows.
|
|
6
|
+
*/
|
|
7
|
+
export function liveTestFilesSource(testFiles) {
|
|
8
|
+
const entries = testFiles.map((file) => ` ${liveStringLiteral(file)}: () => import(${liveStringLiteral(`/${file}`)}),`);
|
|
9
|
+
return ['export default {', ...entries, '}', ''].join('\n');
|
|
10
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vi` and its alias `vitest` on the live page. Keys it does not know do
|
|
3
|
+
* nothing and return `vi`, so fake timers are skipped and time is real.
|
|
4
|
+
* `vi.mock` and `vi.doMock` change nothing either, but post a notice that
|
|
5
|
+
* names the module. `await vi.waitFor(…)` ends, because `vi` has no `then`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function liveVi(): object;
|
package/dist/live-vi.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { liveMock } from './live-mock.js';
|
|
2
|
+
import { liveSpyOn } from './live-spy-on.js';
|
|
3
|
+
import { noticeLiveModuleMock } from './notice-live-module-mock.js';
|
|
4
|
+
let vi;
|
|
5
|
+
function isMockFunction(value) {
|
|
6
|
+
return (typeof value === 'function' && value._isMockFunction === true);
|
|
7
|
+
}
|
|
8
|
+
function createVi() {
|
|
9
|
+
const known = {
|
|
10
|
+
fn: (implementation) => liveMock({ implementation }),
|
|
11
|
+
spyOn: liveSpyOn,
|
|
12
|
+
hoisted: (factory) => factory(),
|
|
13
|
+
stubGlobal: (name, value) => {
|
|
14
|
+
const scope = globalThis;
|
|
15
|
+
scope[name] = value;
|
|
16
|
+
return proxy;
|
|
17
|
+
},
|
|
18
|
+
isMockFunction,
|
|
19
|
+
mock: (path) => {
|
|
20
|
+
noticeLiveModuleMock('vi.mock', path);
|
|
21
|
+
return proxy;
|
|
22
|
+
},
|
|
23
|
+
doMock: (path) => {
|
|
24
|
+
noticeLiveModuleMock('vi.doMock', path);
|
|
25
|
+
return proxy;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
const proxy = new Proxy(known, {
|
|
29
|
+
get(target, key) {
|
|
30
|
+
if (typeof key !== 'string' || key === 'then') {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
if (Object.hasOwn(target, key)) {
|
|
34
|
+
return target[key];
|
|
35
|
+
}
|
|
36
|
+
return () => proxy;
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
return proxy;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* `vi` and its alias `vitest` on the live page. Keys it does not know do
|
|
43
|
+
* nothing and return `vi`, so fake timers are skipped and time is real.
|
|
44
|
+
* `vi.mock` and `vi.doMock` change nothing either, but post a notice that
|
|
45
|
+
* names the module. `await vi.waitFor(…)` ends, because `vi` has no `then`.
|
|
46
|
+
*/
|
|
47
|
+
export function liveVi() {
|
|
48
|
+
vi ??= createVi();
|
|
49
|
+
return vi;
|
|
50
|
+
}
|
package/dist/live.d.ts
ADDED
package/dist/live.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Vite } from 'vitest/node';
|
|
2
|
+
type LoadedConfig = NonNullable<Awaited<ReturnType<typeof Vite.loadConfigFromFile>>>;
|
|
3
|
+
/**
|
|
4
|
+
* Loads the project's config the way Vitest serves it, with `describeMe()`
|
|
5
|
+
* in live mode. The flag is set only while the config file runs, so nothing
|
|
6
|
+
* else in this process sees it.
|
|
7
|
+
*/
|
|
8
|
+
export declare function loadLiveConfig(vite: typeof Vite, configFile: string, root: string): Promise<LoadedConfig>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { LIVE_ENV } from './live-env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Loads the project's config the way Vitest serves it, with `describeMe()`
|
|
4
|
+
* in live mode. The flag is set only while the config file runs, so nothing
|
|
5
|
+
* else in this process sees it.
|
|
6
|
+
*/
|
|
7
|
+
export async function loadLiveConfig(vite, configFile, root) {
|
|
8
|
+
const previous = process.env[LIVE_ENV];
|
|
9
|
+
process.env[LIVE_ENV] = '1';
|
|
10
|
+
try {
|
|
11
|
+
const loaded = await vite.loadConfigFromFile({ command: 'serve', mode: 'test', isSsrBuild: false, isPreview: false }, configFile, root);
|
|
12
|
+
if (loaded === null) {
|
|
13
|
+
throw new Error(`describe-me: cannot load ${configFile}`);
|
|
14
|
+
}
|
|
15
|
+
return loaded;
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
if (previous === undefined) {
|
|
19
|
+
delete process.env[LIVE_ENV];
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
process.env[LIVE_ENV] = previous;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { EnvironmentProfile } from './environment-profile.js';
|
|
2
|
+
import type { DescribeMeEnvironment } from './plugin.js';
|
|
3
|
+
/** Imported on demand, so browser mode never loads the code for DOM environments. */
|
|
4
|
+
export declare function loadProfile(environment: DescribeMeEnvironment): Promise<EnvironmentProfile>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Imported on demand, so browser mode never loads the code for DOM environments. */
|
|
2
|
+
export async function loadProfile(environment) {
|
|
3
|
+
if (environment === 'browser') {
|
|
4
|
+
const { browserProfile } = await import('./browser-profile.js');
|
|
5
|
+
return browserProfile;
|
|
6
|
+
}
|
|
7
|
+
const { domProfile } = await import('./dom-profile.js');
|
|
8
|
+
return domProfile;
|
|
9
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { postLiveMessage } from '@describe-me/core/live';
|
|
2
|
+
const posted = new Set();
|
|
3
|
+
function isThenable(value) {
|
|
4
|
+
return ((typeof value === 'object' || typeof value === 'function') &&
|
|
5
|
+
value !== null &&
|
|
6
|
+
typeof value.then === 'function');
|
|
7
|
+
}
|
|
8
|
+
function pathOf(path) {
|
|
9
|
+
if (typeof path === 'string') {
|
|
10
|
+
return JSON.stringify(path);
|
|
11
|
+
}
|
|
12
|
+
return isThenable(path) ? 'import(…)' : String(path);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Tells the viewer that a `vi.mock` or `vi.doMock` call has no effect on the
|
|
16
|
+
* live page. Each distinct call posts one notice per page.
|
|
17
|
+
*/
|
|
18
|
+
export function noticeLiveModuleMock(method, path) {
|
|
19
|
+
const message = `${method}(${pathOf(path)}) does not apply in the live preview, so the component uses the real module.`;
|
|
20
|
+
if (posted.has(message)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
posted.add(message);
|
|
24
|
+
postLiveMessage({ type: 'notice', message });
|
|
25
|
+
}
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ViteUserConfig } from 'vitest/config';
|
|
2
|
-
import type
|
|
2
|
+
import { type RenderFrameMode } from '@describe-me/core/types';
|
|
3
3
|
/** Where the tests run: Vitest browser mode, or a simulated DOM such as jsdom. */
|
|
4
4
|
export type DescribeMeEnvironment = 'browser' | 'dom';
|
|
5
5
|
export interface DescribeMeOptions {
|
|
@@ -51,6 +51,14 @@ export interface DescribeMeOptions {
|
|
|
51
51
|
* selector. Default: `'eager'`.
|
|
52
52
|
*/
|
|
53
53
|
renderFrame?: RenderFrameMode;
|
|
54
|
+
/**
|
|
55
|
+
* The live preview of this project. `setupFiles` replaces `test.setupFiles`
|
|
56
|
+
* on the live page, for a setup file that needs Node. `false` switches the
|
|
57
|
+
* preview off. Test runs ignore it. Default: on, with `test.setupFiles`.
|
|
58
|
+
*/
|
|
59
|
+
live?: false | {
|
|
60
|
+
setupFiles?: string | string[];
|
|
61
|
+
};
|
|
54
62
|
}
|
|
55
63
|
type VitePlugin = NonNullable<ViteUserConfig['plugins']>[number];
|
|
56
64
|
/**
|
|
@@ -60,7 +68,8 @@ type VitePlugin = NonNullable<ViteUserConfig['plugins']>[number];
|
|
|
60
68
|
* `render` import to our adapter so existing tests record frames unchanged.
|
|
61
69
|
* Works in browser mode and in DOM environments such as jsdom; the adapter
|
|
62
70
|
* itself still needs the real module, so imports coming from inside it are
|
|
63
|
-
* left alone.
|
|
71
|
+
* left alone. While `createLiveServer()` or `buildLive()` loads the config,
|
|
72
|
+
* it returns the live preview's plugin instead.
|
|
64
73
|
*/
|
|
65
74
|
export declare function describeMe(options?: DescribeMeOptions): VitePlugin;
|
|
66
75
|
export {};
|
package/dist/plugin.js
CHANGED
|
@@ -1,86 +1,19 @@
|
|
|
1
|
+
import { LIVE_PROTOCOL } from '@describe-me/core/types';
|
|
1
2
|
import { adapterPackageRoot } from './adapter-package-root.js';
|
|
2
3
|
import { compileGlobs } from './compile-globs.js';
|
|
3
4
|
import { compileInclude } from './compile-include.js';
|
|
4
5
|
import { defaultReporters } from './default-reporters.js';
|
|
6
|
+
import { detectEnvironment } from './detect-environment.js';
|
|
7
|
+
import { isAdapterModule } from './is-adapter-module.js';
|
|
8
|
+
import { LIVE_ENV } from './live-env.js';
|
|
9
|
+
import { livePlugin } from './live-plugin.js';
|
|
10
|
+
import { loadProfile } from './load-profile.js';
|
|
11
|
+
import { packageNameOf } from './package-name-of.js';
|
|
5
12
|
import { projectModulePath } from './project-module-path.js';
|
|
6
13
|
import { registerExports } from './register-exports.js';
|
|
7
14
|
import DescribeMeReporter from './reporter.js';
|
|
8
15
|
import { RUNTIME_OPTIONS_KEY } from './runtime-options.js';
|
|
9
|
-
import { styledComponentsBrowserBuild, } from './styled-components-browser-build.js';
|
|
10
16
|
import { validateRenderFrame } from './validate-render-frame.js';
|
|
11
|
-
/** For each framework and environment: the render module tests import, and our adapter that wraps it. */
|
|
12
|
-
const RENDER_MODULES = {
|
|
13
|
-
react: {
|
|
14
|
-
browser: { original: 'vitest-browser-react', adapter: '@describe-me/react' },
|
|
15
|
-
dom: { original: '@testing-library/react', adapter: '@describe-me/react/testing-library' },
|
|
16
|
-
},
|
|
17
|
-
};
|
|
18
|
-
/**
|
|
19
|
-
* The package an adapter entry point belongs to, e.g.
|
|
20
|
-
* `@describe-me/react/testing-library` → `@describe-me/react`.
|
|
21
|
-
*/
|
|
22
|
-
function packageNameOf(specifier) {
|
|
23
|
-
return specifier.split('/').slice(0, 2).join('/');
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Whether a module is one of the adapter's own files, whose imports of the
|
|
27
|
-
* original module must stay unredirected. `/node_modules/<package>/` is the
|
|
28
|
-
* fallback for when the project root could not resolve the package.
|
|
29
|
-
*/
|
|
30
|
-
function isAdapterModule(id, adapterRoot, adapterPackage) {
|
|
31
|
-
if (adapterRoot !== null && id.startsWith(adapterRoot)) {
|
|
32
|
-
return true;
|
|
33
|
-
}
|
|
34
|
-
return id.includes(`/node_modules/${adapterPackage}/`);
|
|
35
|
-
}
|
|
36
|
-
function detectEnvironment(userConfig) {
|
|
37
|
-
return userConfig.test?.browser?.enabled ? 'browser' : 'dom';
|
|
38
|
-
}
|
|
39
|
-
function browserConfig(renderModule) {
|
|
40
|
-
return {
|
|
41
|
-
// Vite's dependency scanner runs before our `resolveId` redirect, so it
|
|
42
|
-
// never sees the adapter. Discovering it mid-run makes Vite re-optimize
|
|
43
|
-
// and reload, and the test ends up with two copies of the bundled deps
|
|
44
|
-
// ("Vitest failed to find the runner"). Declaring the adapter up front
|
|
45
|
-
// keeps a single optimization pass.
|
|
46
|
-
optimizeDeps: {
|
|
47
|
-
include: [renderModule.adapter, renderModule.original],
|
|
48
|
-
},
|
|
49
|
-
test: {
|
|
50
|
-
setupFiles: ['@describe-me/vitest/setup'],
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
function domConfig(userConfig, styledComponents) {
|
|
55
|
-
const userTest = userConfig.test;
|
|
56
|
-
const test = {
|
|
57
|
-
setupFiles: ['@describe-me/vitest/setup-dom'],
|
|
58
|
-
// Testing Library would otherwise unmount in its own afterEach, before the
|
|
59
|
-
// closing frame is taken. The adapter unmounts through the recorder instead.
|
|
60
|
-
env: { RTL_SKIP_AUTO_CLEANUP: 'true' },
|
|
61
|
-
};
|
|
62
|
-
// Vitest stubs CSS imports by default, which would leave every imported
|
|
63
|
-
// stylesheet out of the snapshots. An explicit choice by the user wins.
|
|
64
|
-
if (userTest?.css === undefined) {
|
|
65
|
-
test.css = true;
|
|
66
|
-
}
|
|
67
|
-
else if (userTest.css === false) {
|
|
68
|
-
console.warn('describe-me: `test.css` is false, so imported stylesheets are stubbed and will be missing from the snapshots.');
|
|
69
|
-
}
|
|
70
|
-
if (!styledComponents) {
|
|
71
|
-
return { test };
|
|
72
|
-
}
|
|
73
|
-
console.info('describe-me: using the browser build of styled-components, for createGlobalStyle');
|
|
74
|
-
const { config } = styledComponents;
|
|
75
|
-
return { resolve: config.resolve, test: { ...test, ...config.test } };
|
|
76
|
-
}
|
|
77
|
-
/** Looked up before the reporter is created, which needs its warning. */
|
|
78
|
-
function styledComponentsFor(environment, userConfig, options) {
|
|
79
|
-
if (environment !== 'dom' || options.styledComponentsBrowserBuild === false) {
|
|
80
|
-
return undefined;
|
|
81
|
-
}
|
|
82
|
-
return styledComponentsBrowserBuild(userConfig.root ?? process.cwd());
|
|
83
|
-
}
|
|
84
17
|
/**
|
|
85
18
|
* One-line integration: `plugins: [describeMe()]`.
|
|
86
19
|
*
|
|
@@ -88,11 +21,17 @@ function styledComponentsFor(environment, userConfig, options) {
|
|
|
88
21
|
* `render` import to our adapter so existing tests record frames unchanged.
|
|
89
22
|
* Works in browser mode and in DOM environments such as jsdom; the adapter
|
|
90
23
|
* itself still needs the real module, so imports coming from inside it are
|
|
91
|
-
* left alone.
|
|
24
|
+
* left alone. While `createLiveServer()` or `buildLive()` loads the config,
|
|
25
|
+
* it returns the live preview's plugin instead.
|
|
92
26
|
*/
|
|
93
27
|
export function describeMe(options = {}) {
|
|
28
|
+
if (process.env[LIVE_ENV] === '1') {
|
|
29
|
+
return livePlugin(options);
|
|
30
|
+
}
|
|
94
31
|
const { enabled = true, framework = 'react', outDir } = options;
|
|
95
|
-
|
|
32
|
+
// Set by `config()`, which Vite runs before `configResolved` and `resolveId`.
|
|
33
|
+
// Stays null while the plugin is disabled.
|
|
34
|
+
let renderModule = null;
|
|
96
35
|
// Known once Vite has resolved its config. Vitest always serves; a production
|
|
97
36
|
// build that shares the config must not carry the export registration.
|
|
98
37
|
let root = '';
|
|
@@ -103,12 +42,13 @@ export function describeMe(options = {}) {
|
|
|
103
42
|
return {
|
|
104
43
|
name: 'describe-me',
|
|
105
44
|
enforce: 'pre',
|
|
106
|
-
config
|
|
45
|
+
// In both modes, so a tool can check the live protocol on any config it loads.
|
|
46
|
+
api: { live: { protocol: LIVE_PROTOCOL, enabled: options.live !== false } },
|
|
47
|
+
async config(userConfig) {
|
|
107
48
|
if (!enabled) {
|
|
108
49
|
return {};
|
|
109
50
|
}
|
|
110
51
|
const environment = options.environment ?? detectEnvironment(userConfig);
|
|
111
|
-
renderModule = RENDER_MODULES[framework][environment];
|
|
112
52
|
// Checked here, so invalid options fail before any test runs. Vite
|
|
113
53
|
// deep-merges `provide`, so the user's own keys survive.
|
|
114
54
|
const runtimeOptions = {
|
|
@@ -117,8 +57,9 @@ export function describeMe(options = {}) {
|
|
|
117
57
|
renderFrame: validateRenderFrame(options.renderFrame),
|
|
118
58
|
};
|
|
119
59
|
const provide = { [RUNTIME_OPTIONS_KEY]: runtimeOptions };
|
|
120
|
-
const
|
|
121
|
-
|
|
60
|
+
const profile = await loadProfile(environment);
|
|
61
|
+
renderModule = profile.renderModules[framework];
|
|
62
|
+
const { config, setupWarnings } = profile.configFor({ userConfig, options, renderModule });
|
|
122
63
|
const reporter = new DescribeMeReporter({
|
|
123
64
|
outDir,
|
|
124
65
|
include: options.include,
|
|
@@ -130,13 +71,13 @@ export function describeMe(options = {}) {
|
|
|
130
71
|
// reporters Vitest picks on its own, so without one of the user's, ours
|
|
131
72
|
// joins Vitest's defaults.
|
|
132
73
|
const reporters = userConfig.test?.reporters ? [reporter] : [...defaultReporters(), reporter];
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
:
|
|
136
|
-
|
|
74
|
+
return {
|
|
75
|
+
...config,
|
|
76
|
+
test: { setupFiles: [profile.setupFile], ...config.test, reporters, provide },
|
|
77
|
+
};
|
|
137
78
|
},
|
|
138
79
|
async resolveId(source, importer) {
|
|
139
|
-
if (!enabled || source !== renderModule.original) {
|
|
80
|
+
if (!enabled || renderModule === null || source !== renderModule.original) {
|
|
140
81
|
return null;
|
|
141
82
|
}
|
|
142
83
|
const fromAdapter = importer !== undefined &&
|
|
@@ -156,7 +97,9 @@ export function describeMe(options = {}) {
|
|
|
156
97
|
configResolved(config) {
|
|
157
98
|
root = config.root;
|
|
158
99
|
serving = config.command === 'serve';
|
|
159
|
-
|
|
100
|
+
if (renderModule !== null) {
|
|
101
|
+
adapterRoot = adapterPackageRoot(config.root, packageNameOf(renderModule.adapter));
|
|
102
|
+
}
|
|
160
103
|
},
|
|
161
104
|
// Names components after their export (see registerExports). `order: 'post'`
|
|
162
105
|
// runs it after the TypeScript and JSX transforms, so it parses plain
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
const DECLARED = /\bexport\s+const\s+([A-Za-z_$][\w$]*)/g;
|
|
5
|
+
const KNOWN_NAMES = ['page', 'server', 'userEvent', 'cdp', 'commands', 'locators', 'utils'];
|
|
6
|
+
/**
|
|
7
|
+
* The names that the project's `vitest/browser` declares. Importing it in
|
|
8
|
+
* Node throws, but its file still declares every name for static analysis.
|
|
9
|
+
*/
|
|
10
|
+
export function readLiveBrowserNames(root) {
|
|
11
|
+
try {
|
|
12
|
+
const file = createRequire(join(root, 'package.json')).resolve('vitest/browser');
|
|
13
|
+
const names = Array.from(readFileSync(file, 'utf8').matchAll(DECLARED), (match) => match[1]);
|
|
14
|
+
return names.length > 0 ? [...new Set(names)] : KNOWN_NAMES;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return KNOWN_NAMES;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** The test that the page URL names, or why the URL names none. */
|
|
2
|
+
export type LiveParams = {
|
|
3
|
+
file: string;
|
|
4
|
+
path: string[];
|
|
5
|
+
occurrence: number;
|
|
6
|
+
} | {
|
|
7
|
+
problem: string;
|
|
8
|
+
};
|
|
9
|
+
/** The parts of `window.location` that the page reads. */
|
|
10
|
+
export interface LivePageLocation {
|
|
11
|
+
search: string;
|
|
12
|
+
origin: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Reads `file`, `path` and `occurrence` from the query of the live page.
|
|
16
|
+
* `path` holds the suite names, outermost first, then the test name.
|
|
17
|
+
*/
|
|
18
|
+
export declare function readLiveParams(location: LivePageLocation): LiveParams;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const OCCURRENCE = /^[1-9]\d*$/;
|
|
2
|
+
function hasControlCharacter(text) {
|
|
3
|
+
return Array.from(text).some((character) => {
|
|
4
|
+
const code = character.charCodeAt(0);
|
|
5
|
+
return code < 0x20 || code === 0x7f;
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
/** Whether `/file` on the page's origin names exactly that file, with no query, hash or dot segment. */
|
|
9
|
+
function namesOnlyFile(file, origin) {
|
|
10
|
+
try {
|
|
11
|
+
const url = new URL(`/${file}`, origin);
|
|
12
|
+
const exact = decodeURIComponent(url.pathname) === `/${file}`;
|
|
13
|
+
return url.origin === origin && url.search === '' && url.hash === '' && exact;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A path relative to the root that stays on the page's origin. The URL
|
|
21
|
+
* parser drops tabs and newlines and reads a leading `//` as another host, so
|
|
22
|
+
* a `file` such as `\t/host/x.js` would load code from that host. The last
|
|
23
|
+
* rule checks the URL the page imports, not only the text.
|
|
24
|
+
*/
|
|
25
|
+
function isRelativePath(file, origin) {
|
|
26
|
+
if (file === '' || file.startsWith('/') || file.includes(':') || hasControlCharacter(file)) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return !file.split('/').includes('..') && namesOnlyFile(file, origin);
|
|
30
|
+
}
|
|
31
|
+
function namesOf(text) {
|
|
32
|
+
if (text === null) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
const names = JSON.parse(text);
|
|
37
|
+
const valid = Array.isArray(names) && names.length > 0 && names.every((name) => typeof name === 'string');
|
|
38
|
+
return valid ? names : null;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Reads `file`, `path` and `occurrence` from the query of the live page.
|
|
46
|
+
* `path` holds the suite names, outermost first, then the test name.
|
|
47
|
+
*/
|
|
48
|
+
export function readLiveParams(location) {
|
|
49
|
+
const params = new URLSearchParams(location.search);
|
|
50
|
+
const file = params.get('file')?.replaceAll('\\', '/') ?? null;
|
|
51
|
+
if (file === null || !isRelativePath(file, location.origin)) {
|
|
52
|
+
return { problem: `file must be a path relative to the root, got ${JSON.stringify(file)}` };
|
|
53
|
+
}
|
|
54
|
+
const path = namesOf(params.get('path'));
|
|
55
|
+
if (path === null) {
|
|
56
|
+
return { problem: 'path must be a JSON array of the suite names and the test name' };
|
|
57
|
+
}
|
|
58
|
+
const occurrence = params.get('occurrence') ?? '1';
|
|
59
|
+
if (!OCCURRENCE.test(occurrence)) {
|
|
60
|
+
return { problem: `occurrence must be a positive integer, got ${JSON.stringify(occurrence)}` };
|
|
61
|
+
}
|
|
62
|
+
return { file, path, occurrence: Number(occurrence) };
|
|
63
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The major version of the `vitest` that the root resolves, whose rules the
|
|
3
|
+
* live page names `each` and `for` rows by. Vitest exports its
|
|
4
|
+
* `package.json`. Falls back to 5 when it cannot be read.
|
|
5
|
+
*/
|
|
6
|
+
export declare function readLiveVitestMajor(root: string): number;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
const DEFAULT_MAJOR = 5;
|
|
5
|
+
/**
|
|
6
|
+
* The major version of the `vitest` that the root resolves, whose rules the
|
|
7
|
+
* live page names `each` and `for` rows by. Vitest exports its
|
|
8
|
+
* `package.json`. Falls back to 5 when it cannot be read.
|
|
9
|
+
*/
|
|
10
|
+
export function readLiveVitestMajor(root) {
|
|
11
|
+
try {
|
|
12
|
+
const file = createRequire(join(root, 'package.json')).resolve('vitest/package.json');
|
|
13
|
+
const { version } = JSON.parse(readFileSync(file, 'utf8'));
|
|
14
|
+
const major = typeof version === 'string' ? Number.parseInt(version, 10) : Number.NaN;
|
|
15
|
+
return Number.isInteger(major) && major > 0 ? major : DEFAULT_MAJOR;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return DEFAULT_MAJOR;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
2
|
+
/** The names of `vitest` that the shim knows, for when Vitest cannot be imported. */
|
|
3
|
+
const KNOWN_NAMES = [
|
|
4
|
+
'describe',
|
|
5
|
+
'suite',
|
|
6
|
+
'it',
|
|
7
|
+
'test',
|
|
8
|
+
'beforeAll',
|
|
9
|
+
'beforeEach',
|
|
10
|
+
'afterEach',
|
|
11
|
+
'afterAll',
|
|
12
|
+
'aroundEach',
|
|
13
|
+
'aroundAll',
|
|
14
|
+
'onTestFinished',
|
|
15
|
+
'onTestFailed',
|
|
16
|
+
'vi',
|
|
17
|
+
'vitest',
|
|
18
|
+
'expect',
|
|
19
|
+
'assert',
|
|
20
|
+
'chai',
|
|
21
|
+
'should',
|
|
22
|
+
'expectTypeOf',
|
|
23
|
+
'assertType',
|
|
24
|
+
'createExpect',
|
|
25
|
+
'inject',
|
|
26
|
+
'recordArtifact',
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* The names that the installed `vitest` exports. The shim exports the same
|
|
30
|
+
* ones, so a test that imports any of them links in the live page.
|
|
31
|
+
*/
|
|
32
|
+
export async function readLiveVitestNames() {
|
|
33
|
+
try {
|
|
34
|
+
const names = Object.keys(await import('vitest'));
|
|
35
|
+
return names.filter((name) => name !== 'default' && IDENTIFIER.test(name));
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return KNOWN_NAMES;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -6,6 +6,7 @@ import type { RuntimeOptions } from './runtime-options.js';
|
|
|
6
6
|
* reporter via task.meta, and only then let adapters unmount. Teardown runs
|
|
7
7
|
* for unrecorded tests too, because Testing Library's auto-cleanup is off in
|
|
8
8
|
* jsdom. Concurrent tests would share the one recorder, so they are not
|
|
9
|
-
* recorded.
|
|
9
|
+
* recorded. The hooks destructure the context, because Vitest rejects any
|
|
10
|
+
* other first parameter in a test that has `test.extend` fixtures.
|
|
10
11
|
*/
|
|
11
12
|
export declare function registerRecordingHooks(options: RuntimeOptions, aroundWait?: AroundWait): void;
|