Files
Imhotep/packages/imhotep-core/src/scene-target.ts
T

203 lines
7.1 KiB
TypeScript
Raw Normal View History

/**
* SceneTarget discriminated union and type guards.
*
* The scene target is the stage. It must be possible to mount any component,
* any story, any page, and treat it as a deterministic scene. The adapter
* boundary is the moat that keeps renderer concerns out of the core.
*/
import type { Environment } from './types.js'
// ---------------------------------------------------------------------------
// SceneTarget Union
// ---------------------------------------------------------------------------
export type SceneTarget =
| { kind: 'page'; url: string }
| { kind: 'playwright-page'; pageRef: string; url?: string }
| { kind: 'storybook-story'; storyId: string; storybookUrl: string }
| { kind: 'react-component'; rendererId: string; componentId: string }
| { kind: 'vue-component'; rendererId: string; componentId: string }
| { kind: 'custom-renderer'; rendererId: string; targetId: string }
| { kind: 'fixture'; fixtureId: string }
// ---------------------------------------------------------------------------
// RenderCase Contract
// ---------------------------------------------------------------------------
export interface RenderCase {
caseId: string
input: unknown
env?: Partial<Environment>
metadata?: Record<string, unknown>
}
// ---------------------------------------------------------------------------
// Type Guards
// ---------------------------------------------------------------------------
export function isSceneTarget(value: unknown): value is SceneTarget {
if (typeof value !== 'object' || value === null) return false
const obj = value as Record<string, unknown>
if (typeof obj.kind !== 'string') return false
switch (obj.kind) {
case 'page':
return typeof obj.url === 'string'
case 'playwright-page':
return typeof obj.pageRef === 'string' && (obj.url === undefined || typeof obj.url === 'string')
case 'storybook-story':
return typeof obj.storyId === 'string' && typeof obj.storybookUrl === 'string'
case 'react-component':
return typeof obj.rendererId === 'string' && typeof obj.componentId === 'string'
case 'vue-component':
return typeof obj.rendererId === 'string' && typeof obj.componentId === 'string'
case 'custom-renderer':
return typeof obj.rendererId === 'string' && typeof obj.targetId === 'string'
case 'fixture':
return typeof obj.fixtureId === 'string'
default:
return false
}
}
export function isPageTarget(target: SceneTarget): target is Extract<SceneTarget, { kind: 'page' }> {
return target.kind === 'page'
}
export function isPlaywrightPageTarget(
target: SceneTarget
): target is Extract<SceneTarget, { kind: 'playwright-page' }> {
return target.kind === 'playwright-page'
}
export function isStorybookStoryTarget(
target: SceneTarget
): target is Extract<SceneTarget, { kind: 'storybook-story' }> {
return target.kind === 'storybook-story'
}
export function isReactComponentTarget(
target: SceneTarget
): target is Extract<SceneTarget, { kind: 'react-component' }> {
return target.kind === 'react-component'
}
export function isVueComponentTarget(
target: SceneTarget
): target is Extract<SceneTarget, { kind: 'vue-component' }> {
return target.kind === 'vue-component'
}
export function isCustomRendererTarget(
target: SceneTarget
): target is Extract<SceneTarget, { kind: 'custom-renderer' }> {
return target.kind === 'custom-renderer'
}
export function isFixtureTarget(target: SceneTarget): target is Extract<SceneTarget, { kind: 'fixture' }> {
return target.kind === 'fixture'
}
export function isComponentTarget(
target: SceneTarget
): target is
| Extract<SceneTarget, { kind: 'react-component' }>
| Extract<SceneTarget, { kind: 'vue-component' }>
| Extract<SceneTarget, { kind: 'custom-renderer' }> {
return target.kind === 'react-component' || target.kind === 'vue-component' || target.kind === 'custom-renderer'
}
export function isRendererTarget(
target: SceneTarget
): target is
| Extract<SceneTarget, { kind: 'react-component' }>
| Extract<SceneTarget, { kind: 'vue-component' }>
| Extract<SceneTarget, { kind: 'storybook-story' }>
| Extract<SceneTarget, { kind: 'custom-renderer' }> {
return (
target.kind === 'react-component' ||
target.kind === 'vue-component' ||
target.kind === 'storybook-story' ||
target.kind === 'custom-renderer'
)
}
// ---------------------------------------------------------------------------
// Matchers
// ---------------------------------------------------------------------------
export function matchSceneTarget<R>(
target: SceneTarget,
cases: {
page: (url: string) => R
'playwright-page': (pageRef: string, url?: string) => R
'storybook-story': (storyId: string, storybookUrl: string) => R
'react-component': (rendererId: string, componentId: string) => R
'vue-component': (rendererId: string, componentId: string) => R
'custom-renderer': (rendererId: string, targetId: string) => R
fixture: (fixtureId: string) => R
}
): R {
switch (target.kind) {
case 'page':
return cases.page(target.url)
case 'playwright-page':
return cases['playwright-page'](target.pageRef, target.url)
case 'storybook-story':
return cases['storybook-story'](target.storyId, target.storybookUrl)
case 'react-component':
return cases['react-component'](target.rendererId, target.componentId)
case 'vue-component':
return cases['vue-component'](target.rendererId, target.componentId)
case 'custom-renderer':
return cases['custom-renderer'](target.rendererId, target.targetId)
case 'fixture':
return cases.fixture(target.fixtureId)
}
}
// ---------------------------------------------------------------------------
// Factory Functions
// ---------------------------------------------------------------------------
export function pageTarget(url: string): SceneTarget {
return { kind: 'page', url }
}
export function playwrightPageTarget(pageRef: string, url?: string): SceneTarget {
return { kind: 'playwright-page', pageRef, url }
}
export function storybookStoryTarget(storyId: string, storybookUrl: string): SceneTarget {
return { kind: 'storybook-story', storyId, storybookUrl }
}
export function reactComponentTarget(rendererId: string, componentId: string): SceneTarget {
return { kind: 'react-component', rendererId, componentId }
}
export function vueComponentTarget(rendererId: string, componentId: string): SceneTarget {
return { kind: 'vue-component', rendererId, componentId }
}
export function customRendererTarget(rendererId: string, targetId: string): SceneTarget {
return { kind: 'custom-renderer', rendererId, targetId }
}
export function fixtureTarget(fixtureId: string): SceneTarget {
return { kind: 'fixture', fixtureId }
}
// ---------------------------------------------------------------------------
// RenderCase Factory
// ---------------------------------------------------------------------------
export function createRenderCase(
caseId: string,
input: unknown,
env?: Partial<Environment>,
metadata?: Record<string, unknown>
): RenderCase {
return { caseId, input, env, metadata }
}