@storybook-astro/framework 1.6.0 → 1.7.0-canary.2

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/testing.js CHANGED
@@ -10,11 +10,21 @@ import {
10
10
  composeStory,
11
11
  setProjectAnnotations
12
12
  } from "./chunk-KXAAX3GN.js";
13
+ import {
14
+ separateStorySlots
15
+ } from "./chunk-YRG32BBU.js";
13
16
  import {
14
17
  ssrLoadModuleWithFsFallback
15
18
  } from "./chunk-BF3USGCF.js";
16
19
  import "./chunk-PUTCAN6X.js";
20
+ import {
21
+ markRawSlots,
22
+ patchCreateAstroCompat,
23
+ reconstructProps,
24
+ reconstructSlots
25
+ } from "./chunk-5POAXNWB.js";
17
26
  import "./chunk-7YBE4TTI.js";
27
+ import "./chunk-ZIDMHD4S.js";
18
28
  import "./chunk-B5HHF6FC.js";
19
29
  import "./chunk-G3PMV62Z.js";
20
30
 
@@ -194,6 +204,7 @@ async function resolveTestingProjectRoot(component) {
194
204
  }
195
205
 
196
206
  // src/testing/astro-runtime.ts
207
+ import { serializeAstroComponentMarkers } from "@storybook-astro/renderer/types";
197
208
  var astroContainerPromise = null;
198
209
  var astroSsrViteServerPromises = /* @__PURE__ */ new Map();
199
210
  var astroSsrHandlerPromises = /* @__PURE__ */ new Map();
@@ -274,20 +285,27 @@ async function resolveAstroComponent(component, resolveFrom) {
274
285
  }
275
286
  return resolvedComponent;
276
287
  }
