]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(warn): avoid warning on empty children with Suspense (#3962)
authorEduardo San Martin Morote <posva@users.noreply.github.com>
Mon, 6 Nov 2023 09:48:40 +0000 (10:48 +0100)
committerGitHub <noreply@github.com>
Mon, 6 Nov 2023 09:48:40 +0000 (17:48 +0800)
packages/runtime-core/__tests__/components/Suspense.spec.ts
packages/runtime-core/src/components/Suspense.ts
packages/runtime-core/src/vnode.ts

index 065898048c3ef431165686c4fc260e7e95100951..d822a992816fde42e3eb53912e51d32c4263f208 100644 (file)
@@ -17,9 +17,12 @@ import {
   onUnmounted,
   onErrorCaptured,
   shallowRef,
+  SuspenseProps,
+  resolveDynamicComponent,
   Fragment
 } from '@vue/runtime-test'
 import { createApp, defineComponent } from 'vue'
+import { type RawSlots } from 'packages/runtime-core/src/componentSlots'
 
 describe('Suspense', () => {
   const deps: Promise<any>[] = []
@@ -1523,4 +1526,75 @@ describe('Suspense', () => {
     expected = `<div>outerB</div><div>innerB</div>`
     expect(serializeInner(root)).toBe(expected)
   })
+
+  describe('warnings', () => {
+    // base function to check if a combination of slots warns or not
+    function baseCheckWarn(
+      shouldWarn: boolean,
+      children: RawSlots,
+      props: SuspenseProps | null = null
+    ) {
+      const Comp = {
+        setup() {
+          return () => h(Suspense, props, children)
+        }
+      }
+
+      const root = nodeOps.createElement('div')
+      render(h(Comp), root)
+
+      if (shouldWarn) {
+        expect(`<Suspense> slots expect a single root node.`).toHaveBeenWarned()
+      } else {
+        expect(
+          `<Suspense> slots expect a single root node.`
+        ).not.toHaveBeenWarned()
+      }
+    }
+
+    // actual function that we use in tests
+    const checkWarn = baseCheckWarn.bind(null, true)
+    const checkNoWarn = baseCheckWarn.bind(null, false)
+
+    test('does not warn on single child', async () => {
+      checkNoWarn({
+        default: h('div'),
+        fallback: h('div')
+      })
+    })
+
+    test('does not warn on null', async () => {
+      checkNoWarn({
+        default: null,
+        fallback: null
+      })
+    })
+
+    test('does not warn on <component :is="null" />', async () => {
+      checkNoWarn({
+        default: () => [resolveDynamicComponent(null)],
+        fallback: () => null
+      })
+    })
+
+    test('does not warn on empty array', async () => {
+      checkNoWarn({
+        default: [],
+        fallback: () => []
+      })
+    })
+
+    test('warns on multiple children in default', async () => {
+      checkWarn({
+        default: [h('div'), h('div')]
+      })
+    })
+
+    test('warns on multiple children in fallback', async () => {
+      checkWarn({
+        default: h('div'),
+        fallback: [h('div'), h('div')]
+      })
+    })
+  })
 })
index 3640733d7343f1c20ea6531b437d49b3d76e4aa9..ddf21dd2ed474afcaee92c724a4b80d4a658fadb 100644 (file)
@@ -29,6 +29,7 @@ import {
   assertNumber
 } from '../warning'
 import { handleError, ErrorCodes } from '../errorHandling'
+import { NULL_DYNAMIC_COMPONENT } from '../helpers/resolveAssets'
 
 export interface SuspenseProps {
   onResolve?: () => void
@@ -795,7 +796,11 @@ function normalizeSuspenseSlot(s: any) {
   }
   if (isArray(s)) {
     const singleChild = filterSingleRoot(s)
-    if (__DEV__ && !singleChild) {
+    if (
+      __DEV__ &&
+      !singleChild &&
+      s.filter(child => child !== NULL_DYNAMIC_COMPONENT).length > 0
+    ) {
       warn(`<Suspense> slots expect a single root node.`)
     }
     s = singleChild
index f8cf6652d31d1d41b67479b518009aa0957f6174..10ee03c29c6c230225a3ee476915b0c1e6d780f2 100644 (file)
@@ -114,6 +114,7 @@ export type VNodeProps = {
 
 type VNodeChildAtom =
   | VNode
+  | typeof NULL_DYNAMIC_COMPONENT
   | string
   | number
   | boolean