@recursica/adapter-tester 2.0.0 → 2.0.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/dist/cli.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- "use strict";const Lt=require("node:child_process"),ge=require("node:fs"),se=require("node:path"),Kt=require("node:url"),Tn=require("node:net"),jn=require("express"),In=require("http-proxy-middleware"),qn=require("./mantineSourceOfTruth-DCoJ2dU-.cjs");var Re=typeof document<"u"?document.currentScript:null;const Yt=se.dirname(Kt.fileURLToPath(typeof document>"u"?require("url").pathToFileURL(__filename).href:Re&&Re.tagName.toUpperCase()==="SCRIPT"&&Re.src||new URL("cli.cjs",document.baseURI).href));function zt(e){return new Promise(t=>{const m=new Tn.Socket;m.setTimeout(200),m.once("connect",()=>{m.destroy(),t(!0)}),m.once("timeout",()=>{m.destroy(),t(!1)}),m.once("error",()=>{m.destroy(),t(!1)}),m.connect(e,"127.0.0.1")})}async function Zt(e,t=6e4){const m=Date.now();for(;Date.now()-m<t;){if(await zt(e))return!0;await new Promise(c=>setTimeout(c,500))}return!1}function Qt(e){return Number(new URL(e).port)}function Xt(e,t){var c,b;console.log(`[Dev Launcher] Port ${t.port} is inactive. Launching Storybook for ${e}...`);const m=Lt.spawn(t.command,{cwd:t.cwd,stdio:"pipe",shell:!0});return(c=m.stdout)==null||c.on("data",s=>{for(const l of s.toString().split(`
2
+ "use strict";const Lt=require("node:child_process"),ge=require("node:fs"),se=require("node:path"),Kt=require("node:url"),Tn=require("node:net"),jn=require("express"),In=require("http-proxy-middleware"),qn=require("./mantineSourceOfTruth-D6tezfE1.cjs");var Re=typeof document<"u"?document.currentScript:null;const Yt=se.dirname(Kt.fileURLToPath(typeof document>"u"?require("url").pathToFileURL(__filename).href:Re&&Re.tagName.toUpperCase()==="SCRIPT"&&Re.src||new URL("cli.cjs",document.baseURI).href));function zt(e){return new Promise(t=>{const m=new Tn.Socket;m.setTimeout(200),m.once("connect",()=>{m.destroy(),t(!0)}),m.once("timeout",()=>{m.destroy(),t(!1)}),m.once("error",()=>{m.destroy(),t(!1)}),m.connect(e,"127.0.0.1")})}async function Zt(e,t=6e4){const m=Date.now();for(;Date.now()-m<t;){if(await zt(e))return!0;await new Promise(c=>setTimeout(c,500))}return!1}function Qt(e){return Number(new URL(e).port)}function Xt(e,t){var c,b;console.log(`[Dev Launcher] Port ${t.port} is inactive. Launching Storybook for ${e}...`);const m=Lt.spawn(t.command,{cwd:t.cwd,stdio:"pipe",shell:!0});return(c=m.stdout)==null||c.on("data",s=>{for(const l of s.toString().split(`
3
3
  `))l.trim()&&console.log(`[${e} SB] ${l.trim()}`)}),(b=m.stderr)==null||b.on("data",s=>{for(const l of s.toString().split(`
4
4
  `))l.trim()&&console.error(`[${e} SB ERROR] ${l.trim()}`)}),m.on("error",s=>{console.error(`[${e} SB ERROR] Failed to start process:`,s)}),m}function Cn(e){const t=process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open";console.log(`[Dev Launcher] Auto-launching browser: ${e}`),Lt.spawn(t,[e],{shell:process.platform==="win32"}).on("error",m=>{console.error("[Dev Launcher] Failed to auto-launch browser:",m)})}function Dn(e,t,m={}){const[c,b]=e.targets,[s,l]=t;if(!(c!=null&&c.sourceOfTruth)||!b||b.sourceOfTruth||!s||!l)throw new Error("adapter-tester dev mode requires exactly two targets: [sourceOfTruth, target] — check adapter-tester.config.json.");const u=m.port??6010,h=Qt(c.url),$=Qt(b.url),_=jn(),P=se.join(Yt,"../public"),v=se.join(Yt,"../report-header.txt");_.get("/",(a,i,r)=>{if(a.query.path){r();return}const o=ge.readFileSync(se.join(P,"index.html"),"utf8").replace("<head>",`<head>
5
5
  <script>window.__ADAPTER_TESTER__ = ${JSON.stringify({sourceOfTruthName:c.name,targetName:b.name,targetPort:$})};<\/script>`);i.send(o)}),_.get("/report-header.txt",(a,i)=>{try{i.type("text/plain").send(ge.readFileSync(v,"utf8"))}catch{i.status(500).send("Error loading report-header.txt")}}),_.use("/",In.createProxyMiddleware({target:`http://localhost:${h}`,changeOrigin:!0,ws:!0}));const y=[];let g=!1;const w=()=>{if(!g){g=!0,console.log(`
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import { fileURLToPath as wn, pathToFileURL as Dn } from "node:url";
6
6
  import An from "node:net";
7
7
  import Mn from "express";
8
8
  import { createProxyMiddleware as zn } from "http-proxy-middleware";
9
- import { m as Vn } from "./mantineSourceOfTruth-BiSfXKff.js";
9
+ import { m as Vn } from "./mantineSourceOfTruth-BtZhbJA_.js";
10
10
  const Bt = $n(wn(import.meta.url));
11
11
  function Mt(e) {
12
12
  return new Promise((t) => {
@@ -1 +1 @@
1
- {"version":3,"file":"mantineSourceOfTruth.d.ts","sourceRoot":"","sources":["../../src/harness/mantineSourceOfTruth.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,kCAAkC;IACjD;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,iFAAiF;IACjF,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,mBAAmB,EAAE,OAAO,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB;AAgHD,8EAA8E;AAC9E,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,kCAAkC,GAC1C,MAAM,CA0BR;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,kCAAkC,GAC1C,sBAAsB,CASxB"}
1
+ {"version":3,"file":"mantineSourceOfTruth.d.ts","sourceRoot":"","sources":["../../src/harness/mantineSourceOfTruth.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,kCAAkC;IACjD;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,iFAAiF;IACjF,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,mBAAmB,EAAE,OAAO,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB;AAqID,8EAA8E;AAC9E,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,kCAAkC,GAC1C,MAAM,CA0BR;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,kCAAkC,GAC1C,sBAAsB,CASxB"}
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./config-B0Eop8Az.cjs"),r=require("./mantineSourceOfTruth-DCoJ2dU-.cjs");exports.defineAdapterTesterConfig=e.defineAdapterTesterConfig;exports.getSourceOfTruth=e.getSourceOfTruth;exports.mantineSourceOfTruthWebServer=r.mantineSourceOfTruthWebServer;exports.scaffoldMantineSourceOfTruthHarness=r.scaffoldMantineSourceOfTruthHarness;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./config-B0Eop8Az.cjs"),r=require("./mantineSourceOfTruth-D6tezfE1.cjs");exports.defineAdapterTesterConfig=e.defineAdapterTesterConfig;exports.getSourceOfTruth=e.getSourceOfTruth;exports.mantineSourceOfTruthWebServer=r.mantineSourceOfTruthWebServer;exports.scaffoldMantineSourceOfTruthHarness=r.scaffoldMantineSourceOfTruthHarness;
2
2
  //# sourceMappingURL=index.cjs.map
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { d as t, g as a } from "./config-CDxTeSAY.js";
2
- import { m as o, s } from "./mantineSourceOfTruth-BiSfXKff.js";
2
+ import { m as o, s } from "./mantineSourceOfTruth-BtZhbJA_.js";
3
3
  export {
4
4
  t as defineAdapterTesterConfig,
5
5
  a as getSourceOfTruth,
@@ -1,13 +1,13 @@
1
- import { mkdirSync as m, writeFileSync as t } from "node:fs";
2
- import { join as o } from "node:path";
3
- const i = "^8.0.0", s = "^10.3.3", a = "^19.0.0", p = {
1
+ import { mkdirSync as p, writeFileSync as o } from "node:fs";
2
+ import { join as t } from "node:path";
3
+ const a = "^8.0.0", s = "^10.3.3", i = "^19.0.0", l = {
4
4
  "@storybook/addon-docs": s,
5
5
  "@storybook/addon-a11y": s,
6
6
  "storybook-dark-mode": "^5.0.0"
7
- }, u = {
7
+ }, m = {
8
8
  "react-markdown": "^10.1.0"
9
9
  };
10
- function l(e) {
10
+ function u(e) {
11
11
  return {
12
12
  name: "adapter-tester-mantine-source-of-truth-harness",
13
13
  private: !0,
@@ -20,14 +20,14 @@ function l(e) {
20
20
  "@recursica/storybook-template": e.storybookTemplateVersion,
21
21
  "@recursica/official-release": "latest",
22
22
  "@recursica/adapter-common": "latest",
23
- "@mantine/core": i,
24
- "@mantine/dates": i,
25
- react: a,
26
- "react-dom": a,
23
+ "@mantine/core": a,
24
+ "@mantine/dates": a,
25
+ react: i,
26
+ "react-dom": i,
27
27
  storybook: s,
28
28
  "@storybook/react-vite": s,
29
- ...p,
30
- ...u
29
+ ...l,
30
+ ...m
31
31
  }
32
32
  };
33
33
  }
@@ -53,6 +53,7 @@ export default config;
53
53
  `, y = `import type { Preview } from "@storybook/react-vite";
54
54
  import { createPreviewConfig } from "@recursica/storybook-template/preview";
55
55
  import { MantineProvider } from "@mantine/core";
56
+ import { Layer } from "@recursica/adapter-common";
56
57
  import "@mantine/core/styles.css";
57
58
  import "@mantine/dates/styles.css";
58
59
  import "@recursica/adapter-common/style.css";
@@ -68,31 +69,51 @@ const basePreview = createPreviewConfig({
68
69
  recursicaUIKitJsonPath: recursicaUIKit,
69
70
  });
70
71
 
72
+ // Mirrors mantine-adapter's own .storybook/preview.tsx decorator (every story defaults to
73
+ // withLayer: true, layer: 0, wrapped with 48px padding) — every real adapter's own preview.tsx
74
+ // applies this same wrapping, so a target adapter's story renders inside the same Layer
75
+ // chrome/padding the source-of-truth side does. Without this, target screenshots come out
76
+ // dramatically smaller/differently-positioned than the source of truth's (no Layer padding,
77
+ // background, or border-radius at all), which alone can blow past the pixel-diff threshold
78
+ // regardless of whether the actual Recursica tokens match — a false positive, not a real
79
+ // component bug. ColorSchemeWrapper (mantine-adapter's dark-mode-toggle sync helper) is
80
+ // intentionally not replicated — it only matters for the interactive dev-mode UI, not automated
81
+ // screenshot diffing, which always runs in a single theme.
71
82
  const preview: Preview = {
72
83
  ...basePreview,
73
84
  decorators: [
74
- (Story) => (
75
- <MantineProvider>
76
- <Story />
77
- </MantineProvider>
78
- ),
85
+ (Story, context) => {
86
+ const { withLayer = true, layer = 0 } = context.args;
87
+ const content = <Story />;
88
+ return (
89
+ <MantineProvider>
90
+ {withLayer ? (
91
+ <Layer layer={layer as 0 | 1 | 2 | 3} style={{ padding: "48px" }}>
92
+ {content}
93
+ </Layer>
94
+ ) : (
95
+ content
96
+ )}
97
+ </MantineProvider>
98
+ );
99
+ },
79
100
  ...(basePreview.decorators || []),
80
101
  ],
81
102
  };
82
103
 
83
104
  export default preview;
84
105
  `;
85
- function k(e) {
106
+ function h(e) {
86
107
  const {
87
108
  dir: r,
88
109
  port: n,
89
110
  mantineAdapterVersion: c = "latest",
90
111
  storybookTemplateVersion: d = "latest"
91
112
  } = e;
92
- return m(o(r, ".storybook"), { recursive: !0 }), t(
93
- o(r, "package.json"),
113
+ return p(t(r, ".storybook"), { recursive: !0 }), o(
114
+ t(r, "package.json"),
94
115
  JSON.stringify(
95
- l({
116
+ u({
96
117
  mantineAdapterVersion: c,
97
118
  storybookTemplateVersion: d,
98
119
  port: n
@@ -101,11 +122,11 @@ function k(e) {
101
122
  2
102
123
  ) + `
103
124
  `
104
- ), t(o(r, ".storybook/main.ts"), f), t(o(r, ".storybook/preview.tsx"), y), t(o(r, ".gitignore"), `node_modules
125
+ ), o(t(r, ".storybook/main.ts"), f), o(t(r, ".storybook/preview.tsx"), y), o(t(r, ".gitignore"), `node_modules
105
126
  `), r;
106
127
  }
107
- function g(e) {
108
- const r = k(e);
128
+ function k(e) {
129
+ const r = h(e);
109
130
  return {
110
131
  command: "npm install --no-audit --no-fund && npm run storybook",
111
132
  port: e.port,
@@ -115,7 +136,7 @@ function g(e) {
115
136
  };
116
137
  }
117
138
  export {
118
- g as m,
119
- k as s
139
+ k as m,
140
+ h as s
120
141
  };
121
- //# sourceMappingURL=mantineSourceOfTruth-BiSfXKff.js.map
142
+ //# sourceMappingURL=mantineSourceOfTruth-BtZhbJA_.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mantineSourceOfTruth-BiSfXKff.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 /** Port the harness's Storybook dev server boots on. */\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 port: number;\n cwd: string;\n reuseExistingServer: boolean;\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 port: number;\n}) {\n return {\n name: \"adapter-tester-mantine-source-of-truth-harness\",\n private: true,\n type: \"module\",\n scripts: {\n storybook: `storybook dev -p ${options.port}`,\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 \"@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\nconst preview: Preview = {\n ...basePreview,\n decorators: [\n (Story) => (\n <MantineProvider>\n <Story />\n </MantineProvider>\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 port,\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 port,\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 return {\n command: \"npm install --no-audit --no-fund && npm run storybook\",\n port: options.port,\n cwd: dir,\n reuseExistingServer: !process.env.CI,\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","port","mantineAdapterVersion","storybookTemplateVersion","mkdirSync","join","writeFileSync","mantineSourceOfTruthWebServer"],"mappings":";;AA2CA,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,GAIzB;AACD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,MAAM;AAAA,IACN,SAAS;AAAA,MACP,WAAW,oBAAoBA,EAAQ,IAAI;AAAA,IAAA;AAAA,IAE7C,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;AAkCb,SAASC,EACdH,GACQ;AACR,QAAM;AAAA,IACJ,KAAAI;AAAA,IACA,MAAAC;AAAA,IACA,uBAAAC,IAAwB;AAAA,IACxB,0BAAAC,IAA2B;AAAA,EAAA,IACzBP;AAEJ,SAAAQ,EAAUC,EAAKL,GAAK,YAAY,GAAG,EAAE,WAAW,IAAM,GACtDM;AAAA,IACED,EAAKL,GAAK,cAAc;AAAA,IACxB,KAAK;AAAA,MACHL,EAAmB;AAAA,QACjB,uBAAAO;AAAA,QACA,0BAAAC;AAAA,QACA,MAAAF;AAAA,MAAA,CACD;AAAA,MACD;AAAA,MACA;AAAA,IAAA,IACE;AAAA;AAAA,EAAA,GAENK,EAAcD,EAAKL,GAAK,oBAAoB,GAAGH,CAAO,GACtDS,EAAcD,EAAKL,GAAK,wBAAwB,GAAGF,CAAW,GAC9DQ,EAAcD,EAAKL,GAAK,YAAY,GAAG;AAAA,CAAgB,GAEhDA;AACT;AAMO,SAASO,EACdX,GACwB;AACxB,QAAMI,IAAMD,EAAoCH,CAAO;AACvD,SAAO;AAAA,IACL,SAAS;AAAA,IACT,MAAMA,EAAQ;AAAA,IACd,KAAKI;AAAA,IACL,qBAAqB,CAAC,QAAQ,IAAI;AAAA,IAClC,SAAS,MAAM;AAAA,EAAA;AAEnB;"}
1
+ {"version":3,"file":"mantineSourceOfTruth-BtZhbJA_.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 /** Port the harness's Storybook dev server boots on. */\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 port: number;\n cwd: string;\n reuseExistingServer: boolean;\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 port: number;\n}) {\n return {\n name: \"adapter-tester-mantine-source-of-truth-harness\",\n private: true,\n type: \"module\",\n scripts: {\n storybook: `storybook dev -p ${options.port}`,\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 port,\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 port,\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 return {\n command: \"npm install --no-audit --no-fund && npm run storybook\",\n port: options.port,\n cwd: dir,\n reuseExistingServer: !process.env.CI,\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","port","mantineAdapterVersion","storybookTemplateVersion","mkdirSync","join","writeFileSync","mantineSourceOfTruthWebServer"],"mappings":";;AA2CA,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,GAIzB;AACD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,MAAM;AAAA,IACN,SAAS;AAAA,MACP,WAAW,oBAAoBA,EAAQ,IAAI;AAAA,IAAA;AAAA,IAE7C,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,MAAAC;AAAA,IACA,uBAAAC,IAAwB;AAAA,IACxB,0BAAAC,IAA2B;AAAA,EAAA,IACzBP;AAEJ,SAAAQ,EAAUC,EAAKL,GAAK,YAAY,GAAG,EAAE,WAAW,IAAM,GACtDM;AAAA,IACED,EAAKL,GAAK,cAAc;AAAA,IACxB,KAAK;AAAA,MACHL,EAAmB;AAAA,QACjB,uBAAAO;AAAA,QACA,0BAAAC;AAAA,QACA,MAAAF;AAAA,MAAA,CACD;AAAA,MACD;AAAA,MACA;AAAA,IAAA,IACE;AAAA;AAAA,EAAA,GAENK,EAAcD,EAAKL,GAAK,oBAAoB,GAAGH,CAAO,GACtDS,EAAcD,EAAKL,GAAK,wBAAwB,GAAGF,CAAW,GAC9DQ,EAAcD,EAAKL,GAAK,YAAY,GAAG;AAAA,CAAgB,GAEhDA;AACT;AAMO,SAASO,EACdX,GACwB;AACxB,QAAMI,IAAMD,EAAoCH,CAAO;AACvD,SAAO;AAAA,IACL,SAAS;AAAA,IACT,MAAMA,EAAQ;AAAA,IACd,KAAKI;AAAA,IACL,qBAAqB,CAAC,QAAQ,IAAI;AAAA,IAClC,SAAS,MAAM;AAAA,EAAA;AAEnB;"}
@@ -0,0 +1,75 @@
1
+ "use strict";const t=require("node:fs"),o=require("node:path"),a="^8.0.0",s="^10.3.3",i="^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":a,"@mantine/dates":a,react:i,"react-dom":i,storybook:s,"@storybook/react-vite":s,...p,...u}}}const f=`import { createMainConfig } from "@recursica/storybook-template/main";
2
+
3
+ const config = createMainConfig({
4
+ stories: [
5
+ "../node_modules/@recursica/mantine-adapter/src/**/*.stories.@(js|jsx|mjs|ts|tsx)",
6
+ ],
7
+ enableCORS: true,
8
+ });
9
+
10
+ // react-docgen-typescript can't resolve a TS project for a config file living
11
+ // in .storybook/ when the component source it's docgen'ing lives three
12
+ // directories down inside node_modules — it throws "Cannot read properties
13
+ // of undefined (reading 'fileExists')", which surfaces as a plain 404 on
14
+ // preview.tsx. Docgen only powers Storybook's Controls/Docs tables, which
15
+ // this harness never renders, so disabling it is a safe workaround (see
16
+ // PROPOSAL-installed-package-harness.md, gap 3).
17
+ config.typescript = { ...config.typescript, reactDocgen: false };
18
+
19
+ export default config;
20
+ `,y=`import type { Preview } from "@storybook/react-vite";
21
+ import { createPreviewConfig } from "@recursica/storybook-template/preview";
22
+ import { MantineProvider } from "@mantine/core";
23
+ import { Layer } from "@recursica/adapter-common";
24
+ import "@mantine/core/styles.css";
25
+ import "@mantine/dates/styles.css";
26
+ import "@recursica/adapter-common/style.css";
27
+ import "@recursica/official-release/recursica_variables_scoped.css";
28
+ import recursicaTokens from "@recursica/official-release/recursica_tokens.json";
29
+ import recursicaBrand from "@recursica/official-release/recursica_brand.json";
30
+ import recursicaUIKit from "@recursica/official-release/recursica_ui-kit.json";
31
+
32
+ const basePreview = createPreviewConfig({
33
+ defaultTheme: "light",
34
+ recursicaTokensJsonPath: recursicaTokens,
35
+ recursicaBrandJsonPath: recursicaBrand,
36
+ recursicaUIKitJsonPath: recursicaUIKit,
37
+ });
38
+
39
+ // Mirrors mantine-adapter's own .storybook/preview.tsx decorator (every story defaults to
40
+ // withLayer: true, layer: 0, wrapped with 48px padding) — every real adapter's own preview.tsx
41
+ // applies this same wrapping, so a target adapter's story renders inside the same Layer
42
+ // chrome/padding the source-of-truth side does. Without this, target screenshots come out
43
+ // dramatically smaller/differently-positioned than the source of truth's (no Layer padding,
44
+ // background, or border-radius at all), which alone can blow past the pixel-diff threshold
45
+ // regardless of whether the actual Recursica tokens match — a false positive, not a real
46
+ // component bug. ColorSchemeWrapper (mantine-adapter's dark-mode-toggle sync helper) is
47
+ // intentionally not replicated — it only matters for the interactive dev-mode UI, not automated
48
+ // screenshot diffing, which always runs in a single theme.
49
+ const preview: Preview = {
50
+ ...basePreview,
51
+ decorators: [
52
+ (Story, context) => {
53
+ const { withLayer = true, layer = 0 } = context.args;
54
+ const content = <Story />;
55
+ return (
56
+ <MantineProvider>
57
+ {withLayer ? (
58
+ <Layer layer={layer as 0 | 1 | 2 | 3} style={{ padding: "48px" }}>
59
+ {content}
60
+ </Layer>
61
+ ) : (
62
+ content
63
+ )}
64
+ </MantineProvider>
65
+ );
66
+ },
67
+ ...(basePreview.decorators || []),
68
+ ],
69
+ };
70
+
71
+ export default preview;
72
+ `;function n(e){const{dir:r,port:c,mantineAdapterVersion:d="latest",storybookTemplateVersion:l="latest"}=e;return t.mkdirSync(o.join(r,".storybook"),{recursive:!0}),t.writeFileSync(o.join(r,"package.json"),JSON.stringify(m({mantineAdapterVersion:d,storybookTemplateVersion:l,port:c}),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=n(e);return{command:"npm install --no-audit --no-fund && npm run storybook",port:e.port,cwd:r,reuseExistingServer:!process.env.CI,timeout:180*1e3}}exports.mantineSourceOfTruthWebServer=h;exports.scaffoldMantineSourceOfTruthHarness=n;
75
+ //# sourceMappingURL=mantineSourceOfTruth-D6tezfE1.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"mantineSourceOfTruth-DCoJ2dU-.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 /** Port the harness's Storybook dev server boots on. */\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 port: number;\n cwd: string;\n reuseExistingServer: boolean;\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 port: number;\n}) {\n return {\n name: \"adapter-tester-mantine-source-of-truth-harness\",\n private: true,\n type: \"module\",\n scripts: {\n storybook: `storybook dev -p ${options.port}`,\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 \"@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\nconst preview: Preview = {\n ...basePreview,\n decorators: [\n (Story) => (\n <MantineProvider>\n <Story />\n </MantineProvider>\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 port,\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 port,\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 return {\n command: \"npm install --no-audit --no-fund && npm run storybook\",\n port: options.port,\n cwd: dir,\n reuseExistingServer: !process.env.CI,\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","port","mantineAdapterVersion","storybookTemplateVersion","mkdirSync","join","writeFileSync","mantineSourceOfTruthWebServer"],"mappings":"+DA2CMA,EAAqB,SACrBC,EAAkB,UAClBC,EAAc,UAOdC,EAA6B,CACjC,wBAAyBF,EACzB,wBAAyBA,EACzB,sBAAuB,QACzB,EAOMG,EAA0B,CAC9B,iBAAkB,SACpB,EAEA,SAASC,EAAmBC,EAIzB,CACD,MAAO,CACL,KAAM,iDACN,QAAS,GACT,KAAM,SACN,QAAS,CACP,UAAW,oBAAoBA,EAAQ,IAAI,EAAA,EAE7C,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,EAkCb,SAASC,EACdH,EACQ,CACR,KAAM,CACJ,IAAAI,EACA,KAAAC,EACA,sBAAAC,EAAwB,SACxB,yBAAAC,EAA2B,QAAA,EACzBP,EAEJQ,OAAAA,EAAAA,UAAUC,EAAAA,KAAKL,EAAK,YAAY,EAAG,CAAE,UAAW,GAAM,EACtDM,EAAAA,cACED,EAAAA,KAAKL,EAAK,cAAc,EACxB,KAAK,UACHL,EAAmB,CACjB,sBAAAO,EACA,yBAAAC,EACA,KAAAF,CAAA,CACD,EACD,KACA,CAAA,EACE;AAAA,CAAA,EAENK,EAAAA,cAAcD,EAAAA,KAAKL,EAAK,oBAAoB,EAAGH,CAAO,EACtDS,EAAAA,cAAcD,EAAAA,KAAKL,EAAK,wBAAwB,EAAGF,CAAW,EAC9DQ,EAAAA,cAAcD,EAAAA,KAAKL,EAAK,YAAY,EAAG;AAAA,CAAgB,EAEhDA,CACT,CAMO,SAASO,EACdX,EACwB,CACxB,MAAMI,EAAMD,EAAoCH,CAAO,EACvD,MAAO,CACL,QAAS,wDACT,KAAMA,EAAQ,KACd,IAAKI,EACL,oBAAqB,CAAC,QAAQ,IAAI,GAClC,QAAS,IAAM,GAAA,CAEnB"}
1
+ {"version":3,"file":"mantineSourceOfTruth-D6tezfE1.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 /** Port the harness's Storybook dev server boots on. */\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 port: number;\n cwd: string;\n reuseExistingServer: boolean;\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 port: number;\n}) {\n return {\n name: \"adapter-tester-mantine-source-of-truth-harness\",\n private: true,\n type: \"module\",\n scripts: {\n storybook: `storybook dev -p ${options.port}`,\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 port,\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 port,\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 return {\n command: \"npm install --no-audit --no-fund && npm run storybook\",\n port: options.port,\n cwd: dir,\n reuseExistingServer: !process.env.CI,\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","port","mantineAdapterVersion","storybookTemplateVersion","mkdirSync","join","writeFileSync","mantineSourceOfTruthWebServer"],"mappings":"+DA2CMA,EAAqB,SACrBC,EAAkB,UAClBC,EAAc,UAOdC,EAA6B,CACjC,wBAAyBF,EACzB,wBAAyBA,EACzB,sBAAuB,QACzB,EAOMG,EAA0B,CAC9B,iBAAkB,SACpB,EAEA,SAASC,EAAmBC,EAIzB,CACD,MAAO,CACL,KAAM,iDACN,QAAS,GACT,KAAM,SACN,QAAS,CACP,UAAW,oBAAoBA,EAAQ,IAAI,EAAA,EAE7C,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,KAAAC,EACA,sBAAAC,EAAwB,SACxB,yBAAAC,EAA2B,QAAA,EACzBP,EAEJQ,OAAAA,EAAAA,UAAUC,EAAAA,KAAKL,EAAK,YAAY,EAAG,CAAE,UAAW,GAAM,EACtDM,EAAAA,cACED,EAAAA,KAAKL,EAAK,cAAc,EACxB,KAAK,UACHL,EAAmB,CACjB,sBAAAO,EACA,yBAAAC,EACA,KAAAF,CAAA,CACD,EACD,KACA,CAAA,EACE;AAAA,CAAA,EAENK,EAAAA,cAAcD,EAAAA,KAAKL,EAAK,oBAAoB,EAAGH,CAAO,EACtDS,EAAAA,cAAcD,EAAAA,KAAKL,EAAK,wBAAwB,EAAGF,CAAW,EAC9DQ,EAAAA,cAAcD,EAAAA,KAAKL,EAAK,YAAY,EAAG;AAAA,CAAgB,EAEhDA,CACT,CAMO,SAASO,EACdX,EACwB,CACxB,MAAMI,EAAMD,EAAoCH,CAAO,EACvD,MAAO,CACL,QAAS,wDACT,KAAMA,EAAQ,KACd,IAAKI,EACL,oBAAqB,CAAC,QAAQ,IAAI,GAClC,QAAS,IAAM,GAAA,CAEnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"runVisualRegression.d.ts","sourceRoot":"","sources":["../../src/testing/runVisualRegression.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAiB,mBAAmB,EAAE,MAAM,cAAc,CAAC;AA2EvE;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAiJf"}
1
+ {"version":3,"file":"runVisualRegression.d.ts","sourceRoot":"","sources":["../../src/testing/runVisualRegression.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAiB,mBAAmB,EAAE,MAAM,cAAc,CAAC;AA8GvE;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,IAAI,CAAC,CA+Jf"}
package/dist/testing.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const b=require("@playwright/test"),q=require("pixelmatch"),u=require("pngjs"),h=require("node:fs"),J=require("./config-B0Eop8Az.cjs"),_=["Theme","Tokens","Introduction"],v=["color","background-color","padding-top","padding-right","padding-bottom","padding-left","margin-top","margin-right","margin-bottom","margin-left","border-top-left-radius","border-top-right-radius","border-bottom-left-radius","border-bottom-right-radius","font-size","font-weight","display","flex-direction","gap","height"];function D(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"-")}async function G(t,r){let c;try{const i=await fetch(`${t.url}/index.json`);if(!i.ok)throw new Error(`Failed to fetch Storybook index: ${i.statusText}`);const g=(await i.json()).entries||{};c=Object.values(g).filter(a=>a.type==="story"&&!r.some(e=>a.title===e||a.title.startsWith(`${e}/`))),c.sort((a,e)=>a.id.localeCompare(e.id))}catch(i){throw console.error("Failed to load Storybook index from",`${t.url}/index.json`,i),new Error(`Storybook source of truth "${t.name}" is not responsive or index.json is missing. Please ensure its Storybook is running.`)}return c}async function R(t){const r=J.getSourceOfTruth(t),c=t.targets.filter(e=>!e.sourceOfTruth),i=t.excludeTitlePrefixes??_,P=t.relaxedThresholdStoryIds??[],g=t.relaxedThresholdPixels,a=await G(r,i);for(const e of c)b.test.describe(`${r.name} vs ${e.name} — Dynamic Visual Regression Suite`,()=>{for(const o of a)b.test(`Visual regression for: ${o.title} - ${o.name} (${o.id})`,async({browser:$},l)=>{const s=await $.newPage(),n=await $.newPage();await s.setViewportSize({width:800,height:600}),await n.setViewportSize({width:800,height:600}),await Promise.all([s.goto(`${r.url}/iframe.html?id=${o.id}&viewMode=story`,{waitUntil:"networkidle"}),n.goto(`${e.url}/iframe.html?id=${o.id}&viewMode=story`,{waitUntil:"networkidle"})]),await Promise.all([s.waitForSelector("#storybook-root"),n.waitForSelector("#storybook-root")]),await Promise.all([s.waitForTimeout(300),n.waitForTimeout(300)]);const x=p=>{function j(S){const T={tag:S.tagName.toLowerCase(),styles:{},children:[]},L=window.getComputedStyle(S);for(const w of p)T.styles[w]=L.getPropertyValue(w);for(const w of Array.from(S.children))T.children.push(j(w));return T}const M=document.querySelector("#storybook-root");return M?j(M):null},O=await s.evaluate(x,v),F=await n.evaluate(x,v);await l.attach(`${r.name} DOM Tree JSON`,{body:JSON.stringify(O,null,2),contentType:"application/json"}),await l.attach(`${e.name} DOM Tree JSON`,{body:JSON.stringify(F,null,2),contentType:"application/json"});const N=D(r.name),k=D(e.name);h.writeFileSync(`/tmp/${o.id}-${N}DOM.json`,JSON.stringify(O,null,2)),h.writeFileSync(`/tmp/${o.id}-${k}DOM.json`,JSON.stringify(F,null,2));const m=await s.screenshot(),y=await n.screenshot(),d=u.PNG.sync.read(m),E=u.PNG.sync.read(y),f=new u.PNG({width:d.width,height:d.height}),V=q(d.data,E.data,f.data,d.width,d.height,{threshold:.1});h.writeFileSync(`/tmp/${o.id}-diff.png`,u.PNG.sync.write(f)),h.writeFileSync(`/tmp/${o.id}-${N}.png`,m),h.writeFileSync(`/tmp/${o.id}-${k}.png`,y),await l.attach("Visual Diff Overlay",{body:u.PNG.sync.write(f),contentType:"image/png"}),await l.attach(`${r.name} Screenshot (Source of Truth)`,{body:m,contentType:"image/png"}),await l.attach(`${e.name} Screenshot (Target)`,{body:y,contentType:"image/png"});const C=g!==void 0&&P.some(p=>o.id.startsWith(p))?g:t.diffThresholdPixels;b.expect.soft(V).toBeLessThan(C)})})}exports.runVisualRegression=R;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const f=require("@playwright/test"),_=require("pixelmatch"),g=require("pngjs"),w=require("node:fs"),G=require("./config-B0Eop8Az.cjs"),R=["Theme","Tokens","Introduction"],D=["color","background-color","padding-top","padding-right","padding-bottom","padding-left","margin-top","margin-right","margin-bottom","margin-left","border-top-left-radius","border-top-right-radius","border-bottom-left-radius","border-bottom-right-radius","font-size","font-weight","display","flex-direction","gap","height"];function E(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"-")}async function U(t,e){let a;try{const i=await fetch(`${t.url}/index.json`);if(!i.ok)throw new Error(`Failed to fetch Storybook index: ${i.statusText}`);const s=(await i.json()).entries||{};a=Object.values(s).filter(n=>n.type==="story"&&!e.some(c=>n.title===c||n.title.startsWith(`${c}/`))),a.sort((n,c)=>n.id.localeCompare(c.id))}catch(i){throw console.error("Failed to load Storybook index from",`${t.url}/index.json`,i),new Error(`Storybook source of truth "${t.name}" is not responsive or index.json is missing. Please ensure its Storybook is running.`)}return a}async function z(t){try{const e=await fetch(`${t.url}/index.json`);if(!e.ok)return console.warn(`Could not load Storybook index from target "${t.name}" (${e.status} ${e.statusText}) — every story will be reported as skipped for this target.`),new Set;const i=(await e.json()).entries||{},y=Object.values(i).filter(s=>s.type==="story").map(s=>s.id);return new Set(y)}catch(e){return console.warn(`Could not load Storybook index from target "${t.name}" — every story will be reported as skipped for this target.`,e),new Set}}async function I(t){const e=G.getSourceOfTruth(t),a=t.targets.filter(r=>!r.sourceOfTruth),i=t.excludeTitlePrefixes??R,y=t.relaxedThresholdStoryIds??[],s=t.relaxedThresholdPixels,n=await U(e,i),c=new Map;for(const r of a)c.set(r.name,await z(r));for(const r of a){const C=c.get(r.name)??new Set;f.test.describe(`${e.name} vs ${r.name} — Dynamic Visual Regression Suite`,()=>{for(const o of n)f.test(`Visual regression for: ${o.title} - ${o.name} (${o.id})`,async({browser:P},u)=>{f.test.skip(!C.has(o.id),`${r.name} has no story with id "${o.id}" yet — not yet implemented, not a failure.`);const d=await P.newPage(),l=await P.newPage();await d.setViewportSize({width:800,height:600}),await l.setViewportSize({width:800,height:600}),await Promise.all([d.goto(`${e.url}/iframe.html?id=${o.id}&viewMode=story`,{waitUntil:"networkidle"}),l.goto(`${r.url}/iframe.html?id=${o.id}&viewMode=story`,{waitUntil:"networkidle"})]),await Promise.all([d.waitForSelector("#storybook-root"),l.waitForSelector("#storybook-root")]),await Promise.all([d.waitForTimeout(300),l.waitForTimeout(300)]);const O=b=>{function N($){const x={tag:$.tagName.toLowerCase(),styles:{},children:[]},J=window.getComputedStyle($);for(const m of b)x.styles[m]=J.getPropertyValue(m);for(const m of Array.from($.children))x.children.push(N(m));return x}const M=document.querySelector("#storybook-root");return M?N(M):null},k=await d.evaluate(O,D),j=await l.evaluate(O,D);await u.attach(`${e.name} DOM Tree JSON`,{body:JSON.stringify(k,null,2),contentType:"application/json"}),await u.attach(`${r.name} DOM Tree JSON`,{body:JSON.stringify(j,null,2),contentType:"application/json"});const F=E(e.name),v=E(r.name);w.writeFileSync(`/tmp/${o.id}-${F}DOM.json`,JSON.stringify(k,null,2)),w.writeFileSync(`/tmp/${o.id}-${v}DOM.json`,JSON.stringify(j,null,2));const p=await d.screenshot(),S=await l.screenshot(),h=g.PNG.sync.read(p),V=g.PNG.sync.read(S),T=new g.PNG({width:h.width,height:h.height}),L=_(h.data,V.data,T.data,h.width,h.height,{threshold:.1});w.writeFileSync(`/tmp/${o.id}-diff.png`,g.PNG.sync.write(T)),w.writeFileSync(`/tmp/${o.id}-${F}.png`,p),w.writeFileSync(`/tmp/${o.id}-${v}.png`,S),await u.attach("Visual Diff Overlay",{body:g.PNG.sync.write(T),contentType:"image/png"}),await u.attach(`${e.name} Screenshot (Source of Truth)`,{body:p,contentType:"image/png"}),await u.attach(`${r.name} Screenshot (Target)`,{body:S,contentType:"image/png"});const q=s!==void 0&&y.some(b=>o.id.startsWith(b))?s:t.diffThresholdPixels;f.expect.soft(L).toBeLessThan(q)})})}}exports.runVisualRegression=I;
2
2
  //# sourceMappingURL=testing.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"testing.cjs","sources":["../src/testing/runVisualRegression.ts"],"sourcesContent":["import { test, expect } from \"@playwright/test\";\nimport pixelmatch from \"pixelmatch\";\nimport { PNG } from \"pngjs\";\nimport fs from \"node:fs\";\nimport type { AdapterTarget, AdapterTesterConfig } from \"../config.js\";\nimport { getSourceOfTruth } from \"../config.js\";\n\nconst DEFAULT_EXCLUDE_TITLE_PREFIXES = [\"Theme\", \"Tokens\", \"Introduction\"];\n\nconst COMMON_STYLES_TO_CAPTURE = [\n \"color\",\n \"background-color\",\n \"padding-top\",\n \"padding-right\",\n \"padding-bottom\",\n \"padding-left\",\n \"margin-top\",\n \"margin-right\",\n \"margin-bottom\",\n \"margin-left\",\n \"border-top-left-radius\",\n \"border-top-right-radius\",\n \"border-bottom-left-radius\",\n \"border-bottom-right-radius\",\n \"font-size\",\n \"font-weight\",\n \"display\",\n \"flex-direction\",\n \"gap\",\n \"height\",\n];\n\ninterface StorybookEntry {\n type: string;\n id: string;\n name: string;\n title: string;\n}\n\nfunction slug(name: string): string {\n return name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n}\n\nasync function fetchStories(\n sourceOfTruth: AdapterTarget,\n excludeTitlePrefixes: string[],\n): Promise<StorybookEntry[]> {\n let stories: StorybookEntry[];\n try {\n const response = await fetch(`${sourceOfTruth.url}/index.json`);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch Storybook index: ${response.statusText}`,\n );\n }\n const data = (await response.json()) as any;\n const entries = data.entries || {};\n stories = Object.values(entries).filter(\n (entry: any) =>\n entry.type === \"story\" &&\n !excludeTitlePrefixes.some(\n (prefix) =>\n entry.title === prefix || entry.title.startsWith(`${prefix}/`),\n ),\n ) as StorybookEntry[];\n stories.sort((a, b) => a.id.localeCompare(b.id));\n } catch (error) {\n console.error(\n \"Failed to load Storybook index from\",\n `${sourceOfTruth.url}/index.json`,\n error,\n );\n throw new Error(\n `Storybook source of truth \"${sourceOfTruth.name}\" is not responsive or index.json is missing. Please ensure its Storybook is running.`,\n );\n }\n return stories;\n}\n\n/**\n * Defines a Playwright suite that diffs every target in `config.targets`\n * against `config.targets`' single `sourceOfTruth` entry, story by story.\n * Call this with a top-level `await` from a Playwright `*.spec.ts` file — it\n * calls `test.describe` at module scope, so it must run inside Playwright's\n * test runner during test-graph compilation.\n */\nexport async function runVisualRegression(\n config: AdapterTesterConfig,\n): Promise<void> {\n const sourceOfTruth = getSourceOfTruth(config);\n const targets = config.targets.filter((target) => !target.sourceOfTruth);\n const excludeTitlePrefixes =\n config.excludeTitlePrefixes ?? DEFAULT_EXCLUDE_TITLE_PREFIXES;\n const relaxedThresholdStoryIds = config.relaxedThresholdStoryIds ?? [];\n const relaxedThresholdPixels = config.relaxedThresholdPixels;\n\n // Fetched once at test-graph compilation time and shared by every target's\n // describe block below.\n const stories = await fetchStories(sourceOfTruth, excludeTitlePrefixes);\n\n for (const target of targets) {\n test.describe(`${sourceOfTruth.name} vs ${target.name} — Dynamic Visual Regression Suite`, () => {\n for (const story of stories) {\n test(`Visual regression for: ${story.title} - ${story.name} (${story.id})`, async ({\n browser,\n }, testInfo) => {\n const sourcePage = await browser.newPage();\n const targetPage = await browser.newPage();\n\n await sourcePage.setViewportSize({ width: 800, height: 600 });\n await targetPage.setViewportSize({ width: 800, height: 600 });\n\n await Promise.all([\n sourcePage.goto(\n `${sourceOfTruth.url}/iframe.html?id=${story.id}&viewMode=story`,\n { waitUntil: \"networkidle\" },\n ),\n targetPage.goto(\n `${target.url}/iframe.html?id=${story.id}&viewMode=story`,\n { waitUntil: \"networkidle\" },\n ),\n ]);\n\n await Promise.all([\n sourcePage.waitForSelector(\"#storybook-root\"),\n targetPage.waitForSelector(\"#storybook-root\"),\n ]);\n\n await Promise.all([\n sourcePage.waitForTimeout(300),\n targetPage.waitForTimeout(300),\n ]);\n\n // 1. EXTRACT DOM STRUCTURE AND STYLES\n const extractFn = (stylesToCapture: string[]) => {\n function crawl(el: Element): any {\n const data: any = {\n tag: el.tagName.toLowerCase(),\n styles: {},\n children: [],\n };\n const computed = window.getComputedStyle(el);\n for (const prop of stylesToCapture) {\n data.styles[prop] = computed.getPropertyValue(prop);\n }\n for (const child of Array.from(el.children)) {\n data.children.push(crawl(child));\n }\n return data;\n }\n const root = document.querySelector(\"#storybook-root\");\n return root ? crawl(root) : null;\n };\n\n const sourceDOM = await sourcePage.evaluate(\n extractFn,\n COMMON_STYLES_TO_CAPTURE,\n );\n const targetDOM = await targetPage.evaluate(\n extractFn,\n COMMON_STYLES_TO_CAPTURE,\n );\n\n await testInfo.attach(`${sourceOfTruth.name} DOM Tree JSON`, {\n body: JSON.stringify(sourceDOM, null, 2),\n contentType: \"application/json\",\n });\n await testInfo.attach(`${target.name} DOM Tree JSON`, {\n body: JSON.stringify(targetDOM, null, 2),\n contentType: \"application/json\",\n });\n\n const sourceSlug = slug(sourceOfTruth.name);\n const targetSlug = slug(target.name);\n fs.writeFileSync(\n `/tmp/${story.id}-${sourceSlug}DOM.json`,\n JSON.stringify(sourceDOM, null, 2),\n );\n fs.writeFileSync(\n `/tmp/${story.id}-${targetSlug}DOM.json`,\n JSON.stringify(targetDOM, null, 2),\n );\n\n // 2. CAPTURE Headless Viewport Screenshots\n const sourceBuffer = await sourcePage.screenshot();\n const targetBuffer = await targetPage.screenshot();\n\n // 3. PIXEL-BY-PIXEL COMPARISON\n const img1 = PNG.sync.read(sourceBuffer);\n const img2 = PNG.sync.read(targetBuffer);\n const diff = new PNG({ width: img1.width, height: img1.height });\n\n const diffPixels = pixelmatch(\n img1.data,\n img2.data,\n diff.data,\n img1.width,\n img1.height,\n { threshold: 0.1 },\n );\n\n fs.writeFileSync(`/tmp/${story.id}-diff.png`, PNG.sync.write(diff));\n fs.writeFileSync(`/tmp/${story.id}-${sourceSlug}.png`, sourceBuffer);\n fs.writeFileSync(`/tmp/${story.id}-${targetSlug}.png`, targetBuffer);\n\n await testInfo.attach(\"Visual Diff Overlay\", {\n body: PNG.sync.write(diff),\n contentType: \"image/png\",\n });\n await testInfo.attach(\n `${sourceOfTruth.name} Screenshot (Source of Truth)`,\n {\n body: sourceBuffer,\n contentType: \"image/png\",\n },\n );\n await testInfo.attach(`${target.name} Screenshot (Target)`, {\n body: targetBuffer,\n contentType: \"image/png\",\n });\n\n const threshold =\n relaxedThresholdPixels !== undefined &&\n relaxedThresholdStoryIds.some((id) => story.id.startsWith(id))\n ? relaxedThresholdPixels\n : config.diffThresholdPixels;\n\n // Perform soft assertions so failures are logged but other stories continue\n expect.soft(diffPixels).toBeLessThan(threshold);\n });\n }\n });\n }\n}\n"],"names":["DEFAULT_EXCLUDE_TITLE_PREFIXES","COMMON_STYLES_TO_CAPTURE","slug","name","fetchStories","sourceOfTruth","excludeTitlePrefixes","stories","response","entries","entry","prefix","b","error","runVisualRegression","config","getSourceOfTruth","targets","target","relaxedThresholdStoryIds","relaxedThresholdPixels","test","story","browser","testInfo","sourcePage","targetPage","extractFn","stylesToCapture","crawl","el","data","computed","prop","child","root","sourceDOM","targetDOM","sourceSlug","targetSlug","fs","sourceBuffer","targetBuffer","img1","PNG","img2","diff","diffPixels","pixelmatch","threshold","id","expect"],"mappings":"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"}
1
+ {"version":3,"file":"testing.cjs","sources":["../src/testing/runVisualRegression.ts"],"sourcesContent":["import { test, expect } from \"@playwright/test\";\nimport pixelmatch from \"pixelmatch\";\nimport { PNG } from \"pngjs\";\nimport fs from \"node:fs\";\nimport type { AdapterTarget, AdapterTesterConfig } from \"../config.js\";\nimport { getSourceOfTruth } from \"../config.js\";\n\nconst DEFAULT_EXCLUDE_TITLE_PREFIXES = [\"Theme\", \"Tokens\", \"Introduction\"];\n\nconst COMMON_STYLES_TO_CAPTURE = [\n \"color\",\n \"background-color\",\n \"padding-top\",\n \"padding-right\",\n \"padding-bottom\",\n \"padding-left\",\n \"margin-top\",\n \"margin-right\",\n \"margin-bottom\",\n \"margin-left\",\n \"border-top-left-radius\",\n \"border-top-right-radius\",\n \"border-bottom-left-radius\",\n \"border-bottom-right-radius\",\n \"font-size\",\n \"font-weight\",\n \"display\",\n \"flex-direction\",\n \"gap\",\n \"height\",\n];\n\ninterface StorybookEntry {\n type: string;\n id: string;\n name: string;\n title: string;\n}\n\nfunction slug(name: string): string {\n return name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n}\n\nasync function fetchStories(\n sourceOfTruth: AdapterTarget,\n excludeTitlePrefixes: string[],\n): Promise<StorybookEntry[]> {\n let stories: StorybookEntry[];\n try {\n const response = await fetch(`${sourceOfTruth.url}/index.json`);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch Storybook index: ${response.statusText}`,\n );\n }\n const data = (await response.json()) as any;\n const entries = data.entries || {};\n stories = Object.values(entries).filter(\n (entry: any) =>\n entry.type === \"story\" &&\n !excludeTitlePrefixes.some(\n (prefix) =>\n entry.title === prefix || entry.title.startsWith(`${prefix}/`),\n ),\n ) as StorybookEntry[];\n stories.sort((a, b) => a.id.localeCompare(b.id));\n } catch (error) {\n console.error(\n \"Failed to load Storybook index from\",\n `${sourceOfTruth.url}/index.json`,\n error,\n );\n throw new Error(\n `Storybook source of truth \"${sourceOfTruth.name}\" is not responsive or index.json is missing. Please ensure its Storybook is running.`,\n );\n }\n return stories;\n}\n\n/**\n * Fetches just the set of story ids a target's own Storybook actually has —\n * used to skip a comparison up front rather than let it burn the full\n * per-test timeout navigating to a story id the target doesn't implement\n * yet. A target with a partially-built component set (a fresh adapter with\n * a handful of real components and many still-stub ones, for example) is\n * the normal case, not an error, so a failed/empty fetch here degrades to\n * \"assume nothing exists yet\" (every story against this target gets\n * skipped) rather than throwing — unlike `fetchStories` above, where the\n * source of truth being unreachable really is fatal to the whole run.\n */\nasync function fetchTargetStoryIds(target: AdapterTarget): Promise<Set<string>> {\n try {\n const response = await fetch(`${target.url}/index.json`);\n if (!response.ok) {\n console.warn(\n `Could not load Storybook index from target \"${target.name}\" (${response.status} ${response.statusText}) — every story will be reported as skipped for this target.`,\n );\n return new Set();\n }\n const data = (await response.json()) as any;\n const entries = data.entries || {};\n const ids = Object.values(entries)\n .filter((entry: any) => entry.type === \"story\")\n .map((entry: any) => entry.id as string);\n return new Set(ids);\n } catch (error) {\n console.warn(\n `Could not load Storybook index from target \"${target.name}\" — every story will be reported as skipped for this target.`,\n error,\n );\n return new Set();\n }\n}\n\n/**\n * Defines a Playwright suite that diffs every target in `config.targets`\n * against `config.targets`' single `sourceOfTruth` entry, story by story.\n * Call this with a top-level `await` from a Playwright `*.spec.ts` file — it\n * calls `test.describe` at module scope, so it must run inside Playwright's\n * test runner during test-graph compilation.\n */\nexport async function runVisualRegression(\n config: AdapterTesterConfig,\n): Promise<void> {\n const sourceOfTruth = getSourceOfTruth(config);\n const targets = config.targets.filter((target) => !target.sourceOfTruth);\n const excludeTitlePrefixes =\n config.excludeTitlePrefixes ?? DEFAULT_EXCLUDE_TITLE_PREFIXES;\n const relaxedThresholdStoryIds = config.relaxedThresholdStoryIds ?? [];\n const relaxedThresholdPixels = config.relaxedThresholdPixels;\n\n // Fetched once at test-graph compilation time and shared by every target's\n // describe block below.\n const stories = await fetchStories(sourceOfTruth, excludeTitlePrefixes);\n\n // One index fetch per target, not per story — a fresh/partially-built adapter is expected to\n // be missing most of the source of truth's stories, and each miss should be a cheap, fast\n // skip rather than a full-timeout navigation to a story id that will never resolve.\n const targetStoryIds = new Map<string, Set<string>>();\n for (const target of targets) {\n targetStoryIds.set(target.name, await fetchTargetStoryIds(target));\n }\n\n for (const target of targets) {\n const storyIds = targetStoryIds.get(target.name) ?? new Set<string>();\n test.describe(`${sourceOfTruth.name} vs ${target.name} — Dynamic Visual Regression Suite`, () => {\n for (const story of stories) {\n test(`Visual regression for: ${story.title} - ${story.name} (${story.id})`, async ({\n browser,\n }, testInfo) => {\n test.skip(\n !storyIds.has(story.id),\n `${target.name} has no story with id \"${story.id}\" yet — not yet implemented, not a failure.`,\n );\n\n const sourcePage = await browser.newPage();\n const targetPage = await browser.newPage();\n\n await sourcePage.setViewportSize({ width: 800, height: 600 });\n await targetPage.setViewportSize({ width: 800, height: 600 });\n\n await Promise.all([\n sourcePage.goto(\n `${sourceOfTruth.url}/iframe.html?id=${story.id}&viewMode=story`,\n { waitUntil: \"networkidle\" },\n ),\n targetPage.goto(\n `${target.url}/iframe.html?id=${story.id}&viewMode=story`,\n { waitUntil: \"networkidle\" },\n ),\n ]);\n\n await Promise.all([\n sourcePage.waitForSelector(\"#storybook-root\"),\n targetPage.waitForSelector(\"#storybook-root\"),\n ]);\n\n await Promise.all([\n sourcePage.waitForTimeout(300),\n targetPage.waitForTimeout(300),\n ]);\n\n // 1. EXTRACT DOM STRUCTURE AND STYLES\n const extractFn = (stylesToCapture: string[]) => {\n function crawl(el: Element): any {\n const data: any = {\n tag: el.tagName.toLowerCase(),\n styles: {},\n children: [],\n };\n const computed = window.getComputedStyle(el);\n for (const prop of stylesToCapture) {\n data.styles[prop] = computed.getPropertyValue(prop);\n }\n for (const child of Array.from(el.children)) {\n data.children.push(crawl(child));\n }\n return data;\n }\n const root = document.querySelector(\"#storybook-root\");\n return root ? crawl(root) : null;\n };\n\n const sourceDOM = await sourcePage.evaluate(\n extractFn,\n COMMON_STYLES_TO_CAPTURE,\n );\n const targetDOM = await targetPage.evaluate(\n extractFn,\n COMMON_STYLES_TO_CAPTURE,\n );\n\n await testInfo.attach(`${sourceOfTruth.name} DOM Tree JSON`, {\n body: JSON.stringify(sourceDOM, null, 2),\n contentType: \"application/json\",\n });\n await testInfo.attach(`${target.name} DOM Tree JSON`, {\n body: JSON.stringify(targetDOM, null, 2),\n contentType: \"application/json\",\n });\n\n const sourceSlug = slug(sourceOfTruth.name);\n const targetSlug = slug(target.name);\n fs.writeFileSync(\n `/tmp/${story.id}-${sourceSlug}DOM.json`,\n JSON.stringify(sourceDOM, null, 2),\n );\n fs.writeFileSync(\n `/tmp/${story.id}-${targetSlug}DOM.json`,\n JSON.stringify(targetDOM, null, 2),\n );\n\n // 2. CAPTURE Headless Viewport Screenshots\n const sourceBuffer = await sourcePage.screenshot();\n const targetBuffer = await targetPage.screenshot();\n\n // 3. PIXEL-BY-PIXEL COMPARISON\n const img1 = PNG.sync.read(sourceBuffer);\n const img2 = PNG.sync.read(targetBuffer);\n const diff = new PNG({ width: img1.width, height: img1.height });\n\n const diffPixels = pixelmatch(\n img1.data,\n img2.data,\n diff.data,\n img1.width,\n img1.height,\n { threshold: 0.1 },\n );\n\n fs.writeFileSync(`/tmp/${story.id}-diff.png`, PNG.sync.write(diff));\n fs.writeFileSync(`/tmp/${story.id}-${sourceSlug}.png`, sourceBuffer);\n fs.writeFileSync(`/tmp/${story.id}-${targetSlug}.png`, targetBuffer);\n\n await testInfo.attach(\"Visual Diff Overlay\", {\n body: PNG.sync.write(diff),\n contentType: \"image/png\",\n });\n await testInfo.attach(\n `${sourceOfTruth.name} Screenshot (Source of Truth)`,\n {\n body: sourceBuffer,\n contentType: \"image/png\",\n },\n );\n await testInfo.attach(`${target.name} Screenshot (Target)`, {\n body: targetBuffer,\n contentType: \"image/png\",\n });\n\n const threshold =\n relaxedThresholdPixels !== undefined &&\n relaxedThresholdStoryIds.some((id) => story.id.startsWith(id))\n ? relaxedThresholdPixels\n : config.diffThresholdPixels;\n\n // Perform soft assertions so failures are logged but other stories continue\n expect.soft(diffPixels).toBeLessThan(threshold);\n });\n }\n });\n }\n}\n"],"names":["DEFAULT_EXCLUDE_TITLE_PREFIXES","COMMON_STYLES_TO_CAPTURE","slug","name","fetchStories","sourceOfTruth","excludeTitlePrefixes","stories","response","entries","entry","prefix","a","b","error","fetchTargetStoryIds","target","ids","runVisualRegression","config","getSourceOfTruth","targets","relaxedThresholdStoryIds","relaxedThresholdPixels","targetStoryIds","storyIds","test","story","browser","testInfo","sourcePage","targetPage","extractFn","stylesToCapture","crawl","el","data","computed","prop","child","root","sourceDOM","targetDOM","sourceSlug","targetSlug","fs","sourceBuffer","targetBuffer","img1","PNG","img2","diff","diffPixels","pixelmatch","threshold","id","expect"],"mappings":"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"}
package/dist/testing.js CHANGED
@@ -1,9 +1,9 @@
1
- import { test as M, expect as J } from "@playwright/test";
2
- import _ from "pixelmatch";
3
- import { PNG as h } from "pngjs";
4
- import g from "node:fs";
5
- import { g as U } from "./config-CDxTeSAY.js";
6
- const z = ["Theme", "Tokens", "Introduction"], j = [
1
+ import { test as x, expect as U } from "@playwright/test";
2
+ import z from "pixelmatch";
3
+ import { PNG as m } from "pngjs";
4
+ import w from "node:fs";
5
+ import { g as I } from "./config-CDxTeSAY.js";
6
+ const R = ["Theme", "Tokens", "Introduction"], E = [
7
7
  "color",
8
8
  "background-color",
9
9
  "padding-top",
@@ -25,23 +25,23 @@ const z = ["Theme", "Tokens", "Introduction"], j = [
25
25
  "gap",
26
26
  "height"
27
27
  ];
28
- function v(t) {
28
+ function C(t) {
29
29
  return t.toLowerCase().replace(/[^a-z0-9]+/g, "-");
30
30
  }
31
- async function R(t, r) {
32
- let c;
31
+ async function A(t, e) {
32
+ let s;
33
33
  try {
34
34
  const i = await fetch(`${t.url}/index.json`);
35
35
  if (!i.ok)
36
36
  throw new Error(
37
37
  `Failed to fetch Storybook index: ${i.statusText}`
38
38
  );
39
- const m = (await i.json()).entries || {};
40
- c = Object.values(m).filter(
41
- (a) => a.type === "story" && !r.some(
42
- (e) => a.title === e || a.title.startsWith(`${e}/`)
39
+ const a = (await i.json()).entries || {};
40
+ s = Object.values(a).filter(
41
+ (n) => n.type === "story" && !e.some(
42
+ (d) => n.title === d || n.title.startsWith(`${d}/`)
43
43
  )
44
- ), c.sort((a, e) => a.id.localeCompare(e.id));
44
+ ), s.sort((n, d) => n.id.localeCompare(d.id));
45
45
  } catch (i) {
46
46
  throw console.error(
47
47
  "Failed to load Storybook index from",
@@ -51,97 +51,121 @@ async function R(t, r) {
51
51
  `Storybook source of truth "${t.name}" is not responsive or index.json is missing. Please ensure its Storybook is running.`
52
52
  );
53
53
  }
54
- return c;
54
+ return s;
55
55
  }
56
- async function G(t) {
57
- const r = U(t), c = t.targets.filter((e) => !e.sourceOfTruth), i = t.excludeTitlePrefixes ?? z, $ = t.relaxedThresholdStoryIds ?? [], m = t.relaxedThresholdPixels, a = await R(r, i);
58
- for (const e of c)
59
- M.describe(`${r.name} vs ${e.name} — Dynamic Visual Regression Suite`, () => {
60
- for (const o of a)
61
- M(`Visual regression for: ${o.title} - ${o.name} (${o.id})`, async ({
62
- browser: b
63
- }, l) => {
64
- const s = await b.newPage(), n = await b.newPage();
65
- await s.setViewportSize({ width: 800, height: 600 }), await n.setViewportSize({ width: 800, height: 600 }), await Promise.all([
66
- s.goto(
67
- `${r.url}/iframe.html?id=${o.id}&viewMode=story`,
56
+ async function B(t) {
57
+ try {
58
+ const e = await fetch(`${t.url}/index.json`);
59
+ if (!e.ok)
60
+ return console.warn(
61
+ `Could not load Storybook index from target "${t.name}" (${e.status} ${e.statusText}) every story will be reported as skipped for this target.`
62
+ ), /* @__PURE__ */ new Set();
63
+ const i = (await e.json()).entries || {}, g = Object.values(i).filter((a) => a.type === "story").map((a) => a.id);
64
+ return new Set(g);
65
+ } catch (e) {
66
+ return console.warn(
67
+ `Could not load Storybook index from target "${t.name}" — every story will be reported as skipped for this target.`,
68
+ e
69
+ ), /* @__PURE__ */ new Set();
70
+ }
71
+ }
72
+ async function H(t) {
73
+ const e = I(t), s = t.targets.filter((r) => !r.sourceOfTruth), i = t.excludeTitlePrefixes ?? R, g = t.relaxedThresholdStoryIds ?? [], a = t.relaxedThresholdPixels, n = await A(e, i), d = /* @__PURE__ */ new Map();
74
+ for (const r of s)
75
+ d.set(r.name, await B(r));
76
+ for (const r of s) {
77
+ const N = d.get(r.name) ?? /* @__PURE__ */ new Set();
78
+ x.describe(`${e.name} vs ${r.name} — Dynamic Visual Regression Suite`, () => {
79
+ for (const o of n)
80
+ x(`Visual regression for: ${o.title} - ${o.name} (${o.id})`, async ({
81
+ browser: O
82
+ }, h) => {
83
+ x.skip(
84
+ !N.has(o.id),
85
+ `${r.name} has no story with id "${o.id}" yet — not yet implemented, not a failure.`
86
+ );
87
+ const c = await O.newPage(), l = await O.newPage();
88
+ await c.setViewportSize({ width: 800, height: 600 }), await l.setViewportSize({ width: 800, height: 600 }), await Promise.all([
89
+ c.goto(
90
+ `${e.url}/iframe.html?id=${o.id}&viewMode=story`,
68
91
  { waitUntil: "networkidle" }
69
92
  ),
70
- n.goto(
71
- `${e.url}/iframe.html?id=${o.id}&viewMode=story`,
93
+ l.goto(
94
+ `${r.url}/iframe.html?id=${o.id}&viewMode=story`,
72
95
  { waitUntil: "networkidle" }
73
96
  )
74
97
  ]), await Promise.all([
75
- s.waitForSelector("#storybook-root"),
76
- n.waitForSelector("#storybook-root")
98
+ c.waitForSelector("#storybook-root"),
99
+ l.waitForSelector("#storybook-root")
77
100
  ]), await Promise.all([
78
- s.waitForTimeout(300),
79
- n.waitForTimeout(300)
101
+ c.waitForTimeout(300),
102
+ l.waitForTimeout(300)
80
103
  ]);
81
- const x = (y) => {
82
- function D(S) {
83
- const T = {
84
- tag: S.tagName.toLowerCase(),
104
+ const k = ($) => {
105
+ function M(T) {
106
+ const b = {
107
+ tag: T.tagName.toLowerCase(),
85
108
  styles: {},
86
109
  children: []
87
- }, V = window.getComputedStyle(S);
88
- for (const u of y)
89
- T.styles[u] = V.getPropertyValue(u);
90
- for (const u of Array.from(S.children))
91
- T.children.push(D(u));
92
- return T;
110
+ }, _ = window.getComputedStyle(T);
111
+ for (const y of $)
112
+ b.styles[y] = _.getPropertyValue(y);
113
+ for (const y of Array.from(T.children))
114
+ b.children.push(M(y));
115
+ return b;
93
116
  }
94
- const E = document.querySelector("#storybook-root");
95
- return E ? D(E) : null;
96
- }, O = await s.evaluate(
97
- x,
98
- j
99
- ), P = await n.evaluate(
100
- x,
101
- j
117
+ const D = document.querySelector("#storybook-root");
118
+ return D ? M(D) : null;
119
+ }, P = await c.evaluate(
120
+ k,
121
+ E
122
+ ), F = await l.evaluate(
123
+ k,
124
+ E
102
125
  );
103
- await l.attach(`${r.name} DOM Tree JSON`, {
104
- body: JSON.stringify(O, null, 2),
105
- contentType: "application/json"
106
- }), await l.attach(`${e.name} DOM Tree JSON`, {
126
+ await h.attach(`${e.name} DOM Tree JSON`, {
107
127
  body: JSON.stringify(P, null, 2),
108
128
  contentType: "application/json"
129
+ }), await h.attach(`${r.name} DOM Tree JSON`, {
130
+ body: JSON.stringify(F, null, 2),
131
+ contentType: "application/json"
109
132
  });
110
- const F = v(r.name), k = v(e.name);
111
- g.writeFileSync(
112
- `/tmp/${o.id}-${F}DOM.json`,
113
- JSON.stringify(O, null, 2)
114
- ), g.writeFileSync(
115
- `/tmp/${o.id}-${k}DOM.json`,
133
+ const j = C(e.name), v = C(r.name);
134
+ w.writeFileSync(
135
+ `/tmp/${o.id}-${j}DOM.json`,
116
136
  JSON.stringify(P, null, 2)
137
+ ), w.writeFileSync(
138
+ `/tmp/${o.id}-${v}DOM.json`,
139
+ JSON.stringify(F, null, 2)
117
140
  );
118
- const w = await s.screenshot(), f = await n.screenshot(), d = h.sync.read(w), N = h.sync.read(f), p = new h({ width: d.width, height: d.height }), C = _(
119
- d.data,
120
- N.data,
121
- p.data,
122
- d.width,
123
- d.height,
141
+ const f = await c.screenshot(), p = await l.screenshot(), u = m.sync.read(f), L = m.sync.read(p), S = new m({ width: u.width, height: u.height }), V = z(
142
+ u.data,
143
+ L.data,
144
+ S.data,
145
+ u.width,
146
+ u.height,
124
147
  { threshold: 0.1 }
125
148
  );
126
- g.writeFileSync(`/tmp/${o.id}-diff.png`, h.sync.write(p)), g.writeFileSync(`/tmp/${o.id}-${F}.png`, w), g.writeFileSync(`/tmp/${o.id}-${k}.png`, f), await l.attach("Visual Diff Overlay", {
127
- body: h.sync.write(p),
149
+ w.writeFileSync(`/tmp/${o.id}-diff.png`, m.sync.write(S)), w.writeFileSync(`/tmp/${o.id}-${j}.png`, f), w.writeFileSync(`/tmp/${o.id}-${v}.png`, p), await h.attach("Visual Diff Overlay", {
150
+ body: m.sync.write(S),
128
151
  contentType: "image/png"
129
- }), await l.attach(
130
- `${r.name} Screenshot (Source of Truth)`,
152
+ }), await h.attach(
153
+ `${e.name} Screenshot (Source of Truth)`,
131
154
  {
132
- body: w,
155
+ body: f,
133
156
  contentType: "image/png"
134
157
  }
135
- ), await l.attach(`${e.name} Screenshot (Target)`, {
136
- body: f,
158
+ ), await h.attach(`${r.name} Screenshot (Target)`, {
159
+ body: p,
137
160
  contentType: "image/png"
138
161
  });
139
- const L = m !== void 0 && $.some((y) => o.id.startsWith(y)) ? m : t.diffThresholdPixels;
140
- J.soft(C).toBeLessThan(L);
162
+ const J = a !== void 0 && g.some(($) => o.id.startsWith($)) ? a : t.diffThresholdPixels;
163
+ U.soft(V).toBeLessThan(J);
141
164
  });
142
165
  });
166
+ }
143
167
  }
144
168
  export {
145
- G as runVisualRegression
169
+ H as runVisualRegression
146
170
  };
147
171
  //# sourceMappingURL=testing.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"testing.js","sources":["../src/testing/runVisualRegression.ts"],"sourcesContent":["import { test, expect } from \"@playwright/test\";\nimport pixelmatch from \"pixelmatch\";\nimport { PNG } from \"pngjs\";\nimport fs from \"node:fs\";\nimport type { AdapterTarget, AdapterTesterConfig } from \"../config.js\";\nimport { getSourceOfTruth } from \"../config.js\";\n\nconst DEFAULT_EXCLUDE_TITLE_PREFIXES = [\"Theme\", \"Tokens\", \"Introduction\"];\n\nconst COMMON_STYLES_TO_CAPTURE = [\n \"color\",\n \"background-color\",\n \"padding-top\",\n \"padding-right\",\n \"padding-bottom\",\n \"padding-left\",\n \"margin-top\",\n \"margin-right\",\n \"margin-bottom\",\n \"margin-left\",\n \"border-top-left-radius\",\n \"border-top-right-radius\",\n \"border-bottom-left-radius\",\n \"border-bottom-right-radius\",\n \"font-size\",\n \"font-weight\",\n \"display\",\n \"flex-direction\",\n \"gap\",\n \"height\",\n];\n\ninterface StorybookEntry {\n type: string;\n id: string;\n name: string;\n title: string;\n}\n\nfunction slug(name: string): string {\n return name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n}\n\nasync function fetchStories(\n sourceOfTruth: AdapterTarget,\n excludeTitlePrefixes: string[],\n): Promise<StorybookEntry[]> {\n let stories: StorybookEntry[];\n try {\n const response = await fetch(`${sourceOfTruth.url}/index.json`);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch Storybook index: ${response.statusText}`,\n );\n }\n const data = (await response.json()) as any;\n const entries = data.entries || {};\n stories = Object.values(entries).filter(\n (entry: any) =>\n entry.type === \"story\" &&\n !excludeTitlePrefixes.some(\n (prefix) =>\n entry.title === prefix || entry.title.startsWith(`${prefix}/`),\n ),\n ) as StorybookEntry[];\n stories.sort((a, b) => a.id.localeCompare(b.id));\n } catch (error) {\n console.error(\n \"Failed to load Storybook index from\",\n `${sourceOfTruth.url}/index.json`,\n error,\n );\n throw new Error(\n `Storybook source of truth \"${sourceOfTruth.name}\" is not responsive or index.json is missing. Please ensure its Storybook is running.`,\n );\n }\n return stories;\n}\n\n/**\n * Defines a Playwright suite that diffs every target in `config.targets`\n * against `config.targets`' single `sourceOfTruth` entry, story by story.\n * Call this with a top-level `await` from a Playwright `*.spec.ts` file — it\n * calls `test.describe` at module scope, so it must run inside Playwright's\n * test runner during test-graph compilation.\n */\nexport async function runVisualRegression(\n config: AdapterTesterConfig,\n): Promise<void> {\n const sourceOfTruth = getSourceOfTruth(config);\n const targets = config.targets.filter((target) => !target.sourceOfTruth);\n const excludeTitlePrefixes =\n config.excludeTitlePrefixes ?? DEFAULT_EXCLUDE_TITLE_PREFIXES;\n const relaxedThresholdStoryIds = config.relaxedThresholdStoryIds ?? [];\n const relaxedThresholdPixels = config.relaxedThresholdPixels;\n\n // Fetched once at test-graph compilation time and shared by every target's\n // describe block below.\n const stories = await fetchStories(sourceOfTruth, excludeTitlePrefixes);\n\n for (const target of targets) {\n test.describe(`${sourceOfTruth.name} vs ${target.name} — Dynamic Visual Regression Suite`, () => {\n for (const story of stories) {\n test(`Visual regression for: ${story.title} - ${story.name} (${story.id})`, async ({\n browser,\n }, testInfo) => {\n const sourcePage = await browser.newPage();\n const targetPage = await browser.newPage();\n\n await sourcePage.setViewportSize({ width: 800, height: 600 });\n await targetPage.setViewportSize({ width: 800, height: 600 });\n\n await Promise.all([\n sourcePage.goto(\n `${sourceOfTruth.url}/iframe.html?id=${story.id}&viewMode=story`,\n { waitUntil: \"networkidle\" },\n ),\n targetPage.goto(\n `${target.url}/iframe.html?id=${story.id}&viewMode=story`,\n { waitUntil: \"networkidle\" },\n ),\n ]);\n\n await Promise.all([\n sourcePage.waitForSelector(\"#storybook-root\"),\n targetPage.waitForSelector(\"#storybook-root\"),\n ]);\n\n await Promise.all([\n sourcePage.waitForTimeout(300),\n targetPage.waitForTimeout(300),\n ]);\n\n // 1. EXTRACT DOM STRUCTURE AND STYLES\n const extractFn = (stylesToCapture: string[]) => {\n function crawl(el: Element): any {\n const data: any = {\n tag: el.tagName.toLowerCase(),\n styles: {},\n children: [],\n };\n const computed = window.getComputedStyle(el);\n for (const prop of stylesToCapture) {\n data.styles[prop] = computed.getPropertyValue(prop);\n }\n for (const child of Array.from(el.children)) {\n data.children.push(crawl(child));\n }\n return data;\n }\n const root = document.querySelector(\"#storybook-root\");\n return root ? crawl(root) : null;\n };\n\n const sourceDOM = await sourcePage.evaluate(\n extractFn,\n COMMON_STYLES_TO_CAPTURE,\n );\n const targetDOM = await targetPage.evaluate(\n extractFn,\n COMMON_STYLES_TO_CAPTURE,\n );\n\n await testInfo.attach(`${sourceOfTruth.name} DOM Tree JSON`, {\n body: JSON.stringify(sourceDOM, null, 2),\n contentType: \"application/json\",\n });\n await testInfo.attach(`${target.name} DOM Tree JSON`, {\n body: JSON.stringify(targetDOM, null, 2),\n contentType: \"application/json\",\n });\n\n const sourceSlug = slug(sourceOfTruth.name);\n const targetSlug = slug(target.name);\n fs.writeFileSync(\n `/tmp/${story.id}-${sourceSlug}DOM.json`,\n JSON.stringify(sourceDOM, null, 2),\n );\n fs.writeFileSync(\n `/tmp/${story.id}-${targetSlug}DOM.json`,\n JSON.stringify(targetDOM, null, 2),\n );\n\n // 2. CAPTURE Headless Viewport Screenshots\n const sourceBuffer = await sourcePage.screenshot();\n const targetBuffer = await targetPage.screenshot();\n\n // 3. PIXEL-BY-PIXEL COMPARISON\n const img1 = PNG.sync.read(sourceBuffer);\n const img2 = PNG.sync.read(targetBuffer);\n const diff = new PNG({ width: img1.width, height: img1.height });\n\n const diffPixels = pixelmatch(\n img1.data,\n img2.data,\n diff.data,\n img1.width,\n img1.height,\n { threshold: 0.1 },\n );\n\n fs.writeFileSync(`/tmp/${story.id}-diff.png`, PNG.sync.write(diff));\n fs.writeFileSync(`/tmp/${story.id}-${sourceSlug}.png`, sourceBuffer);\n fs.writeFileSync(`/tmp/${story.id}-${targetSlug}.png`, targetBuffer);\n\n await testInfo.attach(\"Visual Diff Overlay\", {\n body: PNG.sync.write(diff),\n contentType: \"image/png\",\n });\n await testInfo.attach(\n `${sourceOfTruth.name} Screenshot (Source of Truth)`,\n {\n body: sourceBuffer,\n contentType: \"image/png\",\n },\n );\n await testInfo.attach(`${target.name} Screenshot (Target)`, {\n body: targetBuffer,\n contentType: \"image/png\",\n });\n\n const threshold =\n relaxedThresholdPixels !== undefined &&\n relaxedThresholdStoryIds.some((id) => story.id.startsWith(id))\n ? relaxedThresholdPixels\n : config.diffThresholdPixels;\n\n // Perform soft assertions so failures are logged but other stories continue\n expect.soft(diffPixels).toBeLessThan(threshold);\n });\n }\n });\n }\n}\n"],"names":["DEFAULT_EXCLUDE_TITLE_PREFIXES","COMMON_STYLES_TO_CAPTURE","slug","name","fetchStories","sourceOfTruth","excludeTitlePrefixes","stories","response","entries","entry","prefix","b","error","runVisualRegression","config","getSourceOfTruth","targets","target","relaxedThresholdStoryIds","relaxedThresholdPixels","test","story","browser","testInfo","sourcePage","targetPage","extractFn","stylesToCapture","crawl","el","data","computed","prop","child","root","sourceDOM","targetDOM","sourceSlug","targetSlug","fs","sourceBuffer","targetBuffer","img1","PNG","img2","diff","diffPixels","pixelmatch","threshold","id","expect"],"mappings":"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"}
1
+ {"version":3,"file":"testing.js","sources":["../src/testing/runVisualRegression.ts"],"sourcesContent":["import { test, expect } from \"@playwright/test\";\nimport pixelmatch from \"pixelmatch\";\nimport { PNG } from \"pngjs\";\nimport fs from \"node:fs\";\nimport type { AdapterTarget, AdapterTesterConfig } from \"../config.js\";\nimport { getSourceOfTruth } from \"../config.js\";\n\nconst DEFAULT_EXCLUDE_TITLE_PREFIXES = [\"Theme\", \"Tokens\", \"Introduction\"];\n\nconst COMMON_STYLES_TO_CAPTURE = [\n \"color\",\n \"background-color\",\n \"padding-top\",\n \"padding-right\",\n \"padding-bottom\",\n \"padding-left\",\n \"margin-top\",\n \"margin-right\",\n \"margin-bottom\",\n \"margin-left\",\n \"border-top-left-radius\",\n \"border-top-right-radius\",\n \"border-bottom-left-radius\",\n \"border-bottom-right-radius\",\n \"font-size\",\n \"font-weight\",\n \"display\",\n \"flex-direction\",\n \"gap\",\n \"height\",\n];\n\ninterface StorybookEntry {\n type: string;\n id: string;\n name: string;\n title: string;\n}\n\nfunction slug(name: string): string {\n return name.toLowerCase().replace(/[^a-z0-9]+/g, \"-\");\n}\n\nasync function fetchStories(\n sourceOfTruth: AdapterTarget,\n excludeTitlePrefixes: string[],\n): Promise<StorybookEntry[]> {\n let stories: StorybookEntry[];\n try {\n const response = await fetch(`${sourceOfTruth.url}/index.json`);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch Storybook index: ${response.statusText}`,\n );\n }\n const data = (await response.json()) as any;\n const entries = data.entries || {};\n stories = Object.values(entries).filter(\n (entry: any) =>\n entry.type === \"story\" &&\n !excludeTitlePrefixes.some(\n (prefix) =>\n entry.title === prefix || entry.title.startsWith(`${prefix}/`),\n ),\n ) as StorybookEntry[];\n stories.sort((a, b) => a.id.localeCompare(b.id));\n } catch (error) {\n console.error(\n \"Failed to load Storybook index from\",\n `${sourceOfTruth.url}/index.json`,\n error,\n );\n throw new Error(\n `Storybook source of truth \"${sourceOfTruth.name}\" is not responsive or index.json is missing. Please ensure its Storybook is running.`,\n );\n }\n return stories;\n}\n\n/**\n * Fetches just the set of story ids a target's own Storybook actually has —\n * used to skip a comparison up front rather than let it burn the full\n * per-test timeout navigating to a story id the target doesn't implement\n * yet. A target with a partially-built component set (a fresh adapter with\n * a handful of real components and many still-stub ones, for example) is\n * the normal case, not an error, so a failed/empty fetch here degrades to\n * \"assume nothing exists yet\" (every story against this target gets\n * skipped) rather than throwing — unlike `fetchStories` above, where the\n * source of truth being unreachable really is fatal to the whole run.\n */\nasync function fetchTargetStoryIds(target: AdapterTarget): Promise<Set<string>> {\n try {\n const response = await fetch(`${target.url}/index.json`);\n if (!response.ok) {\n console.warn(\n `Could not load Storybook index from target \"${target.name}\" (${response.status} ${response.statusText}) — every story will be reported as skipped for this target.`,\n );\n return new Set();\n }\n const data = (await response.json()) as any;\n const entries = data.entries || {};\n const ids = Object.values(entries)\n .filter((entry: any) => entry.type === \"story\")\n .map((entry: any) => entry.id as string);\n return new Set(ids);\n } catch (error) {\n console.warn(\n `Could not load Storybook index from target \"${target.name}\" — every story will be reported as skipped for this target.`,\n error,\n );\n return new Set();\n }\n}\n\n/**\n * Defines a Playwright suite that diffs every target in `config.targets`\n * against `config.targets`' single `sourceOfTruth` entry, story by story.\n * Call this with a top-level `await` from a Playwright `*.spec.ts` file — it\n * calls `test.describe` at module scope, so it must run inside Playwright's\n * test runner during test-graph compilation.\n */\nexport async function runVisualRegression(\n config: AdapterTesterConfig,\n): Promise<void> {\n const sourceOfTruth = getSourceOfTruth(config);\n const targets = config.targets.filter((target) => !target.sourceOfTruth);\n const excludeTitlePrefixes =\n config.excludeTitlePrefixes ?? DEFAULT_EXCLUDE_TITLE_PREFIXES;\n const relaxedThresholdStoryIds = config.relaxedThresholdStoryIds ?? [];\n const relaxedThresholdPixels = config.relaxedThresholdPixels;\n\n // Fetched once at test-graph compilation time and shared by every target's\n // describe block below.\n const stories = await fetchStories(sourceOfTruth, excludeTitlePrefixes);\n\n // One index fetch per target, not per story — a fresh/partially-built adapter is expected to\n // be missing most of the source of truth's stories, and each miss should be a cheap, fast\n // skip rather than a full-timeout navigation to a story id that will never resolve.\n const targetStoryIds = new Map<string, Set<string>>();\n for (const target of targets) {\n targetStoryIds.set(target.name, await fetchTargetStoryIds(target));\n }\n\n for (const target of targets) {\n const storyIds = targetStoryIds.get(target.name) ?? new Set<string>();\n test.describe(`${sourceOfTruth.name} vs ${target.name} — Dynamic Visual Regression Suite`, () => {\n for (const story of stories) {\n test(`Visual regression for: ${story.title} - ${story.name} (${story.id})`, async ({\n browser,\n }, testInfo) => {\n test.skip(\n !storyIds.has(story.id),\n `${target.name} has no story with id \"${story.id}\" yet — not yet implemented, not a failure.`,\n );\n\n const sourcePage = await browser.newPage();\n const targetPage = await browser.newPage();\n\n await sourcePage.setViewportSize({ width: 800, height: 600 });\n await targetPage.setViewportSize({ width: 800, height: 600 });\n\n await Promise.all([\n sourcePage.goto(\n `${sourceOfTruth.url}/iframe.html?id=${story.id}&viewMode=story`,\n { waitUntil: \"networkidle\" },\n ),\n targetPage.goto(\n `${target.url}/iframe.html?id=${story.id}&viewMode=story`,\n { waitUntil: \"networkidle\" },\n ),\n ]);\n\n await Promise.all([\n sourcePage.waitForSelector(\"#storybook-root\"),\n targetPage.waitForSelector(\"#storybook-root\"),\n ]);\n\n await Promise.all([\n sourcePage.waitForTimeout(300),\n targetPage.waitForTimeout(300),\n ]);\n\n // 1. EXTRACT DOM STRUCTURE AND STYLES\n const extractFn = (stylesToCapture: string[]) => {\n function crawl(el: Element): any {\n const data: any = {\n tag: el.tagName.toLowerCase(),\n styles: {},\n children: [],\n };\n const computed = window.getComputedStyle(el);\n for (const prop of stylesToCapture) {\n data.styles[prop] = computed.getPropertyValue(prop);\n }\n for (const child of Array.from(el.children)) {\n data.children.push(crawl(child));\n }\n return data;\n }\n const root = document.querySelector(\"#storybook-root\");\n return root ? crawl(root) : null;\n };\n\n const sourceDOM = await sourcePage.evaluate(\n extractFn,\n COMMON_STYLES_TO_CAPTURE,\n );\n const targetDOM = await targetPage.evaluate(\n extractFn,\n COMMON_STYLES_TO_CAPTURE,\n );\n\n await testInfo.attach(`${sourceOfTruth.name} DOM Tree JSON`, {\n body: JSON.stringify(sourceDOM, null, 2),\n contentType: \"application/json\",\n });\n await testInfo.attach(`${target.name} DOM Tree JSON`, {\n body: JSON.stringify(targetDOM, null, 2),\n contentType: \"application/json\",\n });\n\n const sourceSlug = slug(sourceOfTruth.name);\n const targetSlug = slug(target.name);\n fs.writeFileSync(\n `/tmp/${story.id}-${sourceSlug}DOM.json`,\n JSON.stringify(sourceDOM, null, 2),\n );\n fs.writeFileSync(\n `/tmp/${story.id}-${targetSlug}DOM.json`,\n JSON.stringify(targetDOM, null, 2),\n );\n\n // 2. CAPTURE Headless Viewport Screenshots\n const sourceBuffer = await sourcePage.screenshot();\n const targetBuffer = await targetPage.screenshot();\n\n // 3. PIXEL-BY-PIXEL COMPARISON\n const img1 = PNG.sync.read(sourceBuffer);\n const img2 = PNG.sync.read(targetBuffer);\n const diff = new PNG({ width: img1.width, height: img1.height });\n\n const diffPixels = pixelmatch(\n img1.data,\n img2.data,\n diff.data,\n img1.width,\n img1.height,\n { threshold: 0.1 },\n );\n\n fs.writeFileSync(`/tmp/${story.id}-diff.png`, PNG.sync.write(diff));\n fs.writeFileSync(`/tmp/${story.id}-${sourceSlug}.png`, sourceBuffer);\n fs.writeFileSync(`/tmp/${story.id}-${targetSlug}.png`, targetBuffer);\n\n await testInfo.attach(\"Visual Diff Overlay\", {\n body: PNG.sync.write(diff),\n contentType: \"image/png\",\n });\n await testInfo.attach(\n `${sourceOfTruth.name} Screenshot (Source of Truth)`,\n {\n body: sourceBuffer,\n contentType: \"image/png\",\n },\n );\n await testInfo.attach(`${target.name} Screenshot (Target)`, {\n body: targetBuffer,\n contentType: \"image/png\",\n });\n\n const threshold =\n relaxedThresholdPixels !== undefined &&\n relaxedThresholdStoryIds.some((id) => story.id.startsWith(id))\n ? relaxedThresholdPixels\n : config.diffThresholdPixels;\n\n // Perform soft assertions so failures are logged but other stories continue\n expect.soft(diffPixels).toBeLessThan(threshold);\n });\n }\n });\n }\n}\n"],"names":["DEFAULT_EXCLUDE_TITLE_PREFIXES","COMMON_STYLES_TO_CAPTURE","slug","name","fetchStories","sourceOfTruth","excludeTitlePrefixes","stories","response","entries","entry","prefix","a","b","error","fetchTargetStoryIds","target","ids","runVisualRegression","config","getSourceOfTruth","targets","relaxedThresholdStoryIds","relaxedThresholdPixels","targetStoryIds","storyIds","test","story","browser","testInfo","sourcePage","targetPage","extractFn","stylesToCapture","crawl","el","data","computed","prop","child","root","sourceDOM","targetDOM","sourceSlug","targetSlug","fs","sourceBuffer","targetBuffer","img1","PNG","img2","diff","diffPixels","pixelmatch","threshold","id","expect"],"mappings":"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"}
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "url": "git+https://github.com/borderux/recursica.git",
14
14
  "directory": "packages/adapter-tester"
15
15
  },
16
- "version": "2.0.0",
16
+ "version": "2.0.1",
17
17
  "type": "module",
18
18
  "main": "./dist/index.cjs",
19
19
  "module": "./dist/index.js",
@@ -1,54 +0,0 @@
1
- "use strict";const o=require("node:fs"),t=require("node:path"),i="^8.0.0",s="^10.3.3",n="^19.0.0",m={"@storybook/addon-docs":s,"@storybook/addon-a11y":s,"storybook-dark-mode":"^5.0.0"},p={"react-markdown":"^10.1.0"};function l(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":i,"@mantine/dates":i,react:n,"react-dom":n,storybook:s,"@storybook/react-vite":s,...m,...p}}}const f=`import { createMainConfig } from "@recursica/storybook-template/main";
2
-
3
- const config = createMainConfig({
4
- stories: [
5
- "../node_modules/@recursica/mantine-adapter/src/**/*.stories.@(js|jsx|mjs|ts|tsx)",
6
- ],
7
- enableCORS: true,
8
- });
9
-
10
- // react-docgen-typescript can't resolve a TS project for a config file living
11
- // in .storybook/ when the component source it's docgen'ing lives three
12
- // directories down inside node_modules — it throws "Cannot read properties
13
- // of undefined (reading 'fileExists')", which surfaces as a plain 404 on
14
- // preview.tsx. Docgen only powers Storybook's Controls/Docs tables, which
15
- // this harness never renders, so disabling it is a safe workaround (see
16
- // PROPOSAL-installed-package-harness.md, gap 3).
17
- config.typescript = { ...config.typescript, reactDocgen: false };
18
-
19
- export default config;
20
- `,y=`import type { Preview } from "@storybook/react-vite";
21
- import { createPreviewConfig } from "@recursica/storybook-template/preview";
22
- import { MantineProvider } from "@mantine/core";
23
- import "@mantine/core/styles.css";
24
- import "@mantine/dates/styles.css";
25
- import "@recursica/adapter-common/style.css";
26
- import "@recursica/official-release/recursica_variables_scoped.css";
27
- import recursicaTokens from "@recursica/official-release/recursica_tokens.json";
28
- import recursicaBrand from "@recursica/official-release/recursica_brand.json";
29
- import recursicaUIKit from "@recursica/official-release/recursica_ui-kit.json";
30
-
31
- const basePreview = createPreviewConfig({
32
- defaultTheme: "light",
33
- recursicaTokensJsonPath: recursicaTokens,
34
- recursicaBrandJsonPath: recursicaBrand,
35
- recursicaUIKitJsonPath: recursicaUIKit,
36
- });
37
-
38
- const preview: Preview = {
39
- ...basePreview,
40
- decorators: [
41
- (Story) => (
42
- <MantineProvider>
43
- <Story />
44
- </MantineProvider>
45
- ),
46
- ...(basePreview.decorators || []),
47
- ],
48
- };
49
-
50
- export default preview;
51
- `;function a(e){const{dir:r,port:c,mantineAdapterVersion:u="latest",storybookTemplateVersion:d="latest"}=e;return o.mkdirSync(t.join(r,".storybook"),{recursive:!0}),o.writeFileSync(t.join(r,"package.json"),JSON.stringify(l({mantineAdapterVersion:u,storybookTemplateVersion:d,port:c}),null,2)+`
52
- `),o.writeFileSync(t.join(r,".storybook/main.ts"),f),o.writeFileSync(t.join(r,".storybook/preview.tsx"),y),o.writeFileSync(t.join(r,".gitignore"),`node_modules
53
- `),r}function k(e){const r=a(e);return{command:"npm install --no-audit --no-fund && npm run storybook",port:e.port,cwd:r,reuseExistingServer:!process.env.CI,timeout:180*1e3}}exports.mantineSourceOfTruthWebServer=k;exports.scaffoldMantineSourceOfTruthHarness=a;
54
- //# sourceMappingURL=mantineSourceOfTruth-DCoJ2dU-.cjs.map