Files
flyer-crawler.projectium.com/src/hooks/useWatchedItems.test.tsx
Torben Sorensen 46c1e56b14
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 46s
progress enforcing adr-0005
2026-01-08 21:40:20 -08:00

202 lines
6.1 KiB
TypeScript

// src/hooks/useWatchedItems.test.tsx
import { renderHook, act } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useWatchedItems } from './useWatchedItems';
import { useAuth } from '../hooks/useAuth';
import { useUserData } from '../hooks/useUserData';
import { useAddWatchedItemMutation, useRemoveWatchedItemMutation } from './mutations';
import type { User } from '../types';
import {
createMockMasterGroceryItem,
createMockUser,
createMockUserProfile,
} from '../tests/utils/mockFactories';
// Mock the hooks that useWatchedItems depends on
vi.mock('../hooks/useAuth');
vi.mock('../hooks/useUserData');
vi.mock('./mutations', () => ({
useAddWatchedItemMutation: vi.fn(),
useRemoveWatchedItemMutation: vi.fn(),
}));
const mockedUseAuth = vi.mocked(useAuth);
const mockedUseUserData = vi.mocked(useUserData);
const mockedUseAddWatchedItemMutation = vi.mocked(useAddWatchedItemMutation);
const mockedUseRemoveWatchedItemMutation = vi.mocked(useRemoveWatchedItemMutation);
const mockUser: User = createMockUser({ user_id: 'user-123', email: 'test@example.com' });
const mockInitialItems = [
createMockMasterGroceryItem({ master_grocery_item_id: 1, name: 'Milk' }),
createMockMasterGroceryItem({ master_grocery_item_id: 2, name: 'Bread' }),
];
describe('useWatchedItems Hook', () => {
const mockMutateAsync = vi.fn();
const mockAddMutation = {
mutateAsync: mockMutateAsync,
mutate: vi.fn(),
isPending: false,
error: null,
isError: false,
isSuccess: false,
isIdle: true,
};
const mockRemoveMutation = {
mutateAsync: mockMutateAsync,
mutate: vi.fn(),
isPending: false,
error: null,
isError: false,
isSuccess: false,
isIdle: true,
};
beforeEach(() => {
vi.resetAllMocks();
// Mock TanStack Query mutation hooks
mockedUseAddWatchedItemMutation.mockReturnValue(mockAddMutation as any);
mockedUseRemoveWatchedItemMutation.mockReturnValue(mockRemoveMutation as any);
// Provide default implementation for auth
mockedUseAuth.mockReturnValue({
userProfile: createMockUserProfile({ user: mockUser }),
authStatus: 'AUTHENTICATED',
isLoading: false,
login: vi.fn(),
logout: vi.fn(),
updateProfile: vi.fn(),
});
// Provide default implementation for user data (no more setters!)
mockedUseUserData.mockReturnValue({
watchedItems: mockInitialItems,
shoppingLists: [],
isLoading: false,
error: null,
});
});
it('should initialize with the watched items from useData', () => {
const { result } = renderHook(() => useWatchedItems());
expect(result.current.watchedItems).toEqual(mockInitialItems);
expect(result.current.error).toBeNull();
});
it('should use TanStack Query mutation hooks', () => {
renderHook(() => useWatchedItems());
// Verify that the mutation hooks were called
expect(mockedUseAddWatchedItemMutation).toHaveBeenCalled();
expect(mockedUseRemoveWatchedItemMutation).toHaveBeenCalled();
});
describe('addWatchedItem', () => {
it('should call the mutation with correct parameters', async () => {
mockMutateAsync.mockResolvedValue({});
const { result } = renderHook(() => useWatchedItems());
await act(async () => {
await result.current.addWatchedItem('Cheese', 'Dairy');
});
// Verify mutation was called with correct parameters
expect(mockMutateAsync).toHaveBeenCalledWith({
itemName: 'Cheese',
category: 'Dairy',
});
});
it('should expose error from mutation', () => {
const errorMutation = {
...mockAddMutation,
error: new Error('API Error'),
};
mockedUseAddWatchedItemMutation.mockReturnValue(errorMutation as any);
const { result } = renderHook(() => useWatchedItems());
expect(result.current.error).toBe('API Error');
});
it('should handle mutation errors gracefully', async () => {
mockMutateAsync.mockRejectedValue(new Error('Failed to add'));
const { result } = renderHook(() => useWatchedItems());
await act(async () => {
await result.current.addWatchedItem('Failing Item', 'Error');
});
// Should not throw - error is caught and logged
expect(mockMutateAsync).toHaveBeenCalled();
});
});
describe('removeWatchedItem', () => {
it('should call the mutation with correct parameters', async () => {
mockMutateAsync.mockResolvedValue({});
const { result } = renderHook(() => useWatchedItems());
await act(async () => {
await result.current.removeWatchedItem(1);
});
// Verify mutation was called with correct parameters
expect(mockMutateAsync).toHaveBeenCalledWith({
masterItemId: 1,
});
});
it('should expose error from remove mutation', () => {
const errorMutation = {
...mockRemoveMutation,
error: new Error('Deletion Failed'),
};
mockedUseRemoveWatchedItemMutation.mockReturnValue(errorMutation as any);
const { result } = renderHook(() => useWatchedItems());
expect(result.current.error).toBe('Deletion Failed');
});
it('should handle mutation errors gracefully', async () => {
mockMutateAsync.mockRejectedValue(new Error('Failed to remove'));
const { result } = renderHook(() => useWatchedItems());
await act(async () => {
await result.current.removeWatchedItem(999);
});
// Should not throw - error is caught and logged
expect(mockMutateAsync).toHaveBeenCalled();
});
});
it('should not perform actions if the user is not authenticated', async () => {
mockedUseAuth.mockReturnValue({
userProfile: null,
authStatus: 'SIGNED_OUT',
isLoading: false,
login: vi.fn(),
logout: vi.fn(),
updateProfile: vi.fn(),
});
const { result } = renderHook(() => useWatchedItems());
await act(async () => {
await result.current.addWatchedItem('Test', 'Category');
await result.current.removeWatchedItem(1);
});
// Mutations should not be called when user is not authenticated
expect(mockMutateAsync).not.toHaveBeenCalled();
});
});