test(ssr): test for hydration mismatch handling
This commit is contained in:
parent
f7a026109d
commit
dd2d25fee1
@ -8,6 +8,7 @@ import {
|
|||||||
createStaticVNode
|
createStaticVNode
|
||||||
} from '@vue/runtime-dom'
|
} from '@vue/runtime-dom'
|
||||||
import { renderToString } from '@vue/server-renderer'
|
import { renderToString } from '@vue/server-renderer'
|
||||||
|
import { mockWarn } from '@vue/shared'
|
||||||
|
|
||||||
function mountWithHydration(html: string, render: () => any) {
|
function mountWithHydration(html: string, render: () => any) {
|
||||||
const container = document.createElement('div')
|
const container = document.createElement('div')
|
||||||
@ -268,12 +269,48 @@ describe('SSR hydration', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('mismatch handling', () => {
|
describe('mismatch handling', () => {
|
||||||
test('text', () => {})
|
mockWarn()
|
||||||
|
|
||||||
test('not enough children', () => {})
|
test('text node', () => {
|
||||||
|
const { container } = mountWithHydration(`foo`, () => 'bar')
|
||||||
|
expect(container.textContent).toBe('bar')
|
||||||
|
expect(`Hydration text mismatch`).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
test('too many children', () => {})
|
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('complete mismatch', () => {})
|
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)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -94,7 +94,10 @@ export function createHydrationFunctions({
|
|||||||
return hydrateFragment(node, vnode, parentComponent, optimized)
|
return hydrateFragment(node, vnode, parentComponent, optimized)
|
||||||
default:
|
default:
|
||||||
if (shapeFlag & ShapeFlags.ELEMENT) {
|
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 handleMismtach(node, vnode, parentComponent)
|
||||||
}
|
}
|
||||||
return hydrateElement(
|
return hydrateElement(
|
||||||
@ -176,20 +179,32 @@ export function createHydrationFunctions({
|
|||||||
parentComponent,
|
parentComponent,
|
||||||
optimized
|
optimized
|
||||||
)
|
)
|
||||||
|
let hasWarned = false
|
||||||
while (next) {
|
while (next) {
|
||||||
hasMismatch = true
|
hasMismatch = true
|
||||||
__DEV__ &&
|
if (__DEV__ && !hasWarned) {
|
||||||
warn(
|
warn(
|
||||||
`Hydration children mismatch: ` +
|
`Hydration children mismatch in <${vnode.type as string}>: ` +
|
||||||
`server rendered element contains more child nodes than client vdom.`
|
`server rendered element contains more child nodes than client vdom.`
|
||||||
)
|
)
|
||||||
|
hasWarned = true
|
||||||
|
}
|
||||||
// The SSRed DOM contains more nodes than it should. Remove them.
|
// The SSRed DOM contains more nodes than it should. Remove them.
|
||||||
const cur = next
|
const cur = next
|
||||||
next = next.nextSibling
|
next = next.nextSibling
|
||||||
el.removeChild(cur)
|
el.removeChild(cur)
|
||||||
}
|
}
|
||||||
} else if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
|
} 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
|
return el.nextSibling
|
||||||
@ -205,6 +220,7 @@ export function createHydrationFunctions({
|
|||||||
optimized = optimized || vnode.dynamicChildren !== null
|
optimized = optimized || vnode.dynamicChildren !== null
|
||||||
const children = vnode.children as VNode[]
|
const children = vnode.children as VNode[]
|
||||||
const l = children.length
|
const l = children.length
|
||||||
|
let hasWarned = false
|
||||||
for (let i = 0; i < l; i++) {
|
for (let i = 0; i < l; i++) {
|
||||||
const vnode = optimized
|
const vnode = optimized
|
||||||
? children[i]
|
? children[i]
|
||||||
@ -213,11 +229,13 @@ export function createHydrationFunctions({
|
|||||||
node = hydrateNode(node, vnode, parentComponent, optimized)
|
node = hydrateNode(node, vnode, parentComponent, optimized)
|
||||||
} else {
|
} else {
|
||||||
hasMismatch = true
|
hasMismatch = true
|
||||||
__DEV__ &&
|
if (__DEV__ && !hasWarned) {
|
||||||
warn(
|
warn(
|
||||||
`Hydration children mismatch: ` +
|
`Hydration children mismatch in <${container.tagName.toLowerCase()}>: ` +
|
||||||
`server rendered element contains fewer child nodes than client vdom.`
|
`server rendered element contains fewer child nodes than client vdom.`
|
||||||
)
|
)
|
||||||
|
hasWarned = true
|
||||||
|
}
|
||||||
// the SSRed DOM didn't contain enough nodes. Mount the missing ones.
|
// the SSRed DOM didn't contain enough nodes. Mount the missing ones.
|
||||||
patch(null, vnode, container)
|
patch(null, vnode, container)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user