diff --git a/src/components/FlyerCorrectionTool.test.tsx b/src/components/FlyerCorrectionTool.test.tsx index 536c7ccc..559b1d31 100644 --- a/src/components/FlyerCorrectionTool.test.tsx +++ b/src/components/FlyerCorrectionTool.test.tsx @@ -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; @@ -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(); - // 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(); + + // 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(); + + // 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(); - // 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.'); }); -}); +}); \ No newline at end of file