Skip to content

Commit

Permalink
fix(transition/keep-alive): fix unmount bug for component with out-in…
Browse files Browse the repository at this point in the history
… transition (#6839)

fix #6835
  • Loading branch information
zhangzhonghe authored Nov 8, 2022
1 parent 4887618 commit 64e6d92
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 17 deletions.
122 changes: 106 additions & 16 deletions packages/runtime-core/__tests__/components/BaseTransition.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,20 @@ function mount(
withKeepAlive = false
) {
const root = nodeOps.createElement('div')
render(
h(BaseTransition, props, () => {
return withKeepAlive ? h(KeepAlive, null, slot()) : slot()
}),
root
)
return root
const show = ref(true)
const unmount = () => (show.value = false)
const App = {
render() {
return show.value
? h(BaseTransition, props, () => {
return withKeepAlive ? h(KeepAlive, null, slot()) : slot()
})
: null
}
}
render(h(App), root)

return { root, unmount }
}

function mockProps(extra: BaseTransitionProps = {}, withKeepAlive = false) {
Expand Down Expand Up @@ -258,7 +265,7 @@ describe('BaseTransition', () => {
) {
const toggle = ref(true)
const { props, cbs } = mockProps({ mode })
const root = mount(props, () =>
const { root } = mount(props, () =>
toggle.value ? trueBranch() : falseBranch()
)

Expand Down Expand Up @@ -347,7 +354,7 @@ describe('BaseTransition', () => {
}: ToggleOptions) {
const toggle = ref(false)
const { props, cbs } = mockProps()
const root = mount(props, () =>
const { root } = mount(props, () =>
toggle.value ? trueBranch() : falseBranch()
)

Expand Down Expand Up @@ -431,7 +438,7 @@ describe('BaseTransition', () => {
) {
const toggle = ref(true)
const { props, cbs } = mockProps({}, withKeepAlive)
const root = mount(
const { root } = mount(
props,
() => (toggle.value ? trueBranch() : falseBranch()),
withKeepAlive
Expand Down Expand Up @@ -537,7 +544,7 @@ describe('BaseTransition', () => {
) {
const toggle = ref(true)
const { props, cbs } = mockProps({}, withKeepAlive)
const root = mount(
const { root } = mount(
props,
() => (toggle.value ? trueBranch() : falseBranch()),
withKeepAlive
Expand Down Expand Up @@ -670,7 +677,7 @@ describe('BaseTransition', () => {
) {
const toggle = ref(true)
const { props, cbs } = mockProps({ mode: 'out-in' }, withKeepAlive)
const root = mount(
const { root } = mount(
props,
() => (toggle.value ? trueBranch() : falseBranch()),
withKeepAlive
Expand Down Expand Up @@ -763,14 +770,97 @@ describe('BaseTransition', () => {
})
})


// #6835
describe('mode: "out-in" toggle again after unmounted', () => {
async function testOutIn(
{
trueBranch,
falseBranch,
trueSerialized,
falseSerialized
}: ToggleOptions,
withKeepAlive = false
) {
const toggle = ref(true)
const { props, cbs } = mockProps({ mode: 'out-in' }, withKeepAlive)
const { root, unmount } = mount(
props,
() => (toggle.value ? trueBranch() : falseBranch()),
withKeepAlive
)

// trigger toggle
toggle.value = false
await nextTick()
// a placeholder is injected until the leave finishes
expect(serializeInner(root)).toBe(`${trueSerialized}<!---->`)
expect(props.onBeforeLeave).toHaveBeenCalledTimes(1)
assertCalledWithEl(props.onBeforeLeave, trueSerialized)
expect(props.onLeave).toHaveBeenCalledTimes(1)
assertCalledWithEl(props.onLeave, trueSerialized)
expect(props.onAfterLeave).not.toHaveBeenCalled()
// enter should not have started
expect(props.onBeforeEnter).not.toHaveBeenCalled()
expect(props.onEnter).not.toHaveBeenCalled()
expect(props.onAfterEnter).not.toHaveBeenCalled()

cbs.doneLeave[trueSerialized]()
expect(props.onAfterLeave).toHaveBeenCalledTimes(1)
assertCalledWithEl(props.onAfterLeave, trueSerialized)
// have to wait for a tick because this triggers an update
await nextTick()
expect(serializeInner(root)).toBe(falseSerialized)
// enter should start
expect(props.onBeforeEnter).toHaveBeenCalledTimes(1)
assertCalledWithEl(props.onBeforeEnter, falseSerialized)
expect(props.onEnter).toHaveBeenCalledTimes(1)
assertCalledWithEl(props.onEnter, falseSerialized)
expect(props.onAfterEnter).not.toHaveBeenCalled()
// finish enter
cbs.doneEnter[falseSerialized]()
expect(props.onAfterEnter).toHaveBeenCalledTimes(1)
assertCalledWithEl(props.onAfterEnter, falseSerialized)

unmount()
// toggle again after unmounted should not throw error
toggle.value = true
await nextTick()
expect(serializeInner(root)).toBe(`<!---->`)

assertCalls(props, {
onBeforeEnter: 1,
onEnter: 1,
onAfterEnter: 1,
onEnterCancelled: 0,
onBeforeLeave: 1,
onLeave: 1,
onAfterLeave: 1,
onLeaveCancelled: 0
})
}

test('w/ elements', async () => {
await runTestWithElements(testOutIn)
})

test('w/ components', async () => {
await runTestWithComponents(testOutIn)
})

test('w/ KeepAlive', async () => {
await runTestWithKeepAlive(testOutIn)
})
})

describe('mode: "out-in" toggle before finish', () => {
async function testOutInBeforeFinish(
{ trueBranch, falseBranch, trueSerialized }: ToggleOptions,
withKeepAlive = false
) {
const toggle = ref(true)
const { props, cbs } = mockProps({ mode: 'out-in' }, withKeepAlive)
const root = mount(
const { root } = mount(
props,
() => (toggle.value ? trueBranch() : falseBranch()),
withKeepAlive
Expand Down Expand Up @@ -847,7 +937,7 @@ describe('BaseTransition', () => {
) {
const toggle = ref(true)
const { props, cbs } = mockProps({ mode: 'out-in' }, withKeepAlive)
const root = mount(
const { root } = mount(
props,
() => (toggle.value ? trueBranch() : falseBranch()),
withKeepAlive
Expand Down Expand Up @@ -925,7 +1015,7 @@ describe('BaseTransition', () => {
) {
const toggle = ref(true)
const { props, cbs } = mockProps({ mode: 'in-out' }, withKeepAlive)
const root = mount(
const { root } = mount(
props,
() => (toggle.value ? trueBranch() : falseBranch()),
withKeepAlive
Expand Down Expand Up @@ -1029,7 +1119,7 @@ describe('BaseTransition', () => {
) {
const toggle = ref(true)
const { props, cbs } = mockProps({ mode: 'in-out' }, withKeepAlive)
const root = mount(
const { root } = mount(
props,
() => (toggle.value ? trueBranch() : falseBranch()),
withKeepAlive
Expand Down
6 changes: 5 additions & 1 deletion packages/runtime-core/src/components/BaseTransition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,11 @@ const BaseTransitionImpl: ComponentOptions = {
// return placeholder node and queue update when leave finishes
leavingHooks.afterLeave = () => {
state.isLeaving = false
instance.update()
// #6835
// it also needs to be updated when active is undefined
if (instance.update.active !== false) {
instance.update()
}
}
return emptyPlaceholder(child)
} else if (mode === 'in-out' && innerChild.type !== Comment) {
Expand Down

0 comments on commit 64e6d92

Please sign in to comment.