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')
})
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)
+ })
})
})
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(
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
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]
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)
}