import { cleanup, fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { CropOverlay } from '../../../src/components/CropOverlay'; describe('CropOverlay', () => { afterEach(cleanup); it('maps a clockwise-oriented preview back to encoded source pixels', () => { const onChange = vi.fn(); const { container } = render( Local preview ); const rectangle = container.querySelector('.crop-editor__rectangle'); expect(rectangle).toHaveStyle({ left: '25%', top: '10%', width: '50%', height: '30%', }); fireEvent.keyDown( screen.getByRole('button', { name: 'Move crop rectangle' }), { key: 'ArrowRight' } ); expect(onChange).toHaveBeenLastCalledWith({ x: 10, y: 19, width: 30, height: 40, }); }); it('supports pointer dragging with bounded percentage-to-pixel math', () => { const onChange = vi.fn(); const { container } = render(
Preview
); const stage = container.querySelector('.crop-editor__stage'); if (!(stage instanceof HTMLElement)) throw new Error('Missing crop stage'); vi.spyOn(stage, 'getBoundingClientRect').mockReturnValue({ x: 0, y: 0, left: 0, top: 0, right: 200, bottom: 100, width: 200, height: 100, toJSON: () => ({}), }); const move = screen.getByRole('button', { name: 'Move crop rectangle' }); fireEvent.pointerDown(move, { pointerId: 7, clientX: 10, clientY: 10, }); fireEvent.pointerMove(move, { pointerId: 7, clientX: 60, clientY: 30, }); fireEvent.pointerUp(move, { pointerId: 7 }); expect(onChange).toHaveBeenLastCalledWith({ x: 70, y: 30, width: 100, height: 60, }); }); it('locks common display ratios without confusing rotated dimensions', async () => { const user = userEvent.setup(); const onChange = vi.fn(); render(
Preview
); await user.selectOptions( screen.getByRole('combobox', { name: 'Common ratio' }), '16:9' ); const crop = onChange.mock.lastCall?.[0] as { width: number; height: number } | undefined; expect(crop).toBeDefined(); expect((crop?.width ?? 0) / (crop?.height ?? 1)).toBeCloseTo(9 / 16, 2); expect( screen.getByRole('checkbox', { name: 'Lock aspect ratio' }) ).toBeChecked(); }); it('rejects invalid numeric edits and emits even reset bounds when required', async () => { const user = userEvent.setup(); const onChange = vi.fn(); render(
Preview
); await user.clear(screen.getByRole('spinbutton', { name: 'Width' })); expect(screen.getByRole('alert')).toHaveTextContent( 'All crop fields are required.' ); expect(onChange).not.toHaveBeenCalled(); await user.click(screen.getByRole('button', { name: 'Reset crop' })); expect(onChange).toHaveBeenLastCalledWith({ x: 0, y: 0, width: 100, height: 98, }); }); });