This commit is contained in:
2025-11-30 19:37:47 -08:00
parent 0f0836e346
commit c3e3eb907c

View File

@@ -1,6 +1,6 @@
// src/components/FlyerCorrectionTool.test.tsx
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach, type Mocked } from 'vitest';
import { FlyerCorrectionTool } from './FlyerCorrectionTool';
import * as aiApiClient from '../services/aiApiClient';
@@ -30,7 +30,7 @@ describe('FlyerCorrectionTool', () => {
beforeEach(() => {
vi.clearAllMocks();
// Mock global fetch for fetching the image blob
// Mock global fetch for fetching the image blob inside the component
global.fetch = vi.fn(() =>
Promise.resolve(new Response(new Blob(['dummy-image-content'], { type: 'image/jpeg' })))
) as Mocked<typeof fetch>;
@@ -42,6 +42,8 @@ describe('FlyerCorrectionTool', () => {
clearRect: vi.fn(),
strokeRect: vi.fn(),
setLineDash: vi.fn(),
strokeStyle: '',
lineWidth: 0,
} as unknown as CanvasRenderingContext2D;
}
return null;
@@ -63,10 +65,8 @@ describe('FlyerCorrectionTool', () => {
it('should call onClose when the close button is clicked', () => {
render(<FlyerCorrectionTool {...defaultProps} />);
// The close button is an icon-only button inside the dialog header.
// We find the dialog, then query within it for the button to be more specific.
const dialog = screen.getByRole('dialog');
fireEvent.click(dialog.querySelector('button')!); // Assumes the first button in the dialog is the close button.
// Use the specific aria-label defined in the component
fireEvent.click(screen.getByRole('button', { name: /close correction tool/i }));
expect(defaultProps.onClose).toHaveBeenCalledTimes(1);
});
@@ -92,14 +92,18 @@ describe('FlyerCorrectionTool', () => {
it('should call rescanImageArea with correct parameters and show success', async () => {
mockedAiApiClient.rescanImageArea.mockResolvedValue(new Response(JSON.stringify({ text: 'Super Store' })));
render(<FlyerCorrectionTool {...defaultProps} />);
// Wait for the image fetch to complete to ensure 'imageFile' state is populated
await waitFor(() => expect(global.fetch).toHaveBeenCalledWith(defaultProps.imageUrl));
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 });
Object.defineProperty(image, 'naturalWidth', { value: 1000, configurable: true });
Object.defineProperty(image, 'naturalHeight', { value: 800, configurable: true });
Object.defineProperty(image, 'clientWidth', { value: 500, configurable: true });
Object.defineProperty(image, 'clientHeight', { value: 400, configurable: true });
// Simulate drawing a rectangle
fireEvent.mouseDown(canvas, { clientX: 10, clientY: 10 });
@@ -117,7 +121,8 @@ describe('FlyerCorrectionTool', () => {
// 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
// 10*2=20, 10*2=20, (60-10)*2=100, (30-10)*2=40
{ x: 20, y: 20, width: 100, height: 40 },
'store_name'
);
});
@@ -132,6 +137,10 @@ describe('FlyerCorrectionTool', () => {
it('should show an error notification if rescan fails', async () => {
mockedAiApiClient.rescanImageArea.mockRejectedValue(new Error('AI failed'));
render(<FlyerCorrectionTool {...defaultProps} />);
// Wait for image fetch
await waitFor(() => expect(global.fetch).toHaveBeenCalled());
const canvas = screen.getByRole('dialog').querySelector('canvas')!;
// Draw a selection to enable the button
@@ -146,10 +155,19 @@ describe('FlyerCorrectionTool', () => {
});
});
it('should show an error if trying to extract without a selection', () => {
it('should show an error if trying to extract without a selection', async () => {
render(<FlyerCorrectionTool {...defaultProps} />);
// Buttons are disabled, but we can simulate a click for robustness
fireEvent.click(screen.getByRole('button', { name: /extract store name/i }));
// Wait for image fetch to ensure we aren't failing on the missing file check
await waitFor(() => expect(global.fetch).toHaveBeenCalled());
// Buttons are disabled by default, but we force a click to test the handler's validation logic
// We need to find the button even if disabled
const button = screen.getByRole('button', { name: /extract store name/i });
// React testing library fires events even on disabled elements, mimicking some DOM behaviors or direct invocation
fireEvent.click(button);
expect(mockedNotifyError).toHaveBeenCalledWith('Please select an area on the image first.');
});
});
});