}),
onAfterLeave: jest.fn(),
onLeaveCancelled: jest.fn(),
+ onBeforeAppear: jest.fn(),
+ onAppear: jest.fn((el, done) => {
+ cbs.doneEnter[serialize(el as TestElement)] = done
+ }),
+ onAfterAppear: jest.fn(),
+ onAppearCancelled: jest.fn(),
...extra
}
return {
}
describe('BaseTransition', () => {
- test('appear: true', () => {
- const { props, cbs } = mockProps({ appear: true })
+ test('appear: true w/ appear hooks', () => {
+ const { props, cbs } = mockProps({
+ appear: true
+ })
+ mount(props, () => h('div'))
+ expect(props.onBeforeAppear).toHaveBeenCalledTimes(1)
+ expect(props.onAppear).toHaveBeenCalledTimes(1)
+ expect(props.onAfterAppear).not.toHaveBeenCalled()
+
+ // enter should not be called
+ expect(props.onBeforeEnter).not.toHaveBeenCalled()
+ expect(props.onEnter).not.toHaveBeenCalled()
+ expect(props.onAfterEnter).not.toHaveBeenCalled()
+
+ cbs.doneEnter[`<div></div>`]()
+ expect(props.onAfterAppear).toHaveBeenCalledTimes(1)
+ expect(props.onAfterEnter).not.toHaveBeenCalled()
+ })
+
+ test('appear: true w/ fallback to enter hooks', () => {
+ const { props, cbs } = mockProps({
+ appear: true,
+ onBeforeAppear: undefined,
+ onAppear: undefined,
+ onAfterAppear: undefined,
+ onAppearCancelled: undefined
+ })
mount(props, () => h('div'))
expect(props.onBeforeEnter).toHaveBeenCalledTimes(1)
expect(props.onEnter).toHaveBeenCalledTimes(1)
const { hooks } = mockPersistedHooks()
mount(props, () => h('div', hooks))
- expect(props.onBeforeEnter).toHaveBeenCalledTimes(1)
- expect(props.onEnter).toHaveBeenCalledTimes(1)
- expect(props.onAfterEnter).not.toHaveBeenCalled()
+ expect(props.onBeforeAppear).toHaveBeenCalledTimes(1)
+ expect(props.onAppear).toHaveBeenCalledTimes(1)
+ expect(props.onAfterAppear).not.toHaveBeenCalled()
cbs.doneEnter[`<div></div>`]()
- expect(props.onAfterEnter).toHaveBeenCalledTimes(1)
+ expect(props.onAfterAppear).toHaveBeenCalledTimes(1)
})
})
onLeave?: (el: HostElement, done: () => void) => void
onAfterLeave?: (el: HostElement) => void
onLeaveCancelled?: (el: HostElement) => void // only fired in persisted mode
+ // appear
+ onBeforeAppear?: (el: HostElement) => void
+ onAppear?: (el: HostElement, done: () => void) => void
+ onAfterAppear?: (el: HostElement) => void
+ onAppearCancelled?: (el: HostElement) => void
}
-export interface TransitionHooks<HostElement extends RendererElement = RendererElement> {
+export interface TransitionHooks<
+ HostElement extends RendererElement = RendererElement
+> {
persisted: boolean
beforeEnter(el: HostElement): void
enter(el: HostElement): void
onBeforeLeave: Function,
onLeave: Function,
onAfterLeave: Function,
- onLeaveCancelled: Function
+ onLeaveCancelled: Function,
+ // appear
+ onBeforeAppear: Function,
+ onAppear: Function,
+ onAfterAppear: Function,
+ onAppearCancelled: Function
},
setup(props: BaseTransitionProps, { slots }: SetupContext) {
onBeforeLeave,
onLeave,
onAfterLeave,
- onLeaveCancelled
+ onLeaveCancelled,
+ onBeforeAppear,
+ onAppear,
+ onAfterAppear,
+ onAppearCancelled
}: BaseTransitionProps<any>,
state: TransitionState,
instance: ComponentInternalInstance
const hooks: TransitionHooks<TransitionElement> = {
persisted,
beforeEnter(el) {
- if (!appear && !state.isMounted) {
- return
+ let hook = onBeforeEnter
+ if (!state.isMounted) {
+ if (appear) {
+ hook = onBeforeAppear || onBeforeEnter
+ } else {
+ return
+ }
}
// for same element (v-show)
if (el._leaveCb) {
// force early removal (not cancelled)
leavingVNode.el!._leaveCb()
}
- callHook(onBeforeEnter, [el])
+ callHook(hook, [el])
},
enter(el) {
- if (!appear && !state.isMounted) {
- return
+ let hook = onEnter
+ let afterHook = onAfterEnter
+ let cancelHook = onEnterCancelled
+ if (!state.isMounted) {
+ if (appear) {
+ hook = onAppear || onEnter
+ afterHook = onAfterAppear || onAfterEnter
+ cancelHook = onAppearCancelled || onEnterCancelled
+ } else {
+ return
+ }
}
let called = false
- const afterEnter = (el._enterCb = (cancelled?) => {
+ const done = (el._enterCb = (cancelled?) => {
if (called) return
called = true
if (cancelled) {
- callHook(onEnterCancelled, [el])
+ callHook(cancelHook, [el])
} else {
- callHook(onAfterEnter, [el])
+ callHook(afterHook, [el])
}
if (hooks.delayedLeave) {
hooks.delayedLeave()
}
el._enterCb = undefined
})
- if (onEnter) {
- onEnter(el, afterEnter)
+ if (hook) {
+ hook(el, done)
} else {
- afterEnter()
+ done()
}
},
return baseProps
}
- const originEnterClass = [enterFromClass, enterActiveClass, enterToClass]
const instance = getCurrentInstance()!
const durations = normalizeDuration(duration)
const enterDuration = durations && durations[0]
const leaveDuration = durations && durations[1]
const {
- appear,
onBeforeEnter,
onEnter,
- onLeave,
onEnterCancelled,
- onLeaveCancelled
+ onLeave,
+ onLeaveCancelled,
+ onBeforeAppear,
+ onAppear,
+ onAppearCancelled
} = baseProps
- // is appearing
- if (appear && !instance.isMounted) {
- enterFromClass = appearFromClass
- enterActiveClass = appearActiveClass
- enterToClass = appearToClass
- }
-
- type Hook =
- | ((el: Element, done: () => void) => void)
- | ((el: Element) => void)
+ type HookWithDone = (el: Element, done: () => void) => void
+ type Hook = HookWithDone | ((el: Element) => void)
- const finishEnter = (el: Element, done?: () => void) => {
- removeTransitionClass(el, enterToClass)
- removeTransitionClass(el, enterActiveClass)
+ const finishEnter = (el: Element, isAppear: boolean, done?: () => void) => {
+ removeTransitionClass(el, isAppear ? appearToClass : enterToClass)
+ removeTransitionClass(el, isAppear ? appearActiveClass : enterActiveClass)
done && done()
- // reset enter class
- if (appear) {
- ;[enterFromClass, enterActiveClass, enterToClass] = originEnterClass
- }
}
const finishLeave = (el: Element, done?: () => void) => {
)
}
- return extend(baseProps, {
- onBeforeEnter(el) {
- onBeforeEnter && onBeforeEnter(el)
- addTransitionClass(el, enterActiveClass)
- addTransitionClass(el, enterFromClass)
- },
- onEnter(el, done) {
+ const makeEnterHook = (isAppear: boolean): HookWithDone => {
+ const hook = isAppear ? onAppear : onEnter
+ return (el, done) => {
nextFrame(() => {
- const resolve = () => finishEnter(el, done)
- callHook(onEnter, [el, resolve])
- removeTransitionClass(el, enterFromClass)
- addTransitionClass(el, enterToClass)
- if (!(onEnter && onEnter.length > 1)) {
+ const resolve = () => finishEnter(el, isAppear, done)
+ callHook(hook, [el, resolve])
+ removeTransitionClass(el, isAppear ? appearFromClass : enterFromClass)
+ addTransitionClass(el, isAppear ? appearToClass : enterToClass)
+ if (!(hook && hook.length > 1)) {
if (enterDuration) {
setTimeout(resolve, enterDuration)
} else {
}
}
})
+ }
+ }
+
+ return extend(baseProps, {
+ onBeforeEnter(el) {
+ onBeforeEnter && onBeforeEnter(el)
+ addTransitionClass(el, enterActiveClass)
+ addTransitionClass(el, enterFromClass)
},
+ onBeforeAppear(el) {
+ onBeforeAppear && onBeforeAppear(el)
+ addTransitionClass(el, appearActiveClass)
+ addTransitionClass(el, appearFromClass)
+ },
+ onEnter: makeEnterHook(false),
+ onAppear: makeEnterHook(true),
onLeave(el, done) {
addTransitionClass(el, leaveActiveClass)
addTransitionClass(el, leaveFromClass)
})
},
onEnterCancelled(el) {
- finishEnter(el)
+ finishEnter(el, false)
onEnterCancelled && onEnterCancelled(el)
},
+ onAppearCancelled(el) {
+ finishEnter(el, true)
+ onAppearCancelled && onAppearCancelled(el)
+ },
onLeaveCancelled(el) {
finishLeave(el)
onLeaveCancelled && onLeaveCancelled(el)
test(
'transition on appear',
async () => {
- await page().evaluate(async () => {
+ const appearClass = await page().evaluate(async () => {
const { createApp, ref } = (window as any).Vue
createApp({
template: `
return { toggle, click }
}
}).mount('#app')
+ return Promise.resolve().then(() => {
+ return document.querySelector('.test')!.className.split(/\s+/g)
+ })
})
// appear
- expect(await classList('.test')).toStrictEqual([
+ expect(appearClass).toStrictEqual([
'test',
'test-appear-active',
'test-appear-from'
return document.querySelector('.test')!.className.split(/\s+/g)
})
})
- // appear fixme spy called
+ // appear
expect(appearClass).toStrictEqual([
'test',
'test-appear-active',
'test-appear-from'
])
- expect(beforeAppearSpy).not.toBeCalled()
+ expect(beforeAppearSpy).toBeCalled()
expect(onAppearSpy).not.toBeCalled()
expect(afterAppearSpy).not.toBeCalled()
await nextFrame()
'test-appear-active',
'test-appear-to'
])
- expect(onAppearSpy).not.toBeCalled()
+ expect(onAppearSpy).toBeCalled()
expect(afterAppearSpy).not.toBeCalled()
await transitionFinish()
expect(await html('#container')).toBe('<div class="test">content</div>')
- expect(afterAppearSpy).not.toBeCalled()
+ expect(afterAppearSpy).toBeCalled()
+
+ expect(beforeEnterSpy).not.toBeCalled()
+ expect(onEnterSpy).not.toBeCalled()
+ expect(afterEnterSpy).not.toBeCalled()
// leave
expect(await classWhenTransitionStart()).toStrictEqual([
expect(await html('#container')).toBe('<!--v-if-->')
expect(afterLeaveSpy).toBeCalled()
- // enter fixme spy called
+ // enter
expect(await classWhenTransitionStart()).toStrictEqual([
'test',
'test-enter-active',
'test-enter-from'
])
expect(beforeEnterSpy).toBeCalled()
- expect(onEnterSpy).toBeCalled()
- expect(afterEnterSpy).toBeCalled()
+ expect(onEnterSpy).not.toBeCalled()
+ expect(afterEnterSpy).not.toBeCalled()
await nextFrame()
expect(await classList('.test')).toStrictEqual([
'test',
'test-enter-to'
])
expect(onEnterSpy).toBeCalled()
- expect(afterEnterSpy).toBeCalled()
+ expect(afterEnterSpy).not.toBeCalled()
await transitionFinish()
expect(await html('#container')).toBe('<div class="test">content</div>')
expect(afterEnterSpy).toBeCalled()