import { describe, it, expect } from "vitest"; import React from "react"; import ReactDOMServer from "react-dom/server"; import type { Block, ProjectConfig } from "@/features/editor/state/editorStore"; import { PublicPageRenderer } from "@/features/editor/components/PublicPageRenderer"; import { buildStaticHtml } from "@/app/api/export/route"; describe("프리뷰와 정적 내보내기 일치 (고수준)", () => { it("여러 블록이 섞인 페이지에서 핵심 텍스트는 프리뷰와 정적 HTML 모두에 존재해야 한다", () => { const blocks: Block[] = [ { id: "text_root", type: "text", props: { text: "텍스트 블록", align: "left", size: "base", }, } as any, { id: "btn_root", type: "button", props: { label: "버튼 A", href: "#", align: "center", }, } as any, { id: "img_root", type: "image", props: { src: "/api/image/sample-id", alt: "이미지 설명", align: "center", widthMode: "auto", borderRadius: "md", }, } as any, { id: "sec_1", type: "section", props: { background: "primary", paddingY: "lg", columns: [ { id: "sec_1_col_1", span: 12, }, ], maxWidthMode: "normal", gapX: "md", alignItems: "top", }, } as any, { id: "sec_1_text", type: "text", props: { text: "섹션 안 텍스트", align: "left", size: "base", }, sectionId: "sec_1", columnId: "sec_1_col_1", } as any, { id: "sec_1_btn", type: "button", props: { label: "섹션 버튼", href: "#", align: "left", }, sectionId: "sec_1", columnId: "sec_1_col_1", } as any, { id: "divider_1", type: "divider", props: { align: "center", thickness: "medium", widthMode: "full", }, } as any, { id: "list_1", type: "list", props: { items: ["리스트 아이템 1", "리스트 아이템 2"], ordered: false, align: "left", }, } as any, { id: "form_1", type: "form", props: { kind: "contact", submitTarget: "internal", // v1 fallback 필드를 사용해 양쪽 렌더러가 동일한 폼 필드를 가지도록 한다. fields: [ { id: "f_name", name: "name", label: "폼 이름", type: "text", required: true }, { id: "f_email", name: "email", label: "폼 이메일", type: "email", required: true }, { id: "f_msg", name: "message", label: "폼 메시지", type: "textarea", required: true }, ], fieldIds: [], submitButtonId: null, formWidthMode: "full", }, } as any, { id: "input_standalone", type: "formInput", props: { label: "입력(단독)", formFieldName: "single_input", required: true, }, } as any, { id: "select_standalone", type: "formSelect", props: { label: "선택(단독)", formFieldName: "single_select", options: [ { label: "옵션 A", value: "a" }, { label: "옵션 B", value: "b" }, ], required: false, }, } as any, { id: "checkbox_standalone", type: "formCheckbox", props: { groupLabel: "체크 그룹", formFieldName: "checks", options: [ { label: "체크 A", value: "a" }, { label: "체크 B", value: "b" }, ], required: false, }, } as any, { id: "radio_standalone", type: "formRadio", props: { groupLabel: "라디오 그룹", formFieldName: "radios", options: [ { label: "라디오 A", value: "a" }, { label: "라디오 B", value: "b" }, ], required: false, }, } as any, ]; const projectConfig: ProjectConfig = { title: "프리뷰-내보내기 고수준 테스트", slug: "preview-export-parity", canvasPreset: "full", }; const previewHtml = ReactDOMServer.renderToString( React.createElement(PublicPageRenderer, { blocks }), ); const staticHtml = buildStaticHtml(blocks, projectConfig); const importantStrings = [ "텍스트 블록", "버튼 A", "이미지 설명", "섹션 안 텍스트", "섹션 버튼", "리스트 아이템 1", "입력(단독)", "선택(단독)", "체크 A", "라디오 A", ]; for (const text of importantStrings) { expect(previewHtml).toContain(text); expect(staticHtml).toContain(text); } }); it( "리스트+폼+구분선 조합에서 리스트 컨테이너와 구분선이 프리뷰/정적 HTML 모두에 존재하고, 리스트는 pb-list 클래스를 사용하며 li 에 margin-bottom 인라인 스타일을 사용하지 않아야 한다", () => { const blocks: Block[] = [ { id: "divider_combo", type: "divider", props: { align: "center", thickness: "medium", widthMode: "full", }, } as any, { id: "list_combo", type: "list", props: { items: ["리스트 아이템 1", "리스트 아이템 2"], ordered: false, align: "left", }, } as any, { id: "form_combo", type: "form", props: { kind: "contact", submitTarget: "internal", // v1 fallback 필드를 사용해 양쪽 렌더러가 동일한 폼 필드를 가지도록 한다. fields: [ { id: "f_name", name: "name", label: "이름", type: "text", required: true }, ], fieldIds: [], submitButtonId: null, formWidthMode: "full", }, } as any, ]; const projectConfig: ProjectConfig = { title: "리스트+폼+구분선 레이아웃 테스트", slug: "list-form-divider-layout", canvasPreset: "full", }; const previewHtml = ReactDOMServer.renderToString( React.createElement(PublicPageRenderer, { blocks }), ); const staticHtml = buildStaticHtml(blocks, projectConfig); // 리스트 텍스트는 양쪽에 존재해야 한다. expect(previewHtml).toContain("리스트 아이템 1"); expect(staticHtml).toContain("리스트 아이템 1"); // 구분선은 프리뷰/Export 모두에 존재해야 한다. expect(previewHtml).toContain('data-testid="preview-divider"'); expect(staticHtml).toContain('
]*style=\"[^\"]*margin-bottom:/); expect(staticHtml).not.toMatch(/]*style=\"[^\"]*margin-bottom:/); }, ); });