Browse Source

#4371 - Explicit transition durations (#4857)

* Add transition explicit duration

* Fix tests for explicit transition duration

* Tweaks & default to milliseconds

* Better tests

* Better test for change value case

* Fix transition duration tests

* Better flow typing

* Fix transition test

* Revert "Fix transition test"

This reverts commit db75b3801ed11182119c78ebae87f40a62803714.

* Fix transition test


Revert "Fix transition test"

This reverts commit db75b3801ed11182119c78ebae87f40a62803714.
Fix transition test

* Better flow types

* Warn message

* Better prop handling

* Better flow typings

* adjustments
dev
Guillaume Chau 8 years ago
committed by Evan You
parent
commit
0598ab0c64
  1. 3
      src/platforms/web/runtime/components/transition.js
  2. 60
      src/platforms/web/runtime/modules/transition.js
  3. 2
      src/platforms/web/runtime/transition-util.js
  4. 228
      test/unit/features/transition/transition.spec.js

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

@ -21,7 +21,8 @@ export const transitionProps = {
leaveActiveClass: String,
appearClass: String,
appearActiveClass: String,
appearToClass: String
appearToClass: String,
duration: [Number, Object]
}
// in case the child is also an abstract component, e.g. <keep-alive>

60
src/platforms/web/runtime/modules/transition.js

