@recursica/adapter-tester 2.2.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +69 -23
  2. package/dist/adapter-tester.schema.json.d.ts +24 -11
  3. package/dist/cli.cjs +32 -23
  4. package/dist/cli.cjs.map +1 -1
  5. package/dist/cli.js +296 -4853
  6. package/dist/cli.js.map +1 -1
  7. package/dist/config.d.ts +57 -6
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/fileConfig.d.ts +21 -5
  10. package/dist/fileConfig.d.ts.map +1 -1
  11. package/dist/golden/diffPng.d.ts +13 -0
  12. package/dist/golden/diffPng.d.ts.map +1 -0
  13. package/dist/golden/manifest.schema.json.d.ts +27 -0
  14. package/dist/golden/manifestStore.d.ts +33 -0
  15. package/dist/golden/manifestStore.d.ts.map +1 -0
  16. package/dist/golden/resolveSourceOfTruthGolden.d.ts +29 -0
  17. package/dist/golden/resolveSourceOfTruthGolden.d.ts.map +1 -0
  18. package/dist/golden/validateManifest.d.ts +7 -0
  19. package/dist/golden/validateManifest.d.ts.map +1 -0
  20. package/dist/index-C6uYPRmx.cjs +9 -0
  21. package/dist/index-C6uYPRmx.cjs.map +1 -0
  22. package/dist/index-DqQzFSAH.js +4667 -0
  23. package/dist/index-DqQzFSAH.js.map +1 -0
  24. package/dist/index.cjs +1 -1
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.js +21 -6
  27. package/dist/index.js.map +1 -1
  28. package/dist/testing/runVisualRegression.d.ts +48 -6
  29. package/dist/testing/runVisualRegression.d.ts.map +1 -1
  30. package/dist/testing.cjs +4 -1
  31. package/dist/testing.cjs.map +1 -1
  32. package/dist/testing.d.ts +1 -1
  33. package/dist/testing.d.ts.map +1 -1
  34. package/dist/testing.js +311 -155
  35. package/dist/testing.js.map +1 -1
  36. package/package.json +4 -7
  37. package/src/adapter-tester.schema.json +24 -11
  38. package/src/golden/manifest.schema.json +24 -0
  39. package/dist/config-B0Eop8Az.cjs +0 -2
  40. package/dist/config-B0Eop8Az.cjs.map +0 -1
  41. package/dist/config-CDxTeSAY.js +0 -21
  42. package/dist/config-CDxTeSAY.js.map +0 -1
package/README.md CHANGED
@@ -8,13 +8,13 @@ The `@recursica/adapter-tester` ensures that a Recursica adapter's components vi
8
8
 
9
9
  This package provides three primary utilities:
10
10
 
