@describe-me/vitest 0.5.0-next.1 → 0.5.0-next.2
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 +5 -0
- package/dist/plugin.d.ts +7 -0
- package/dist/plugin.js +3 -1
- package/dist/read-runtime-options.js +5 -1
- package/dist/record-action.js +18 -8
- package/dist/register-recording-hooks.d.ts +5 -4
- package/dist/register-recording-hooks.js +11 -5
- package/dist/runtime-options.d.ts +3 -1
- package/dist/runtime-options.js +1 -1
- package/dist/setup-dom.js +3 -2
- package/dist/setup.js +2 -1
- package/dist/validate-render-frame.d.ts +6 -0
- package/dist/validate-render-frame.js +26 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -61,6 +61,11 @@ font links that the app shell loads and tests never do. It never reaches the
|
|
|
61
61
|
test page, and the local files it links are copied into the output directory
|
|
62
62
|
([Fonts](https://github.com/grzehub/describe-me#fonts) in the root README).
|
|
63
63
|
|
|
64
|
+
`describeMe({ renderFrame })` chooses when the render frame is taken:
|
|
65
|
+
`'eager'` (default) right after mount, `'lazy'` right before the next
|
|
66
|
+
interaction, or `{ pending: '<selector>' }` once a loader is gone. See
|
|
67
|
+
[Render frame timing](https://github.com/grzehub/describe-me#render-frame-timing).
|
|
68
|
+
|
|
64
69
|
The pieces can also be wired by hand. Tests then import from the adapter
|
|
65
70
|
themselves: `render` from `@describe-me/react` in browser mode, and `render`,
|
|
66
71
|
`fireEvent` and `cleanup` from `@describe-me/react/testing-library` in jsdom:
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ViteUserConfig } from 'vitest/config';
|
|
2
|
+
import type { RenderFrameMode } from '@describe-me/core/types';
|
|
2
3
|
/** Where the tests run: Vitest browser mode, or a simulated DOM such as jsdom. */
|
|
3
4
|
export type DescribeMeEnvironment = 'browser' | 'dom';
|
|
4
5
|
export interface DescribeMeOptions {
|
|
@@ -41,6 +42,12 @@ export interface DescribeMeOptions {
|
|
|
41
42
|
* Project files it links are copied into the output directory. Default: none.
|
|
42
43
|
*/
|
|
43
44
|
previewHead?: string;
|
|
45
|
+
/**
|
|
46
|
+
* When the render frame is taken: `'eager'` right after mount, `'lazy'`
|
|
47
|
+
* before the next interaction, `{ pending }` once nothing matches the
|
|
48
|
+
* selector. Default: `'eager'`.
|
|
49
|
+
*/
|
|
50
|
+
renderFrame?: RenderFrameMode;
|
|
44
51
|
}
|
|
45
52
|
type VitePlugin = NonNullable<ViteUserConfig['plugins']>[number];
|
|
46
53
|
/**
|
package/dist/plugin.js
CHANGED
|
@@ -5,6 +5,7 @@ import { registerExports } from './register-exports.js';
|
|
|
5
5
|
import DescribeMeReporter from './reporter.js';
|
|
6
6
|
import { RUNTIME_OPTIONS_KEY } from './runtime-options.js';
|
|
7
7
|
import { styledComponentsBrowserBuild } from './styled-components-browser-build.js';
|
|
8
|
+
import { validateRenderFrame } from './validate-render-frame.js';
|
|
8
9
|
/** For each framework and environment: the render module tests import, and our adapter that wraps it. */
|
|
9
10
|
const RENDER_MODULES = {
|
|
10
11
|
react: {
|
|
@@ -101,11 +102,12 @@ export function describeMe(options = {}) {
|
|
|
101
102
|
}
|
|
102
103
|
const environment = options.environment ?? detectEnvironment(userConfig);
|
|
103
104
|
renderModule = RENDER_MODULES[framework][environment];
|
|
104
|
-
//
|
|
105
|
+
// Checked here, so invalid options fail before any test runs. Vite
|
|
105
106
|
// deep-merges `provide`, so the user's own keys survive.
|
|
106
107
|
const runtimeOptions = {
|
|
107
108
|
include: compileGlobs(options.include),
|
|
108
109
|
exclude: compileGlobs(options.exclude),
|
|
110
|
+
renderFrame: validateRenderFrame(options.renderFrame),
|
|
109
111
|
};
|
|
110
112
|
const provide = { [RUNTIME_OPTIONS_KEY]: runtimeOptions };
|
|
111
113
|
const reporter = new DescribeMeReporter({
|
|
@@ -2,5 +2,9 @@ import { inject } from 'vitest';
|
|
|
2
2
|
import { RUNTIME_OPTIONS_KEY } from './runtime-options.js';
|
|
3
3
|
/** Without the plugin nothing is provided, so a hand-wired setup keeps recording every test. */
|
|
4
4
|
export function readRuntimeOptions() {
|
|
5
|
-
return inject(RUNTIME_OPTIONS_KEY) ?? {
|
|
5
|
+
return (inject(RUNTIME_OPTIONS_KEY) ?? {
|
|
6
|
+
include: [],
|
|
7
|
+
exclude: [],
|
|
8
|
+
renderFrame: 'eager',
|
|
9
|
+
});
|
|
6
10
|
}
|
package/dist/record-action.js
CHANGED
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
import { recorder } from '@describe-me/core';
|
|
2
2
|
/**
|
|
3
|
-
* Nesting depth of interactions
|
|
4
|
-
* delegates to `locator.click()`, and `copy()` delegates
|
|
5
|
-
* without this guard one gesture would produce two
|
|
3
|
+
* Nesting depth of the interactions in flight, and the test they started in.
|
|
4
|
+
* `userEvent.click(el)` delegates to `locator.click()`, and `copy()` delegates
|
|
5
|
+
* to `keyboard()`, so without this guard one gesture would produce two
|
|
6
|
+
* frames. Keyed to the recorder's generation, so an interaction that an ended
|
|
7
|
+
* test left running cannot swallow the next test's frame.
|
|
6
8
|
*/
|
|
7
|
-
|
|
9
|
+
const nesting = { generation: -1, depth: 0 };
|
|
8
10
|
/** Run one user interaction and record a frame for it, but only for the outermost call. */
|
|
9
11
|
export async function recordAction(label, run) {
|
|
10
|
-
|
|
12
|
+
const generation = recorder.generation;
|
|
13
|
+
if (generation !== nesting.generation) {
|
|
14
|
+
nesting.generation = generation;
|
|
15
|
+
nesting.depth = 0;
|
|
16
|
+
}
|
|
17
|
+
recorder.beforeInteraction();
|
|
18
|
+
nesting.depth++;
|
|
11
19
|
let result;
|
|
12
20
|
try {
|
|
13
21
|
result = await run();
|
|
14
22
|
}
|
|
15
23
|
finally {
|
|
16
|
-
|
|
24
|
+
if (generation === nesting.generation) {
|
|
25
|
+
nesting.depth--;
|
|
26
|
+
}
|
|
17
27
|
}
|
|
18
|
-
if (depth === 0) {
|
|
19
|
-
await recorder.capture('action', label);
|
|
28
|
+
if (generation === nesting.generation && nesting.depth === 0) {
|
|
29
|
+
await recorder.capture('action', label, undefined, { generation });
|
|
20
30
|
}
|
|
21
31
|
return result;
|
|
22
32
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { RuntimeOptions } from './runtime-options.js';
|
|
2
2
|
/**
|
|
3
3
|
* The per-test lifecycle shared by every environment: begin a recording, take
|
|
4
|
-
* the closing frame, hand the frames to the
|
|
5
|
-
* then let adapters unmount. Teardown runs
|
|
6
|
-
* Testing Library's auto-cleanup is off in
|
|
7
|
-
* the one recorder, so they are not
|
|
4
|
+
* a deferred render frame or else the closing frame, hand the frames to the
|
|
5
|
+
* reporter via task.meta, and only then let adapters unmount. Teardown runs
|
|
6
|
+
* for unrecorded tests too, because Testing Library's auto-cleanup is off in
|
|
7
|
+
* jsdom. Concurrent tests would share the one recorder, so they are not
|
|
8
|
+
* recorded.
|
|
8
9
|
*/
|
|
9
10
|
export declare function registerRecordingHooks(options: RuntimeOptions): void;
|
|
@@ -3,14 +3,16 @@ import { META_KEY, recorder } from '@describe-me/core';
|
|
|
3
3
|
import { fileFilter } from './file-filter.js';
|
|
4
4
|
/**
|
|
5
5
|
* The per-test lifecycle shared by every environment: begin a recording, take
|
|
6
|
-
* the closing frame, hand the frames to the
|
|
7
|
-
* then let adapters unmount. Teardown runs
|
|
8
|
-
* Testing Library's auto-cleanup is off in
|
|
9
|
-
* the one recorder, so they are not
|
|
6
|
+
* a deferred render frame or else the closing frame, hand the frames to the
|
|
7
|
+
* reporter via task.meta, and only then let adapters unmount. Teardown runs
|
|
8
|
+
* for unrecorded tests too, because Testing Library's auto-cleanup is off in
|
|
9
|
+
* jsdom. Concurrent tests would share the one recorder, so they are not
|
|
10
|
+
* recorded.
|
|
10
11
|
*/
|
|
11
12
|
export function registerRecordingHooks(options) {
|
|
12
13
|
const isRecordedFile = fileFilter(options);
|
|
13
14
|
let warnedConcurrent = false;
|
|
15
|
+
recorder.configure({ renderFrame: options.renderFrame });
|
|
14
16
|
beforeEach((context) => {
|
|
15
17
|
if (context.task.concurrent) {
|
|
16
18
|
if (!warnedConcurrent) {
|
|
@@ -26,7 +28,11 @@ export function registerRecordingHooks(options) {
|
|
|
26
28
|
afterEach(async (context) => {
|
|
27
29
|
try {
|
|
28
30
|
if (recorder.isActive) {
|
|
29
|
-
await recorder.
|
|
31
|
+
const tookRender = await recorder.flush();
|
|
32
|
+
// A render frame taken this late already shows how the test ended.
|
|
33
|
+
if (!tookRender) {
|
|
34
|
+
await recorder.capture('end', 'end of test');
|
|
35
|
+
}
|
|
30
36
|
const record = recorder.end();
|
|
31
37
|
if (record.frames.length > 0) {
|
|
32
38
|
;
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Travels from the plugin to the setup files through Vitest's `provide` /
|
|
3
3
|
* `inject`. Keep it plain JSON: the browser receives it serialized, so a
|
|
4
4
|
* RegExp would not survive. The test runtime imports this file, so it imports
|
|
5
|
-
*
|
|
5
|
+
* only types.
|
|
6
6
|
*/
|
|
7
|
+
import type { RenderFrameMode } from '@describe-me/core/types';
|
|
7
8
|
export interface CompiledGlob {
|
|
8
9
|
source: string;
|
|
9
10
|
flags: string;
|
|
@@ -13,6 +14,7 @@ export interface RuntimeOptions {
|
|
|
13
14
|
include: CompiledGlob[];
|
|
14
15
|
/** Wins over `include`. */
|
|
15
16
|
exclude: CompiledGlob[];
|
|
17
|
+
renderFrame: RenderFrameMode;
|
|
16
18
|
}
|
|
17
19
|
export declare const RUNTIME_OPTIONS_KEY: "describe-me";
|
|
18
20
|
declare module 'vitest' {
|
package/dist/runtime-options.js
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* Travels from the plugin to the setup files through Vitest's `provide` /
|
|
3
3
|
* `inject`. Keep it plain JSON: the browser receives it serialized, so a
|
|
4
4
|
* RegExp would not survive. The test runtime imports this file, so it imports
|
|
5
|
-
*
|
|
5
|
+
* only types.
|
|
6
6
|
*/
|
|
7
7
|
export const RUNTIME_OPTIONS_KEY = 'describe-me';
|
package/dist/setup-dom.js
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* `setupFiles: ['@describe-me/vitest/setup-dom']`. Patches Testing Library's
|
|
4
4
|
* userEvent, when it is installed, so every interaction records a frame, sets
|
|
5
5
|
* React's act flag where Testing Library would, and registers the recording
|
|
6
|
-
* hooks. Which test files are recorded (`include` / `exclude`)
|
|
7
|
-
*
|
|
6
|
+
* hooks. Which test files are recorded (`include` / `exclude`) and when the
|
|
7
|
+
* render frame is taken (`renderFrame`) come from the plugin. Never imports
|
|
8
|
+
* `vitest/browser`, which only exists in browser mode.
|
|
8
9
|
*/
|
|
9
10
|
import { mirrorReactActEnvironment } from './mirror-react-act-environment.js';
|
|
10
11
|
import { patchTestingLibraryUserEvent } from './patch-testing-library-user-event.js';
|
package/dist/setup.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Vitest setup file for browser mode: `setupFiles: ['@describe-me/vitest/setup']`.
|
|
3
3
|
* Patches Vitest's locators and userEvent so every interaction records a
|
|
4
4
|
* frame, and registers the recording hooks. Which test files are recorded
|
|
5
|
-
* (`include` / `exclude`)
|
|
5
|
+
* (`include` / `exclude`) and when the render frame is taken (`renderFrame`)
|
|
6
|
+
* come from the plugin. For jsdom use `./setup-dom`.
|
|
6
7
|
*/
|
|
7
8
|
import { patchLocators } from './patch-locators.js';
|
|
8
9
|
import { patchUserEvent } from './patch-user-event.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { RenderFrameMode } from '@describe-me/core/types';
|
|
2
|
+
/**
|
|
3
|
+
* The plugin's `renderFrame`, checked before any test runs. Selector syntax is
|
|
4
|
+
* left to `recorder.configure()` in the test runtime, which has a DOM to parse it.
|
|
5
|
+
*/
|
|
6
|
+
export declare function validateRenderFrame(value: unknown): RenderFrameMode;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { inspect } from 'node:util';
|
|
2
|
+
const ACCEPTED = "'eager', 'lazy' or { pending: string, timeout?: number } with a non-empty selector and a timeout above 0 ms";
|
|
3
|
+
function isPendingMode(value) {
|
|
4
|
+
if (typeof value !== 'object' || value === null) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
const { pending, timeout, ...rest } = value;
|
|
8
|
+
if (Object.keys(rest).length > 0 || typeof pending !== 'string' || pending === '') {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
return (timeout === undefined ||
|
|
12
|
+
(typeof timeout === 'number' && Number.isFinite(timeout) && timeout > 0));
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The plugin's `renderFrame`, checked before any test runs. Selector syntax is
|
|
16
|
+
* left to `recorder.configure()` in the test runtime, which has a DOM to parse it.
|
|
17
|
+
*/
|
|
18
|
+
export function validateRenderFrame(value) {
|
|
19
|
+
if (value === undefined) {
|
|
20
|
+
return 'eager';
|
|
21
|
+
}
|
|
22
|
+
if (value === 'eager' || value === 'lazy' || isPendingMode(value)) {
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
throw new Error(`describe-me: renderFrame expects ${ACCEPTED}, got ${inspect(value)}`);
|
|
26
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@describe-me/vitest",
|
|
3
|
-
"version": "0.5.0-next.
|
|
3
|
+
"version": "0.5.0-next.2",
|
|
4
4
|
"description": "Vitest integration for describe-me: plugin, setup file and reporter",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"vitest",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
],
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"picomatch": "^4.0.3",
|
|
62
|
-
"@describe-me/core": "^0.5.0-next.
|
|
62
|
+
"@describe-me/core": "^0.5.0-next.2"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
65
|
"vitest": "^4.0.0 || ^5.0.0",
|