143 lines
4.8 KiB
TypeScript
143 lines
4.8 KiB
TypeScript
// src/components/MyDealsPage.test.tsx
|
|
import React from 'react';
|
|
import { render, screen, waitFor } from '@testing-library/react';
|
|
import { describe, it, expect, vi, beforeEach, type Mocked } from 'vitest';
|
|
import MyDealsPage from './MyDealsPage';
|
|
import * as apiClient from '../services/apiClient';
|
|
import { WatchedItemDeal } from '../types';
|
|
import { logger } from '../services/logger.client';
|
|
import { createMockWatchedItemDeal } from '../tests/utils/mockFactories';
|
|
|
|
// Mock the apiClient. The component now directly uses `fetchBestSalePrices`.
|
|
// By mocking the entire module, we can control the behavior of `fetchBestSalePrices`
|
|
// for our tests.
|
|
vi.mock('../services/apiClient');
|
|
const mockedApiClient = apiClient as Mocked<typeof apiClient>;
|
|
|
|
// Mock the logger
|
|
vi.mock('../services/logger.client', () => ({
|
|
logger: {
|
|
error: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
// Mock lucide-react icons to prevent rendering errors in the test environment
|
|
vi.mock('lucide-react', () => ({
|
|
AlertCircle: () => <div data-testid="alert-circle-icon" />,
|
|
Tag: () => <div data-testid="tag-icon" />,
|
|
Store: () => <div data-testid="store-icon" />,
|
|
Calendar: () => <div data-testid="calendar-icon" />,
|
|
}));
|
|
|
|
describe('MyDealsPage', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('should display a loading message initially', () => {
|
|
// Mock a pending promise
|
|
mockedApiClient.fetchBestSalePrices.mockReturnValue(new Promise(() => {}));
|
|
render(<MyDealsPage />);
|
|
expect(screen.getByText('Loading your deals...')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should display an error message if the API call fails', async () => {
|
|
mockedApiClient.fetchBestSalePrices.mockResolvedValue(
|
|
new Response(null, { status: 500, statusText: 'Server Error' }),
|
|
);
|
|
render(<MyDealsPage />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Error')).toBeInTheDocument();
|
|
expect(
|
|
screen.getByText('Failed to fetch deals. Please try again later.'),
|
|
).toBeInTheDocument();
|
|
});
|
|
expect(logger.error).toHaveBeenCalledWith(
|
|
'Error fetching watched item deals:',
|
|
'Failed to fetch deals. Please try again later.',
|
|
);
|
|
});
|
|
|
|
it('should handle network errors and log them', async () => {
|
|
const networkError = new Error('Network connection failed');
|
|
mockedApiClient.fetchBestSalePrices.mockRejectedValue(networkError);
|
|
render(<MyDealsPage />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Error')).toBeInTheDocument();
|
|
expect(screen.getByText('Network connection failed')).toBeInTheDocument();
|
|
});
|
|
expect(logger.error).toHaveBeenCalledWith(
|
|
'Error fetching watched item deals:',
|
|
'Network connection failed',
|
|
);
|
|
});
|
|
|
|
it('should handle unknown errors and log them', async () => {
|
|
// Mock a rejection with a non-Error object (e.g., a string) to trigger the fallback error message
|
|
mockedApiClient.fetchBestSalePrices.mockRejectedValue('Unknown failure');
|
|
render(<MyDealsPage />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Error')).toBeInTheDocument();
|
|
expect(screen.getByText('An unknown error occurred.')).toBeInTheDocument();
|
|
});
|
|
expect(logger.error).toHaveBeenCalledWith(
|
|
'Error fetching watched item deals:',
|
|
'An unknown error occurred.',
|
|
);
|
|
});
|
|
|
|
it('should display a message when no deals are found', async () => {
|
|
mockedApiClient.fetchBestSalePrices.mockResolvedValue(
|
|
new Response(JSON.stringify([]), {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}),
|
|
);
|
|
render(<MyDealsPage />);
|
|
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.getByText('No deals found for your watched items right now.'),
|
|
).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should render the list of deals on successful fetch', async () => {
|
|
const mockDeals: WatchedItemDeal[] = [
|
|
createMockWatchedItemDeal({
|
|
master_item_id: 1,
|
|
item_name: 'Organic Bananas',
|
|
best_price_in_cents: 99,
|
|
store_name: 'Green Grocer',
|
|
flyer_id: 101,
|
|
valid_to: '2024-10-20',
|
|
}),
|
|
createMockWatchedItemDeal({
|
|
master_item_id: 2,
|
|
item_name: 'Almond Milk',
|
|
best_price_in_cents: 349,
|
|
store_name: 'SuperMart',
|
|
flyer_id: 102,
|
|
valid_to: '2024-10-22',
|
|
}),
|
|
];
|
|
mockedApiClient.fetchBestSalePrices.mockResolvedValue(
|
|
new Response(JSON.stringify(mockDeals), {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}),
|
|
);
|
|
|
|
render(<MyDealsPage />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Organic Bananas')).toBeInTheDocument();
|
|
expect(screen.getByText('$0.99')).toBeInTheDocument();
|
|
expect(screen.getByText('Almond Milk')).toBeInTheDocument();
|
|
expect(screen.getByText('$3.49')).toBeInTheDocument();
|
|
expect(screen.getByText('Green Grocer')).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|