diff --git a/packages/vue/__tests__/Transition.spec.ts b/packages/vue/__tests__/Transition.spec.ts
new file mode 100644
index 00000000..135343ef
--- /dev/null
+++ b/packages/vue/__tests__/Transition.spec.ts
@@ -0,0 +1,1636 @@
+import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils'
+import path from 'path'
+import { mockWarn } from '@vue/shared'
+import { h, createApp, Transition } from 'vue'
+
+describe('e2e: Transition', () => {
+ mockWarn()
+ const { page, html, classList, isVisible } = setupPuppeteer()
+ const baseUrl = `file://${path.resolve(__dirname, './transition.html')}`
+
+ const duration = 50
+ const buffer = 5
+
+ const classWhenTransitionStart = () =>
+ page().evaluate(() => {
+ (document.querySelector('#toggleBtn') as any)!.click()
+ return Promise.resolve().then(() => {
+ return document.querySelector('#container div')!.className.split(/\s+/g)
+ })
+ })
+
+ const transitionFinish = (time = duration) =>
+ new Promise(r => {
+ setTimeout(r, time + buffer)
+ })
+
+ const nextFrame = () => {
+ return page().evaluate(() => {
+ return new Promise(resolve => {
+ requestAnimationFrame(() => {
+ requestAnimationFrame(resolve)
+ })
+ })
+ })
+ }
+
+ beforeEach(async () => {
+ await page().goto(baseUrl)
+ await page().waitFor('#app')
+ })
+
+ describe('transition with v-if', () => {
+ test(
+ 'basic transition',
+ async () => {
+ await page().evaluate(() => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+
+ `,
+ setup: () => {
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ return { toggle, click }
+ }
+ }).mount('#app')
+ })
+ expect(await html('#container')).toBe('content
')
+
+ // leave
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'v-leave-active',
+ 'v-leave-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'v-leave-active',
+ 'v-leave-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe('')
+
+ // enter
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'v-enter-active',
+ 'v-enter-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'v-enter-active',
+ 'v-enter-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe('content
')
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'named transition',
+ async () => {
+ await page().evaluate(() => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+
+ `,
+ setup: () => {
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ return { toggle, click }
+ }
+ }).mount('#app')
+ })
+ expect(await html('#container')).toBe('content
')
+
+ // leave
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe('')
+
+ // enter
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe('content
')
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'custom transition classes',
+ async () => {
+ await page().evaluate(() => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+
+ `,
+ setup: () => {
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ return { toggle, click }
+ }
+ }).mount('#app')
+ })
+ expect(await html('#container')).toBe('content
')
+
+ // leave
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'bye-active',
+ 'bye-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'bye-active',
+ 'bye-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe('')
+
+ // enter
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'hello-active',
+ 'hello-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'hello-active',
+ 'hello-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe('content
')
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'transition with dynamic name',
+ async () => {
+ await page().evaluate(() => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+
+
+ `,
+ setup: () => {
+ const name = ref('test')
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ const changeName = () => (name.value = 'changed')
+ return { toggle, click, name, changeName }
+ }
+ }).mount('#app')
+ })
+ expect(await html('#container')).toBe('content
')
+
+ // leave
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe('')
+
+ // enter
+ await page().evaluate(() => {
+ ;(document.querySelector('#changeNameBtn') as any).click()
+ })
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'changed-enter-active',
+ 'changed-enter-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'changed-enter-active',
+ 'changed-enter-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe('content
')
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'transition events without appear',
+ async () => {
+ const beforeLeaveSpy = jest.fn()
+ const onLeaveSpy = jest.fn()
+ const afterLeaveSpy = jest.fn()
+ const beforeEnterSpy = jest.fn()
+ const onEnterSpy = jest.fn()
+ const afterEnterSpy = jest.fn()
+
+ await page().exposeFunction('onLeaveSpy', onLeaveSpy)
+ await page().exposeFunction('onEnterSpy', onEnterSpy)
+ await page().exposeFunction('beforeLeaveSpy', beforeLeaveSpy)
+ await page().exposeFunction('beforeEnterSpy', beforeEnterSpy)
+ await page().exposeFunction('afterLeaveSpy', afterLeaveSpy)
+ await page().exposeFunction('afterEnterSpy', afterEnterSpy)
+
+ await page().evaluate(() => {
+ const {
+ beforeEnterSpy,
+ onEnterSpy,
+ afterEnterSpy,
+ beforeLeaveSpy,
+ onLeaveSpy,
+ afterLeaveSpy
+ } = window as any
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+
+ `,
+ setup: () => {
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ return {
+ toggle,
+ click,
+ beforeEnterSpy,
+ onEnterSpy,
+ afterEnterSpy,
+ beforeLeaveSpy,
+ onLeaveSpy,
+ afterLeaveSpy
+ }
+ }
+ }).mount('#app')
+ })
+ expect(await html('#container')).toBe('content
')
+
+ // leave
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-from'
+ ])
+ // todo test event with arguments. Note: not get dom, get object. '{}'
+ expect(beforeLeaveSpy).toBeCalled()
+ expect(onLeaveSpy).not.toBeCalled()
+ expect(afterLeaveSpy).not.toBeCalled()
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-to'
+ ])
+ expect(beforeLeaveSpy).toBeCalled()
+ expect(afterLeaveSpy).not.toBeCalled()
+ await transitionFinish()
+ expect(await html('#container')).toBe('')
+ expect(afterLeaveSpy).toBeCalled()
+
+ // enter
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-from'
+ ])
+ expect(beforeEnterSpy).toBeCalled()
+ expect(onEnterSpy).not.toBeCalled()
+ expect(afterEnterSpy).not.toBeCalled()
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-to'
+ ])
+ expect(onEnterSpy).toBeCalled()
+ expect(afterEnterSpy).not.toBeCalled()
+ await transitionFinish()
+ expect(await html('#container')).toBe('content
')
+ expect(afterEnterSpy).toBeCalled()
+ },
+ E2E_TIMEOUT
+ )
+
+ test('onEnterCancelled', async () => {
+ const enterCancelledSpy = jest.fn()
+
+ await page().exposeFunction('enterCancelledSpy', enterCancelledSpy)
+
+ await page().evaluate(() => {
+ const { enterCancelledSpy } = window as any
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+
+ `,
+ setup: () => {
+ const toggle = ref(false)
+ const click = () => (toggle.value = !toggle.value)
+ return {
+ toggle,
+ click,
+ enterCancelledSpy
+ }
+ }
+ }).mount('#app')
+ })
+ expect(await html('#container')).toBe('')
+
+ // enter
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-to'
+ ])
+
+ // cancel (leave)
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-from'
+ ])
+ // fixme
+ expect(enterCancelledSpy).not.toBeCalled()
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe('')
+ })
+
+ test(
+ 'transition on appear',
+ async () => {
+ await page().evaluate(async () => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+
+ `,
+ setup: () => {
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ return { toggle, click }
+ }
+ }).mount('#app')
+ })
+ // appear
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-appear-active',
+ 'test-appear-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-appear-active',
+ 'test-appear-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe('content
')
+
+ // leave
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe('')
+
+ // enter
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe('content
')
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'transition events with appear',
+ async () => {
+ const onLeaveSpy = jest.fn()
+ const onEnterSpy = jest.fn()
+ const onAppearSpy = jest.fn()
+ const beforeLeaveSpy = jest.fn()
+ const beforeEnterSpy = jest.fn()
+ const beforeAppearSpy = jest.fn()
+ const afterLeaveSpy = jest.fn()
+ const afterEnterSpy = jest.fn()
+ const afterAppearSpy = jest.fn()
+
+ await page().exposeFunction('onLeaveSpy', onLeaveSpy)
+ await page().exposeFunction('onEnterSpy', onEnterSpy)
+ await page().exposeFunction('onAppearSpy', onAppearSpy)
+ await page().exposeFunction('beforeLeaveSpy', beforeLeaveSpy)
+ await page().exposeFunction('beforeEnterSpy', beforeEnterSpy)
+ await page().exposeFunction('beforeAppearSpy', beforeAppearSpy)
+ await page().exposeFunction('afterLeaveSpy', afterLeaveSpy)
+ await page().exposeFunction('afterEnterSpy', afterEnterSpy)
+ await page().exposeFunction('afterAppearSpy', afterAppearSpy)
+
+ const appearClass = await page().evaluate(async () => {
+ const {
+ beforeAppearSpy,
+ onAppearSpy,
+ afterAppearSpy,
+ beforeEnterSpy,
+ onEnterSpy,
+ afterEnterSpy,
+ beforeLeaveSpy,
+ onLeaveSpy,
+ afterLeaveSpy
+ } = window as any
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+
+ `,
+ setup: () => {
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ return {
+ toggle,
+ click,
+ beforeAppearSpy,
+ onAppearSpy,
+ afterAppearSpy,
+ beforeEnterSpy,
+ onEnterSpy,
+ afterEnterSpy,
+ beforeLeaveSpy,
+ onLeaveSpy,
+ afterLeaveSpy
+ }
+ }
+ }).mount('#app')
+ return Promise.resolve().then(() => {
+ return document.querySelector('.test')!.className.split(/\s+/g)
+ })
+ })
+ // appear fixme spy called
+ expect(appearClass).toStrictEqual([
+ 'test',
+ 'test-appear-active',
+ 'test-appear-from'
+ ])
+ expect(beforeAppearSpy).not.toBeCalled()
+ expect(onAppearSpy).not.toBeCalled()
+ expect(afterAppearSpy).not.toBeCalled()
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-appear-active',
+ 'test-appear-to'
+ ])
+ expect(onAppearSpy).not.toBeCalled()
+ expect(afterAppearSpy).not.toBeCalled()
+ await transitionFinish()
+ expect(await html('#container')).toBe('content
')
+ expect(afterAppearSpy).not.toBeCalled()
+
+ // leave
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-from'
+ ])
+ expect(beforeLeaveSpy).toBeCalled()
+ expect(onLeaveSpy).not.toBeCalled()
+ expect(afterLeaveSpy).not.toBeCalled()
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-to'
+ ])
+ expect(onLeaveSpy).toBeCalled()
+ expect(afterLeaveSpy).not.toBeCalled()
+ await transitionFinish()
+ expect(await html('#container')).toBe('')
+ expect(afterLeaveSpy).toBeCalled()
+
+ // enter fixme spy called
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-from'
+ ])
+ expect(beforeEnterSpy).toBeCalled()
+ expect(onEnterSpy).toBeCalled()
+ expect(afterEnterSpy).toBeCalled()
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-to'
+ ])
+ expect(onEnterSpy).toBeCalled()
+ expect(afterEnterSpy).toBeCalled()
+ await transitionFinish()
+ expect(await html('#container')).toBe('content
')
+ expect(afterEnterSpy).toBeCalled()
+ },
+ E2E_TIMEOUT
+ )
+
+ // fixme
+ test(
+ 'css: false',
+ async () => {
+ const onLeaveSpy = jest.fn()
+ const onEnterSpy = jest.fn()
+
+ await page().exposeFunction('onLeaveSpy', onLeaveSpy)
+ await page().exposeFunction('onEnterSpy', onEnterSpy)
+
+ await page().evaluate(() => {
+ const { onLeaveSpy, onEnterSpy } = window as any
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+
+ `,
+ setup: () => {
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ return { toggle, click, onLeaveSpy, onEnterSpy }
+ }
+ }).mount('#app')
+ })
+ expect(await html('#container')).toBe('content
')
+
+ // leave
+ await classWhenTransitionStart()
+ expect(onLeaveSpy).toBeCalled()
+ expect(await html('#container')).toBe(
+ 'content
'
+ )
+ // enter
+ await classWhenTransitionStart()
+ expect(onEnterSpy).toBeCalled()
+ expect(await html('#container')).toBe('content
')
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'no transition detected',
+ async () => {
+ await page().evaluate(() => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+
+ `,
+ setup: () => {
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ return { toggle, click }
+ }
+ }).mount('#app')
+ })
+ expect(await html('#container')).toBe('content
')
+
+ // leave
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'noop-leave-active',
+ 'noop-leave-from'
+ ])
+ await nextFrame()
+ expect(await html('#container')).toBe('')
+
+ // enter
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'noop-enter-active',
+ 'noop-enter-from'
+ ])
+ await nextFrame()
+ expect(await html('#container')).toBe('content
')
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'animations',
+ async () => {
+ await page().evaluate(() => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+
+ `,
+ setup: () => {
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ return { toggle, click }
+ }
+ }).mount('#app')
+ })
+ expect(await html('#container')).toBe('content
')
+
+ // leave
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test-anim-leave-active',
+ 'test-anim-leave-from'
+ ])
+ await nextFrame()
+ expect(await classList('#container div')).toStrictEqual([
+ 'test-anim-leave-active',
+ 'test-anim-leave-to'
+ ])
+ await transitionFinish(duration * 2)
+ expect(await html('#container')).toBe('')
+
+ // enter
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test-anim-enter-active',
+ 'test-anim-enter-from'
+ ])
+ await nextFrame()
+ expect(await classList('#container div')).toStrictEqual([
+ 'test-anim-enter-active',
+ 'test-anim-enter-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe('content
')
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'explicit transition type',
+ async () => {
+ await page().evaluate(() => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+
+ `,
+ setup: () => {
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ return { toggle, click }
+ }
+ }).mount('#app')
+ })
+ expect(await html('#container')).toBe('content
')
+
+ // leave
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test-anim-long-leave-active',
+ 'test-anim-long-leave-from'
+ ])
+ await nextFrame()
+ expect(await classList('#container div')).toStrictEqual([
+ 'test-anim-long-leave-active',
+ 'test-anim-long-leave-to'
+ ])
+ await new Promise(r => {
+ setTimeout(r, duration + 5)
+ })
+ expect(await classList('#container div')).toStrictEqual([
+ 'test-anim-long-leave-active',
+ 'test-anim-long-leave-to'
+ ])
+ await transitionFinish(duration * 2)
+ expect(await html('#container')).toBe('')
+
+ // enter
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test-anim-long-enter-active',
+ 'test-anim-long-enter-from'
+ ])
+ await nextFrame()
+ expect(await classList('#container div')).toStrictEqual([
+ 'test-anim-long-enter-active',
+ 'test-anim-long-enter-to'
+ ])
+ await new Promise(r => {
+ setTimeout(r, duration + 5)
+ })
+ expect(await classList('#container div')).toStrictEqual([
+ 'test-anim-long-enter-active',
+ 'test-anim-long-enter-to'
+ ])
+ await transitionFinish(duration * 2)
+ expect(await html('#container')).toBe('content
')
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'transition on SVG elements',
+ async () => {
+ await page().evaluate(() => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+
+ `,
+ setup: () => {
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ return { toggle, click }
+ }
+ }).mount('#app')
+ })
+ expect(await html('#container')).toBe(
+ ''
+ )
+
+ const svgTransitionStart = () =>
+ page().evaluate(() => {
+ document.querySelector('button')!.click()
+ return Promise.resolve().then(() => {
+ return document
+ .querySelector('.test')!
+ .getAttribute('class')!
+ .split(/\s+/g)
+ })
+ })
+
+ // leave
+ expect(await svgTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe('')
+
+ // enter
+ expect(await svgTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe(
+ ''
+ )
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'custom transition higher-order component',
+ async () => {
+ await page().evaluate(() => {
+ const { createApp, ref, h, Transition } = (window as any).Vue
+ createApp({
+ template: `
+
+
+ `,
+ components: {
+ 'my-transition': (props: any, { slots }: any) => {
+ return h(Transition, { name: 'test' }, slots)
+ }
+ },
+ setup: () => {
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ return { toggle, click }
+ }
+ }).mount('#app')
+ })
+ expect(await html('#container')).toBe('content
')
+
+ // leave
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe('')
+
+ // enter
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe('content
')
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'transition on child components with empty root node',
+ async () => {
+ await page().evaluate(() => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+
+
+
+
+
+
+ `,
+ components: {
+ one: {
+ template: 'one
'
+ },
+ two: {
+ template: 'two
'
+ }
+ },
+ setup: () => {
+ const toggle = ref(true)
+ const view = ref('one')
+ const click = () => (toggle.value = !toggle.value)
+ const change = () =>
+ (view.value = view.value === 'one' ? 'two' : 'one')
+ return { toggle, click, change, view }
+ }
+ }).mount('#app')
+ })
+ expect(await html('#container')).toBe('')
+
+ // change view -> 'two'
+ await page().evaluate(() => {
+ (document.querySelector('#changeViewBtn') as any)!.click()
+ })
+ // enter
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe('two
')
+
+ // change view -> 'one'
+ await page().evaluate(() => {
+ (document.querySelector('#changeViewBtn') as any)!.click()
+ })
+ // leave
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe('')
+ },
+ E2E_TIMEOUT
+ )
+ })
+
+ describe('transition with v-show', () => {
+ test(
+ 'named transition with v-show',
+ async () => {
+ await page().evaluate(() => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+
+ `,
+ setup: () => {
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ return { toggle, click }
+ }
+ }).mount('#app')
+ })
+ expect(await html('#container')).toBe('content
')
+ expect(await isVisible('.test')).toBe(true)
+
+ // leave
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-to'
+ ])
+ await transitionFinish()
+ expect(await isVisible('.test')).toBe(false)
+
+ // enter
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe(
+ 'content
'
+ )
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'transition events with v-show',
+ async () => {
+ const beforeLeaveSpy = jest.fn()
+ const onLeaveSpy = jest.fn()
+ const afterLeaveSpy = jest.fn()
+ const beforeEnterSpy = jest.fn()
+ const onEnterSpy = jest.fn()
+ const afterEnterSpy = jest.fn()
+
+ await page().exposeFunction('onLeaveSpy', onLeaveSpy)
+ await page().exposeFunction('onEnterSpy', onEnterSpy)
+ await page().exposeFunction('beforeLeaveSpy', beforeLeaveSpy)
+ await page().exposeFunction('beforeEnterSpy', beforeEnterSpy)
+ await page().exposeFunction('afterLeaveSpy', afterLeaveSpy)
+ await page().exposeFunction('afterEnterSpy', afterEnterSpy)
+
+ await page().evaluate(() => {
+ const {
+ beforeEnterSpy,
+ onEnterSpy,
+ afterEnterSpy,
+ beforeLeaveSpy,
+ onLeaveSpy,
+ afterLeaveSpy
+ } = window as any
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+
+ `,
+ setup: () => {
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ return {
+ toggle,
+ click,
+ beforeEnterSpy,
+ onEnterSpy,
+ afterEnterSpy,
+ beforeLeaveSpy,
+ onLeaveSpy,
+ afterLeaveSpy
+ }
+ }
+ }).mount('#app')
+ })
+ expect(await html('#container')).toBe('content
')
+
+ // leave
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-from'
+ ])
+ expect(beforeLeaveSpy).toBeCalled()
+ expect(onLeaveSpy).not.toBeCalled()
+ expect(afterLeaveSpy).not.toBeCalled()
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-to'
+ ])
+ expect(beforeLeaveSpy).toBeCalled()
+ expect(afterLeaveSpy).not.toBeCalled()
+ await transitionFinish()
+ expect(await isVisible('.test')).toBe(false)
+ expect(afterLeaveSpy).toBeCalled()
+
+ // enter
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-from'
+ ])
+ expect(beforeEnterSpy).toBeCalled()
+ expect(onEnterSpy).not.toBeCalled()
+ expect(afterEnterSpy).not.toBeCalled()
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-to'
+ ])
+ expect(onEnterSpy).toBeCalled()
+ expect(afterEnterSpy).not.toBeCalled()
+ await transitionFinish()
+ expect(await html('#container')).toBe(
+ 'content
'
+ )
+ expect(afterEnterSpy).toBeCalled()
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'onLeaveCancelled (v-show only)',
+ async () => {
+ const onLeaveCancelledSpy = jest.fn()
+
+ await page().exposeFunction('onLeaveCancelledSpy', onLeaveCancelledSpy)
+ await page().evaluate(() => {
+ const { onLeaveCancelledSpy } = window as any
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+
+ `,
+ setup: () => {
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ return { toggle, click, onLeaveCancelledSpy }
+ }
+ }).mount('#app')
+ })
+ expect(await html('#container')).toBe('content
')
+ expect(await isVisible('.test')).toBe(true)
+
+ // leave
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-to'
+ ])
+
+ // cancel (enter)
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-from'
+ ])
+ // fixme
+ expect(onLeaveCancelledSpy).not.toBeCalled()
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe(
+ 'content
'
+ )
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'transition on appear with v-show',
+ async () => {
+ const appearClass = await page().evaluate(async () => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+
+ `,
+ setup: () => {
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ return { toggle, click }
+ }
+ }).mount('#app')
+ return Promise.resolve().then(() => {
+ return document.querySelector('.test')!.className.split(/\s+/g)
+ })
+ })
+ // appear
+ expect(appearClass).toStrictEqual([
+ 'test',
+ 'test-appear-active',
+ 'test-appear-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-appear-active',
+ 'test-appear-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe('content
')
+
+ // leave
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-to'
+ ])
+ await transitionFinish()
+ expect(await isVisible('.test')).toBe(false)
+
+ // enter
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe(
+ 'content
'
+ )
+ },
+ E2E_TIMEOUT
+ )
+ })
+
+ test(
+ 'warn when used on multiple elements',
+ async () => {
+ createApp({
+ render() {
+ return h(Transition, null, {
+ default: () => [h('div'), h('div')]
+ })
+ }
+ }).mount(document.createElement('div'))
+ expect(
+ ' can only be used on a single element or component'
+ ).toHaveBeenWarned()
+ },
+ E2E_TIMEOUT
+ )
+
+ describe('explicit durations', () => {
+ test(
+ 'single value',
+ async () => {
+ await page().evaluate(duration => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+
+ `,
+ setup: () => {
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ return { toggle, click }
+ }
+ }).mount('#app')
+ }, duration)
+ expect(await html('#container')).toBe('content
')
+
+ // leave
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-to'
+ ])
+ await transitionFinish(duration * 2)
+ expect(await html('#container')).toBe('')
+
+ // enter
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-to'
+ ])
+ await transitionFinish(duration * 2)
+ expect(await html('#container')).toBe('content
')
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'enter with explicit durations',
+ async () => {
+ await page().evaluate(duration => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+
+ `,
+ setup: () => {
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ return { toggle, click }
+ }
+ }).mount('#app')
+ }, duration)
+ expect(await html('#container')).toBe('content
')
+
+ // leave
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe('')
+
+ // enter
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-to'
+ ])
+ await transitionFinish(duration * 2)
+ expect(await html('#container')).toBe('content
')
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'leave with explicit durations',
+ async () => {
+ await page().evaluate(duration => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+
+ `,
+ setup: () => {
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ return { toggle, click }
+ }
+ }).mount('#app')
+ }, duration)
+ expect(await html('#container')).toBe('content
')
+
+ // leave
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-to'
+ ])
+ await transitionFinish(duration * 2)
+ expect(await html('#container')).toBe('')
+
+ // enter
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-to'
+ ])
+ await transitionFinish()
+ expect(await html('#container')).toBe('content
')
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'separate enter and leave',
+ async () => {
+ await page().evaluate(duration => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+
+ `,
+ setup: () => {
+ const toggle = ref(true)
+ const click = () => (toggle.value = !toggle.value)
+ return { toggle, click }
+ }
+ }).mount('#app')
+ }, duration)
+ expect(await html('#container')).toBe('content
')
+
+ // leave
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-leave-active',
+ 'test-leave-to'
+ ])
+ await transitionFinish(duration * 2)
+ expect(await html('#container')).toBe('')
+
+ // enter
+ expect(await classWhenTransitionStart()).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-from'
+ ])
+ await nextFrame()
+ expect(await classList('.test')).toStrictEqual([
+ 'test',
+ 'test-enter-active',
+ 'test-enter-to'
+ ])
+ await transitionFinish(200)
+ expect(await html('#container')).toBe('content
')
+ },
+ E2E_TIMEOUT
+ )
+
+ // fixme
+ test.todo('warn invalid durations')
+ })
+})
diff --git a/packages/vue/examples/__tests__/e2eUtils.ts b/packages/vue/__tests__/e2eUtils.ts
similarity index 98%
rename from packages/vue/examples/__tests__/e2eUtils.ts
rename to packages/vue/__tests__/e2eUtils.ts
index f014623d..7d5a0a0e 100644
--- a/packages/vue/examples/__tests__/e2eUtils.ts
+++ b/packages/vue/__tests__/e2eUtils.ts
@@ -79,7 +79,7 @@ export function setupPuppeteer() {
await page.$eval(
selector,
(node, value) => {
- (node as HTMLInputElement).value = value
+ ;(node as HTMLInputElement).value = value
node.dispatchEvent(new Event('input'))
},
value
diff --git a/packages/vue/examples/transition/index.html b/packages/vue/__tests__/transition.html
similarity index 73%
rename from packages/vue/examples/transition/index.html
rename to packages/vue/__tests__/transition.html
index af66e19d..51553064 100644
--- a/packages/vue/examples/transition/index.html
+++ b/packages/vue/__tests__/transition.html
@@ -1,19 +1,6 @@
-
-
-
+
+