@recursica/adapter-tester 5.0.2 → 5.1.0

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/dist/testing.js CHANGED
@@ -1,15 +1,15 @@
1
- import { expect as J } from "@playwright/test";
2
- import { existsSync as v, unlinkSync as Y, readFileSync as x, mkdirSync as b, writeFileSync as E, closeSync as Q, openSync as Z, renameSync as ee, rmSync as te } from "node:fs";
3
- import re from "pixelmatch";
1
+ import { expect as D } from "@playwright/test";
2
+ import { existsSync as y, unlinkSync as H, readFileSync as E, mkdirSync as b, writeFileSync as A, closeSync as Y, openSync as Q, renameSync as Z, rmSync as ee } from "node:fs";
3
+ import te from "pixelmatch";
4
4
  import { PNG as P } from "pngjs";
5
- import { join as G } from "node:path";
6
- import { a as oe, i as ne } from "./index-DqQzFSAH.js";
7
- function R(e, t) {
5
+ import { join as M } from "node:path";
6
+ import { a as re, i as oe } from "./index-DqQzFSAH.js";
7
+ function J(e, t) {
8
8
  const o = P.sync.read(e), r = P.sync.read(t);
9
9
  if (o.width !== r.width || o.height !== r.height)
10
10
  return { diffPixels: 1 / 0, diffImage: null };
11
11
  const s = new P({ width: o.width, height: o.height });
12
- return { diffPixels: re(
12
+ return { diffPixels: te(
13
13
  o.data,
14
14
  r.data,
15
15
  s.data,
@@ -18,19 +18,19 @@ function R(e, t) {
18
18
  { threshold: 0.1 }
19
19
  ), diffImage: P.sync.write(s) };
20
20
  }
21
- const se = "http://json-schema.org/draft-07/schema#", ie = "https://github.com/borderux/recursica/tree/main/packages/adapter-tester/src/golden/manifest.schema.json", ae = "test/golden/manifest.json", ce = "Tracks golden (baseline) image metadata for @recursica/adapter-tester's golden-image visual regression checks. One manifest lives alongside its adapter's test/golden/<story-id>.png files, keyed by story id.", de = "object", he = { type: "object", additionalProperties: !1, required: ["createdAt"], properties: { createdAt: { type: "string", format: "date-time", description: "When this story's own golden PNG was last captured, via --update-golden, --approve-divergence, or first-run auto-create." }, sourceOfTruthCreatedAt: { type: "string", format: "date-time", description: "The source-of-truth adapter's (mantine) manifest `createdAt` for this story at the time this adapter's divergence from it was last reviewed via --approve-divergence. Omitted on the source-of-truth adapter's own manifest, and omitted here until the first approval. If mantine's current `createdAt` for this story is newer than this value, the divergence is flagged again for re-review." } } }, ue = {
22
- $schema: se,
23
- $id: ie,
24
- title: ae,
25
- description: ce,
26
- type: de,
27
- additionalProperties: he
28
- }, le = ne, V = new oe.Ajv({ allErrors: !0, strict: !0 });
29
- le(V);
30
- const U = V.compile(ue);
31
- function F(e, t) {
32
- if (U(e)) return;
33
- const o = (U.errors ?? []).map((r) => {
21
+ const ne = "http://json-schema.org/draft-07/schema#", se = "https://github.com/borderux/recursica/tree/main/packages/adapter-tester/src/golden/manifest.schema.json", ie = "test/golden/manifest.json", ae = "Tracks golden (baseline) image metadata for @recursica/adapter-tester's golden-image visual regression checks. One manifest lives alongside its adapter's test/golden/<story-id>.png files, keyed by story id.", ce = "object", de = { type: "object", additionalProperties: !1, required: ["createdAt"], properties: { createdAt: { type: "string", format: "date-time", description: "When this story's own golden PNG was last captured, via --update-golden, --approve-divergence, or first-run auto-create." }, sourceOfTruthCreatedAt: { type: "string", format: "date-time", description: "The source-of-truth adapter's (mantine) manifest `createdAt` for this story at the time this adapter's divergence from it was last reviewed via --approve-divergence. Omitted on the source-of-truth adapter's own manifest, and omitted here until the first approval. If mantine's current `createdAt` for this story is newer than this value, the divergence is flagged again for re-review." } } }, he = {
22
+ $schema: ne,
23
+ $id: se,
24
+ title: ie,
25
+ description: ae,
26
+ type: ce,
27
+ additionalProperties: de
28
+ }, ue = oe, K = new re.Ajv({ allErrors: !0, strict: !0 });
29
+ ue(K);
30
+ const R = K.compile(he);
31
+ function B(e, t) {
32
+ if (R(e)) return;
33
+ const o = (R.errors ?? []).map((r) => {
34
34
  var i;
35
35
  const s = (i = r.params) != null && i.additionalProperty ? ` '${r.params.additionalProperty}'` : "";
36
36
  return ` - ${r.instancePath || "root"} ${r.message}${s}`;
@@ -39,42 +39,42 @@ function F(e, t) {
39
39
  throw new Error(`Invalid ${t}:
40
40
  ${o}`);
41
41
  }
42
- function A(e) {
43
- return G(e, "manifest.json");
42
+ function G(e) {
43
+ return M(e, "manifest.json");
44
44
  }
45
- function W(e) {
46
- return G(e, "manifest.json.lock");
45
+ function V(e) {
46
+ return M(e, "manifest.json.lock");
47
47
  }
48
48
  function S(e, t) {
49
- return G(e, `${t}.png`);
49
+ return M(e, `${t}.png`);
50
50
  }
51
51
  function j(e) {
52
- const t = A(e);
53
- if (!v(t)) return {};
54
- const o = JSON.parse(x(t, "utf8"));
55
- return F(o, t), o;
52
+ const t = G(e);
53
+ if (!y(t)) return {};
54
+ const o = JSON.parse(E(t, "utf8"));
55
+ return B(o, t), o;
56
56
  }
57
- function fe(e, t) {
58
- const o = A(e);
59
- F(t, o);
57
+ function le(e, t) {
58
+ const o = G(e);
59
+ B(t, o);
60
60
  const r = {};
61
61
  for (const i of Object.keys(t).sort())
62
62
  r[i] = t[i];
63
63
  b(e, { recursive: !0 });
64
64
  const s = `${o}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
65
- E(s, JSON.stringify(r, null, 2) + `
66
- `), ee(s, o);
65
+ A(s, JSON.stringify(r, null, 2) + `
66
+ `), Z(s, o);
67
67
  }
68
- const pe = 25, ge = 15e3;
69
- function me(e) {
68
+ const fe = 25, pe = 15e3;
69
+ function ge(e) {
70
70
  return new Promise((t) => setTimeout(t, e));
71
71
  }
72
- async function we(e) {
72
+ async function me(e) {
73
73
  b(e, { recursive: !0 });
74
- const t = W(e), o = Date.now() + ge;
74
+ const t = V(e), o = Date.now() + pe;
75
75
  for (; ; )
76
76
  try {
77
- Q(Z(t, "wx"));
77
+ Y(Q(t, "wx"));
78
78
  return;
79
79
  } catch (r) {
80
80
  if (r.code !== "EEXIST") throw r;
@@ -82,37 +82,37 @@ async function we(e) {
82
82
  throw new Error(
83
83
  `Timed out waiting for the manifest lock at "${t}" — delete it if a previous run crashed while holding it.`
84
84
  );
85
- await me(pe);
85
+ await ge(fe);
86
86
  }
87
87
  }
88
- function ye(e) {
89
- te(W(e), { force: !0 });
88
+ function we(e) {
89
+ ee(V(e), { force: !0 });
90
90
  }
91
- async function X(e, t, o) {
92
- await we(e);
91
+ async function W(e, t, o) {
92
+ await me(e);
93
93
  try {
94
94
  const r = j(e), s = o(r[t]);
95
- return s === void 0 ? delete r[t] : r[t] = s, fe(e, r), s;
95
+ return s === void 0 ? delete r[t] : r[t] = s, le(e, r), s;
96
96
  } finally {
97
- ye(e);
97
+ we(e);
98
98
  }
99
99
  }
100
- function ve(e, t, o) {
101
- b(e, { recursive: !0 }), E(S(e, t), o);
100
+ function ye(e, t, o) {
101
+ b(e, { recursive: !0 }), A(S(e, t), o);
102
102
  }
103
- async function Te(e, t) {
103
+ async function ve(e, t) {
104
104
  const o = j(e), r = Object.keys(o).filter(
105
105
  (s) => !t.has(s)
106
106
  );
107
107
  for (const s of r) {
108
108
  const i = S(e, s);
109
- v(i) && Y(i), await X(e, s, () => {
109
+ y(i) && H(i), await W(e, s, () => {
110
110
  });
111
111
  }
112
112
  return r;
113
113
  }
114
- const $e = "borderux/recursica";
115
- async function Oe(e, t) {
114
+ const Te = "borderux/recursica";
115
+ async function $e(e, t) {
116
116
  var i, d;
117
117
  const o = await fetch(`https://registry.npmjs.org/${e}`);
118
118
  if (!o.ok)
@@ -126,23 +126,23 @@ async function Oe(e, t) {
126
126
  );
127
127
  return s;
128
128
  }
129
- function ke(e) {
129
+ function Oe(e) {
130
130
  return `packages/${e.split("/").pop()}`;
131
131
  }
132
- async function xe(e) {
132
+ async function ke(e) {
133
133
  if (e.type === "local")
134
- return v(A(e.dir)) ? {
134
+ return y(G(e.dir)) ? {
135
135
  manifest: j(e.dir),
136
136
  async readImage(h) {
137
137
  const c = S(e.dir, h);
138
- return v(c) ? x(c) : null;
138
+ return y(c) ? E(c) : null;
139
139
  }
140
140
  } : (console.warn(
141
141
  `No golden baseline found yet at ${e.dir} — source-of-truth divergence check skipped for this run.`
142
142
  ), null);
143
143
  let t;
144
144
  try {
145
- t = await Oe(
145
+ t = await $e(
146
146
  e.packageName,
147
147
  e.versionSpec
148
148
  );
@@ -152,10 +152,10 @@ async function xe(e) {
152
152
  a
153
153
  ), null;
154
154
  }
155
- const o = G(e.cacheDir, t), r = `${e.packageName}@${t}`, s = `https://raw.githubusercontent.com/${$e}/${r}/${ke(e.packageName)}/test/golden`;
155
+ const o = M(e.cacheDir, t), r = `${e.packageName}@${t}`, s = `https://raw.githubusercontent.com/${Te}/${r}/${Oe(e.packageName)}/test/golden`;
156
156
  let i;
157
- const d = A(o);
158
- if (v(d))
157
+ const d = G(o);
158
+ if (y(d))
159
159
  i = j(o);
160
160
  else {
161
161
  let a;
@@ -172,25 +172,25 @@ async function xe(e) {
172
172
  `No golden baseline published for ${r} — source-of-truth divergence check skipped for this run.`
173
173
  ), null;
174
174
  const h = await a.text(), c = JSON.parse(h);
175
- F(c, `${s}/manifest.json`), b(o, { recursive: !0 }), E(d, h), i = c;
175
+ B(c, `${s}/manifest.json`), b(o, { recursive: !0 }), A(d, h), i = c;
176
176
  }
177
177
  return {
178
178
  manifest: i,
179
179
  async readImage(a) {
180
180
  const h = S(o, a);
181
- if (v(h)) return x(h);
181
+ if (y(h)) return E(h);
182
182
  const c = await fetch(`${s}/${a}.png`);
183
183
  if (!c.ok) return null;
184
184
  const f = Buffer.from(await c.arrayBuffer());
185
- return b(o, { recursive: !0 }), E(h, f), f;
185
+ return b(o, { recursive: !0 }), A(h, f), f;
186
186
  }
187
187
  };
188
188
  }
189
- const be = ["Theme", "Tokens", "Introduction"];
190
- function B(e, t) {
189
+ const xe = ["Theme", "Tokens", "Introduction"];
190
+ function N(e, t) {
191
191
  return e === t || e.startsWith(t);
192
192
  }
193
- async function Se(e, t) {
193
+ async function be(e, t) {
194
194
  let o;
195
195
  try {
196
196
  const r = await fetch(`${e.url}/index.json`);
@@ -215,141 +215,141 @@ async function Se(e, t) {
215
215
  }
216
216
  return o;
217
217
  }
218
- function K(e, t, o) {
218
+ function U(e, t, o) {
219
219
  let r;
220
220
  for (const s of Object.keys(t))
221
- B(e, s) && (!r || s.length > r.length) && (r = s);
221
+ N(e, s) && (!r || s.length > r.length) && (r = s);
222
222
  return r !== void 0 ? t[r] : o;
223
223
  }
224
- async function Ie(e) {
224
+ async function Me(e) {
225
225
  const t = e.isSourceOfTruthAdapter ? e.targets[0] : e.targets.find((n) => !n.sourceOfTruth);
226
226
  if (!t)
227
227
  throw new Error(
228
228
  "adapter-tester config has no non-sourceOfTruth target to run the golden check against."
229
229
  );
230
- const o = e.excludeTitlePrefixes ?? be, r = e.stories ?? {}, s = Object.keys(r).filter(
230
+ const o = e.excludeTitlePrefixes ?? xe, r = e.stories ?? {}, s = Object.keys(r).filter(
231
231
  (n) => r[n].exclude
232
232
  ), i = Object.fromEntries(
233
233
  Object.entries(r).filter(([, n]) => n.goldenThreshold !== void 0).map(([n, u]) => [n, u.goldenThreshold])
234
234
  ), d = Object.fromEntries(
235
235
  Object.entries(r).filter(([, n]) => n.sourceOfTruthThreshold !== void 0).map(([n, u]) => [n, u.sourceOfTruthThreshold])
236
- ), a = e.goldenDir, h = e.goldenMode, c = e.checkMode, f = await Se(t, o), M = f.filter(
237
- (n) => !s.some((u) => B(n.id, u))
236
+ ), a = e.goldenDir, h = e.goldenMode, c = e.checkMode, f = await be(t, o), I = f.filter(
237
+ (n) => !s.some((u) => N(n.id, u))
238
238
  );
239
239
  if (h === "update-golden") {
240
- const n = await Te(
240
+ const n = await ve(
241
241
  a,
242
- new Set(M.map((u) => u.id))
242
+ new Set(I.map((u) => u.id))
243
243
  );
244
244
  n.length > 0 && console.warn(
245
245
  `Pruned ${n.length} orphaned golden(s) no longer in Storybook: ${n.join(", ")}`
246
246
  );
247
247
  }
248
- const p = c !== "divergence" || e.isSourceOfTruthAdapter || !e.sourceOfTruthGolden ? null : await xe(e.sourceOfTruthGolden), q = new Set(f.map((n) => n.id)), T = p ? Object.keys(p.manifest).filter(
249
- (n) => !q.has(n) && !s.some((u) => B(n, u))
248
+ const p = c !== "divergence" || e.isSourceOfTruthAdapter || !e.sourceOfTruthGolden ? null : await ke(e.sourceOfTruthGolden), X = new Set(f.map((n) => n.id)), v = p ? Object.keys(p.manifest).filter(
249
+ (n) => !X.has(n) && !s.some((u) => N(n, u))
250
250
  ).sort() : [];
251
- T.length > 0 && console.error(
252
- `[adapter-tester] ${T.length} stor(y/ies) exist in the source of truth but are missing here: ${T.join(", ")}. Add the missing story, or mark it \`exclude: true\` under \`stories\` in adapter-tester.config.json if intentional.`
251
+ v.length > 0 && console.error(
252
+ `[adapter-tester] ${v.length} stor(y/ies) exist in the source of truth but are missing here: ${v.join(", ")}. Add the missing story, or mark it \`exclude: true\` under \`stories\` in adapter-tester.config.json if intentional.`
253
253
  ), console.log(
254
254
  [
255
255
  `[adapter-tester] target: "${t.name}" (${t.url})`,
256
- `[adapter-tester] checkMode: "${c}" (${c === "divergence" ? "this project's golden vs source-of-truth's golden" : "live render vs this project's own golden"})`,
256
+ `[adapter-tester] checkMode: "${c}" (${c === "divergence" ? "live render vs source-of-truth's golden" : "live render vs this project's own golden"})`,
257
257
  `[adapter-tester] goldenMode: "${h}"`,
258
258
  `[adapter-tester] goldenThresholdPixels: ${e.goldenThresholdPixels}`,
259
259
  `[adapter-tester] sourceOfTruthThresholdPixels: ${e.sourceOfTruthThresholdPixels}`,
260
260
  c === "divergence" ? e.isSourceOfTruthAdapter ? "[adapter-tester] sourceOfTruthGolden: skipped — this is the source-of-truth adapter, nothing to diverge from" : e.sourceOfTruthGolden ? p ? `[adapter-tester] sourceOfTruthGolden: resolved, ${Object.keys(p.manifest).length} golden(s) available (config: ${JSON.stringify(e.sourceOfTruthGolden)})` : `[adapter-tester] sourceOfTruthGolden: unavailable — no baseline found or unreachable (config: ${JSON.stringify(e.sourceOfTruthGolden)}); divergence check will skip every story` : "[adapter-tester] sourceOfTruthGolden: skipped — no sourceOfTruthGolden configured" : '[adapter-tester] sourceOfTruthGolden: not used in "own" checkMode',
261
- `[adapter-tester] stories: ${M.length} to check (excluded: ${s.length}, title prefixes excluded: ${o.join(", ") || "none"})`,
262
- `[adapter-tester] story parity with source of truth: ${T.length === 0 ? "OK" : `${T.length} missing (see error above)`}`
261
+ `[adapter-tester] stories: ${I.length} to check (excluded: ${s.length}, title prefixes excluded: ${o.join(", ") || "none"})`,
262
+ `[adapter-tester] story parity with source of truth: ${v.length === 0 ? "OK" : `${v.length} missing (see error above)`}`
263
263
  ].join(`
264
264
  `)
265
265
  );
266
- const z = c === "divergence" ? "Source-of-Truth Divergence Check" : "Own-Drift Golden Image Check";
266
+ const q = c === "divergence" ? "Source-of-Truth Divergence Check" : "Own-Drift Golden Image Check";
267
267
  return {
268
268
  ownTargetName: t.name,
269
- suiteLabel: z,
270
- stories: M,
271
- missingFromSourceOfTruth: T,
269
+ suiteLabel: q,
270
+ stories: I,
271
+ missingFromSourceOfTruth: v,
272
272
  checkStory: async (n, u, l) => {
273
- const $ = await u.newPage();
274
- await $.setViewportSize({ width: 800, height: 600 }), await $.goto(
273
+ const T = await u.newPage();
274
+ await T.setViewportSize({ width: 800, height: 600 }), await T.goto(
275
275
  `${t.url}/iframe.html?id=${n.id}&viewMode=story`,
276
276
  { waitUntil: "networkidle" }
277
- ), await $.waitForSelector("#storybook-root"), await $.addStyleTag({
277
+ ), await T.waitForSelector("#storybook-root"), await T.addStyleTag({
278
278
  content: "* { -webkit-font-smoothing: antialiased !important; -moz-osx-font-smoothing: grayscale !important; }"
279
- }), await $.waitForTimeout(300);
280
- const I = await $.screenshot(), C = S(a, n.id), g = j(a)[n.id], H = h !== "check" || !g || !v(C);
281
- let O;
282
- if (H)
283
- ve(a, n.id, I), O = g != null && g.sourceOfTruthCreatedAt ? {
279
+ }), await T.waitForTimeout(300);
280
+ const x = await T.screenshot(), F = S(a, n.id), g = j(a)[n.id], z = h !== "check" || !g || !y(F);
281
+ let $;
282
+ if (z)
283
+ ye(a, n.id, x), $ = g != null && g.sourceOfTruthCreatedAt ? {
284
284
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
285
285
  sourceOfTruthCreatedAt: g.sourceOfTruthCreatedAt
286
286
  } : { createdAt: (/* @__PURE__ */ new Date()).toISOString() }, g || l.annotations.push({
287
287
  type: "golden-created",
288
288
  description: `No golden existed yet for "${n.id}" — captured one from this run.`
289
289
  });
290
- else if (O = g, c === "own") {
291
- const m = x(C), { diffPixels: w, diffImage: k } = R(
292
- I,
290
+ else if ($ = g, c === "own") {
291
+ const m = E(F), { diffPixels: w, diffImage: O } = J(
292
+ x,
293
293
  m
294
- ), y = K(
294
+ ), k = U(
295
295
  n.id,
296
296
  i,
297
297
  e.goldenThresholdPixels
298
298
  );
299
- w >= y && (await l.attach("expected", {
299
+ w >= k && (await l.attach("expected", {
300
300
  body: m,
301
301
  contentType: "image/png"
302
302
  }), await l.attach("actual", {
303
- body: I,
303
+ body: x,
304
304
  contentType: "image/png"
305
- }), k && await l.attach("diff", {
306
- body: k,
305
+ }), O && await l.attach("diff", {
306
+ body: O,
307
307
  contentType: "image/png"
308
- })), J.soft(
308
+ })), D.soft(
309
309
  w,
310
- `"${n.id}" has drifted from its own golden image (${w} mismatched pixels, threshold ${y})`
311
- ).toBeLessThan(y);
310
+ `"${n.id}" has drifted from its own golden image (${w} mismatched pixels, threshold ${k})`
311
+ ).toBeLessThan(k);
312
312
  }
313
313
  if (p) {
314
314
  const m = p.manifest[n.id], w = m ? await p.readImage(n.id) : null;
315
315
  if (m && w)
316
316
  if (h === "approve-divergence")
317
- O = {
318
- ...O,
317
+ $ = {
318
+ ...$,
319
319
  sourceOfTruthCreatedAt: m.createdAt
320
320
  };
321
321
  else {
322
- const k = x(C), { diffPixels: y, diffImage: L } = R(
323
- k,
322
+ const { diffPixels: O, diffImage: k } = J(
323
+ x,
324
324
  w
325
- ), N = K(
325
+ ), C = U(
326
326
  n.id,
327
327
  d,
328
328
  e.sourceOfTruthThresholdPixels
329
- ), _ = O.sourceOfTruthCreatedAt, D = y < N || _ !== void 0 && _ >= m.createdAt;
330
- D || (await l.attach("expected", {
329
+ ), L = $.sourceOfTruthCreatedAt, _ = O < C || L !== void 0 && L >= m.createdAt;
330
+ _ || (await l.attach("expected", {
331
331
  body: w,
332
332
  contentType: "image/png"
333
333
  }), await l.attach("actual", {
334
- body: k,
334
+ body: x,
335
335
  contentType: "image/png"
336
- }), L && await l.attach("diff", {
337
- body: L,
336
+ }), k && await l.attach("diff", {
337
+ body: k,
338
338
  contentType: "image/png"
339
339
  }), l.annotations.push({
340
340
  type: "source-of-truth-divergence",
341
- description: `"${n.id}" differs from the source of truth's golden by ${y} mismatched pixels (threshold ${N}) and hasn't been reviewed — run with --approve-divergence if this is intentional, or fix the adapter styling.`
342
- })), J.soft(
343
- D,
344
- `"${n.id}" differs from the source of truth's golden by ${y} mismatched pixels (threshold ${N}) and hasn't been reviewed — run with --approve-divergence if this is intentional, or fix the adapter styling.`
341
+ description: `"${n.id}" differs from the source of truth's golden by ${O} mismatched pixels (threshold ${C}) and hasn't been reviewed — run with --approve-divergence if this is intentional, or fix the adapter styling.`
342
+ })), D.soft(
343
+ _,
344
+ `"${n.id}" differs from the source of truth's golden by ${O} mismatched pixels (threshold ${C}) and hasn't been reviewed — run with --approve-divergence if this is intentional, or fix the adapter styling.`
345
345
  ).toBe(!0);
346
346
  }
347
347
  }
348
- await X(a, n.id, () => O);
348
+ await W(a, n.id, () => $);
349
349
  }
350
350
  };
351
351
  }
352
352
  export {
353
- Ie as resolveVisualRegressionPlan
353
+ Me as resolveVisualRegressionPlan
354
354
  };
355
355
  //# sourceMappingURL=testing.js.map