} 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
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 () => {
} 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(() => {
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')
},
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,
+ )
+ })
})
<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>
<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>
import { createApp, vaporInteropPlugin } from 'vue'
import App from './App.vue'
+import '../transition/style.css'
createApp(App).use(vaporInteropPlugin).mount('#app')
-<script vapor>
+<script setup vapor lang="ts">
const msg = 'vapor compA'
</script>
<template>
-<script vapor>
+<script setup vapor lang="ts">
const msg = 'vapor compB'
</script>
<template>
-<script setup>
+<script setup lang="ts">
const msg = 'vdom comp'
</script>
<template>
type VaporTransitionHooks,
isFragment,
} from '../block'
-import { isVaporComponent } from '../component'
+import { type VaporComponentInstance, isVaporComponent } from '../component'
const decorate = (t: typeof VaporTransition) => {
t.displayName = 'VaporTransition'
return child
}
+
+export function setTransitionToInstance(
+ block: VaporComponentInstance,
+ hooks: VaporTransitionHooks,
+): void {
+ const child = findTransitionBlock(block.block)
+ if (!child) return
+
+ setTransitionHooks(child, hooks)
+}
ensureRenderer,
onScopeDispose,
renderSlot,
- setTransitionHooks,
+ setTransitionHooks as setVNodeTransitionHooks,
shallowRef,
simpleSetCurrentInstance,
} from '@vue/runtime-dom'
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<
))
instance.rawPropsRef = propsRef
instance.rawSlotsRef = slotsRef
+ if (vnode.transition) {
+ setTransitionToInstance(
+ instance,
+ vnode.transition as VaporTransitionHooks,
+ )
+ }
mountComponent(instance, container, selfAnchor)
simpleSetCurrentInstance(prev)
return instance
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)
}
const prev = currentInstance
simpleSetCurrentInstance(parentInstance)
if (!isMounted) {
- if (transition) setTransitionHooks(vnode, transition)
+ if (transition) setVNodeTransitionHooks(vnode, transition)
internals.mt(
vnode,
parentNode,
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 {
})
}
+ 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,
clearValue,
timeout,
nextFrame,
+ transitionStart,
+ waitForElement,
}
}