From 299fda46a1aa05d92e60f79da269585e159818d9 Mon Sep 17 00:00:00 2001
From: underfin <2218301630@qq.com>
Date: Fri, 26 Jun 2020 04:38:22 +0800
Subject: [PATCH] test(TransitionGroup): test for `TransitionGroup` (#1269)
---
.../src/components/TransitionGroup.ts | 19 +-
packages/vue/__tests__/Transition.spec.ts | 26 +-
.../vue/__tests__/TransitionGroup.spec.ts | 516 ++++++++++++++++++
packages/vue/__tests__/e2eUtils.ts | 22 +-
4 files changed, 555 insertions(+), 28 deletions(-)
create mode 100644 packages/vue/__tests__/TransitionGroup.spec.ts
diff --git a/packages/runtime-dom/src/components/TransitionGroup.ts b/packages/runtime-dom/src/components/TransitionGroup.ts
index a71ed99178f..34d8466e0fd 100644
--- a/packages/runtime-dom/src/components/TransitionGroup.ts
+++ b/packages/runtime-dom/src/components/TransitionGroup.ts
@@ -47,7 +47,6 @@ const TransitionGroupImpl = {
const state = useTransitionState()
let prevChildren: VNode[]
let children: VNode[]
- let hasMove: boolean | null = null
onUpdated(() => {
// children is guaranteed to exist after initial render
@@ -55,16 +54,14 @@ const TransitionGroupImpl = {
return
}
const moveClass = props.moveClass || `${props.name || 'v'}-move`
- // Check if move transition is needed. This check is cached per-instance.
- hasMove =
- hasMove === null
- ? (hasMove = hasCSSTransform(
- prevChildren[0].el as ElementWithTransition,
- instance.vnode.el as Node,
- moveClass
- ))
- : hasMove
- if (!hasMove) {
+
+ if (
+ !hasCSSTransform(
+ prevChildren[0].el as ElementWithTransition,
+ instance.vnode.el as Node,
+ moveClass
+ )
+ ) {
return
}
diff --git a/packages/vue/__tests__/Transition.spec.ts b/packages/vue/__tests__/Transition.spec.ts
index 976e34a07bd..4a24fa268ef 100644
--- a/packages/vue/__tests__/Transition.spec.ts
+++ b/packages/vue/__tests__/Transition.spec.ts
@@ -5,12 +5,21 @@ import { h, createApp, Transition } from 'vue'
describe('e2e: Transition', () => {
mockWarn()
- const { page, html, classList, isVisible } = setupPuppeteer()
+ const {
+ page,
+ html,
+ classList,
+ isVisible,
+ timeout,
+ nextFrame
+ } = setupPuppeteer()
const baseUrl = `file://${path.resolve(__dirname, './transition.html')}`
const duration = 50
const buffer = 5
+ const transitionFinish = (time = duration) => timeout(time + buffer)
+
const classWhenTransitionStart = () =>
page().evaluate(() => {
(document.querySelector('#toggleBtn') as any)!.click()
@@ -19,21 +28,6 @@ describe('e2e: Transition', () => {
})
})
- 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')
diff --git a/packages/vue/__tests__/TransitionGroup.spec.ts b/packages/vue/__tests__/TransitionGroup.spec.ts
new file mode 100644
index 00000000000..796be0d5f47
--- /dev/null
+++ b/packages/vue/__tests__/TransitionGroup.spec.ts
@@ -0,0 +1,516 @@
+import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils'
+import path from 'path'
+import { mockWarn } from '@vue/shared'
+import { createApp, ref } from 'vue'
+
+describe('e2e: TransitionGroup', () => {
+ mockWarn()
+ const { page, html, nextFrame, timeout } = setupPuppeteer()
+ const baseUrl = `file://${path.resolve(__dirname, './transition.html')}`
+
+ const duration = 50
+ const buffer = 5
+
+ const htmlWhenTransitionStart = () =>
+ page().evaluate(() => {
+ (document.querySelector('#toggleBtn') as any)!.click()
+ return Promise.resolve().then(() => {
+ return document.querySelector('#container')!.innerHTML
+ })
+ })
+
+ const transitionFinish = (time = duration) => timeout(time + buffer)
+
+ beforeEach(async () => {
+ await page().goto(baseUrl)
+ await page().waitFor('#app')
+ })
+
+ test(
+ 'enter',
+ async () => {
+ await page().evaluate(() => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+ button
+ `,
+ setup: () => {
+ const items = ref(['a', 'b', 'c'])
+ const click = () => items.value.push('d', 'e')
+ return { click, items }
+ }
+ }).mount('#app')
+ })
+ expect(await html('#container')).toBe(
+ `a
` +
+ `b
` +
+ `c
`
+ )
+
+ expect(await htmlWhenTransitionStart()).toBe(
+ `a
` +
+ `b
` +
+ `c
` +
+ `d
` +
+ `e
`
+ )
+ await nextFrame()
+ expect(await html('#container')).toBe(
+ `a
` +
+ `b
` +
+ `c
` +
+ `d
` +
+ `e
`
+ )
+ await transitionFinish()
+ expect(await html('#container')).toBe(
+ `a
` +
+ `b
` +
+ `c
` +
+ `d
` +
+ `e
`
+ )
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'leave',
+ async () => {
+ await page().evaluate(() => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+ button
+ `,
+ setup: () => {
+ const items = ref(['a', 'b', 'c'])
+ const click = () => (items.value = ['b'])
+ return { click, items }
+ }
+ }).mount('#app')
+ })
+ expect(await html('#container')).toBe(
+ `a
` +
+ `b
` +
+ `c
`
+ )
+
+ expect(await htmlWhenTransitionStart()).toBe(
+ `a
` +
+ `b
` +
+ `c
`
+ )
+ await nextFrame()
+ expect(await html('#container')).toBe(
+ `a
` +
+ `b
` +
+ `c
`
+ )
+ await transitionFinish()
+ expect(await html('#container')).toBe(`b
`)
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'enter + leave',
+ async () => {
+ await page().evaluate(() => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+ button
+ `,
+ setup: () => {
+ const items = ref(['a', 'b', 'c'])
+ const click = () => (items.value = ['b', 'c', 'd'])
+ return { click, items }
+ }
+ }).mount('#app')
+ })
+ expect(await html('#container')).toBe(
+ `a
` +
+ `b
` +
+ `c
`
+ )
+
+ expect(await htmlWhenTransitionStart()).toBe(
+ `a
` +
+ `b
` +
+ `c
` +
+ `d
`
+ )
+ await nextFrame()
+ expect(await html('#container')).toBe(
+ `a
` +
+ `b
` +
+ `c
` +
+ `d
`
+ )
+ await transitionFinish()
+ expect(await html('#container')).toBe(
+ `b
` +
+ `c
` +
+ `d
`
+ )
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'appear',
+ async () => {
+ const appearHtml = await page().evaluate(() => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+ button
+ `,
+ setup: () => {
+ const items = ref(['a', 'b', 'c'])
+ const click = () => items.value.push('d', 'e')
+ return { click, items }
+ }
+ }).mount('#app')
+ return Promise.resolve().then(() => {
+ return document.querySelector('#container')!.innerHTML
+ })
+ })
+ // appear
+ expect(appearHtml).toBe(
+ `a
` +
+ `b
` +
+ `c
`
+ )
+ await nextFrame()
+ expect(await html('#container')).toBe(
+ `a
` +
+ `b
` +
+ `c
`
+ )
+ await transitionFinish()
+ expect(await html('#container')).toBe(
+ `a
` +
+ `b
` +
+ `c
`
+ )
+
+ // enter
+ expect(await htmlWhenTransitionStart()).toBe(
+ `a
` +
+ `b
` +
+ `c
` +
+ `d
` +
+ `e
`
+ )
+ await nextFrame()
+ expect(await html('#container')).toBe(
+ `a
` +
+ `b
` +
+ `c
` +
+ `d
` +
+ `e
`
+ )
+ await transitionFinish()
+ expect(await html('#container')).toBe(
+ `a
` +
+ `b
` +
+ `c
` +
+ `d
` +
+ `e
`
+ )
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'move',
+ async () => {
+ await page().evaluate(() => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+ button
+ `,
+ setup: () => {
+ const items = ref(['a', 'b', 'c'])
+ const click = () => (items.value = ['d', 'b', 'a'])
+ return { click, items }
+ }
+ }).mount('#app')
+ })
+ expect(await html('#container')).toBe(
+ `a
` +
+ `b
` +
+ `c
`
+ )
+
+ expect(await htmlWhenTransitionStart()).toBe(
+ `d
` +
+ `b
` +
+ `a
` +
+ `c
`
+ )
+ await nextFrame()
+ expect(await html('#container')).toBe(
+ `d
` +
+ `b
` +
+ `a
` +
+ `c
`
+ )
+ await transitionFinish(duration * 2)
+ expect(await html('#container')).toBe(
+ `d
` +
+ `b
` +
+ `a
`
+ )
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'dynamic name',
+ async () => {
+ await page().evaluate(() => {
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+ button
+ button
+ `,
+ setup: () => {
+ const items = ref(['a', 'b', 'c'])
+ const name = ref('invalid')
+ const click = () => (items.value = ['b', 'c', 'a'])
+ const changeName = () => {
+ name.value = 'group'
+ items.value = ['a', 'b', 'c']
+ }
+ return { click, items, name, changeName }
+ }
+ }).mount('#app')
+ })
+ expect(await html('#container')).toBe(
+ `a
` + `b
` + `c
`
+ )
+
+ // invalid name
+ expect(await htmlWhenTransitionStart()).toBe(
+ `b
` + `c
` + `a
`
+ )
+ // change name
+ const moveHtml = await page().evaluate(() => {
+ ;(document.querySelector('#changeNameBtn') as any).click()
+ return Promise.resolve().then(() => {
+ return document.querySelector('#container')!.innerHTML
+ })
+ })
+ expect(moveHtml).toBe(
+ `a
` +
+ `b
` +
+ `c
`
+ )
+ // not sure why but we just have to wait really long for this to
+ // pass consistently :/
+ await transitionFinish(duration * 4)
+ expect(await html('#container')).toBe(
+ `a
` +
+ `b
` +
+ `c
`
+ )
+ },
+ E2E_TIMEOUT
+ )
+
+ test(
+ 'events',
+ 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 appearHtml = await page().evaluate(() => {
+ const {
+ beforeAppearSpy,
+ onAppearSpy,
+ afterAppearSpy,
+ beforeEnterSpy,
+ onEnterSpy,
+ afterEnterSpy,
+ beforeLeaveSpy,
+ onLeaveSpy,
+ afterLeaveSpy
+ } = window as any
+ const { createApp, ref } = (window as any).Vue
+ createApp({
+ template: `
+
+ button
+ `,
+ setup: () => {
+ const items = ref(['a', 'b', 'c'])
+ const click = () => (items.value = ['b', 'c', 'd'])
+ return {
+ click,
+ items,
+ beforeAppearSpy,
+ onAppearSpy,
+ afterAppearSpy,
+ beforeEnterSpy,
+ onEnterSpy,
+ afterEnterSpy,
+ beforeLeaveSpy,
+ onLeaveSpy,
+ afterLeaveSpy
+ }
+ }
+ }).mount('#app')
+ return Promise.resolve().then(() => {
+ return document.querySelector('#container')!.innerHTML
+ })
+ })
+ expect(beforeAppearSpy).toBeCalled()
+ expect(onAppearSpy).not.toBeCalled()
+ expect(afterAppearSpy).not.toBeCalled()
+ expect(appearHtml).toBe(
+ `a
` +
+ `b
` +
+ `c
`
+ )
+ await nextFrame()
+ expect(onAppearSpy).toBeCalled()
+ expect(afterAppearSpy).not.toBeCalled()
+ expect(await html('#container')).toBe(
+ `a
` +
+ `b
` +
+ `c
`
+ )
+ await transitionFinish()
+ expect(afterAppearSpy).toBeCalled()
+ expect(await html('#container')).toBe(
+ `a
` +
+ `b
` +
+ `c
`
+ )
+
+ // enter + leave
+ expect(await htmlWhenTransitionStart()).toBe(
+ `a
` +
+ `b
` +
+ `c
` +
+ `d
`
+ )
+ expect(beforeLeaveSpy).toBeCalled()
+ expect(onLeaveSpy).not.toBeCalled()
+ expect(afterLeaveSpy).not.toBeCalled()
+ expect(beforeEnterSpy).toBeCalled()
+ expect(onEnterSpy).not.toBeCalled()
+ expect(afterEnterSpy).not.toBeCalled()
+ await nextFrame()
+ expect(await html('#container')).toBe(
+ `a
` +
+ `b
` +
+ `c
` +
+ `d
`
+ )
+ expect(onLeaveSpy).toBeCalled()
+ expect(afterLeaveSpy).not.toBeCalled()
+ expect(onEnterSpy).toBeCalled()
+ expect(afterEnterSpy).not.toBeCalled()
+ await transitionFinish()
+ expect(await html('#container')).toBe(
+ `b
` +
+ `c
` +
+ `d
`
+ )
+ expect(afterLeaveSpy).toBeCalled()
+ expect(afterEnterSpy).toBeCalled()
+ },
+ E2E_TIMEOUT
+ )
+
+ test('warn unkeyed children', () => {
+ createApp({
+ template: `
+
+ {{item}}
+
+ `,
+ setup: () => {
+ const items = ref(['a', 'b', 'c'])
+ return { items }
+ }
+ }).mount(document.createElement('div'))
+
+ expect(` children must be keyed`).toHaveBeenWarned()
+ })
+})
diff --git a/packages/vue/__tests__/e2eUtils.ts b/packages/vue/__tests__/e2eUtils.ts
index 7d5a0a0e069..2dc39cfdc2d 100644
--- a/packages/vue/__tests__/e2eUtils.ts
+++ b/packages/vue/__tests__/e2eUtils.ts
@@ -106,6 +106,24 @@ export function setupPuppeteer() {
)
}
+ function timeout(time: number) {
+ return page.evaluate(time => {
+ return new Promise(r => {
+ setTimeout(r, time)
+ })
+ }, time)
+ }
+
+ function nextFrame() {
+ return page.evaluate(() => {
+ return new Promise(resolve => {
+ requestAnimationFrame(() => {
+ requestAnimationFrame(resolve)
+ })
+ })
+ })
+ }
+
return {
page: () => page,
click,
@@ -121,6 +139,8 @@ export function setupPuppeteer() {
setValue,
typeValue,
enterValue,
- clearValue
+ clearValue,
+ timeout,
+ nextFrame
}
}