|
| 1 | +// Playwright component gallery — implements the contract in the playwright-component-testing |
| 2 | +// skill (references/gallery-spec.md): a single page exposing window.mount()/window.unmount(). |
| 3 | +// The built-in `mount` fixture navigates here (baseURL) and calls window.mount via |
| 4 | +// page.evaluate(..., { exposeFunctions: true }), so props may carry real callbacks. |
| 5 | +import { flushSync } from 'react-dom'; |
| 6 | +import { createRoot, type Root } from 'react-dom/client'; |
| 7 | +import '../../src/assets/index.css'; |
| 8 | + |
| 9 | +// import.meta.glob must stay inline: Vite analyzes it statically, relative to this file. |
| 10 | +const stories = import.meta.glob('../../src/**/*.story.tsx'); |
| 11 | +const id = (f: string) => f.replace(/^(\.\.\/)+src\//, '').replace(/\.story\.\w+$/, ''); |
| 12 | + |
| 13 | +// Story id is '<path under src, without .story.tsx>/<ExportName>', e.g. 'components/Button/Default'. |
| 14 | +async function resolve(storyId: string) { |
| 15 | + const sep = storyId.lastIndexOf('/'); |
| 16 | + const [path, name] = [storyId.slice(0, sep), storyId.slice(sep + 1)]; |
| 17 | + const file = Object.keys(stories).find(f => id(f) === path || id(f).endsWith('/' + path)); |
| 18 | + const mod = (file && await stories[file]()) as Record<string, any> | undefined; |
| 19 | + return mod?.[name] ?? mod?.default; |
| 20 | +} |
| 21 | + |
| 22 | +const rootEl = document.getElementById('root')!; |
| 23 | +let root: Root | undefined; |
| 24 | + |
| 25 | +(window as any).mount = async ({ story, props }: { story: string, props?: Record<string, any> }) => { |
| 26 | + const Story = await resolve(story); |
| 27 | + if (!Story) |
| 28 | + throw new Error(`Unknown story: ${story}`); |
| 29 | + // Reuse the root so component.update() reconciles in place and preserves state. |
| 30 | + root ??= createRoot(rootEl); |
| 31 | + // flushSync so a render error rejects the promise instead of being swallowed. |
| 32 | + flushSync(() => root!.render(<Story {...props} />)); |
| 33 | +}; |
| 34 | + |
| 35 | +(window as any).unmount = async () => { |
| 36 | + root?.unmount(); |
| 37 | + root = undefined; |
| 38 | +}; |
0 commit comments