feat: publish av-tools 0.1.0
This commit is contained in:
149
tests/unit/components/crop-overlay.test.tsx
Normal file
149
tests/unit/components/crop-overlay.test.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
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(
|
||||
<CropOverlay
|
||||
sourceWidth={100}
|
||||
sourceHeight={80}
|
||||
orientation={90}
|
||||
value={{ x: 10, y: 20, width: 30, height: 40 }}
|
||||
onChange={onChange}
|
||||
>
|
||||
<img src="blob:local-preview" alt="Local preview" />
|
||||
</CropOverlay>
|
||||
);
|
||||
|
||||
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(
|
||||
<CropOverlay
|
||||
sourceWidth={200}
|
||||
sourceHeight={100}
|
||||
value={{ x: 20, y: 10, width: 100, height: 60 }}
|
||||
onChange={onChange}
|
||||
>
|
||||
<div>Preview</div>
|
||||
</CropOverlay>
|
||||
);
|
||||
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(
|
||||
<CropOverlay
|
||||
sourceWidth={100}
|
||||
sourceHeight={80}
|
||||
orientation={90}
|
||||
value={{ x: 0, y: 0, width: 100, height: 80 }}
|
||||
onChange={onChange}
|
||||
>
|
||||
<div>Preview</div>
|
||||
</CropOverlay>
|
||||
);
|
||||
|
||||
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(
|
||||
<CropOverlay
|
||||
sourceWidth={101}
|
||||
sourceHeight={99}
|
||||
requireEvenDimensions
|
||||
value={{ x: 0, y: 0, width: 100, height: 98 }}
|
||||
onChange={onChange}
|
||||
>
|
||||
<div>Preview</div>
|
||||
</CropOverlay>
|
||||
);
|
||||
|
||||
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,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user