Skip to content

Commit

Permalink
fix out-in transition for async components (fix #5760)
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jun 5, 2017
1 parent b4dd0be commit c3cdfcf
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 3 deletions.
15 changes: 12 additions & 3 deletions src/platforms/web/runtime/components/transition.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
Expand Down
114 changes: 114 additions & 0 deletions test/unit/features/transition/transition.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down

0 comments on commit c3cdfcf

Please sign in to comment.