Files
flyer-crawler.projectium.com/src/pages/HomePage.test.tsx

164 lines
5.7 KiB
TypeScript

// src/pages/HomePage.test.tsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { MemoryRouter, useOutletContext } from 'react-router-dom';
import { HomePage } from './HomePage';
import { createMockFlyer, createMockFlyerItem } from '../tests/utils/mockFactories';
import type { Flyer, FlyerItem } from '../types';
// Unmock the component to test the real implementation
vi.unmock('./HomePage');
// Mock child components to isolate the HomePage logic
vi.mock('../features/flyer/FlyerDisplay', async () => {
const { MockFlyerDisplay } = await import('../tests/utils/componentMocks');
return { FlyerDisplay: MockFlyerDisplay };
});
vi.mock('../features/flyer/AnalysisPanel', async () => {
const { MockAnalysisPanel } = await import('../tests/utils/componentMocks');
return { AnalysisPanel: MockAnalysisPanel };
});
// Mock the useOutletContext hook from react-router-dom
vi.mock('react-router-dom', async (importOriginal) => {
const original = await importOriginal<typeof import('react-router-dom')>();
return {
...original,
useOutletContext: vi.fn(),
};
});
// Mock ExtractedDataTable separately to use the imported props interface
import { ExtractedDataTable } from '../features/flyer/ExtractedDataTable';
vi.mock('../features/flyer/ExtractedDataTable', async () => {
const { MockExtractedDataTable } = await import('../tests/utils/componentMocks');
// Wrap the imported mock component in vi.fn() to allow spying on its calls.
return { ExtractedDataTable: vi.fn(MockExtractedDataTable) };
});
const mockedUseOutletContext = vi.mocked(useOutletContext);
describe('HomePage Component', () => {
const mockOnOpenCorrectionTool = vi.fn();
// Define a default context that can be overridden in specific tests
const mockContext = {
totalActiveItems: 10,
masterItems: [],
addWatchedItem: vi.fn(),
shoppingLists: [],
activeListId: null,
addItemToList: vi.fn(),
};
beforeEach(() => {
vi.clearAllMocks();
// Provide the default mock implementation for the context hook
mockedUseOutletContext.mockReturnValue(mockContext);
});
const renderInRouter = (ui: React.ReactElement) => {
return render(<MemoryRouter>{ui}</MemoryRouter>);
};
it('should render the welcome message when no flyer is selected', () => {
renderInRouter(
<HomePage
selectedFlyer={null}
flyerItems={[]}
onOpenCorrectionTool={mockOnOpenCorrectionTool}
/>,
);
expect(screen.getByRole('heading', { name: /welcome to flyer crawler/i })).toBeInTheDocument();
expect(screen.getByText(/upload a new grocery flyer to begin/i)).toBeInTheDocument();
expect(screen.queryByTestId('flyer-display')).not.toBeInTheDocument();
});
describe('when a flyer is selected', () => {
const mockFlyer: Flyer = createMockFlyer({
flyer_id: 1,
image_url: 'http://example.com/flyer.jpg',
});
it('should render FlyerDisplay but not data tables if there are no flyer items', () => {
renderInRouter(
<HomePage
selectedFlyer={mockFlyer}
flyerItems={[]}
onOpenCorrectionTool={mockOnOpenCorrectionTool}
/>,
);
const flyerDisplay = screen.getByTestId('flyer-display');
expect(flyerDisplay).toBeInTheDocument();
expect(flyerDisplay).toHaveAttribute('data-image-url', mockFlyer.image_url);
expect(screen.queryByTestId('extracted-data-table')).not.toBeInTheDocument();
expect(screen.queryByTestId('analysis-panel')).not.toBeInTheDocument();
});
it('should render all components when there are flyer items', () => {
const mockItems: FlyerItem[] = [createMockFlyerItem({ flyer_item_id: 101 })];
renderInRouter(
<HomePage
selectedFlyer={mockFlyer}
flyerItems={mockItems}
onOpenCorrectionTool={mockOnOpenCorrectionTool}
/>,
);
expect(screen.getByTestId('flyer-display')).toBeInTheDocument();
expect(screen.getByTestId('extracted-data-table')).toBeInTheDocument();
expect(screen.getByTestId('analysis-panel')).toBeInTheDocument();
});
it('should pass correct props to ExtractedDataTable', () => {
const mockItems: FlyerItem[] = [
createMockFlyerItem({ flyer_item_id: 101 }),
createMockFlyerItem({ flyer_item_id: 102 }),
];
renderInRouter(
<HomePage
selectedFlyer={mockFlyer}
flyerItems={mockItems}
onOpenCorrectionTool={mockOnOpenCorrectionTool}
/>,
);
// The mock component renders the number of items it received
const dataTable = screen.getByTestId('extracted-data-table');
expect(dataTable).toHaveTextContent('2 items');
// We can also check the props passed to the mock component by inspecting the mock itself.
// This is a more robust way to test prop passing.
const ExtractedDataTableMock = vi.mocked(ExtractedDataTable);
const props =
ExtractedDataTableMock.mock.calls[ExtractedDataTableMock.mock.calls.length - 1][0];
expect(props.items).toEqual(mockItems);
});
it('should call onOpenCorrectionTool when the correction tool button is clicked', () => {
const mockItems: FlyerItem[] = [createMockFlyerItem({ flyer_item_id: 101 })];
renderInRouter(
<HomePage
selectedFlyer={mockFlyer}
flyerItems={mockItems}
onOpenCorrectionTool={mockOnOpenCorrectionTool}
/>,
);
// The button is inside our updated MockFlyerDisplay
const openButton = screen.getByTestId('mock-open-correction-tool');
fireEvent.click(openButton);
expect(mockOnOpenCorrectionTool).toHaveBeenCalledTimes(1);
});
});
});