@ -1,15 +1,15 @@
/* @flow */
import { inBrowser, isIE9 } from 'core/util/index'
import { once } from 'shared/util'
import { once, isObject } from 'shared/util'
import { inBrowser, isIE9, warn } from 'core/util/index'
import { mergeVNodeHook } from 'core/vdom/helpers/index'
import { activeInstance } from 'core/instance/lifecycle'
import {
resolveTransition,
nextFrame,
resolveTransition,
whenTransitionEnds,
addTransitionClass,
removeTransitionClass,
whenTransitionEnds
removeTransitionClass
} from '../transition-util'
export function enter (vnode: VNodeWithData, toggleDisplay: ?() => void) {
@ -47,7 +47,8 @@ export function enter (vnode: VNodeWithData, toggleDisplay: ?() => void) {
beforeAppear,
appear,
afterAppear,
appearCancelled
appearCancelled,
duration
} = data
// activeInstance will always be the <transition> component managing this
@ -70,11 +71,17 @@ export function enter (vnode: VNodeWithData, toggleDisplay: ?() => void) {
const startClass = isAppear ? appearClass : enterClass
const activeClass = isAppear ? appearActiveClass : enterActiveClass
const toClass = isAppear ? appearToClass : enterToClass
const beforeEnterHook = isAppear ? (beforeAppear || beforeEnter) : beforeEnter
const enterHook = isAppear ? (typeof appear === 'function' ? appear : enter) : enter
const afterEnterHook = isAppear ? (afterAppear || afterEnter) : afterEnter
const enterCancelledHook = isAppear ? (appearCancelled || enterCancelled) : enterCancelled
const explicitEnterDuration = isObject(duration) ? duration.enter : duration
if (process.env.NODE_ENV !== 'production' && explicitEnterDuration != null) {
checkDuration(explicitEnterDuration, 'enter', vnode)
}
const expectsCSS = css !== false && !isIE9
const userWantsControl =
enterHook &&
@ -121,7 +128,11 @@ export function enter (vnode: VNodeWithData, toggleDisplay: ?() => void) {
addTransitionClass(el, toClass)
removeTransitionClass(el, startClass)
if (!cb.cancelled && !userWantsControl) {
whenTransitionEnds(el, type, cb)
if (isValidDuration(explicitEnterDuration)) {
setTimeout(cb, explicitEnterDuration)
} else {
whenTransitionEnds(el, type, cb)
}
}
})
}
@ -165,7 +176,8 @@ export function leave (vnode: VNodeWithData, rm: Function) {
leave,
afterLeave,
leaveCancelled,
delayLeave
delayLeave,
duration
} = data
const expectsCSS = css !== false && !isIE9
@ -175,6 +187,11 @@ export function leave (vnode: VNodeWithData, rm: Function) {
// the length of original fn as _length
(leave._length || leave.length) > 1
const explicitLeaveDuration = isObject(duration) ? duration.leave : duration
if (process.env.NODE_ENV !== 'production' && explicitLeaveDuration != null) {
checkDuration(explicitLeaveDuration, 'leave', vnode)
}
const cb = el._leaveCb = once(() => {
if (el.parentNode && el.parentNode._pending) {
el.parentNode._pending[vnode.key] = null
@ -218,7 +235,11 @@ export function leave (vnode: VNodeWithData, rm: Function) {
addTransitionClass(el, leaveToClass)
removeTransitionClass(el, leaveClass)
if (!cb.cancelled && !userWantsControl) {
whenTransitionEnds(el, type, cb)
if (isValidDuration(explicitLeaveDuration)) {
setTimeout(cb, explicitLeaveDuration)
} else {
whenTransitionEnds(el, type, cb)
}
}
})
}
@ -229,6 +250,27 @@ export function leave (vnode: VNodeWithData, rm: Function) {
}
}
// only used in dev mode
function checkDuration (val, name, vnode) {
if (typeof val !== 'number') {
warn(
`<transition> explicit ${name} duration is not a valid number - ` +
`got ${JSON.stringify(val)}.`,
vnode.context
)
} else if (isNaN(val)) {
warn(
`<transition> explicit ${name} duration is NaN - ` +
'the duration expression might be incorrect.',
vnode.context
)
}
}
function isValidDuration (val) {
return typeof val === 'number' && !isNaN(val)
}
function _enter (_: any, vnode: VNodeWithData) {
if (!vnode.data.show) {
enter(vnode)

2
src/platforms/web/runtime/transition-util.js

@ -1,8 +1,8 @@
/* @flow */
import { inBrowser, isIE9 } from 'core/util/index'
import { remove, extend, cached } from 'shared/util'
import { addClass, removeClass } from './class-util'
import { remove, extend, cached } from 'shared/util'
export function resolveTransition (def?: string | Object): ?Object {
if (!def) {

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

@ -6,6 +6,7 @@ import { nextFrame } from 'web/runtime/transition-util'
if (!isIE9) {
describe('Transition basic', () => {
const { duration, buffer } = injectStyles()
const explicitDuration = 100
let el
beforeEach(() => {
@ -875,5 +876,232 @@ if (!isIE9) {
}).$mount()
expect(`<transition> can only be used on a single element`).toHaveBeenWarned()
})
it('explicit transition total duration', done => {
const vm = new Vue({
template: `
<div>
<transition :duration="${explicitDuration}">
<div v-if="ok" class="test">foo</div>
</transition>
</div>
`,
data: { ok: true }
}).$mount(el)
vm.ok = false
waitForUpdate(() => {
expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')
}).thenWaitFor(nextFrame).then(() => {
expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
}).thenWaitFor(explicitDuration - buffer).then(() => {
expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
}).thenWaitFor(buffer * 2).then(() => {
expect(vm.$el.children.length).toBe(0)
vm.ok = true
}).then(() => {
expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')
}).thenWaitFor(nextFrame).then(() => {
expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
}).thenWaitFor(explicitDuration - buffer).then(() => {
expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
}).thenWaitFor(buffer * 2).then(() => {
expect(vm.$el.children[0].className).toBe('test')
}).then(done)
})
it('explicit transition enter duration and auto leave duration', done => {
const vm = new Vue({
template: `
<div>
<transition :duration="{ enter: ${explicitDuration} }">
<div v-if="ok" class="test">foo</div>
</transition>
</div>
`,
data: { ok: true }
}).$mount(el)
vm.ok = false
waitForUpdate(() => {
expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')
}).thenWaitFor(nextFrame).then(() => {
expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
}).thenWaitFor(duration - buffer).then(() => {
expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
}).thenWaitFor(buffer * 2).then(() => {
expect(vm.$el.children.length).toBe(0)
vm.ok = true
}).then(() => {
expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')
}).thenWaitFor(nextFrame).then(() => {
expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
}).thenWaitFor(explicitDuration - buffer).then(() => {
expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
}).thenWaitFor(buffer * 2).then(() => {
expect(vm.$el.children[0].className).toBe('test')
}).then(done)
})
it('explicit transition leave duration and auto enter duration', done => {
const vm = new Vue({
template: `
<div>
<transition :duration="{ leave: ${explicitDuration} }">
<div v-if="ok" class="test">foo</div>
</transition>
</div>
`,
data: { ok: true }
}).$mount(el)
vm.ok = false
waitForUpdate(() => {
expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')
}).thenWaitFor(nextFrame).then(() => {
expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
}).thenWaitFor(explicitDuration - buffer).then(() => {
expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
}).thenWaitFor(buffer * 2).then(() => {
expect(vm.$el.children.length).toBe(0)
vm.ok = true
}).then(() => {
expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')
}).thenWaitFor(nextFrame).then(() => {
expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
}).thenWaitFor(duration - buffer).then(() => {
expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
}).thenWaitFor(buffer * 2).then(() => {
expect(vm.$el.children[0].className).toBe('test')
}).then(done)
})
it('explicit transition separate enter and leave duration', done => {
const enter = 100
const leave = 200
const vm = new Vue({
template: `
<div>
<transition :duration="{ enter: ${enter}, leave: ${leave} }">
<div v-if="ok" class="test">foo</div>
</transition>
</div>
`,
data: { ok: true }
}).$mount(el)
vm.ok = false
waitForUpdate(() => {
expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')
}).thenWaitFor(nextFrame).then(() => {
expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
}).thenWaitFor(leave - buffer).then(() => {
expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
}).thenWaitFor(buffer * 2).then(() => {
expect(vm.$el.children.length).toBe(0)
vm.ok = true
}).then(() => {
expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')
}).thenWaitFor(nextFrame).then(() => {
expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
}).thenWaitFor(enter - buffer).then(() => {
expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
}).thenWaitFor(buffer * 2).then(() => {
expect(vm.$el.children[0].className).toBe('test')
}).then(done)
})
it('explicit transition enter and leave duration + duration change', done => {
const enter1 = 200
const enter2 = 100
const leave1 = 50
const leave2 = 300
const vm = new Vue({
template: `
<div>
<transition :duration="{ enter: enter, leave: leave }">
<div v-if="ok" class="test">foo</div>
</transition>
</div>
`,
data: {
ok: true,
enter: enter1,
leave: leave1
}
}).$mount(el)
vm.ok = false
waitForUpdate(() => {
expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')
}).thenWaitFor(nextFrame).then(() => {
expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
}).thenWaitFor(leave1 - buffer).then(() => {
expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
}).thenWaitFor(buffer * 2).then(() => {
expect(vm.$el.children.length).toBe(0)
vm.ok = true
}).then(() => {
expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')
}).thenWaitFor(nextFrame).then(() => {
expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
}).thenWaitFor(enter1 - buffer).then(() => {
expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
}).thenWaitFor(buffer * 2).then(() => {
expect(vm.$el.children[0].className).toBe('test')
vm.enter = enter2
vm.leave = leave2
}).then(() => {
vm.ok = false
}).then(() => {
expect(vm.$el.children[0].className).toBe('test v-leave v-leave-active')
}).thenWaitFor(nextFrame).then(() => {
expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
}).thenWaitFor(leave2 - buffer).then(() => {
expect(vm.$el.children[0].className).toBe('test v-leave-active v-leave-to')
}).thenWaitFor(buffer * 2).then(() => {
expect(vm.$el.children.length).toBe(0)
vm.ok = true
}).then(() => {
expect(vm.$el.children[0].className).toBe('test v-enter v-enter-active')
}).thenWaitFor(nextFrame).then(() => {
expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
}).thenWaitFor(enter2 - buffer).then(() => {
expect(vm.$el.children[0].className).toBe('test v-enter-active v-enter-to')
}).thenWaitFor(buffer * 2).then(() => {
expect(vm.$el.children[0].className).toBe('test')
}).then(done)
})
it('warn invalid explicit durations', done => {
const vm = new Vue({
template: `
<div>
<transition :duration="{ enter: NaN, leave: 'foo' }">
<div v-if="ok" class="test">foo</div>
</transition>
</div>
`,
data: {
ok: true
}
}).$mount(el)
vm.ok = false
waitForUpdate(() => {
expect(`<transition> explicit leave duration is not a valid number - got "foo"`).toHaveBeenWarned()
}).thenWaitFor(duration + buffer).then(() => {
vm.ok = true
}).then(() => {
expect(`<transition> explicit enter duration is NaN`).toHaveBeenWarned()
}).then(done)
})
})
}

Loading…
Cancel
Save