import { describe, it, expect } from "vitest"; import { expectNoPxInStyleObject, expectNoPxInStyleParts } from "./styleInvariants"; import { computeSectionExportTokens, computeSectionPublicTokens, computeSectionEditorTokens, } from "@/features/editor/utils/sectionHelpers"; import type { SectionBlockProps } from "@/features/editor/state/editorStore"; const baseProps: SectionBlockProps = { background: "default", paddingY: "md", columns: [{ id: "sec_col_1", span: 12 }], maxWidthMode: "normal", gapX: "md", }; describe("sectionHelpers.computeSectionExportTokens", () => { it("기본 섹션은 pb-section / pb-section-bg-default / pb-section-py-md 를 포함해야 한다", () => { const tokens = computeSectionExportTokens(baseProps); expect(tokens.sectionClasses).toContain("pb-section"); expect(tokens.sectionClasses).toContain("pb-section-bg-default"); expect(tokens.sectionClasses).toContain("pb-section-py-md"); expect(tokens.sectionStyleParts.length).toBe(0); expect(tokens.backgroundVideoHtml).toBe(""); }); it("background=muted/primary 와 paddingY=sm/lg 는 각각 대응하는 pb-section-bg-*/pb-section-py-* 클래스로 매핑되어야 한다", () => { const muted = computeSectionExportTokens({ ...baseProps, background: "muted" }); const primary = computeSectionExportTokens({ ...baseProps, background: "primary" }); const sm = computeSectionExportTokens({ ...baseProps, paddingY: "sm" }); const lg = computeSectionExportTokens({ ...baseProps, paddingY: "lg" }); expect(muted.sectionClasses).toContain("pb-section-bg-muted"); expect(primary.sectionClasses).toContain("pb-section-bg-primary"); expect(sm.sectionClasses).toContain("pb-section-py-sm"); expect(lg.sectionClasses).toContain("pb-section-py-lg"); }); it("backgroundColorCustom 이 있으면 background-color 스타일을 추가해야 한다", () => { const tokens = computeSectionExportTokens({ ...baseProps, backgroundColorCustom: "#123456", }); expect(tokens.sectionStyleParts).toContain("background-color:#123456"); }); it("backgroundImage 관련 옵션이 있으면 background-image/size/position/repeat 스타일을 추가해야 한다", () => { const tokens = computeSectionExportTokens({ ...baseProps, backgroundImageSrc: "https://example.com/bg.png", backgroundImageSize: "cover", backgroundImagePositionMode: "preset", backgroundImagePosition: "top", backgroundImageRepeat: "repeat-x", }); expect(tokens.sectionStyleParts).toContain("background-image:url(https://example.com/bg.png)"); expect(tokens.sectionStyleParts).toContain("background-size:cover"); expect(tokens.sectionStyleParts).toContain("background-position:center top"); expect(tokens.sectionStyleParts).toContain("background-repeat:repeat-x"); }); it("배경 비디오가 있으면 backgroundVideoHtml 과 position/overflow 스타일을 추가해야 한다", () => { const tokens = computeSectionExportTokens({ ...baseProps, backgroundVideoSrc: "https://example.com/bg.mp4", }); expect(tokens.backgroundVideoHtml).toContain("pb-section-bg-video"); expect(tokens.backgroundVideoHtml).toContain("https://example.com/bg.mp4"); expect(tokens.sectionStyleParts).toContain("position:relative"); expect(tokens.sectionStyleParts).toContain("overflow:hidden"); }); it("paddingYPx / maxWidthPx / gapXPx 는 Export 토큰 styleParts 에 em 단위로 반영되어야 한다", () => { const tokens = computeSectionExportTokens({ ...baseProps, backgroundColorCustom: "#123456", paddingYPx: 80, maxWidthPx: 960, gapXPx: 32, } as SectionBlockProps); // 섹션 wrapper: 배경색 + 세로 패딩(em) expect(tokens.sectionStyleParts).toContain("background-color:#123456"); expect(tokens.sectionStyleParts).toContain("padding-top:5em"); expect(tokens.sectionStyleParts).toContain("padding-bottom:5em"); // 내부 래퍼: 최대 폭(em) expect(tokens.innerWrapperStyleParts).toContain("max-width:60em"); // 컬럼 컨테이너: column-gap(em) expect(tokens.columnsStyleParts).toContain("column-gap:2em"); // px 단위는 어디에도 포함되면 안 된다. expectNoPxInStyleParts(tokens.sectionStyleParts); expectNoPxInStyleParts(tokens.innerWrapperStyleParts); expectNoPxInStyleParts(tokens.columnsStyleParts); }); it("Export 섹션 sectionStyleParts 에는 px 단위 값이 포함되지 않아야 한다", () => { const tokens = computeSectionExportTokens({ ...baseProps, backgroundColorCustom: "#123456", backgroundImageSrc: "https://example.com/bg.png", backgroundImageSize: "cover", backgroundImagePositionMode: "preset", backgroundImagePosition: "top", backgroundImageRepeat: "repeat-x", backgroundVideoSrc: "https://example.com/bg.mp4", } as SectionBlockProps); expectNoPxInStyleParts(tokens.sectionStyleParts); expectNoPxInStyleParts(tokens.innerWrapperStyleParts); expectNoPxInStyleParts(tokens.columnsStyleParts); }); }); describe("sectionHelpers.computeSectionPublicTokens", () => { it("배경색 / paddingYPx / maxWidthPx / gapXPx 를 em 단위 스타일로 계산해야 한다", () => { const tokens = computeSectionPublicTokens({ ...baseProps, backgroundColorCustom: " #123456 ", paddingYPx: 32, maxWidthPx: 800, gapXPx: 24, }); expect(tokens.sectionStyle.backgroundColor).toBe("#123456"); expect(tokens.sectionStyle.paddingTop).toBe("2em"); expect(tokens.sectionStyle.paddingBottom).toBe("2em"); expect(tokens.innerWrapperStyle.maxWidth).toBe("50em"); expect(tokens.columnsContainerStyle.columnGap).toBe("1.5em"); // 퍼블릭 섹션 토큰 스타일에는 px 단위 값이 포함되지 않아야 한다. expectNoPxInStyleObject(tokens.sectionStyle); expectNoPxInStyleObject(tokens.innerWrapperStyle); expectNoPxInStyleObject(tokens.columnsContainerStyle); }); it("backgroundImage* 와 backgroundVideoSrc 를 올바르게 반영해야 한다", () => { const tokens = computeSectionPublicTokens({ ...baseProps, backgroundImageSrc: "https://example.com/bg.png", backgroundImageSize: "cover", backgroundImagePositionMode: "preset", backgroundImagePosition: "top", backgroundImageRepeat: "repeat-x", backgroundVideoSrc: " https://example.com/bg.mp4 ", } as any); expect(tokens.sectionStyle.backgroundImage).toBe("url(https://example.com/bg.png)"); expect(tokens.sectionStyle.backgroundSize).toBe("cover"); expect(tokens.sectionStyle.backgroundPosition).toBe("center top"); expect(tokens.sectionStyle.backgroundRepeat).toBe("repeat-x"); expect(tokens.hasBackgroundVideo).toBe(true); expect(tokens.backgroundVideoSrc).toBe("https://example.com/bg.mp4"); expect(tokens.sectionStyle.position).toBe("relative"); expect(tokens.sectionStyle.overflow).toBe("hidden"); }); }); describe("sectionHelpers.computeSectionEditorTokens", () => { it("backgroundColorCustom / paddingYPx / maxWidthPx / gapXPx 가 px 단위 스타일로 계산되어야 한다", () => { const tokens = computeSectionEditorTokens({ ...baseProps, backgroundColorCustom: "#123456", paddingYPx: 80, maxWidthPx: 960, gapXPx: 32, alignItems: "center", } as any); expect(tokens.wrapperStyle.backgroundColor).toBe("#123456"); expect(tokens.wrapperStyle.paddingTop).toBe("80px"); expect(tokens.wrapperStyle.paddingBottom).toBe("80px"); expect(tokens.innerWrapperStyle.maxWidth).toBe("960px"); expect(tokens.columnsContainerStyle.columnGap).toBe("32px"); expect(tokens.alignItemsClass).toBe("items-center"); }); it("background 및 paddingY 값에 따라 bgClass/pyClass 를 계산해야 한다", () => { const muted = computeSectionEditorTokens({ ...baseProps, background: "muted" } as any); const primary = computeSectionEditorTokens({ ...baseProps, background: "primary" } as any); const sm = computeSectionEditorTokens({ ...baseProps, paddingY: "sm" } as any); const lg = computeSectionEditorTokens({ ...baseProps, paddingY: "lg" } as any); expect(muted.bgClass).toContain("bg-slate-950/40"); expect(primary.bgClass).toContain("bg-sky-950/40"); expect(primary.bgClass).toContain("border-sky-900/60"); expect(sm.pyClass).toBe("py-4"); expect(lg.pyClass).toBe("py-10"); }); it("backgroundImage* 옵션이 있으면 wrapperStyle 에 backgroundImage/Size/Position/Repeat 이 반영되어야 한다", () => { const tokens = computeSectionEditorTokens({ ...baseProps, backgroundImageSrc: "https://example.com/bg.png", backgroundImageSize: "cover", backgroundImagePositionMode: "preset", backgroundImagePosition: "top", backgroundImageRepeat: "repeat-x", } as any); expect(tokens.wrapperStyle.backgroundImage).toBe("url(https://example.com/bg.png)"); expect(tokens.wrapperStyle.backgroundSize).toBe("cover"); expect(tokens.wrapperStyle.backgroundPosition).toBe("center top"); expect(tokens.wrapperStyle.backgroundRepeat).toBe("repeat-x"); }); it("backgroundImagePositionMode 가 custom 이고 X/Y 퍼센트가 설정되면 background-position 은 'X% Y%' 로 설정되어야 한다", () => { const tokens = computeSectionEditorTokens({ ...baseProps, backgroundImageSrc: "https://example.com/bg-xy.png", backgroundImageSize: "cover", backgroundImagePositionMode: "custom", backgroundImagePositionXPercent: 30, backgroundImagePositionYPercent: 70, } as any); expect(tokens.wrapperStyle.backgroundImage).toBe("url(https://example.com/bg-xy.png)"); expect(tokens.wrapperStyle.backgroundPosition).toBe("30% 70%"); }); it("backgroundVideoSrc 가 있으면 hasBackgroundVideo=true, backgroundVideoSrc 와 position/overflow 스타일이 설정되어야 한다", () => { const tokens = computeSectionEditorTokens({ ...baseProps, backgroundVideoSrc: " https://example.com/bg.mp4 ", } as any); expect(tokens.hasBackgroundVideo).toBe(true); expect(tokens.backgroundVideoSrc).toBe("https://example.com/bg.mp4"); expect(tokens.wrapperStyle.position).toBe("relative"); expect(tokens.wrapperStyle.overflow).toBe("hidden"); }); });