Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(Transition): should not be updated again after unmounted (#6835) #6839

Merged
merged 4 commits into from Nov 8, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
122 changes: 106 additions & 16 deletions packages/runtime-core/__tests__/components/BaseTransition.spec.ts
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
3 changes: 2 additions & 1 deletion packages/runtime-core/src/components/BaseTransition.ts
Expand Up @@ -238,7 +238,8 @@ const BaseTransitionImpl: ComponentOptions = {
// return placeholder node and queue update when leave finishes
leavingHooks.afterLeave = () => {
state.isLeaving = false
instance.update()
// #6835
instance.update.active !== false && instance.update()
antfu marked this conversation as resolved.
Show resolved Hide resolved
}
return emptyPlaceholder(child)
} else if (mode === 'in-out' && innerChild.type !== Comment) {
Expand Down