// 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(); 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({ui}); }; it('should render the welcome message when no flyer is selected', () => { renderInRouter( , ); 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( , ); 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( , ); 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( , ); // 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( , ); // The button is inside our updated MockFlyerDisplay const openButton = screen.getByTestId('mock-open-correction-tool'); fireEvent.click(openButton); expect(mockOnOpenCorrectionTool).toHaveBeenCalledTimes(1); }); }); });