From 9a99c5c9556872dae6d325de89edb158a02e1303 Mon Sep 17 00:00:00 2001 From: Wonsuk Choi Date: Tue, 8 Sep 2026 09:53:52 +0900 Subject: [PATCH] test(svelte-query/createMutation): add tests for 'MutationFunctionContext' passed to mutationFn and callbacks --- .../InvalidateFromContext.svelte | 23 +++++ .../createMutation/MutationFnContext.svelte | 23 +++++ .../createMutation/PerCallSuccess.svelte | 24 ++++++ .../createMutation/SuccessContext.svelte | 24 ++++++ .../createMutation.svelte.test.ts | 86 +++++++++++++++++++ .../createMutation/createMutation.test-d.ts | 58 ++++++++++++- 6 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 packages/svelte-query/tests/createMutation/InvalidateFromContext.svelte create mode 100644 packages/svelte-query/tests/createMutation/MutationFnContext.svelte create mode 100644 packages/svelte-query/tests/createMutation/PerCallSuccess.svelte create mode 100644 packages/svelte-query/tests/createMutation/SuccessContext.svelte diff --git a/packages/svelte-query/tests/createMutation/InvalidateFromContext.svelte b/packages/svelte-query/tests/createMutation/InvalidateFromContext.svelte new file mode 100644 index 00000000000..400c153d780 --- /dev/null +++ b/packages/svelte-query/tests/createMutation/InvalidateFromContext.svelte @@ -0,0 +1,23 @@ + + + diff --git a/packages/svelte-query/tests/createMutation/MutationFnContext.svelte b/packages/svelte-query/tests/createMutation/MutationFnContext.svelte new file mode 100644 index 00000000000..1e831a03316 --- /dev/null +++ b/packages/svelte-query/tests/createMutation/MutationFnContext.svelte @@ -0,0 +1,23 @@ + + + + +
data: {mutation.data}
diff --git a/packages/svelte-query/tests/createMutation/PerCallSuccess.svelte b/packages/svelte-query/tests/createMutation/PerCallSuccess.svelte new file mode 100644 index 00000000000..f50fa885950 --- /dev/null +++ b/packages/svelte-query/tests/createMutation/PerCallSuccess.svelte @@ -0,0 +1,24 @@ + + + diff --git a/packages/svelte-query/tests/createMutation/SuccessContext.svelte b/packages/svelte-query/tests/createMutation/SuccessContext.svelte new file mode 100644 index 00000000000..a132cb2c7dc --- /dev/null +++ b/packages/svelte-query/tests/createMutation/SuccessContext.svelte @@ -0,0 +1,24 @@ + + + diff --git a/packages/svelte-query/tests/createMutation/createMutation.svelte.test.ts b/packages/svelte-query/tests/createMutation/createMutation.svelte.test.ts index 2c44298cda3..7195054194b 100644 --- a/packages/svelte-query/tests/createMutation/createMutation.svelte.test.ts +++ b/packages/svelte-query/tests/createMutation/createMutation.svelte.test.ts @@ -9,6 +9,10 @@ import Reset from './Reset.svelte' import Success from './Success.svelte' import Failure from './Failure.svelte' import OptimisticUpdate from './OptimisticUpdate.svelte' +import SuccessContext from './SuccessContext.svelte' +import InvalidateFromContext from './InvalidateFromContext.svelte' +import PerCallSuccess from './PerCallSuccess.svelte' +import MutationFnContext from './MutationFnContext.svelte' describe('createMutation', () => { let queryClient: QueryClient @@ -171,4 +175,86 @@ describe('createMutation', () => { expect(queryClient.getQueryData(key)).toEqual(['Todo 1', 'Todo 2']) }) + + it('should pass a non-undefined onMutateResult alongside context to onSuccess', async () => { + const onSuccessMock = vi.fn() + + const rendered = render(SuccessContext, { + props: { queryClient, onSuccessMock }, + }) + + fireEvent.click(rendered.getByRole('button', { name: /Mutate/i })) + await vi.advanceTimersByTimeAsync(10) + + expect(onSuccessMock).toHaveBeenCalledTimes(1) + const [data, variables, onMutateResult, context] = + onSuccessMock.mock.calls[0]! + expect(data).toBe('TODO') + expect(variables).toBe('todo') + expect(onMutateResult).toEqual({ startedWith: 'todo' }) + expect(context.client).toBe(queryClient) + expect(context.meta).toBeUndefined() + expect(context.mutationKey).toBeUndefined() + }) + + it('should include mutationKey in the context passed to hook-level callbacks', async () => { + const onSuccessMock = vi.fn() + + const rendered = render(SuccessContext, { + props: { queryClient, mutationKey: ['todos', 'add'], onSuccessMock }, + }) + + fireEvent.click(rendered.getByRole('button', { name: /Mutate/i })) + await vi.advanceTimersByTimeAsync(10) + + expect(onSuccessMock).toHaveBeenCalledTimes(1) + expect(onSuccessMock.mock.calls[0]?.[3].mutationKey).toEqual([ + 'todos', + 'add', + ]) + }) + + it('should give mutationFn the same QueryClient instance via context', async () => { + const key = queryKey() + queryClient.setQueryData(key, 'tag-from-this-client') + + const rendered = render(MutationFnContext, { + props: { queryClient, queryKey: key }, + }) + + fireEvent.click(rendered.getByRole('button', { name: /Mutate/i })) + await vi.advanceTimersByTimeAsync(11) + + expect(rendered.getByText('data: tag-from-this-client')).toBeInTheDocument() + }) + + it('should let onSuccess invalidate queries via context.client without a useQueryClient() closure', async () => { + const key = queryKey() + queryClient.setQueryData(key, 'data') + + const rendered = render(InvalidateFromContext, { + props: { queryClient, queryKey: key }, + }) + + expect(queryClient.getQueryState(key)?.isInvalidated).toBe(false) + + fireEvent.click(rendered.getByRole('button', { name: /Mutate/i })) + await vi.advanceTimersByTimeAsync(10) + + expect(queryClient.getQueryState(key)?.isInvalidated).toBe(true) + }) + + it('should give a per-call onSuccess the same QueryClient instance via context', async () => { + const perCallOnSuccess = vi.fn() + + const rendered = render(PerCallSuccess, { + props: { queryClient, perCallOnSuccess }, + }) + + fireEvent.click(rendered.getByRole('button', { name: /Mutate/i })) + await vi.advanceTimersByTimeAsync(10) + + expect(perCallOnSuccess).toHaveBeenCalledTimes(1) + expect(perCallOnSuccess.mock.calls[0]?.[3].client).toBe(queryClient) + }) }) diff --git a/packages/svelte-query/tests/createMutation/createMutation.test-d.ts b/packages/svelte-query/tests/createMutation/createMutation.test-d.ts index a5a4bd8419a..cc1cfa6c1a0 100644 --- a/packages/svelte-query/tests/createMutation/createMutation.test-d.ts +++ b/packages/svelte-query/tests/createMutation/createMutation.test-d.ts @@ -1,7 +1,11 @@ import { describe, expectTypeOf, it } from 'vitest' import { QueryClient } from '@tanstack/query-core' import { createMutation } from '../../src/index.js' -import type { DefaultError } from '@tanstack/query-core' +import type { + DefaultError, + MutationFunctionContext, + MutationKey, +} from '@tanstack/query-core' import type { CreateMutationResult } from '../../src/types.js' describe('createMutation', () => { @@ -144,4 +148,56 @@ describe('createMutation', () => { expectTypeOf(mutation.data).toEqualTypeOf() }) + + it('should type context as the last argument for mutationFn and every hook-level callback', () => { + createMutation(() => ({ + mutationFn: (_vars: string, context) => { + expectTypeOf(context).toEqualTypeOf() + expectTypeOf(context.client).toEqualTypeOf() + return Promise.resolve('data') + }, + onMutate: (_variables, context) => { + expectTypeOf(context).toEqualTypeOf() + }, + onSuccess: (_data, _variables, _onMutateResult, context) => { + expectTypeOf(context).toEqualTypeOf() + }, + onError: (_error, _variables, _onMutateResult, context) => { + expectTypeOf(context).toEqualTypeOf() + }, + onSettled: (_data, _error, _variables, _onMutateResult, context) => { + expectTypeOf(context).toEqualTypeOf() + }, + })) + }) + + it('should type context as the last argument for every per-call mutate option', () => { + const mutation = createMutation(() => ({ + mutationFn: () => Promise.resolve('data'), + })) + + mutation.mutate(undefined, { + onSuccess: (_data, _variables, _onMutateResult, context) => { + expectTypeOf(context).toEqualTypeOf() + }, + onError: (_error, _variables, _onMutateResult, context) => { + expectTypeOf(context).toEqualTypeOf() + }, + onSettled: (_data, _error, _variables, _onMutateResult, context) => { + expectTypeOf(context).toEqualTypeOf() + }, + }) + }) + + it('should type context.mutationKey as MutationKey', () => { + createMutation(() => ({ + mutationKey: ['todos', 'add'] as const, + mutationFn: () => Promise.resolve('data'), + onSuccess: (_data, _variables, _onMutateResult, context) => { + expectTypeOf(context.mutationKey).toEqualTypeOf< + MutationKey | undefined + >() + }, + })) + }) })