@describe-me/core 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 +3 -0
- package/dist/manifest-diagnostics.d.ts +4 -3
- package/dist/manifest-diagnostics.js +5 -3
- package/dist/recorder.d.ts +8 -3
- package/dist/recorder.js +57 -15
- package/dist/types.d.ts +25 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,6 +14,9 @@ Most people never install this package directly — `@describe-me/react` and
|
|
|
14
14
|
pnpm add -D @describe-me/core
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
All describe-me packages must be on the same version
|
|
18
|
+
([Add it to your project](https://github.com/grzehub/describe-me#add-it-to-your-project)).
|
|
19
|
+
|
|
17
20
|
## Usage
|
|
18
21
|
|
|
19
22
|
`step()` names a phase of a test. The DOM is captured after the body resolves,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type Manifest, type ManifestDiagnostics } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* List what the manifest could not document: anonymous components, named ones
|
|
4
|
-
* without props, assets that will not load, font families that nothing loads
|
|
5
|
-
*
|
|
6
|
-
* run and the viewer shows it in the header, so
|
|
4
|
+
* without props, assets that will not load, font families that nothing loads,
|
|
5
|
+
* hosts that frames load stylesheets from and problems in the project's setup.
|
|
6
|
+
* The reporter prints it after a run and the viewer shows it in the header, so
|
|
7
|
+
* both agree on what is missing.
|
|
7
8
|
*/
|
|
8
9
|
export declare function manifestDiagnostics(manifest: Manifest): ManifestDiagnostics;
|
|
@@ -5,9 +5,10 @@ function isHostElement(name) {
|
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
7
|
* List what the manifest could not document: anonymous components, named ones
|
|
8
|
-
* without props, assets that will not load, font families that nothing loads
|
|
9
|
-
*
|
|
10
|
-
* run and the viewer shows it in the header, so
|
|
8
|
+
* without props, assets that will not load, font families that nothing loads,
|
|
9
|
+
* hosts that frames load stylesheets from and problems in the project's setup.
|
|
10
|
+
* The reporter prints it after a run and the viewer shows it in the header, so
|
|
11
|
+
* both agree on what is missing.
|
|
11
12
|
*/
|
|
12
13
|
export function manifestDiagnostics(manifest) {
|
|
13
14
|
const anonymous = [];
|
|
@@ -33,5 +34,6 @@ export function manifestDiagnostics(manifest) {
|
|
|
33
34
|
assetsMissing: manifest.assetsMissing ?? [],
|
|
34
35
|
fontsMissing: manifest.fontsMissing ?? [],
|
|
35
36
|
remoteStylesheets: manifest.remoteStylesheets ?? [],
|
|
37
|
+
setupWarnings: manifest.setupWarnings ?? [],
|
|
36
38
|
};
|
|
37
39
|
}
|
package/dist/recorder.d.ts
CHANGED
|
@@ -7,12 +7,15 @@ type Teardown = () => void;
|
|
|
7
7
|
* `beforeInteraction()`, `setComponent()` and `onTeardown()`.
|
|
8
8
|
*/
|
|
9
9
|
declare class Recorder {
|
|
10
|
+
/** Which contract this recorder follows, so another copy of core can refuse it. */
|
|
11
|
+
readonly protocol = 2;
|
|
10
12
|
private slots;
|
|
11
13
|
private currentGeneration;
|
|
12
14
|
private inFlight;
|
|
13
15
|
private deferred;
|
|
14
16
|
private pending;
|
|
15
17
|
private renderFrame;
|
|
18
|
+
private aroundWait;
|
|
16
19
|
private component?;
|
|
17
20
|
private startedAt;
|
|
18
21
|
private active;
|
|
@@ -26,9 +29,10 @@ declare class Recorder {
|
|
|
26
29
|
*/
|
|
27
30
|
get generation(): number;
|
|
28
31
|
/**
|
|
29
|
-
* Choose when the render frame is taken, from the next render capture on
|
|
30
|
-
* The setup files call it with the plugin's
|
|
31
|
-
* selector
|
|
32
|
+
* Choose when the render frame is taken, from the next render capture on,
|
|
33
|
+
* and what runs around each wait. The setup files call it with the plugin's
|
|
34
|
+
* options. A `pending` selector the DOM rejects falls back to `'lazy'` with
|
|
35
|
+
* a warning.
|
|
32
36
|
*/
|
|
33
37
|
configure(options: RecorderOptions): void;
|
|
34
38
|
/**
|
|
@@ -62,6 +66,7 @@ declare class Recorder {
|
|
|
62
66
|
*/
|
|
63
67
|
private defer;
|
|
64
68
|
private clearDeferred;
|
|
69
|
+
private waitToSettle;
|
|
65
70
|
private settleThenFill;
|
|
66
71
|
private fill;
|
|
67
72
|
}
|
package/dist/recorder.js
CHANGED
|
@@ -5,16 +5,18 @@ import { realTimers } from './real-timers.js';
|
|
|
5
5
|
import { serializeDocument } from './serialize-document.js';
|
|
6
6
|
import { settle } from './settle.js';
|
|
7
7
|
import { ANONYMOUS_COMPONENT, } from './types.js';
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Bump it whenever the contract between the recorder and the adapters or setup
|
|
10
|
+
* files changes: a method or property added, removed or changed in meaning.
|
|
11
|
+
*/
|
|
12
|
+
const RECORDER_PROTOCOL = 2;
|
|
13
|
+
function isValidSelector(selector) {
|
|
13
14
|
try {
|
|
14
15
|
document.querySelector(selector);
|
|
16
|
+
return true;
|
|
15
17
|
}
|
|
16
18
|
catch {
|
|
17
|
-
|
|
19
|
+
return false;
|
|
18
20
|
}
|
|
19
21
|
}
|
|
20
22
|
/**
|
|
@@ -24,6 +26,8 @@ function assertValidSelector(selector) {
|
|
|
24
26
|
* `beforeInteraction()`, `setComponent()` and `onTeardown()`.
|
|
25
27
|
*/
|
|
26
28
|
class Recorder {
|
|
29
|
+
/** Which contract this recorder follows, so another copy of core can refuse it. */
|
|
30
|
+
protocol = RECORDER_PROTOCOL;
|
|
27
31
|
// One per capture, in call order, so a frame sits where it was asked for.
|
|
28
32
|
slots = [];
|
|
29
33
|
currentGeneration = 0;
|
|
@@ -32,6 +36,7 @@ class Recorder {
|
|
|
32
36
|
deferred = null;
|
|
33
37
|
pending = null;
|
|
34
38
|
renderFrame = 'eager';
|
|
39
|
+
aroundWait = (wait) => wait();
|
|
35
40
|
component;
|
|
36
41
|
startedAt = 0;
|
|
37
42
|
active = false;
|
|
@@ -59,14 +64,22 @@ class Recorder {
|
|
|
59
64
|
return this.currentGeneration;
|
|
60
65
|
}
|
|
61
66
|
/**
|
|
62
|
-
* Choose when the render frame is taken, from the next render capture on
|
|
63
|
-
* The setup files call it with the plugin's
|
|
64
|
-
* selector
|
|
67
|
+
* Choose when the render frame is taken, from the next render capture on,
|
|
68
|
+
* and what runs around each wait. The setup files call it with the plugin's
|
|
69
|
+
* options. A `pending` selector the DOM rejects falls back to `'lazy'` with
|
|
70
|
+
* a warning.
|
|
65
71
|
*/
|
|
66
72
|
configure(options) {
|
|
67
73
|
const renderFrame = options.renderFrame ?? 'eager';
|
|
68
|
-
if (
|
|
69
|
-
|
|
74
|
+
if (options.aroundWait !== undefined) {
|
|
75
|
+
this.aroundWait = options.aroundWait;
|
|
76
|
+
}
|
|
77
|
+
if (typeof renderFrame === 'object' &&
|
|
78
|
+
typeof document !== 'undefined' &&
|
|
79
|
+
!isValidSelector(renderFrame.pending)) {
|
|
80
|
+
console.warn(`describe-me: renderFrame.pending is not a valid CSS selector: "${renderFrame.pending}". Render frames fall back to 'lazy'.`);
|
|
81
|
+
this.renderFrame = 'lazy';
|
|
82
|
+
return;
|
|
70
83
|
}
|
|
71
84
|
this.renderFrame = renderFrame;
|
|
72
85
|
}
|
|
@@ -149,7 +162,7 @@ class Recorder {
|
|
|
149
162
|
return false;
|
|
150
163
|
}
|
|
151
164
|
const generation = this.currentGeneration;
|
|
152
|
-
await
|
|
165
|
+
await this.waitToSettle();
|
|
153
166
|
// The test ended, or an interaction took the frame, while this settled.
|
|
154
167
|
if (generation !== this.currentGeneration || this.deferred !== slot) {
|
|
155
168
|
return false;
|
|
@@ -207,8 +220,13 @@ class Recorder {
|
|
|
207
220
|
this.pending?.dispose();
|
|
208
221
|
this.pending = null;
|
|
209
222
|
}
|
|
223
|
+
// Only the wait runs inside `aroundWait`, so whatever the wrapper changes is
|
|
224
|
+
// undone before the snapshot and the teardown.
|
|
225
|
+
waitToSettle() {
|
|
226
|
+
return this.aroundWait(settle);
|
|
227
|
+
}
|
|
210
228
|
async settleThenFill(slot, generation) {
|
|
211
|
-
await
|
|
229
|
+
await this.waitToSettle();
|
|
212
230
|
if (generation === this.currentGeneration) {
|
|
213
231
|
this.fill(slot);
|
|
214
232
|
}
|
|
@@ -227,6 +245,21 @@ class Recorder {
|
|
|
227
245
|
}
|
|
228
246
|
}
|
|
229
247
|
const RECORDER_KEY = Symbol.for('describe-me.recorder');
|
|
248
|
+
function protocolOf(existing) {
|
|
249
|
+
if (typeof existing !== 'object' || existing === null) {
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
252
|
+
const candidate = existing;
|
|
253
|
+
if (typeof candidate.protocol === 'number') {
|
|
254
|
+
return candidate.protocol;
|
|
255
|
+
}
|
|
256
|
+
// 0.5.0 did not stamp its recorder. It is the first version with `flush()`,
|
|
257
|
+
// and its contract is protocol 1.
|
|
258
|
+
if (typeof candidate.flush === 'function') {
|
|
259
|
+
return 1;
|
|
260
|
+
}
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
230
263
|
/**
|
|
231
264
|
* The adapter and the setup file may receive two copies of this module: Vite
|
|
232
265
|
* pre-bundles the adapter together with its own copy of core, while a linked
|
|
@@ -237,7 +270,16 @@ const RECORDER_KEY = Symbol.for('describe-me.recorder');
|
|
|
237
270
|
*/
|
|
238
271
|
function sharedRecorder() {
|
|
239
272
|
const scope = globalThis;
|
|
240
|
-
scope[RECORDER_KEY]
|
|
241
|
-
|
|
273
|
+
const existing = scope[RECORDER_KEY];
|
|
274
|
+
if (existing === undefined) {
|
|
275
|
+
const created = new Recorder();
|
|
276
|
+
scope[RECORDER_KEY] = created;
|
|
277
|
+
return created;
|
|
278
|
+
}
|
|
279
|
+
const found = protocolOf(existing);
|
|
280
|
+
if (found !== RECORDER_PROTOCOL) {
|
|
281
|
+
throw new Error(`describe-me: all describe-me packages must be on the same version. Another copy of @describe-me/core created the test recorder (protocol ${found ?? 'none'}, expected ${RECORDER_PROTOCOL}). Install @describe-me/core, @describe-me/react, @describe-me/vitest and describe-me at one version.`);
|
|
282
|
+
}
|
|
283
|
+
return existing;
|
|
242
284
|
}
|
|
243
285
|
export const recorder = sharedRecorder();
|
package/dist/types.d.ts
CHANGED
|
@@ -49,10 +49,18 @@ export type RenderFrameMode = 'eager' | 'lazy' | {
|
|
|
49
49
|
pending: string;
|
|
50
50
|
timeout?: number;
|
|
51
51
|
};
|
|
52
|
+
/** Runs one wait of the recorder. It must call `wait` once and resolve after it. */
|
|
53
|
+
export type AroundWait = (wait: () => Promise<void>) => Promise<void>;
|
|
52
54
|
/** Options for `recorder.configure()`. */
|
|
53
55
|
export interface RecorderOptions {
|
|
54
56
|
/** When the render frame is taken. Default: `'eager'`. */
|
|
55
57
|
renderFrame?: RenderFrameMode;
|
|
58
|
+
/**
|
|
59
|
+
* Runs each wait of the recorder. It is kept when omitted, so a later
|
|
60
|
+
* `configure({ renderFrame })` keeps the setup file's wrapper. By default
|
|
61
|
+
* it runs `wait` as is.
|
|
62
|
+
*/
|
|
63
|
+
aroundWait?: AroundWait;
|
|
56
64
|
}
|
|
57
65
|
/** Options for `recorder.capture()`. */
|
|
58
66
|
export interface CaptureOptions {
|
|
@@ -157,8 +165,17 @@ export interface ComponentDoc {
|
|
|
157
165
|
file: string;
|
|
158
166
|
props: PropDoc[];
|
|
159
167
|
}
|
|
168
|
+
/** The package that wrote a manifest. */
|
|
169
|
+
export interface ManifestGenerator {
|
|
170
|
+
/** The package name, e.g. `@describe-me/vitest`. */
|
|
171
|
+
name: string;
|
|
172
|
+
/** Its version, e.g. `0.5.1`. */
|
|
173
|
+
version: string;
|
|
174
|
+
}
|
|
160
175
|
export interface Manifest {
|
|
161
176
|
version: 1;
|
|
177
|
+
/** The package and version that wrote the manifest. Absent in manifests written before 0.5.1. */
|
|
178
|
+
generator?: ManifestGenerator;
|
|
162
179
|
generatedAt: string;
|
|
163
180
|
root: string;
|
|
164
181
|
modules: ManifestModule[];
|
|
@@ -186,6 +203,12 @@ export interface Manifest {
|
|
|
186
203
|
* manifests written before 0.5, which counts as empty.
|
|
187
204
|
*/
|
|
188
205
|
remoteStylesheets?: RemoteStylesheet[];
|
|
206
|
+
/**
|
|
207
|
+
* Problems the plugin found in the project's setup, one sentence each.
|
|
208
|
+
* Absent when there are none and in manifests written before 0.5.1, which
|
|
209
|
+
* counts as empty.
|
|
210
|
+
*/
|
|
211
|
+
setupWarnings?: string[];
|
|
189
212
|
}
|
|
190
213
|
/** A font family that the captured CSS uses but no `@font-face`, font stylesheet or preview head loads. */
|
|
191
214
|
export interface FontMissing {
|
|
@@ -223,4 +246,6 @@ export interface ManifestDiagnostics {
|
|
|
223
246
|
fontsMissing: FontMissing[];
|
|
224
247
|
/** Hosts that frames load stylesheets from. */
|
|
225
248
|
remoteStylesheets: RemoteStylesheet[];
|
|
249
|
+
/** Problems in the project's setup that can make tests fail. */
|
|
250
|
+
setupWarnings: string[];
|
|
226
251
|
}
|