288
+ function setRenderedHtml(html) {
289
+ if (typeof document !== "undefined") {
290
+ document.body.innerHTML = html;
291
+ }
292
+ return html;
293
+ }
277
294
  async function renderAstroComponentToDom(component, args, resolveFrom) {
278
295
  const moduleId = getComponentModuleId(component);
296
+ const { componentArgs, storySlots } = separateStorySlots(args);
297
+ const serializedArgs = serializeAstroComponentMarkers(componentArgs);
298
+ const serializedSlots = serializeAstroComponentMarkers(storySlots);
279
299
  if (moduleId) {
280
300
  try {
281
301
  const html2 = await renderViaTestingRendererDaemon({
282
302
  resolveFrom,
283
303
  component: moduleId,
284
- args
304
+ args: serializedArgs,
305
+ slots: serializedSlots
285
306
  });
286
307
  if (typeof html2 === "string") {
287
- if (typeof document !== "undefined") {
288
- document.body.innerHTML = html2;
289
- }
290
- return html2;
308
+ return setRenderedHtml(html2);
291
309
  }
292
310
  } catch {
293
311
  }
@@ -295,12 +313,10 @@ async function renderAstroComponentToDom(component, args, resolveFrom) {
295
313
  const handler = await getAstroSsrHandler(resolveFrom);
296
314
  const html2 = await handler({
297
315
  component: moduleId,
298
- args
316
+ args: serializedArgs,
317
+ slots: serializedSlots
299
318
  });
300
- if (typeof document !== "undefined") {
301
- document.body.innerHTML = html2;
302
- }
303
- return html2;
319
+ return setRenderedHtml(html2);
304
320
  } catch {
305
321
  }
306
322
  }
@@ -309,13 +325,21 @@ async function renderAstroComponentToDom(component, args, resolveFrom) {
309
325
  if (!container) {
310
326
  throw new Error("Failed to initialize Astro container for rendering");
311
327
  }
328
+ const loadComponent = async (id) => {
329
+ const viteServer = await getAstroSsrViteServer(resolveFrom);
330
+ const mod = await ssrLoadModuleWithFsFallback(viteServer, id);
331
+ return patchCreateAstroCompat(mod.default);
332
+ };
333
+ const reconstructedArgs = await reconstructProps(serializedArgs, { loadComponent });
334
+ const reconstructedSlots = await reconstructSlots(serializedSlots, {
335
+ loadComponent,
336
+ renderToHtml: (child) => container.renderToString(child, {})
337
+ });
312
338
  const html = await container.renderToString(resolvedComponent, {
313
- props: args
339
+ props: reconstructedArgs,
340
+ slots: markRawSlots(reconstructedSlots)
314
341
  });
315
- if (typeof document !== "undefined") {
316
- document.body.innerHTML = html;
317
- }
318
- return html;
342
+ return setRenderedHtml(html);
319
343
  }
320
344
  async function renderComposedStory(story) {
321
345
  const meta = story.__storybookAstroMeta;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/testing/story-composition.ts","../src/testing/astro-runtime.ts","../src/testing/project-root.ts","../src/testing/component-utils.ts"],"sourcesContent":["import {\n composeStories as portableComposeStories,\n composeStory as portableComposeStory,\n setProjectAnnotations as portableSetProjectAnnotations,\n} from '../portable-stories.ts';\nimport type { ProjectAnnotations, Store_CSFExports as StoreCsfExports } from 'storybook/internal/types';\nimport type { AstroRenderer } from '../portable-stories.ts';\nimport type { ComposedStory, StoryMeta } from './types.ts';\n\nexport function composeStories<\n TModule extends StoreCsfExports<AstroRenderer> & Record<string, unknown>\n>(\n storiesImport: TModule,\n projectAnnotations?: ProjectAnnotations<AstroRenderer>\n) {\n const composed = portableComposeStories(storiesImport, projectAnnotations);\n\n for (const [storyExportName, story] of Object.entries(composed)) {\n if (typeof story === 'function') {\n const composedStory = story as ComposedStory;\n\n composedStory.__storybookAstroMeta = storiesImport.default as StoryMeta;\n composedStory.__storybookAstroStoryExport = storiesImport[\n storyExportName as keyof TModule\n ] as ComposedStory['__storybookAstroStoryExport'];\n }\n }\n\n return composed;\n}\n\nexport const composeStory = portableComposeStory;\nexport const setProjectAnnotations = portableSetProjectAnnotations;\n","import { fileURLToPath } from 'node:url';\nimport type { ViteDevServer } from 'vite';\nimport type { Integration as StorybookAstroIntegration } from '../integrations/base.ts';\nimport { resolveTestingIntegrationsForRoot } from './integration-config.ts';\nimport { resolveTestingProjectRoot } from './project-root.ts';\nimport { runWithWorkingDirectory } from './working-directory.ts';\nimport { getComponentModuleId, isAstroComponentFactory, isStorybookAstroClientStub } from './component-utils.ts';\nimport { ssrLoadModuleWithFsFallback } from '../lib/ssr-load-module-with-fs-fallback.ts';\nimport type { ComposedStory } from './types.ts';\nimport { renderViaTestingRendererDaemon } from './renderer-daemon.ts';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet astroContainerPromise: Promise<any> | null = null;\n\nconst astroSsrViteServerPromises = new Map<string, Promise<ViteDevServer>>();\n\nconst astroSsrHandlerPromises = new Map<\n string,\n Promise<(data: { component: string; args?: Record<string, unknown> }) => Promise<string>>\n>();\n\nconst testingIntegrationsCache = new Map<string, StorybookAstroIntegration[]>();\n\nfunction getTestingIntegrations(resolveFrom: string) {\n if (!testingIntegrationsCache.has(resolveFrom)) {\n testingIntegrationsCache.set(resolveFrom, resolveTestingIntegrationsForRoot(resolveFrom));\n }\n\n return testingIntegrationsCache.get(resolveFrom)!;\n}\n\nasync function getAstroContainer() {\n if (!astroContainerPromise) {\n const { experimental_AstroContainer: AstroContainer } = await import('astro/container');\n\n astroContainerPromise = AstroContainer.create();\n }\n\n return astroContainerPromise;\n}\n\nasync function getAstroSsrViteServer(resolveFrom: string) {\n if (!astroSsrViteServerPromises.has(resolveFrom)) {\n const { createViteServer } = await import('../viteStorybookAstroMiddlewarePlugin.ts');\n const integrations = getTestingIntegrations(resolveFrom);\n\n astroSsrViteServerPromises.set(\n resolveFrom,\n runWithWorkingDirectory(resolveFrom, () => createViteServer(integrations, resolveFrom))\n );\n }\n\n return astroSsrViteServerPromises.get(resolveFrom)!;\n}\n\nasync function getAstroSsrHandler(resolveFrom: string) {\n if (!astroSsrHandlerPromises.has(resolveFrom)) {\n astroSsrHandlerPromises.set(resolveFrom, (async () => {\n const integrations = getTestingIntegrations(resolveFrom);\n const viteServer = await getAstroSsrViteServer(resolveFrom);\n const middlewareModulePath = fileURLToPath(new URL('../middleware', import.meta.url));\n const middleware = await runWithWorkingDirectory(resolveFrom, () =>\n viteServer.ssrLoadModule(middlewareModulePath, {\n fixStacktrace: true\n })\n );\n\n return middleware.handlerFactory(integrations, {\n loadModule: (id: string) =>\n ssrLoadModuleWithFsFallback(viteServer, id, {\n fixStacktrace: true\n })\n });\n })());\n }\n\n return astroSsrHandlerPromises.get(resolveFrom)!;\n}\n\nasync function resolveAstroComponent(component: unknown, resolveFrom: string) {\n let resolvedComponent = component;\n\n if (!isAstroComponentFactory(resolvedComponent)) {\n throw new Error('Story meta.component must be an Astro component factory.');\n }\n\n if ('moduleId' in resolvedComponent && typeof resolvedComponent.moduleId === 'string') {\n const moduleId = resolvedComponent.moduleId;\n const normalizedModuleId = moduleId.split('?')[0].split('#')[0];\n\n try {\n const mod = await import(/* @vite-ignore */ normalizedModuleId) as Record<string, unknown>;\n\n if (isAstroComponentFactory(mod.default)) {\n resolvedComponent = mod.default;\n }\n } catch {\n // keep current component when direct module import is unavailable\n }\n\n if (isStorybookAstroClientStub(resolvedComponent)) {\n try {\n const viteServer = await getAstroSsrViteServer(resolveFrom);\n const mod = (await ssrLoadModuleWithFsFallback(viteServer, normalizedModuleId)) as Record<string, unknown>;\n\n if (isAstroComponentFactory(mod.default)) {\n resolvedComponent = mod.default;\n }\n } catch {\n // keep current component when SSR module loading is unavailable\n }\n }\n }\n\n return resolvedComponent;\n}\n\nasync function renderAstroComponentToDom(\n component: unknown,\n args: Record<string, unknown>,\n resolveFrom: string\n) {\n const moduleId = getComponentModuleId(component);\n\n if (moduleId) {\n try {\n // Fast path: reuse a single shared SSR daemon instead of spinning SSR in each worker.\n const html = await renderViaTestingRendererDaemon({\n resolveFrom,\n component: moduleId,\n args\n });\n\n if (typeof html === 'string') {\n if (typeof document !== 'undefined') {\n document.body.innerHTML = html;\n }\n\n return html;\n }\n } catch {\n // Fall back to in-worker rendering below when daemon render fails.\n }\n\n try {\n const handler = await getAstroSsrHandler(resolveFrom);\n const html = await handler({\n component: moduleId,\n args\n });\n\n if (typeof document !== 'undefined') {\n document.body.innerHTML = html;\n }\n\n return html;\n } catch {\n // Fall back to direct Container rendering below\n }\n }\n\n const resolvedComponent = await resolveAstroComponent(component, resolveFrom);\n const container = await getAstroContainer();\n \n if (!container) {\n throw new Error('Failed to initialize Astro container for rendering');\n }\n \n const html = await container.renderToString(resolvedComponent, {\n props: args\n });\n\n if (typeof document !== 'undefined') {\n document.body.innerHTML = html;\n }\n\n return html;\n}\n\nasync function renderComposedStory(story: ComposedStory) {\n const meta = story.__storybookAstroMeta;\n const storyExport = story.__storybookAstroStoryExport;\n let component = meta?.component ?? story.component;\n\n if (!isAstroComponentFactory(component)) {\n const maybeRendered = await story();\n\n if (isAstroComponentFactory(maybeRendered)) {\n component = maybeRendered;\n } else if (\n typeof maybeRendered === 'object' &&\n maybeRendered !== null &&\n 'component' in maybeRendered &&\n isAstroComponentFactory((maybeRendered as { component: unknown }).component)\n ) {\n component = (maybeRendered as { component: unknown }).component;\n }\n }\n\n if (!component) {\n throw new Error('Unable to resolve Astro component from composed story.');\n }\n\n const args = {\n ...(meta?.args ?? {}),\n ...(storyExport?.args ?? {}),\n ...(story.args ?? {})\n };\n\n const resolveFrom = await resolveTestingProjectRoot(component);\n\n return renderAstroComponentToDom(component, args, resolveFrom);\n}\n\nexport async function renderStory(story: ComposedStory) {\n return renderComposedStory(story);\n}\n\nexport const renderAstroStory = renderStory;\n","import { existsSync, readFileSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { getComponentModuleFilePath } from './component-utils.ts';\n\nconst VITEST_CONFIG_FILES = [\n 'vitest.config.ts',\n 'vitest.config.mts',\n 'vitest.config.js',\n 'vitest.config.mjs',\n 'vitest.config.cjs'\n];\n\nfunction extractStackFilePath(line: string) {\n const trimmed = line.trim();\n\n const match = trimmed.match(/\\((.+):(\\d+):(\\d+)\\)$/) ?? trimmed.match(/^at\\s+(.+):(\\d+):(\\d+)$/);\n\n if (!match) {\n return null;\n }\n\n const rawPath = match[1];\n\n if (rawPath.startsWith('node:')) {\n return null;\n }\n\n if (rawPath.startsWith('file://')) {\n return fileURLToPath(rawPath);\n }\n\n if (rawPath.startsWith('/')) {\n return rawPath;\n }\n\n return null;\n}\n\nasync function getCurrentTestFilePath() {\n try {\n const { expect } = await import('vitest');\n const vitestState = expect.getState() as {\n testPath?: string;\n filepath?: string;\n filePath?: string;\n };\n\n const fromVitestState = vitestState.testPath ?? vitestState.filepath ?? vitestState.filePath;\n\n if (typeof fromVitestState === 'string') {\n const absolutePath = fromVitestState.startsWith('/')\n ? fromVitestState\n : resolve(process.cwd(), fromVitestState);\n\n if (existsSync(absolutePath)) {\n return absolutePath;\n }\n }\n } catch {\n // Fall through to stack-based lookup when Vitest state is unavailable.\n }\n\n const stack = new Error().stack;\n\n if (!stack) {\n return null;\n }\n\n const thisFilePath = fileURLToPath(import.meta.url);\n\n for (const line of stack.split('\\n')) {\n const filePath = extractStackFilePath(line);\n\n if (!filePath) {\n continue;\n }\n\n if (filePath === thisFilePath || filePath.includes('/node_modules/')) {\n continue;\n }\n\n if (existsSync(filePath)) {\n return filePath;\n }\n }\n\n return null;\n}\n\nfunction findNearestVitestConfigDir(startPath: string) {\n let dir = dirname(startPath);\n\n while (true) {\n if (VITEST_CONFIG_FILES.some((name) => existsSync(join(dir, name)))) {\n return dir;\n }\n\n const parent = dirname(dir);\n\n if (parent === dir) {\n break;\n }\n\n dir = parent;\n }\n\n return null;\n}\n\nfunction packageJsonDeclaresAstro(packageJsonPath: string) {\n if (!existsSync(packageJsonPath)) {\n return false;\n }\n\n try {\n const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n\n return ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'].some(\n (field) =>\n packageJson[field] &&\n typeof packageJson[field] === 'object' &&\n Object.prototype.hasOwnProperty.call(packageJson[field], 'astro')\n );\n } catch {\n return false;\n }\n}\n\nfunction findNearestAstroPackageDir(startPath: string) {\n let dir = dirname(startPath);\n\n while (true) {\n const packageJsonPath = join(dir, 'package.json');\n\n if (packageJsonDeclaresAstro(packageJsonPath)) {\n return dir;\n }\n\n const parent = dirname(dir);\n\n if (parent === dir) {\n break;\n }\n\n dir = parent;\n }\n\n return null;\n}\n\nfunction canResolveAstroFrom(dir: string) {\n try {\n const require = createRequire(`${join(dir, '__storybook-astro-testing-resolve__.js')}`);\n\n require.resolve('astro/package.json');\n\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function resolveTestingProjectRoot(component: unknown) {\n const currentTestFilePath = await getCurrentTestFilePath();\n const componentModulePath = getComponentModuleFilePath(component);\n const candidates = [\n currentTestFilePath ? findNearestVitestConfigDir(currentTestFilePath) : null,\n currentTestFilePath ? findNearestAstroPackageDir(currentTestFilePath) : null,\n componentModulePath ? findNearestAstroPackageDir(componentModulePath) : null,\n packageJsonDeclaresAstro(join(process.cwd(), 'package.json')) ? process.cwd() : null,\n process.env.INIT_CWD && packageJsonDeclaresAstro(join(process.env.INIT_CWD, 'package.json'))\n ? process.env.INIT_CWD\n : null\n ].filter((value): value is string => Boolean(value));\n\n for (const candidate of candidates) {\n if (canResolveAstroFrom(candidate)) {\n return candidate;\n }\n }\n\n return process.cwd();\n}\n","export function isStorybookAstroClientStub(component: unknown) {\n return (\n typeof component === 'function' &&\n String(component).includes('Astro components are rendered server-side by Storybook')\n );\n}\n\nexport function isAstroComponentFactory(component: unknown) {\n return typeof component === 'function' && 'isAstroComponentFactory' in component;\n}\n\nexport function getComponentModuleId(component: unknown) {\n if (typeof component !== 'function' || !('moduleId' in component)) {\n return null;\n }\n\n if (typeof component.moduleId !== 'string') {\n return null;\n }\n\n return component.moduleId.split('?')[0].split('#')[0];\n}\n\nexport function getComponentModuleFilePath(component: unknown) {\n const moduleId = getComponentModuleId(component);\n\n if (!moduleId || !moduleId.startsWith('/')) {\n return null;\n }\n\n return moduleId;\n}\n"],"mappings":"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","names":["composeStories","composeStory","setProjectAnnotations","fileURLToPath","require","fileURLToPath","html"]}
1
+ {"version":3,"sources":["../src/testing/story-composition.ts","../src/testing/astro-runtime.ts","../src/testing/project-root.ts","../src/testing/component-utils.ts"],"sourcesContent":["import {\n composeStories as portableComposeStories,\n composeStory as portableComposeStory,\n setProjectAnnotations as portableSetProjectAnnotations,\n} from '../portable-stories.ts';\nimport type { ProjectAnnotations, Store_CSFExports as StoreCsfExports } from 'storybook/internal/types';\nimport type { AstroRenderer } from '../portable-stories.ts';\nimport type { ComposedStory, StoryMeta } from './types.ts';\n\nexport function composeStories<\n TModule extends StoreCsfExports<AstroRenderer> & Record<string, unknown>\n>(\n storiesImport: TModule,\n projectAnnotations?: ProjectAnnotations<AstroRenderer>\n) {\n const composed = portableComposeStories(storiesImport, projectAnnotations);\n\n for (const [storyExportName, story] of Object.entries(composed)) {\n if (typeof story === 'function') {\n const composedStory = story as ComposedStory;\n\n composedStory.__storybookAstroMeta = storiesImport.default as StoryMeta;\n composedStory.__storybookAstroStoryExport = storiesImport[\n storyExportName as keyof TModule\n ] as ComposedStory['__storybookAstroStoryExport'];\n }\n }\n\n return composed;\n}\n\nexport const composeStory = portableComposeStory;\nexport const setProjectAnnotations = portableSetProjectAnnotations;\n","import { fileURLToPath } from 'node:url';\nimport type { ViteDevServer } from 'vite';\nimport type { Integration as StorybookAstroIntegration } from '../integrations/base.ts';\nimport { resolveTestingIntegrationsForRoot } from './integration-config.ts';\nimport { resolveTestingProjectRoot } from './project-root.ts';\nimport { runWithWorkingDirectory } from './working-directory.ts';\nimport { getComponentModuleId, isAstroComponentFactory, isStorybookAstroClientStub } from './component-utils.ts';\nimport { ssrLoadModuleWithFsFallback } from '../lib/ssr-load-module-with-fs-fallback.ts';\nimport { separateStorySlots } from '../lib/separate-story-slots.ts';\nimport { reconstructProps, reconstructSlots } from '../lib/reconstruct-component-args.ts';\nimport { patchCreateAstroCompat, markRawSlots } from '../astroRenderHandler.ts';\nimport { serializeAstroComponentMarkers } from '@storybook-astro/renderer/types';\nimport type { ComposedStory } from './types.ts';\nimport { renderViaTestingRendererDaemon } from './renderer-daemon.ts';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nlet astroContainerPromise: Promise<any> | null = null;\n\nconst astroSsrViteServerPromises = new Map<string, Promise<ViteDevServer>>();\n\nconst astroSsrHandlerPromises = new Map<\n string,\n Promise<\n (data: {\n component: string;\n args?: Record<string, unknown>;\n slots?: Record<string, unknown>;\n }) => Promise<string>\n >\n>();\n\nconst testingIntegrationsCache = new Map<string, StorybookAstroIntegration[]>();\n\nfunction getTestingIntegrations(resolveFrom: string) {\n if (!testingIntegrationsCache.has(resolveFrom)) {\n testingIntegrationsCache.set(resolveFrom, resolveTestingIntegrationsForRoot(resolveFrom));\n }\n\n return testingIntegrationsCache.get(resolveFrom)!;\n}\n\nasync function getAstroContainer() {\n if (!astroContainerPromise) {\n const { experimental_AstroContainer: AstroContainer } = await import('astro/container');\n\n astroContainerPromise = AstroContainer.create();\n }\n\n return astroContainerPromise;\n}\n\nasync function getAstroSsrViteServer(resolveFrom: string) {\n if (!astroSsrViteServerPromises.has(resolveFrom)) {\n const { createViteServer } = await import('../viteStorybookAstroMiddlewarePlugin.ts');\n const integrations = getTestingIntegrations(resolveFrom);\n\n astroSsrViteServerPromises.set(\n resolveFrom,\n runWithWorkingDirectory(resolveFrom, () => createViteServer(integrations, resolveFrom))\n );\n }\n\n return astroSsrViteServerPromises.get(resolveFrom)!;\n}\n\nasync function getAstroSsrHandler(resolveFrom: string) {\n if (!astroSsrHandlerPromises.has(resolveFrom)) {\n astroSsrHandlerPromises.set(resolveFrom, (async () => {\n const integrations = getTestingIntegrations(resolveFrom);\n const viteServer = await getAstroSsrViteServer(resolveFrom);\n const middlewareModulePath = fileURLToPath(new URL('../middleware', import.meta.url));\n const middleware = await runWithWorkingDirectory(resolveFrom, () =>\n viteServer.ssrLoadModule(middlewareModulePath, {\n fixStacktrace: true\n })\n );\n\n return middleware.handlerFactory(integrations, {\n loadModule: (id: string) =>\n ssrLoadModuleWithFsFallback(viteServer, id, {\n fixStacktrace: true\n })\n });\n })());\n }\n\n return astroSsrHandlerPromises.get(resolveFrom)!;\n}\n\nasync function resolveAstroComponent(component: unknown, resolveFrom: string) {\n let resolvedComponent = component;\n\n if (!isAstroComponentFactory(resolvedComponent)) {\n throw new Error('Story meta.component must be an Astro component factory.');\n }\n\n if ('moduleId' in resolvedComponent && typeof resolvedComponent.moduleId === 'string') {\n const moduleId = resolvedComponent.moduleId;\n const normalizedModuleId = moduleId.split('?')[0].split('#')[0];\n\n try {\n const mod = await import(/* @vite-ignore */ normalizedModuleId) as Record<string, unknown>;\n\n if (isAstroComponentFactory(mod.default)) {\n resolvedComponent = mod.default;\n }\n } catch {\n // keep current component when direct module import is unavailable\n }\n\n if (isStorybookAstroClientStub(resolvedComponent)) {\n try {\n const viteServer = await getAstroSsrViteServer(resolveFrom);\n const mod = (await ssrLoadModuleWithFsFallback(viteServer, normalizedModuleId)) as Record<string, unknown>;\n\n if (isAstroComponentFactory(mod.default)) {\n resolvedComponent = mod.default;\n }\n } catch {\n // keep current component when SSR module loading is unavailable\n }\n }\n }\n\n return resolvedComponent;\n}\n\nfunction setRenderedHtml(html: string) {\n if (typeof document !== 'undefined') {\n document.body.innerHTML = html;\n }\n\n return html;\n}\n\nasync function renderAstroComponentToDom(\n component: unknown,\n args: Record<string, unknown>,\n resolveFrom: string\n) {\n const moduleId = getComponentModuleId(component);\n\n // Split slot content from props, then serialize any Astro component passed as\n // a prop or slot into a moduleId marker. The handler reconstructs each marker —\n // loading the real server component by moduleId — so a story can nest Astro\n // components without the unrenderable client stub leaking through.\n const { componentArgs, storySlots } = separateStorySlots(args);\n const serializedArgs = serializeAstroComponentMarkers(componentArgs) as Record<string, unknown>;\n const serializedSlots = serializeAstroComponentMarkers(storySlots) as Record<string, unknown>;\n\n if (moduleId) {\n try {\n // Fast path: reuse a single shared SSR daemon instead of spinning SSR in each worker.\n const html = await renderViaTestingRendererDaemon({\n resolveFrom,\n component: moduleId,\n args: serializedArgs,\n slots: serializedSlots\n });\n\n if (typeof html === 'string') {\n return setRenderedHtml(html);\n }\n } catch {\n // Fall back to in-worker rendering below when daemon render fails.\n }\n\n try {\n const handler = await getAstroSsrHandler(resolveFrom);\n const html = await handler({\n component: moduleId,\n args: serializedArgs,\n slots: serializedSlots\n });\n\n return setRenderedHtml(html);\n } catch {\n // Fall back to direct Container rendering below\n }\n }\n\n const resolvedComponent = await resolveAstroComponent(component, resolveFrom);\n const container = await getAstroContainer();\n\n if (!container) {\n throw new Error('Failed to initialize Astro container for rendering');\n }\n\n // The direct fallback has no handler, so reconstruct nested components here:\n // load each marker's real server module by id and render slot markers to HTML.\n const loadComponent = async (id: string) => {\n const viteServer = await getAstroSsrViteServer(resolveFrom);\n const mod = (await ssrLoadModuleWithFsFallback(viteServer, id)) as Record<string, unknown>;\n\n return patchCreateAstroCompat(mod.default);\n };\n const reconstructedArgs = await reconstructProps(serializedArgs, { loadComponent });\n const reconstructedSlots = await reconstructSlots(serializedSlots, {\n loadComponent,\n renderToHtml: (child) => container.renderToString(child, {})\n });\n\n const html = await container.renderToString(resolvedComponent, {\n props: reconstructedArgs,\n slots: markRawSlots(reconstructedSlots)\n });\n\n return setRenderedHtml(html);\n}\n\nasync function renderComposedStory(story: ComposedStory) {\n const meta = story.__storybookAstroMeta;\n const storyExport = story.__storybookAstroStoryExport;\n let component = meta?.component ?? story.component;\n\n if (!isAstroComponentFactory(component)) {\n const maybeRendered = await story();\n\n if (isAstroComponentFactory(maybeRendered)) {\n component = maybeRendered;\n } else if (\n typeof maybeRendered === 'object' &&\n maybeRendered !== null &&\n 'component' in maybeRendered &&\n isAstroComponentFactory((maybeRendered as { component: unknown }).component)\n ) {\n component = (maybeRendered as { component: unknown }).component;\n }\n }\n\n if (!component) {\n throw new Error('Unable to resolve Astro component from composed story.');\n }\n\n const args = {\n ...(meta?.args ?? {}),\n ...(storyExport?.args ?? {}),\n ...(story.args ?? {})\n };\n\n const resolveFrom = await resolveTestingProjectRoot(component);\n\n return renderAstroComponentToDom(component, args, resolveFrom);\n}\n\nexport async function renderStory(story: ComposedStory) {\n return renderComposedStory(story);\n}\n\nexport const renderAstroStory = renderStory;\n","import { existsSync, readFileSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { getComponentModuleFilePath } from './component-utils.ts';\n\nconst VITEST_CONFIG_FILES = [\n 'vitest.config.ts',\n 'vitest.config.mts',\n 'vitest.config.js',\n 'vitest.config.mjs',\n 'vitest.config.cjs'\n];\n\nfunction extractStackFilePath(line: string) {\n const trimmed = line.trim();\n\n const match = trimmed.match(/\\((.+):(\\d+):(\\d+)\\)$/) ?? trimmed.match(/^at\\s+(.+):(\\d+):(\\d+)$/);\n\n if (!match) {\n return null;\n }\n\n const rawPath = match[1];\n\n if (rawPath.startsWith('node:')) {\n return null;\n }\n\n if (rawPath.startsWith('file://')) {\n return fileURLToPath(rawPath);\n }\n\n if (rawPath.startsWith('/')) {\n return rawPath;\n }\n\n return null;\n}\n\nasync function getCurrentTestFilePath() {\n try {\n const { expect } = await import('vitest');\n const vitestState = expect.getState() as {\n testPath?: string;\n filepath?: string;\n filePath?: string;\n };\n\n const fromVitestState = vitestState.testPath ?? vitestState.filepath ?? vitestState.filePath;\n\n if (typeof fromVitestState === 'string') {\n const absolutePath = fromVitestState.startsWith('/')\n ? fromVitestState\n : resolve(process.cwd(), fromVitestState);\n\n if (existsSync(absolutePath)) {\n return absolutePath;\n }\n }\n } catch {\n // Fall through to stack-based lookup when Vitest state is unavailable.\n }\n\n const stack = new Error().stack;\n\n if (!stack) {\n return null;\n }\n\n const thisFilePath = fileURLToPath(import.meta.url);\n\n for (const line of stack.split('\\n')) {\n const filePath = extractStackFilePath(line);\n\n if (!filePath) {\n continue;\n }\n\n if (filePath === thisFilePath || filePath.includes('/node_modules/')) {\n continue;\n }\n\n if (existsSync(filePath)) {\n return filePath;\n }\n }\n\n return null;\n}\n\nfunction findNearestVitestConfigDir(startPath: string) {\n let dir = dirname(startPath);\n\n while (true) {\n if (VITEST_CONFIG_FILES.some((name) => existsSync(join(dir, name)))) {\n return dir;\n }\n\n const parent = dirname(dir);\n\n if (parent === dir) {\n break;\n }\n\n dir = parent;\n }\n\n return null;\n}\n\nfunction packageJsonDeclaresAstro(packageJsonPath: string) {\n if (!existsSync(packageJsonPath)) {\n return false;\n }\n\n try {\n const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n\n return ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'].some(\n (field) =>\n packageJson[field] &&\n typeof packageJson[field] === 'object' &&\n Object.prototype.hasOwnProperty.call(packageJson[field], 'astro')\n );\n } catch {\n return false;\n }\n}\n\nfunction findNearestAstroPackageDir(startPath: string) {\n let dir = dirname(startPath);\n\n while (true) {\n const packageJsonPath = join(dir, 'package.json');\n\n if (packageJsonDeclaresAstro(packageJsonPath)) {\n return dir;\n }\n\n const parent = dirname(dir);\n\n if (parent === dir) {\n break;\n }\n\n dir = parent;\n }\n\n return null;\n}\n\nfunction canResolveAstroFrom(dir: string) {\n try {\n const require = createRequire(`${join(dir, '__storybook-astro-testing-resolve__.js')}`);\n\n require.resolve('astro/package.json');\n\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function resolveTestingProjectRoot(component: unknown) {\n const currentTestFilePath = await getCurrentTestFilePath();\n const componentModulePath = getComponentModuleFilePath(component);\n const candidates = [\n currentTestFilePath ? findNearestVitestConfigDir(currentTestFilePath) : null,\n currentTestFilePath ? findNearestAstroPackageDir(currentTestFilePath) : null,\n componentModulePath ? findNearestAstroPackageDir(componentModulePath) : null,\n packageJsonDeclaresAstro(join(process.cwd(), 'package.json')) ? process.cwd() : null,\n process.env.INIT_CWD && packageJsonDeclaresAstro(join(process.env.INIT_CWD, 'package.json'))\n ? process.env.INIT_CWD\n : null\n ].filter((value): value is string => Boolean(value));\n\n for (const candidate of candidates) {\n if (canResolveAstroFrom(candidate)) {\n return candidate;\n }\n }\n\n return process.cwd();\n}\n","export function isStorybookAstroClientStub(component: unknown) {\n return (\n typeof component === 'function' &&\n String(component).includes('Astro components are rendered server-side by Storybook')\n );\n}\n\nexport function isAstroComponentFactory(component: unknown) {\n return typeof component === 'function' && 'isAstroComponentFactory' in component;\n}\n\nexport function getComponentModuleId(component: unknown) {\n if (typeof component !== 'function' || !('moduleId' in component)) {\n return null;\n }\n\n if (typeof component.moduleId !== 'string') {\n return null;\n }\n\n return component.moduleId.split('?')[0].split('#')[0];\n}\n\nexport function getComponentModuleFilePath(component: unknown) {\n const moduleId = getComponentModuleId(component);\n\n if (!moduleId || !moduleId.startsWith('/')) {\n return null;\n }\n\n return moduleId;\n}\n"],"mappings":"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","names":["composeStories","composeStory","setProjectAnnotations","fileURLToPath","require","fileURLToPath","html"]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  vitePluginAstroComponentMarker
3
- } from "../chunk-6RIGYMZP.js";
3
+ } from "../chunk-UIGE5653.js";
4
4
  import {
5
5
  registerTestingIntegrationsForRoot
6
6
  } from "../chunk-WUTCMEF5.js";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@storybook-astro/framework",
3
- "version": "1.6.0",
4
- "description": "Community-supported Storybook framework for Astro 5 & 6 components",
3
+ "version": "1.7.0-canary.2",
4
+ "description": "Community-supported Storybook framework for Astro 5, 6 & 7 components",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -60,6 +60,7 @@
60
60
  "build": "tsup && node scripts/patch-shim-ref.mjs",
61
61
  "dev": "tsup --watch --no-clean",
62
62
  "lint": "eslint .",
63
+ "test": "vitest run",
63
64
  "prepublishOnly": "tsup && node scripts/patch-shim-ref.mjs"
64
65
  },
65
66
  "devDependencies": {
@@ -73,7 +74,7 @@
73
74
  "@vitejs/plugin-react": "^5.1.4",
74
75
  "@vitest/coverage-v8": "4.0.18",
75
76
  "alpinejs": "^3.14.9",
76
- "astro": "^5.6.1 || ^6.0.0",
77
+ "astro": "^6.4.0",
77
78
  "eslint": "^9.39.2",
78
79
  "storybook-solidjs": "^1.0.0-beta.7",
79
80
  "tsup": "^8.5.1",
@@ -81,21 +82,21 @@
81
82
  "vitest": "^4.0.18"
82
83
  },
83
84
  "peerDependencies": {
84
- "@astrojs/alpinejs": "^0.4.5 || ^0.5.0",
85
- "@astrojs/preact": "^4.0.8 || ^5.0.0",
86
- "@astrojs/react": "^4.2.3 || ^5.0.0",
87
- "@astrojs/solid-js": "^5.0.7 || ^6.0.0",
88
- "@astrojs/svelte": "^7.0.9 || ^8.0.0",
89
- "@astrojs/vue": "^5.0.9 || ^6.0.0",
85
+ "@astrojs/alpinejs": "^0.4.5 || ^0.5.0 || ^1.0.0",
86
+ "@astrojs/preact": "^4.0.8 || ^5.0.0 || ^6.0.0",
87
+ "@astrojs/react": "^4.2.3 || ^5.0.0 || ^6.0.0",
88
+ "@astrojs/solid-js": "^5.0.7 || ^6.0.0 || ^7.0.0",
89
+ "@astrojs/svelte": "^7.0.9 || ^8.0.0 || ^9.0.0",
90
+ "@astrojs/vue": "^5.0.9 || ^6.0.0 || ^7.0.0",
90
91
  "@preact/preset-vite": "^2.10.1",
91
92
  "@storybook/preact": "^10.0.0",
92
93
  "@storybook/react": "^10.0.0",
93
94
  "@storybook/svelte": "^10.0.0",
94
95
  "@storybook/vue3": "^10.0.0",
95
- "@vitejs/plugin-react": "^5.0.0",
96
+ "@vitejs/plugin-react": "^5.0.0 || ^6.0.0",
96
97
  "@vitejs/plugin-vue": "^5.2.3 || ^6.0.0",
97
98
  "@vitejs/plugin-vue-jsx": "^4.1.2 || ^5.0.0",
98
- "astro": "^5.5.3 || ^6.0.0",
99
+ "astro": "^5.5.3 || ^6.0.0 || ^7.0.0",
99
100
  "storybook": "^10.0.0",
100
101
  "storybook-solidjs": "^1.0.0-beta.7",
101
102
  "vite": "^6.4.1 || ^7.0.0 || ^8.0.0"
@@ -134,6 +135,9 @@
134
135
  "@storybook/vue3": {
135
136
  "optional": true
136
137
  },
138
+ "@vitejs/plugin-react": {
139
+ "optional": true
140
+ },
137
141
  "@vitejs/plugin-vue": {
138
142
  "optional": true
139
143
  },
@@ -145,8 +149,8 @@
145
149
  }
146
150
  },
147
151
  "dependencies": {
148
- "@storybook-astro/renderer": "1.6.0",
149
- "get-tsconfig": "^4.14.0",
152
+ "@storybook-astro/renderer": "1.7.0-canary.2",
153
+ "get-tsconfig": "5.0.0-beta.4",
150
154
  "hono": "^4.11.12",
151
155
  "sanitize-html": "^2.17.0",
152
156
  "vite": "^6.4.1 || ^7.0.0 || ^8.0.0"
@@ -1,7 +1,9 @@
1
1
  import type { experimental_AstroContainer as AstroContainer } from 'astro/container';
2
+ import { markHTMLString } from 'astro/runtime/server/index.js';
2
3
  import type { SanitizationOptions } from './lib/sanitization.ts';
3
4
  import { resolveSanitizationOptions, sanitizeRenderPayload } from './lib/sanitization.ts';
4
5
  import { reviveDateStrings } from './lib/revive-dates.ts';
6
+ import { reconstructProps, reconstructSlots } from './lib/reconstruct-component-args.ts';
5
7
  import { runWithStoryRules, type ResolveRulesConfigModule } from './storyRulesRuntime.ts';
6
8
  import type { RenderStoryInput } from './types.ts';
7
9
 
@@ -86,7 +88,13 @@ export function createAstroRenderHandler(options: CreateAstroRenderHandlerOption
86
88
  data.component,
87
89
  selectedRules.moduleMocks.size === 0
88
90
  );
89
- const processedArgs = await processImageMetadata(data.args ?? {});
91
+ // Resolve Astro components passed as props back to real factories
92
+ // before the other arg processing (factories pass through those
93
+ // untouched), so the parent template can render them with `<Comp />`.
94
+ const reconstructedArgs = await reconstructProps(data.args ?? {}, {
95
+ loadComponent: (moduleId) => loadPatchedComponent(moduleId)
96
+ });
97
+ const processedArgs = await processImageMetadata(reconstructedArgs);
90
98
  const revivedArgs = reviveDateStrings(processedArgs);
91
99
  const sanitizedPayload = sanitizeRenderPayload(
92
100
  {
@@ -96,11 +104,23 @@ export function createAstroRenderHandler(options: CreateAstroRenderHandlerOption
96
104
  sanitizationOptions
97
105
  );
98
106
 
107
+ // Render component slots to HTML *after* sanitization so a component's
108
+ // own markup isn't stripped by the slot allowlist (string slots above
109
+ // still are). Markers pass through sanitization untouched.
110
+ const renderedSlots = await reconstructSlots(sanitizedPayload.slots, {
111
+ loadComponent: (moduleId) => loadPatchedComponent(moduleId),
112
+ renderToHtml: (component) =>
113
+ options.container.renderToString(
114
+ component as Parameters<typeof options.container.renderToString>[0],
115
+ {}
116
+ )
117
+ });
118
+
99
119
  return options.container.renderToString(
100
120
  patchedComponent as Parameters<typeof options.container.renderToString>[0],
101
121
  {
102
122
  props: sanitizedPayload.args,
103
- slots: sanitizedPayload.slots
123
+ slots: markRawSlots(renderedSlots)
104
124
  }
105
125
  );
106
126
  }
@@ -118,6 +138,23 @@ export function createAstroRenderHandler(options: CreateAstroRenderHandlerOption
118
138
  };
119
139
  }
120
140
 
141
+ /**
142
+ * Marks each slot's HTML string as already-rendered so the Astro Container emits
143
+ * it raw instead of escaping it. Astro 5 and 7 render string slots raw anyway,
144
+ * but Astro 6 escapes an unmarked string slot — this normalizes all versions.
145
+ * `markHTMLString` tags the string via a global symbol, so it's recognized even
146
+ * across Astro module instances.
147
+ */
148
+ export function markRawSlots(slots: Record<string, unknown>): Record<string, unknown> {
149
+ const marked: Record<string, unknown> = {};
150
+
151
+ for (const [name, value] of Object.entries(slots)) {
152
+ marked[name] = typeof value === 'string' ? markHTMLString(value) : value;
153
+ }
154
+
155
+ return marked;
156
+ }
157
+
121
158
  export function patchCreateAstroCompat(component: unknown): AstroComponentFactory {
122
159
  if (typeof component !== 'function') {
123
160
  throw new Error('Expected Astro component factory to be a function.');
package/src/index.ts CHANGED
@@ -97,7 +97,14 @@ export function definePreview<Addons extends PreviewAddon<never>[] = []>(
97
97
  ): CsfPreview<AstroRenderer & InferTypes<Addons>> {
98
98
  // Kick off the renderer load eagerly so the impl is ready by the time
99
99
  // Storybook calls renderToCanvas — but don't await, so this stays sync.
100
- void loadRendererEntryPreview();
100
+ // Only do this in a browser: the renderer's entry-preview imports browser-only
101
+ // modules (and the `virtual:storybook-astro-renderer` graph) that aren't
102
+ // available when `.storybook/preview.ts` is evaluated under Node during build
103
+ // prerendering. renderToCanvas never runs there, and the browser path still
104
+ // loads the renderer lazily via `composedRenderToCanvas`.
105
+ if (typeof document !== 'undefined') {
106
+ void loadRendererEntryPreview();
107
+ }
101
108
 
102
109
  return definePreviewBase<AstroRenderer, Addons>({
103
110
  ...input,
@@ -0,0 +1,48 @@
1
+ import { test, expect, vi } from 'vitest';
2
+ import {
3
+ ASTRO_COMPONENT_MARKER,
4
+ isAstroComponentMarker,
5
+ serializeAstroComponentMarkers
6
+ } from '@storybook-astro/renderer/types';
7
+
8
+ const factory = (moduleId: string | undefined) =>
9
+ Object.assign(() => undefined, { isAstroComponentFactory: true as const, moduleId });
10
+
11
+ test('isAstroComponentMarker requires both the flag and a string moduleId', () => {
12
+ expect(isAstroComponentMarker({ [ASTRO_COMPONENT_MARKER]: true, moduleId: '/A.astro' })).toBe(true);
13
+ // A plain object that happens to share one key is not a marker.
14
+ expect(isAstroComponentMarker({ [ASTRO_COMPONENT_MARKER]: true })).toBe(false);
15
+ expect(isAstroComponentMarker({ moduleId: '/A.astro' })).toBe(false);
16
+ expect(isAstroComponentMarker({ [ASTRO_COMPONENT_MARKER]: 'yes', moduleId: '/A.astro' })).toBe(false);
17
+ });
18
+
19
+ test('serializeAstroComponentMarkers replaces factories with markers, including when nested', () => {
20
+ const result = serializeAstroComponentMarkers({
21
+ Icon: factory('/Icon.astro'),
22
+ nested: { Inner: factory('/Inner.astro') },
23
+ list: [factory('/A.astro'), 'keep']
24
+ }) as Record<string, unknown>;
25
+
26
+ expect(result.Icon).toEqual({ [ASTRO_COMPONENT_MARKER]: true, moduleId: '/Icon.astro' });
27
+ expect((result.nested as Record<string, unknown>).Inner).toEqual({
28
+ [ASTRO_COMPONENT_MARKER]: true,
29
+ moduleId: '/Inner.astro'
30
+ });
31
+ expect((result.list as unknown[])[0]).toEqual({ [ASTRO_COMPONENT_MARKER]: true, moduleId: '/A.astro' });
32
+ expect((result.list as unknown[])[1]).toBe('keep');
33
+ });
34
+
35
+ test('serializeAstroComponentMarkers drops a factory with no moduleId and reports it', () => {
36
+ const error = vi.spyOn(console, 'error').mockImplementation(() => undefined);
37
+
38
+ const result = serializeAstroComponentMarkers({ Icon: factory(undefined) }) as Record<string, unknown>;
39
+
40
+ expect(result.Icon).toBeUndefined();
41
+ expect(error).toHaveBeenCalled();
42
+
43
+ error.mockRestore();
44
+ });
45
+
46
+ test('serializeAstroComponentMarkers leaves non-component values untouched', () => {
47
+ expect(serializeAstroComponentMarkers({ a: 1, b: 'x', c: null })).toEqual({ a: 1, b: 'x', c: null });
48
+ });
@@ -0,0 +1,97 @@
1
+ import { test, expect, vi } from 'vitest';
2
+ import { reconstructProps, reconstructSlots } from './reconstruct-component-args.ts';
3
+
4
+ const marker = (moduleId: string) => ({ __astroComponent: true as const, moduleId });
5
+
6
+ // A fake Astro factory: a function carrying the detection flag + moduleId.
7
+ const factory = (moduleId: string) =>
8
+ Object.assign(() => undefined, { isAstroComponentFactory: true as const, moduleId });
9
+
10
+ test('reconstructProps resolves a marker to the loaded factory', async () => {
11
+ const loaded = factory('/Icon.astro');
12
+ const loadComponent = vi.fn().mockResolvedValue(loaded);
13
+
14
+ const result = await reconstructProps({ Icon: marker('/Icon.astro') }, { loadComponent });
15
+
16
+ expect(loadComponent).toHaveBeenCalledWith('/Icon.astro');
17
+ expect(result.Icon).toBe(loaded);
18
+ });
19
+
20
+ test('reconstructProps passes a raw factory through (testing path)', async () => {
21
+ const raw = factory('/Icon.astro');
22
+ const loadComponent = vi.fn();
23
+
24
+ const result = await reconstructProps({ Icon: raw }, { loadComponent });
25
+
26
+ expect(result.Icon).toBe(raw);
27
+ expect(loadComponent).not.toHaveBeenCalled();
28
+ });
29
+
30
+ test('reconstructProps resolves markers nested in objects and arrays', async () => {
31
+ const loadComponent = vi.fn(async (id: string) => factory(id));
32
+
33
+ const result = await reconstructProps(
34
+ { wrap: { Icon: marker('/A.astro') }, list: [marker('/B.astro'), 'x'] },
35
+ { loadComponent }
36
+ );
37
+
38
+ const resolvedIcon = (result.wrap as Record<string, unknown>).Icon as { moduleId?: string };
39
+ const resolvedListItem = (result.list as Array<{ moduleId?: string }>)[0];
40
+
41
+ expect(resolvedIcon.moduleId).toBe('/A.astro');
42
+ expect(resolvedListItem.moduleId).toBe('/B.astro');
43
+ expect((result.list as unknown[])[1]).toBe('x');
44
+ });
45
+
46
+ test('reconstructProps preserves identity when there are no component references', async () => {
47
+ const args = { title: 'hi', meta: { src: '/img.png', width: 10 } };
48
+ const result = await reconstructProps(args, { loadComponent: vi.fn() });
49
+
50
+ expect(result.meta).toBe(args.meta);
51
+ });
52
+
53
+ test('reconstructSlots renders a marker to HTML via the loaded component', async () => {
54
+ const loaded = factory('/Child.astro');
55
+ const loadComponent = vi.fn().mockResolvedValue(loaded);
56
+ const renderToHtml = vi.fn().mockResolvedValue('<span>child</span>');
57
+
58
+ const result = await reconstructSlots({ default: marker('/Child.astro') }, {
59
+ loadComponent,
60
+ renderToHtml
61
+ });
62
+
63
+ expect(loadComponent).toHaveBeenCalledWith('/Child.astro');
64
+ expect(renderToHtml).toHaveBeenCalledWith(loaded);
65
+ expect(result.default).toBe('<span>child</span>');
66
+ });
67
+
68
+ test('reconstructSlots renders a raw factory slot and passes strings through', async () => {
69
+ const renderToHtml = vi.fn().mockResolvedValue('<b>x</b>');
70
+
71
+ const result = await reconstructSlots(
72
+ { default: factory('/Child.astro'), footer: '<p>plain</p>' },
73
+ { loadComponent: vi.fn(), renderToHtml }
74
+ );
75
+
76
+ expect(result.default).toBe('<b>x</b>');
77
+ expect(result.footer).toBe('<p>plain</p>');
78
+ });
79
+
80
+ test('reconstructSlots concatenates an array slot into one HTML string', async () => {
81
+ const renderToHtml = vi.fn().mockResolvedValue('<i>c</i>');
82
+
83
+ const result = await reconstructSlots({ default: [marker('/C.astro'), ' and ', '<u>d</u>'] }, {
84
+ loadComponent: vi.fn(async (id: string) => factory(id)),
85
+ renderToHtml
86
+ });
87
+
88
+ expect(result.default).toBe('<i>c</i> and <u>d</u>');
89
+ });
90
+
91
+ test('reconstructSlots wraps a render failure with the slot name', async () => {
92
+ const renderToHtml = vi.fn().mockRejectedValue(new Error('boom'));
93
+
94
+ await expect(
95
+ reconstructSlots({ header: factory('/C.astro') }, { loadComponent: vi.fn(), renderToHtml })
96
+ ).rejects.toThrow(/slot "header".*boom/);
97
+ });