11
- 1. **Automated Visual Tests:** Headless, pixel-by-pixel regression checks for all stories, run as a single CLI command (`adapter-tester`) reading a small JSON config — no Playwright config or spec files to author.
11
+ 1. **Automated Visual Tests:** Headless, golden-image regression checks for all stories, run as a single CLI command (`adapter-tester`) reading a small JSON config — no Playwright config or spec files to author. See [Golden Images](#golden-images) below for the model.
12
12
  2. **Interactive Dev Mode:** A side-by-side synchronized browser environment with built-in note taking for auditing components and feeding fixes directly to an AI agent.
13
- 3. **An installable devDependency**, currently wired into `@recursica/mui-adapter` (not `@recursica/mantine-adapter` Mantine is itself the source of truth, so it has nothing to diff against). Any other adapter repo — including one that never checked out this monorepo — can install it the same way. See [Using this package in another adapter repo](#using-this-package-in-another-adapter-repo).
13
+ 3. **An installable devDependency**, wired into `@recursica/mantine-adapter` the source-of-truth adapter, run in `isSourceOfTruthAdapter` mode (own-drift check only, since it has nothing above it to diverge from). Any other adapter repo — including one that never checked out this monorepo — can install it the same way. See [Using this package in another adapter repo](#using-this-package-in-another-adapter-repo).
14
14
 
15
- **Default mode** (used by `@recursica/mui-adapter`, and any external adapter repo installing this package): compares this project's own Storybook — the one already defined by its `storybook` npm script — against a throwaway harness that installs the _published_ `@recursica/mantine-adapter` from npm. No monorepo checkout, no manual config, in most cases no config file at all.
15
+ **Default mode** (used by any adapter repo installing this package): checks this project's own Storybook — the one already defined by its `storybook` npm script — against its own committed golden images. No monorepo checkout, no manual config, in most cases no config file at all. Divergence from the source-of-truth adapter's (`@recursica/mantine-adapter`) own golden images is a separate, opt-in check — see [`--divergence-only`](#automated-visual-tests).
16
16
 
17
- **Non-standard mode**: this monorepo also runs adapter-tester against itself Mantine and MUI both as local sibling workspace packagesto catch regressions in _uncommitted_ code before it's published. See `adapter-tester.config.json` in this package for that config; it's the same CLI, just pointed at `sourceOfTruth.type: "url"` instead of the default Mantine harness.
17
+ `@recursica/adapter-tester` itself has no default mode and no `adapter-tester.config.json` of its ownit's purely a tool other adapters install and configure. `mantine-adapter` and `mui-adapter` are the ones that run it, each with their own config.
18
18
 
19
19
  ---
20
20
 
@@ -26,13 +26,15 @@ Installing `@recursica/adapter-tester` as a devDependency (see [below](#using-th
26
26
  {
27
27
  "scripts": {
28
28
  "adapter-tester": "adapter-tester --serve",
29
- "adapter-tester:automated": "adapter-tester"
29
+ "adapter-tester:automated": "adapter-tester",
30
+ "adapter-tester:source-of-truth": "adapter-tester --divergence-only"
30
31
  }
31
32
  }
32
33
  ```
33
34
 
34
35
  - `npm run adapter-tester` — Interactive Dev Mode: boots both Storybooks (reusing them if already running) and opens the synced side-by-side comparison browser.
35
- - `npm run adapter-tester:automated` — the headless Playwright pixel-diff suite; see [Automated Visual Tests](#automated-visual-tests) below.
36
+ - `npm run adapter-tester:automated` — the headless own-drift check against this project's own committed goldens; see [Automated Visual Tests](#automated-visual-tests) below. This is the one to run normally — fast, no network calls.
37
+ - `npm run adapter-tester:source-of-truth` — the separate, opt-in divergence check against the source-of-truth adapter's published goldens. Not wired up for `@recursica/mantine-adapter` itself — it _is_ the source of truth, so it has nothing to diverge from.
36
38
 
37
39
  Both boot Storybooks themselves — nothing needs to be running beforehand.
38
40
 
@@ -76,10 +78,11 @@ The Dev Mode server fetches this file dynamically, so any changes you make to `r
76
78
 
77
79
  ## Automated Visual Tests
78
80
 
79
- To execute a full headless visual regression scan across all components:
81
+ Two independent checks, gated by `--divergence-only` the normal, everyday one and a separate opt-in one:
80
82
 
81
83
  ```bash
82
- npm run adapter-tester:automated # runs the `adapter-tester` CLI
84
+ npm run adapter-tester:automated # own-drift check only — the normal one to run
85
+ npm run adapter-tester:source-of-truth # divergence check only — opt-in, against Mantine
83
86
  ```
84
87
 
85
88
  `adapter-tester` reads `adapter-tester.config.json` from the current directory (or falls back to defaults if the file doesn't exist), generates a throwaway Playwright config + spec under `.adapter-tester/run/` (git-ignore this directory), and runs it. There's no Playwright config or spec file to hand-author.
@@ -92,13 +95,22 @@ Any other args are forwarded to `playwright test`, so you can scope a run to spe
92
95
  npm run adapter-tester:automated -- --grep "Toast"
93
96
  ```
94
97
 
98
+ Pin the source-of-truth adapter's version for the divergence check (instead of resolving `@recursica/mantine-adapter`'s `latest` on npm) with `--source-of-truth-version`:
99
+
100
+ ```bash
101
+ npm run adapter-tester:source-of-truth -- --source-of-truth-version 0.53.0
102
+ ```
103
+
104
+ Only meaningful with the default `sourceOfTruth.type: "mantine-harness"` mode — throws if combined with `sourceOfTruth.type: "url"` (that mode reads a sibling package's local checkout directly, there's no version to pin) or with `isSourceOfTruthAdapter: true` (nothing to pin a version for).
105
+
95
106
  ### What happens during execution?
96
107
 
97
- 1. **Automatic Storybook Bootup**: `adapter-tester` boots both sides of the comparison — this project's own Storybook, and the source-of-truth Storybook (a throwaway Mantine harness by default) reusing them if already running.
98
- 2. **Story Discovery**: The test suite fetches the source of truth's Storybook index (`/index.json`) to dynamically discover and parameterize tests for every component story, excluding `@recursica/storybook-template`'s own default token/theme demo stories (`Theme/*`, `Tokens/*`) and each adapter's own onboarding stories (`Introduction/*`).
99
- 3. **Headless Snapshots**: It launches headless Chrome in the background, navigates to the isolated iframe views for both targets, and takes side-by-side snapshots.
100
- 4. **Pixel Diffing**: It diffs the images using `pixelmatch` against the configured mismatch threshold.
101
- 5. **Native Report Generation**: All raw snapshots, highlighted pixel diff overlays, and extracted computed CSS DOM trees are embedded directly as test attachments.
108
+ 1. **Automatic Storybook Bootup**: `adapter-tester` boots only this project's own Storybook the divergence check reads the source-of-truth adapter's stored golden files, never a live Storybook, so nothing else needs to boot.
109
+ 2. **Story Discovery**: The test suite fetches this project's own Storybook index (`/index.json`) to dynamically discover and parameterize tests for every component story, excluding `@recursica/storybook-template`'s own default token/theme demo stories (`Theme/*`, `Tokens/*`) and each adapter's own onboarding stories (`Introduction/*`).
110
+ 3. **Headless Snapshot**: It launches headless Chrome, navigates to the isolated iframe view, and takes a screenshot. A missing golden is captured from this screenshot regardless of which check is running.
111
+ 4. **Own-Drift Check** (`adapter-tester:automated`): Diffs the screenshot against this project's own `test/golden/<story-id>.png` using `pixelmatch`, against the configured mismatch threshold. No golden yet for a story is not a failure — one is captured from this run.
112
+ 5. **Divergence Check** (`adapter-tester:source-of-truth`): Diffs this project's own golden against the source-of-truth adapter's golden (skipped for the source-of-truth adapter's own config). Different and not yet reviewed via `--approve-divergence`? Flagged as a report annotation — never a failure on its own.
113
+ 6. **Native Report Generation**: Screenshots, diff overlays, and divergence flags are embedded directly as test attachments/annotations.
102
114
 
103
115
  ### Output & Reports
104
116
 
@@ -113,6 +125,36 @@ All test outcomes and visual outputs are compiled into the standard, git-ignored
113
125
 
114
126
  ---
115
127
 
128
+ ## Golden Images
129
+
130
+ Each adapter's `test/golden/` directory — committed to git, alongside a `manifest.json` tracking when each was captured — is its own baseline:
131
+
132
+ - **Own-drift check (hard fail):** this run's live render vs this project's own golden. Catches unintended CSS regressions.
133
+ - **Divergence check (soft flag, never fails a run):** this project's own golden vs the source-of-truth adapter's golden — different underlying UI libraries can legitimately render a component differently, so a divergence isn't automatically wrong, just unreviewed.
134
+
135
+ These are explicit, developer-run workflows — not run in CI, and not scoped down automatically; use `--grep` to target specific stories.
136
+
137
+ ```bash
138
+ # Capture/refresh this project's own goldens for stories matching "Toast",
139
+ # from the current live render. Use after an intentional styling change.
140
+ npm run adapter-tester:automated -- --grep "Toast" --update-golden
141
+
142
+ # Review a flagged divergence from the source of truth, and accept it as
143
+ # intentional. Only valid alongside --divergence-only — there's nothing to
144
+ # approve without the divergence check running.
145
+ npm run adapter-tester:source-of-truth -- --grep "Toast" --approve-divergence
146
+ ```
147
+
148
+ `--approve-divergence` records the source-of-truth adapter's own `manifest.json` `createdAt` for that story at review time. If the source of truth's golden is recaptured later, the divergence flags again for re-review — approval isn't a permanent exemption.
149
+
150
+ A story with no golden yet (new story, or one never captured) is not a failure in either mode — one is captured from the current run automatically.
151
+
152
+ Renamed/removed stories just leave an orphaned `test/golden/<story-id>.png`/manifest entry behind; nothing prunes these automatically.
153
+
154
+ `manifest.json` is validated against [`src/golden/manifest.schema.json`](./src/golden/manifest.schema.json) on every read/write — don't hand-edit it.
155
+
156
+ ---
157
+
116
158
  ## Configuring `adapter-tester.config.json`
117
159
 
118
160
  All fields are optional — an adapter repo with a normal `storybook` npm script (e.g. `storybook dev -p 6012`) needs no config file at all.
@@ -121,13 +163,15 @@ Every `adapter-tester.config.json` is validated against [`src/adapter-tester.sch
121
163
 
122
164
  ```json
123
165
  {
124
- "$schema": "./node_modules/@recursica/adapter-tester/src/adapter-tester.schema.json",
166
+ "$schema": "https://raw.githubusercontent.com/borderux/recursica/main/packages/adapter-tester/src/adapter-tester.schema.json",
125
167
  "name": "MyAdapter",
126
168
  "storybook": { "port": 6006, "command": "npm run storybook" },
127
169
  "sourceOfTruth": { "type": "mantine-harness" },
128
170
  "diffThresholdPixels": 3500,
129
- "relaxedThresholdStoryIds": ["ui-kit-slider"],
130
- "relaxedThresholdPixels": 15000,
171
+ "stories": {
172
+ "ui-kit-slider": { "threshold": 15000 },
173
+ "ui-kit-slider--range-mode-with-icons-and-inputs": { "exclude": true }
174
+ },
131
175
  "excludeTitlePrefixes": ["Theme", "Tokens", "Introduction"]
132
176
  }
133
177
  ```
@@ -135,9 +179,10 @@ Every `adapter-tester.config.json` is validated against [`src/adapter-tester.sch
135
179
  - `name` — label for this project's own target. Defaults to the unscoped `package.json` name.
136
180
  - `storybook.port` — defaults to whatever `-p`/`--port` is set on this project's own `storybook` script; falls back to Storybook's default of `6006`.
137
181
  - `storybook.command`/`storybook.cwd` — default to `npm run storybook` in the current directory.
138
- - `sourceOfTruth` — defaults to `{ "type": "mantine-harness" }`: a throwaway harness that installs the _published_ `@recursica/mantine-adapter` from npm, so you never need this monorepo checked out. This is the standard mode every external adapter repo (and `@recursica/mui-adapter`) uses.
139
- - `sourceOfTruth.type: "url"` — the **non-standard** mode: points at an already-addressable Storybook via `command`/`port`/`cwd` instead of the harness. Used inside this monorepo to compare local, uncommitted `mantine-adapter`/`mui-adapter` workspace packages against each other see `adapter-tester.config.json` in this package.
140
- - `diffThresholdPixels`/`relaxedThresholdStoryIds`/`relaxedThresholdPixels`/`excludeTitlePrefixes` — same meaning as before; see `src/adapter-tester.schema.json` for full docs.
182
+ - `sourceOfTruth` — defaults to `{ "type": "mantine-harness" }`: a throwaway harness that installs the _published_ `@recursica/mantine-adapter` from npm, so you never need this monorepo checked out. This is the standard mode every external adapter repo uses. `sourceOfTruth.mantineAdapterVersion` pins the version installed/fetched (defaults to `latest`) — or override it per-run with `--source-of-truth-version` instead of hardcoding it in the config.
183
+ - `sourceOfTruth.type: "url"` — the **non-standard** mode: points at an already-addressable Storybook via `command`/`port`/`cwd` instead of the harness, and reads that sibling package's `test/golden/` directly from disk for the divergence check (including uncommitted local changes) instead of resolving a published npm version. For comparing two locally checked-out sibling adapters instead of a published one.
184
+ - `isSourceOfTruthAdapter` — set `true` only in the source-of-truth adapter's own config (`@recursica/mantine-adapter`). Skips `sourceOfTruth`/the divergence check entirely; runs the own-drift check standalone. Also disables Dev Mode (`--serve`) there's nothing to sync against.
185
+ - `diffThresholdPixels`/`stories`/`excludeTitlePrefixes` — same meaning as before; see `src/adapter-tester.schema.json` for full docs.
141
186
 
142
187
  ---
143
188
 
@@ -150,11 +195,12 @@ npm install --save-dev @recursica/adapter-tester @playwright/test
150
195
  npx playwright install chromium
151
196
  ```
152
197
 
153
- If your repo already has a `storybook` npm script with a port set (`storybook dev -p 6006`), no config file is required. Add the two scripts from [npm scripts](#npm-scripts) above to your `package.json`, then:
198
+ If your repo already has a `storybook` npm script with a port set (`storybook dev -p 6006`), no config file is required. Add the scripts from [npm scripts](#npm-scripts) above to your `package.json`, then:
154
199
 
155
200
  ```bash
156
- npm run adapter-tester # Interactive Dev Mode
157
- npm run adapter-tester:automated # headless Playwright suite
201
+ npm run adapter-tester # Interactive Dev Mode
202
+ npm run adapter-tester:automated # own-drift check — run this normally
203
+ npm run adapter-tester:source-of-truth # divergence check — opt-in, against Mantine
158
204
  ```
159
205
 
160
206
  Any extra args after `adapter-tester:automated` are passed straight through to `playwright test`, so you can scope a run instead of executing the full suite:
@@ -163,4 +209,4 @@ Any extra args after `adapter-tester:automated` are passed straight through to `
163
209
  npm run adapter-tester:automated -- --grep "Toast"
164
210
  ```
165
211
 
166
- Add `adapter-tester.config.json` only to override defaults (see [Configuring `adapter-tester.config.json`](#configuring-adapter-testerconfigjson) above), e.g. to raise the diff threshold or relax specific stories. Add `.adapter-tester/` to your `.gitignore` — it's regenerated on every run.
212
+ Add `adapter-tester.config.json` only to override defaults (see [Configuring `adapter-tester.config.json`](#configuring-adapter-testerconfigjson) above), e.g. to raise the diff threshold or relax specific stories. Add `.adapter-tester/` to your `.gitignore` — it's regenerated on every run. Commit `test/golden/` — that's your project's actual baseline, not a build artifact.
@@ -8,11 +8,11 @@ declare const _default: {
8
8
  "properties": {
9
9
  "$schema": {
10
10
  "type": "string",
11
- "description": "Optional pointer to this schema for editor autocomplete, e.g. \"./node_modules/@recursica/adapter-tester/src/adapter-tester.schema.json\"."
11
+ "description": "Optional pointer to this schema for editor autocomplete, e.g. \"https://raw.githubusercontent.com/borderux/recursica/main/packages/adapter-tester/src/adapter-tester.schema.json\"."
12
12
  },
13
13
  "name": {
14
14
  "type": "string",
15
- "description": "Label for this project's own target. Defaults to the unscoped name in this project's package.json (e.g. \"@recursica/mui-adapter\" -> \"mui-adapter\")."
15
+ "description": "Label for this project's own target. Defaults to the unscoped name in this project's package.json (e.g. \"@recursica/some-adapter\" -> \"some-adapter\")."
16
16
  },
17
17
  "storybook": {
18
18
  "type": "object",
@@ -89,20 +89,33 @@ declare const _default: {
89
89
  }
90
90
  ]
91
91
  },
92
+ "isSourceOfTruthAdapter": {
93
+ "type": "boolean",
94
+ "description": "True only for the source-of-truth adapter's own config (mantine-adapter). Skips `sourceOfTruth` entirely — there's nothing above it to diverge from — and runs the own-drift golden check standalone, against just this project's own Storybook. Defaults to false."
95
+ },
92
96
  "diffThresholdPixels": {
93
97
  "type": "number",
94
98
  "minimum": 0,
95
99
  "description": "Global visual diff threshold, in mismatched pixels, before a story is considered a failure. Defaults to 3500. AI agents must not modify this field — see AGENT.md."
96
100
  },
97
- "relaxedThresholdStoryIds": {
98
- "type": "array",
99
- "items": { "type": "string" },
100
- "description": "Story id prefixes that use `relaxedThresholdPixels` instead of the global `diffThresholdPixels`."
101
- },
102
- "relaxedThresholdPixels": {
103
- "type": "number",
104
- "minimum": 0,
105
- "description": "Diff threshold applied to stories matching `relaxedThresholdStoryIds`."
101
+ "stories": {
102
+ "type": "object",
103
+ "additionalProperties": {
104
+ "type": "object",
105
+ "additionalProperties": false,
106
+ "properties": {
107
+ "threshold": {
108
+ "type": "number",
109
+ "minimum": 0,
110
+ "description": "Diff threshold override for matching stories, in mismatched pixels. Overrides `diffThresholdPixels`."
111
+ },
112
+ "exclude": {
113
+ "type": "boolean",
114
+ "description": "Skip matching stories entirely — no own-drift check, no divergence check, no golden captured. For stories that don't have a cross-adapter counterpart; use `excludeTitlePrefixes` to drop a whole title instead."
115
+ }
116
+ }
117
+ },
118
+ "description": "Per-story overrides, keyed by story id prefix (a story matches if its id equals the key or starts with it). When more than one key matches a story, the longest (most specific) key wins."
106
119
  },
107
120
  "excludeTitlePrefixes": {
108
121
  "type": "array",