Browse Source

fix(suspense): ensure nested suspense patching if in fallback state (#10417)

close #10415
pull/10423/head
edison 1 year ago
committed by GitHub
parent
commit
7c97778aec
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 139
      packages/runtime-core/__tests__/components/Suspense.spec.ts
  2. 6
      packages/runtime-core/src/components/Suspense.ts

139
packages/runtime-core/__tests__/components/Suspense.spec.ts

@ -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() {
@ -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 () =>
@ -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[] = []

6
packages/runtime-core/src/components/Suspense.ts

@ -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

Loading…
Cancel
Save