Files
flyer-crawler.projectium.com/src/hooks/useFlyerItems.test.ts
Torben Sorensen 2564df1c64
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 33m19s
get rid of localhost in tests - not a qualified URL - we'll see
2026-01-05 20:02:44 -08:00

181 lines
5.5 KiB
TypeScript

// src/hooks/useFlyerItems.test.ts
import { renderHook } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useFlyerItems } from './useFlyerItems';
import { useApiOnMount } from './useApiOnMount';
import * as apiClient from '../services/apiClient';
import { createMockFlyer, createMockFlyerItem } from '../tests/utils/mockFactories';
// Mock the underlying useApiOnMount hook to isolate the useFlyerItems hook's logic.
vi.mock('./useApiOnMount');
const mockedUseApiOnMount = vi.mocked(useApiOnMount);
describe('useFlyerItems Hook', () => {
const mockFlyer = createMockFlyer({
flyer_id: 123,
file_name: 'test-flyer.jpg',
image_url: 'https://example.com/test.jpg',
icon_url: 'https://example.com/icon.jpg',
checksum: 'abc',
valid_from: '2024-01-01',
valid_to: '2024-01-07',
item_count: 1,
store: {
store_id: 1,
name: 'Test Store',
},
});
const mockFlyerItems = [
createMockFlyerItem({
flyer_item_id: 1,
flyer_id: 123,
item: 'Apples',
price_display: '$1.99',
price_in_cents: 199,
quantity: '1lb',
}),
];
beforeEach(() => {
// Clear mock history before each test
vi.clearAllMocks();
});
it('should return initial state and not call useApiOnMount when flyer is null', () => {
// Arrange: Mock the return value of the inner hook.
mockedUseApiOnMount.mockReturnValue({
data: null,
loading: false,
error: null,
isRefetching: false,
reset: vi.fn(),
});
// Act: Render the hook with a null flyer.
const { result } = renderHook(() => useFlyerItems(null));
// Assert: Check that the hook returns the correct initial state.
expect(result.current.flyerItems).toEqual([]);
expect(result.current.isLoading).toBe(false);
expect(result.current.error).toBeNull();
// Assert: Check that useApiOnMount was called with `enabled: false`.
expect(mockedUseApiOnMount).toHaveBeenCalledWith(
expect.any(Function), // the wrapped fetcher function
[null], // dependencies array
{ enabled: false }, // options object
undefined, // flyer_id
);
});
it('should call useApiOnMount with enabled: true when a flyer is provided', () => {
mockedUseApiOnMount.mockReturnValue({
data: null,
loading: true,
error: null,
isRefetching: false,
reset: vi.fn(),
});
renderHook(() => useFlyerItems(mockFlyer));
// Assert: Check that useApiOnMount was called with the correct parameters.
expect(mockedUseApiOnMount).toHaveBeenCalledWith(
expect.any(Function),
[mockFlyer],
{ enabled: true },
mockFlyer.flyer_id,
);
});
it('should return isLoading: true when the inner hook is loading', () => {
mockedUseApiOnMount.mockReturnValue({
data: null,
loading: true,
error: null,
isRefetching: false,
reset: vi.fn(),
});
const { result } = renderHook(() => useFlyerItems(mockFlyer));
expect(result.current.isLoading).toBe(true);
});
it('should return flyerItems when the inner hook provides data', () => {
mockedUseApiOnMount.mockReturnValue({
data: { items: mockFlyerItems },
loading: false,
error: null,
isRefetching: false,
reset: vi.fn(),
});
const { result } = renderHook(() => useFlyerItems(mockFlyer));
expect(result.current.isLoading).toBe(false);
expect(result.current.flyerItems).toEqual(mockFlyerItems);
expect(result.current.error).toBeNull();
});
it('should return an error when the inner hook returns an error', () => {
const mockError = new Error('Failed to fetch');
mockedUseApiOnMount.mockReturnValue({
data: null,
loading: false,
error: mockError,
isRefetching: false,
reset: vi.fn(),
});
const { result } = renderHook(() => useFlyerItems(mockFlyer));
expect(result.current.isLoading).toBe(false);
expect(result.current.flyerItems).toEqual([]);
expect(result.current.error).toEqual(mockError);
});
describe('wrappedFetcher behavior', () => {
it('should reject if called with undefined flyerId', async () => {
// We need to trigger the hook to get access to the internal wrappedFetcher
mockedUseApiOnMount.mockReturnValue({
data: null,
loading: false,
error: null,
isRefetching: false,
reset: vi.fn(),
});
renderHook(() => useFlyerItems(mockFlyer));
// The first argument passed to useApiOnMount is the wrappedFetcher function
const wrappedFetcher = mockedUseApiOnMount.mock.calls[0][0];
// Verify the fetcher rejects when no ID is passed (which shouldn't happen in normal flow due to 'enabled')
await expect(wrappedFetcher(undefined)).rejects.toThrow(
'Cannot fetch items for an undefined flyer ID.',
);
});
it('should call apiClient.fetchFlyerItems when called with a valid ID', async () => {
mockedUseApiOnMount.mockReturnValue({
data: null,
loading: false,
error: null,
isRefetching: false,
reset: vi.fn(),
});
renderHook(() => useFlyerItems(mockFlyer));
const wrappedFetcher = mockedUseApiOnMount.mock.calls[0][0];
const mockResponse = new Response();
const mockedApiClient = vi.mocked(apiClient);
mockedApiClient.fetchFlyerItems.mockResolvedValue(mockResponse);
const response = await wrappedFetcher(123);
expect(mockedApiClient.fetchFlyerItems).toHaveBeenCalledWith(123);
expect(response).toBe(mockResponse);
});
});
});