// src/hooks/mutations/useAddShoppingListItemMutation.test.tsx import { renderHook, waitFor } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { ReactNode } from 'react'; import { useAddShoppingListItemMutation } from './useAddShoppingListItemMutation'; import * as apiClient from '../../services/apiClient'; import * as notificationService from '../../services/notificationService'; vi.mock('../../services/apiClient'); vi.mock('../../services/notificationService'); const mockedApiClient = vi.mocked(apiClient); const mockedNotifications = vi.mocked(notificationService); describe('useAddShoppingListItemMutation', () => { let queryClient: QueryClient; const wrapper = ({ children }: { children: ReactNode }) => ( {children} ); beforeEach(() => { vi.resetAllMocks(); queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }); }); it('should add a master item to shopping list successfully', async () => { const mockResponse = { shopping_list_item_id: 1, master_item_id: 42 }; mockedApiClient.addShoppingListItem.mockResolvedValue({ ok: true, json: () => Promise.resolve(mockResponse), } as Response); const { result } = renderHook(() => useAddShoppingListItemMutation(), { wrapper }); result.current.mutate({ listId: 1, item: { masterItemId: 42 } }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(mockedApiClient.addShoppingListItem).toHaveBeenCalledWith(1, { masterItemId: 42 }); expect(mockedNotifications.notifySuccess).toHaveBeenCalledWith('Item added to shopping list'); }); it('should add a custom item to shopping list successfully', async () => { const mockResponse = { shopping_list_item_id: 2, custom_item_name: 'Special Milk' }; mockedApiClient.addShoppingListItem.mockResolvedValue({ ok: true, json: () => Promise.resolve(mockResponse), } as Response); const { result } = renderHook(() => useAddShoppingListItemMutation(), { wrapper }); result.current.mutate({ listId: 1, item: { customItemName: 'Special Milk' } }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(mockedApiClient.addShoppingListItem).toHaveBeenCalledWith(1, { customItemName: 'Special Milk' }); }); it('should invalidate shopping-lists query on success', async () => { mockedApiClient.addShoppingListItem.mockResolvedValue({ ok: true, json: () => Promise.resolve({}), } as Response); const invalidateQueriesSpy = vi.spyOn(queryClient, 'invalidateQueries'); const { result } = renderHook(() => useAddShoppingListItemMutation(), { wrapper }); result.current.mutate({ listId: 1, item: { masterItemId: 42 } }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['shopping-lists'] }); }); it('should handle API error with error message', async () => { mockedApiClient.addShoppingListItem.mockResolvedValue({ ok: false, status: 400, json: () => Promise.resolve({ message: 'Item already exists' }), } as Response); const { result } = renderHook(() => useAddShoppingListItemMutation(), { wrapper }); result.current.mutate({ listId: 1, item: { masterItemId: 42 } }); await waitFor(() => expect(result.current.isError).toBe(true)); expect(result.current.error?.message).toBe('Item already exists'); expect(mockedNotifications.notifyError).toHaveBeenCalledWith('Item already exists'); }); it('should handle API error without message', async () => { mockedApiClient.addShoppingListItem.mockResolvedValue({ ok: false, status: 500, json: () => Promise.reject(new Error('Parse error')), } as Response); const { result } = renderHook(() => useAddShoppingListItemMutation(), { wrapper }); result.current.mutate({ listId: 1, item: { masterItemId: 42 } }); await waitFor(() => expect(result.current.isError).toBe(true)); expect(result.current.error?.message).toBe('Request failed with status 500'); expect(mockedNotifications.notifyError).toHaveBeenCalledWith('Request failed with status 500'); }); it('should handle network error', async () => { mockedApiClient.addShoppingListItem.mockRejectedValue(new Error('Network error')); const { result } = renderHook(() => useAddShoppingListItemMutation(), { wrapper }); result.current.mutate({ listId: 1, item: { masterItemId: 42 } }); await waitFor(() => expect(result.current.isError).toBe(true)); expect(result.current.error?.message).toBe('Network error'); }); });