Skip to content

Commit c3cdfcf

Browse files
committed
fix out-in transition for async components (fix #5760)
1 parent b4dd0be commit c3cdfcf

File tree

2 files changed

+126
-3
lines changed

2 files changed

+126
-3
lines changed

src/platforms/web/runtime/components/transition.js

+12-3
Original file line numberDiff line numberDiff line change
@@ -72,19 +72,23 @@ function isSameChild (child: VNode, oldChild: VNode): boolean {
7272
return oldChild.key === child.key && oldChild.tag === child.tag
7373
}
7474

75+
function isAsyncPlaceholder (node: VNode): boolean {
76+
return node.isComment && node.asyncFactory
77+
}
78+
7579
export default {
7680
name: 'transition',
7781
props: transitionProps,
7882
abstract: true,
7983

8084
render (h: Function) {
81-
let children: ?Array<VNode> = this.$slots.default
85+
let children: ?Array<VNode> = this.$options._renderChildren
8286
if (!children) {
8387
return
8488
}
8589

8690
// filter out text nodes (possible whitespaces)
87-
children = children.filter((c: VNode) => c.tag)
91+
children = children.filter((c: VNode) => c.tag || isAsyncPlaceholder(c))
8892
/* istanbul ignore if */
8993
if (!children.length) {
9094
return
@@ -151,7 +155,12 @@ export default {
151155
child.data.show = true
152156
}
153157

154-
if (oldChild && oldChild.data && !isSameChild(child, oldChild)) {
158+
if (
159+
oldChild &&
160+
oldChild.data &&
161+
!isSameChild(child, oldChild) &&
162+
!isAsyncPlaceholder(oldChild)
163+
) {
155164
// replace old child transition data with fresh one
156165
// important for dynamic transitions!
157166
const oldData: Object = oldChild && (oldChild.data.transition = extend({}, data))

test/unit/features/transition/transition.spec.js

+114
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,120 @@ if (!isIE9) {
877877
expect(`<transition> can only be used on a single element`).toHaveBeenWarned()
878878
})
879879

880+
it('transition out-in on async component (resolve before leave complete)', done => {
881+
const vm = new Vue({
882+
template: `
883+
<div>
884+
<transition name="test-anim" mode="out-in">
885+
<component-a v-if="ok"></component-a>
886+
<component-b v-else></component-b>
887+
</transition>
888+
</div>
889+
`,
890+
components: {
891+
componentA: resolve => {
892+
setTimeout(() => {
893+
resolve({ template: '<div><h1>component A</h1></div>' })
894+
next1()
895+
}, duration / 2)
896+
},
897+
componentB: resolve => {
898+
setTimeout(() => {
899+
resolve({ template: '<div><h1>component B</h1></div>' })
900+
}, duration / 2)
901+
}
902+
},
903+
data: {
904+
ok: true
905+
}
906+
}).$mount(el)
907+
908+
expect(vm.$el.innerHTML).toBe('<!---->')
909+
910+
function next1 () {
911+
Vue.nextTick(() => {
912+
expect(vm.$el.children.length).toBe(1)
913+
expect(vm.$el.textContent).toBe('component A')
914+
expect(vm.$el.children[0].className).toBe('test-anim-enter test-anim-enter-active')
915+
nextFrame(() => {
916+
expect(vm.$el.children[0].className).toBe('test-anim-enter-active test-anim-enter-to')
917+
setTimeout(() => {
918+
expect(vm.$el.children[0].className).toBe('')
919+
vm.ok = false
920+
next2()
921+
}, duration + buffer)
922+
})
923+
})
924+
}
925+
926+
function next2 () {
927+
waitForUpdate(() => {
928+
expect(vm.$el.children.length).toBe(1)
929+
expect(vm.$el.textContent).toBe('component A')
930+
expect(vm.$el.children[0].className).toBe('test-anim-leave test-anim-leave-active')
931+
}).thenWaitFor(nextFrame).then(() => {
932+
expect(vm.$el.children[0].className).toBe('test-anim-leave-active test-anim-leave-to')
933+
}).thenWaitFor(duration + buffer).then(() => {
934+
expect(vm.$el.children.length).toBe(1)
935+
expect(vm.$el.textContent).toBe('component B')
936+
expect(vm.$el.children[0].className).toBe('test-anim-enter-active test-anim-enter-to')
937+
}).thenWaitFor(duration + buffer).then(() => {
938+
expect(vm.$el.children[0].className).toBe('')
939+
}).then(done)
940+
}
941+
})
942+
943+
it('transition out-in on async component (resolve after leave complete)', done => {
944+
const vm = new Vue({
945+
template: `
946+
<div>
947+
<transition name="test-anim" mode="out-in">
948+
<component-a v-if="ok"></component-a>
949+
<component-b v-else></component-b>
950+
</transition>
951+
</div>
952+
`,
953+
components: {
954+
componentA: { template: '<div><h1>component A</h1></div>' },
955+
componentB: resolve => {
956+
setTimeout(() => {
957+
resolve({ template: '<div><h1>component B</h1></div>' })
958+
Vue.nextTick(next)
959+
}, (duration + buffer) * 1.5)
960+
}
961+
},
962+
data: {
963+
ok: true
964+
}
965+
}).$mount(el)
966+
967+
expect(vm.$el.innerHTML).toBe('<div><h1>component A</h1></div>')
968+
969+
let next
970+
971+
vm.ok = false
972+
waitForUpdate(() => {
973+
expect(vm.$el.children.length).toBe(1)
974+
expect(vm.$el.textContent).toBe('component A')
975+
expect(vm.$el.children[0].className).toBe('test-anim-leave test-anim-leave-active')
976+
}).thenWaitFor(nextFrame).then(() => {
977+
expect(vm.$el.children[0].className).toBe('test-anim-leave-active test-anim-leave-to')
978+
}).thenWaitFor(duration + buffer).then(() => {
979+
expect(vm.$el.children.length).toBe(0)
980+
expect(vm.$el.innerHTML).toBe('<!---->')
981+
}).thenWaitFor(_next => { next = _next }).then(() => {
982+
expect(vm.$el.children.length).toBe(1)
983+
expect(vm.$el.textContent).toBe('component B')
984+
expect(vm.$el.children[0].className).toBe('test-anim-enter test-anim-enter-active')
985+
}).thenWaitFor(nextFrame).then(() => {
986+
expect(vm.$el.children[0].className).toBe('test-anim-enter-active test-anim-enter-to')
987+
}).thenWaitFor(duration + buffer).then(() => {
988+
expect(vm.$el.children.length).toBe(1)
989+
expect(vm.$el.textContent).toBe('component B')
990+
expect(vm.$el.children[0].className).toBe('')
991+
}).then(done)
992+
})
993+
880994
describe('explicit durations -', () => {
881995
it('single value', done => {
882996
const vm = new Vue({

0 commit comments

Comments
 (0)