676 lines
24 KiB
TypeScript
676 lines
24 KiB
TypeScript
import { describe, it, expect, afterEach } from "vitest";
|
|
import { render, cleanup } from "@testing-library/react";
|
|
import { PublicPageRenderer } from "@/features/editor/components/PublicPageRenderer";
|
|
import type { Block } from "@/features/editor/state/editorStore";
|
|
|
|
// 폼 필드 스타일 TDD
|
|
// - formInput: 색상/패딩/너비/둥글기 스타일
|
|
// - formSelect: 색상/패딩/너비/둥글기 스타일
|
|
// - formCheckbox/formRadio: 옵션 컨테이너 색상/패딩/둥글기 스타일
|
|
|
|
function getFirstLabel(root: HTMLElement): HTMLLabelElement {
|
|
const label = root.querySelector("label");
|
|
if (!label) {
|
|
throw new Error("label element not found");
|
|
}
|
|
return label as HTMLLabelElement;
|
|
}
|
|
|
|
describe("PublicPageRenderer - 폼 필드 스타일", () => {
|
|
afterEach(() => {
|
|
cleanup();
|
|
});
|
|
|
|
it("formInput 블록은 색상/패딩/너비/둥글기 스타일을 반영해야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "form_input_styles",
|
|
type: "formInput",
|
|
props: {
|
|
label: "이메일",
|
|
formFieldName: "email",
|
|
align: "center",
|
|
widthMode: "fixed",
|
|
widthPx: 320,
|
|
paddingX: 16,
|
|
paddingY: 8,
|
|
textColorCustom: "#112233",
|
|
fillColorCustom: "#123456",
|
|
strokeColorCustom: "#ff0000",
|
|
borderRadius: "lg",
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const wrapper = getByTestId("preview-form-input-wrapper") as HTMLElement;
|
|
const input = wrapper.querySelector('[data-testid="preview-form-input"]') as HTMLElement | null;
|
|
expect(input).not.toBeNull();
|
|
|
|
// 정렬
|
|
expect((input as HTMLInputElement).style.textAlign).toBe("center");
|
|
// width: 320px / 16 = 20em
|
|
expect((input as HTMLInputElement).style.width).toBe("20em");
|
|
// padding-inline/block: 16px/8px -> 1em / 0.5em
|
|
expect((input as HTMLInputElement).style.paddingInline).toBe("1em");
|
|
expect((input as HTMLInputElement).style.paddingBlock).toBe("0.5em");
|
|
// 색상: hex -> rgb 변환
|
|
expect((input as HTMLInputElement).style.color).toBe("rgb(17, 34, 51)");
|
|
expect((input as HTMLInputElement).style.backgroundColor).toBe("rgb(18, 52, 86)");
|
|
expect((input as HTMLInputElement).style.borderColor).toBe("rgb(255, 0, 0)");
|
|
// borderRadius: lg -> 6px
|
|
expect((input as HTMLInputElement).style.borderRadius).toBe("6px");
|
|
});
|
|
|
|
it("formInput 블록에서 inputType/email 과 formFieldName 은 type/name/placeholder 로 반영되어야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "form_input_email_attrs",
|
|
type: "formInput",
|
|
props: {
|
|
label: "이메일",
|
|
formFieldName: "email_field",
|
|
inputType: "email",
|
|
placeholder: "이메일 주소",
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { getByLabelText } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const input = getByLabelText("이메일") as HTMLInputElement;
|
|
expect(input.type).toBe("email");
|
|
expect(input.name).toBe("email_field");
|
|
expect(input.placeholder).toBe("이메일 주소");
|
|
});
|
|
|
|
it("formInput 블록의 타이포그래피 커스텀 값은 em 단위 스타일로 반영되어야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "form_input_typography",
|
|
type: "formInput",
|
|
props: {
|
|
label: "커스텀 타이포",
|
|
formFieldName: "typo",
|
|
fontSizeCustom: "24px",
|
|
lineHeightCustom: "30px",
|
|
letterSpacingCustom: "2px",
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const wrapper = getByTestId("preview-form-input-wrapper") as HTMLElement;
|
|
const input = wrapper.querySelector('[data-testid="preview-form-input"]') as HTMLInputElement | null;
|
|
expect(input).not.toBeNull();
|
|
|
|
expect(input!.style.fontSize).toBe("1.5em");
|
|
expect(input!.style.lineHeight).toBe("1.875em");
|
|
expect(input!.style.letterSpacing).toBe("0.125em");
|
|
});
|
|
|
|
it("formInput 블록에서 라벨 표시 방식이 hidden 이면 sr-only 클래스로 시각적으로 숨겨야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "form_input_hidden_label",
|
|
type: "formInput",
|
|
props: {
|
|
label: "숨김 라벨",
|
|
formFieldName: "hidden_label",
|
|
labelDisplay: "hidden",
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { getByTestId, getByLabelText } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const wrapper = getByTestId("preview-form-input-wrapper") as HTMLElement;
|
|
const span = wrapper.querySelector("span");
|
|
expect(span).not.toBeNull();
|
|
expect((span as HTMLSpanElement).className).toContain("sr-only");
|
|
|
|
// label 이 숨김이더라도 접근성 측면에서 getByLabelText 로는 접근 가능해야 한다.
|
|
const input = getByLabelText("숨김 라벨") as HTMLInputElement;
|
|
expect(input).toBeTruthy();
|
|
});
|
|
|
|
it("formInput 블록에서 라벨 표시 방식이 floating 이면 pb-form-field/pb-form-label 기반 플로팅 라벨 마크업을 사용해야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "form_input_floating_label",
|
|
type: "formInput",
|
|
props: {
|
|
label: "플로팅 라벨",
|
|
formFieldName: "floating_label",
|
|
labelDisplay: "floating",
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { container, getByLabelText } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const field = container.querySelector(
|
|
'[data-testid="preview-form-input-wrapper"]',
|
|
) as HTMLDivElement | null;
|
|
expect(field).not.toBeNull();
|
|
expect(field!.className).toContain("pb-form-field");
|
|
expect(field!.className).toContain("pb-form-field--floating");
|
|
|
|
const firstChild = field!.firstElementChild as HTMLElement | null;
|
|
const lastChild = field!.lastElementChild as HTMLElement | null;
|
|
expect(firstChild).not.toBeNull();
|
|
expect(lastChild).not.toBeNull();
|
|
// 플로팅 라벨 CSS 는 input/textarea 다음 형제 label 을 기준으로 동작하므로,
|
|
// DOM 상에서 input 이 먼저, label 이 나중에 와야 한다.
|
|
expect(firstChild!.getAttribute("data-testid")).toBe("preview-form-input");
|
|
expect(lastChild!.tagName).toBe("LABEL");
|
|
|
|
const label = lastChild as HTMLLabelElement;
|
|
expect(label.className).toContain("pb-form-label");
|
|
|
|
const input = getByLabelText("플로팅 라벨") as HTMLInputElement;
|
|
expect(input.className).toContain("pb-input");
|
|
// 플로팅 라벨 모드에서는 placeholder 를 공백으로 두고, 라벨만 인풋 안/위에서 이동시킨다.
|
|
expect(input.placeholder).toBe(" ");
|
|
// formFieldName 은 name/id/htmlFor 로 연결되어야 한다.
|
|
expect(input.name).toBe("floating_label");
|
|
expect(input.id).toBe("floating_label");
|
|
expect(label.htmlFor).toBe("floating_label");
|
|
});
|
|
|
|
it("formInput 플로팅 라벨에서 paddingY 값은 pb-form-field--floating 의 --pb-input-padding-y CSS 변수로 전달되어야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "form_input_floating_padding",
|
|
type: "formInput",
|
|
props: {
|
|
label: "플로팅 패딩",
|
|
formFieldName: "floating_padding",
|
|
labelDisplay: "floating",
|
|
paddingY: 16,
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const field = getByTestId("preview-form-input-wrapper") as HTMLDivElement;
|
|
// paddingY: 16px -> 1rem 으로 변환되어 CSS 변수에 설정되어야 한다.
|
|
expect(field.style.getPropertyValue("--pb-input-padding-y")).toBe("1rem");
|
|
});
|
|
|
|
it("formInput 플로팅 라벨에서 label 과 placeholder 가 같으면 placeholder 텍스트를 인풋 안에 표시하지 않아야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "form_input_floating_same_placeholder",
|
|
type: "formInput",
|
|
props: {
|
|
label: "이름",
|
|
formFieldName: "name",
|
|
labelDisplay: "floating",
|
|
placeholder: "이름",
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { getByLabelText } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const input = getByLabelText("이름") as HTMLInputElement;
|
|
// 플로팅 라벨 모드에서는 placeholder 를 공백으로 두고, 라벨만 인풋 안/위에서 이동시킨다.
|
|
expect(input.placeholder).toBe(" ");
|
|
});
|
|
|
|
it("formSelect 블록은 색상/패딩/너비/둥글기 스타일을 반영해야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "form_select_styles",
|
|
type: "formSelect",
|
|
props: {
|
|
label: "카테고리",
|
|
formFieldName: "category",
|
|
options: [
|
|
{ label: "A", value: "a" },
|
|
{ label: "B", value: "b" },
|
|
],
|
|
widthMode: "fixed",
|
|
widthPx: 240,
|
|
paddingX: 16,
|
|
paddingY: 8,
|
|
textColorCustom: "#00ff00",
|
|
fillColorCustom: "#123456",
|
|
strokeColorCustom: "#ff0000",
|
|
borderRadius: "lg",
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const wrapper = getByTestId("preview-form-select") as HTMLElement;
|
|
const select = wrapper.querySelector("select") as HTMLSelectElement | null;
|
|
expect(select).not.toBeNull();
|
|
|
|
// width: 240px / 16 = 15em
|
|
expect(wrapper.style.width).toBe("15em");
|
|
// padding-inline/block: 16px/8px -> 1em / 0.5em
|
|
expect(select!.style.paddingInline).toBe("1em");
|
|
expect(select!.style.paddingBlock).toBe("0.5em");
|
|
// 색상
|
|
expect(select!.style.color).toBe("rgb(0, 255, 0)");
|
|
expect(select!.style.backgroundColor).toBe("rgb(18, 52, 86)");
|
|
expect(select!.style.borderColor).toBe("rgb(255, 0, 0)");
|
|
// 둥글기
|
|
expect(select!.style.borderRadius).toBe("6px");
|
|
});
|
|
|
|
it("formSelect 블록에서 라벨 표시 방식이 hidden 이면 sr-only 클래스로 시각적으로 숨겨야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "form_select_hidden_label",
|
|
type: "formSelect",
|
|
props: {
|
|
label: "숨김 셀렉트 라벨",
|
|
formFieldName: "select_hidden",
|
|
options: [
|
|
{ label: "A", value: "a" },
|
|
],
|
|
labelDisplay: "hidden",
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { container } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const label = container.querySelector("label") as HTMLLabelElement | null;
|
|
expect(label).not.toBeNull();
|
|
const span = label!.querySelector("span");
|
|
expect(span).not.toBeNull();
|
|
expect((span as HTMLSpanElement).className).toContain("sr-only");
|
|
});
|
|
|
|
it("formSelect 블록의 select 요소는 formFieldName 을 name 속성으로 사용해야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "form_select_name_attr",
|
|
type: "formSelect",
|
|
props: {
|
|
label: "카테고리",
|
|
formFieldName: "category_field",
|
|
options: [
|
|
{ label: "A", value: "a" },
|
|
{ label: "B", value: "b" },
|
|
],
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const wrapper = getByTestId("preview-form-select") as HTMLElement;
|
|
const select = wrapper.querySelector("select") as HTMLSelectElement | null;
|
|
expect(select).not.toBeNull();
|
|
expect(select!.name).toBe("category_field");
|
|
});
|
|
|
|
it("formCheckbox 블록은 옵션 컨테이너에 색상/패딩/둥글기 스타일을 반영해야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "form_checkbox_styles",
|
|
type: "formCheckbox",
|
|
props: {
|
|
groupLabel: "옵션들",
|
|
formFieldName: "features",
|
|
options: [{ label: "옵션 1", value: "opt1" }],
|
|
widthMode: "fixed",
|
|
widthPx: 320,
|
|
paddingX: 8,
|
|
paddingY: 4,
|
|
optionGapPx: 16,
|
|
textColorCustom: "#ffffff",
|
|
fillColorCustom: "#123456",
|
|
strokeColorCustom: "#ff0000",
|
|
borderRadius: "lg",
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const group = getByTestId("preview-form-checkbox-group") as HTMLElement;
|
|
const firstLabel = getFirstLabel(group);
|
|
|
|
// width: 320px / 16 = 20em
|
|
expect(group.style.width).toBe("20em");
|
|
// 옵션 간 간격: 16px -> 1em
|
|
const optionsContainer = group.querySelector('[data-testid="preview-form-checkbox-options"]') as HTMLElement | null;
|
|
expect(optionsContainer).not.toBeNull();
|
|
expect(optionsContainer!.style.rowGap).toBe("1em");
|
|
|
|
// 옵션 컨테이너 스타일: padding, 배경, 보더, 둥글기
|
|
expect(firstLabel.style.paddingInline).toBe("0.5em");
|
|
expect(firstLabel.style.paddingBlock).toBe("0.25em");
|
|
expect(firstLabel.style.backgroundColor).toBe("rgb(18, 52, 86)");
|
|
expect(firstLabel.style.borderColor).toBe("rgb(255, 0, 0)");
|
|
expect(firstLabel.style.borderRadius).toBe("6px");
|
|
});
|
|
|
|
it("formRadio 블록의 옵션 input 은 type/name/value 및 FormBlock.requiredFieldIds 기반 required 를 가져야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "form_ctrl_radio",
|
|
type: "form",
|
|
props: {
|
|
kind: "contact",
|
|
submitTarget: "internal",
|
|
requiredFieldIds: ["radio_required"],
|
|
} as any,
|
|
},
|
|
{
|
|
id: "radio_required",
|
|
type: "formRadio",
|
|
props: {
|
|
groupLabel: "플랜",
|
|
formFieldName: "plan",
|
|
options: [
|
|
{ label: "A", value: "a" },
|
|
{ label: "B", value: "b" },
|
|
],
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const group = getByTestId("preview-form-radio-group") as HTMLElement;
|
|
const inputs = group.querySelectorAll("input[type='radio']");
|
|
expect(inputs.length).toBe(2);
|
|
|
|
const first = inputs[0] as HTMLInputElement;
|
|
const second = inputs[1] as HTMLInputElement;
|
|
|
|
expect(first.type).toBe("radio");
|
|
expect(first.name).toBe("plan");
|
|
expect(first.value).toBe("a");
|
|
expect(first.required).toBe(true);
|
|
|
|
expect(second.type).toBe("radio");
|
|
expect(second.name).toBe("plan");
|
|
expect(second.value).toBe("b");
|
|
expect(second.required).toBe(false);
|
|
});
|
|
|
|
it("formCheckbox 블록의 각 옵션 라벨은 builder.css 의 pb-form-option 클래스를 사용해야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "form_checkbox_pb_option_class",
|
|
type: "formCheckbox",
|
|
props: {
|
|
groupLabel: "옵션들",
|
|
formFieldName: "features",
|
|
options: [
|
|
{ label: "옵션 1", value: "opt1" },
|
|
{ label: "옵션 2", value: "opt2" },
|
|
],
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const group = getByTestId("preview-form-checkbox-group") as HTMLElement;
|
|
const optionLabels = group.querySelectorAll("label");
|
|
expect(optionLabels.length).toBeGreaterThan(0);
|
|
optionLabels.forEach((label) => {
|
|
expect((label as HTMLLabelElement).className).toContain("pb-form-option");
|
|
});
|
|
});
|
|
|
|
it("formCheckbox 블록에서 optionLayout 이 stacked 이면 옵션 컨테이너가 pb-form-options--stacked 클래스를 사용해야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "form_checkbox_layout_stacked",
|
|
type: "formCheckbox",
|
|
props: {
|
|
groupLabel: "옵션들",
|
|
formFieldName: "features",
|
|
options: [
|
|
{ label: "옵션 1", value: "opt1" },
|
|
{ label: "옵션 2", value: "opt2" },
|
|
],
|
|
optionLayout: "stacked",
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const optionsContainer = getByTestId(
|
|
"preview-form-checkbox-options",
|
|
) as HTMLElement;
|
|
expect(optionsContainer.className).toContain("pb-form-options");
|
|
expect(optionsContainer.className).toContain("pb-form-options--stacked");
|
|
});
|
|
|
|
it("formCheckbox 블록에서 optionLayout 이 inline 이면 옵션 컨테이너가 pb-form-options--inline 클래스를 사용해야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "form_checkbox_layout_inline",
|
|
type: "formCheckbox",
|
|
props: {
|
|
groupLabel: "옵션들",
|
|
formFieldName: "features",
|
|
options: [
|
|
{ label: "옵션 1", value: "opt1" },
|
|
{ label: "옵션 2", value: "opt2" },
|
|
],
|
|
optionLayout: "inline",
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const optionsContainer = getByTestId(
|
|
"preview-form-checkbox-options",
|
|
) as HTMLElement;
|
|
expect(optionsContainer.className).toContain("pb-form-options");
|
|
expect(optionsContainer.className).toContain("pb-form-options--inline");
|
|
});
|
|
|
|
it("formCheckbox 블록에서 그룹 타이틀 표시 방식이 hidden 이면 sr-only 클래스로 시각적으로 숨겨야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "form_checkbox_hidden_group_label",
|
|
type: "formCheckbox",
|
|
props: {
|
|
groupLabel: "숨김 체크박스 그룹",
|
|
formFieldName: "features_hidden",
|
|
options: [{ label: "옵션 1", value: "opt1" }],
|
|
groupLabelDisplay: "hidden",
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);
|
|
const group = getByTestId("preview-form-checkbox-group") as HTMLElement;
|
|
const spans = group.querySelectorAll("span");
|
|
const spanArray = Array.from(spans) as HTMLSpanElement[];
|
|
const groupLabelSpan = spanArray.find((s) => s.textContent === "숨김 체크박스 그룹");
|
|
expect(groupLabelSpan).toBeTruthy();
|
|
expect(groupLabelSpan!.className).toContain("sr-only");
|
|
});
|
|
|
|
it("formCheckbox 블록의 옵션 input 은 type/ name/value 및 FormBlock.requiredFieldIds 기반 required 를 가져야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "form_ctrl_checkbox",
|
|
type: "form",
|
|
props: {
|
|
kind: "contact",
|
|
submitTarget: "internal",
|
|
requiredFieldIds: ["checkbox_required"],
|
|
} as any,
|
|
},
|
|
{
|
|
id: "checkbox_required",
|
|
type: "formCheckbox",
|
|
props: {
|
|
groupLabel: "기능",
|
|
formFieldName: "feature",
|
|
options: [
|
|
{ label: "A", value: "a" },
|
|
{ label: "B", value: "b" },
|
|
],
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const group = getByTestId("preview-form-checkbox-group") as HTMLElement;
|
|
const inputs = group.querySelectorAll("input[type='checkbox']");
|
|
expect(inputs.length).toBe(2);
|
|
|
|
const first = inputs[0] as HTMLInputElement;
|
|
const second = inputs[1] as HTMLInputElement;
|
|
|
|
expect(first.type).toBe("checkbox");
|
|
expect(first.name).toBe("feature");
|
|
expect(first.value).toBe("a");
|
|
expect(first.required).toBe(true);
|
|
|
|
expect(second.type).toBe("checkbox");
|
|
expect(second.name).toBe("feature");
|
|
expect(second.value).toBe("b");
|
|
expect(second.required).toBe(false);
|
|
});
|
|
|
|
it("formRadio 블록에서 그룹 타이틀 레이아웃이 inline 이면 그룹 컨테이너가 가로 정렬되고 세로 중앙 정렬 및 columnGap 이 labelGapPx 로 반영되어야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "form_radio_label_inline_preview",
|
|
type: "formRadio",
|
|
props: {
|
|
groupLabel: "플랜 인라인",
|
|
formFieldName: "plan-inline",
|
|
groupLabelDisplay: "visible",
|
|
labelLayout: "inline",
|
|
labelGapPx: 16,
|
|
options: [
|
|
{ label: "플랜 A", value: "a" },
|
|
{ label: "플랜 B", value: "b" },
|
|
],
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);
|
|
const group = getByTestId("preview-form-radio-group") as HTMLElement;
|
|
|
|
expect(group.className).toContain("flex");
|
|
expect(group.className).toContain("flex-row");
|
|
expect(group.className).toContain("items-center");
|
|
// labelGapPx: 16px → 1em
|
|
expect(group.style.columnGap).toBe("1em");
|
|
});
|
|
|
|
it("formCheckbox 블록에서 그룹 타이틀 레이아웃이 inline 이면 그룹 컨테이너가 가로 정렬되고 세로 중앙 정렬 및 columnGap 이 labelGapPx 로 반영되어야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "form_checkbox_label_inline_preview",
|
|
type: "formCheckbox",
|
|
props: {
|
|
groupLabel: "체크 인라인",
|
|
formFieldName: "features-inline",
|
|
groupLabelDisplay: "visible",
|
|
labelLayout: "inline",
|
|
labelGapPx: 12,
|
|
options: [
|
|
{ label: "옵션 1", value: "opt1" },
|
|
{ label: "옵션 2", value: "opt2" },
|
|
],
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);
|
|
const group = getByTestId("preview-form-checkbox-group") as HTMLElement;
|
|
|
|
expect(group.className).toContain("flex");
|
|
expect(group.className).toContain("flex-row");
|
|
expect(group.className).toContain("items-center");
|
|
// labelGapPx: 12px → 0.75em
|
|
expect(group.style.columnGap).toBe("0.75em");
|
|
});
|
|
|
|
it("formRadio 블록은 옵션 컨테이너에 색상/패딩/둥글기 스타일을 반영해야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "form_radio_styles",
|
|
type: "formRadio",
|
|
props: {
|
|
groupLabel: "플랜",
|
|
formFieldName: "plan",
|
|
options: [{ label: "플랜 A", value: "a" }],
|
|
widthMode: "fixed",
|
|
widthPx: 320,
|
|
paddingX: 8,
|
|
paddingY: 4,
|
|
optionGapPx: 16,
|
|
textColorCustom: "#ffffff",
|
|
fillColorCustom: "#123456",
|
|
strokeColorCustom: "#ff0000",
|
|
borderRadius: "lg",
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const group = getByTestId("preview-form-radio-group") as HTMLElement;
|
|
const firstLabel = getFirstLabel(group);
|
|
|
|
// width: 320px / 16 = 20em
|
|
expect(group.style.width).toBe("20em");
|
|
// 옵션 간 간격: 16px -> 1em
|
|
const optionsContainer = group.querySelector('[data-testid="preview-form-radio-options"]') as HTMLElement | null;
|
|
expect(optionsContainer).not.toBeNull();
|
|
expect(optionsContainer!.style.rowGap).toBe("1em");
|
|
|
|
// 옵션 컨테이너 스타일: padding, 배경, 보더, 둥글기
|
|
expect(firstLabel.style.paddingInline).toBe("0.5em");
|
|
expect(firstLabel.style.paddingBlock).toBe("0.25em");
|
|
expect(firstLabel.style.backgroundColor).toBe("rgb(18, 52, 86)");
|
|
expect(firstLabel.style.borderColor).toBe("rgb(255, 0, 0)");
|
|
expect(firstLabel.style.borderRadius).toBe("6px");
|
|
});
|
|
|
|
it("formRadio 블록의 각 옵션 라벨은 builder.css 의 pb-form-option 클래스를 사용해야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "form_radio_pb_option_class",
|
|
type: "formRadio",
|
|
props: {
|
|
groupLabel: "플랜",
|
|
formFieldName: "plan",
|
|
options: [
|
|
{ label: "플랜 A", value: "a" },
|
|
{ label: "플랜 B", value: "b" },
|
|
],
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { getByTestId } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const group = getByTestId("preview-form-radio-group") as HTMLElement;
|
|
const optionLabels = group.querySelectorAll("label");
|
|
expect(optionLabels.length).toBeGreaterThan(0);
|
|
optionLabels.forEach((label) => {
|
|
expect((label as HTMLLabelElement).className).toContain("pb-form-option");
|
|
});
|
|
});
|
|
});
|