071faa482c
纳入 AGC Cargo workspace 的统一 rustfmt 检查与格式化入口 完成项目 TypeScript/Prettier 与 Rust 全量格式化 修复 Pingora expected executable 门禁的空白敏感误报 同步开发运维文档与 AGC skill pack 格式化忽略规则
123 lines
3.6 KiB
TypeScript
123 lines
3.6 KiB
TypeScript
/* @vitest-environment jsdom */
|
||
|
||
import { fireEvent, render, screen } from '@testing-library/react';
|
||
import { expect, test, vi } from 'vitest';
|
||
|
||
import { SquareImageCropModal } from './SquareImageCropModal';
|
||
|
||
function renderSquareImageCropModal(
|
||
overrideProps: Partial<Parameters<typeof SquareImageCropModal>[0]> = {},
|
||
) {
|
||
const onCropRectChange = vi.fn();
|
||
const onClose = vi.fn();
|
||
const onSubmit = vi.fn();
|
||
|
||
render(
|
||
<SquareImageCropModal
|
||
source="data:image/png;base64,preview"
|
||
imageSize={{ width: 800, height: 600 }}
|
||
cropRect={{ x: 100, y: 0, size: 600 }}
|
||
labels={{
|
||
title: '裁剪拼图图片',
|
||
close: '关闭拼图图片裁剪',
|
||
editor: '拼图图片裁剪操作区',
|
||
previewAlt: '拼图图片裁剪预览',
|
||
cancel: '取消',
|
||
submit: '应用',
|
||
saving: '裁剪中',
|
||
}}
|
||
onCropRectChange={onCropRectChange}
|
||
onClose={onClose}
|
||
onSubmit={onSubmit}
|
||
{...overrideProps}
|
||
/>,
|
||
);
|
||
|
||
return { onCropRectChange, onClose, onSubmit };
|
||
}
|
||
|
||
test('reuses unified modal shell while keeping crop close semantics disabled on backdrop and escape', () => {
|
||
const { onClose } = renderSquareImageCropModal();
|
||
|
||
const dialog = screen.getByRole('dialog', { name: '裁剪拼图图片' });
|
||
|
||
fireEvent.click(dialog.parentElement as HTMLElement);
|
||
fireEvent.keyDown(window, { key: 'Escape' });
|
||
|
||
expect(onClose).not.toHaveBeenCalled();
|
||
expect(
|
||
screen.getByRole('button', { name: '关闭拼图图片裁剪' }).className,
|
||
).toContain('platform-profile-icon-button');
|
||
expect(
|
||
screen.getByRole('button', { name: '关闭拼图图片裁剪' }).textContent,
|
||
).toContain('×');
|
||
});
|
||
|
||
test('keeps shared footer actions and saving disabled state', () => {
|
||
const { onClose, onSubmit } = renderSquareImageCropModal({ isSaving: true });
|
||
|
||
const cancelButton = screen.getByRole('button', { name: '取消' });
|
||
const submitButton = screen.getByRole('button', { name: '裁剪中' });
|
||
|
||
expect(cancelButton.className).toContain('platform-button--secondary');
|
||
expect(submitButton.hasAttribute('disabled')).toBe(true);
|
||
|
||
fireEvent.click(cancelButton);
|
||
fireEvent.click(submitButton);
|
||
|
||
expect(onClose).toHaveBeenCalledTimes(1);
|
||
expect(onSubmit).not.toHaveBeenCalled();
|
||
});
|
||
|
||
test('keeps pointer drag flow wired through the crop surface', () => {
|
||
const { onCropRectChange } = renderSquareImageCropModal();
|
||
const preview = screen.getByLabelText('拼图图片裁剪操作区');
|
||
const moveHandle = preview.querySelector('[class*="cursor-grab"]');
|
||
|
||
expect(moveHandle).toBeTruthy();
|
||
|
||
Object.defineProperty(preview, 'getBoundingClientRect', {
|
||
value: () => ({
|
||
width: 400,
|
||
height: 300,
|
||
top: 0,
|
||
left: 0,
|
||
right: 400,
|
||
bottom: 300,
|
||
x: 0,
|
||
y: 0,
|
||
toJSON: () => ({}),
|
||
}),
|
||
});
|
||
|
||
const setPointerCapture = vi.fn();
|
||
const releasePointerCapture = vi.fn();
|
||
Object.defineProperty(moveHandle as HTMLDivElement, 'setPointerCapture', {
|
||
value: setPointerCapture,
|
||
});
|
||
Object.defineProperty(moveHandle as HTMLDivElement, 'releasePointerCapture', {
|
||
value: releasePointerCapture,
|
||
});
|
||
|
||
fireEvent.pointerDown(moveHandle as Element, {
|
||
pointerId: 1,
|
||
clientX: 100,
|
||
clientY: 80,
|
||
});
|
||
fireEvent.pointerMove(moveHandle as Element, {
|
||
pointerId: 1,
|
||
clientX: 140,
|
||
clientY: 120,
|
||
});
|
||
fireEvent.pointerUp(moveHandle as Element, {
|
||
pointerId: 1,
|
||
});
|
||
|
||
expect(setPointerCapture).toHaveBeenCalledTimes(1);
|
||
expect(releasePointerCapture).toHaveBeenCalledTimes(1);
|
||
expect(onCropRectChange).toHaveBeenCalledTimes(1);
|
||
expect(onCropRectChange.mock.calls[0]?.[0]).toMatchObject({
|
||
size: 600,
|
||
});
|
||
});
|