@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
package/README.md
CHANGED
|
@@ -1,108 +1,77 @@
|
|
|
1
1
|
# @describe-me/vitest
|
|
2
2
|
|
|
3
|
-
The Vitest
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
**Your tests are your stories.** The Vitest plugin, setup files and reporter of
|
|
4
|
+
describe-me: living component documentation generated from the Vitest tests
|
|
5
|
+
you already have.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**[Read the docs](https://grzehub.github.io/describe-me/)** · open the viewer
|
|
8
|
+
of the [browser-mode example](https://grzehub.github.io/describe-me/examples/react-browser/)
|
|
9
|
+
or the [jsdom example](https://grzehub.github.io/describe-me/examples/react-jsdom/)
|
|
10
|
+
· [Limitations](https://grzehub.github.io/describe-me/limitations.html)
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
describe-me captures the DOM after each step of a test and shows the result as
|
|
13
|
+
a browsable catalog. `describe` blocks become the sidebar, each `it` is a
|
|
14
|
+
story, and each step is a frame you can scrub through. Stories cannot rot,
|
|
15
|
+
because they are tests. If one drifts from the component, CI goes red.
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
## Usage
|
|
17
|
-
|
|
18
|
-
One line in the Vitest config registers the setup file and the reporter, and
|
|
19
|
-
redirects the test library's `render` import to the recording adapter. The
|
|
20
|
-
plugin detects where the tests run:
|
|
21
|
-
|
|
22
|
-
- **Browser mode** (`test.browser.enabled`): redirects `vitest-browser-react`,
|
|
23
|
-
records a frame after every `Locator` action and keyboard-level `userEvent`.
|
|
24
|
-
- **DOM environments** such as jsdom (anything else): redirects
|
|
25
|
-
`@testing-library/react`, records a frame after every `fireEvent` and every
|
|
26
|
-
`@testing-library/user-event` call, turns on `test.css` so imported
|
|
27
|
-
stylesheets reach the snapshots, and sets `RTL_SKIP_AUTO_CLEANUP` so the
|
|
28
|
-
component is unmounted only after the closing frame. The setup file sets
|
|
29
|
-
React's `IS_REACT_ACT_ENVIRONMENT` where Testing Library would. With
|
|
30
|
-
`@testing-library/react`, it takes user-event frames without a wait. The
|
|
31
|
-
flag is off for the few waits that remain, which can still move an act
|
|
32
|
-
warning
|
|
33
|
-
([jsdom](https://grzehub.github.io/describe-me/environments.html#jsdom)). It
|
|
34
|
-
also works without user-event installed.
|
|
35
|
-
|
|
36
|
-
jsdom has no layout, so positioned popovers show in the top-left corner and
|
|
37
|
-
canvas stays blank
|
|
38
|
-
([Limitations in jsdom](https://grzehub.github.io/describe-me/environments.html#jsdom-limits)).
|
|
17
|
+
Status: prototype. React 18 and 19, Vitest 4 and 5 in browser mode or jsdom,
|
|
18
|
+
recorded frames and Live in the viewer.
|
|
39
19
|
|
|
40
|
-
|
|
41
|
-
import { describeMe } from '@describe-me/vitest/plugin'
|
|
20
|
+
## Which package do I need
|
|
42
21
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
})
|
|
22
|
+
| Package | What it is | Install it |
|
|
23
|
+
| --------------------- | -------------------------------------------------- | ----------------------- |
|
|
24
|
+
| `describe-me` | the viewer and the `describe-me` CLI | yes |
|
|
25
|
+
| `@describe-me/vitest` | the Vitest plugin, setup files and reporter (this) | yes |
|
|
26
|
+
| `@describe-me/react` | `render` functions that record frames | yes |
|
|
27
|
+
| `@describe-me/core` | the recorder and the manifest types | no, the others bring it |
|
|
50
28
|
|
|
51
|
-
|
|
52
|
-
export default defineConfig({
|
|
53
|
-
plugins: [react(), describeMe()],
|
|
54
|
-
test: { environment: 'jsdom' },
|
|
55
|
-
})
|
|
56
|
-
```
|
|
29
|
+
## Quick start
|
|
57
30
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
no interaction, no `step()`) costs no snapshot and is left out of the
|
|
62
|
-
manifest. `describeMe({ include, exclude })` chooses which test files are
|
|
63
|
-
recorded, with picomatch globs relative to the Vitest root. `exclude` wins.
|
|
64
|
-
Tests in other files still run, but record nothing. Leaving `include` out
|
|
65
|
-
records every test file. `include: []` records none and warns, while
|
|
66
|
-
`exclude: '**'` records none without a warning. The reporter takes the
|
|
67
|
-
same two options, but on its own it only filters the manifest: without the
|
|
68
|
-
plugin the setup file does not know them.
|
|
69
|
-
|
|
70
|
-
`describeMe({ previewHead })` takes HTML that the viewer adds to the start of
|
|
71
|
-
every frame's `<head>`, like Storybook's `preview-head.html`: typically the
|
|
72
|
-
font links that the app shell loads and tests never do. It never reaches the
|
|
73
|
-
test page, and the local files it links are copied into the output directory
|
|
74
|
-
([Fonts](https://grzehub.github.io/describe-me/styles-and-fonts.html#fonts) in the docs).
|
|
75
|
-
|
|
76
|
-
`describeMe({ renderFrame })` chooses when the render frame is taken:
|
|
77
|
-
`'eager'` (default) right after mount, `'lazy'` right before the next
|
|
78
|
-
interaction, or `{ pending: '<selector>' }` once a loader is gone. See
|
|
79
|
-
[Render frame timing](https://grzehub.github.io/describe-me/configuration.html#render-frame).
|
|
80
|
-
|
|
81
|
-
The plugin adds its reporter to the ones your config names. If it names
|
|
82
|
-
none, the reporter joins the ones Vitest picks on its own, such as `minimal`
|
|
83
|
-
for AI agents and `github-actions` in GitHub Actions.
|
|
84
|
-
|
|
85
|
-
The pieces can also be wired by hand. Tests then import from the adapter
|
|
86
|
-
themselves: `render` from `@describe-me/react` in browser mode, and `render`,
|
|
87
|
-
`fireEvent` and `cleanup` from `@describe-me/react/testing-library` in jsdom:
|
|
31
|
+
```sh
|
|
32
|
+
pnpm add -D describe-me @describe-me/vitest @describe-me/react
|
|
33
|
+
```
|
|
88
34
|
|
|
89
35
|
```ts
|
|
90
|
-
|
|
36
|
+
// vitest.config.ts
|
|
37
|
+
import { describeMe } from '@describe-me/vitest/plugin'
|
|
91
38
|
|
|
92
39
|
export default defineConfig({
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
setupFiles: ['@describe-me/vitest/setup-dom'],
|
|
96
|
-
reporters: ['default', new DescribeMeReporter()],
|
|
97
|
-
// jsdom only: keep imported CSS and leave unmounting to describe-me
|
|
98
|
-
css: true,
|
|
99
|
-
env: { RTL_SKIP_AUTO_CLEANUP: 'true' },
|
|
100
|
-
},
|
|
40
|
+
plugins: [react(), describeMe()],
|
|
41
|
+
test: { environment: 'jsdom' }, // or your browser-mode settings
|
|
101
42
|
})
|
|
102
43
|
```
|
|
103
44
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
45
|
+
Run your tests, then `describe-me dev` opens the viewer.
|
|
46
|
+
[Getting started](https://grzehub.github.io/describe-me/getting-started.html)
|
|
47
|
+
walks through both environments. Keep all describe-me packages on one version
|
|
48
|
+
([why](https://grzehub.github.io/describe-me/installation.html#lockstep)).
|
|
49
|
+
|
|
50
|
+
## This package
|
|
51
|
+
|
|
52
|
+
The plugin is the whole integration. It detects browser mode or a DOM
|
|
53
|
+
environment, registers the setup file and the reporter, and redirects your
|
|
54
|
+
test library's `render` to the recording one, so existing tests record frames
|
|
55
|
+
unchanged.
|
|
56
|
+
[What the plugin changes](https://grzehub.github.io/describe-me/configuration.html#plugin)
|
|
57
|
+
lists every change it makes to your config, and
|
|
58
|
+
[Options](https://grzehub.github.io/describe-me/configuration.html#options)
|
|
59
|
+
every option it takes.
|
|
60
|
+
|
|
61
|
+
| Entry point | What it holds |
|
|
62
|
+
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
63
|
+
| `@describe-me/vitest/plugin` | [`describeMe()`](https://grzehub.github.io/describe-me/api.html#entry-vitest-plugin) |
|
|
64
|
+
| `@describe-me/vitest` | [`step()` and `recorder`](https://grzehub.github.io/describe-me/api.html#entry-vitest) |
|
|
65
|
+
| `@describe-me/vitest/setup` | [the browser-mode setup file](https://grzehub.github.io/describe-me/api.html#entry-vitest-setup) |
|
|
66
|
+
| `@describe-me/vitest/setup-dom` | [the jsdom setup file](https://grzehub.github.io/describe-me/api.html#entry-vitest-setup-dom) |
|
|
67
|
+
| `@describe-me/vitest/reporter` | [the reporter, to wire by hand](https://grzehub.github.io/describe-me/api.html#entry-vitest-reporter) |
|
|
68
|
+
| `@describe-me/vitest/live` | [`createLiveServer()` and `buildLive()`, for the CLI](https://grzehub.github.io/describe-me/api.html#entry-vitest-live) |
|
|
69
|
+
| `@describe-me/vitest/live-runtime` | [the runtime of the live page](https://grzehub.github.io/describe-me/api.html#entry-vitest-live-runtime) |
|
|
70
|
+
|
|
71
|
+
`typescript` and `@testing-library/user-event` are optional peers. Without
|
|
72
|
+
TypeScript the docs have no props tables. user-event is needed only when your
|
|
73
|
+
jsdom tests use it.
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
MIT
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Vitest browser mode, where tests render in a real browser. */
|
|
2
|
+
export const browserProfile = {
|
|
3
|
+
renderModules: {
|
|
4
|
+
react: {
|
|
5
|
+
original: 'vitest-browser-react',
|
|
6
|
+
adapter: '@describe-me/react',
|
|
7
|
+
live: '@describe-me/react/live',
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
setupFile: '@describe-me/vitest/setup',
|
|
11
|
+
configFor({ renderModule }) {
|
|
12
|
+
return {
|
|
13
|
+
config: {
|
|
14
|
+
// Vite's dependency scanner runs before our `resolveId` redirect, so it
|
|
15
|
+
// never sees the adapter. Discovering it mid-run makes Vite re-optimize
|
|
16
|
+
// and reload, and the test ends up with two copies of the bundled deps
|
|
17
|
+
// ("Vitest failed to find the runner"). Declaring the adapter up front
|
|
18
|
+
// keeps a single optimization pass.
|
|
19
|
+
optimizeDeps: {
|
|
20
|
+
include: [renderModule.adapter, renderModule.original],
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
setupWarnings: [],
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** What `buildLive()` builds, and where it writes the page. */
|
|
2
|
+
export interface LiveBuildOptions {
|
|
3
|
+
/** The project root. */
|
|
4
|
+
root: string;
|
|
5
|
+
/** The Vitest config file, relative to `root` or absolute. Default: the one Vitest picks. */
|
|
6
|
+
configFile?: string;
|
|
7
|
+
/** Where the live page goes, relative to the current directory or absolute. Emptied first. */
|
|
8
|
+
outDir: string;
|
|
9
|
+
/** The test files the page can import: module ids of the manifest, paths relative to `root`. */
|
|
10
|
+
testFiles: string[];
|
|
11
|
+
}
|
|
12
|
+
/** A finished live build. */
|
|
13
|
+
export interface LiveBuild {
|
|
14
|
+
/** `api.live.protocol` of the project's describeMe() plugin. */
|
|
15
|
+
protocol: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Builds the live preview into `outDir` as static files: `live.html`, the
|
|
19
|
+
* setup files and a lazy import of each test file, with a relative base, so
|
|
20
|
+
* it works under any path of a static host. It loads the project's config
|
|
21
|
+
* the way `createLiveServer()` does, with the project's own Vite, and builds
|
|
22
|
+
* the page the dev server serves. When the project's describeMe() has
|
|
23
|
+
* `live: false`, it writes nothing and rejects with an error whose `code` is
|
|
24
|
+
* `DESCRIBE_ME_LIVE_OFF`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildLive(options: LiveBuildOptions): Promise<LiveBuild>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { existsSync, realpathSync } from 'node:fs';
|
|
2
|
+
import { isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
3
|
+
import { findLiveConfigFile } from './find-live-config-file.js';
|
|
4
|
+
import { importLiveVite } from './import-live-vite.js';
|
|
5
|
+
import { liveBuildPlugin } from './live-build-plugin.js';
|
|
6
|
+
import { liveProjectPlugins } from './live-project-plugins.js';
|
|
7
|
+
import { loadLiveConfig } from './load-live-config.js';
|
|
8
|
+
function isInRoot(file, root) {
|
|
9
|
+
const fromRoot = relative(root, file);
|
|
10
|
+
return fromRoot !== '' && fromRoot.split(sep)[0] !== '..' && !isAbsolute(fromRoot);
|
|
11
|
+
}
|
|
12
|
+
/** The test files with `/` separators, without those that are not under the root, which it names once. */
|
|
13
|
+
function liveTestFiles(testFiles, root) {
|
|
14
|
+
const kept = new Set();
|
|
15
|
+
const missing = [];
|
|
16
|
+
for (const file of testFiles.map((testFile) => testFile.replaceAll('\\', '/'))) {
|
|
17
|
+
const path = resolve(root, file);
|
|
18
|
+
if (isInRoot(path, root) && existsSync(path)) {
|
|
19
|
+
kept.add(file);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
missing.push(file);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (missing.length > 0) {
|
|
26
|
+
console.warn(`describe-me: the live build leaves out test files that are not in ${root}: ${missing.join(', ')}`);
|
|
27
|
+
}
|
|
28
|
+
return [...kept];
|
|
29
|
+
}
|
|
30
|
+
function liveBuildOptions(viteVersion, root, outDir) {
|
|
31
|
+
// Vite 8 does not resolve a relative input against the root.
|
|
32
|
+
const bundlerOptions = { input: join(root, 'live.html'), shimMissingExports: true };
|
|
33
|
+
// Vite 8 bundles with Rolldown and older majors with Rollup, each under its own key.
|
|
34
|
+
const bundler = Number.parseInt(viteVersion, 10) >= 8
|
|
35
|
+
? { rolldownOptions: bundlerOptions }
|
|
36
|
+
: { rollupOptions: bundlerOptions };
|
|
37
|
+
return {
|
|
38
|
+
outDir: resolve(outDir),
|
|
39
|
+
// Vite empties an outDir outside the root only when asked.
|
|
40
|
+
emptyOutDir: true,
|
|
41
|
+
// Suite and test names come from `fn.name`, as in Vitest. A minifier would rename
|
|
42
|
+
// `describe(Button, …)` and `%s` rows of functions, so the page would not find tests that
|
|
43
|
+
// dev mounts. Dev allows top-level await, which Vite 6's default target rejects.
|
|
44
|
+
minify: false,
|
|
45
|
+
target: 'esnext',
|
|
46
|
+
// Unminified, React's development build alone is above Vite's default limit.
|
|
47
|
+
chunkSizeWarningLimit: 10_000,
|
|
48
|
+
...bundler,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** Runs `build` with `NODE_ENV=development`, as on the dev server, and puts back what was there. */
|
|
52
|
+
async function inDevelopment(build) {
|
|
53
|
+
const previous = process.env.NODE_ENV;
|
|
54
|
+
process.env.NODE_ENV = 'development';
|
|
55
|
+
try {
|
|
56
|
+
return await build();
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
if (previous === undefined) {
|
|
60
|
+
delete process.env.NODE_ENV;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
process.env.NODE_ENV = previous;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Builds the live preview into `outDir` as static files: `live.html`, the
|
|
69
|
+
* setup files and a lazy import of each test file, with a relative base, so
|
|
70
|
+
* it works under any path of a static host. It loads the project's config
|
|
71
|
+
* the way `createLiveServer()` does, with the project's own Vite, and builds
|
|
72
|
+
* the page the dev server serves. When the project's describeMe() has
|
|
73
|
+
* `live: false`, it writes nothing and rejects with an error whose `code` is
|
|
74
|
+
* `DESCRIBE_ME_LIVE_OFF`.
|
|
75
|
+
*/
|
|
76
|
+
export async function buildLive(options) {
|
|
77
|
+
// Vite 8 takes the real path of the root, and the page must lie under it to land in outDir.
|
|
78
|
+
const root = realpathSync(resolve(options.root));
|
|
79
|
+
const configFile = options.configFile === undefined ? findLiveConfigFile(root) : resolve(root, options.configFile);
|
|
80
|
+
const testFiles = liveTestFiles(options.testFiles, root);
|
|
81
|
+
const vite = await importLiveVite(root);
|
|
82
|
+
const loaded = await loadLiveConfig(vite, configFile, root);
|
|
83
|
+
const configName = relative(root, configFile) || configFile;
|
|
84
|
+
let protocol = 0;
|
|
85
|
+
const config = vite.mergeConfig(loaded.config, {
|
|
86
|
+
root,
|
|
87
|
+
mode: 'test',
|
|
88
|
+
base: './',
|
|
89
|
+
logLevel: 'warn',
|
|
90
|
+
clearScreen: false,
|
|
91
|
+
});
|
|
92
|
+
// Dev never reads the project's build section, nor runs its build-only plugins.
|
|
93
|
+
config.build = liveBuildOptions(vite.version, root, options.outDir);
|
|
94
|
+
config.plugins = [
|
|
95
|
+
...(await liveProjectPlugins(loaded.config.plugins)),
|
|
96
|
+
liveBuildPlugin({
|
|
97
|
+
configName,
|
|
98
|
+
testFiles,
|
|
99
|
+
onProtocol: (value) => {
|
|
100
|
+
protocol = value;
|
|
101
|
+
},
|
|
102
|
+
}),
|
|
103
|
+
];
|
|
104
|
+
await inDevelopment(() => vite.build({ ...config, configFile: false }));
|
|
105
|
+
return { protocol };
|
|
106
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The code of the error for a project whose describeMe() has `live: false`. */
|
|
2
|
+
const LIVE_OFF = 'DESCRIBE_ME_LIVE_OFF';
|
|
3
|
+
/** Throws with the code `DESCRIBE_ME_LIVE_OFF` when the project's describeMe() has `live: false`. */
|
|
4
|
+
export function checkLiveEnabled(plugins, configName) {
|
|
5
|
+
const plugin = plugins.find((candidate) => candidate.name === 'describe-me');
|
|
6
|
+
if (plugin?.api?.live?.enabled !== false) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
throw Object.assign(new Error(`describe-me: live: false in ${configName} switches the live preview off`), { code: LIVE_OFF });
|
|
10
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { LiveSuite } from './live-suite.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runs the factories of the registered suites once the test file has
|
|
4
|
+
* imported, as Vitest collects: depth first in registration order, each one
|
|
5
|
+
* awaited. A factory's own `describe` calls are collected after it returns.
|
|
6
|
+
*/
|
|
7
|
+
export declare function collectLiveSuites(suite?: LiveSuite): Promise<void>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { liveRegistry } from './live-registry.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runs the factories of the registered suites once the test file has
|
|
4
|
+
* imported, as Vitest collects: depth first in registration order, each one
|
|
5
|
+
* awaited. A factory's own `describe` calls are collected after it returns.
|
|
6
|
+
*/
|
|
7
|
+
export async function collectLiveSuites(suite = liveRegistry().root) {
|
|
8
|
+
const registry = liveRegistry();
|
|
9
|
+
for (const child of suite.children) {
|
|
10
|
+
if (child.kind !== 'suite') {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
if (child.factory !== undefined) {
|
|
14
|
+
const previous = registry.current;
|
|
15
|
+
registry.current = child;
|
|
16
|
+
try {
|
|
17
|
+
await child.factory();
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
registry.current = previous;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
await collectLiveSuites(child);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Where the live preview finds the project. */
|
|
2
|
+
export interface LiveServerOptions {
|
|
3
|
+
/** The project root. */
|
|
4
|
+
root: string;
|
|
5
|
+
/**
|
|
6
|
+
* The Vitest config file, relative to `root` or absolute. Default: the one
|
|
7
|
+
* Vitest picks, the first `vitest.config.*`, then `vite.config.*`.
|
|
8
|
+
*/
|
|
9
|
+
configFile?: string;
|
|
10
|
+
/** The port on localhost. Default: a free one. */
|
|
11
|
+
port?: number;
|
|
12
|
+
}
|
|
13
|
+
/** A running live preview server. */
|
|
14
|
+
export interface LiveServer {
|
|
15
|
+
/** The URL the live page is under, such as `http://localhost:5173/__describe-me/`. */
|
|
16
|
+
base: string;
|
|
17
|
+
/** `api.live.protocol` of the project's describeMe() plugin. */
|
|
18
|
+
protocol: number;
|
|
19
|
+
/** Stops the server. */
|
|
20
|
+
close(): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Starts the live preview: a second Vite dev server on the project's own
|
|
24
|
+
* config, in mode `test` and on localhost only, whose page mounts one test
|
|
25
|
+
* at a time. A change to the config file takes a new server. When the
|
|
26
|
+
* project's describeMe() has `live: false`, it closes the server and rejects
|
|
27
|
+
* with an error whose `code` is `DESCRIBE_ME_LIVE_OFF`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createLiveServer(options: LiveServerOptions): Promise<LiveServer>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { relative, resolve } from 'node:path';
|
|
2
|
+
import { checkLiveEnabled } from './check-live-enabled.js';
|
|
3
|
+
import { findLiveConfigFile } from './find-live-config-file.js';
|
|
4
|
+
import { freeLivePort } from './free-live-port.js';
|
|
5
|
+
import { importLiveVite } from './import-live-vite.js';
|
|
6
|
+
import { livePluginProtocol } from './live-plugin-protocol.js';
|
|
7
|
+
import { loadLiveConfig } from './load-live-config.js';
|
|
8
|
+
/** Closes the server when `run` throws, so a failed start leaves no server behind. */
|
|
9
|
+
async function closingOnError(server, run) {
|
|
10
|
+
try {
|
|
11
|
+
return await run();
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
await server.close();
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Starts the live preview: a second Vite dev server on the project's own
|
|
20
|
+
* config, in mode `test` and on localhost only, whose page mounts one test
|
|
21
|
+
* at a time. A change to the config file takes a new server. When the
|
|
22
|
+
* project's describeMe() has `live: false`, it closes the server and rejects
|
|
23
|
+
* with an error whose `code` is `DESCRIBE_ME_LIVE_OFF`.
|
|
24
|
+
*/
|
|
25
|
+
export async function createLiveServer(options) {
|
|
26
|
+
const root = resolve(options.root);
|
|
27
|
+
const configFile = options.configFile === undefined ? findLiveConfigFile(root) : resolve(root, options.configFile);
|
|
28
|
+
const vite = await importLiveVite(root);
|
|
29
|
+
const loaded = await loadLiveConfig(vite, configFile, root);
|
|
30
|
+
const port = options.port ?? (await freeLivePort());
|
|
31
|
+
// With `configFile: false` Vite does not watch the config. An edit would
|
|
32
|
+
// restart the server and run the config again without the flag, in recording mode.
|
|
33
|
+
const server = await vite.createServer({
|
|
34
|
+
...vite.mergeConfig(loaded.config, {
|
|
35
|
+
root,
|
|
36
|
+
mode: 'test',
|
|
37
|
+
base: '/',
|
|
38
|
+
clearScreen: false,
|
|
39
|
+
logLevel: 'warn',
|
|
40
|
+
server: { host: 'localhost', port, strictPort: true, open: false },
|
|
41
|
+
}),
|
|
42
|
+
configFile: false,
|
|
43
|
+
});
|
|
44
|
+
const configName = relative(root, configFile) || configFile;
|
|
45
|
+
const { plugins } = server.config;
|
|
46
|
+
const protocol = await closingOnError(server, () => livePluginProtocol(plugins, configName));
|
|
47
|
+
await closingOnError(server, () => checkLiveEnabled(plugins, configName));
|
|
48
|
+
await closingOnError(server, () => server.listen());
|
|
49
|
+
const local = server.resolvedUrls?.local[0] ?? `http://localhost:${port}/`;
|
|
50
|
+
return {
|
|
51
|
+
base: new URL('__describe-me/', local).href,
|
|
52
|
+
protocol,
|
|
53
|
+
close: () => server.close(),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ViteUserConfig } from 'vitest/config';
|
|
2
|
+
import type { DescribeMeEnvironment } from './plugin.js';
|
|
3
|
+
/** The environment the tests run in, when the `environment` option does not say. */
|
|
4
|
+
export declare function detectEnvironment(userConfig: ViteUserConfig): DescribeMeEnvironment;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { styledComponentsBrowserBuild } from './styled-components-browser-build.js';
|
|
2
|
+
/** DOM environments such as jsdom, where tests render through Testing Library. */
|
|
3
|
+
export const domProfile = {
|
|
4
|
+
renderModules: {
|
|
5
|
+
react: {
|
|
6
|
+
original: '@testing-library/react',
|
|
7
|
+
adapter: '@describe-me/react/testing-library',
|
|
8
|
+
live: '@describe-me/react/live-testing-library',
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
setupFile: '@describe-me/vitest/setup-dom',
|
|
12
|
+
configFor({ userConfig, options }) {
|
|
13
|
+
const build = options.styledComponentsBrowserBuild === false
|
|
14
|
+
? undefined
|
|
15
|
+
: styledComponentsBrowserBuild(userConfig.root ?? process.cwd());
|
|
16
|
+
const userTest = userConfig.test;
|
|
17
|
+
const test = {
|
|
18
|
+
// Testing Library would otherwise unmount in its own afterEach, before the
|
|
19
|
+
// closing frame is taken. The adapter unmounts through the recorder instead.
|
|
20
|
+
env: { RTL_SKIP_AUTO_CLEANUP: 'true' },
|
|
21
|
+
};
|
|
22
|
+
// Vitest stubs CSS imports by default, which would leave every imported
|
|
23
|
+
// stylesheet out of the snapshots. An explicit choice by the user wins.
|
|
24
|
+
if (userTest?.css === undefined) {
|
|
25
|
+
test.css = true;
|
|
26
|
+
}
|
|
27
|
+
else if (userTest.css === false) {
|
|
28
|
+
console.warn('describe-me: `test.css` is false, so imported stylesheets are stubbed and will be missing from the snapshots.');
|
|
29
|
+
}
|
|
30
|
+
if (!build) {
|
|
31
|
+
return { config: { test }, setupWarnings: [] };
|
|
32
|
+
}
|
|
33
|
+
console.info('describe-me: using the browser build of styled-components, for createGlobalStyle');
|
|
34
|
+
return {
|
|
35
|
+
config: { resolve: build.config.resolve, test: { ...test, ...build.config.test } },
|
|
36
|
+
setupWarnings: build.warning ? [build.warning] : [],
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ViteUserConfig } from 'vitest/config';
|
|
2
|
+
import type { DescribeMeOptions } from './plugin.js';
|
|
3
|
+
/** The render module tests import, and the modules that replace it. */
|
|
4
|
+
export interface RenderModule {
|
|
5
|
+
/** The module tests import `render` from. */
|
|
6
|
+
original: string;
|
|
7
|
+
/** Our recording adapter for it. */
|
|
8
|
+
adapter: string;
|
|
9
|
+
/** The live preview's stand-in for it, which mounts at the first render and stops the test. */
|
|
10
|
+
live: string;
|
|
11
|
+
}
|
|
12
|
+
/** What the plugin hands a profile when it asks for its config. */
|
|
13
|
+
export interface ProfileContext {
|
|
14
|
+
userConfig: ViteUserConfig;
|
|
15
|
+
options: DescribeMeOptions;
|
|
16
|
+
renderModule: RenderModule;
|
|
17
|
+
}
|
|
18
|
+
/** One environment's config, and the setup problems found while building it. */
|
|
19
|
+
export interface ProfileConfig {
|
|
20
|
+
config: ViteUserConfig;
|
|
21
|
+
/** Handed to the reporter, which writes them to the manifest. */
|
|
22
|
+
setupWarnings: string[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Everything describe-me does differently in one environment. The plugin
|
|
26
|
+
* picks a profile and adds what every environment shares.
|
|
27
|
+
*/
|
|
28
|
+
export interface EnvironmentProfile {
|
|
29
|
+
/** For each framework, the render module tests import and our adapter for it. */
|
|
30
|
+
renderModules: Record<NonNullable<DescribeMeOptions['framework']>, RenderModule>;
|
|
31
|
+
/** The setup file that patches interactions and registers the recording hooks. */
|
|
32
|
+
setupFile: string;
|
|
33
|
+
/** The rest of the environment's config, built in the plugin's `config` hook. */
|
|
34
|
+
configFor(context: ProfileContext): ProfileConfig;
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const NAMES = ['vitest.config', 'vite.config'];
|
|
4
|
+
const EXTENSIONS = ['ts', 'mts', 'cts', 'js', 'mjs', 'cjs'];
|
|
5
|
+
/** The config file Vitest picks in `root` when none is named: `vitest.config.*`, then `vite.config.*`. */
|
|
6
|
+
export function findLiveConfigFile(root) {
|
|
7
|
+
for (const name of NAMES) {
|
|
8
|
+
for (const extension of EXTENSIONS) {
|
|
9
|
+
const file = join(root, `${name}.${extension}`);
|
|
10
|
+
if (existsSync(file)) {
|
|
11
|
+
return file;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
throw new Error(`describe-me: no Vitest or Vite config file in ${root}`);
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LiveSuite, LiveTest } from './live-suite.js';
|
|
2
|
+
/** A registered test, with the suites around it from the root down. */
|
|
3
|
+
export interface LiveTestMatch {
|
|
4
|
+
test: LiveTest;
|
|
5
|
+
suites: LiveSuite[];
|
|
6
|
+
/** The suite names, outermost first, then the test name. The root has no name. */
|
|
7
|
+
path: string[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The `occurrence`-th test whose suite path and name equal `path`, counted
|
|
11
|
+
* 1-based over the whole file like the stable test id, or null.
|
|
12
|
+
*/
|
|
13
|
+
export declare function findLiveTest(path: string[], occurrence: number): LiveTestMatch | null;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { liveRegistry } from './live-registry.js';
|
|
2
|
+
/** Every test, depth first in registration order, the order Vitest reports them in. */
|
|
3
|
+
function* testsIn(suite, suites, names) {
|
|
4
|
+
for (const child of suite.children) {
|
|
5
|
+
if (child.kind === 'test') {
|
|
6
|
+
yield { test: child, suites, path: [...names, child.name] };
|
|
7
|
+
}
|
|
8
|
+
else {
|
|
9
|
+
yield* testsIn(child, [...suites, child], [...names, child.name]);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The `occurrence`-th test whose suite path and name equal `path`, counted
|
|
15
|
+
* 1-based over the whole file like the stable test id, or null.
|
|
16
|
+
*/
|
|
17
|
+
export function findLiveTest(path, occurrence) {
|
|
18
|
+
const root = liveRegistry().root;
|
|
19
|
+
const wanted = JSON.stringify(path);
|
|
20
|
+
let seen = 0;
|
|
21
|
+
for (const match of testsIn(root, [root], [])) {
|
|
22
|
+
if (JSON.stringify(match.path) !== wanted) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
seen += 1;
|
|
26
|
+
if (seen === occurrence) {
|
|
27
|
+
return match;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|