이미지 파일 업로드 기능
This commit is contained in:
@@ -19,6 +19,8 @@ import type {
|
||||
DividerBlockProps,
|
||||
ImageBlockProps,
|
||||
SectionBlockProps,
|
||||
ListLine,
|
||||
ButtonBlockProps,
|
||||
} from "@/features/editor/state/editorStore";
|
||||
|
||||
// 에디터 상태 스토어에 대한 첫 TDD: 텍스트 블록 추가
|
||||
@@ -1188,4 +1190,40 @@ describe("editorStore", () => {
|
||||
expect(updatedFormProps.fieldIds).toEqual(fieldIds);
|
||||
expect(updatedFormProps.submitButtonId).toBe(submitButtonId);
|
||||
});
|
||||
|
||||
it("projectConfig 기본값과 updateProjectConfig 액션으로 프로젝트 설정을 관리할 수 있어야 한다", () => {
|
||||
const store = createEditorStore();
|
||||
const stateAny = store.getState() as any;
|
||||
|
||||
expect(stateAny.projectConfig).toBeTruthy();
|
||||
expect(stateAny.projectConfig.title).toBe("새 페이지");
|
||||
expect(stateAny.projectConfig.slug).toBe("my-landing");
|
||||
expect(stateAny.projectConfig.canvasPreset).toBe("full");
|
||||
expect(stateAny.projectConfig.canvasBgColorHex).toBe("#020617");
|
||||
expect(stateAny.projectConfig.bodyBgColorHex).toBe("#020617");
|
||||
expect(stateAny.projectConfig.headHtml).toBe("");
|
||||
expect(stateAny.projectConfig.trackingScript).toBe("");
|
||||
|
||||
stateAny.updateProjectConfig({
|
||||
title: "랜딩 페이지",
|
||||
slug: "landing-page",
|
||||
canvasPreset: "desktop",
|
||||
bodyBgColorHex: "#111111",
|
||||
});
|
||||
|
||||
const next = store.getState() as any;
|
||||
expect(next.projectConfig.title).toBe("랜딩 페이지");
|
||||
expect(next.projectConfig.slug).toBe("landing-page");
|
||||
expect(next.projectConfig.canvasPreset).toBe("desktop");
|
||||
expect(next.projectConfig.bodyBgColorHex).toBe("#111111");
|
||||
|
||||
stateAny.updateProjectConfig({
|
||||
canvasPreset: "custom",
|
||||
canvasWidthPx: 1024,
|
||||
});
|
||||
|
||||
const custom = store.getState() as any;
|
||||
expect(custom.projectConfig.canvasPreset).toBe("custom");
|
||||
expect(custom.projectConfig.canvasWidthPx).toBe(1024);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user