]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test(TransitionGroup): test for `TransitionGroup` (#1269)
authorunderfin <2218301630@qq.com>
Thu, 25 Jun 2020 20:38:22 +0000 (04:38 +0800)
committerGitHub <noreply@github.com>
Thu, 25 Jun 2020 20:38:22 +0000 (16:38 -0400)
packages/runtime-dom/src/components/TransitionGroup.ts
packages/vue/__tests__/Transition.spec.ts
packages/vue/__tests__/TransitionGroup.spec.ts [new file with mode: 0644]
packages/vue/__tests__/e2eUtils.ts

index a71ed99178f9d4938a412f04f99a9b9e98394410..34d8466e0fde2e4907ac609992ebcba31d4861bc 100644 (file)
@@ -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
       }
 
index 976e34a07bd9a37b6d2ae5b702102f0d993c1efa..4a24fa268ef983ccb3238367ef4254ec9e2f7f19 100644 (file)
@@ -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 (file)
index 0000000..796be0d
--- /dev/null
@@ -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: `
+              <div id="container">
+                                                               <transition-group name="test">
+                                                                       <div v-for="item in items" :key="item" class="test">{{item}}</div>
+                                                               </transition-group>
+                                                       </div>
+              <button id="toggleBtn" @click="click">button</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(
+        `<div class="test">a</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test">c</div>`
+      )
+
+      expect(await htmlWhenTransitionStart()).toBe(
+        `<div class="test">a</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test">c</div>` +
+          `<div class="test test-enter-active test-enter-from">d</div>` +
+          `<div class="test test-enter-active test-enter-from">e</div>`
+      )
+      await nextFrame()
+      expect(await html('#container')).toBe(
+        `<div class="test">a</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test">c</div>` +
+          `<div class="test test-enter-active test-enter-to">d</div>` +
+          `<div class="test test-enter-active test-enter-to">e</div>`
+      )
+      await transitionFinish()
+      expect(await html('#container')).toBe(
+        `<div class="test">a</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test">c</div>` +
+          `<div class="test">d</div>` +
+          `<div class="test">e</div>`
+      )
+    },
+    E2E_TIMEOUT
+  )
+
+  test(
+    'leave',
+    async () => {
+      await page().evaluate(() => {
+        const { createApp, ref } = (window as any).Vue
+        createApp({
+          template: `
+              <div id="container">
+                                                               <transition-group name="test">
+                                                                       <div v-for="item in items" :key="item" class="test">{{item}}</div>
+                                                               </transition-group>
+                                                       </div>
+              <button id="toggleBtn" @click="click">button</button>
+            `,
+          setup: () => {
+            const items = ref(['a', 'b', 'c'])
+            const click = () => (items.value = ['b'])
+            return { click, items }
+          }
+        }).mount('#app')
+      })
+      expect(await html('#container')).toBe(
+        `<div class="test">a</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test">c</div>`
+      )
+
+      expect(await htmlWhenTransitionStart()).toBe(
+        `<div class="test test-leave-active test-leave-from">a</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test test-leave-active test-leave-from">c</div>`
+      )
+      await nextFrame()
+      expect(await html('#container')).toBe(
+        `<div class="test test-leave-active test-leave-to">a</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test test-leave-active test-leave-to">c</div>`
+      )
+      await transitionFinish()
+      expect(await html('#container')).toBe(`<div class="test">b</div>`)
+    },
+    E2E_TIMEOUT
+  )
+
+  test(
+    'enter + leave',
+    async () => {
+      await page().evaluate(() => {
+        const { createApp, ref } = (window as any).Vue
+        createApp({
+          template: `
+              <div id="container">
+                                                               <transition-group name="test">
+                                                                       <div v-for="item in items" :key="item" class="test">{{item}}</div>
+                                                               </transition-group>
+                                                       </div>
+              <button id="toggleBtn" @click="click">button</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(
+        `<div class="test">a</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test">c</div>`
+      )
+
+      expect(await htmlWhenTransitionStart()).toBe(
+        `<div class="test test-leave-active test-leave-from">a</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test">c</div>` +
+          `<div class="test test-enter-active test-enter-from">d</div>`
+      )
+      await nextFrame()
+      expect(await html('#container')).toBe(
+        `<div class="test test-leave-active test-leave-to">a</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test">c</div>` +
+          `<div class="test test-enter-active test-enter-to">d</div>`
+      )
+      await transitionFinish()
+      expect(await html('#container')).toBe(
+        `<div class="test">b</div>` +
+          `<div class="test">c</div>` +
+          `<div class="test">d</div>`
+      )
+    },
+    E2E_TIMEOUT
+  )
+
+  test(
+    'appear',
+    async () => {
+      const appearHtml = await page().evaluate(() => {
+        const { createApp, ref } = (window as any).Vue
+        createApp({
+          template: `
+              <div id="container">
+                                                               <transition-group appear
+                                                                               appear-from-class="test-appear-from"
+                                                                               appear-to-class="test-appear-to"
+                                                                               appear-active-class="test-appear-active"
+                                                                               name="test">
+                                                                       <div v-for="item in items" :key="item" class="test">{{item}}</div>
+                                                               </transition-group>
+                                                       </div>
+              <button id="toggleBtn" @click="click">button</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(
+        `<div class="test test-appear-active test-appear-from">a</div>` +
+          `<div class="test test-appear-active test-appear-from">b</div>` +
+          `<div class="test test-appear-active test-appear-from">c</div>`
+      )
+      await nextFrame()
+      expect(await html('#container')).toBe(
+        `<div class="test test-appear-active test-appear-to">a</div>` +
+          `<div class="test test-appear-active test-appear-to">b</div>` +
+          `<div class="test test-appear-active test-appear-to">c</div>`
+      )
+      await transitionFinish()
+      expect(await html('#container')).toBe(
+        `<div class="test">a</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test">c</div>`
+      )
+
+      // enter
+      expect(await htmlWhenTransitionStart()).toBe(
+        `<div class="test">a</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test">c</div>` +
+          `<div class="test test-enter-active test-enter-from">d</div>` +
+          `<div class="test test-enter-active test-enter-from">e</div>`
+      )
+      await nextFrame()
+      expect(await html('#container')).toBe(
+        `<div class="test">a</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test">c</div>` +
+          `<div class="test test-enter-active test-enter-to">d</div>` +
+          `<div class="test test-enter-active test-enter-to">e</div>`
+      )
+      await transitionFinish()
+      expect(await html('#container')).toBe(
+        `<div class="test">a</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test">c</div>` +
+          `<div class="test">d</div>` +
+          `<div class="test">e</div>`
+      )
+    },
+    E2E_TIMEOUT
+  )
+
+  test(
+    'move',
+    async () => {
+      await page().evaluate(() => {
+        const { createApp, ref } = (window as any).Vue
+        createApp({
+          template: `
+              <div id="container">
+                                                               <transition-group name="group">
+                                                                       <div v-for="item in items" :key="item" class="test">{{item}}</div>
+                                                               </transition-group>
+                                                       </div>
+              <button id="toggleBtn" @click="click">button</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(
+        `<div class="test">a</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test">c</div>`
+      )
+
+      expect(await htmlWhenTransitionStart()).toBe(
+        `<div class="test group-enter-active group-enter-from">d</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test group-move" style="">a</div>` +
+          `<div class="test group-leave-active group-leave-from group-move" style="">c</div>`
+      )
+      await nextFrame()
+      expect(await html('#container')).toBe(
+        `<div class="test group-enter-active group-enter-to">d</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test group-move" style="">a</div>` +
+          `<div class="test group-leave-active group-move group-leave-to" style="">c</div>`
+      )
+      await transitionFinish(duration * 2)
+      expect(await html('#container')).toBe(
+        `<div class="test">d</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test" style="">a</div>`
+      )
+    },
+    E2E_TIMEOUT
+  )
+
+  test(
+    'dynamic name',
+    async () => {
+      await page().evaluate(() => {
+        const { createApp, ref } = (window as any).Vue
+        createApp({
+          template: `
+              <div id="container">
+                                                               <transition-group :name="name">
+                                                                       <div v-for="item in items" :key="item" >{{item}}</div>
+                                                               </transition-group>
+                                                       </div>
+              <button id="toggleBtn" @click="click">button</button>
+              <button id="changeNameBtn" @click="changeName">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(
+        `<div>a</div>` + `<div>b</div>` + `<div>c</div>`
+      )
+
+      // invalid name
+      expect(await htmlWhenTransitionStart()).toBe(
+        `<div>b</div>` + `<div>c</div>` + `<div>a</div>`
+      )
+      // 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(
+        `<div class="group-move" style="">a</div>` +
+          `<div class="group-move" style="">b</div>` +
+          `<div class="group-move" style="">c</div>`
+      )
+      // 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(
+        `<div class="" style="">a</div>` +
+          `<div class="" style="">b</div>` +
+          `<div class="" style="">c</div>`
+      )
+    },
+    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: `
+                <div id="container">
+                  <transition-group name="test"
+                      appear
+                      appear-from-class="test-appear-from"
+                      appear-to-class="test-appear-to"
+                      appear-active-class="test-appear-active"
+                      @before-enter="beforeEnterSpy"
+                      @enter="onEnterSpy"
+                      @after-enter="afterEnterSpy"
+                      @before-leave="beforeLeaveSpy"
+                      @leave="onLeaveSpy"
+                      @after-leave="afterLeaveSpy"
+                      @before-appear="beforeAppearSpy"
+                      @appear="onAppearSpy"
+                      @after-appear="afterAppearSpy">
+                    <div v-for="item in items" :key="item" class="test">{{item}}</div>
+                  </transition-group>
+                </div>
+                <button id="toggleBtn" @click="click">button</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(
+        `<div class="test test-appear-active test-appear-from">a</div>` +
+          `<div class="test test-appear-active test-appear-from">b</div>` +
+          `<div class="test test-appear-active test-appear-from">c</div>`
+      )
+      await nextFrame()
+      expect(onAppearSpy).toBeCalled()
+      expect(afterAppearSpy).not.toBeCalled()
+      expect(await html('#container')).toBe(
+        `<div class="test test-appear-active test-appear-to">a</div>` +
+          `<div class="test test-appear-active test-appear-to">b</div>` +
+          `<div class="test test-appear-active test-appear-to">c</div>`
+      )
+      await transitionFinish()
+      expect(afterAppearSpy).toBeCalled()
+      expect(await html('#container')).toBe(
+        `<div class="test">a</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test">c</div>`
+      )
+
+      // enter + leave
+      expect(await htmlWhenTransitionStart()).toBe(
+        `<div class="test test-leave-active test-leave-from">a</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test">c</div>` +
+          `<div class="test test-enter-active test-enter-from">d</div>`
+      )
+      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(
+        `<div class="test test-leave-active test-leave-to">a</div>` +
+          `<div class="test">b</div>` +
+          `<div class="test">c</div>` +
+          `<div class="test test-enter-active test-enter-to">d</div>`
+      )
+      expect(onLeaveSpy).toBeCalled()
+      expect(afterLeaveSpy).not.toBeCalled()
+      expect(onEnterSpy).toBeCalled()
+      expect(afterEnterSpy).not.toBeCalled()
+      await transitionFinish()
+      expect(await html('#container')).toBe(
+        `<div class="test">b</div>` +
+          `<div class="test">c</div>` +
+          `<div class="test">d</div>`
+      )
+      expect(afterLeaveSpy).toBeCalled()
+      expect(afterEnterSpy).toBeCalled()
+    },
+    E2E_TIMEOUT
+  )
+
+  test('warn unkeyed children', () => {
+    createApp({
+      template: `
+        <transition-group name="test">
+          <div v-for="item in items" class="test">{{item}}</div>
+        </transition-group>
+            `,
+      setup: () => {
+        const items = ref(['a', 'b', 'c'])
+        return { items }
+      }
+    }).mount(document.createElement('div'))
+
+    expect(`<TransitionGroup> children must be keyed`).toHaveBeenWarned()
+  })
+})
index 7d5a0a0e069d688b764c1f87343990e11935c9c8..2dc39cfdc2d405b863d57f3024c7d5b9c105dc01 100644 (file)
@@ -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
   }
 }