Skip to content

Commit

Permalink
fix(suspense): handle suspense switching with nested suspense (vuejs#…
Browse files Browse the repository at this point in the history
  • Loading branch information
edison1105 authored and OnlyWick committed Feb 27, 2024
1 parent cd0b528 commit 5cd74e5
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 2 deletions.
95 changes: 94 additions & 1 deletion packages/runtime-core/__tests__/components/Suspense.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
watch,
watchEffect,
} from '@vue/runtime-test'
import { createApp, defineComponent } from 'vue'
import { computed, createApp, defineComponent, inject, provide } from 'vue'
import type { RawSlots } from 'packages/runtime-core/src/componentSlots'
import { resetSuspenseId } from '../../src/components/Suspense'

Expand Down Expand Up @@ -1039,6 +1039,99 @@ describe('Suspense', () => {
expect(serializeInner(root)).toBe(`<div>foo<div>foo nested</div></div>`)
})

// #10098
test('switching branches w/ nested suspense', async () => {
const RouterView = {
setup(_: any, { slots }: any) {
const route = inject('route') as any
const depth = inject('depth', 0)
provide('depth', depth + 1)
return () => {
const current = route.value[depth]
return slots.default({ Component: current })[0]
}
},
}

const OuterB = defineAsyncComponent({
setup: () => {
return () =>
h(RouterView, null, {
default: ({ Component }: any) => [
h(Suspense, null, {
default: () => h(Component),
}),
],
})
},
})

const InnerB = defineAsyncComponent({
setup: () => {
return () => h('div', 'innerB')
},
})

const OuterA = defineAsyncComponent({
setup: () => {
return () =>
h(RouterView, null, {
default: ({ Component }: any) => [
h(Suspense, null, {
default: () => h(Component),
}),
],
})
},
})

const InnerA = defineAsyncComponent({
setup: () => {
return () => h('div', 'innerA')
},
})

const toggle = ref(true)
const route = computed(() => {
return toggle.value ? [OuterA, InnerA] : [OuterB, InnerB]
})

const Comp = {
setup() {
provide('route', route)
return () =>
h(RouterView, null, {
default: ({ Component }: any) => [
h(Suspense, null, {
default: () => h(Component),
}),
],
})
},
}

const root = nodeOps.createElement('div')
render(h(Comp), root)
await Promise.all(deps)
await nextTick()
expect(serializeInner(root)).toBe(`<!---->`)

await Promise.all(deps)
await nextTick()
expect(serializeInner(root)).toBe(`<div>innerA</div>`)

deps.length = 0

toggle.value = false
await nextTick()
// toggle again
toggle.value = true

await Promise.all(deps)
await nextTick()
expect(serializeInner(root)).toBe(`<div>innerA</div>`)
})

test('branch switch to 3rd branch before resolve', async () => {
const calls: string[] = []

Expand Down
4 changes: 3 additions & 1 deletion packages/runtime-core/src/components/Suspense.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ export const SuspenseImpl = {
// it is necessary to skip the current patch to avoid multiple mounts
// of inner components.
if (parentSuspense && parentSuspense.deps > 0) {
n2.suspense = n1.suspense
n2.suspense = n1.suspense!
n2.suspense.vnode = n2
n2.el = n1.el
return
}
patchSuspense(
Expand Down

0 comments on commit 5cd74e5

Please sign in to comment.