lots more tests !
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 7m32s
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 7m32s
This commit is contained in:
137
src/hooks/useWatchedItems.test.tsx
Normal file
137
src/hooks/useWatchedItems.test.tsx
Normal file
@@ -0,0 +1,137 @@
|
||||
// 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 './useAuth';
|
||||
import { useData } from './useData';
|
||||
import * as apiClient from '../services/apiClient';
|
||||
import type { MasterGroceryItem, User } from '../types';
|
||||
|
||||
// Mock the hooks that useWatchedItems depends on
|
||||
vi.mock('./useAuth');
|
||||
vi.mock('./useData');
|
||||
|
||||
// The apiClient is globally mocked in our test setup, so we just need to cast it
|
||||
const mockedUseAuth = vi.mocked(useAuth);
|
||||
const mockedUseData = vi.mocked(useData);
|
||||
const mockedApiClient = vi.mocked(apiClient);
|
||||
|
||||
const mockUser: User = { user_id: 'user-123', email: 'test@example.com' };
|
||||
const mockInitialItems: MasterGroceryItem[] = [
|
||||
{ master_grocery_item_id: 1, name: 'Milk', created_at: '' },
|
||||
{ master_grocery_item_id: 2, name: 'Bread', created_at: '' },
|
||||
];
|
||||
|
||||
describe('useWatchedItems Hook', () => {
|
||||
// Create a mock setter function that we can spy on
|
||||
const mockSetWatchedItems = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset all mocks before each test to ensure isolation
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Provide a default implementation for the mocked hooks
|
||||
mockedUseAuth.mockReturnValue({
|
||||
user: mockUser,
|
||||
} as any);
|
||||
|
||||
mockedUseData.mockReturnValue({
|
||||
watchedItems: mockInitialItems,
|
||||
setWatchedItems: mockSetWatchedItems,
|
||||
} as any);
|
||||
});
|
||||
|
||||
it('should initialize with the watched items from useData', () => {
|
||||
const { result } = renderHook(() => useWatchedItems());
|
||||
|
||||
expect(result.current.watchedItems).toEqual(mockInitialItems);
|
||||
expect(result.current.error).toBeNull();
|
||||
});
|
||||
|
||||
describe('addWatchedItem', () => {
|
||||
it('should call the API and update state on successful addition', async () => {
|
||||
const newItem: MasterGroceryItem = { master_grocery_item_id: 3, name: 'Cheese', created_at: '' };
|
||||
mockedApiClient.addWatchedItem.mockResolvedValue(new Response(JSON.stringify(newItem)));
|
||||
|
||||
const { result } = renderHook(() => useWatchedItems());
|
||||
|
||||
await act(async () => {
|
||||
await result.current.addWatchedItem('Cheese', 'Dairy');
|
||||
});
|
||||
|
||||
expect(mockedApiClient.addWatchedItem).toHaveBeenCalledWith('Cheese', 'Dairy');
|
||||
// Check that the global state setter was called with an updater function
|
||||
expect(mockSetWatchedItems).toHaveBeenCalledWith(expect.any(Function));
|
||||
|
||||
// To verify the logic inside the updater, we can call it directly
|
||||
const updater = mockSetWatchedItems.mock.calls[0][0];
|
||||
const newState = updater(mockInitialItems);
|
||||
|
||||
expect(newState).toHaveLength(3);
|
||||
expect(newState).toContainEqual(newItem);
|
||||
});
|
||||
|
||||
it('should set an error message if the API call fails', async () => {
|
||||
mockedApiClient.addWatchedItem.mockRejectedValue(new Error('API Error'));
|
||||
|
||||
const { result } = renderHook(() => useWatchedItems());
|
||||
|
||||
await act(async () => {
|
||||
await result.current.addWatchedItem('Failing Item', 'Error');
|
||||
});
|
||||
|
||||
expect(result.current.error).toBe('Could not add watched item: API Error');
|
||||
expect(mockSetWatchedItems).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeWatchedItem', () => {
|
||||
it('should call the API and update state on successful removal', async () => {
|
||||
const itemIdToRemove = 1;
|
||||
mockedApiClient.removeWatchedItem.mockResolvedValue(new Response(null, { status: 204 }));
|
||||
|
||||
const { result } = renderHook(() => useWatchedItems());
|
||||
|
||||
await act(async () => {
|
||||
await result.current.removeWatchedItem(itemIdToRemove);
|
||||
});
|
||||
|
||||
expect(mockedApiClient.removeWatchedItem).toHaveBeenCalledWith(itemIdToRemove);
|
||||
expect(mockSetWatchedItems).toHaveBeenCalledWith(expect.any(Function));
|
||||
|
||||
// Verify the logic inside the updater function
|
||||
const updater = mockSetWatchedItems.mock.calls[0][0];
|
||||
const newState = updater(mockInitialItems);
|
||||
|
||||
expect(newState).toHaveLength(1);
|
||||
expect(newState.some((item: MasterGroceryItem) => item.master_grocery_item_id === itemIdToRemove)).toBe(false);
|
||||
});
|
||||
|
||||
it('should set an error message if the API call fails', async () => {
|
||||
mockedApiClient.removeWatchedItem.mockRejectedValue(new Error('Deletion Failed'));
|
||||
|
||||
const { result } = renderHook(() => useWatchedItems());
|
||||
|
||||
await act(async () => {
|
||||
await result.current.removeWatchedItem(999);
|
||||
});
|
||||
|
||||
expect(result.current.error).toBe('Could not remove watched item: Deletion Failed');
|
||||
expect(mockSetWatchedItems).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not perform actions if the user is not authenticated', async () => {
|
||||
mockedUseAuth.mockReturnValue({ user: null } as any);
|
||||
|
||||
const { result } = renderHook(() => useWatchedItems());
|
||||
|
||||
await act(async () => {
|
||||
await result.current.addWatchedItem('Test', 'Category');
|
||||
await result.current.removeWatchedItem(1);
|
||||
});
|
||||
|
||||
expect(mockedApiClient.addWatchedItem).not.toHaveBeenCalled();
|
||||
expect(mockedApiClient.removeWatchedItem).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user