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: ` +
+ +
{{item}}
+
+
+ + `, + 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: ` +
+ +
{{item}}
+
+
+ + `, + 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: ` +
+ +
{{item}}
+
+
+ + `, + 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: ` +
+ +
{{item}}
+
+
+ + `, + 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: ` +
+ +
{{item}}
+
+
+ + `, + 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: ` +
+ +
{{item}}
+
+
+ + + `, + 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: ` +
+ +
{{item}}
+
+
+ + `, + 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 } }