m,ore TS fixes + tests
Some checks failed
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Failing after 2m40s

This commit is contained in:
2025-11-29 15:43:39 -08:00
parent bb2ad6ce1a
commit 399243c6a0
17 changed files with 500 additions and 34 deletions

View 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.');
});
});