shallowRef,
Fragment
} from '@vue/runtime-test'
-import { createApp } from 'vue'
+import { createApp, defineComponent } from 'vue'
describe('Suspense', () => {
const deps: Promise<any>[] = []
h(Suspense, null, {
default: [
h(outerToggle.value ? OuterB : OuterA, null, {
- default: () => h(Suspense, { suspensible: true },{
- default: h(innerToggle.value ? InnerB : InnerA)
- })
+ default: () =>
+ h(
+ Suspense,
+ { suspensible: true },
+ {
+ default: h(innerToggle.value ? InnerB : InnerA)
+ }
+ )
})
],
fallback: h('div', 'fallback outer')
expect(serializeInner(root)).toBe(expected)
expect(calls).toContain('innerB mounted')
})
+
+ // #8206
+ test('nested suspense with suspensible & no async deps', async () => {
+ const calls: string[] = []
+ let expected = ''
+
+ const InnerA = defineComponent({
+ setup: () => {
+ calls.push('innerA created')
+ onMounted(() => {
+ calls.push('innerA mounted')
+ })
+ return () => h('div', 'innerA')
+ }
+ })
+
+ const InnerB = defineComponent({
+ setup: () => {
+ calls.push('innerB created')
+ onMounted(() => {
+ calls.push('innerB mounted')
+ })
+ return () => h('div', 'innerB')
+ }
+ })
+
+ const OuterA = defineComponent({
+ setup: (_, { slots }: any) => {
+ calls.push('outerA created')
+ onMounted(() => {
+ calls.push('outerA mounted')
+ })
+ return () => h(Fragment, null, [h('div', 'outerA'), slots.default?.()])
+ }
+ })
+
+ const OuterB = defineComponent({
+ setup: (_, { slots }: any) => {
+ calls.push('outerB created')
+ onMounted(() => {
+ calls.push('outerB mounted')
+ })
+ return () => h(Fragment, null, [h('div', 'outerB'), slots.default?.()])
+ }
+ })
+
+ const outerToggle = ref(false)
+ const innerToggle = ref(false)
+
+ /**
+ * <Suspense>
+ * <component :is="outerToggle ? outerB : outerA">
+ * <Suspense suspensible>
+ * <component :is="innerToggle ? innerB : innerA" />
+ * </Suspense>
+ * </component>
+ * </Suspense>
+ */
+ const Comp = defineComponent({
+ setup() {
+ return () =>
+ h(Suspense, null, {
+ default: [
+ h(outerToggle.value ? OuterB : OuterA, null, {
+ default: () =>
+ h(
+ Suspense,
+ { suspensible: true },
+ {
+ default: h(innerToggle.value ? InnerB : InnerA)
+ }
+ )
+ })
+ ],
+ fallback: h('div', 'fallback outer')
+ })
+ }
+ })
+
+ expected = `<div>outerA</div><div>innerA</div>`
+ const root = nodeOps.createElement('div')
+ render(h(Comp), root)
+ expect(serializeInner(root)).toBe(expected)
+
+ // mount outer component and inner component
+ await Promise.all(deps)
+ await nextTick()
+
+ expect(serializeInner(root)).toBe(expected)
+ expect(calls).toEqual([
+ 'outerA created',
+ 'innerA created',
+ 'innerA mounted',
+ 'outerA mounted'
+ ])
+
+ // toggle outer component
+ calls.length = 0
+ deps.length = 0
+ outerToggle.value = true
+ await nextTick()
+
+ await Promise.all(deps)
+ await nextTick()
+ expected = `<div>outerB</div><div>innerA</div>`
+ expect(serializeInner(root)).toBe(expected)
+ expect(calls).toContain('outerB mounted')
+ expect(calls).toContain('innerA mounted')
+
+ // toggle inner component
+ calls.length = 0
+ deps.length = 0
+ innerToggle.value = true
+ await Promise.all(deps)
+ await nextTick()
+ expected = `<div>outerB</div><div>innerB</div>`
+ expect(serializeInner(root)).toBe(expected)
+ })
})
setActiveBranch(suspense, vnode.ssFallback!)
} else {
// Suspense has no async deps. Just resolve.
- suspense.resolve()
+ suspense.resolve(false, true)
}
}
isHydrating: boolean
isUnmounted: boolean
effects: Function[]
- resolve(force?: boolean): void
+ resolve(force?: boolean, sync?: boolean): void
fallback(fallbackVNode: VNode): void
move(
container: RendererElement,
// if set `suspensible: true`, set the current suspense as a dep of parent suspense
let parentSuspenseId: number | undefined
- const isSuspensible =
- vnode.props?.suspensible != null && vnode.props.suspensible !== false
+ const isSuspensible = isVNodeSuspensible(vnode)
if (isSuspensible) {
if (parentSuspense?.pendingBranch) {
- parentSuspenseId = parentSuspense?.pendingId
+ parentSuspenseId = parentSuspense.pendingId
parentSuspense.deps++
}
}
isUnmounted: false,
effects: [],
- resolve(resume = false) {
+ resolve(resume = false, sync = false) {
if (__DEV__) {
if (!resume && !suspense.pendingBranch) {
throw new Error(
parentSuspenseId === parentSuspense.pendingId
) {
parentSuspense.deps--
- if (parentSuspense.deps === 0) {
+ if (parentSuspense.deps === 0 && !sync) {
parentSuspense.resolve()
}
}
updateHOCHostEl(parentComponent, el)
}
}
+
+function isVNodeSuspensible(vnode: VNode) {
+ return vnode.props?.suspensible != null && vnode.props.suspensible !== false
+}