diff --git a/packages/runtime-core/__tests__/hydration.spec.ts b/packages/runtime-core/__tests__/hydration.spec.ts
index dffa2bc2246..f272a67b21c 100644
--- a/packages/runtime-core/__tests__/hydration.spec.ts
+++ b/packages/runtime-core/__tests__/hydration.spec.ts
@@ -676,6 +676,7 @@ describe('SSR hydration', () => {
)
expect(teleportContainer.innerHTML).toBe('Teleported Comp1')
expect(`Hydration children mismatch`).toHaveBeenWarned()
+ expect(`Hydration completed but contains mismatches.`).toHaveBeenErrored()
toggle.value = false
await nextTick()
@@ -2472,6 +2473,9 @@ describe('SSR hydration', () => {
'
',
)
expect(`Hydration node mismatch`).not.toHaveBeenWarned()
+ expect(
+ `Hydration completed but contains mismatches.`,
+ ).not.toHaveBeenErrored()
})
test('fragment too many children', () => {
diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts
index a9ce641321f..fec7ff455c9 100644
--- a/packages/runtime-core/src/hydration.ts
+++ b/packages/runtime-core/src/hydration.ts
@@ -57,7 +57,7 @@ export enum DOMNodeTypes {
let hasLoggedMismatchError = false
const logMismatchError = () => {
- if (__TEST__ || hasLoggedMismatchError) {
+ if (hasLoggedMismatchError) {
return
}
// this error should show up in production
@@ -664,9 +664,11 @@ export function createHydrationFunctions(
if (next && isComment(next) && next.data === ']') {
return nextSibling((vnode.anchor = next))
} else {
- // fragment didn't hydrate successfully, since we didn't get a end anchor
- // back. This should have led to node/children mismatch warnings.
- logMismatchError()
+ if (!isMismatchAllowed(container, MismatchTypes.CHILDREN)) {
+ // fragment didn't hydrate successfully, since we didn't get a end anchor
+ // back. This should have led to node/children mismatch warnings.
+ logMismatchError()
+ }
// since the anchor is missing, we need to create one and insert it
insert((vnode.anchor = createComment(`]`)), container, next)
diff --git a/scripts/setup-vitest.ts b/scripts/setup-vitest.ts
index 08203572aff..fb8653b2130 100644
--- a/scripts/setup-vitest.ts
+++ b/scripts/setup-vitest.ts
@@ -6,6 +6,7 @@ declare module 'vitest' {
}
interface CustomMatchers {
+ toHaveBeenErrored(): R
toHaveBeenWarned(): R
toHaveBeenWarnedLast(): R
toHaveBeenWarnedTimes(n: number): R
@@ -14,6 +15,27 @@ interface CustomMatchers {
vi.stubGlobal('MathMLElement', class MathMLElement {})
expect.extend({
+ toHaveBeenErrored(received: string) {
+ const passed = error.mock.calls.some(args => args[0].includes(received))
+ if (passed) {
+ asserted.add(received)
+ return {
+ pass: true,
+ message: () => `expected "${received}" not to have been errored.`,
+ }
+ } else {
+ const msgs = error.mock.calls.map(args => args[0]).join('\n - ')
+ return {
+ pass: false,
+ message: () =>
+ `expected "${received}" to have been errored` +
+ (msgs.length
+ ? `.\n\nActual messages:\n\n - ${msgs}`
+ : ` but no error was recorded.`),
+ }
+ }
+ },
+
toHaveBeenWarned(received: string) {
const passed = warn.mock.calls.some(args => args[0].includes(received))
if (passed) {
@@ -79,16 +101,21 @@ expect.extend({
})
let warn: MockInstance
+let error: MockInstance
const asserted: Set = new Set()
beforeEach(() => {
asserted.clear()
warn = vi.spyOn(console, 'warn')
warn.mockImplementation(() => {})
+
+ error = vi.spyOn(console, 'error')
+ error.mockImplementation(() => {})
})
afterEach(() => {
const assertedArray = Array.from(asserted)
+
const nonAssertedWarnings = warn.mock.calls
.map(args => args[0])
.filter(received => {
@@ -104,4 +131,20 @@ afterEach(() => {
)}`,
)
}
+
+ const nonAssertedErrors = error.mock.calls
+ .map(args => args[0])
+ .filter(received => {
+ return !assertedArray.some(assertedMsg => {
+ return received.includes(assertedMsg)
+ })
+ })
+ error.mockRestore()
+ if (nonAssertedErrors.length) {
+ throw new Error(
+ `test case threw unexpected errors:\n - ${nonAssertedErrors.join(
+ '\n - ',
+ )}`,
+ )
+ }
})