fix(transition): fix appear hooks handling
This commit is contained in:
parent
acd3156d2c
commit
7ae70ea44c
@ -53,6 +53,12 @@ function mockProps(extra: BaseTransitionProps = {}, withKeepAlive = false) {
|
||||
}),
|
||||
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 {
|
||||
@ -132,8 +138,33 @@ function runTestWithKeepAlive(tester: TestFn) {
|
||||
}
|
||||
|
||||
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)
|
||||
@ -207,11 +238,11 @@ describe('BaseTransition', () => {
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -41,9 +41,16 @@ export interface BaseTransitionProps<HostElement = RendererElement> {
|
||||
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
|
||||
@ -115,7 +122,12 @@ const BaseTransitionImpl = {
|
||||
onBeforeLeave: Function,
|
||||
onLeave: Function,
|
||||
onAfterLeave: Function,
|
||||
onLeaveCancelled: Function
|
||||
onLeaveCancelled: Function,
|
||||
// appear
|
||||
onBeforeAppear: Function,
|
||||
onAppear: Function,
|
||||
onAfterAppear: Function,
|
||||
onAppearCancelled: Function
|
||||
},
|
||||
|
||||
setup(props: BaseTransitionProps, { slots }: SetupContext) {
|
||||
@ -254,7 +266,11 @@ export function resolveTransitionHooks(
|
||||
onBeforeLeave,
|
||||
onLeave,
|
||||
onAfterLeave,
|
||||
onLeaveCancelled
|
||||
onLeaveCancelled,
|
||||
onBeforeAppear,
|
||||
onAppear,
|
||||
onAfterAppear,
|
||||
onAppearCancelled
|
||||
}: BaseTransitionProps<any>,
|
||||
state: TransitionState,
|
||||
instance: ComponentInternalInstance
|
||||
@ -275,9 +291,14 @@ export function resolveTransitionHooks(
|
||||
const hooks: TransitionHooks<TransitionElement> = {
|
||||
persisted,
|
||||
beforeEnter(el) {
|
||||
if (!appear && !state.isMounted) {
|
||||
let hook = onBeforeEnter
|
||||
if (!state.isMounted) {
|
||||
if (appear) {
|
||||
hook = onBeforeAppear || onBeforeEnter
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
// for same element (v-show)
|
||||
if (el._leaveCb) {
|
||||
el._leaveCb(true /* cancelled */)
|
||||
@ -292,31 +313,40 @@ export function resolveTransitionHooks(
|
||||
// force early removal (not cancelled)
|
||||
leavingVNode.el!._leaveCb()
|
||||
}
|
||||
callHook(onBeforeEnter, [el])
|
||||
callHook(hook, [el])
|
||||
},
|
||||
|
||||
enter(el) {
|
||||
if (!appear && !state.isMounted) {
|
||||
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()
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -94,39 +94,28 @@ export function resolveTransitionProps(
|
||||
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 HookWithDone = (el: Element, done: () => void) => void
|
||||
type Hook = HookWithDone | ((el: Element) => void)
|
||||
|
||||
type Hook =
|
||||
| ((el: Element, done: () => void) => void)
|
||||
| ((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) => {
|
||||
@ -147,19 +136,15 @@ export function resolveTransitionProps(
|
||||
)
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -167,7 +152,22 @@ export function resolveTransitionProps(
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@ -186,9 +186,13 @@ export function resolveTransitionProps(
|
||||
})
|
||||
},
|
||||
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)
|
||||
|
@ -447,7 +447,7 @@ describe('e2e: Transition', () => {
|
||||
test(
|
||||
'transition on appear',
|
||||
async () => {
|
||||
await page().evaluate(async () => {
|
||||
const appearClass = await page().evaluate(async () => {
|
||||
const { createApp, ref } = (window as any).Vue
|
||||
createApp({
|
||||
template: `
|
||||
@ -468,9 +468,12 @@ describe('e2e: Transition', () => {
|
||||
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'
|
||||
@ -598,13 +601,13 @@ describe('e2e: Transition', () => {
|
||||
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()
|
||||
@ -613,11 +616,15 @@ describe('e2e: Transition', () => {
|
||||
'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([
|
||||
@ -640,15 +647,15 @@ describe('e2e: Transition', () => {
|
||||
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',
|
||||
@ -656,7 +663,7 @@ describe('e2e: Transition', () => {
|
||||
'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()
|
||||
|
Loading…
Reference in New Issue
Block a user