@recursica/adapter-tester 3.0.0 → 5.0.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.
Files changed (40) hide show
  1. package/README.md +47 -27
  2. package/dist/adapter-tester.schema.json.d.ts +36 -15
  3. package/dist/cli.cjs +41 -20
  4. package/dist/cli.cjs.map +1 -1
  5. package/dist/cli.js +344 -218
  6. package/dist/cli.js.map +1 -1
  7. package/dist/config.d.ts +38 -14
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/devServer.d.ts +1 -1
  10. package/dist/devServer.d.ts.map +1 -1
  11. package/dist/fileConfig.d.ts +25 -10
  12. package/dist/fileConfig.d.ts.map +1 -1
  13. package/dist/golden/diffPng.d.ts +12 -7
  14. package/dist/golden/diffPng.d.ts.map +1 -1
  15. package/dist/golden/manifestStore.d.ts +19 -1
  16. package/dist/golden/manifestStore.d.ts.map +1 -1
  17. package/dist/harness/mantineSourceOfTruth.d.ts +10 -1
  18. package/dist/harness/mantineSourceOfTruth.d.ts.map +1 -1
  19. package/dist/index.cjs +1 -1
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.js +1 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/{mantineSourceOfTruth-BUkqMNEo.js → mantineSourceOfTruth-DE7XOgea.js} +36 -34
  24. package/dist/mantineSourceOfTruth-DE7XOgea.js.map +1 -0
  25. package/dist/{mantineSourceOfTruth-BUb5MZy0.cjs → mantineSourceOfTruth-Dpe4mlmF.cjs} +6 -6
  26. package/dist/mantineSourceOfTruth-Dpe4mlmF.cjs.map +1 -0
  27. package/dist/portDiscovery.d.ts +39 -0
  28. package/dist/portDiscovery.d.ts.map +1 -0
  29. package/dist/testing/runVisualRegression.d.ts +69 -20
  30. package/dist/testing/runVisualRegression.d.ts.map +1 -1
  31. package/dist/testing.cjs +4 -3
  32. package/dist/testing.cjs.map +1 -1
  33. package/dist/testing.d.ts +1 -1
  34. package/dist/testing.d.ts.map +1 -1
  35. package/dist/testing.js +286 -174
  36. package/dist/testing.js.map +1 -1
  37. package/package.json +2 -6
  38. package/src/adapter-tester.schema.json +36 -15
  39. package/dist/mantineSourceOfTruth-BUb5MZy0.cjs.map +0 -1
  40. package/dist/mantineSourceOfTruth-BUkqMNEo.js.map +0 -1
@@ -1,37 +1,40 @@
1
- import { mkdirSync as p, writeFileSync as o } from "node:fs";
1
+ import { mkdirSync as d, writeFileSync as o } from "node:fs";
2
2
  import { join as t } from "node:path";
