@describe-me/core 0.5.0 → 0.5.1

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
@@ -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
- * and hosts that frames load stylesheets from. The reporter prints it after a
6
- * run and the viewer shows it in the header, so both agree on what is missing.
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
- * and hosts that frames load stylesheets from. The reporter prints it after a
10
- * run and the viewer shows it in the header, so both agree on what is missing.
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
  }
@@ -7,6 +7,8 @@ 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 = 1;
10
12
  private slots;
11
13
  private currentGeneration;
12
14
  private inFlight;
@@ -27,8 +29,8 @@ declare class Recorder {
27
29
  get generation(): number;
28
30
  /**
29
31
  * Choose when the render frame is taken, from the next render capture on.
30
- * The setup files call it with the plugin's options. An invalid `pending`
31
- * selector throws.
32
+ * The setup files call it with the plugin's options. A `pending` selector
33
+ * the DOM rejects falls back to `'lazy'` with a warning.
32
34
  */
33
35
  configure(options: RecorderOptions): void;
34
36
  /**
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
- /** Fails in the setup file rather than in the first test that renders. */
9
- function assertValidSelector(selector) {
10
- if (typeof document === 'undefined') {
11
- return;
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 = 1;
13
+ function isValidSelector(selector) {
13
14
  try {
14
15
  document.querySelector(selector);
16
+ return true;
15
17
  }
16
18
  catch {
17
- throw new Error(`describe-me: renderFrame.pending is not a valid CSS selector: "${selector}"`);
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;
@@ -60,13 +64,17 @@ class Recorder {
60
64
  }
61
65
  /**
62
66
  * Choose when the render frame is taken, from the next render capture on.
63
- * The setup files call it with the plugin's options. An invalid `pending`
64
- * selector throws.
67
+ * The setup files call it with the plugin's options. A `pending` selector
68
+ * the DOM rejects falls back to `'lazy'` with a warning.
65
69
  */
66
70
  configure(options) {
67
71
  const renderFrame = options.renderFrame ?? 'eager';
68
- if (typeof renderFrame === 'object') {
69
- assertValidSelector(renderFrame.pending);
72
+ if (typeof renderFrame === 'object' &&
73
+ typeof document !== 'undefined' &&
74
+ !isValidSelector(renderFrame.pending)) {
75
+ console.warn(`describe-me: renderFrame.pending is not a valid CSS selector: "${renderFrame.pending}". Render frames fall back to 'lazy'.`);
76
+ this.renderFrame = 'lazy';
77
+ return;
70
78
  }
71
79
  this.renderFrame = renderFrame;
72
80
  }
@@ -227,6 +235,21 @@ class Recorder {
227
235
  }
228
236
  }
229
237
  const RECORDER_KEY = Symbol.for('describe-me.recorder');
238
+ function protocolOf(existing) {
239
+ if (typeof existing !== 'object' || existing === null) {
240
+ return undefined;
241
+ }
242
+ const candidate = existing;
243
+ if (typeof candidate.protocol === 'number') {
244
+ return candidate.protocol;
245
+ }
246
+ // 0.5.0 did not stamp its recorder. It is the first version with `flush()`,
247
+ // and its contract is protocol 1.
248
+ if (typeof candidate.flush === 'function') {
249
+ return 1;
250
+ }
251
+ return undefined;
252
+ }
230
253
  /**
231
254
  * The adapter and the setup file may receive two copies of this module: Vite
232
255
  * pre-bundles the adapter together with its own copy of core, while a linked
@@ -237,7 +260,16 @@ const RECORDER_KEY = Symbol.for('describe-me.recorder');
237
260
  */
238
261
  function sharedRecorder() {
239
262
  const scope = globalThis;
240
- scope[RECORDER_KEY] ??= new Recorder();
241
- return scope[RECORDER_KEY];
263
+ const existing = scope[RECORDER_KEY];
264
+ if (existing === undefined) {
265
+ const created = new Recorder();
266
+ scope[RECORDER_KEY] = created;
267
+ return created;
268
+ }
269
+ const found = protocolOf(existing);
270
+ if (found !== RECORDER_PROTOCOL) {
271
+ 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.`);
272
+ }
273
+ return existing;
242
274
  }
243
275
  export const recorder = sharedRecorder();
package/dist/types.d.ts CHANGED
@@ -157,8 +157,17 @@ export interface ComponentDoc {
157
157
  file: string;
158
158
  props: PropDoc[];
159
159
  }
160
+ /** The package that wrote a manifest. */
161
+ export interface ManifestGenerator {
162
+ /** The package name, e.g. `@describe-me/vitest`. */
163
+ name: string;
164
+ /** Its version, e.g. `0.5.1`. */
165
+ version: string;
166
+ }
160
167
  export interface Manifest {
161
168
  version: 1;
169
+ /** The package and version that wrote the manifest. Absent in manifests written before 0.5.1. */
170
+ generator?: ManifestGenerator;
162
171
  generatedAt: string;
163
172
  root: string;
164
173
  modules: ManifestModule[];
@@ -186,6 +195,12 @@ export interface Manifest {
186
195
  * manifests written before 0.5, which counts as empty.
187
196
  */
188
197
  remoteStylesheets?: RemoteStylesheet[];
198
+ /**
199
+ * Problems the plugin found in the project's setup, one sentence each.
200
+ * Absent when there are none and in manifests written before 0.5.1, which
201
+ * counts as empty.
202
+ */
203
+ setupWarnings?: string[];
189
204
  }
190
205
  /** A font family that the captured CSS uses but no `@font-face`, font stylesheet or preview head loads. */
191
206
  export interface FontMissing {
@@ -223,4 +238,6 @@ export interface ManifestDiagnostics {
223
238
  fontsMissing: FontMissing[];
224
239
  /** Hosts that frames load stylesheets from. */
225
240
  remoteStylesheets: RemoteStylesheet[];
241
+ /** Problems in the project's setup that can make tests fail. */
242
+ setupWarnings: string[];
226
243
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@describe-me/core",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Recorder and manifest types for describe-me: living component docs from your tests",
5
5
  "keywords": [
6
6
  "vitest",