Skip to content

Commit 51f813c

Browse files
author
Alexander Dmitryuk
committed
fix(hydration): don't error if data-allow-mismatch provided for fragment
1 parent 11f7674 commit 51f813c

File tree

3 files changed

+53
-5
lines changed

3 files changed

+53
-5
lines changed

packages/runtime-core/__tests__/hydration.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,7 @@ describe('SSR hydration', () => {
668668
)
669669
expect(teleportContainer.innerHTML).toBe('<span>Teleported Comp1</span>')
670670
expect(`Hydration children mismatch`).toHaveBeenWarned()
671+
expect(`Hydration completed but contains mismatches.`).toHaveBeenErrored()
671672

672673
toggle.value = false
673674
await nextTick()
@@ -2271,6 +2272,9 @@ describe('SSR hydration', () => {
22712272
'<div data-allow-mismatch="children"><!--[--><div>foo</div><div>bar</div><!--]--><div>baz</div></div>',
22722273
)
22732274
expect(`Hydration node mismatch`).not.toHaveBeenWarned()
2275+
expect(
2276+
`Hydration completed but contains mismatches.`,
2277+
).not.toHaveBeenErrored()
22742278
})
22752279

22762280
test('fragment too many children', () => {
@@ -2287,7 +2291,6 @@ describe('SSR hydration', () => {
22872291
// excessive children removal
22882292
expect(`Hydration children mismatch`).not.toHaveBeenWarned()
22892293
})
2290-
22912294
test('comment mismatch (element)', () => {
22922295
const { container } = mountWithHydration(
22932296
`<div data-allow-mismatch="children"><span></span></div>`,

packages/runtime-core/src/hydration.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export enum DOMNodeTypes {
5656

5757
let hasLoggedMismatchError = false
5858
const logMismatchError = () => {
59-
if (__TEST__ || hasLoggedMismatchError) {
59+
if (hasLoggedMismatchError) {
6060
return
6161
}
6262
// this error should show up in production
@@ -657,9 +657,11 @@ export function createHydrationFunctions(
657657
if (next && isComment(next) && next.data === ']') {
658658
return nextSibling((vnode.anchor = next))
659659
} else {
660-
// fragment didn't hydrate successfully, since we didn't get a end anchor
661-
// back. This should have led to node/children mismatch warnings.
662-
logMismatchError()
660+
if (!isMismatchAllowed(node.parentElement, 1 /* CHILDREN */)) {
661+
// fragment didn't hydrate successfully, since we didn't get a end anchor
662+
// back. This should have led to node/children mismatch warnings.
663+
logMismatchError()
664+
}
663665

664666
// since the anchor is missing, we need to create one and insert it
665667
insert((vnode.anchor = createComment(`]`)), container, next)

scripts/setup-vitest.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ declare module 'vitest' {
66
}
77

88
interface CustomMatchers<R = unknown> {
9+
toHaveBeenErrored(): R
910
toHaveBeenWarned(): R
1011
toHaveBeenWarnedLast(): R
1112
toHaveBeenWarnedTimes(n: number): R
@@ -14,6 +15,27 @@ interface CustomMatchers<R = unknown> {
1415
vi.stubGlobal('MathMLElement', class MathMLElement {})
1516

1617
expect.extend({
18+
toHaveBeenErrored(received: string) {
19+
const passed = error.mock.calls.some(args => args[0].includes(received))
20+
if (passed) {
21+
asserted.add(received)
22+
return {
23+
pass: true,
24+
message: () => `expected "${received}" not to have been errored.`,
25+
}
26+
} else {
27+
const msgs = error.mock.calls.map(args => args[0]).join('\n - ')
28+
return {
29+
pass: false,
30+
message: () =>
31+
`expected "${received}" to have been errored` +
32+
(msgs.length
33+
? `.\n\nActual messages:\n\n - ${msgs}`
34+
: ` but no error was recorded.`),
35+
}
36+
}
37+
},
38+
1739
toHaveBeenWarned(received: string) {
1840
const passed = warn.mock.calls.some(args => args[0].includes(received))
1941
if (passed) {
@@ -79,16 +101,21 @@ expect.extend({
79101
})
80102

81103
let warn: MockInstance
104+
let error: MockInstance
82105
const asserted: Set<string> = new Set()
83106

84107
beforeEach(() => {
85108
asserted.clear()
86109
warn = vi.spyOn(console, 'warn')
87110
warn.mockImplementation(() => {})
111+
112+
error = vi.spyOn(console, 'error')
113+
error.mockImplementation(() => {})
88114
})
89115

90116
afterEach(() => {
91117
const assertedArray = Array.from(asserted)
118+
92119
const nonAssertedWarnings = warn.mock.calls
93120
.map(args => args[0])
94121
.filter(received => {
@@ -104,4 +131,20 @@ afterEach(() => {
104131
)}`,
105132
)
106133
}
134+
135+
const nonAssertedErrors = error.mock.calls
136+
.map(args => args[0])
137+
.filter(received => {
138+
return !assertedArray.some(assertedMsg => {
139+
return received.includes(assertedMsg)
140+
})
141+
})
142+
error.mockRestore()
143+
if (nonAssertedErrors.length) {
144+
throw new Error(
145+
`test case threw unexpected errors:\n - ${nonAssertedErrors.join(
146+
'\n - ',
147+
)}`,
148+
)
149+
}
107150
})

0 commit comments

Comments
 (0)