import { describe, it, expect } from "vitest"; import { expectNoPxInStyleObject, expectNoPxInStyleParts } from "./styleInvariants"; import { computeListExportTokens, computeListEditorTokens, computeListPublicTokens, } from "@/features/editor/utils/listHelpers"; import type { ListBlockProps } from "@/features/editor/state/editorStore"; const baseProps: ListBlockProps = { items: [], ordered: false, align: "left", }; describe("listHelpers.computeListExportTokens", () => { it("기본 unordered 리스트는 Tag=ul, left 정렬, items 플랫닝을 수행해야 한다", () => { const props: ListBlockProps = { ...baseProps, items: [" first ", "", " second"], }; const tokens = computeListExportTokens(props); expect(tokens.Tag).toBe("ul"); expect(tokens.items).toEqual([" first ", " second"]); expect(tokens.listStyleParts).toContain("text-align:left"); }); it("ordered=true 인 경우 Tag=ol 이어야 한다", () => { const props: ListBlockProps = { ...baseProps, ordered: true, items: ["one", "two"], }; const tokens = computeListExportTokens(props); expect(tokens.Tag).toBe("ol"); expect(tokens.items).toEqual(["one", "two"]); }); it("itemsTree 가 있으면 중첩 트리를 DFS 순서로 평탄화해야 한다", () => { const props: ListBlockProps = { ...baseProps, itemsTree: [ { id: "n1", text: "parent", children: [ { id: "n1-1", text: "child-1", children: [] }, { id: "n1-2", text: "child-2", children: [] }, ], }, ], }; const tokens = computeListExportTokens(props); expect(tokens.items).toEqual(["parent", "child-1", "child-2"]); }); it("align 값에 따라 text-align 스타일이 left/center/right 로 설정되어야 한다", () => { const centerTokens = computeListExportTokens({ ...baseProps, align: "center", items: ["a"] }); const rightTokens = computeListExportTokens({ ...baseProps, align: "right", items: ["a"] }); expect(centerTokens.listStyleParts).toContain("text-align:center"); expect(rightTokens.listStyleParts).toContain("text-align:right"); }); it("backgroundColorCustom 이 있으면 background-color 스타일을 추가해야 한다", () => { const tokens = computeListExportTokens({ ...baseProps, items: ["a"], backgroundColorCustom: " #ff0000 ", }); expect(tokens.listStyleParts).toContain("background-color:#ff0000"); }); it("items 와 itemsTree 가 모두 비어 있으면 items 배열은 빈 배열이어야 한다", () => { const tokens = computeListExportTokens({ ...baseProps }); expect(tokens.items).toEqual([]); }); it("Export 리스트 listStyleParts 에는 px 단위 값이 포함되지 않아야 한다", () => { const tokens = computeListExportTokens({ ...baseProps, align: "center", items: ["a"], backgroundColorCustom: "#123456", }); expectNoPxInStyleParts(tokens.listStyleParts); }); }); describe("listHelpers.computeListEditorTokens", () => { it("gapY 토큰에 따라 gapPx 가 sm=4, md=8, lg=16 으로 계산되어야 한다", () => { const sm = computeListEditorTokens({ ...baseProps, gapY: "sm" }); const md = computeListEditorTokens({ ...baseProps, gapY: "md" }); const lg = computeListEditorTokens({ ...baseProps, gapY: "lg" }); expect(sm.gapPx).toBe(4); expect(md.gapPx).toBe(8); expect(lg.gapPx).toBe(16); }); it("gapYPx 가 있으면 gapY 토큰보다 우선해서 gapPx 를 설정해야 한다", () => { const tokens = computeListEditorTokens({ ...baseProps, gapY: "sm", gapYPx: 24 }); expect(tokens.gapPx).toBe(24); }); it("fontSizeCustom / lineHeightCustom / textColorCustom 이 listStyle 에 반영되어야 한다", () => { const tokens = computeListEditorTokens({ ...baseProps, fontSizeCustom: "18px", lineHeightCustom: "1.8", textColorCustom: "#ff0000", }); expect(tokens.listStyle.fontSize).toBe("18px"); expect(tokens.listStyle.lineHeight).toBe("1.8"); expect(tokens.listStyle.color).toBe("#ff0000"); }); it("bulletStyle 은 bulletStyle 지정값 또는 ordered 여부에 따라 결정되어야 한다", () => { const defaultDisc = computeListEditorTokens({ ...baseProps, ordered: false, bulletStyle: undefined, }); const defaultDecimal = computeListEditorTokens({ ...baseProps, ordered: true, bulletStyle: undefined, }); const none = computeListEditorTokens({ ...baseProps, bulletStyle: "none", }); expect(defaultDisc.bulletStyle).toBe("disc"); expect(defaultDecimal.bulletStyle).toBe("decimal"); expect(none.bulletStyle).toBe("none"); }); }); describe("listHelpers.computeListPublicTokens", () => { it("align 값에 따라 alignClass 가 pb-text-left/center/right 로 설정되어야 한다", () => { const leftTokens = computeListPublicTokens({ ...baseProps, align: "left" }); const centerTokens = computeListPublicTokens({ ...baseProps, align: "center" }); const rightTokens = computeListPublicTokens({ ...baseProps, align: "right" }); expect(leftTokens.alignClass).toBe("pb-text-left"); expect(centerTokens.alignClass).toBe("pb-text-center"); expect(rightTokens.alignClass).toBe("pb-text-right"); }); it("gapYPx 와 gapY 토큰에 따라 gapEm 이 px/16 값으로 계산되어야 한다", () => { const byPx = computeListPublicTokens({ ...baseProps, gapYPx: 24 }); const byTokenSm = computeListPublicTokens({ ...baseProps, gapY: "sm" }); const byTokenLg = computeListPublicTokens({ ...baseProps, gapY: "lg" }); expect(byPx.gapEm).toBe(24 / 16); expect(byTokenSm.gapEm).toBe(4 / 16); expect(byTokenLg.gapEm).toBe(16 / 16); }); it("fontSizeCustom / lineHeightCustom 이 px 인 경우 em 단위로 변환되어야 하고, backgroundColorCustom 은 trim 되어야 한다", () => { const tokens = computeListPublicTokens({ ...baseProps, fontSizeCustom: "32px", lineHeightCustom: "24px", backgroundColorCustom: " #123456 ", }); expect(tokens.listStyle.fontSize).toBe("2em"); expect(tokens.listStyle.lineHeight).toBe("1.5em"); expect(tokens.listStyle.backgroundColor).toBe("#123456"); }); it("lineHeightCustom 이 px 인 경우 em 단위로 변환되어야 한다", () => { const tokens = computeListPublicTokens({ ...baseProps, lineHeightCustom: "24px", }); expect(tokens.listStyle.lineHeight).toBe("1.5em"); }); it("bulletStyle 은 bulletStyle 지정값 또는 ordered 여부에 따라 결정되어야 한다", () => { const defaultDisc = computeListPublicTokens({ ...baseProps, ordered: false, bulletStyle: undefined }); const defaultDecimal = computeListPublicTokens({ ...baseProps, ordered: true, bulletStyle: undefined }); const none = computeListPublicTokens({ ...baseProps, bulletStyle: "none" }); expect(defaultDisc.bulletStyle).toBe("disc"); expect(defaultDecimal.bulletStyle).toBe("decimal"); expect(none.bulletStyle).toBe("none"); }); it("퍼블릭 리스트 style 에 px 단위 값이 포함되지 않아야 한다", () => { const tokens = computeListPublicTokens({ ...baseProps, fontSizeCustom: "32px", lineHeightCustom: "24px", textColorCustom: "#ff0000", backgroundColorCustom: "#123456", }); expectNoPxInStyleObject(tokens.listStyle); }); });