]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
test(ssr): test for hydration mismatch handling
authorEvan You <yyx990803@gmail.com>
Fri, 6 Mar 2020 20:39:54 +0000 (15:39 -0500)
committerEvan You <yyx990803@gmail.com>
Fri, 6 Mar 2020 20:39:54 +0000 (15:39 -0500)
packages/compiler-core/__tests__/hydration.spec.ts
packages/runtime-core/src/hydration.ts

index 37672db06fc9ca4020d6bcc762309843920a9da9..6d311bfe2421acd63e49da95a7cec1ce7a3eb35a 100644 (file)
@@ -8,6 +8,7 @@ import {
   createStaticVNode
 } from '@vue/runtime-dom'
 import { renderToString } from '@vue/server-renderer'
+import { mockWarn } from '@vue/shared'
 
 function mountWithHydration(html: string, render: () => any) {
   const container = document.createElement('div')
@@ -268,12 +269,48 @@ describe('SSR hydration', () => {
   })
 
   describe('mismatch handling', () => {
-    test('text', () => {})
-
-    test('not enough children', () => {})
-
-    test('too many children', () => {})
-
-    test('complete mismatch', () => {})
+    mockWarn()
+
+    test('text node', () => {
+      const { container } = mountWithHydration(`foo`, () => 'bar')
+      expect(container.textContent).toBe('bar')
+      expect(`Hydration text mismatch`).toHaveBeenWarned()
+    })
+
+    test('element text content', () => {
+      const { container } = mountWithHydration(`<div>foo</div>`, () =>
+        h('div', 'bar')
+      )
+      expect(container.innerHTML).toBe('<div>bar</div>')
+      expect(`Hydration text content mismatch in <div>`).toHaveBeenWarned()
+    })
+
+    test('not enough children', () => {
+      const { container } = mountWithHydration(`<div></div>`, () =>
+        h('div', [h('span', 'foo'), h('span', 'bar')])
+      )
+      expect(container.innerHTML).toBe(
+        '<div><span>foo</span><span>bar</span></div>'
+      )
+      expect(`Hydration children mismatch in <div>`).toHaveBeenWarned()
+    })
+
+    test('too many children', () => {
+      const { container } = mountWithHydration(
+        `<div><span>foo</span><span>bar</span></div>`,
+        () => h('div', [h('span', 'foo')])
+      )
+      expect(container.innerHTML).toBe('<div><span>foo</span></div>')
+      expect(`Hydration children mismatch in <div>`).toHaveBeenWarned()
+    })
+
+    test('complete mismatch', () => {
+      const { container } = mountWithHydration(
+        `<div><span>foo</span><span>bar</span></div>`,
+        () => h('div', [h('div', 'foo'), h('p', 'bar')])
+      )
+      expect(container.innerHTML).toBe('<div><div>foo</div><p>bar</p></div>')
+      expect(`Hydration node mismatch`).toHaveBeenWarnedTimes(2)
+    })
   })
 })
index efb11cf715b1b28aa26d4f4eaa43a9f0970b4d23..c38ea9497467da62a36bdc3eb09a4d48f4676926 100644 (file)
@@ -94,7 +94,10 @@ export function createHydrationFunctions({
         return hydrateFragment(node, vnode, parentComponent, optimized)
       default:
         if (shapeFlag & ShapeFlags.ELEMENT) {
-          if (domType !== DOMNodeTypes.ELEMENT) {
+          if (
+            domType !== DOMNodeTypes.ELEMENT ||
+            vnode.type !== (node as Element).tagName.toLowerCase()
+          ) {
             return handleMismtach(node, vnode, parentComponent)
           }
           return hydrateElement(
@@ -176,20 +179,32 @@ export function createHydrationFunctions({
           parentComponent,
           optimized
         )
+        let hasWarned = false
         while (next) {
           hasMismatch = true
-          __DEV__ &&
+          if (__DEV__ && !hasWarned) {
             warn(
-              `Hydration children mismatch: ` +
+              `Hydration children mismatch in <${vnode.type as string}>: ` +
                 `server rendered element contains more child nodes than client vdom.`
             )
+            hasWarned = true
+          }
           // The SSRed DOM contains more nodes than it should. Remove them.
           const cur = next
           next = next.nextSibling
           el.removeChild(cur)
         }
       } else if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
-        el.textContent = vnode.children as string
+        if (el.textContent !== vnode.children) {
+          hasMismatch = true
+          __DEV__ &&
+            warn(
+              `Hydration text content mismatch in <${vnode.type as string}>:\n` +
+                `- Client: ${el.textContent}\n` +
+                `- Server: ${vnode.children as string}`
+            )
+          el.textContent = vnode.children as string
+        }
       }
     }
     return el.nextSibling
@@ -205,6 +220,7 @@ export function createHydrationFunctions({
     optimized = optimized || vnode.dynamicChildren !== null
     const children = vnode.children as VNode[]
     const l = children.length
+    let hasWarned = false
     for (let i = 0; i < l; i++) {
       const vnode = optimized
         ? children[i]
@@ -213,11 +229,13 @@ export function createHydrationFunctions({
         node = hydrateNode(node, vnode, parentComponent, optimized)
       } else {
         hasMismatch = true
-        __DEV__ &&
+        if (__DEV__ && !hasWarned) {
           warn(
-            `Hydration children mismatch: ` +
+            `Hydration children mismatch in <${container.tagName.toLowerCase()}>: ` +
               `server rendered element contains fewer child nodes than client vdom.`
           )
+          hasWarned = true
+        }
         // the SSRed DOM didn't contain enough nodes. Mount the missing ones.
         patch(null, vnode, container)
       }