3
- const c = "^8.0.0", a = "^10.3.3", d = "^19.0.0", m = {
3
+ const n = "^8.0.0", a = "^10.3.3", c = "^19.0.0", p = {
4
4
  "@storybook/addon-docs": a,
5
5
  "@storybook/addon-a11y": a,
6
6
  "storybook-dark-mode": "^5.0.0"
7
7
  }, l = {
8
8
  "react-markdown": "^10.1.0"
9
9
  };
10
- function u(e) {
10
+ function m(r) {
11
11
  return {
12
12
  name: "adapter-tester-mantine-source-of-truth-harness",
13
13
  private: !0,
14
14
  type: "module",
15
15
  scripts: {
16
- storybook: `storybook dev -p ${e.port}`
16
+ // No `-p` pin — Storybook silently falls back to an OS-assigned port
17
+ // whenever its default is taken, so the caller detects the real port
18
+ // from this process's own output rather than trusting a fixed one.
19
+ storybook: "storybook dev"
17
20
  },
18
21
  dependencies: {
19
- "@recursica/mantine-adapter": e.mantineAdapterVersion,
20
- "@recursica/storybook-template": e.storybookTemplateVersion,
22
+ "@recursica/mantine-adapter": r.mantineAdapterVersion,
23
+ "@recursica/storybook-template": r.storybookTemplateVersion,
21
24
  "@recursica/official-release": "latest",
22
25
  "@recursica/adapter-common": "latest",
23
- "@mantine/core": c,
24
- "@mantine/dates": c,
25
- react: d,
26
- "react-dom": d,
26
+ "@mantine/core": n,
27
+ "@mantine/dates": n,
28
+ react: c,
29
+ "react-dom": c,
27
30
  storybook: a,
28
31
  "@storybook/react-vite": a,
29
- ...m,
32
+ ...p,
30
33
  ...l
31
34
  }
32
35
  };
33
36
  }
34
- const f = `import { createMainConfig } from "@recursica/storybook-template/main";
37
+ const u = `import { createMainConfig } from "@recursica/storybook-template/main";
35
38
 
36
39
  const config = createMainConfig({
37
40
  stories: [
@@ -50,7 +53,7 @@ const config = createMainConfig({
50
53
  config.typescript = { ...config.typescript, reactDocgen: false };
51
54
 
52
55
  export default config;
53
- `, y = `import type { Preview } from "@storybook/react-vite";
56
+ `, f = `import type { Preview } from "@storybook/react-vite";
54
57
  import { createPreviewConfig } from "@recursica/storybook-template/preview";
55
58
  import { MantineProvider } from "@mantine/core";
56
59
  import { Layer } from "@recursica/adapter-common";
@@ -103,43 +106,42 @@ const preview: Preview = {
103
106
 
104
107
  export default preview;
105
108
  `;
106
- function h(e) {
109
+ function y(r) {
107
110
  const {
108
- dir: r,
109
- port: s,
110
- mantineAdapterVersion: i = "latest",
111
- storybookTemplateVersion: n = "latest"
112
- } = e;
113
- return p(t(r, ".storybook"), { recursive: !0 }), o(
114
- t(r, "package.json"),
111
+ dir: e,
112
+ mantineAdapterVersion: s = "latest",
113
+ storybookTemplateVersion: i = "latest"
114
+ } = r;
115
+ return d(t(e, ".storybook"), { recursive: !0 }), o(
116
+ t(e, "package.json"),
115
117
  JSON.stringify(
116
- u({
117
- mantineAdapterVersion: i,
118
- storybookTemplateVersion: n,
119
- port: s
118
+ m({
119
+ mantineAdapterVersion: s,
120
+ storybookTemplateVersion: i
120
121
  }),
121
122
  null,
122
123
  2
123
124
  ) + `
124
125
  `
125
- ), o(t(r, ".storybook/main.ts"), f), o(t(r, ".storybook/preview.tsx"), y), o(t(r, ".gitignore"), `node_modules
126
- `), r;
126
+ ), o(t(e, ".storybook/main.ts"), u), o(t(e, ".storybook/preview.tsx"), f), o(t(e, ".gitignore"), `node_modules
127
+ `), e;
127
128
  }
128
- function w(e) {
129
- const r = h(e), {
129
+ function w(r) {
130
+ const e = y(r), {
130
131
  mantineAdapterVersion: s = "latest",
131
132
  storybookTemplateVersion: i = "latest"
132
- } = e;
133
+ } = r;
133
134
  return {
134
135
  command: `npm install @recursica/mantine-adapter@${s} @recursica/storybook-template@${i} --no-audit --no-fund && npm run storybook`,
135
- port: e.port,
136
- cwd: r,
136
+ port: r.port,
137
+ cwd: e,
137
138
  reuseExistingServer: !process.env.CI,
139
+ cacheFile: t(e, "last-port.json"),
138
140
  timeout: 180 * 1e3
139
141
  };
140
142
  }
141
143
  export {
142
144
  w as m,
143
- h as s
145
+ y as s
144
146
  };
145
- //# sourceMappingURL=mantineSourceOfTruth-BUkqMNEo.js.map
147
+ //# sourceMappingURL=mantineSourceOfTruth-DE7XOgea.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mantineSourceOfTruth-DE7XOgea.js","sources":["../src/harness/mantineSourceOfTruth.ts"],"sourcesContent":["import { mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n/**\n * Generates a small, throwaway Storybook project that installs\n * `@recursica/mantine-adapter` as a real npm dependency (not a workspace\n * link) and boots a real Storybook from its published `src/**\\/*.stories.tsx`\n * files, using `@recursica/storybook-template`'s exported factories.\n *\n * This lets any repo — including ones that never checked out the Recursica\n * monorepo — run adapter-tester's visual regression suite against Mantine\n * (Recursica's source-of-truth adapter) as one side of the comparison, while\n * the other side is that repo's own already-running local Storybook.\n *\n * See PROPOSAL-installed-package-harness.md for the verified prototype this\n * is built from, and the three upstream gaps it works around.\n */\n\nexport interface MantineSourceOfTruthHarnessOptions {\n /**\n * Directory the harness project is scaffolded into. Regenerated on every\n * call — add it to your .gitignore rather than committing it.\n */\n dir: string;\n /** First-guess port, shown for `--dry-run` visibility only. The harness's\n * Storybook is never pinned to this — see `HarnessWebServerConfig.port`. */\n port: number;\n /** npm version/range for @recursica/mantine-adapter. Defaults to \"latest\". */\n mantineAdapterVersion?: string;\n /** npm version/range for @recursica/storybook-template. Defaults to \"latest\". */\n storybookTemplateVersion?: string;\n}\n\nexport interface HarnessWebServerConfig {\n command: string;\n /** First-guess port, shown for `--dry-run` visibility only — not\n * authoritative. The real port is whatever this Storybook's own startup\n * banner reports once it's actually running (see portDiscovery.ts), since\n * Storybook silently falls back to an OS-assigned port whenever this one\n * is taken. */\n port: number;\n cwd: string;\n reuseExistingServer: boolean;\n /** File the real, detected port is cached in between runs, so a later\n * `reuseExistingServer` run can find this instance again. */\n cacheFile: string;\n timeout: number;\n}\n\n// Peer/dev ranges pinned to what @recursica/mantine-adapter and\n// @recursica/storybook-template themselves require, so the harness can't\n// drift onto an incompatible Mantine or Storybook major version.\nconst MANTINE_CORE_RANGE = \"^8.0.0\";\nconst STORYBOOK_RANGE = \"^10.3.3\";\nconst REACT_RANGE = \"^19.0.0\";\n\n// storybook-template's createMainConfig() defaults its addons list to these\n// three but doesn't declare them as peerDependencies (proposal gap 2) — a\n// harness that skips installing any of them gets a silent \"could not\n// resolve addon\" warning at boot, then a hard runtime crash later when Vite\n// pre-bundles preview.tsx's dependency graph. Installed explicitly here.\nconst DEFAULT_ADDON_DEPENDENCIES = {\n \"@storybook/addon-docs\": STORYBOOK_RANGE,\n \"@storybook/addon-a11y\": STORYBOOK_RANGE,\n \"storybook-dark-mode\": \"^5.0.0\",\n};\n\n// mantine-adapter's Introduction.stories.tsx (Version.tsx/OverStyling.tsx)\n// needs react-markdown, but it's a devDependency there — Storybook-only,\n// never bundled into dist — so an external `npm install` of the published\n// package won't pull it in. The harness boots a real Storybook against\n// src/, so it must provide this itself. Installed explicitly here.\nconst WORKAROUND_DEPENDENCIES = {\n \"react-markdown\": \"^10.1.0\",\n};\n\nfunction harnessPackageJson(options: {\n mantineAdapterVersion: string;\n storybookTemplateVersion: string;\n}) {\n return {\n name: \"adapter-tester-mantine-source-of-truth-harness\",\n private: true,\n type: \"module\",\n scripts: {\n // No `-p` pin — Storybook silently falls back to an OS-assigned port\n // whenever its default is taken, so the caller detects the real port\n // from this process's own output rather than trusting a fixed one.\n storybook: `storybook dev`,\n },\n dependencies: {\n \"@recursica/mantine-adapter\": options.mantineAdapterVersion,\n \"@recursica/storybook-template\": options.storybookTemplateVersion,\n \"@recursica/official-release\": \"latest\",\n \"@recursica/adapter-common\": \"latest\",\n \"@mantine/core\": MANTINE_CORE_RANGE,\n \"@mantine/dates\": MANTINE_CORE_RANGE,\n react: REACT_RANGE,\n \"react-dom\": REACT_RANGE,\n storybook: STORYBOOK_RANGE,\n \"@storybook/react-vite\": STORYBOOK_RANGE,\n ...DEFAULT_ADDON_DEPENDENCIES,\n ...WORKAROUND_DEPENDENCIES,\n },\n };\n}\n\nconst MAIN_TS = `import { createMainConfig } from \"@recursica/storybook-template/main\";\n\nconst config = createMainConfig({\n stories: [\n \"../node_modules/@recursica/mantine-adapter/src/**/*.stories.@(js|jsx|mjs|ts|tsx)\",\n ],\n enableCORS: true,\n});\n\n// react-docgen-typescript can't resolve a TS project for a config file living\n// in .storybook/ when the component source it's docgen'ing lives three\n// directories down inside node_modules — it throws \"Cannot read properties\n// of undefined (reading 'fileExists')\", which surfaces as a plain 404 on\n// preview.tsx. Docgen only powers Storybook's Controls/Docs tables, which\n// this harness never renders, so disabling it is a safe workaround (see\n// PROPOSAL-installed-package-harness.md, gap 3).\nconfig.typescript = { ...config.typescript, reactDocgen: false };\n\nexport default config;\n`;\n\nconst PREVIEW_TSX = `import type { Preview } from \"@storybook/react-vite\";\nimport { createPreviewConfig } from \"@recursica/storybook-template/preview\";\nimport { MantineProvider } from \"@mantine/core\";\nimport { Layer } from \"@recursica/adapter-common\";\nimport \"@mantine/core/styles.css\";\nimport \"@mantine/dates/styles.css\";\nimport \"@recursica/adapter-common/style.css\";\nimport \"@recursica/official-release/recursica_variables_scoped.css\";\nimport recursicaTokens from \"@recursica/official-release/recursica_tokens.json\";\nimport recursicaBrand from \"@recursica/official-release/recursica_brand.json\";\nimport recursicaUIKit from \"@recursica/official-release/recursica_ui-kit.json\";\n\nconst basePreview = createPreviewConfig({\n defaultTheme: \"light\",\n recursicaTokensJsonPath: recursicaTokens,\n recursicaBrandJsonPath: recursicaBrand,\n recursicaUIKitJsonPath: recursicaUIKit,\n});\n\n// Mirrors mantine-adapter's own .storybook/preview.tsx decorator (every story defaults to\n// withLayer: true, layer: 0, wrapped with 48px padding) — every real adapter's own preview.tsx\n// applies this same wrapping, so a target adapter's story renders inside the same Layer\n// chrome/padding the source-of-truth side does. Without this, target screenshots come out\n// dramatically smaller/differently-positioned than the source of truth's (no Layer padding,\n// background, or border-radius at all), which alone can blow past the pixel-diff threshold\n// regardless of whether the actual Recursica tokens match — a false positive, not a real\n// component bug. ColorSchemeWrapper (mantine-adapter's dark-mode-toggle sync helper) is\n// intentionally not replicated — it only matters for the interactive dev-mode UI, not automated\n// screenshot diffing, which always runs in a single theme.\nconst preview: Preview = {\n ...basePreview,\n decorators: [\n (Story, context) => {\n const { withLayer = true, layer = 0 } = context.args;\n const content = <Story />;\n return (\n <MantineProvider>\n {withLayer ? (\n <Layer layer={layer as 0 | 1 | 2 | 3} style={{ padding: \"48px\" }}>\n {content}\n </Layer>\n ) : (\n content\n )}\n </MantineProvider>\n );\n },\n ...(basePreview.decorators || []),\n ],\n};\n\nexport default preview;\n`;\n\n/** Writes the harness project's files to `options.dir` without booting it. */\nexport function scaffoldMantineSourceOfTruthHarness(\n options: MantineSourceOfTruthHarnessOptions,\n): string {\n const {\n dir,\n mantineAdapterVersion = \"latest\",\n storybookTemplateVersion = \"latest\",\n } = options;\n\n mkdirSync(join(dir, \".storybook\"), { recursive: true });\n writeFileSync(\n join(dir, \"package.json\"),\n JSON.stringify(\n harnessPackageJson({\n mantineAdapterVersion,\n storybookTemplateVersion,\n }),\n null,\n 2,\n ) + \"\\n\",\n );\n writeFileSync(join(dir, \".storybook/main.ts\"), MAIN_TS);\n writeFileSync(join(dir, \".storybook/preview.tsx\"), PREVIEW_TSX);\n writeFileSync(join(dir, \".gitignore\"), \"node_modules\\n\");\n\n return dir;\n}\n\n/**\n * Scaffolds the harness and returns a Playwright `webServer` entry for it.\n * Spread the result directly into `playwright.config.ts`'s `webServer` array.\n */\nexport function mantineSourceOfTruthWebServer(\n options: MantineSourceOfTruthHarnessOptions,\n): HarnessWebServerConfig {\n const dir = scaffoldMantineSourceOfTruthHarness(options);\n const {\n mantineAdapterVersion = \"latest\",\n storybookTemplateVersion = \"latest\",\n } = options;\n\n // A bare `npm install` is satisfied by a package-lock.json already sitting\n // in `dir` from a prior run and skips re-resolving against the registry\n // entirely — no network call — so a run can silently keep testing against\n // a stale @recursica/mantine-adapter/storybook-template even after a newer\n // version is published. Naming the two version-pinned packages as explicit\n // `pkg@specifier` CLI args instead forces npm to re-check just those two\n // against the registry every run, while the rest of node_modules stays\n // cached.\n const command = `npm install @recursica/mantine-adapter@${mantineAdapterVersion} @recursica/storybook-template@${storybookTemplateVersion} --no-audit --no-fund && npm run storybook`;\n\n return {\n command,\n port: options.port,\n cwd: dir,\n reuseExistingServer: !process.env.CI,\n cacheFile: join(dir, \"last-port.json\"),\n timeout: 180 * 1000,\n };\n}\n"],"names":["MANTINE_CORE_RANGE","STORYBOOK_RANGE","REACT_RANGE","DEFAULT_ADDON_DEPENDENCIES","WORKAROUND_DEPENDENCIES","harnessPackageJson","options","MAIN_TS","PREVIEW_TSX","scaffoldMantineSourceOfTruthHarness","dir","mantineAdapterVersion","storybookTemplateVersion","mkdirSync","join","writeFileSync","mantineSourceOfTruthWebServer"],"mappings":";;AAoDA,MAAMA,IAAqB,UACrBC,IAAkB,WAClBC,IAAc,WAOdC,IAA6B;AAAA,EACjC,yBAAyBF;AAAA,EACzB,yBAAyBA;AAAA,EACzB,uBAAuB;AACzB,GAOMG,IAA0B;AAAA,EAC9B,kBAAkB;AACpB;AAEA,SAASC,EAAmBC,GAGzB;AACD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,MAAM;AAAA,IACN,SAAS;AAAA;AAAA;AAAA;AAAA,MAIP,WAAW;AAAA,IAAA;AAAA,IAEb,cAAc;AAAA,MACZ,8BAA8BA,EAAQ;AAAA,MACtC,iCAAiCA,EAAQ;AAAA,MACzC,+BAA+B;AAAA,MAC/B,6BAA6B;AAAA,MAC7B,iBAAiBN;AAAA,MACjB,kBAAkBA;AAAA,MAClB,OAAOE;AAAA,MACP,aAAaA;AAAA,MACb,WAAWD;AAAA,MACX,yBAAyBA;AAAA,MACzB,GAAGE;AAAA,MACH,GAAGC;AAAA,IAAA;AAAA,EACL;AAEJ;AAEA,MAAMG,IAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAqBVC,IAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuDb,SAASC,EACdH,GACQ;AACR,QAAM;AAAA,IACJ,KAAAI;AAAA,IACA,uBAAAC,IAAwB;AAAA,IACxB,0BAAAC,IAA2B;AAAA,EAAA,IACzBN;AAEJ,SAAAO,EAAUC,EAAKJ,GAAK,YAAY,GAAG,EAAE,WAAW,IAAM,GACtDK;AAAA,IACED,EAAKJ,GAAK,cAAc;AAAA,IACxB,KAAK;AAAA,MACHL,EAAmB;AAAA,QACjB,uBAAAM;AAAA,QACA,0BAAAC;AAAA,MAAA,CACD;AAAA,MACD;AAAA,MACA;AAAA,IAAA,IACE;AAAA;AAAA,EAAA,GAENG,EAAcD,EAAKJ,GAAK,oBAAoB,GAAGH,CAAO,GACtDQ,EAAcD,EAAKJ,GAAK,wBAAwB,GAAGF,CAAW,GAC9DO,EAAcD,EAAKJ,GAAK,YAAY,GAAG;AAAA,CAAgB,GAEhDA;AACT;AAMO,SAASM,EACdV,GACwB;AACxB,QAAMI,IAAMD,EAAoCH,CAAO,GACjD;AAAA,IACJ,uBAAAK,IAAwB;AAAA,IACxB,0BAAAC,IAA2B;AAAA,EAAA,IACzBN;AAYJ,SAAO;AAAA,IACL,SAHc,0CAA0CK,CAAqB,kCAAkCC,CAAwB;AAAA,IAIvI,MAAMN,EAAQ;AAAA,IACd,KAAKI;AAAA,IACL,qBAAqB,CAAC,QAAQ,IAAI;AAAA,IAClC,WAAWI,EAAKJ,GAAK,gBAAgB;AAAA,IACrC,SAAS,MAAM;AAAA,EAAA;AAEnB;"}
@@ -1,4 +1,4 @@
1
- "use strict";const t=require("node:fs"),o=require("node:path"),c="^8.0.0",s="^10.3.3",d="^19.0.0",p={"@storybook/addon-docs":s,"@storybook/addon-a11y":s,"storybook-dark-mode":"^5.0.0"},u={"react-markdown":"^10.1.0"};function m(e){return{name:"adapter-tester-mantine-source-of-truth-harness",private:!0,type:"module",scripts:{storybook:`storybook dev -p ${e.port}`},dependencies:{"@recursica/mantine-adapter":e.mantineAdapterVersion,"@recursica/storybook-template":e.storybookTemplateVersion,"@recursica/official-release":"latest","@recursica/adapter-common":"latest","@mantine/core":c,"@mantine/dates":c,react:d,"react-dom":d,storybook:s,"@storybook/react-vite":s,...p,...u}}}const f=`import { createMainConfig } from "@recursica/storybook-template/main";
1
+ "use strict";const o=require("node:fs"),t=require("node:path"),n="^8.0.0",s="^10.3.3",c="^19.0.0",l={"@storybook/addon-docs":s,"@storybook/addon-a11y":s,"storybook-dark-mode":"^5.0.0"},p={"react-markdown":"^10.1.0"};function u(r){return{name:"adapter-tester-mantine-source-of-truth-harness",private:!0,type:"module",scripts:{storybook:"storybook dev"},dependencies:{"@recursica/mantine-adapter":r.mantineAdapterVersion,"@recursica/storybook-template":r.storybookTemplateVersion,"@recursica/official-release":"latest","@recursica/adapter-common":"latest","@mantine/core":n,"@mantine/dates":n,react:c,"react-dom":c,storybook:s,"@storybook/react-vite":s,...l,...p}}}const m=`import { createMainConfig } from "@recursica/storybook-template/main";
2
2
 
3
3
  const config = createMainConfig({
4
4
  stories: [
@@ -17,7 +17,7 @@ const config = createMainConfig({
17
17
  config.typescript = { ...config.typescript, reactDocgen: false };
18
18
 
19
19
  export default config;
20
- `,y=`import type { Preview } from "@storybook/react-vite";
20
+ `,f=`import type { Preview } from "@storybook/react-vite";
21
21
  import { createPreviewConfig } from "@recursica/storybook-template/preview";
22
22
  import { MantineProvider } from "@mantine/core";
23
23
  import { Layer } from "@recursica/adapter-common";
@@ -69,7 +69,7 @@ const preview: Preview = {
69
69
  };
70
70
 
71
71
  export default preview;
72
- `;function l(e){const{dir:r,port:a,mantineAdapterVersion:i="latest",storybookTemplateVersion:n="latest"}=e;return t.mkdirSync(o.join(r,".storybook"),{recursive:!0}),t.writeFileSync(o.join(r,"package.json"),JSON.stringify(m({mantineAdapterVersion:i,storybookTemplateVersion:n,port:a}),null,2)+`
73
- `),t.writeFileSync(o.join(r,".storybook/main.ts"),f),t.writeFileSync(o.join(r,".storybook/preview.tsx"),y),t.writeFileSync(o.join(r,".gitignore"),`node_modules
74
- `),r}function h(e){const r=l(e),{mantineAdapterVersion:a="latest",storybookTemplateVersion:i="latest"}=e;return{command:`npm install @recursica/mantine-adapter@${a} @recursica/storybook-template@${i} --no-audit --no-fund && npm run storybook`,port:e.port,cwd:r,reuseExistingServer:!process.env.CI,timeout:180*1e3}}exports.mantineSourceOfTruthWebServer=h;exports.scaffoldMantineSourceOfTruthHarness=l;
75
- //# sourceMappingURL=mantineSourceOfTruth-BUb5MZy0.cjs.map
72
+ `;function d(r){const{dir:e,mantineAdapterVersion:a="latest",storybookTemplateVersion:i="latest"}=r;return o.mkdirSync(t.join(e,".storybook"),{recursive:!0}),o.writeFileSync(t.join(e,"package.json"),JSON.stringify(u({mantineAdapterVersion:a,storybookTemplateVersion:i}),null,2)+`
73
+ `),o.writeFileSync(t.join(e,".storybook/main.ts"),m),o.writeFileSync(t.join(e,".storybook/preview.tsx"),f),o.writeFileSync(t.join(e,".gitignore"),`node_modules
74
+ `),e}function y(r){const e=d(r),{mantineAdapterVersion:a="latest",storybookTemplateVersion:i="latest"}=r;return{command:`npm install @recursica/mantine-adapter@${a} @recursica/storybook-template@${i} --no-audit --no-fund && npm run storybook`,port:r.port,cwd:e,reuseExistingServer:!process.env.CI,cacheFile:t.join(e,"last-port.json"),timeout:180*1e3}}exports.mantineSourceOfTruthWebServer=y;exports.scaffoldMantineSourceOfTruthHarness=d;
75
+ //# sourceMappingURL=mantineSourceOfTruth-Dpe4mlmF.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mantineSourceOfTruth-Dpe4mlmF.cjs","sources":["../src/harness/mantineSourceOfTruth.ts"],"sourcesContent":["import { mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n/**\n * Generates a small, throwaway Storybook project that installs\n * `@recursica/mantine-adapter` as a real npm dependency (not a workspace\n * link) and boots a real Storybook from its published `src/**\\/*.stories.tsx`\n * files, using `@recursica/storybook-template`'s exported factories.\n *\n * This lets any repo — including ones that never checked out the Recursica\n * monorepo — run adapter-tester's visual regression suite against Mantine\n * (Recursica's source-of-truth adapter) as one side of the comparison, while\n * the other side is that repo's own already-running local Storybook.\n *\n * See PROPOSAL-installed-package-harness.md for the verified prototype this\n * is built from, and the three upstream gaps it works around.\n */\n\nexport interface MantineSourceOfTruthHarnessOptions {\n /**\n * Directory the harness project is scaffolded into. Regenerated on every\n * call — add it to your .gitignore rather than committing it.\n */\n dir: string;\n /** First-guess port, shown for `--dry-run` visibility only. The harness's\n * Storybook is never pinned to this — see `HarnessWebServerConfig.port`. */\n port: number;\n /** npm version/range for @recursica/mantine-adapter. Defaults to \"latest\". */\n mantineAdapterVersion?: string;\n /** npm version/range for @recursica/storybook-template. Defaults to \"latest\". */\n storybookTemplateVersion?: string;\n}\n\nexport interface HarnessWebServerConfig {\n command: string;\n /** First-guess port, shown for `--dry-run` visibility only — not\n * authoritative. The real port is whatever this Storybook's own startup\n * banner reports once it's actually running (see portDiscovery.ts), since\n * Storybook silently falls back to an OS-assigned port whenever this one\n * is taken. */\n port: number;\n cwd: string;\n reuseExistingServer: boolean;\n /** File the real, detected port is cached in between runs, so a later\n * `reuseExistingServer` run can find this instance again. */\n cacheFile: string;\n timeout: number;\n}\n\n// Peer/dev ranges pinned to what @recursica/mantine-adapter and\n// @recursica/storybook-template themselves require, so the harness can't\n// drift onto an incompatible Mantine or Storybook major version.\nconst MANTINE_CORE_RANGE = \"^8.0.0\";\nconst STORYBOOK_RANGE = \"^10.3.3\";\nconst REACT_RANGE = \"^19.0.0\";\n\n// storybook-template's createMainConfig() defaults its addons list to these\n// three but doesn't declare them as peerDependencies (proposal gap 2) — a\n// harness that skips installing any of them gets a silent \"could not\n// resolve addon\" warning at boot, then a hard runtime crash later when Vite\n// pre-bundles preview.tsx's dependency graph. Installed explicitly here.\nconst DEFAULT_ADDON_DEPENDENCIES = {\n \"@storybook/addon-docs\": STORYBOOK_RANGE,\n \"@storybook/addon-a11y\": STORYBOOK_RANGE,\n \"storybook-dark-mode\": \"^5.0.0\",\n};\n\n// mantine-adapter's Introduction.stories.tsx (Version.tsx/OverStyling.tsx)\n// needs react-markdown, but it's a devDependency there — Storybook-only,\n// never bundled into dist — so an external `npm install` of the published\n// package won't pull it in. The harness boots a real Storybook against\n// src/, so it must provide this itself. Installed explicitly here.\nconst WORKAROUND_DEPENDENCIES = {\n \"react-markdown\": \"^10.1.0\",\n};\n\nfunction harnessPackageJson(options: {\n mantineAdapterVersion: string;\n storybookTemplateVersion: string;\n}) {\n return {\n name: \"adapter-tester-mantine-source-of-truth-harness\",\n private: true,\n type: \"module\",\n scripts: {\n // No `-p` pin — Storybook silently falls back to an OS-assigned port\n // whenever its default is taken, so the caller detects the real port\n // from this process's own output rather than trusting a fixed one.\n storybook: `storybook dev`,\n },\n dependencies: {\n \"@recursica/mantine-adapter\": options.mantineAdapterVersion,\n \"@recursica/storybook-template\": options.storybookTemplateVersion,\n \"@recursica/official-release\": \"latest\",\n \"@recursica/adapter-common\": \"latest\",\n \"@mantine/core\": MANTINE_CORE_RANGE,\n \"@mantine/dates\": MANTINE_CORE_RANGE,\n react: REACT_RANGE,\n \"react-dom\": REACT_RANGE,\n storybook: STORYBOOK_RANGE,\n \"@storybook/react-vite\": STORYBOOK_RANGE,\n ...DEFAULT_ADDON_DEPENDENCIES,\n ...WORKAROUND_DEPENDENCIES,\n },\n };\n}\n\nconst MAIN_TS = `import { createMainConfig } from \"@recursica/storybook-template/main\";\n\nconst config = createMainConfig({\n stories: [\n \"../node_modules/@recursica/mantine-adapter/src/**/*.stories.@(js|jsx|mjs|ts|tsx)\",\n ],\n enableCORS: true,\n});\n\n// react-docgen-typescript can't resolve a TS project for a config file living\n// in .storybook/ when the component source it's docgen'ing lives three\n// directories down inside node_modules — it throws \"Cannot read properties\n// of undefined (reading 'fileExists')\", which surfaces as a plain 404 on\n// preview.tsx. Docgen only powers Storybook's Controls/Docs tables, which\n// this harness never renders, so disabling it is a safe workaround (see\n// PROPOSAL-installed-package-harness.md, gap 3).\nconfig.typescript = { ...config.typescript, reactDocgen: false };\n\nexport default config;\n`;\n\nconst PREVIEW_TSX = `import type { Preview } from \"@storybook/react-vite\";\nimport { createPreviewConfig } from \"@recursica/storybook-template/preview\";\nimport { MantineProvider } from \"@mantine/core\";\nimport { Layer } from \"@recursica/adapter-common\";\nimport \"@mantine/core/styles.css\";\nimport \"@mantine/dates/styles.css\";\nimport \"@recursica/adapter-common/style.css\";\nimport \"@recursica/official-release/recursica_variables_scoped.css\";\nimport recursicaTokens from \"@recursica/official-release/recursica_tokens.json\";\nimport recursicaBrand from \"@recursica/official-release/recursica_brand.json\";\nimport recursicaUIKit from \"@recursica/official-release/recursica_ui-kit.json\";\n\nconst basePreview = createPreviewConfig({\n defaultTheme: \"light\",\n recursicaTokensJsonPath: recursicaTokens,\n recursicaBrandJsonPath: recursicaBrand,\n recursicaUIKitJsonPath: recursicaUIKit,\n});\n\n// Mirrors mantine-adapter's own .storybook/preview.tsx decorator (every story defaults to\n// withLayer: true, layer: 0, wrapped with 48px padding) — every real adapter's own preview.tsx\n// applies this same wrapping, so a target adapter's story renders inside the same Layer\n// chrome/padding the source-of-truth side does. Without this, target screenshots come out\n// dramatically smaller/differently-positioned than the source of truth's (no Layer padding,\n// background, or border-radius at all), which alone can blow past the pixel-diff threshold\n// regardless of whether the actual Recursica tokens match — a false positive, not a real\n// component bug. ColorSchemeWrapper (mantine-adapter's dark-mode-toggle sync helper) is\n// intentionally not replicated — it only matters for the interactive dev-mode UI, not automated\n// screenshot diffing, which always runs in a single theme.\nconst preview: Preview = {\n ...basePreview,\n decorators: [\n (Story, context) => {\n const { withLayer = true, layer = 0 } = context.args;\n const content = <Story />;\n return (\n <MantineProvider>\n {withLayer ? (\n <Layer layer={layer as 0 | 1 | 2 | 3} style={{ padding: \"48px\" }}>\n {content}\n </Layer>\n ) : (\n content\n )}\n </MantineProvider>\n );\n },\n ...(basePreview.decorators || []),\n ],\n};\n\nexport default preview;\n`;\n\n/** Writes the harness project's files to `options.dir` without booting it. */\nexport function scaffoldMantineSourceOfTruthHarness(\n options: MantineSourceOfTruthHarnessOptions,\n): string {\n const {\n dir,\n mantineAdapterVersion = \"latest\",\n storybookTemplateVersion = \"latest\",\n } = options;\n\n mkdirSync(join(dir, \".storybook\"), { recursive: true });\n writeFileSync(\n join(dir, \"package.json\"),\n JSON.stringify(\n harnessPackageJson({\n mantineAdapterVersion,\n storybookTemplateVersion,\n }),\n null,\n 2,\n ) + \"\\n\",\n );\n writeFileSync(join(dir, \".storybook/main.ts\"), MAIN_TS);\n writeFileSync(join(dir, \".storybook/preview.tsx\"), PREVIEW_TSX);\n writeFileSync(join(dir, \".gitignore\"), \"node_modules\\n\");\n\n return dir;\n}\n\n/**\n * Scaffolds the harness and returns a Playwright `webServer` entry for it.\n * Spread the result directly into `playwright.config.ts`'s `webServer` array.\n */\nexport function mantineSourceOfTruthWebServer(\n options: MantineSourceOfTruthHarnessOptions,\n): HarnessWebServerConfig {\n const dir = scaffoldMantineSourceOfTruthHarness(options);\n const {\n mantineAdapterVersion = \"latest\",\n storybookTemplateVersion = \"latest\",\n } = options;\n\n // A bare `npm install` is satisfied by a package-lock.json already sitting\n // in `dir` from a prior run and skips re-resolving against the registry\n // entirely — no network call — so a run can silently keep testing against\n // a stale @recursica/mantine-adapter/storybook-template even after a newer\n // version is published. Naming the two version-pinned packages as explicit\n // `pkg@specifier` CLI args instead forces npm to re-check just those two\n // against the registry every run, while the rest of node_modules stays\n // cached.\n const command = `npm install @recursica/mantine-adapter@${mantineAdapterVersion} @recursica/storybook-template@${storybookTemplateVersion} --no-audit --no-fund && npm run storybook`;\n\n return {\n command,\n port: options.port,\n cwd: dir,\n reuseExistingServer: !process.env.CI,\n cacheFile: join(dir, \"last-port.json\"),\n timeout: 180 * 1000,\n };\n}\n"],"names":["MANTINE_CORE_RANGE","STORYBOOK_RANGE","REACT_RANGE","DEFAULT_ADDON_DEPENDENCIES","WORKAROUND_DEPENDENCIES","harnessPackageJson","options","MAIN_TS","PREVIEW_TSX","scaffoldMantineSourceOfTruthHarness","dir","mantineAdapterVersion","storybookTemplateVersion","mkdirSync","join","writeFileSync","mantineSourceOfTruthWebServer"],"mappings":"+DAoDMA,EAAqB,SACrBC,EAAkB,UAClBC,EAAc,UAOdC,EAA6B,CACjC,wBAAyBF,EACzB,wBAAyBA,EACzB,sBAAuB,QACzB,EAOMG,EAA0B,CAC9B,iBAAkB,SACpB,EAEA,SAASC,EAAmBC,EAGzB,CACD,MAAO,CACL,KAAM,iDACN,QAAS,GACT,KAAM,SACN,QAAS,CAIP,UAAW,eAAA,EAEb,aAAc,CACZ,6BAA8BA,EAAQ,sBACtC,gCAAiCA,EAAQ,yBACzC,8BAA+B,SAC/B,4BAA6B,SAC7B,gBAAiBN,EACjB,iBAAkBA,EAClB,MAAOE,EACP,YAAaA,EACb,UAAWD,EACX,wBAAyBA,EACzB,GAAGE,EACH,GAAGC,CAAA,CACL,CAEJ,CAEA,MAAMG,EAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBVC,EAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuDb,SAASC,EACdH,EACQ,CACR,KAAM,CACJ,IAAAI,EACA,sBAAAC,EAAwB,SACxB,yBAAAC,EAA2B,QAAA,EACzBN,EAEJO,OAAAA,EAAAA,UAAUC,EAAAA,KAAKJ,EAAK,YAAY,EAAG,CAAE,UAAW,GAAM,EACtDK,EAAAA,cACED,EAAAA,KAAKJ,EAAK,cAAc,EACxB,KAAK,UACHL,EAAmB,CACjB,sBAAAM,EACA,yBAAAC,CAAA,CACD,EACD,KACA,CAAA,EACE;AAAA,CAAA,EAENG,EAAAA,cAAcD,EAAAA,KAAKJ,EAAK,oBAAoB,EAAGH,CAAO,EACtDQ,EAAAA,cAAcD,EAAAA,KAAKJ,EAAK,wBAAwB,EAAGF,CAAW,EAC9DO,EAAAA,cAAcD,EAAAA,KAAKJ,EAAK,YAAY,EAAG;AAAA,CAAgB,EAEhDA,CACT,CAMO,SAASM,EACdV,EACwB,CACxB,MAAMI,EAAMD,EAAoCH,CAAO,EACjD,CACJ,sBAAAK,EAAwB,SACxB,yBAAAC,EAA2B,QAAA,EACzBN,EAYJ,MAAO,CACL,QAHc,0CAA0CK,CAAqB,kCAAkCC,CAAwB,6CAIvI,KAAMN,EAAQ,KACd,IAAKI,EACL,oBAAqB,CAAC,QAAQ,IAAI,GAClC,UAAWI,EAAAA,KAAKJ,EAAK,gBAAgB,EACrC,QAAS,IAAM,GAAA,CAEnB"}
@@ -0,0 +1,39 @@
1
+ import { ChildProcess } from 'node:child_process';
2
+ import { HarnessWebServerConfig } from './harness/mantineSourceOfTruth.js';
3
+ /**
4
+ * Boots a target's Storybook and discovers the real port it ends up on,
5
+ * instead of pinning one via `-p`/`--port`. Storybook silently falls back to
6
+ * an OS-assigned port whenever its default/configured one is taken (this is
7
+ * what caused the flaky `webServer` timeouts noted in mui-adapter), so the
8
+ * only reliable source of truth is the URL it prints in its own startup
9
+ * banner. Used by both the automated/headless run (cli.ts) and Dev Mode
10
+ * (devServer.ts) — neither pins a port anymore.
11
+ */
12
+ export interface LaunchTarget {
13
+ /** Human-readable name used in log lines. */
14
+ name: string;
15
+ command: string;
16
+ cwd: string;
17
+ /** Reuse an already-running instance (detected via the last-known-port
18
+ * cache) instead of spawning a new one. Mirrors the old `reuseExistingServer`
19
+ * behavior, which used to just probe the one fixed configured port. */
20
+ reuseExistingServer: boolean;
21
+ /** File the discovered port is cached in between runs, so a later
22
+ * `reuseExistingServer` run knows where to look. One per target. */
23
+ cacheFile: string;
24
+ timeoutMs?: number;
25
+ }
26
+ export interface DiscoveredServer {
27
+ url: string;
28
+ port: number;
29
+ /** The process we spawned, or `null` if an already-running instance was
30
+ * reused — callers should only kill what they started. */
31
+ process: ChildProcess | null;
32
+ }
33
+ /** Adapts a resolved `HarnessWebServerConfig` into a `LaunchTarget`. */
34
+ export declare function toLaunchTarget(name: string, server: HarnessWebServerConfig): LaunchTarget;
35
+ /** Reuses an already-running Storybook if `reuseExistingServer` is set and
36
+ * the last-known-port cache points at something still listening; otherwise
37
+ * spawns `target.command` fresh and detects the real port from its output. */
38
+ export declare function launchAndDetectStorybook(target: LaunchTarget): Promise<DiscoveredServer>;
39
+ //# sourceMappingURL=portDiscovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portDiscovery.d.ts","sourceRoot":"","sources":["../src/portDiscovery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAI9D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAEhF;;;;;;;;GAQG;AAEH,MAAM,WAAW,YAAY;IAC3B,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ;;2EAEuE;IACvE,mBAAmB,EAAE,OAAO,CAAC;IAC7B;wEACoE;IACpE,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb;8DAC0D;IAC1D,OAAO,EAAE,YAAY,GAAG,IAAI,CAAC;CAC9B;AAgHD,wEAAwE;AACxE,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,sBAAsB,GAC7B,YAAY,CASd;AAED;;8EAE8E;AAC9E,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,gBAAgB,CAAC,CAgB3B"}
@@ -1,27 +1,76 @@
1
+ import { Browser, TestInfo } from '@playwright/test';
1
2
  import { AdapterTesterConfig } from '../config.js';
3
+ interface StorybookEntry {
4
+ type: string;
5
+ id: string;
6
+ name: string;
7
+ title: string;
8
+ }
9
+ /** Everything a generated Playwright spec needs to register the golden-image
10
+ * suite itself. Split out from the actual `test.describe`/`test` calls so
11
+ * those calls execute in the spec file that imports this, not in this
12
+ * library file — otherwise Playwright's HTML report groups every story under
13
+ * this file's own (sourcemapped) path instead of a stable spec name. */
14
+ export interface VisualRegressionPlan {
15
+ /** `config`'s own (non-source-of-truth) target name, for the suite title. */
16
+ ownTargetName: string;
17
+ /** Suite title suffix describing which check mode is running. */
18
+ suiteLabel: string;
19
+ /** Stories to check, already filtered and sorted by id. */
20
+ stories: StorybookEntry[];
21
+ /** Story ids the source-of-truth adapter has a golden for but this
22
+ * project's own Storybook doesn't — empty outside `checkMode: "divergence"`
23
+ * or when `sourceOfTruthGolden` didn't resolve. Excludes ids covered by a
24
+ * `stories.<id>.exclude` entry: an intentional gap, not a sync failure. */
25
+ missingFromSourceOfTruth: string[];
26
+ /** Golden-checks one story. Call this from inside a `test(story.id, ...)`
27
+ * body — safe to run concurrently across Playwright workers, since each
28
+ * call only ever reads/writes its own story's manifest entry (locked at
29
+ * the point it writes it back, so concurrent workers never race each
30
+ * other's entries — see `updateManifestEntry`). */
31
+ checkStory: (story: StorybookEntry, browser: Browser, testInfo: TestInfo) => Promise<void>;
32
+ }
33
+ /** Everything a generated Playwright spec needs to register the golden-image
34
+ * suite itself. Split out from the actual `test.describe`/`test` calls so
35
+ * those calls execute in the spec file that imports this, not in this
36
+ * library file — otherwise Playwright's HTML report groups every story under
37
+ * this file's own (sourcemapped) path instead of a stable spec name. */
38
+ export interface VisualRegressionPlan {
39
+ /** `config`'s own (non-source-of-truth) target name, for the suite title. */
40
+ ownTargetName: string;
41
+ /** Suite title suffix describing which check mode is running. */
42
+ suiteLabel: string;
43
+ /** Stories to check, already filtered and sorted by id. */
44
+ stories: StorybookEntry[];
45
+ /** Golden-checks one story. Call this from inside a `test(story.id, ...)`
46
+ * body — safe to run concurrently across Playwright workers, since each
47
+ * call only ever reads/writes its own story's manifest entry (locked at
48
+ * the point it writes it back, so concurrent workers never race each
49
+ * other's entries — see `updateManifestEntry`). */
50
+ checkStory: (story: StorybookEntry, browser: Browser, testInfo: TestInfo) => Promise<void>;
51
+ }
2
52
  /**
3
- * Defines a Playwright suite that golden-image-tests every story in
4
- * `config`'s own target (the one target in `config.targets` not marked
5
- * `sourceOfTruth`). Call this with a top-level `await` from a Playwright
6
- * `*.spec.ts` file — it calls `test.describe` at module scope, so it must
7
- * run inside Playwright's test runner during test-graph compilation.
53
+ * Resolves the golden-image plan for `config`'s own target (the one target
54
+ * in `config.targets` not marked `sourceOfTruth`).
8
55
  *
9
- * Two independent checks per story, neither of which boots the
10
- * source-of-truth adapter's own Storybook — the divergence check below
11
- * compares stored golden files, not live pages:
56
+ * Two independent checks per story, gated by `config.checkMode`, neither of
57
+ * which boots the source-of-truth adapter's own Storybook — the divergence
58
+ * check below compares stored golden files, not live pages:
12
59
  *
13
- * 1. **Own-drift (hard fail):** this run's live render vs this project's own
14
- * stored `test/golden/<story-id>.png`. No golden yet for a story is not a
15
- * failure — one is captured from this run instead (same as
16
- * `--update-golden`, scoped to just that story).
17
- * 2. **Source-of-truth divergence (soft flag, never fails the run):** this
18
- * project's own golden vs the source-of-truth's golden (`config`'s
60
+ * 1. **Own-drift (`checkMode: "own"`, the default; hard fail):** this run's
61
+ * live render vs this project's own stored `test/golden/<story-id>.png`.
62
+ * No golden yet for a story is not a failure — one is captured from this
63
+ * run instead (same as `--update-golden`, scoped to just that story), in
64
+ * either mode.
65
+ * 2. **Source-of-truth divergence (`checkMode: "divergence"`; hard fail):**
66
+ * this project's own golden vs the source-of-truth's golden (`config`'s
19
67
  * `sourceOfTruthGolden`). Skipped entirely when
20
- * `config.isSourceOfTruthAdapter` is true — the source-of-truth adapter
21
- * has nothing above it to diverge from — and skipped per-story when
22
- * neither side has a baseline yet. A once-flagged divergence stays quiet
23
- * after `--approve-divergence`, until the source of truth's own golden
24
- * changes again.
68
+ * `config.isSourceOfTruthAdapter` is true — the
69
+ * source-of-truth adapter has nothing above it to diverge from — and
70
+ * skipped per-story when neither side has a baseline yet. A
71
+ * once-flagged divergence stays quiet after `--approve-divergence`,
72
+ * until the source of truth's own golden changes again.
25
73
  */
26
- export declare function runVisualRegression(config: AdapterTesterConfig): Promise<void>;
74
+ export declare function resolveVisualRegressionPlan(config: AdapterTesterConfig): Promise<VisualRegressionPlan>;
75
+ export {};
27
76
  //# sourceMappingURL=runVisualRegression.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runVisualRegression.d.ts","sourceRoot":"","sources":["../../src/testing/runVisualRegression.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAmFxD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,IAAI,CAAC,CA0Hf"}
1
+ {"version":3,"file":"runVisualRegression.d.ts","sourceRoot":"","sources":["../../src/testing/runVisualRegression.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAcxD,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAmED;;;;wEAIwE;AACxE,MAAM,WAAW,oBAAoB;IACnC,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAC;IACtB,iEAAiE;IACjE,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B;;;+EAG2E;IAC3E,wBAAwB,EAAE,MAAM,EAAE,CAAC;IACnC;;;;uDAImD;IACnD,UAAU,EAAE,CACV,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,KACf,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB;AAED;;;;wEAIwE;AACxE,MAAM,WAAW,oBAAoB;IACnC,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAC;IACtB,iEAAiE;IACjE,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B;;;;uDAImD;IACnD,UAAU,EAAE,CACV,KAAK,EAAE,cAAc,EACrB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,KACf,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,oBAAoB,CAAC,CA2P/B"}
package/dist/testing.cjs CHANGED
@@ -1,5 +1,6 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const S=require("@playwright/test"),a=require("node:fs"),D=require("pixelmatch"),k=require("pngjs"),T=require("node:path"),G=require("./index-C6uYPRmx.cjs");function A(e,t){const r=k.PNG.sync.read(e),s=k.PNG.sync.read(t);if(r.width!==s.width||r.height!==s.height)return 1/0;const n=new k.PNG({width:r.width,height:r.height});return D(r.data,s.data,n.data,r.width,r.height,{threshold:.1})}const B="http://json-schema.org/draft-07/schema#",M="https://github.com/borderux/recursica/tree/main/packages/adapter-tester/src/golden/manifest.schema.json",q="test/golden/manifest.json",_="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.",L="object",R={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."}}},V={$schema:B,$id:M,title:q,description:_,type:L,additionalProperties:R},U=G.index,I=new G.ajvExports.Ajv({allErrors:!0,strict:!0});U(I);const E=I.compile(V);function b(e,t){if(E(e))return;const r=(E.errors??[]).map(s=>{var d;const n=(d=s.params)!=null&&d.additionalProperty?` '${s.params.additionalProperty}'`:"";return` - ${s.instancePath||"root"} ${s.message}${n}`}).join(`
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const L=require("@playwright/test"),i=require("node:fs"),H=require("pixelmatch"),b=require("pngjs"),P=require("node:path"),R=require("./index-C6uYPRmx.cjs");function _(e,t){const n=b.PNG.sync.read(e),r=b.PNG.sync.read(t);if(n.width!==r.width||n.height!==r.height)return{diffPixels:1/0,diffImage:null};const o=new b.PNG({width:n.width,height:n.height});return{diffPixels:H(n.data,r.data,o.data,n.width,n.height,{threshold:.1}),diffImage:b.PNG.sync.write(o)}}const z="http://json-schema.org/draft-07/schema#",Y="https://github.com/borderux/recursica/tree/main/packages/adapter-tester/src/golden/manifest.schema.json",Q="test/golden/manifest.json",Z="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.",ee="object",te={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."}}},re={$schema:z,$id:Y,title:Q,description:Z,type:ee,additionalProperties:te},ne=R.index,J=new R.ajvExports.Ajv({allErrors:!0,strict:!0});ne(J);const q=J.compile(re);function N(e,t){if(q(e))return;const n=(q.errors??[]).map(r=>{var a;const o=(a=r.params)!=null&&a.additionalProperty?` '${r.params.additionalProperty}'`:"";return` - ${r.instancePath||"root"} ${r.message}${o}`}).join(`
2
2
  `);throw new Error(`Invalid ${t}:
3
- ${r}`)}function m(e){return T.join(e,"manifest.json")}function w(e,t){return T.join(e,`${t}.png`)}function x(e){const t=m(e);if(!a.existsSync(t))return{};const r=JSON.parse(a.readFileSync(t,"utf8"));return b(r,t),r}function J(e,t){const r=m(e);b(t,r);const s={};for(const n of Object.keys(t).sort())s[n]=t[n];a.mkdirSync(e,{recursive:!0}),a.writeFileSync(r,JSON.stringify(s,null,2)+`
4
- `)}function W(e,t,r){a.mkdirSync(e,{recursive:!0}),a.writeFileSync(w(e,t),r)}const H="borderux/recursica";async function K(e,t){var d,h;const r=await fetch(`https://registry.npmjs.org/${e}`);if(!r.ok)throw new Error(`Could not reach npm registry for ${e}: ${r.statusText}`);const s=await r.json(),n=((d=s["dist-tags"])==null?void 0:d[t])??((h=s.versions)!=null&&h[t]?t:void 0);if(!n)throw new Error(`${e} has no version or dist-tag "${t}" on the npm registry.`);return n}function X(e){return`packages/${e.split("/").pop()}`}async function z(e){if(e.type==="local")return a.existsSync(m(e.dir))?{manifest:x(e.dir),async readImage(i){const u=w(e.dir,i);return a.existsSync(u)?a.readFileSync(u):null}}:(console.warn(`No golden baseline found yet at ${e.dir} — source-of-truth divergence check skipped for this run.`),null);let t;try{t=await K(e.packageName,e.versionSpec)}catch(c){return console.warn(`Could not resolve ${e.packageName}@${e.versionSpec} — source-of-truth divergence check skipped for this run.`,c),null}const r=T.join(e.cacheDir,t),s=`${e.packageName}@${t}`,n=`https://raw.githubusercontent.com/${H}/${s}/${X(e.packageName)}/test/golden`;let d;const h=m(r);if(a.existsSync(h))d=x(r);else{let c;try{c=await fetch(`${n}/manifest.json`)}catch(o){return console.warn(`Could not reach GitHub to fetch ${s}'s golden baseline — source-of-truth divergence check skipped for this run.`,o),null}if(!c.ok)return console.warn(`No golden baseline published for ${s} — source-of-truth divergence check skipped for this run.`),null;const i=await c.text(),u=JSON.parse(i);b(u,`${n}/manifest.json`),a.mkdirSync(r,{recursive:!0}),a.writeFileSync(h,i),d=u}return{manifest:d,async readImage(c){const i=w(r,c);if(a.existsSync(i))return a.readFileSync(i);const u=await fetch(`${n}/${c}.png`);if(!u.ok)return null;const o=Buffer.from(await u.arrayBuffer());return a.mkdirSync(r,{recursive:!0}),a.writeFileSync(i,o),o}}}const Q=["Theme","Tokens","Introduction"];function F(e,t){return e===t||e.startsWith(t)}async function Y(e,t,r){let s;try{const n=await fetch(`${e.url}/index.json`);if(!n.ok)throw new Error(`Failed to fetch Storybook index: ${n.statusText}`);const h=(await n.json()).entries||{};s=Object.values(h).filter(c=>c.type==="story"&&!t.some(i=>c.title===i||c.title.startsWith(`${i}/`))&&!r.some(i=>F(c.id,i))),s.sort((c,i)=>c.id.localeCompare(i.id))}catch(n){throw console.error("Failed to load Storybook index from",`${e.url}/index.json`,n),new Error(`Storybook target "${e.name}" is not responsive or index.json is missing. Please ensure its Storybook is running.`)}return s}function Z(e,t,r){let s;for(const n of Object.keys(t))F(e,n)&&(!s||n.length>s.length)&&(s=n);return s!==void 0?t[s]:r}async function ee(e){const t=e.isSourceOfTruthAdapter?e.targets[0]:e.targets.find(o=>!o.sourceOfTruth);if(!t)throw new Error("adapter-tester config has no non-sourceOfTruth target to run the golden check against.");const r=e.excludeTitlePrefixes??Q,s=e.excludeStoryIds??[],n=e.storyThresholds??{},d=e.goldenDir,h=e.goldenMode,c=await Y(t,r,s),i=x(d),u=e.isSourceOfTruthAdapter||!e.sourceOfTruthGolden?null:await z(e.sourceOfTruthGolden);S.test.describe(`${t.name} — Golden Image Visual Regression`,()=>{for(const o of c)S.test(`Golden regression for: ${o.title} - ${o.name} (${o.id})`,async({browser:N},y)=>{const p=await N.newPage();await p.setViewportSize({width:800,height:600}),await p.goto(`${t.url}/iframe.html?id=${o.id}&viewMode=story`,{waitUntil:"networkidle"}),await p.waitForSelector("#storybook-root"),await p.waitForTimeout(300);const O=await p.screenshot(),v=w(d,o.id),l=i[o.id];if(h!=="check"||!l||!a.existsSync(v))W(d,o.id,O),i[o.id]=l!=null&&l.sourceOfTruthCreatedAt?{createdAt:new Date().toISOString(),sourceOfTruthCreatedAt:l.sourceOfTruthCreatedAt}:{createdAt:new Date().toISOString()},l||y.annotations.push({type:"golden-created",description:`No golden existed yet for "${o.id}" — captured one from this run.`});else{const f=a.readFileSync(v),g=A(O,f);await y.attach("Live vs Golden Diff",{body:`${g} mismatched pixels`,contentType:"text/plain"});const $=Z(o.id,n,e.diffThresholdPixels);S.expect.soft(g,`"${o.id}" has drifted from its own golden image`).toBeLessThan($)}const j=i[o.id];if(u){const f=u.manifest[o.id],g=f?await u.readImage(o.id):null;if(f&&g)if(h==="approve-divergence")i[o.id]={...j,sourceOfTruthCreatedAt:f.createdAt};else{const $=a.readFileSync(v),C=A($,g),P=j.sourceOfTruthCreatedAt;C===0||P!==void 0&&P>=f.createdAt||y.annotations.push({type:"source-of-truth-divergence",description:`"${o.id}" differs from the source of truth's golden and hasn't been reviewed — run with --approve-divergence if this is intentional, or fix the adapter styling.`})}}J(d,i)})})}exports.runVisualRegression=ee;
3
+ ${n}`)}function j(e){return P.join(e,"manifest.json")}function U(e){return P.join(e,"manifest.json.lock")}function k(e,t){return P.join(e,`${t}.png`)}function x(e){const t=j(e);if(!i.existsSync(t))return{};const n=JSON.parse(i.readFileSync(t,"utf8"));return N(n,t),n}function se(e,t){const n=j(e);N(t,n);const r={};for(const a of Object.keys(t).sort())r[a]=t[a];i.mkdirSync(e,{recursive:!0});const o=`${n}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;i.writeFileSync(o,JSON.stringify(r,null,2)+`
4
+ `),i.renameSync(o,n)}const oe=25,ie=15e3;function ae(e){return new Promise(t=>setTimeout(t,e))}async function ce(e){i.mkdirSync(e,{recursive:!0});const t=U(e),n=Date.now()+ie;for(;;)try{i.closeSync(i.openSync(t,"wx"));return}catch(r){if(r.code!=="EEXIST")throw r;if(Date.now()>=n)throw new Error(`Timed out waiting for the manifest lock at "${t}" — delete it if a previous run crashed while holding it.`);await ae(oe)}}function de(e){i.rmSync(U(e),{force:!0})}async function K(e,t,n){await ce(e);try{const r=x(e),o=n(r[t]);return o===void 0?delete r[t]:r[t]=o,se(e,r),o}finally{de(e)}}function ue(e,t,n){i.mkdirSync(e,{recursive:!0}),i.writeFileSync(k(e,t),n)}async function he(e,t){const n=x(e),r=Object.keys(n).filter(o=>!t.has(o));for(const o of r){const a=k(e,o);i.existsSync(a)&&i.unlinkSync(a),await K(e,o,()=>{})}return r}const le="borderux/recursica";async function fe(e,t){var a,u;const n=await fetch(`https://registry.npmjs.org/${e}`);if(!n.ok)throw new Error(`Could not reach npm registry for ${e}: ${n.statusText}`);const r=await n.json(),o=((a=r["dist-tags"])==null?void 0:a[t])??((u=r.versions)!=null&&u[t]?t:void 0);if(!o)throw new Error(`${e} has no version or dist-tag "${t}" on the npm registry.`);return o}function pe(e){return`packages/${e.split("/").pop()}`}async function ge(e){if(e.type==="local")return i.existsSync(j(e.dir))?{manifest:x(e.dir),async readImage(h){const d=k(e.dir,h);return i.existsSync(d)?i.readFileSync(d):null}}:(console.warn(`No golden baseline found yet at ${e.dir} — source-of-truth divergence check skipped for this run.`),null);let t;try{t=await fe(e.packageName,e.versionSpec)}catch(c){return console.warn(`Could not resolve ${e.packageName}@${e.versionSpec} — source-of-truth divergence check skipped for this run.`,c),null}const n=P.join(e.cacheDir,t),r=`${e.packageName}@${t}`,o=`https://raw.githubusercontent.com/${le}/${r}/${pe(e.packageName)}/test/golden`;let a;const u=j(n);if(i.existsSync(u))a=x(n);else{let c;try{c=await fetch(`${o}/manifest.json`)}catch(p){return console.warn(`Could not reach GitHub to fetch ${r}'s golden baseline — source-of-truth divergence check skipped for this run.`,p),null}if(!c.ok)return console.warn(`No golden baseline published for ${r} — source-of-truth divergence check skipped for this run.`),null;const h=await c.text(),d=JSON.parse(h);N(d,`${o}/manifest.json`),i.mkdirSync(n,{recursive:!0}),i.writeFileSync(u,h),a=d}return{manifest:a,async readImage(c){const h=k(n,c);if(i.existsSync(h))return i.readFileSync(h);const d=await fetch(`${o}/${c}.png`);if(!d.ok)return null;const p=Buffer.from(await d.arrayBuffer());return i.mkdirSync(n,{recursive:!0}),i.writeFileSync(h,p),p}}}const me=["Theme","Tokens","Introduction"];function I(e,t){return e===t||e.startsWith(t)}async function we(e,t){let n;try{const r=await fetch(`${e.url}/index.json`);if(!r.ok)throw new Error(`Failed to fetch Storybook index: ${r.statusText}`);const a=(await r.json()).entries||{};n=Object.values(a).filter(u=>u.type==="story"&&!t.some(c=>u.title===c||u.title.startsWith(`${c}/`))),n.sort((u,c)=>u.id.localeCompare(c.id))}catch(r){throw console.error("Failed to load Storybook index from",`${e.url}/index.json`,r),new Error(`Storybook target "${e.name}" is not responsive or index.json is missing. Please ensure its Storybook is running.`)}return n}function D(e,t,n){let r;for(const o of Object.keys(t))I(e,o)&&(!r||o.length>r.length)&&(r=o);return r!==void 0?t[r]:n}async function ye(e){const t=e.isSourceOfTruthAdapter?e.targets[0]:e.targets.find(s=>!s.sourceOfTruth);if(!t)throw new Error("adapter-tester config has no non-sourceOfTruth target to run the golden check against.");const n=e.excludeTitlePrefixes??me,r=e.stories??{},o=Object.keys(r).filter(s=>r[s].exclude),a=Object.fromEntries(Object.entries(r).filter(([,s])=>s.goldenThreshold!==void 0).map(([s,l])=>[s,l.goldenThreshold])),u=Object.fromEntries(Object.entries(r).filter(([,s])=>s.sourceOfTruthThreshold!==void 0).map(([s,l])=>[s,l.sourceOfTruthThreshold])),c=e.goldenDir,h=e.goldenMode,d=e.checkMode,p=await we(t,n),E=p.filter(s=>!o.some(l=>I(s.id,l)));if(h==="update-golden"){const s=await he(c,new Set(E.map(l=>l.id)));s.length>0&&console.warn(`Pruned ${s.length} orphaned golden(s) no longer in Storybook: ${s.join(", ")}`)}const g=d!=="divergence"||e.isSourceOfTruthAdapter||!e.sourceOfTruthGolden?null:await ge(e.sourceOfTruthGolden),V=new Set(p.map(s=>s.id)),T=g?Object.keys(g.manifest).filter(s=>!V.has(s)&&!o.some(l=>I(s,l))).sort():[];T.length>0&&console.error(`[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.`),console.log([`[adapter-tester] target: "${t.name}" (${t.url})`,`[adapter-tester] checkMode: "${d}" (${d==="divergence"?"this project's golden vs source-of-truth's golden":"live render vs this project's own golden"})`,`[adapter-tester] goldenMode: "${h}"`,`[adapter-tester] goldenThresholdPixels: ${e.goldenThresholdPixels}`,`[adapter-tester] sourceOfTruthThresholdPixels: ${e.sourceOfTruthThresholdPixels}`,d==="divergence"?e.isSourceOfTruthAdapter?"[adapter-tester] sourceOfTruthGolden: skipped — this is the source-of-truth adapter, nothing to diverge from":e.sourceOfTruthGolden?g?`[adapter-tester] sourceOfTruthGolden: resolved, ${Object.keys(g.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',`[adapter-tester] stories: ${E.length} to check (excluded: ${o.length}, title prefixes excluded: ${n.join(", ")||"none"})`,`[adapter-tester] story parity with source of truth: ${T.length===0?"OK":`${T.length} missing (see error above)`}`].join(`
5
+ `));const W=d==="divergence"?"Source-of-Truth Divergence Check":"Own-Drift Golden Image Check";return{ownTargetName:t.name,suiteLabel:W,stories:E,missingFromSourceOfTruth:T,checkStory:async(s,l,f)=>{const S=await l.newPage();await S.setViewportSize({width:800,height:600}),await S.goto(`${t.url}/iframe.html?id=${s.id}&viewMode=story`,{waitUntil:"networkidle"}),await S.waitForSelector("#storybook-root"),await S.waitForTimeout(300);const G=await S.screenshot(),A=k(c,s.id),m=x(c)[s.id],X=h!=="check"||!m||!i.existsSync(A);let $;if(X)ue(c,s.id,G),$=m!=null&&m.sourceOfTruthCreatedAt?{createdAt:new Date().toISOString(),sourceOfTruthCreatedAt:m.sourceOfTruthCreatedAt}:{createdAt:new Date().toISOString()},m||f.annotations.push({type:"golden-created",description:`No golden existed yet for "${s.id}" — captured one from this run.`});else if($=m,d==="own"){const w=i.readFileSync(A),{diffPixels:y,diffImage:O}=_(G,w),v=D(s.id,a,e.goldenThresholdPixels);y>=v&&(await f.attach("expected",{body:w,contentType:"image/png"}),await f.attach("actual",{body:G,contentType:"image/png"}),O&&await f.attach("diff",{body:O,contentType:"image/png"})),L.expect.soft(y,`"${s.id}" has drifted from its own golden image (${y} mismatched pixels, threshold ${v})`).toBeLessThan(v)}if(g){const w=g.manifest[s.id],y=w?await g.readImage(s.id):null;if(w&&y)if(h==="approve-divergence")$={...$,sourceOfTruthCreatedAt:w.createdAt};else{const O=i.readFileSync(A),{diffPixels:v,diffImage:F}=_(O,y),M=D(s.id,u,e.sourceOfTruthThresholdPixels),C=$.sourceOfTruthCreatedAt,B=v<M||C!==void 0&&C>=w.createdAt;B||(await f.attach("expected",{body:y,contentType:"image/png"}),await f.attach("actual",{body:O,contentType:"image/png"}),F&&await f.attach("diff",{body:F,contentType:"image/png"}),f.annotations.push({type:"source-of-truth-divergence",description:`"${s.id}" differs from the source of truth's golden by ${v} mismatched pixels (threshold ${M}) and hasn't been reviewed — run with --approve-divergence if this is intentional, or fix the adapter styling.`})),L.expect.soft(B,`"${s.id}" differs from the source of truth's golden by ${v} mismatched pixels (threshold ${M}) and hasn't been reviewed — run with --approve-divergence if this is intentional, or fix the adapter styling.`).toBe(!0)}}await K(c,s.id,()=>$)}}}exports.resolveVisualRegressionPlan=ye;
5
6
  //# sourceMappingURL=testing.cjs.map