Skip to content

Commit

Permalink
fix(suspense): ensure nested suspense patching if in fallback state (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
edison1105 authored Feb 28, 2024
1 parent 1f6a110 commit 7c97778
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 13 deletions.
139 changes: 127 additions & 12 deletions packages/runtime-core/__tests__/components/Suspense.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ describe('Suspense', () => {
}
}

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]
}
},
}

test('fallback content', async () => {
const Async = defineAsyncComponent({
render() {
Expand Down Expand Up @@ -1041,18 +1053,6 @@ describe('Suspense', () => {

// #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 () =>
Expand Down Expand Up @@ -1132,6 +1132,121 @@ describe('Suspense', () => {
expect(serializeInner(root)).toBe(`<div>innerA</div>`)
})

// #10415
test('nested suspense (w/ suspensible) switch several times before parent suspense resolve', async () => {
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 route = shallowRef([OuterA, InnerA])
const InnerB = defineAsyncComponent(
{
setup: () => {
return () => h('div', 'innerB')
},
},
5,
)

const InnerB1 = defineAsyncComponent(
{
setup: () => {
return () => h('div', 'innerB1')
},
},
5,
)

const InnerB2 = defineAsyncComponent(
{
setup: () => {
return () => h('div', 'innerB2')
},
},
5,
)

const OuterB = defineAsyncComponent(
{
setup() {
nextTick(async () => {
await new Promise(resolve => setTimeout(resolve, 1))
route.value = [OuterB, InnerB1]
})

nextTick(async () => {
await new Promise(resolve => setTimeout(resolve, 1))
route.value = [OuterB, InnerB2]
})

return () =>
h(RouterView, null, {
default: ({ Component }: any) => [
h(
Suspense,
{ suspensible: true },
{
default: () => h(Component),
},
),
],
})
},
},
5,
)

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

route.value = [OuterB, InnerB]
await nextTick()

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

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

Expand Down
6 changes: 5 additions & 1 deletion packages/runtime-core/src/components/Suspense.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,11 @@ export const SuspenseImpl = {
// 2. mounting along with the pendingBranch of parentSuspense
// it is necessary to skip the current patch to avoid multiple mounts
// of inner components.
if (parentSuspense && parentSuspense.deps > 0) {
if (
parentSuspense &&
parentSuspense.deps > 0 &&
!n1.suspense!.isInFallback
) {
n2.suspense = n1.suspense!
n2.suspense.vnode = n2
n2.el = n1.el
Expand Down

0 comments on commit 7c97778

Please sign in to comment.