@describe-me/vitest 0.5.0 → 0.5.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 +13 -4
- package/dist/act-neutral-wait.d.ts +7 -0
- package/dist/act-neutral-wait.js +30 -0
- package/dist/asset-store.js +3 -2
- package/dist/collect-component-docs-safely.js +7 -1
- package/dist/collectable-files.d.ts +5 -0
- package/dist/collectable-files.js +10 -0
- package/dist/compile-include.d.ts +3 -0
- package/dist/compile-include.js +8 -0
- package/dist/default-reporters.d.ts +5 -0
- package/dist/default-reporters.js +35 -0
- package/dist/file-filter.d.ts +2 -1
- package/dist/file-filter.js +4 -3
- package/dist/plugin.d.ts +5 -2
- package/dist/plugin.js +24 -11
- package/dist/print-diagnostics.d.ts +5 -4
- package/dist/print-diagnostics.js +9 -5
- package/dist/read-generator.d.ts +7 -0
- package/dist/read-generator.js +21 -0
- package/dist/read-runtime-options.js +1 -1
- package/dist/register-recording-hooks.d.ts +2 -1
- package/dist/register-recording-hooks.js +2 -2
- package/dist/reporter.d.ts +7 -1
- package/dist/reporter.js +20 -4
- package/dist/runtime-options.d.ts +5 -2
- package/dist/setup-dom.js +4 -2
- package/dist/snapshot-store.js +3 -2
- package/dist/style-store.js +3 -2
- package/dist/styled-components-browser-build.d.ts +11 -1
- package/dist/styled-components-browser-build.js +16 -2
- package/dist/styled-components-tslib.d.ts +16 -0
- package/dist/styled-components-tslib.js +40 -0
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -10,6 +10,9 @@ reporter that writes `.describe-me/`.
|
|
|
10
10
|
pnpm add -D @describe-me/vitest @describe-me/react describe-me
|
|
11
11
|
```
|
|
12
12
|
|
|
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)).
|
|
15
|
+
|
|
13
16
|
## Usage
|
|
14
17
|
|
|
15
18
|
One line in the Vitest config registers the setup file and the reporter, and
|
|
@@ -23,9 +26,9 @@ plugin detects where the tests run:
|
|
|
23
26
|
`@testing-library/user-event` call, turns on `test.css` so imported
|
|
24
27
|
stylesheets reach the snapshots, and sets `RTL_SKIP_AUTO_CLEANUP` so the
|
|
25
28
|
component is unmounted only after the closing frame. The setup file sets
|
|
26
|
-
React's `IS_REACT_ACT_ENVIRONMENT` where Testing Library would
|
|
27
|
-
|
|
28
|
-
installed.
|
|
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
32
|
|
|
30
33
|
jsdom has no layout, so positioned popovers show in the top-left corner and
|
|
31
34
|
canvas stays blank
|
|
@@ -55,7 +58,9 @@ A test that records no frame before the closing one (no recording `render`,
|
|
|
55
58
|
no interaction, no `step()`) costs no snapshot and is left out of the
|
|
56
59
|
manifest. `describeMe({ include, exclude })` chooses which test files are
|
|
57
60
|
recorded, with picomatch globs relative to the Vitest root. `exclude` wins.
|
|
58
|
-
Tests in other files still run, but record nothing.
|
|
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
|
|
59
64
|
same two options, but on its own it only filters the manifest: without the
|
|
60
65
|
plugin the setup file does not know them.
|
|
61
66
|
|
|
@@ -70,6 +75,10 @@ test page, and the local files it links are copied into the output directory
|
|
|
70
75
|
interaction, or `{ pending: '<selector>' }` once a loader is gone. See
|
|
71
76
|
[Render frame timing](https://github.com/grzehub/describe-me#render-frame-timing).
|
|
72
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
|
+
|
|
73
82
|
The pieces can also be wired by hand. Tests then import from the adapter
|
|
74
83
|
themselves: `render` from `@describe-me/react` in browser mode, and `render`,
|
|
75
84
|
`fireEvent` and `cleanup` from `@describe-me/react/testing-library` in jsdom:
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run a recorder wait with React's `IS_REACT_ACT_ENVIRONMENT` flag off, as
|
|
3
|
+
* Testing Library's `asyncWrapper` does for `waitFor` and user-event. Otherwise
|
|
4
|
+
* an update that lands in the wait logs an act warning that a run without
|
|
5
|
+
* describe-me never shows.
|
|
6
|
+
*/
|
|
7
|
+
export declare function actNeutralWait(wait: () => Promise<void>): Promise<void>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Waits can overlap, for example an un-awaited interaction or `flush()` with
|
|
3
|
+
* captures in flight. The flag from before the first one comes back when the
|
|
4
|
+
* last one ends.
|
|
5
|
+
*/
|
|
6
|
+
let depth = 0;
|
|
7
|
+
let previous;
|
|
8
|
+
/**
|
|
9
|
+
* Run a recorder wait with React's `IS_REACT_ACT_ENVIRONMENT` flag off, as
|
|
10
|
+
* Testing Library's `asyncWrapper` does for `waitFor` and user-event. Otherwise
|
|
11
|
+
* an update that lands in the wait logs an act warning that a run without
|
|
12
|
+
* describe-me never shows.
|
|
13
|
+
*/
|
|
14
|
+
export async function actNeutralWait(wait) {
|
|
15
|
+
const host = globalThis;
|
|
16
|
+
if (depth === 0) {
|
|
17
|
+
previous = host.IS_REACT_ACT_ENVIRONMENT;
|
|
18
|
+
host.IS_REACT_ACT_ENVIRONMENT = false;
|
|
19
|
+
}
|
|
20
|
+
depth++;
|
|
21
|
+
try {
|
|
22
|
+
await wait();
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
depth--;
|
|
26
|
+
if (depth === 0) {
|
|
27
|
+
host.IS_REACT_ACT_ENVIRONMENT = previous;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
package/dist/asset-store.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { copyFileSync, existsSync, mkdirSync,
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync, } from 'node:fs';
|
|
3
3
|
import { dirname, extname, join, resolve, sep } from 'node:path';
|
|
4
4
|
import { ASSET_URL_PREFIX } from '@describe-me/core/types';
|
|
5
|
+
import { collectableFiles } from './collectable-files.js';
|
|
5
6
|
import { cssAssetReferences } from './css-asset-references.js';
|
|
6
7
|
import { rewriteCssReferences } from './rewrite-css-references.js';
|
|
7
8
|
import { rewriteHeadReferences } from './rewrite-head-references.js';
|
|
@@ -233,7 +234,7 @@ export class AssetStore {
|
|
|
233
234
|
* re-run were rewritten by an earlier run.
|
|
234
235
|
*/
|
|
235
236
|
collectGarbage(files, inlineTexts = []) {
|
|
236
|
-
const stored =
|
|
237
|
+
const stored = collectableFiles(this.dir);
|
|
237
238
|
if (stored.length === 0) {
|
|
238
239
|
return;
|
|
239
240
|
}
|
|
@@ -13,6 +13,12 @@ function warnAboutMissingTypeScript() {
|
|
|
13
13
|
* props tables instead of a crashing reporter.
|
|
14
14
|
*/
|
|
15
15
|
export async function collectComponentDocsSafely(root, entries) {
|
|
16
|
+
const rendered = Array.from(entries);
|
|
17
|
+
// Importing TypeScript takes a few hundred milliseconds, wasted on a run
|
|
18
|
+
// that recorded no component.
|
|
19
|
+
if (rendered.length === 0) {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
16
22
|
try {
|
|
17
23
|
await import('typescript');
|
|
18
24
|
}
|
|
@@ -21,5 +27,5 @@ export async function collectComponentDocsSafely(root, entries) {
|
|
|
21
27
|
return {};
|
|
22
28
|
}
|
|
23
29
|
const { collectComponentDocs } = await import('./collect-component-docs.js');
|
|
24
|
-
return collectComponentDocs(root,
|
|
30
|
+
return collectComponentDocs(root, rendered);
|
|
25
31
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { readdirSync } from 'node:fs';
|
|
2
|
+
/**
|
|
3
|
+
* The names of the entries in `dir` that garbage collection may delete. No
|
|
4
|
+
* store writes a folder, so a folder belongs to someone else and is left out.
|
|
5
|
+
*/
|
|
6
|
+
export function collectableFiles(dir) {
|
|
7
|
+
return readdirSync(dir, { withFileTypes: true })
|
|
8
|
+
.filter((entry) => !entry.isDirectory())
|
|
9
|
+
.map((entry) => entry.name);
|
|
10
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { compileGlobs } from './compile-globs.js';
|
|
2
|
+
/** Keeps an omitted `include` (`null`, every test file) apart from an empty list (no test file). */
|
|
3
|
+
export function compileInclude(patterns) {
|
|
4
|
+
if (patterns === undefined) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
return compileGlobs(patterns);
|
|
8
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { isAgent } from 'std-env';
|
|
4
|
+
import { configDefaults } from 'vitest/config';
|
|
5
|
+
/**
|
|
6
|
+
* Vitest 4.0 has no `agent` reporter and loads the name as a reporter module,
|
|
7
|
+
* which fails the run. An unreadable version counts as 4.0.
|
|
8
|
+
*/
|
|
9
|
+
function hasAgentReporter() {
|
|
10
|
+
try {
|
|
11
|
+
const manifest = createRequire(import.meta.url).resolve('vitest/package.json');
|
|
12
|
+
const { version } = JSON.parse(readFileSync(manifest, 'utf8'));
|
|
13
|
+
const [major, minor] = version.split('.').map(Number);
|
|
14
|
+
return major > 4 || (major === 4 && minor >= 1);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The reporters the installed Vitest uses when the config names none, such as
|
|
22
|
+
* `minimal` for AI agents and `github-actions` in GitHub Actions.
|
|
23
|
+
*/
|
|
24
|
+
export function defaultReporters() {
|
|
25
|
+
// Vitest 5 exports the list. Vitest 4 exports none and fills it while
|
|
26
|
+
// resolving the config, only when it is still empty, so its rule is rebuilt here.
|
|
27
|
+
if (configDefaults.reporters.length > 0) {
|
|
28
|
+
return [...configDefaults.reporters];
|
|
29
|
+
}
|
|
30
|
+
const reporters = [isAgent && hasAgentReporter() ? 'agent' : 'default'];
|
|
31
|
+
if (process.env.GITHUB_ACTIONS === 'true') {
|
|
32
|
+
reporters.push('github-actions');
|
|
33
|
+
}
|
|
34
|
+
return reporters;
|
|
35
|
+
}
|
package/dist/file-filter.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { RuntimeOptions } from './runtime-options.js';
|
|
2
2
|
/**
|
|
3
|
-
* Takes a path relative to the root, with posix separators.
|
|
3
|
+
* Takes a path relative to the root, with posix separators. An `include` of
|
|
4
|
+
* `null` selects every file, an empty list selects none. Build it once and
|
|
4
5
|
* reuse the returned function, which memoizes per file name. The test runtime
|
|
5
6
|
* imports this file, so no Node imports.
|
|
6
7
|
*/
|
package/dist/file-filter.js
CHANGED
|
@@ -2,12 +2,13 @@ function toRegExps(globs) {
|
|
|
2
2
|
return globs.map((glob) => new RegExp(glob.source, glob.flags));
|
|
3
3
|
}
|
|
4
4
|
/**
|
|
5
|
-
* Takes a path relative to the root, with posix separators.
|
|
5
|
+
* Takes a path relative to the root, with posix separators. An `include` of
|
|
6
|
+
* `null` selects every file, an empty list selects none. Build it once and
|
|
6
7
|
* reuse the returned function, which memoizes per file name. The test runtime
|
|
7
8
|
* imports this file, so no Node imports.
|
|
8
9
|
*/
|
|
9
10
|
export function fileFilter(options) {
|
|
10
|
-
const include = toRegExps(options.include);
|
|
11
|
+
const include = options.include === null ? null : toRegExps(options.include);
|
|
11
12
|
const exclude = toRegExps(options.exclude);
|
|
12
13
|
const answers = new Map();
|
|
13
14
|
return (fileName) => {
|
|
@@ -15,7 +16,7 @@ export function fileFilter(options) {
|
|
|
15
16
|
if (known !== undefined) {
|
|
16
17
|
return known;
|
|
17
18
|
}
|
|
18
|
-
const included = include
|
|
19
|
+
const included = include === null || include.some((glob) => glob.test(fileName));
|
|
19
20
|
const answer = included && !exclude.some((glob) => glob.test(fileName));
|
|
20
21
|
answers.set(fileName, answer);
|
|
21
22
|
return answer;
|
package/dist/plugin.d.ts
CHANGED
|
@@ -25,13 +25,16 @@ export interface DescribeMeOptions {
|
|
|
25
25
|
* DOM environments only. Load the browser build of styled-components (v5+)
|
|
26
26
|
* instead of the Node one, whose `createGlobalStyle` never inserts its CSS on
|
|
27
27
|
* the client: global resets and fonts would be missing from every snapshot.
|
|
28
|
-
* Applied when styled-components is installed.
|
|
28
|
+
* Applied when styled-components is installed. For 6.0 to 6.3 it also points
|
|
29
|
+
* `tslib` at the `tslib.es6.mjs` they resolve, so the optimizer keeps its
|
|
30
|
+
* exports. Default: true.
|
|
29
31
|
*/
|
|
30
32
|
styledComponentsBrowserBuild?: boolean;
|
|
31
33
|
/**
|
|
32
34
|
* Test files to record, as globs relative to the Vitest root, matched with
|
|
33
35
|
* picomatch, dotfiles included. Tests in other files still run, but record
|
|
34
|
-
* nothing and leave the manifest.
|
|
36
|
+
* nothing and leave the manifest. An empty list records no test file and
|
|
37
|
+
* warns. Default: every test file.
|
|
35
38
|
*/
|
|
36
39
|
include?: string | string[];
|
|
37
40
|
/** Test files never to record, as globs like `include`. Wins over `include`. Default: none. */
|
package/dist/plugin.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { adapterPackageRoot } from './adapter-package-root.js';
|
|
2
2
|
import { compileGlobs } from './compile-globs.js';
|
|
3
|
+
import { compileInclude } from './compile-include.js';
|
|
4
|
+
import { defaultReporters } from './default-reporters.js';
|
|
3
5
|
import { projectModulePath } from './project-module-path.js';
|
|
4
6
|
import { registerExports } from './register-exports.js';
|
|
5
7
|
import DescribeMeReporter from './reporter.js';
|
|
6
8
|
import { RUNTIME_OPTIONS_KEY } from './runtime-options.js';
|
|
7
|
-
import { styledComponentsBrowserBuild } from './styled-components-browser-build.js';
|
|
9
|
+
import { styledComponentsBrowserBuild, } from './styled-components-browser-build.js';
|
|
8
10
|
import { validateRenderFrame } from './validate-render-frame.js';
|
|
9
11
|
/** For each framework and environment: the render module tests import, and our adapter that wraps it. */
|
|
10
12
|
const RENDER_MODULES = {
|
|
@@ -49,7 +51,7 @@ function browserConfig(renderModule) {
|
|
|
49
51
|
},
|
|
50
52
|
};
|
|
51
53
|
}
|
|
52
|
-
function domConfig(userConfig,
|
|
54
|
+
function domConfig(userConfig, styledComponents) {
|
|
53
55
|
const userTest = userConfig.test;
|
|
54
56
|
const test = {
|
|
55
57
|
setupFiles: ['@describe-me/vitest/setup-dom'],
|
|
@@ -65,14 +67,19 @@ function domConfig(userConfig, options) {
|
|
|
65
67
|
else if (userTest.css === false) {
|
|
66
68
|
console.warn('describe-me: `test.css` is false, so imported stylesheets are stubbed and will be missing from the snapshots.');
|
|
67
69
|
}
|
|
68
|
-
const styledComponents = options.styledComponentsBrowserBuild === false
|
|
69
|
-
? undefined
|
|
70
|
-
: styledComponentsBrowserBuild(userConfig.root ?? process.cwd());
|
|
71
70
|
if (!styledComponents) {
|
|
72
71
|
return { test };
|
|
73
72
|
}
|
|
74
73
|
console.info('describe-me: using the browser build of styled-components, for createGlobalStyle');
|
|
75
|
-
|
|
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());
|
|
76
83
|
}
|
|
77
84
|
/**
|
|
78
85
|
* One-line integration: `plugins: [describeMe()]`.
|
|
@@ -105,21 +112,27 @@ export function describeMe(options = {}) {
|
|
|
105
112
|
// Checked here, so invalid options fail before any test runs. Vite
|
|
106
113
|
// deep-merges `provide`, so the user's own keys survive.
|
|
107
114
|
const runtimeOptions = {
|
|
108
|
-
include:
|
|
115
|
+
include: compileInclude(options.include),
|
|
109
116
|
exclude: compileGlobs(options.exclude),
|
|
110
117
|
renderFrame: validateRenderFrame(options.renderFrame),
|
|
111
118
|
};
|
|
112
119
|
const provide = { [RUNTIME_OPTIONS_KEY]: runtimeOptions };
|
|
120
|
+
const styledComponents = styledComponentsFor(environment, userConfig, options);
|
|
121
|
+
const setupWarnings = styledComponents?.warning ? [styledComponents.warning] : [];
|
|
113
122
|
const reporter = new DescribeMeReporter({
|
|
114
123
|
outDir,
|
|
115
124
|
include: options.include,
|
|
116
125
|
exclude: options.exclude,
|
|
117
126
|
previewHead: options.previewHead,
|
|
127
|
+
setupWarnings,
|
|
118
128
|
});
|
|
119
|
-
// Vite concatenates
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
const
|
|
129
|
+
// Vite concatenates our list with the user's. Any list replaces the
|
|
130
|
+
// reporters Vitest picks on its own, so without one of the user's, ours
|
|
131
|
+
// joins Vitest's defaults.
|
|
132
|
+
const reporters = userConfig.test?.reporters ? [reporter] : [...defaultReporters(), reporter];
|
|
133
|
+
const config = environment === 'browser'
|
|
134
|
+
? browserConfig(renderModule)
|
|
135
|
+
: domConfig(userConfig, styledComponents);
|
|
123
136
|
return { ...config, test: { ...config.test, reporters, provide } };
|
|
124
137
|
},
|
|
125
138
|
async resolveId(source, importer) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Manifest } from '@describe-me/core/types';
|
|
2
2
|
/**
|
|
3
|
-
* Warn
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* Warn about what the manifest could not document, one line per kind. Setup
|
|
4
|
+
* warnings come first, one line each, because they can explain failing tests.
|
|
5
|
+
* Prints nothing when the setup is sound, everything is named, documented and
|
|
6
|
+
* loadable, every font is loaded and no stylesheet comes from a remote host.
|
|
7
|
+
* The viewer shows the same list in its header.
|
|
7
8
|
*/
|
|
8
9
|
export declare function printDiagnostics(manifest: Manifest): void;
|
|
@@ -13,13 +13,17 @@ function plural(count, one, many) {
|
|
|
13
13
|
return `${count} ${count === 1 ? one : many}`;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* Warn
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* Warn about what the manifest could not document, one line per kind. Setup
|
|
17
|
+
* warnings come first, one line each, because they can explain failing tests.
|
|
18
|
+
* Prints nothing when the setup is sound, everything is named, documented and
|
|
19
|
+
* loadable, every font is loaded and no stylesheet comes from a remote host.
|
|
20
|
+
* The viewer shows the same list in its header.
|
|
20
21
|
*/
|
|
21
22
|
export function printDiagnostics(manifest) {
|
|
22
|
-
const { anonymous, undocumented, assetsMissing, fontsMissing, remoteStylesheets } = manifestDiagnostics(manifest);
|
|
23
|
+
const { setupWarnings, anonymous, undocumented, assetsMissing, fontsMissing, remoteStylesheets } = manifestDiagnostics(manifest);
|
|
24
|
+
for (const warning of setupWarnings) {
|
|
25
|
+
console.warn(`describe-me: ${warning}`);
|
|
26
|
+
}
|
|
23
27
|
if (anonymous.length > 0) {
|
|
24
28
|
console.warn(`describe-me: ${plural(anonymous.length, 'test renders', 'tests render')} an anonymous component; export it from a project module or give it a displayName`);
|
|
25
29
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ManifestGenerator } from '@describe-me/core/types';
|
|
2
|
+
/**
|
|
3
|
+
* The name and version of this package, for the manifest's `generator`, or
|
|
4
|
+
* `undefined` when its package.json cannot be read, so the manifest is still
|
|
5
|
+
* written.
|
|
6
|
+
*/
|
|
7
|
+
export declare function readGenerator(): ManifestGenerator | undefined;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
/**
|
|
3
|
+
* The name and version of this package, for the manifest's `generator`, or
|
|
4
|
+
* `undefined` when its package.json cannot be read, so the manifest is still
|
|
5
|
+
* written.
|
|
6
|
+
*/
|
|
7
|
+
export function readGenerator() {
|
|
8
|
+
try {
|
|
9
|
+
// The compiled file sits in `dist/`, one level below the package root, and
|
|
10
|
+
// `exports` does not list `./package.json`.
|
|
11
|
+
const file = new URL('../package.json', import.meta.url);
|
|
12
|
+
const { name, version } = JSON.parse(readFileSync(file, 'utf8'));
|
|
13
|
+
if (typeof name !== 'string' || typeof version !== 'string') {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
return { name, version };
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -3,7 +3,7 @@ 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
5
|
return (inject(RUNTIME_OPTIONS_KEY) ?? {
|
|
6
|
-
include:
|
|
6
|
+
include: null,
|
|
7
7
|
exclude: [],
|
|
8
8
|
renderFrame: 'eager',
|
|
9
9
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AroundWait } from '@describe-me/core';
|
|
1
2
|
import type { RuntimeOptions } from './runtime-options.js';
|
|
2
3
|
/**
|
|
3
4
|
* The per-test lifecycle shared by every environment: begin a recording, take
|
|
@@ -7,4 +8,4 @@ import type { RuntimeOptions } from './runtime-options.js';
|
|
|
7
8
|
* jsdom. Concurrent tests would share the one recorder, so they are not
|
|
8
9
|
* recorded.
|
|
9
10
|
*/
|
|
10
|
-
export declare function registerRecordingHooks(options: RuntimeOptions): void;
|
|
11
|
+
export declare function registerRecordingHooks(options: RuntimeOptions, aroundWait?: AroundWait): void;
|
|
@@ -9,10 +9,10 @@ import { fileFilter } from './file-filter.js';
|
|
|
9
9
|
* jsdom. Concurrent tests would share the one recorder, so they are not
|
|
10
10
|
* recorded.
|
|
11
11
|
*/
|
|
12
|
-
export function registerRecordingHooks(options) {
|
|
12
|
+
export function registerRecordingHooks(options, aroundWait) {
|
|
13
13
|
const isRecordedFile = fileFilter(options);
|
|
14
14
|
let warnedConcurrent = false;
|
|
15
|
-
recorder.configure({ renderFrame: options.renderFrame });
|
|
15
|
+
recorder.configure({ renderFrame: options.renderFrame, aroundWait });
|
|
16
16
|
beforeEach((context) => {
|
|
17
17
|
if (context.task.concurrent) {
|
|
18
18
|
if (!warnedConcurrent) {
|
package/dist/reporter.d.ts
CHANGED
|
@@ -4,7 +4,8 @@ export interface DescribeMeReporterOptions {
|
|
|
4
4
|
outDir?: string;
|
|
5
5
|
/**
|
|
6
6
|
* Which test files appear in the manifest: globs relative to the Vitest
|
|
7
|
-
* root, matched with picomatch, dotfiles included.
|
|
7
|
+
* root, matched with picomatch, dotfiles included. An empty list leaves every
|
|
8
|
+
* file out of the manifest and warns. Default: every test file.
|
|
8
9
|
*/
|
|
9
10
|
include?: string | string[];
|
|
10
11
|
/**
|
|
@@ -18,11 +19,16 @@ export interface DescribeMeReporterOptions {
|
|
|
18
19
|
* Project files it links are copied into the output directory. Default: none.
|
|
19
20
|
*/
|
|
20
21
|
previewHead?: string;
|
|
22
|
+
/** Problems in the project's setup, filled by the plugin. Default: none. */
|
|
23
|
+
setupWarnings?: string[];
|
|
21
24
|
}
|
|
22
25
|
export default class DescribeMeReporter implements Reporter {
|
|
23
26
|
private readonly outDirOption;
|
|
24
27
|
private readonly isRecordedFile;
|
|
28
|
+
private readonly emptyInclude;
|
|
25
29
|
private readonly previewHead;
|
|
30
|
+
private readonly generator;
|
|
31
|
+
private readonly setupWarnings;
|
|
26
32
|
private root;
|
|
27
33
|
private outDir;
|
|
28
34
|
private snapshots;
|
package/dist/reporter.js
CHANGED
|
@@ -8,11 +8,13 @@ import { META_KEY, } from '@describe-me/core/types';
|
|
|
8
8
|
import { AssetStore } from './asset-store.js';
|
|
9
9
|
import { collectComponentDocsSafely } from './collect-component-docs-safely.js';
|
|
10
10
|
import { compileGlobs } from './compile-globs.js';
|
|
11
|
+
import { compileInclude } from './compile-include.js';
|
|
11
12
|
import { componentEntries } from './component-entries.js';
|
|
12
13
|
import { fileFilter } from './file-filter.js';
|
|
13
14
|
import { FontAudit } from './font-audit.js';
|
|
14
15
|
import { isRecordedTest } from './is-recorded-test.js';
|
|
15
16
|
import { printDiagnostics } from './print-diagnostics.js';
|
|
17
|
+
import { readGenerator } from './read-generator.js';
|
|
16
18
|
import { SnapshotStore } from './snapshot-store.js';
|
|
17
19
|
import { stableTestIds } from './stable-test-ids.js';
|
|
18
20
|
import { StyleStore } from './style-store.js';
|
|
@@ -26,7 +28,10 @@ function recordOf(tc) {
|
|
|
26
28
|
export default class DescribeMeReporter {
|
|
27
29
|
outDirOption;
|
|
28
30
|
isRecordedFile;
|
|
31
|
+
emptyInclude;
|
|
29
32
|
previewHead;
|
|
33
|
+
generator;
|
|
34
|
+
setupWarnings;
|
|
30
35
|
root = process.cwd();
|
|
31
36
|
outDir = '';
|
|
32
37
|
snapshots;
|
|
@@ -36,12 +41,13 @@ export default class DescribeMeReporter {
|
|
|
36
41
|
modules = new Map();
|
|
37
42
|
constructor(options = {}) {
|
|
38
43
|
this.outDirOption = options.outDir ?? '.describe-me';
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
});
|
|
44
|
+
const include = compileInclude(options.include);
|
|
45
|
+
this.isRecordedFile = fileFilter({ include, exclude: compileGlobs(options.exclude) });
|
|
46
|
+
this.emptyInclude = include !== null && include.length === 0;
|
|
43
47
|
// A blank head, such as an empty preview-head.html, is the same as none.
|
|
44
48
|
this.previewHead = options.previewHead?.trim() ? options.previewHead : undefined;
|
|
49
|
+
this.generator = readGenerator();
|
|
50
|
+
this.setupWarnings = options.setupWarnings ?? [];
|
|
45
51
|
}
|
|
46
52
|
onInit(vitest) {
|
|
47
53
|
this.root = vitest.config.root;
|
|
@@ -54,6 +60,11 @@ export default class DescribeMeReporter {
|
|
|
54
60
|
if (this.previewHead !== undefined && /<script\b/i.test(this.previewHead)) {
|
|
55
61
|
console.warn('describe-me: previewHead contains a <script>, which the viewer never runs.');
|
|
56
62
|
}
|
|
63
|
+
// Here and not in the constructor: the plugin builds the reporter in
|
|
64
|
+
// `config()`, which also runs for a `vite build` that shares the config.
|
|
65
|
+
if (this.emptyInclude) {
|
|
66
|
+
console.warn("describe-me: include is an empty list, so no test file is recorded. Leave include out to record every test file, or set exclude: '**' to record none without this warning.");
|
|
67
|
+
}
|
|
57
68
|
}
|
|
58
69
|
onTestRunStart() {
|
|
59
70
|
this.assets.startRun();
|
|
@@ -136,6 +147,7 @@ export default class DescribeMeReporter {
|
|
|
136
147
|
const assetsMissing = this.assets.missing();
|
|
137
148
|
const manifest = {
|
|
138
149
|
version: 1,
|
|
150
|
+
generator: this.generator,
|
|
139
151
|
generatedAt: new Date().toISOString(),
|
|
140
152
|
root: this.root,
|
|
141
153
|
modules,
|
|
@@ -150,6 +162,10 @@ export default class DescribeMeReporter {
|
|
|
150
162
|
const styleFiles = this.styles.collectGarbage(snapshotFiles);
|
|
151
163
|
this.assets.collectGarbage([...snapshotFiles, ...styleFiles], head === undefined ? [] : [head]);
|
|
152
164
|
this.auditFonts(manifest);
|
|
165
|
+
// Never seeded from the last run: the plugin checks the setup on every start.
|
|
166
|
+
if (this.setupWarnings.length > 0) {
|
|
167
|
+
manifest.setupWarnings = this.setupWarnings;
|
|
168
|
+
}
|
|
153
169
|
writeFileSync(join(this.outDir, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
154
170
|
printDiagnostics(manifest);
|
|
155
171
|
}
|
|
@@ -10,8 +10,11 @@ export interface CompiledGlob {
|
|
|
10
10
|
flags: string;
|
|
11
11
|
}
|
|
12
12
|
export interface RuntimeOptions {
|
|
13
|
-
/**
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* `null` means every test file, an empty list means none. `null` survives
|
|
15
|
+
* `provide` / `inject` and Vite's config merge, `undefined` would not.
|
|
16
|
+
*/
|
|
17
|
+
include: CompiledGlob[] | null;
|
|
15
18
|
/** Wins over `include`. */
|
|
16
19
|
exclude: CompiledGlob[];
|
|
17
20
|
renderFrame: RenderFrameMode;
|
package/dist/setup-dom.js
CHANGED
|
@@ -3,14 +3,16 @@
|
|
|
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.
|
|
6
|
+
* hooks. The act flag is off while the recorder waits, as in Testing Library's
|
|
7
|
+
* own waits. Which test files are recorded (`include` / `exclude`) and when the
|
|
7
8
|
* render frame is taken (`renderFrame`) come from the plugin. Never imports
|
|
8
9
|
* `vitest/browser`, which only exists in browser mode.
|
|
9
10
|
*/
|
|
11
|
+
import { actNeutralWait } from './act-neutral-wait.js';
|
|
10
12
|
import { mirrorReactActEnvironment } from './mirror-react-act-environment.js';
|
|
11
13
|
import { patchTestingLibraryUserEvent } from './patch-testing-library-user-event.js';
|
|
12
14
|
import { readRuntimeOptions } from './read-runtime-options.js';
|
|
13
15
|
import { registerRecordingHooks } from './register-recording-hooks.js';
|
|
14
16
|
await patchTestingLibraryUserEvent();
|
|
15
17
|
mirrorReactActEnvironment();
|
|
16
|
-
registerRecordingHooks(readRuntimeOptions());
|
|
18
|
+
registerRecordingHooks(readRuntimeOptions(), actNeutralWait);
|
package/dist/snapshot-store.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { existsSync, mkdirSync,
|
|
2
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
+
import { collectableFiles } from './collectable-files.js';
|
|
4
5
|
/**
|
|
5
6
|
* Content-addressed store for the serialized DOM of every frame, living in
|
|
6
7
|
* `<outDir>/snapshots`. Identical DOM across tests or runs is written once.
|
|
@@ -27,7 +28,7 @@ export class SnapshotStore {
|
|
|
27
28
|
for (const path of referenced) {
|
|
28
29
|
keep.add(path.replace('snapshots/', ''));
|
|
29
30
|
}
|
|
30
|
-
for (const file of
|
|
31
|
+
for (const file of collectableFiles(this.dir)) {
|
|
31
32
|
if (!keep.has(file)) {
|
|
32
33
|
rmSync(join(this.dir, file), { force: true });
|
|
33
34
|
}
|
package/dist/style-store.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { existsSync, mkdirSync,
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { STYLE_URL_PREFIX } from '@describe-me/core/types';
|
|
5
|
+
import { collectableFiles } from './collectable-files.js';
|
|
5
6
|
import { splitStylesheet } from './split-stylesheet.js';
|
|
6
7
|
/** Shorter sheets stay inline, where they cost less than a reference and a file of their own. */
|
|
7
8
|
const MIN_EXTRACTED_LENGTH = 256;
|
|
@@ -54,7 +55,7 @@ export class StyleStore {
|
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
const kept = [];
|
|
57
|
-
for (const file of
|
|
58
|
+
for (const file of collectableFiles(this.dir)) {
|
|
58
59
|
if (keep.has(file)) {
|
|
59
60
|
kept.push(`styles/${file}`);
|
|
60
61
|
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import type { ViteUserConfig } from 'vitest/config';
|
|
2
|
+
/** The config that loads the browser build, and a setup problem found on the way. */
|
|
3
|
+
export interface StyledComponentsBrowserBuild {
|
|
4
|
+
config: ViteUserConfig;
|
|
5
|
+
/** Set when the build's tslib cannot be aliased, so it may fail to load. */
|
|
6
|
+
warning?: string;
|
|
7
|
+
}
|
|
2
8
|
/**
|
|
3
9
|
* The Vite and Vitest config that makes a DOM environment load the browser
|
|
4
10
|
* build of styled-components, or undefined when the project does not use
|
|
@@ -11,5 +17,9 @@ import type { ViteUserConfig } from 'vitest/config';
|
|
|
11
17
|
* styled-components through Node, which ignores the alias, and would get a
|
|
12
18
|
* second instance whose styles its serializer cannot see. Bundling both
|
|
13
19
|
* together gives them one.
|
|
20
|
+
*
|
|
21
|
+
* The optimizer bundles tslib's UMD file without its default export, which the
|
|
22
|
+
* browser build of 6.0 to 6.3 destructures. An alias is the only resolution the
|
|
23
|
+
* optimizer applies, so `tslib` is aliased to its ES module for the whole config.
|
|
14
24
|
*/
|
|
15
|
-
export declare function styledComponentsBrowserBuild(root: string):
|
|
25
|
+
export declare function styledComponentsBrowserBuild(root: string): StyledComponentsBrowserBuild | undefined;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { styledComponentsTslib } from './styled-components-tslib.js';
|
|
4
5
|
function resolverFor(root) {
|
|
5
6
|
// createRequire wants an absolute file inside the directory; it does not have to exist.
|
|
6
7
|
const requireFromRoot = createRequire(join(resolve(root), 'package.json'));
|
|
@@ -41,6 +42,10 @@ function browserModule(pkg) {
|
|
|
41
42
|
* styled-components through Node, which ignores the alias, and would get a
|
|
42
43
|
* second instance whose styles its serializer cannot see. Bundling both
|
|
43
44
|
* together gives them one.
|
|
45
|
+
*
|
|
46
|
+
* The optimizer bundles tslib's UMD file without its default export, which the
|
|
47
|
+
* browser build of 6.0 to 6.3 destructures. An alias is the only resolution the
|
|
48
|
+
* optimizer applies, so `tslib` is aliased to its ES module for the whole config.
|
|
44
49
|
*/
|
|
45
50
|
export function styledComponentsBrowserBuild(root) {
|
|
46
51
|
const resolveFromRoot = resolverFor(root);
|
|
@@ -62,8 +67,17 @@ export function styledComponentsBrowserBuild(root) {
|
|
|
62
67
|
if (resolveFromRoot('jest-styled-components')) {
|
|
63
68
|
include.push('jest-styled-components');
|
|
64
69
|
}
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
const alias = [{ find: /^styled-components$/, replacement }];
|
|
71
|
+
const tslib = styledComponentsTslib(manifest);
|
|
72
|
+
if (tslib.kind === 'alias') {
|
|
73
|
+
alias.push({ find: /^tslib$/, replacement: tslib.replacement });
|
|
74
|
+
}
|
|
75
|
+
const config = {
|
|
76
|
+
resolve: { alias },
|
|
67
77
|
test: { deps: { optimizer: { client: { enabled: true, include } } } },
|
|
68
78
|
};
|
|
79
|
+
if (tslib.kind === 'warning') {
|
|
80
|
+
return { config, warning: tslib.warning };
|
|
81
|
+
}
|
|
82
|
+
return { config };
|
|
69
83
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** How the browser build of styled-components gets its tslib helpers. */
|
|
2
|
+
export type StyledComponentsTslib = {
|
|
3
|
+
kind: 'untouched';
|
|
4
|
+
} | {
|
|
5
|
+
kind: 'alias';
|
|
6
|
+
replacement: string;
|
|
7
|
+
} | {
|
|
8
|
+
kind: 'warning';
|
|
9
|
+
warning: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Where `tslib` should point for the styled-components whose `package.json`
|
|
13
|
+
* is given. Only 6.0 to 6.3 import tslib, and only tslib 2.5.3 and later ship
|
|
14
|
+
* the `tslib.es6.mjs` that keeps its exports intact through the optimizer.
|
|
15
|
+
*/
|
|
16
|
+
export declare function styledComponentsTslib(styledComponentsManifest: string): StyledComponentsTslib;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
function readPackage(manifest) {
|
|
5
|
+
return JSON.parse(readFileSync(manifest, 'utf8'));
|
|
6
|
+
}
|
|
7
|
+
function resolveTslib(styledComponentsManifest) {
|
|
8
|
+
try {
|
|
9
|
+
return createRequire(styledComponentsManifest).resolve('tslib/package.json');
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Where `tslib` should point for the styled-components whose `package.json`
|
|
17
|
+
* is given. Only 6.0 to 6.3 import tslib, and only tslib 2.5.3 and later ship
|
|
18
|
+
* the `tslib.es6.mjs` that keeps its exports intact through the optimizer.
|
|
19
|
+
*/
|
|
20
|
+
export function styledComponentsTslib(styledComponentsManifest) {
|
|
21
|
+
const styledComponents = readPackage(styledComponentsManifest);
|
|
22
|
+
if (!styledComponents.dependencies?.tslib) {
|
|
23
|
+
return { kind: 'untouched' };
|
|
24
|
+
}
|
|
25
|
+
// Resolved from styled-components itself, so a nested or pnpm-linked tslib
|
|
26
|
+
// wins over whatever the project hoists.
|
|
27
|
+
const tslibManifest = resolveTslib(styledComponentsManifest);
|
|
28
|
+
if (!tslibManifest) {
|
|
29
|
+
return { kind: 'untouched' };
|
|
30
|
+
}
|
|
31
|
+
const replacement = join(dirname(tslibManifest), 'tslib.es6.mjs');
|
|
32
|
+
if (existsSync(replacement)) {
|
|
33
|
+
return { kind: 'alias', replacement };
|
|
34
|
+
}
|
|
35
|
+
const tslib = readPackage(tslibManifest);
|
|
36
|
+
return {
|
|
37
|
+
kind: 'warning',
|
|
38
|
+
warning: `styled-components ${styledComponents.version} resolves tslib ${tslib.version}, which has no tslib.es6.mjs, so its browser build may fail to load ("Cannot destructure property '__extends'"). Upgrade styled-components to 6.1.10 or later, or override tslib to 2.5.3 or later.`,
|
|
39
|
+
};
|
|
40
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@describe-me/vitest",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Vitest integration for describe-me: plugin, setup file and reporter",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"vitest",
|
|
@@ -59,12 +59,14 @@
|
|
|
59
59
|
],
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"picomatch": "^4.0.3",
|
|
62
|
-
"
|
|
62
|
+
"std-env": "^4.0.0",
|
|
63
|
+
"@describe-me/core": "^0.5.2"
|
|
63
64
|
},
|
|
64
65
|
"peerDependencies": {
|
|
65
66
|
"vitest": "^4.0.0 || ^5.0.0",
|
|
66
67
|
"typescript": "^5.0.0 || ^6.0.0",
|
|
67
|
-
"@testing-library/user-event": "^14.0.0"
|
|
68
|
+
"@testing-library/user-event": "^14.0.0",
|
|
69
|
+
"describe-me": "^0.5.2"
|
|
68
70
|
},
|
|
69
71
|
"peerDependenciesMeta": {
|
|
70
72
|
"typescript": {
|
|
@@ -72,6 +74,9 @@
|
|
|
72
74
|
},
|
|
73
75
|
"@testing-library/user-event": {
|
|
74
76
|
"optional": true
|
|
77
|
+
},
|
|
78
|
+
"describe-me": {
|
|
79
|
+
"optional": true
|
|
75
80
|
}
|
|
76
81
|
},
|
|
77
82
|
"devDependencies": {
|