wip: add interop tests

This commit is contained in:
daiwei 2025-03-07 14:28:42 +08:00
parent d5d8ada577
commit d1849288d1
10 changed files with 265 additions and 53 deletions

View File

@ -5,8 +5,18 @@ import {
} from '../../../packages/vue/__tests__/e2e/e2eUtils'
import connect from 'connect'
import sirv from 'sirv'
const { page, classList, text, nextFrame, timeout, isVisible, count, html } =
setupPuppeteer()
const {
page,
classList,
text,
nextFrame,
timeout,
isVisible,
count,
html,
transitionStart,
waitForElement,
} = setupPuppeteer()
const duration = process.env.CI ? 200 : 50
const buffer = process.env.CI ? 50 : 20
@ -32,43 +42,6 @@ describe('vapor transition', () => {
await page().waitForSelector('#app')
})
const transitionStart = (btnSelector: string, containerSelector: string) =>
page().evaluate(
([btnSel, containerSel]) => {
;(document.querySelector(btnSel) as HTMLElement)!.click()
return Promise.resolve().then(() => {
const container = document.querySelector(containerSel)!
return {
classNames: container.className.split(/\s+/g),
innerHTML: container.innerHTML,
}
})
},
[btnSelector, containerSelector],
)
const waitForElement = (
selector: string,
text: string,
classNames: string[], // if empty, check for no classes
timeout = 2000,
) =>
page().waitForFunction(
(sel, expectedText, expectedClasses) => {
const el = document.querySelector(sel)
const hasClasses =
expectedClasses.length === 0
? el?.classList.length === 0
: expectedClasses.every(c => el?.classList.contains(c))
const hasText = el?.textContent?.includes(expectedText)
return !!el && hasClasses && hasText
},
{ timeout },
selector,
text,
classNames,
)
test(
'should work with v-show',
async () => {

View File

@ -5,10 +5,23 @@ import {
} from '../../../packages/vue/__tests__/e2e/e2eUtils'
import connect from 'connect'
import sirv from 'sirv'
const {
page,
click,
text,
enterValue,
html,
transitionStart,
waitForElement,
nextFrame,
timeout,
} = setupPuppeteer()
const duration = process.env.CI ? 200 : 50
const buffer = process.env.CI ? 50 : 20
const transitionFinish = (time = duration) => timeout(time + buffer)
describe('vdom / vapor interop', () => {
const { page, click, text, enterValue } = setupPuppeteer()
let server: any
const port = '8193'
beforeAll(() => {
@ -22,12 +35,15 @@ describe('vdom / vapor interop', () => {
server.close()
})
beforeEach(async () => {
const baseUrl = `http://localhost:${port}/interop/`
await page().goto(baseUrl)
await page().waitForSelector('#app')
})
test(
'should work',
async () => {
const baseUrl = `http://localhost:${port}/interop/`
await page().goto(baseUrl)
expect(await text('.vapor > h2')).toContain('Vapor component in VDOM')
expect(await text('.vapor-prop')).toContain('hello')
@ -81,4 +97,121 @@ describe('vdom / vapor interop', () => {
},
E2E_TIMEOUT,
)
describe('vdom transition', () => {
test(
'render vapor component',
async () => {
const btnSelector = '.trans-vapor > button'
const containerSelector = '.trans-vapor > div'
expect(await html(containerSelector)).toBe(
`<div key="0">vapor compA</div>`,
)
// comp leave
expect(
(await transitionStart(btnSelector, containerSelector)).innerHTML,
).toBe(
`<div key="0" class="v-leave-from v-leave-active">vapor compA</div><!---->`,
)
await nextFrame()
expect(await html(containerSelector)).toBe(
`<div key="0" class="v-leave-active v-leave-to">vapor compA</div><!---->`,
)
await transitionFinish()
expect(await html(containerSelector)).toBe(`<!---->`)
// comp enter
expect(
(await transitionStart(btnSelector, containerSelector)).innerHTML,
).toBe(
`<div key="0" class="v-enter-from v-enter-active">vapor compA</div>`,
)
await nextFrame()
expect(await html(containerSelector)).toBe(
`<div key="0" class="v-enter-active v-enter-to">vapor compA</div>`,
)
await transitionFinish()
expect(await html(containerSelector)).toBe(
`<div key="0" class="">vapor compA</div>`,
)
},
E2E_TIMEOUT,
)
test(
'switch between vdom/vapor component (out-in mode)',
async () => {
const btnSelector = '.trans-vdom-vapor-out-in > button'
const containerSelector = '.trans-vdom-vapor-out-in > div'
const childSelector = `${containerSelector} > div`
expect(await html(containerSelector)).toBe(`<div>vdom comp</div>`)
// switch to vapor comp
// vdom comp leave
expect(
(await transitionStart(btnSelector, containerSelector)).innerHTML,
).toBe(
`<div class="fade-leave-from fade-leave-active">vdom comp</div><!---->`,
)
await nextFrame()
expect(await html(containerSelector)).toBe(
`<div class="fade-leave-active fade-leave-to">vdom comp</div><!---->`,
)
// vapor comp enter
await waitForElement(childSelector, 'vapor compA', [
'fade-enter-from',
'fade-enter-active',
])
await nextFrame()
expect(await html(containerSelector)).toBe(
`<div class="fade-enter-active fade-enter-to">vapor compA</div>`,
)
await transitionFinish()
expect(await html(containerSelector)).toBe(
`<div class="">vapor compA</div>`,
)
// switch to vdom comp
// vapor comp leave
expect(
(await transitionStart(btnSelector, containerSelector)).innerHTML,
).toBe(
`<div class="fade-leave-from fade-leave-active">vapor compA</div><!---->`,
)
await nextFrame()
expect(await html(containerSelector)).toBe(
`<div class="fade-leave-active fade-leave-to">vapor compA</div><!---->`,
)
// vdom comp enter
await waitForElement(childSelector, 'vdom comp', [
'fade-enter-from',
'fade-enter-active',
])
await nextFrame()
expect(await html(containerSelector)).toBe(
`<div class="fade-enter-active fade-enter-to">vdom comp</div>`,
)
await transitionFinish()
expect(await html(containerSelector)).toBe(
`<div class="">vdom comp</div>`,
)
},
E2E_TIMEOUT,
)
})
})

View File

@ -1,9 +1,18 @@
<script setup lang="ts">
import { ref } from 'vue'
import { ref, shallowRef } from 'vue'
import VaporComp from './VaporComp.vue'
import VaporCompA from '../transition/components/VaporCompA.vue'
import VdomComp from '../transition/components/VdomComp.vue'
const msg = ref('hello')
const passSlot = ref(true)
const toggleVapor = ref(true)
const interopComponent = shallowRef(VdomComp)
function toggleInteropComponent() {
interopComponent.value =
interopComponent.value === VaporCompA ? VdomComp : VaporCompA
}
</script>
<template>
@ -19,4 +28,28 @@ const passSlot = ref(true)
<template #test v-if="passSlot">A test slot</template>
</VaporComp>
<!-- transition interop -->
<div>
<div class="trans-vapor">
<button @click="toggleVapor = !toggleVapor">
toggle vapor component
</button>
<div>
<Transition>
<VaporCompA v-if="toggleVapor" />
</Transition>
</div>
</div>
<div class="trans-vdom-vapor-out-in">
<button @click="toggleInteropComponent">
switch between vdom/vapor component out-in mode
</button>
<div>
<Transition name="fade" mode="out-in">
<component :is="interopComponent"></component>
</Transition>
</div>
</div>
</div>
</template>

View File

@ -1,4 +1,5 @@
import { createApp, vaporInteropPlugin } from 'vue'
import App from './App.vue'
import '../transition/style.css'
createApp(App).use(vaporInteropPlugin).mount('#app')

View File

@ -1,4 +1,4 @@
<script vapor>
<script setup vapor lang="ts">
const msg = 'vapor compA'
</script>
<template>

View File

@ -1,4 +1,4 @@
<script vapor>
<script setup vapor lang="ts">
const msg = 'vapor compB'
</script>
<template>

View File

@ -1,4 +1,4 @@
<script setup>
<script setup lang="ts">
const msg = 'vdom comp'
</script>
<template>

View File

@ -20,7 +20,7 @@ import {
type VaporTransitionHooks,
isFragment,
} from '../block'
import { isVaporComponent } from '../component'
import { type VaporComponentInstance, isVaporComponent } from '../component'
const decorate = (t: typeof VaporTransition) => {
t.displayName = 'VaporTransition'
@ -244,3 +244,13 @@ export function findTransitionBlock(block: Block): TransitionBlock | undefined {
return child
}
export function setTransitionToInstance(
block: VaporComponentInstance,
hooks: VaporTransitionHooks,
): void {
const child = findTransitionBlock(block.block)
if (!child) return
setTransitionHooks(child, hooks)
}

View File

@ -15,7 +15,7 @@ import {
ensureRenderer,
onScopeDispose,
renderSlot,
setTransitionHooks,
setTransitionHooks as setVNodeTransitionHooks,
shallowRef,
simpleSetCurrentInstance,
} from '@vue/runtime-dom'
@ -28,13 +28,20 @@ import {
mountComponent,
unmountComponent,
} from './component'
import { type Block, VaporFragment, insert, remove } from './block'
import {
type Block,
VaporFragment,
type VaporTransitionHooks,
insert,
remove,
} from './block'
import { EMPTY_OBJ, extend, isFunction } from '@vue/shared'
import { type RawProps, rawPropsProxyHandlers } from './componentProps'
import type { RawSlots, VaporSlot } from './componentSlots'
import { renderEffect } from './renderEffect'
import { createTextNode } from './dom/node'
import { optimizePropertyLookup } from './dom/prop'
import { setTransitionToInstance } from './components/Transition'
// mounting vapor components and slots in vdom
const vaporInteropImpl: Omit<
@ -62,6 +69,12 @@ const vaporInteropImpl: Omit<
))
instance.rawPropsRef = propsRef
instance.rawSlotsRef = slotsRef
if (vnode.transition) {
setTransitionToInstance(
instance,
vnode.transition as VaporTransitionHooks,
)
}
mountComponent(instance, container, selfAnchor)
simpleSetCurrentInstance(prev)
return instance
@ -174,7 +187,7 @@ function createVDOMComponent(
let isMounted = false
const parentInstance = currentInstance as VaporComponentInstance
const unmount = (parentNode?: ParentNode, transition?: TransitionHooks) => {
if (transition) setTransitionHooks(vnode, transition)
if (transition) setVNodeTransitionHooks(vnode, transition)
internals.umt(vnode.component!, null, !!parentNode)
}
@ -182,7 +195,7 @@ function createVDOMComponent(
const prev = currentInstance
simpleSetCurrentInstance(parentInstance)
if (!isMounted) {
if (transition) setTransitionHooks(vnode, transition)
if (transition) setVNodeTransitionHooks(vnode, transition)
internals.mt(
vnode,
parentNode,

View File

@ -50,6 +50,16 @@ interface PuppeteerUtils {
clearValue(selector: string): Promise<any>
timeout(time: number): Promise<any>
nextFrame(): Promise<any>
transitionStart(
btnSelector: string,
containerSelector: string,
): Promise<{ classNames: string[]; innerHTML: string }>
waitForElement(
selector: string,
text: string,
classNames: string[],
timeout?: number,
): Promise<any>
}
export function setupPuppeteer(args?: string[]): PuppeteerUtils {
@ -200,6 +210,43 @@ export function setupPuppeteer(args?: string[]): PuppeteerUtils {
})
}
const transitionStart = (btnSelector: string, containerSelector: string) =>
page.evaluate(
([btnSel, containerSel]) => {
;(document.querySelector(btnSel) as HTMLElement)!.click()
return Promise.resolve().then(() => {
const container = document.querySelector(containerSel)!
return {
classNames: container.className.split(/\s+/g),
innerHTML: container.innerHTML,
}
})
},
[btnSelector, containerSelector],
)
const waitForElement = (
selector: string,
text: string,
classNames: string[], // if empty, check for no classes
timeout = 2000,
) =>
page.waitForFunction(
(sel, expectedText, expectedClasses) => {
const el = document.querySelector(sel)
const hasClasses =
expectedClasses.length === 0
? el?.classList.length === 0
: expectedClasses.every(c => el?.classList.contains(c))
const hasText = el?.textContent?.includes(expectedText)
return !!el && hasClasses && hasText
},
{ timeout },
selector,
text,
classNames,
)
return {
page: () => page,
click,
@ -219,5 +266,7 @@ export function setupPuppeteer(args?: string[]): PuppeteerUtils {
clearValue,
timeout,
nextFrame,
transitionStart,
waitForElement,
}
}