diff --git a/src/platforms/web/runtime/components/transition.js b/src/platforms/web/runtime/components/transition.js index a6f44a32f81..fd8e398ccbf 100644 --- a/src/platforms/web/runtime/components/transition.js +++ b/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 = this.$slots.default + let children: ?Array = 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)) diff --git a/test/unit/features/transition/transition.spec.js b/test/unit/features/transition/transition.spec.js index 866a0af56cb..c88dbbd0e9f 100644 --- a/test/unit/features/transition/transition.spec.js +++ b/test/unit/features/transition/transition.spec.js @@ -877,6 +877,120 @@ if (!isIE9) { expect(` 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: ` +
+ + + + +
+ `, + components: { + componentA: resolve => { + setTimeout(() => { + resolve({ template: '

component A

' }) + next1() + }, duration / 2) + }, + componentB: resolve => { + setTimeout(() => { + resolve({ template: '

component B

' }) + }, 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: ` +
+ + + + +
+ `, + components: { + componentA: { template: '

component A

' }, + componentB: resolve => { + setTimeout(() => { + resolve({ template: '

component B

' }) + Vue.nextTick(next) + }, (duration + buffer) * 1.5) + } + }, + data: { + ok: true + } + }).$mount(el) + + expect(vm.$el.innerHTML).toBe('

component A

') + + 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({