fix(transition): fix appear hooks handling

This commit is contained in:
Evan You
2020-06-25 16:02:28 -04:00
parent acd3156d2c
commit 7ae70ea44c
4 changed files with 136 additions and 64 deletions

View File

@@ -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)
})
})

View File

@@ -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,8 +291,13 @@ export function resolveTransitionHooks(
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) {
@@ -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) {
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()
}
},