import { describe, it, expect, vi, afterEach } from "vitest"; import { render, screen, fireEvent, cleanup } from "@testing-library/react"; import { ImagePropertiesPanel } from "@/app/editor/panels/ImagePropertiesPanel"; import type { ImageBlockProps } from "@/features/editor/state/editorStore"; // ImagePropertiesPanel 컨트롤 TDD // - 카드 배경색(backgroundColorCustom) HEX 인풋 변경 시 updateBlock 이 올바르게 호출되는지 describe("ImagePropertiesPanel", () => { const baseProps: ImageBlockProps = { src: "/images/example.png", alt: "예시 이미지", align: "center", widthMode: "auto", borderRadius: "md", }; afterEach(() => { cleanup(); }); it("카드 배경색 HEX 인풋 변경 시 updateBlock 이 backgroundColorCustom 으로 호출되어야 한다", () => { const updateBlock = vi.fn(); render( , ); const hexInput = screen.getByLabelText("이미지 카드 배경색 HEX"); fireEvent.change(hexInput, { target: { value: "#112233" } }); expect(updateBlock).toHaveBeenCalledWith( "image-1", expect.objectContaining({ backgroundColorCustom: "#112233" }), ); }); it("이미지 소스 셀렉트에서 url 선택 시 sourceType 이 externalUrl 이 되고 assetId 가 null 로 설정되어야 한다", () => { const updateBlock = vi.fn(); render( , ); const select = screen.getByLabelText("이미지 소스"); fireEvent.change(select, { target: { value: "url" } }); expect(updateBlock).toHaveBeenCalledWith( "image-source-1", expect.objectContaining({ sourceType: "externalUrl", assetId: null }), ); }); it("이미지 URL 인풋 변경 시 src / sourceType / assetId 가 externalUrl/null 로 업데이트되어야 한다", () => { const updateBlock = vi.fn(); render( , ); const urlInput = screen.getByLabelText("이미지 URL"); fireEvent.change(urlInput, { target: { value: "https://example.com/after.png" } }); expect(updateBlock).toHaveBeenCalledWith( "image-url-1", expect.objectContaining({ src: "https://example.com/after.png", sourceType: "externalUrl", assetId: null, }), ); }); it("대체 텍스트 인풋 변경 시 updateBlock 이 alt 로 호출되어야 한다", () => { const updateBlock = vi.fn(); render( , ); const altInput = screen.getByLabelText("대체 텍스트"); fireEvent.change(altInput, { target: { value: "새 대체 텍스트" } }); expect(updateBlock).toHaveBeenCalledWith( "image-alt-1", expect.objectContaining({ alt: "새 대체 텍스트" }), ); }); it("이미지 정렬 셀렉트 변경 시 updateBlock 이 align 으로 호출되어야 한다", () => { const updateBlock = vi.fn(); render( , ); const select = screen.getByLabelText("이미지 정렬"); fireEvent.change(select, { target: { value: "left" } }); expect(updateBlock).toHaveBeenCalledWith( "image-align-1", expect.objectContaining({ align: "left" }), ); }); it("너비 모드 셀렉트 변경 시 updateBlock 이 widthMode 로 호출되어야 한다", () => { const updateBlock = vi.fn(); render( , ); const select = screen.getByLabelText("이미지 너비 모드"); fireEvent.change(select, { target: { value: "fixed" } }); expect(updateBlock).toHaveBeenCalledWith( "image-width-mode-1", expect.objectContaining({ widthMode: "fixed" }), ); }); it("너비 모드가 fixed 일 때 고정 너비 슬라이더 변경 시 updateBlock 이 widthPx 로 호출되어야 한다", () => { const updateBlock = vi.fn(); render( , ); const slider = screen.getByLabelText("고정 너비 (px) 슬라이더"); fireEvent.change(slider, { target: { value: "480" } }); expect(updateBlock).toHaveBeenCalledWith( "image-width-fixed-1", expect.objectContaining({ widthPx: 480 }), ); }); it("모서리 둥글기 슬라이더 변경 시 updateBlock 이 borderRadius 와 borderRadiusPx 를 함께 업데이트해야 한다", () => { const updateBlock = vi.fn(); render( , ); const slider = screen.getByLabelText("모서리 둥글기 슬라이더"); fireEvent.change(slider, { target: { value: "24" } }); expect(updateBlock).toHaveBeenCalledWith( "image-radius-1", expect.objectContaining({ borderRadius: "sm", borderRadiusPx: 24 }), ); }); it("ImagePropertiesPanel 주요 인풋/셀렉트 컨트롤은 라이트/다크 듀얼 테마 크롬을 사용해야 한다", () => { const updateBlock = vi.fn(); render( , ); const sourceSelect = screen.getByLabelText("이미지 소스") as HTMLSelectElement; const sourceClass = sourceSelect.getAttribute("class") ?? ""; expect(sourceClass).toContain("bg-white"); expect(sourceClass).toContain("text-slate-900"); expect(sourceClass).toContain("border-slate-300"); expect(sourceClass).toContain("dark:bg-slate-900"); expect(sourceClass).toContain("dark:text-slate-100"); expect(sourceClass).toContain("dark:border-slate-700"); const urlInput = screen.getByLabelText("이미지 URL") as HTMLInputElement; const urlClass = urlInput.getAttribute("class") ?? ""; expect(urlClass).toContain("bg-white"); expect(urlClass).toContain("text-slate-900"); expect(urlClass).toContain("border-slate-300"); expect(urlClass).toContain("dark:bg-slate-900"); expect(urlClass).toContain("dark:text-slate-100"); expect(urlClass).toContain("dark:border-slate-700"); const alignSelect = screen.getByLabelText("이미지 정렬") as HTMLSelectElement; const alignClass = alignSelect.getAttribute("class") ?? ""; expect(alignClass).toContain("bg-white"); expect(alignClass).toContain("text-slate-900"); expect(alignClass).toContain("border-slate-300"); expect(alignClass).toContain("dark:bg-slate-900"); expect(alignClass).toContain("dark:text-slate-100"); expect(alignClass).toContain("dark:border-slate-700"); }); });