feat(ai-character): 관리자 인증 셸 구현

This commit is contained in:
Yu Sung
2026-07-27 15:04:03 +09:00
parent ba43dd829e
commit 5356bd1e6a
82 changed files with 6017 additions and 18 deletions

View File

@@ -0,0 +1,68 @@
import { fireEvent, render, screen } from "@testing-library/react";
import { expect, test, vi } from "vitest";
import { ImageCropDialog } from "@/shared/ui/image-crop-dialog";
import type { CropRenderRequest } from "@/shared/lib/crop-image";
const image = {
file: new File(["image"], "profile.png", { type: "image/png" }),
height: 600,
previewUrl: "blob:profile",
width: 600,
};
test("ImageCropDialog provides move, zoom, reset, preview, cancel, and apply controls", async () => {
const onApply = vi.fn();
const onCancel = vi.fn();
const renderCrop = vi.fn((request: CropRenderRequest) => Promise.resolve(new File([String(request.zoom)], "crop.png", { type: "image/png" })));
render(<ImageCropDialog image={image} onApply={onApply} onCancel={onCancel} open policy={{ aspect: 1, maxWidth: 800, noUpscale: true }} renderCrop={renderCrop} />);
fireEvent.click(screen.getByRole("button", { name: "오른쪽으로 이동" }));
fireEvent.change(screen.getByRole("slider", { name: "확대 비율" }), { target: { value: "1.5" } });
expect(screen.getByText("예상 결과 600 × 600px")).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "초기화" }));
fireEvent.click(screen.getByRole("button", { name: "적용" }));
await screen.findByText("예상 결과 600 × 600px");
expect(renderCrop).toHaveBeenCalledWith(expect.objectContaining({ offsetX: 0, offsetY: 0, outputHeight: 600, outputWidth: 600, zoom: 1 }));
expect(onApply).toHaveBeenCalledWith(expect.any(File));
fireEvent.click(screen.getByRole("button", { name: "취소" }));
expect(onCancel).toHaveBeenCalled();
});
test("ImageCropDialog supports keyboard movement and no-upscale sizing", async () => {
const renderCrop = vi.fn((request: CropRenderRequest) => Promise.resolve(new File([`${request.offsetX}`], "crop.png", { type: "image/png" })));
render(<ImageCropDialog image={image} onApply={vi.fn()} onCancel={vi.fn()} open policy={{ aspect: 2, maxWidth: 800, noUpscale: true }} renderCrop={renderCrop} />);
const preview = screen.getByRole("application", { name: "이미지 crop 미리보기" });
fireEvent.keyDown(preview, { key: "ArrowRight" });
fireEvent.keyDown(preview, { key: "+" });
fireEvent.click(screen.getByRole("button", { name: "적용" }));
expect(await screen.findByText("예상 결과 600 × 300px")).toBeInTheDocument();
expect(renderCrop).toHaveBeenCalledWith(expect.objectContaining({ offsetX: 10, outputHeight: 300, outputWidth: 600, zoom: 1.1 }));
});
test("ImageCropDialog supports free ratio output and pointer drag movement", async () => {
const renderCrop = vi.fn((request: CropRenderRequest) => Promise.resolve(new File([`${request.offsetX},${request.offsetY}`], "crop.png", { type: "image/png" })));
render(<ImageCropDialog image={{ ...image, height: 600, width: 1200 }} onApply={vi.fn()} onCancel={vi.fn()} open policy={{ aspect: "free", maxWidth: 800, noUpscale: true }} renderCrop={renderCrop} />);
const preview = screen.getByRole("application", { name: "이미지 crop 미리보기" });
fireEvent.pointerDown(preview, { clientX: 100, clientY: 100, pointerId: 1 });
fireEvent.pointerMove(preview, { clientX: 130, clientY: 115, pointerId: 1 });
fireEvent.pointerUp(preview, { pointerId: 1 });
fireEvent.click(screen.getByRole("button", { name: "적용" }));
expect(await screen.findByText("예상 결과 800 × 400px")).toBeInTheDocument();
expect(renderCrop).toHaveBeenCalledWith(expect.objectContaining({ offsetX: 30, offsetY: 15, outputHeight: 400, outputWidth: 800 }));
});
test("ImageCropDialog renders nothing when closed", () => {
render(<ImageCropDialog image={image} onApply={vi.fn()} onCancel={vi.fn()} open={false} policy={{ aspect: 1, maxWidth: 800, noUpscale: true }} />);
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
});