Skip to content

Commit 64e6d92

Browse files
authored
fix(transition/keep-alive): fix unmount bug for component with out-in transition (#6839)
fix #6835
1 parent 4887618 commit 64e6d92

File tree

2 files changed

+111
-17
lines changed

2 files changed

+111
-17
lines changed

packages/runtime-core/__tests__/components/BaseTransition.spec.ts

+106-16
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,20 @@ function mount(
1919
withKeepAlive = false
2020
) {
2121
const root = nodeOps.createElement('div')
22-
render(
23-
h(BaseTransition, props, () => {
24-
return withKeepAlive ? h(KeepAlive, null, slot()) : slot()
25-
}),
26-
root
27-
)
28-
return root
22+
const show = ref(true)
23+
const unmount = () => (show.value = false)
24+
const App = {
25+
render() {
26+
return show.value
27+
? h(BaseTransition, props, () => {
28+
return withKeepAlive ? h(KeepAlive, null, slot()) : slot()
29+
})
30+
: null
31+
}
32+
}
33+
render(h(App), root)
34+
35+
return { root, unmount }
2936
}
3037

3138
function mockProps(extra: BaseTransitionProps = {}, withKeepAlive = false) {
@@ -258,7 +265,7 @@ describe('BaseTransition', () => {
258265
) {
259266
const toggle = ref(true)
260267
const { props, cbs } = mockProps({ mode })
261-
const root = mount(props, () =>
268+
const { root } = mount(props, () =>
262269
toggle.value ? trueBranch() : falseBranch()
263270
)
264271

@@ -347,7 +354,7 @@ describe('BaseTransition', () => {
347354
}: ToggleOptions) {
348355
const toggle = ref(false)
349356
const { props, cbs } = mockProps()
350-
const root = mount(props, () =>
357+
const { root } = mount(props, () =>
351358
toggle.value ? trueBranch() : falseBranch()
352359
)
353360

@@ -431,7 +438,7 @@ describe('BaseTransition', () => {
431438
) {
432439
const toggle = ref(true)
433440
const { props, cbs } = mockProps({}, withKeepAlive)
434-
const root = mount(
441+
const { root } = mount(
435442
props,
436443
() => (toggle.value ? trueBranch() : falseBranch()),
437444
withKeepAlive
@@ -537,7 +544,7 @@ describe('BaseTransition', () => {
537544
) {
538545
const toggle = ref(true)
539546
const { props, cbs } = mockProps({}, withKeepAlive)
540-
const root = mount(
547+
const { root } = mount(
541548
props,
542549
() => (toggle.value ? trueBranch() : falseBranch()),
543550
withKeepAlive
@@ -670,7 +677,7 @@ describe('BaseTransition', () => {
670677
) {
671678
const toggle = ref(true)
672679
const { props, cbs } = mockProps({ mode: 'out-in' }, withKeepAlive)
673-
const root = mount(
680+
const { root } = mount(
674681
props,
675682
() => (toggle.value ? trueBranch() : falseBranch()),
676683
withKeepAlive
@@ -763,14 +770,97 @@ describe('BaseTransition', () => {
763770
})
764771
})
765772

773+
774+
// #6835
775+
describe('mode: "out-in" toggle again after unmounted', () => {
776+
async function testOutIn(
777+
{
778+
trueBranch,
779+
falseBranch,
780+
trueSerialized,
781+
falseSerialized
782+
}: ToggleOptions,
783+
withKeepAlive = false
784+
) {
785+
const toggle = ref(true)
786+
const { props, cbs } = mockProps({ mode: 'out-in' }, withKeepAlive)
787+
const { root, unmount } = mount(
788+
props,
789+
() => (toggle.value ? trueBranch() : falseBranch()),
790+
withKeepAlive
791+
)
792+
793+
// trigger toggle
794+
toggle.value = false
795+
await nextTick()
796+
// a placeholder is injected until the leave finishes
797+
expect(serializeInner(root)).toBe(`${trueSerialized}<!---->`)
798+
expect(props.onBeforeLeave).toHaveBeenCalledTimes(1)
799+
assertCalledWithEl(props.onBeforeLeave, trueSerialized)
800+
expect(props.onLeave).toHaveBeenCalledTimes(1)
801+
assertCalledWithEl(props.onLeave, trueSerialized)
802+
expect(props.onAfterLeave).not.toHaveBeenCalled()
803+
// enter should not have started
804+
expect(props.onBeforeEnter).not.toHaveBeenCalled()
805+
expect(props.onEnter).not.toHaveBeenCalled()
806+
expect(props.onAfterEnter).not.toHaveBeenCalled()
807+
808+
cbs.doneLeave[trueSerialized]()
809+
expect(props.onAfterLeave).toHaveBeenCalledTimes(1)
810+
assertCalledWithEl(props.onAfterLeave, trueSerialized)
811+
// have to wait for a tick because this triggers an update
812+
await nextTick()
813+
expect(serializeInner(root)).toBe(falseSerialized)
814+
// enter should start
815+
expect(props.onBeforeEnter).toHaveBeenCalledTimes(1)
816+
assertCalledWithEl(props.onBeforeEnter, falseSerialized)
817+
expect(props.onEnter).toHaveBeenCalledTimes(1)
818+
assertCalledWithEl(props.onEnter, falseSerialized)
819+
expect(props.onAfterEnter).not.toHaveBeenCalled()
820+
// finish enter
821+
cbs.doneEnter[falseSerialized]()
822+
expect(props.onAfterEnter).toHaveBeenCalledTimes(1)
823+
assertCalledWithEl(props.onAfterEnter, falseSerialized)
824+
825+
unmount()
826+
// toggle again after unmounted should not throw error
827+
toggle.value = true
828+
await nextTick()
829+
expect(serializeInner(root)).toBe(`<!---->`)
830+
831+
assertCalls(props, {
832+
onBeforeEnter: 1,
833+
onEnter: 1,
834+
onAfterEnter: 1,
835+
onEnterCancelled: 0,
836+
onBeforeLeave: 1,
837+
onLeave: 1,
838+
onAfterLeave: 1,
839+
onLeaveCancelled: 0
840+
})
841+
}
842+
843+
test('w/ elements', async () => {
844+
await runTestWithElements(testOutIn)
845+
})
846+
847+
test('w/ components', async () => {
848+
await runTestWithComponents(testOutIn)
849+
})
850+
851+
test('w/ KeepAlive', async () => {
852+
await runTestWithKeepAlive(testOutIn)
853+
})
854+
})
855+
766856
describe('mode: "out-in" toggle before finish', () => {
767857
async function testOutInBeforeFinish(
768858
{ trueBranch, falseBranch, trueSerialized }: ToggleOptions,
769859
withKeepAlive = false
770860
) {
771861
const toggle = ref(true)
772862
const { props, cbs } = mockProps({ mode: 'out-in' }, withKeepAlive)
773-
const root = mount(
863+
const { root } = mount(
774864
props,
775865
() => (toggle.value ? trueBranch() : falseBranch()),
776866
withKeepAlive
@@ -847,7 +937,7 @@ describe('BaseTransition', () => {
847937
) {
848938
const toggle = ref(true)
849939
const { props, cbs } = mockProps({ mode: 'out-in' }, withKeepAlive)
850-
const root = mount(
940+
const { root } = mount(
851941
props,
852942
() => (toggle.value ? trueBranch() : falseBranch()),
853943
withKeepAlive
@@ -925,7 +1015,7 @@ describe('BaseTransition', () => {
9251015
) {
9261016
const toggle = ref(true)
9271017
const { props, cbs } = mockProps({ mode: 'in-out' }, withKeepAlive)
928-
const root = mount(
1018+
const { root } = mount(
9291019
props,
9301020
() => (toggle.value ? trueBranch() : falseBranch()),
9311021
withKeepAlive
@@ -1029,7 +1119,7 @@ describe('BaseTransition', () => {
10291119
) {
10301120
const toggle = ref(true)
10311121
const { props, cbs } = mockProps({ mode: 'in-out' }, withKeepAlive)
1032-
const root = mount(
1122+
const { root } = mount(
10331123
props,
10341124
() => (toggle.value ? trueBranch() : falseBranch()),
10351125
withKeepAlive

packages/runtime-core/src/components/BaseTransition.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,11 @@ const BaseTransitionImpl: ComponentOptions = {
238238
// return placeholder node and queue update when leave finishes
239239
leavingHooks.afterLeave = () => {
240240
state.isLeaving = false
241-
instance.update()
241+
// #6835
242+
// it also needs to be updated when active is undefined
243+
if (instance.update.active !== false) {
244+
instance.update()
245+
}
242246
}
243247
return emptyPlaceholder(child)
244248
} else if (mode === 'in-out' && innerChild.type !== Comment) {

0 commit comments

Comments
 (0)