169 lines
7.0 KiB
TypeScript
169 lines
7.0 KiB
TypeScript
import { describe, it, expect, afterEach, vi } from "vitest";
|
|
import { render, screen, fireEvent, cleanup } from "@testing-library/react";
|
|
import { BlocksSidebar } from "@/app/editor/panels/BlocksSidebar";
|
|
|
|
// BlocksSidebar 템플릿 버튼 TDD
|
|
// - 각 템플릿 추가 버튼이 대응하는 editorStore 액션(add*TemplateSection)을 호출하는지 검증한다.
|
|
|
|
const templateActions = {
|
|
addHeroTemplateSection: vi.fn(),
|
|
addFeaturesTemplateSection: vi.fn(),
|
|
addCtaTemplateSection: vi.fn(),
|
|
addFaqTemplateSection: vi.fn(),
|
|
addPricingTemplateSection: vi.fn(),
|
|
addTestimonialsTemplateSection: vi.fn(),
|
|
addBlogTemplateSection: vi.fn(),
|
|
addTeamTemplateSection: vi.fn(),
|
|
addFooterTemplateSection: vi.fn(),
|
|
};
|
|
|
|
// BlocksSidebar 가 사용하는 나머지 액션들도 더미 함수로 채워둔다.
|
|
const otherActions = {
|
|
addTextBlock: vi.fn(),
|
|
addButtonBlock: vi.fn(),
|
|
addImageBlock: vi.fn(),
|
|
addVideoBlock: vi.fn(),
|
|
addDividerBlock: vi.fn(),
|
|
addListBlock: vi.fn(),
|
|
addSectionBlock: vi.fn(),
|
|
addFormBlock: vi.fn(),
|
|
addFormInputBlock: vi.fn(),
|
|
addFormSelectBlock: vi.fn(),
|
|
addFormCheckboxBlock: vi.fn(),
|
|
addFormRadioBlock: vi.fn(),
|
|
};
|
|
|
|
const mockState = {
|
|
...templateActions,
|
|
...otherActions,
|
|
};
|
|
|
|
vi.mock("@/features/editor/state/editorStore", () => {
|
|
return {
|
|
__esModule: true,
|
|
// useEditorStore(selector) 형태로 사용되므로, selector 에 mockState 를 넘겨준다.
|
|
useEditorStore: (selector: (state: typeof mockState) => unknown) => selector(mockState),
|
|
};
|
|
});
|
|
|
|
describe("BlocksSidebar - 템플릿 버튼", () => {
|
|
afterEach(() => {
|
|
cleanup();
|
|
Object.values(templateActions).forEach((fn) => fn.mockReset());
|
|
});
|
|
|
|
it("Hero 템플릿 추가 버튼 클릭 시 addHeroTemplateSection 이 호출되어야 한다", () => {
|
|
render(<BlocksSidebar />);
|
|
|
|
const button = screen.getByRole("button", { name: "Hero 템플릿 추가" });
|
|
fireEvent.click(button);
|
|
|
|
expect(templateActions.addHeroTemplateSection).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("Features 템플릿 추가 버튼 클릭 시 addFeaturesTemplateSection 이 호출되어야 한다", () => {
|
|
render(<BlocksSidebar />);
|
|
|
|
const button = screen.getByRole("button", { name: "Features 템플릿 추가" });
|
|
fireEvent.click(button);
|
|
|
|
expect(templateActions.addFeaturesTemplateSection).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("CTA 템플릿 추가 버튼 클릭 시 addCtaTemplateSection 이 호출되어야 한다", () => {
|
|
render(<BlocksSidebar />);
|
|
|
|
const button = screen.getByRole("button", { name: "CTA 템플릿 추가" });
|
|
fireEvent.click(button);
|
|
|
|
expect(templateActions.addCtaTemplateSection).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("FAQ/Pricing/Testimonials/Blog/Team/Footer 템플릿 버튼도 각각의 액션을 호출해야 한다", () => {
|
|
render(<BlocksSidebar />);
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: "FAQ 템플릿 추가" }));
|
|
fireEvent.click(screen.getByRole("button", { name: "Pricing 템플릿 추가" }));
|
|
fireEvent.click(screen.getByRole("button", { name: "Testimonials 템플릿 추가" }));
|
|
fireEvent.click(screen.getByRole("button", { name: "Blog 템플릿 추가" }));
|
|
fireEvent.click(screen.getByRole("button", { name: "Team 템플릿 추가" }));
|
|
fireEvent.click(screen.getByRole("button", { name: "Footer 템플릿 추가" }));
|
|
|
|
expect(templateActions.addFaqTemplateSection).toHaveBeenCalledTimes(1);
|
|
expect(templateActions.addPricingTemplateSection).toHaveBeenCalledTimes(1);
|
|
expect(templateActions.addTestimonialsTemplateSection).toHaveBeenCalledTimes(1);
|
|
expect(templateActions.addBlogTemplateSection).toHaveBeenCalledTimes(1);
|
|
expect(templateActions.addTeamTemplateSection).toHaveBeenCalledTimes(1);
|
|
expect(templateActions.addFooterTemplateSection).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("템플릿 섹션에는 각 템플릿의 용도를 설명하는 텍스트가 함께 표시되어야 한다", () => {
|
|
render(<BlocksSidebar />);
|
|
|
|
expect(screen.getByText("페이지 상단 Hero 섹션 (큰 제목 + 서브텍스트 + 버튼)"));
|
|
expect(screen.getByText("3컬럼 기능 소개 섹션"));
|
|
expect(screen.getByText("콜투액션(CTA) 섹션"));
|
|
expect(screen.getByText("자주 묻는 질문(FAQ) 섹션"));
|
|
expect(screen.getByText("요금제/플랜 소개 섹션"));
|
|
expect(screen.getByText("고객 후기(Testimonials) 섹션"));
|
|
expect(screen.getByText("블로그 포스트 목록 섹션"));
|
|
expect(screen.getByText("팀 소개 섹션"));
|
|
expect(screen.getByText("페이지 푸터 섹션"));
|
|
});
|
|
|
|
it("비디오 블록 추가 버튼 클릭 시 addVideoBlock 이 호출되어야 한다", () => {
|
|
render(<BlocksSidebar />);
|
|
|
|
const button = screen.getByRole("button", { name: "비디오 블록 추가" });
|
|
fireEvent.click(button);
|
|
|
|
expect(otherActions.addVideoBlock).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("템플릿들은 카테고리별로 그룹 헤더를 가져야 한다", () => {
|
|
render(<BlocksSidebar />);
|
|
|
|
expect(screen.getByText("히어로 · CTA")).toBeDefined();
|
|
expect(screen.getByText("콘텐츠 섹션")).toBeDefined();
|
|
expect(screen.getByText("신뢰/소개")).toBeDefined();
|
|
expect(screen.getByText("푸터/기타")).toBeDefined();
|
|
});
|
|
|
|
it("각 템플릿 카드에는 미니 썸네일(레이아웃 힌트)가 렌더링되어야 한다", () => {
|
|
render(<BlocksSidebar />);
|
|
|
|
expect(screen.getByTestId("template-preview-hero")).toBeDefined();
|
|
expect(screen.getByTestId("template-preview-features")).toBeDefined();
|
|
expect(screen.getByTestId("template-preview-cta")).toBeDefined();
|
|
expect(screen.getByTestId("template-preview-faq")).toBeDefined();
|
|
expect(screen.getByTestId("template-preview-pricing")).toBeDefined();
|
|
expect(screen.getByTestId("template-preview-testimonials")).toBeDefined();
|
|
expect(screen.getByTestId("template-preview-blog")).toBeDefined();
|
|
expect(screen.getByTestId("template-preview-team")).toBeDefined();
|
|
expect(screen.getByTestId("template-preview-footer")).toBeDefined();
|
|
});
|
|
|
|
it("미니 썸네일은 각 템플릿 레이아웃을 암시하는 구조를 가져야 한다", () => {
|
|
render(<BlocksSidebar />);
|
|
|
|
const heroThumb = screen.getByTestId("template-preview-hero");
|
|
const ctaThumb = screen.getByTestId("template-preview-cta");
|
|
const featuresThumb = screen.getByTestId("template-preview-features");
|
|
const pricingThumb = screen.getByTestId("template-preview-pricing");
|
|
const blogThumb = screen.getByTestId("template-preview-blog");
|
|
const teamThumb = screen.getByTestId("template-preview-team");
|
|
|
|
// Hero/CTA: 하나의 메인 영역을 가진 1열 구조
|
|
expect(heroThumb.children.length).toBe(1);
|
|
expect(ctaThumb.children.length).toBe(1);
|
|
|
|
// Features/Blog: 3컬럼 레이아웃을 암시하는 3개의 세그먼트
|
|
expect(featuresThumb.children.length).toBe(3);
|
|
expect(blogThumb.children.length).toBe(3);
|
|
|
|
// Pricing/Team: 높이가 다른 3개의 바를 가진 3컬럼 카드 레이아웃
|
|
expect(pricingThumb.children.length).toBe(3);
|
|
expect(teamThumb.children.length).toBe(3);
|
|
});
|
|
});
|