203 lines
7.1 KiB
TypeScript
203 lines
7.1 KiB
TypeScript
|
|
/**
|
||
|
|
* 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 }
|
||
|
|
}
|