wip: add tests

This commit is contained in:
daiwei 2025-05-14 11:03:54 +08:00
parent b9620e8473
commit 7070f33300
3 changed files with 287 additions and 3 deletions

View File

@ -2,6 +2,10 @@ import { createVaporApp } from '../src'
import type { App } from '@vue/runtime-dom'
import type { VaporComponent, VaporComponentInstance } from '../src/component'
import type { RawProps } from '../src/componentProps'
import { compileScript, parse } from '@vue/compiler-sfc'
import * as runtimeVapor from '../src'
import * as runtimeDom from '@vue/runtime-dom'
import * as VueServerRenderer from '@vue/server-renderer'
export interface RenderContext {
component: VaporComponent
@ -82,3 +86,50 @@ export function makeRender<C = VaporComponent>(
return define
}
export { runtimeDom, runtimeVapor }
export function compile(
sfc: string,
data: runtimeDom.Ref<any>,
components: Record<string, any> = {},
{
vapor = true,
ssr = false,
}: {
vapor?: boolean | undefined
ssr?: boolean | undefined
} = {},
): any {
if (!sfc.includes(`<script`)) {
sfc =
`<script vapor>const data = _data; const components = _components;</script>` +
sfc
}
const descriptor = parse(sfc).descriptor
const script = compileScript(descriptor, {
id: 'x',
isProd: true,
inlineTemplate: true,
genDefaultAs: '__sfc__',
vapor,
templateOptions: {
ssr,
},
})
const code =
script.content
.replace(/\bimport {/g, 'const {')
.replace(/ as _/g, ': _')
.replace(/} from ['"]vue['"]/g, `} = Vue`)
.replace(/} from "vue\/server-renderer"/g, '} = VueServerRenderer') +
'\nreturn __sfc__'
return new Function('Vue', 'VueServerRenderer', '_data', '_components', code)(
{ ...runtimeDom, ...runtimeVapor },
VueServerRenderer,
data,
components,
)
}

View File

@ -5,11 +5,12 @@ import {
createIf,
createSlot,
createTemplateRefSetter,
delegateEvents,
insert,
renderEffect,
template,
} from '../../src'
import { makeRender } from '../_utils'
import { compile, makeRender, runtimeDom, runtimeVapor } from '../_utils'
import {
type ShallowRef,
currentInstance,
@ -716,3 +717,227 @@ describe('api: template ref', () => {
// expect(elRef1.value).toBe(elRef2.value)
// })
})
describe('interop: template ref', () => {
beforeEach(() => {
document.body.innerHTML = ''
})
const triggerEvent = (type: string, el: Element) => {
const event = new Event(type, { bubbles: true })
el.dispatchEvent(event)
}
delegateEvents('click')
async function testTemplateRefInterop(
code: string,
components: Record<string, { code: string; vapor: boolean }> = {},
data: any = {},
{ vapor = false } = {},
) {
const clientComponents: any = {}
for (const key in components) {
const comp = components[key]
const code = comp.code
const isVaporComp = !!comp.vapor
clientComponents[key] = compile(code, data, clientComponents, {
vapor: isVaporComp,
})
}
const clientComp = compile(code, data, clientComponents, {
vapor,
})
const app = (vapor ? runtimeVapor.createVaporApp : runtimeDom.createApp)(
clientComp,
)
app.use(runtimeVapor.vaporInteropPlugin)
const container = document.createElement('div')
document.body.appendChild(container)
app.mount(container)
return { container }
}
test('vdom app: useTemplateRef on vapor child', async () => {
const { container } = await testTemplateRefInterop(
`<script setup>
import { useTemplateRef } from 'vue'
const data = _data; const components = _components;
const elRef = useTemplateRef('el')
function click() {
elRef.value.change()
}
</script>
<template>
<button class="btn" @click="click"></button>
<components.VaporChild ref="el"/>
</template>`,
{
VaporChild: {
code: `
<script vapor>
import { ref } from 'vue'
const msg = ref('foo')
function change(){
msg.value = 'bar'
}
defineExpose({ change })
</script>
<template><div>{{msg}}</div></template>
`,
vapor: true,
},
},
)
expect(container.innerHTML).toBe(
`<button class="btn"></button><div>foo</div>`,
)
const btn = container.querySelector('.btn')
triggerEvent('click', btn!)
await nextTick()
expect(container.innerHTML).toBe(
`<button class="btn"></button><div>bar</div>`,
)
})
test('vdom app: static ref on vapor child', async () => {
const { container } = await testTemplateRefInterop(
`<script setup>
import { ref } from 'vue'
const data = _data; const components = _components;
const elRef = ref(null)
function click() {
elRef.value.change()
}
</script>
<template>
<button class="btn" @click="click"></button>
<components.VaporChild ref="elRef"/>
</template>`,
{
VaporChild: {
code: `
<script vapor>
import { ref } from 'vue'
const msg = ref('foo')
function change(){
msg.value = 'bar'
}
defineExpose({ change })
</script>
<template><div>{{msg}}</div></template>
`,
vapor: true,
},
},
)
expect(container.innerHTML).toBe(
`<button class="btn"></button><div>foo</div>`,
)
const btn = container.querySelector('.btn')
triggerEvent('click', btn!)
await nextTick()
expect(container.innerHTML).toBe(
`<button class="btn"></button><div>bar</div>`,
)
})
test('vapor app: useTemplateRef on vdom child', async () => {
const { container } = await testTemplateRefInterop(
`<script vapor>
import { useTemplateRef } from 'vue'
const data = _data; const components = _components;
const elRef = useTemplateRef('el')
function click() {
elRef.value.change()
}
</script>
<template>
<button class="btn" @click="click"></button>
<components.VDOMChild ref="el"/>
</template>`,
{
VDOMChild: {
code: `
<script setup>
import { ref } from 'vue'
const msg = ref('foo')
function change(){
msg.value = 'bar'
}
defineExpose({ change })
</script>
<template><div>{{msg}}</div></template>
`,
vapor: false,
},
},
undefined,
{ vapor: true },
)
expect(container.innerHTML).toBe(
`<button class="btn"></button><div>foo</div>`,
)
const btn = container.querySelector('.btn')
triggerEvent('click', btn!)
await nextTick()
expect(container.innerHTML).toBe(
`<button class="btn"></button><div>bar</div>`,
)
})
test('vapor app: static ref on vdom child', async () => {
const { container } = await testTemplateRefInterop(
`<script vapor>
import { ref } from 'vue'
const data = _data; const components = _components;
const elRef = ref(null)
function click() {
elRef.value.change()
}
</script>
<template>
<button class="btn" @click="click"></button>
<components.VDomChild ref="elRef"/>
</template>`,
{
VDomChild: {
code: `
<script setup>
import { ref } from 'vue'
const msg = ref('foo')
function change(){
msg.value = 'bar'
}
defineExpose({ change })
</script>
<template><div>{{msg}}</div></template>
`,
vapor: false,
},
},
undefined,
{ vapor: true },
)
expect(container.innerHTML).toBe(
`<button class="btn"></button><div>foo</div>`,
)
const btn = container.querySelector('.btn')
triggerEvent('click', btn!)
await nextTick()
expect(container.innerHTML).toBe(
`<button class="btn"></button><div>bar</div>`,
)
})
})

View File

@ -30,7 +30,7 @@ import {
unmountComponent,
} from './component'
import { type Block, VaporFragment, insert, remove } from './block'
import { EMPTY_OBJ, extend, isFunction } from '@vue/shared'
import { EMPTY_OBJ, extend, isFunction, isReservedProp } from '@vue/shared'
import { type RawProps, rawPropsProxyHandlers } from './componentProps'
import type { RawSlots, VaporSlot } from './componentSlots'
import { renderEffect } from './renderEffect'
@ -49,7 +49,15 @@ const vaporInteropImpl: Omit<
const prev = currentInstance
simpleSetCurrentInstance(parentComponent)
const propsRef = shallowRef(vnode.props)
// filter out reserved props
const props: VNode['props'] = {}
for (const key in vnode.props) {
if (!isReservedProp(key)) {
props[key] = vnode.props[key]
}
}
const propsRef = shallowRef(props)
const slotsRef = shallowRef(vnode.children)
// @ts-expect-error