From c7fa963ec41b9f8ba6fc5ead08116cb8818d403b Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Thu, 24 Sep 2026 14:04:05 +0900 Subject: [PATCH 1/3] test(vue-query/useMutation): assert the error watcher throw on both Vue 2 and Vue 3 --- .../src/__tests__/useMutation.test.ts | 63 +++++++++++++------ 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/packages/vue-query/src/__tests__/useMutation.test.ts b/packages/vue-query/src/__tests__/useMutation.test.ts index 97502dc7715..12615128080 100644 --- a/packages/vue-query/src/__tests__/useMutation.test.ts +++ b/packages/vue-query/src/__tests__/useMutation.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { reactive, ref } from 'vue-demi' +import { isVue2, isVue3, reactive, ref } from 'vue-demi' import { noop } from '@tanstack/query-core' import { queryKey, sleep } from '@tanstack/query-test-utils' import { useMutation } from '../useMutation' @@ -553,27 +553,52 @@ describe('useMutation', () => { expect(throwOnError).toHaveBeenCalledWith(err) }) - it('should throw from error watcher when throwOnError returns true', async () => { - const throwOnError = vi.fn().mockReturnValue(true) - const { mutate } = useMutation({ - mutationFn: () => - sleep(10).then(() => Promise.reject(new Error('Some error'))), - throwOnError, - }) - - mutate() - - // Suppress the Unhandled Rejection caused by watcher throw in Vue 3 - const rejectionHandler = () => {} - process.on('unhandledRejection', rejectionHandler) + it.runIf(isVue2)( + 'should throw from error watcher when throwOnError returns true, which Vue 2 logs via console.error', + async () => { + const consoleMock = vi + .spyOn(console, 'error') + .mockImplementation(() => undefined) + const throwOnError = vi.fn().mockReturnValue(true) + const { mutate } = useMutation({ + mutationFn: () => + sleep(10).then(() => Promise.reject(new Error('Some error'))), + throwOnError, + }) - await vi.advanceTimersByTimeAsync(10) + mutate() + await vi.advanceTimersByTimeAsync(10) + expect(throwOnError).toHaveBeenCalledTimes(1) + expect(throwOnError).toHaveBeenCalledWith(Error('Some error')) + expect(consoleMock).toHaveBeenCalledWith(Error('Some error')) + consoleMock.mockRestore() + }, + ) - process.off('unhandledRejection', rejectionHandler) + it.runIf(isVue3)( + 'should throw from error watcher when throwOnError returns true, which Vue 3 surfaces as an unhandled rejection', + async () => { + const throwOnError = vi.fn().mockReturnValue(true) + const { mutate } = useMutation({ + mutationFn: () => + sleep(10).then(() => Promise.reject(new Error('Some error'))), + throwOnError, + }) - expect(throwOnError).toHaveBeenCalledTimes(1) - expect(throwOnError).toHaveBeenCalledWith(Error('Some error')) - }) + const unhandledRejectionFn = vi.fn() + process.on('unhandledRejection', unhandledRejectionFn) + mutate() + await vi.advanceTimersByTimeAsync(10) + process.off('unhandledRejection', unhandledRejectionFn) + expect(throwOnError).toHaveBeenCalledTimes(1) + expect(throwOnError).toHaveBeenCalledWith(Error('Some error')) + expect(unhandledRejectionFn).toHaveBeenCalledTimes(1) + expect(unhandledRejectionFn).toHaveBeenCalledWith( + Error('Some error'), + expect.anything(), + ) + }, + ) }) describe('optimistic updates', () => { From a254b6733e36284d2a4a039a699789576488bb87 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Thu, 24 Sep 2026 14:17:21 +0900 Subject: [PATCH 2/3] test(vue-query/useMutation): remove the 'unhandledRejection' listener in 'onTestFinished' so it does not leak when the test fails --- packages/vue-query/src/__tests__/useMutation.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/vue-query/src/__tests__/useMutation.test.ts b/packages/vue-query/src/__tests__/useMutation.test.ts index 12615128080..ae03f963fba 100644 --- a/packages/vue-query/src/__tests__/useMutation.test.ts +++ b/packages/vue-query/src/__tests__/useMutation.test.ts @@ -577,7 +577,7 @@ describe('useMutation', () => { it.runIf(isVue3)( 'should throw from error watcher when throwOnError returns true, which Vue 3 surfaces as an unhandled rejection', - async () => { + async ({ onTestFinished }) => { const throwOnError = vi.fn().mockReturnValue(true) const { mutate } = useMutation({ mutationFn: () => @@ -587,9 +587,12 @@ describe('useMutation', () => { const unhandledRejectionFn = vi.fn() process.on('unhandledRejection', unhandledRejectionFn) + onTestFinished(() => { + process.off('unhandledRejection', unhandledRejectionFn) + }) + mutate() await vi.advanceTimersByTimeAsync(10) - process.off('unhandledRejection', unhandledRejectionFn) expect(throwOnError).toHaveBeenCalledTimes(1) expect(throwOnError).toHaveBeenCalledWith(Error('Some error')) expect(unhandledRejectionFn).toHaveBeenCalledTimes(1) From e8cdcf6828d49c9de286e1aac6c29c4fbe8642d4 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Thu, 24 Sep 2026 14:26:46 +0900 Subject: [PATCH 3/3] test(vue-query/useMutation): assert the 'unhandledRejection' listener receives the rejected promise with 'expect.any(Promise)' --- packages/vue-query/src/__tests__/useMutation.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vue-query/src/__tests__/useMutation.test.ts b/packages/vue-query/src/__tests__/useMutation.test.ts index ae03f963fba..579539eda8b 100644 --- a/packages/vue-query/src/__tests__/useMutation.test.ts +++ b/packages/vue-query/src/__tests__/useMutation.test.ts @@ -598,7 +598,7 @@ describe('useMutation', () => { expect(unhandledRejectionFn).toHaveBeenCalledTimes(1) expect(unhandledRejectionFn).toHaveBeenCalledWith( Error('Some error'), - expect.anything(), + expect.any(Promise), ) }, )