v1.1.0: pooled runtime, 959 tests, production hardening (0 squash)
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Vue renderer adapter for Imhotep Playwright.
|
||||
*
|
||||
* Mounts a Vue component into a container div on the page.
|
||||
* Uses Vue's createApp API (Vue 3) or constructor (Vue 2) depending
|
||||
* on what is available in the injected Vue runtime.
|
||||
*/
|
||||
|
||||
import { Page } from 'playwright'
|
||||
import type { SceneTarget } from 'imhotep-core/scene-target'
|
||||
import { RendererAdapter } from './renderers.js'
|
||||
|
||||
export interface VueAdapterOptions {
|
||||
/** Id for this renderer instance (defaults to 'vue'). */
|
||||
id?: string
|
||||
/**
|
||||
* Vue createApp entry point; used for capability detection at adapter
|
||||
* construction time (e.g. determining Vue 3 vs Vue 2). The actual runtime
|
||||
* must be exposed on the page as `window.__imhotepVueCreateApp`.
|
||||
*/
|
||||
createApp: (component: unknown, props?: Record<string, unknown>) => {
|
||||
mount(selector: string): void
|
||||
unmount(): void
|
||||
}
|
||||
/** Container selector or id to mount into. */
|
||||
containerSelector?: string
|
||||
}
|
||||
|
||||
export function createVueAdapter(options: VueAdapterOptions): RendererAdapter {
|
||||
const { createApp, containerSelector = '#__imhotep-mount' } = options
|
||||
|
||||
return {
|
||||
id: options.id ?? 'vue',
|
||||
|
||||
async mount(page: Page, target: SceneTarget, input: unknown): Promise<void> {
|
||||
if (target.kind !== 'vue-component') {
|
||||
throw new Error(`Vue adapter received non-Vue target: ${target.kind}`)
|
||||
}
|
||||
|
||||
const component = (input as Record<string, unknown>)?.component ?? target.componentId
|
||||
const props = (input as Record<string, unknown>)?.props ?? input
|
||||
|
||||
await page.evaluate(
|
||||
(args: {
|
||||
componentRef: string
|
||||
propsJson: string
|
||||
containerSelector: string
|
||||
}) => {
|
||||
const container = document.querySelector(args.containerSelector)
|
||||
if (!container) {
|
||||
throw new Error(`Mount container not found: ${args.containerSelector}`)
|
||||
}
|
||||
|
||||
// Clear previous content.
|
||||
container.innerHTML = ''
|
||||
|
||||
// Resolve Vue createApp BEFORE looking for the component.
|
||||
// This gives a clearer error when Vue is missing vs component missing.
|
||||
const createApp = (window as unknown as Record<string, unknown>).__imhotepVueCreateApp as
|
||||
| ((component: unknown, props?: Record<string, unknown>) => {
|
||||
mount(selector: string): void
|
||||
unmount(): void
|
||||
})
|
||||
| undefined
|
||||
|
||||
if (!createApp) {
|
||||
throw new Error(
|
||||
'Vue createApp not found on window.__imhotepVueCreateApp. ' +
|
||||
'Expose Vue.createApp as window.__imhotepVueCreateApp before mounting. ' +
|
||||
'Example: window.__imhotepVueCreateApp = Vue.createApp'
|
||||
)
|
||||
}
|
||||
|
||||
// Resolve the component from the global scope.
|
||||
const componentMap = (window as unknown as Record<string, unknown>).__imhotepComponents as
|
||||
| Record<string, unknown>
|
||||
| undefined
|
||||
const Component = componentMap?.[args.componentRef] ?? (window as unknown as Record<string, unknown>)[args.componentRef]
|
||||
|
||||
if (!Component) {
|
||||
throw new Error(
|
||||
`Component "${args.componentRef}" not found on window. ` +
|
||||
`Expose it as window.__imhotepComponents = { "${args.componentRef}": MyComponent } ` +
|
||||
`or window["${args.componentRef}"] = MyComponent`
|
||||
)
|
||||
}
|
||||
|
||||
const parsedProps = JSON.parse(args.propsJson)
|
||||
|
||||
const app = createApp(Component, parsedProps)
|
||||
app.mount(args.containerSelector)
|
||||
},
|
||||
{
|
||||
componentRef: component as string,
|
||||
propsJson: JSON.stringify(props),
|
||||
containerSelector,
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
async unmount(page: Page): Promise<void> {
|
||||
await page.evaluate((selector: string) => {
|
||||
// Attempt Vue 3 unmount if the element has a __vue_app__ property.
|
||||
const container = document.querySelector(selector)
|
||||
if (container) {
|
||||
const app = (container as unknown as Record<string, unknown>).__vue_app__ as
|
||||
| { unmount(): void }
|
||||
| undefined
|
||||
if (app?.unmount) {
|
||||
app.unmount()
|
||||
} else {
|
||||
container.innerHTML = ''
|
||||
}
|
||||
}
|
||||
}, containerSelector)
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user