From e448fd475da17b7492a11ade2e8d7761bb4b1155 Mon Sep 17 00:00:00 2001 From: jkzing Date: Wed, 16 Aug 2017 01:12:36 +0800 Subject: [PATCH] fix(transition): consider async placeholder as valid child to return, fix #6256 --- .../web/runtime/components/transition.js | 19 ++++- .../component/component-keep-alive.spec.js | 84 +++++++++++++++++++ 2 files changed, 100 insertions(+), 3 deletions(-) diff --git a/src/platforms/web/runtime/components/transition.js b/src/platforms/web/runtime/components/transition.js index 9ac73b164ec..ce1f7282db0 100644 --- a/src/platforms/web/runtime/components/transition.js +++ b/src/platforms/web/runtime/components/transition.js @@ -4,8 +4,8 @@ // supports transition mode (out-in / in-out) import { warn } from 'core/util/index' -import { camelize, extend, isPrimitive } from 'shared/util' -import { mergeVNodeHook, getFirstComponentChild } from 'core/vdom/helpers/index' +import { camelize, extend, isPrimitive, isDef } from 'shared/util' +import { mergeVNodeHook } from 'core/vdom/helpers/index' export const transitionProps = { name: String, @@ -25,12 +25,25 @@ export const transitionProps = { duration: [Number, String, Object] } +// similar with getFirstComponentChild +// but consider async placeholder as valid child +function getFirstValidChild (children: ?Array): ?VNode { + if (Array.isArray(children)) { + for (let i = 0; i < children.length; i++) { + const c = children[i] + if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) { + return c + } + } + } +} + // in case the child is also an abstract component, e.g. // we want to recursively retrieve the real component to be rendered function getRealChild (vnode: ?VNode): ?VNode { const compOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions if (compOptions && compOptions.Ctor.options.abstract) { - return getRealChild(getFirstComponentChild(compOptions.children)) + return getRealChild(getFirstValidChild(compOptions.children)) } else { return vnode } diff --git a/test/unit/features/component/component-keep-alive.spec.js b/test/unit/features/component/component-keep-alive.spec.js index 1295c17f50e..dd3e3903b83 100644 --- a/test/unit/features/component/component-keep-alive.spec.js +++ b/test/unit/features/component/component-keep-alive.spec.js @@ -862,5 +862,89 @@ describe('Component keep-alive', () => { ) }).then(done) }) + + it('async components with transition-mode out-in', done => { + const barResolve = jasmine.createSpy('bar resolved') + let next + const foo = (resolve) => { + setTimeout(() => { + resolve(one) + Vue.nextTick(next) + }, duration / 2) + } + const bar = (resolve) => { + setTimeout(() => { + resolve(two) + barResolve() + }, duration / 2) + } + components = { + foo, + bar + } + const vm = new Vue({ + template: `
+ + + + + +
`, + data: { + view: 'foo' + }, + components, + methods: { + afterEnter () { + next() + }, + afterLeave () { + next() + } + } + }).$mount(el) + expect(vm.$el.textContent).toBe('') + next = () => { + assertHookCalls(one, [1, 1, 1, 0, 0]) + assertHookCalls(two, [0, 0, 0, 0, 0]) + waitForUpdate(() => { + expect(vm.$el.innerHTML).toBe( + '
one
' + ) + }).thenWaitFor(nextFrame).then(() => { + expect(vm.$el.innerHTML).toBe( + '
one
' + ) + }).thenWaitFor(_next => { next = _next }).then(() => { + // foo afterEnter get called + expect(vm.$el.innerHTML).toBe('
one
') + vm.view = 'bar' + }).thenWaitFor(nextFrame).then(() => { + assertHookCalls(one, [1, 1, 1, 1, 0]) + assertHookCalls(two, [0, 0, 0, 0, 0]) + expect(vm.$el.innerHTML).toBe( + '
one
' + ) + }).thenWaitFor(_next => { next = _next }).then(() => { + // foo afterLeave get called + // and bar has already been resolved before afterLeave get called + expect(barResolve.calls.count()).toBe(1) + expect(vm.$el.innerHTML).toBe('') + }).thenWaitFor(nextFrame).then(() => { + expect(vm.$el.innerHTML).toBe( + '
two
' + ) + assertHookCalls(one, [1, 1, 1, 1, 0]) + assertHookCalls(two, [1, 1, 1, 0, 0]) + }).thenWaitFor(nextFrame).then(() => { + expect(vm.$el.innerHTML).toBe( + '
two
' + ) + }).thenWaitFor(_next => { next = _next }).then(() => { + // bar afterEnter get called + expect(vm.$el.innerHTML).toBe('
two
') + }).then(done) + } + }) } })