Browse Source

fix out-in transition for async components (fix #5760)

Evan You 9 years ago
parent
commit
c3cdfcfa68

+ 12 - 3
src/platforms/web/runtime/components/transition.js

@@ -72,19 +72,23 @@ function isSameChild (child: VNode, oldChild: VNode): boolean {
   return oldChild.key === child.key && oldChild.tag === child.tag
 }
 
+function isAsyncPlaceholder (node: VNode): boolean {
+  return node.isComment && node.asyncFactory
+}
+
 export default {
   name: 'transition',
   props: transitionProps,
   abstract: true,
 
   render (h: Function) {
-    let children: ?Array<VNode> = this.$slots.default
+    let children: ?Array<VNode> = this.$options._renderChildren
     if (!children) {
       return
     }
 
     // filter out text nodes (possible whitespaces)
-    children = children.filter((c: VNode) => c.tag)
+    children = children.filter((c: VNode) => c.tag || isAsyncPlaceholder(c))
     /* istanbul ignore if */
     if (!children.length) {
       return
@@ -151,7 +155,12 @@ export default {
       child.data.show = true
     }
 
-    if (oldChild && oldChild.data && !isSameChild(child, oldChild)) {
+    if (
+      oldChild &&
+      oldChild.data &&
+      !isSameChild(child, oldChild) &&
+      !isAsyncPlaceholder(oldChild)
+    ) {
       // replace old child transition data with fresh one
       // important for dynamic transitions!
       const oldData: Object = oldChild && (oldChild.data.transition = extend({}, data))

+ 114 - 0
test/unit/features/transition/transition.spec.js

@@ -877,6 +877,120 @@ if (!isIE9) {
       expect(`<transition> can only be used on a single element`).toHaveBeenWarned()
     })
 
+    it('transition out-in on async component (resolve before leave complete)', done => {
+      const vm = new Vue({
+        template: `
+          <div>
+            <transition name="test-anim" mode="out-in">
+              <component-a v-if="ok"></component-a>
+              <component-b v-else></component-b>
+            </transition>
+          </div>
+        `,
+        components: {
+          componentA: resolve => {
+            setTimeout(() => {
+              resolve({ template: '<div><h1>component A</h1></div>' })
+              next1()
+            }, duration / 2)
+          },
+          componentB: resolve => {
+            setTimeout(() => {
+              resolve({ template: '<div><h1>component B</h1></div>' })
+            }, duration / 2)
+          }
+        },
+        data: {
+          ok: true
+        }
+      }).$mount(el)
+
+      expect(vm.$el.innerHTML).toBe('<!---->')
+
+      function next1 () {
+        Vue.nextTick(() => {
+          expect(vm.$el.children.length).toBe(1)
+          expect(vm.$el.textContent).toBe('component A')
+          expect(vm.$el.children[0].className).toBe('test-anim-enter test-anim-enter-active')
+          nextFrame(() => {
+            expect(vm.$el.children[0].className).toBe('test-anim-enter-active test-anim-enter-to')
+            setTimeout(() => {
+              expect(vm.$el.children[0].className).toBe('')
+              vm.ok = false
+              next2()
+            }, duration + buffer)
+          })
+        })
+      }
+
+      function next2 () {
+        waitForUpdate(() => {
+          expect(vm.$el.children.length).toBe(1)
+          expect(vm.$el.textContent).toBe('component A')
+          expect(vm.$el.children[0].className).toBe('test-anim-leave test-anim-leave-active')
+        }).thenWaitFor(nextFrame).then(() => {
+          expect(vm.$el.children[0].className).toBe('test-anim-leave-active test-anim-leave-to')
+        }).thenWaitFor(duration + buffer).then(() => {
+          expect(vm.$el.children.length).toBe(1)
+          expect(vm.$el.textContent).toBe('component B')
+          expect(vm.$el.children[0].className).toBe('test-anim-enter-active test-anim-enter-to')
+        }).thenWaitFor(duration + buffer).then(() => {
+          expect(vm.$el.children[0].className).toBe('')
+        }).then(done)
+      }
+    })
+
+    it('transition out-in on async component (resolve after leave complete)', done => {
+      const vm = new Vue({
+        template: `
+          <div>
+            <transition name="test-anim" mode="out-in">
+              <component-a v-if="ok"></component-a>
+              <component-b v-else></component-b>
+            </transition>
+          </div>
+        `,
+        components: {
+          componentA: { template: '<div><h1>component A</h1></div>' },
+          componentB: resolve => {
+            setTimeout(() => {
+              resolve({ template: '<div><h1>component B</h1></div>' })
+              Vue.nextTick(next)
+            }, (duration + buffer) * 1.5)
+          }
+        },
+        data: {
+          ok: true
+        }
+      }).$mount(el)
+
+      expect(vm.$el.innerHTML).toBe('<div><h1>component A</h1></div>')
+
+      let next
+
+      vm.ok = false
+      waitForUpdate(() => {
+        expect(vm.$el.children.length).toBe(1)
+        expect(vm.$el.textContent).toBe('component A')
+        expect(vm.$el.children[0].className).toBe('test-anim-leave test-anim-leave-active')
+      }).thenWaitFor(nextFrame).then(() => {
+        expect(vm.$el.children[0].className).toBe('test-anim-leave-active test-anim-leave-to')
+      }).thenWaitFor(duration + buffer).then(() => {
+        expect(vm.$el.children.length).toBe(0)
+        expect(vm.$el.innerHTML).toBe('<!---->')
+      }).thenWaitFor(_next => { next = _next }).then(() => {
+        expect(vm.$el.children.length).toBe(1)
+        expect(vm.$el.textContent).toBe('component B')
+        expect(vm.$el.children[0].className).toBe('test-anim-enter test-anim-enter-active')
+      }).thenWaitFor(nextFrame).then(() => {
+        expect(vm.$el.children[0].className).toBe('test-anim-enter-active test-anim-enter-to')
+      }).thenWaitFor(duration + buffer).then(() => {
+        expect(vm.$el.children.length).toBe(1)
+        expect(vm.$el.textContent).toBe('component B')
+        expect(vm.$el.children[0].className).toBe('')
+      }).then(done)
+    })
+
     describe('explicit durations -', () => {
       it('single value', done => {
         const vm = new Vue({