@juxtapose/adapter-ladle 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +17 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +127 -0
- package/dist/index.js.map +1 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Magic Factory LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @juxtapose/adapter-ladle
|
|
2
|
+
|
|
3
|
+
Ladle platform adapter for juxtapose visual testing.
|
|
4
|
+
|
|
5
|
+
Part of [juxtapose](https://github.com/juxtapose-dev/juxtapose), visual
|
|
6
|
+
regression testing for component workbenches.
|
|
7
|
+
|
|
8
|
+
**You probably don't need to install this directly.** Use
|
|
9
|
+
[`@juxtapose/cli`](https://www.npmjs.com/package/@juxtapose/cli) for the
|
|
10
|
+
command line, or
|
|
11
|
+
[`@juxtapose/storybook`](https://www.npmjs.com/package/@juxtapose/storybook)
|
|
12
|
+
to review diffs inside Storybook. This package is a building block those
|
|
13
|
+
depend on.
|
|
14
|
+
|
|
15
|
+
Full documentation: https://github.com/juxtapose-dev/juxtapose
|
|
16
|
+
|
|
17
|
+
MIT © Magic Factory LLC
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Page } from 'playwright';
|
|
2
|
+
import type { Platform, EnumerateTarget, SettleContext, StoryManifest } from '@juxtapose/core';
|
|
3
|
+
export declare class LadlePlatform implements Platform {
|
|
4
|
+
readonly name = "ladle";
|
|
5
|
+
detect(target: EnumerateTarget): Promise<boolean>;
|
|
6
|
+
enumerate(target: EnumerateTarget): Promise<StoryManifest[]>;
|
|
7
|
+
storyUrl(baseUrl: string, storyId: string): string;
|
|
8
|
+
/** URL-addressable, so showing a story is just a navigation. */
|
|
9
|
+
showStory(page: Page, storyId: string, baseUrl: string): Promise<void>;
|
|
10
|
+
previewUrl(baseUrl: string): string;
|
|
11
|
+
settle(page: Page, ctx: SettleContext): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Switch stories without a navigation via Ladle's exposed dispatch.
|
|
14
|
+
*
|
|
15
|
+
* `window.ladleDispatch` is set deliberately — Ladle's own public `linkTo()`
|
|
16
|
+
* API is built on it — so this is a supported path, not a private poke.
|
|
17
|
+
*
|
|
18
|
+
* The subtlety: `data-storyloaded` is a plain attribute toggle, so a naive
|
|
19
|
+
* wait after dispatching can match the *outgoing* story's attribute before
|
|
20
|
+
* React tears it down. Clear it ourselves first, then wait for it to come
|
|
21
|
+
* back, which makes the signal unambiguous.
|
|
22
|
+
*/
|
|
23
|
+
switchStory(page: Page, storyId: string, ctx: SettleContext): Promise<boolean>;
|
|
24
|
+
readonly configDirs: string[];
|
|
25
|
+
readonly storyFileGlobs: string[];
|
|
26
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Ladle platform adapter.
|
|
2
|
+
//
|
|
3
|
+
// Written second, against the interface the Storybook adapter shaped. It needed
|
|
4
|
+
// no changes to `Platform` — which is the evidence the seam is real rather than
|
|
5
|
+
// a Storybook API wearing a trenchcoat.
|
|
6
|
+
import { readFile } from 'node:fs/promises';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { navigateToStory } from '@juxtapose/core';
|
|
9
|
+
/** Where a story declares juxtapose config. Ladle's `meta` is an open index
|
|
10
|
+
* signature, so a namespaced key passes through untouched. */
|
|
11
|
+
const META_NAMESPACE = 'juxtapose';
|
|
12
|
+
export class LadlePlatform {
|
|
13
|
+
name = 'ladle';
|
|
14
|
+
async detect(target) {
|
|
15
|
+
return (await loadMeta(target)) !== null;
|
|
16
|
+
}
|
|
17
|
+
async enumerate(target) {
|
|
18
|
+
const meta = await loadMeta(target);
|
|
19
|
+
if (!meta) {
|
|
20
|
+
throw new Error(`[ladle] no meta.json at ${target.origin ?? target.buildDir}. ` +
|
|
21
|
+
`Run \`ladle build\`, or point --origin at a running dev server.`);
|
|
22
|
+
}
|
|
23
|
+
return Object.entries(meta.stories)
|
|
24
|
+
.map(([id, story]) => ({
|
|
25
|
+
storyId: id,
|
|
26
|
+
title: story.levels.join('/'),
|
|
27
|
+
name: story.name,
|
|
28
|
+
// Empty when absent rather than fabricated — affected-story analysis
|
|
29
|
+
// treats a blank path as "can't prove unaffected" and captures it.
|
|
30
|
+
componentPath: story.filePath
|
|
31
|
+
? path.resolve(target.projectRoot, story.filePath)
|
|
32
|
+
: '',
|
|
33
|
+
params: readParams(story.meta),
|
|
34
|
+
}))
|
|
35
|
+
.sort((a, b) => a.storyId.localeCompare(b.storyId));
|
|
36
|
+
}
|
|
37
|
+
storyUrl(baseUrl, storyId) {
|
|
38
|
+
// No iframe route; `mode=preview` is what strips the sidebar and addon
|
|
39
|
+
// chrome, leaving only the story.
|
|
40
|
+
return `${stripSlash(baseUrl)}/?story=${encodeURIComponent(storyId)}&mode=preview`;
|
|
41
|
+
}
|
|
42
|
+
/** URL-addressable, so showing a story is just a navigation. */
|
|
43
|
+
async showStory(page, storyId, baseUrl) {
|
|
44
|
+
await navigateToStory((b, s) => this.storyUrl(b, s))(page, storyId, baseUrl);
|
|
45
|
+
}
|
|
46
|
+
previewUrl(baseUrl) {
|
|
47
|
+
return `${stripSlash(baseUrl)}/?mode=preview`;
|
|
48
|
+
}
|
|
49
|
+
async settle(page, ctx) {
|
|
50
|
+
await waitForStoryLoaded(page, ctx.timeoutMs);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Switch stories without a navigation via Ladle's exposed dispatch.
|
|
54
|
+
*
|
|
55
|
+
* `window.ladleDispatch` is set deliberately — Ladle's own public `linkTo()`
|
|
56
|
+
* API is built on it — so this is a supported path, not a private poke.
|
|
57
|
+
*
|
|
58
|
+
* The subtlety: `data-storyloaded` is a plain attribute toggle, so a naive
|
|
59
|
+
* wait after dispatching can match the *outgoing* story's attribute before
|
|
60
|
+
* React tears it down. Clear it ourselves first, then wait for it to come
|
|
61
|
+
* back, which makes the signal unambiguous.
|
|
62
|
+
*/
|
|
63
|
+
async switchStory(page, storyId, ctx) {
|
|
64
|
+
const dispatched = await page
|
|
65
|
+
.evaluate((id) => {
|
|
66
|
+
const w = window;
|
|
67
|
+
if (typeof w.ladleDispatch !== 'function')
|
|
68
|
+
return false;
|
|
69
|
+
document.documentElement.removeAttribute('data-storyloaded');
|
|
70
|
+
w.ladleDispatch({ type: 'update-story', value: id });
|
|
71
|
+
return true;
|
|
72
|
+
}, storyId)
|
|
73
|
+
.catch(() => false);
|
|
74
|
+
if (!dispatched)
|
|
75
|
+
return false;
|
|
76
|
+
await waitForStoryLoaded(page, ctx.timeoutMs);
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
configDirs = ['.ladle'];
|
|
80
|
+
storyFileGlobs = ['**/*.stories.@(ts|tsx|js|jsx|mdx)'];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Ladle's authoritative render signal: `data-storyloaded` on <html>.
|
|
84
|
+
*
|
|
85
|
+
* Set by a component rendered inside Suspense after the story commits, and
|
|
86
|
+
* removed by the loading spinner on mount — so its presence means the story
|
|
87
|
+
* chunk loaded and React committed, not merely that the page fired `load`.
|
|
88
|
+
* This is what Ladle's own screenshot suite waits on.
|
|
89
|
+
*/
|
|
90
|
+
async function waitForStoryLoaded(page, timeoutMs) {
|
|
91
|
+
await page
|
|
92
|
+
.waitForSelector('[data-storyloaded]', { timeout: timeoutMs })
|
|
93
|
+
// Timing out is not fatal: capture what rendered. A slow story should
|
|
94
|
+
// surface as a reviewable diff, not abort the run.
|
|
95
|
+
.catch(() => { });
|
|
96
|
+
}
|
|
97
|
+
function readParams(meta) {
|
|
98
|
+
const ns = meta?.[META_NAMESPACE];
|
|
99
|
+
return ns && typeof ns === 'object' ? ns : {};
|
|
100
|
+
}
|
|
101
|
+
async function loadMeta(target) {
|
|
102
|
+
if (target.origin) {
|
|
103
|
+
try {
|
|
104
|
+
const res = await fetch(new URL('meta.json', withSlash(target.origin)));
|
|
105
|
+
if (!res.ok)
|
|
106
|
+
return null;
|
|
107
|
+
const parsed = (await res.json());
|
|
108
|
+
return parsed?.stories ? parsed : null;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (!target.buildDir)
|
|
115
|
+
return null;
|
|
116
|
+
try {
|
|
117
|
+
const raw = await readFile(path.join(target.buildDir, 'meta.json'), 'utf8');
|
|
118
|
+
const parsed = JSON.parse(raw);
|
|
119
|
+
return parsed?.stories ? parsed : null;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const withSlash = (u) => (u.endsWith('/') ? u : u + '/');
|
|
126
|
+
const stripSlash = (u) => u.replace(/\/+$/, '');
|
|
127
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@juxtapose/adapter-ladle",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Ladle platform adapter for juxtapose visual testing.",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./dist/index.js",
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@juxtapose/core": "0.2.0"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"playwright": "^1.48.0"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/juxtapose-dev/juxtapose.git",
|
|
28
|
+
"directory": "packages/adapter-ladle"
|
|
29
|
+
},
|
|
30
|
+
"peerDependenciesMeta": {
|
|
31
|
+
"playwright": {
|
|
32
|
+
"optional": true
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc -p tsconfig.json"
|
|
37
|
+
}
|
|
38
|
+
}
|