m,ore TS fixes + tests
Some checks failed
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Failing after 2m40s
Some checks failed
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Failing after 2m40s
This commit is contained in:
147
src/components/FlyerCorrectionTool.test.tsx
Normal file
147
src/components/FlyerCorrectionTool.test.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
// src/components/FlyerCorrectionTool.test.tsx
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach, type Mocked } from 'vitest';
|
||||
import { FlyerCorrectionTool } from './FlyerCorrectionTool';
|
||||
import * as aiApiClient from '../services/aiApiClient';
|
||||
import { notifyError, notifySuccess } from '../services/notificationService';
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('../services/aiApiClient');
|
||||
vi.mock('../services/notificationService');
|
||||
vi.mock('../services/logger', () => ({
|
||||
logger: {
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const mockedAiApiClient = aiApiClient as Mocked<typeof aiApiClient>;
|
||||
const mockedNotifyError = notifyError as Mocked<typeof notifyError>;
|
||||
const mockedNotifySuccess = notifySuccess as Mocked<typeof notifySuccess>;
|
||||
|
||||
const defaultProps = {
|
||||
isOpen: true,
|
||||
onClose: vi.fn(),
|
||||
imageUrl: 'http://example.com/flyer.jpg',
|
||||
onDataExtracted: vi.fn(),
|
||||
};
|
||||
|
||||
describe('FlyerCorrectionTool', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Mock global fetch for fetching the image blob
|
||||
global.fetch = vi.fn(() =>
|
||||
Promise.resolve(new Response(new Blob(['dummy-image-content'], { type: 'image/jpeg' })))
|
||||
) as Mocked<typeof fetch>;
|
||||
|
||||
// Mock canvas methods for jsdom environment
|
||||
window.HTMLCanvasElement.prototype.getContext = () => ({
|
||||
clearRect: vi.fn(),
|
||||
strokeRect: vi.fn(),
|
||||
setLineDash: vi.fn(),
|
||||
} as any);
|
||||
});
|
||||
|
||||
it('should not render when isOpen is false', () => {
|
||||
const { container } = render(<FlyerCorrectionTool {...defaultProps} isOpen={false} />);
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it('should render correctly when isOpen is true', () => {
|
||||
render(<FlyerCorrectionTool {...defaultProps} />);
|
||||
expect(screen.getByRole('heading', { name: /flyer correction tool/i })).toBeInTheDocument();
|
||||
expect(screen.getByAltText('Flyer for correction')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /extract store name/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /extract sale dates/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onClose when the close button is clicked', () => {
|
||||
render(<FlyerCorrectionTool {...defaultProps} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /close/i }));
|
||||
expect(defaultProps.onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should have disabled extraction buttons initially', () => {
|
||||
render(<FlyerCorrectionTool {...defaultProps} />);
|
||||
expect(screen.getByRole('button', { name: /extract store name/i })).toBeDisabled();
|
||||
expect(screen.getByRole('button', { name: /extract sale dates/i })).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should enable extraction buttons after a selection is made', () => {
|
||||
render(<FlyerCorrectionTool {...defaultProps} />);
|
||||
const canvas = screen.getByRole('dialog').querySelector('canvas')!;
|
||||
|
||||
// Simulate drawing a rectangle
|
||||
fireEvent.mouseDown(canvas, { clientX: 10, clientY: 10 });
|
||||
fireEvent.mouseMove(canvas, { clientX: 100, clientY: 50 });
|
||||
fireEvent.mouseUp(canvas);
|
||||
|
||||
expect(screen.getByRole('button', { name: /extract store name/i })).toBeEnabled();
|
||||
expect(screen.getByRole('button', { name: /extract sale dates/i })).toBeEnabled();
|
||||
});
|
||||
|
||||
it('should call rescanImageArea with correct parameters and show success', async () => {
|
||||
mockedAiApiClient.rescanImageArea.mockResolvedValue(new Response(JSON.stringify({ text: 'Super Store' })));
|
||||
render(<FlyerCorrectionTool {...defaultProps} />);
|
||||
const canvas = screen.getByRole('dialog').querySelector('canvas')!;
|
||||
const image = screen.getByAltText('Flyer for correction');
|
||||
|
||||
// Mock image dimensions for coordinate scaling
|
||||
Object.defineProperty(image, 'naturalWidth', { value: 1000 });
|
||||
Object.defineProperty(image, 'naturalHeight', { value: 800 });
|
||||
Object.defineProperty(image, 'clientWidth', { value: 500 });
|
||||
Object.defineProperty(image, 'clientHeight', { value: 400 });
|
||||
|
||||
// Simulate drawing a rectangle
|
||||
fireEvent.mouseDown(canvas, { clientX: 10, clientY: 10 });
|
||||
fireEvent.mouseMove(canvas, { clientX: 60, clientY: 30 });
|
||||
fireEvent.mouseUp(canvas);
|
||||
|
||||
// Click the extract button
|
||||
fireEvent.click(screen.getByRole('button', { name: /extract store name/i }));
|
||||
|
||||
// Check for loading state
|
||||
expect(await screen.findByText('Processing...')).toBeInTheDocument();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockedAiApiClient.rescanImageArea).toHaveBeenCalledTimes(1);
|
||||
// Check that coordinates were scaled correctly (e.g., 500 -> 1000 is a 2x scale)
|
||||
expect(mockedAiApiClient.rescanImageArea).toHaveBeenCalledWith(
|
||||
expect.any(File),
|
||||
{ x: 20, y: 20, width: 100, height: 40 }, // 10*2, 10*2, (60-10)*2, (30-10)*2
|
||||
'store_name'
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockedNotifySuccess).toHaveBeenCalledWith('Extracted: Super Store');
|
||||
expect(defaultProps.onDataExtracted).toHaveBeenCalledWith('store_name', 'Super Store');
|
||||
expect(defaultProps.onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should show an error notification if rescan fails', async () => {
|
||||
mockedAiApiClient.rescanImageArea.mockRejectedValue(new Error('AI failed'));
|
||||
render(<FlyerCorrectionTool {...defaultProps} />);
|
||||
const canvas = screen.getByRole('dialog').querySelector('canvas')!;
|
||||
|
||||
// Draw a selection to enable the button
|
||||
fireEvent.mouseDown(canvas, { clientX: 10, clientY: 10 });
|
||||
fireEvent.mouseMove(canvas, { clientX: 100, clientY: 50 });
|
||||
fireEvent.mouseUp(canvas);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /extract sale dates/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockedNotifyError).toHaveBeenCalledWith('AI failed');
|
||||
});
|
||||
});
|
||||
|
||||
it('should show an error if trying to extract without a selection', () => {
|
||||
render(<FlyerCorrectionTool {...defaultProps} />);
|
||||
// Buttons are disabled, but we can simulate a click for robustness
|
||||
fireEvent.click(screen.getByRole('button', { name: /extract store name/i }));
|
||||
expect(mockedNotifyError).toHaveBeenCalledWith('Please select an area on the image first.');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user