164 lines
5.7 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|