@describe-me/vitest 0.5.2 → 0.5.4

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 CHANGED
@@ -1,106 +1,75 @@
1
1
  # @describe-me/vitest
2
2
 
3
- The Vitest integration for [describe-me](https://github.com/grzehub/describe-me):
4
- a plugin, a setup file that makes every interaction record a frame, and the Node
5
- reporter that writes `.describe-me/`.
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
- ## Install
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
- ```sh
10
- pnpm add -D @describe-me/vitest @describe-me/react describe-me
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
- All describe-me packages must be on the same version
14
- ([Add it to your project](https://github.com/grzehub/describe-me#add-it-to-your-project)).
17
+ Status: prototype. React 18 and 19, Vitest 4 and 5 in browser mode or jsdom,
18
+ static snapshots.
15
19
 
16
- ## Usage
20
+ ## Which package do I need
17
21
 
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:
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 |
21
28
 
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 and turns it
30
- off while the recorder waits, so act warnings match a run without
31
- describe-me. It also works without user-event installed.
29
+ ## Quick start
32
30
 
33
- jsdom has no layout, so positioned popovers show in the top-left corner and
34
- canvas stays blank
35
- ([Limitations in jsdom](https://github.com/grzehub/describe-me#limitations-in-jsdom)).
31
+ ```sh
32
+ pnpm add -D describe-me @describe-me/vitest @describe-me/react
33
+ ```
36
34
 
37
35
  ```ts
36
+ // vitest.config.ts
38
37
  import { describeMe } from '@describe-me/vitest/plugin'
39
38
 
40
- // browser mode
41
- export default defineConfig({
42
- plugins: [react(), describeMe()],
43
- test: {
44
- browser: { enabled: true, provider: playwright(), instances: [{ browser: 'chromium' }] },
45
- },
46
- })
47
-
48
- // jsdom
49
39
  export default defineConfig({
50
40
  plugins: [react(), describeMe()],
51
- test: { environment: 'jsdom' },
41
+ test: { environment: 'jsdom' }, // or your browser-mode settings
52
42
  })
53
43
  ```
54
44
 
55
- Pass `describeMe({ environment: 'browser' | 'dom' })` to override the detection.
56
-
57
- A test that records no frame before the closing one (no recording `render`,
58
- no interaction, no `step()`) costs no snapshot and is left out of the
59
- manifest. `describeMe({ include, exclude })` chooses which test files are
60
- recorded, with picomatch globs relative to the Vitest root. `exclude` wins.
61
- Tests in other files still run, but record nothing. Leaving `include` out
62
- records every test file. `include: []` records none and warns, while
63
- `exclude: '**'` records none without a warning. The reporter takes the
64
- same two options, but on its own it only filters the manifest: without the
65
- plugin the setup file does not know them.
66
-
67
- `describeMe({ previewHead })` takes HTML that the viewer adds to the start of
68
- every frame's `<head>`, like Storybook's `preview-head.html`: typically the
69
- font links that the app shell loads and tests never do. It never reaches the
70
- test page, and the local files it links are copied into the output directory
71
- ([Fonts](https://github.com/grzehub/describe-me#fonts) in the root README).
72
-
73
- `describeMe({ renderFrame })` chooses when the render frame is taken:
74
- `'eager'` (default) right after mount, `'lazy'` right before the next
75
- interaction, or `{ pending: '<selector>' }` once a loader is gone. See
76
- [Render frame timing](https://github.com/grzehub/describe-me#render-frame-timing).
77
-
78
- The plugin adds its reporter to the ones your config names. If it names
79
- none, the reporter joins the ones Vitest picks on its own, such as `minimal`
80
- for AI agents and `github-actions` in GitHub Actions.
81
-
82
- The pieces can also be wired by hand. Tests then import from the adapter
83
- themselves: `render` from `@describe-me/react` in browser mode, and `render`,
84
- `fireEvent` and `cleanup` from `@describe-me/react/testing-library` in jsdom:
85
-
86
- ```ts
87
- import DescribeMeReporter from '@describe-me/vitest/reporter'
88
-
89
- export default defineConfig({
90
- test: {
91
- // browser mode: '@describe-me/vitest/setup'
92
- setupFiles: ['@describe-me/vitest/setup-dom'],
93
- reporters: ['default', new DescribeMeReporter()],
94
- // jsdom only: keep imported CSS and leave unmounting to describe-me
95
- css: true,
96
- env: { RTL_SKIP_AUTO_CLEANUP: 'true' },
97
- },
98
- })
99
- ```
100
-
101
- `typescript` is an optional peer: without it the docs are still generated, only
102
- the props tables are left out. `@testing-library/user-event` is an optional
103
- peer too, needed only in DOM environments.
104
-
105
- See the [root README](https://github.com/grzehub/describe-me#readme) for the
106
- full picture.
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
+
69
+ `typescript` and `@testing-library/user-event` are optional peers. Without
70
+ TypeScript the docs have no props tables. user-event is needed only when your
71
+ jsdom tests use it.
72
+
73
+ ## License
74
+
75
+ MIT
@@ -3,7 +3,8 @@
3
3
  * It comes in two shapes, the direct API (`userEvent.click(el)`) and an
4
4
  * instance from `userEvent.setup()`, and the first delegates to the second,
5
5
  * so both are patched and `recordAction`'s depth guard keeps one gesture to
6
- * one frame. When the optional peer is not installed, there is nothing to
7
- * patch. Safe to call more than once.
6
+ * one frame. Under @testing-library/react a frame is taken as soon as the
7
+ * call resolves, otherwise after one settle. When the optional peer is not
8
+ * installed, there is nothing to patch. Safe to call more than once.
8
9
  */
9
10
  export declare function patchTestingLibraryUserEvent(): Promise<void>;
@@ -1,6 +1,7 @@
1
1
  import { elementLabel } from '@describe-me/core';
2
2
  import { describeArg } from './describe-arg.js';
3
3
  import { recordAction } from './record-action.js';
4
+ import { testingLibraryWrapsInAct } from './testing-library-wraps-in-act.js';
4
5
  /** Every interaction of user-event 14 that can change what the page shows. */
5
6
  const METHODS = [
6
7
  'click',
@@ -31,7 +32,11 @@ function patchMethods(target) {
31
32
  if (typeof original !== 'function' || original[PATCHED]) {
32
33
  continue;
33
34
  }
34
- const wrapped = (...args) => recordAction(labelFor(method, args), async () => original.apply(target, args));
35
+ // Testing Library's wrappers commit the DOM before the call resolves. A settle
36
+ // would only move React updates into a wait with the act flag off.
37
+ const wrapped = (...args) => recordAction(labelFor(method, args), async () => original.apply(target, args), {
38
+ settle: !testingLibraryWrapsInAct(),
39
+ });
35
40
  wrapped[PATCHED] = true;
36
41
  target[method] = wrapped;
37
42
  }
@@ -41,8 +46,9 @@ function patchMethods(target) {
41
46
  * It comes in two shapes, the direct API (`userEvent.click(el)`) and an
42
47
  * instance from `userEvent.setup()`, and the first delegates to the second,
43
48
  * so both are patched and `recordAction`'s depth guard keeps one gesture to
44
- * one frame. When the optional peer is not installed, there is nothing to
45
- * patch. Safe to call more than once.
49
+ * one frame. Under @testing-library/react a frame is taken as soon as the
50
+ * call resolves, otherwise after one settle. When the optional peer is not
51
+ * installed, there is nothing to patch. Safe to call more than once.
46
52
  */
47
53
  export async function patchTestingLibraryUserEvent() {
48
54
  let userEvent;
@@ -1,2 +1,6 @@
1
- /** Run one user interaction and record a frame for it, but only for the outermost call. */
2
- export declare function recordAction<T>(label: string, run: () => Promise<T>): Promise<T>;
1
+ import type { CaptureOptions } from '@describe-me/core';
2
+ /**
3
+ * Run one user interaction and record a frame for it, but only for the outermost call.
4
+ * `settle: false` takes the frame as soon as the interaction resolves, without a wait.
5
+ */
6
+ export declare function recordAction<T>(label: string, run: () => Promise<T>, options?: Pick<CaptureOptions, 'settle'>): Promise<T>;
@@ -7,8 +7,11 @@ import { recorder } from '@describe-me/core';
7
7
  * test left running cannot swallow the next test's frame.
8
8
  */
9
9
  const nesting = { generation: -1, depth: 0 };
10
- /** Run one user interaction and record a frame for it, but only for the outermost call. */
11
- export async function recordAction(label, run) {
10
+ /**
11
+ * Run one user interaction and record a frame for it, but only for the outermost call.
12
+ * `settle: false` takes the frame as soon as the interaction resolves, without a wait.
13
+ */
14
+ export async function recordAction(label, run, options = {}) {
12
15
  const generation = recorder.generation;
13
16
  if (generation !== nesting.generation) {
14
17
  nesting.generation = generation;
@@ -26,7 +29,7 @@ export async function recordAction(label, run) {
26
29
  }
27
30
  }
28
31
  if (generation === nesting.generation && nesting.depth === 0) {
29
- await recorder.capture('action', label, undefined, { generation });
32
+ await recorder.capture('action', label, undefined, { generation, settle: options.settle });
30
33
  }
31
34
  return result;
32
35
  }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Whether @testing-library/dom still runs user-event calls through the
3
+ * `asyncWrapper` that @testing-library/react installed, so the DOM is committed
4
+ * when a call resolves. False when @describe-me/react/testing-library never loaded.
5
+ */
6
+ export declare function testingLibraryWrapsInAct(): boolean;
@@ -0,0 +1,11 @@
1
+ // `publish-testing-library-act.ts` in @describe-me/react writes this key.
2
+ const ACT_CHECK = Symbol.for('describe-me.testing-library-act');
3
+ /**
4
+ * Whether @testing-library/dom still runs user-event calls through the
5
+ * `asyncWrapper` that @testing-library/react installed, so the DOM is committed
6
+ * when a call resolves. False when @describe-me/react/testing-library never loaded.
7
+ */
8
+ export function testingLibraryWrapsInAct() {
9
+ const check = globalThis[ACT_CHECK];
10
+ return typeof check === 'function' && check() === true;
11
+ }
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@describe-me/vitest",
3
- "version": "0.5.2",
4
- "description": "Vitest integration for describe-me: plugin, setup file and reporter",
3
+ "version": "0.5.4",
4
+ "description": "Living component documentation from your Vitest tests: the Vitest plugin, setup files and reporter of describe-me",
5
5
  "keywords": [
6
6
  "vitest",
7
+ "react",
7
8
  "storybook",
9
+ "storybook alternative",
8
10
  "component documentation",
9
- "visual testing",
11
+ "living documentation",
12
+ "jsdom",
10
13
  "testing"
11
14
  ],
12
15
  "license": "MIT",
@@ -60,13 +63,13 @@
60
63
  "dependencies": {
61
64
  "picomatch": "^4.0.3",
62
65
  "std-env": "^4.0.0",
63
- "@describe-me/core": "^0.5.2"
66
+ "@describe-me/core": "^0.5.4"
64
67
  },
65
68
  "peerDependencies": {
66
69
  "vitest": "^4.0.0 || ^5.0.0",
67
70
  "typescript": "^5.0.0 || ^6.0.0",
68
71
  "@testing-library/user-event": "^14.0.0",
69
- "describe-me": "^0.5.2"
72
+ "describe-me": "^0.5.4"
70
73
  },
71
74
  "peerDependenciesMeta": {
72
75
  "typescript": {