Playwright E2E CI 설정 및 새 블록 타입 테스트 추가
CI / test (push) Failing after 6m6s
CI / pr_and_merge (push) Has been skipped

This commit is contained in:
2025-11-19 00:52:15 +09:00
parent 0621f95a5b
commit 86cf8f7712
4 changed files with 238 additions and 7 deletions
+163 -1
View File
@@ -25,6 +25,8 @@ import type {
ButtonBlockProps,
ImageBlockProps,
SectionBlockProps,
DividerBlockProps,
ListBlockProps,
} from "@/features/editor/state/editorStore";
export default function EditorPage() {
@@ -33,6 +35,8 @@ export default function EditorPage() {
const addTextBlock = useEditorStore((state) => state.addTextBlock);
const addButtonBlock = useEditorStore((state) => state.addButtonBlock);
const addImageBlock = useEditorStore((state) => state.addImageBlock);
const addDividerBlock = useEditorStore((state) => state.addDividerBlock);
const addListBlock = useEditorStore((state) => state.addListBlock);
const addSectionBlock = useEditorStore((state) => state.addSectionBlock);
const addHeroTemplateSection = useEditorStore((state) => state.addHeroTemplateSection);
const addFeaturesTemplateSection = useEditorStore((state) => state.addFeaturesTemplateSection);
@@ -349,6 +353,20 @@ export default function EditorPage() {
>
</button>
<button
type="button"
className="w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-left text-xs hover:bg-slate-800"
onClick={addDividerBlock}
>
</button>
<button
type="button"
className="w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-left text-xs hover:bg-slate-800"
onClick={addListBlock}
>
</button>
<button
type="button"
className="w-full rounded border border-slate-700 bg-slate-900 px-3 py-2 text-left text-xs hover:bg-slate-800"
@@ -423,7 +441,7 @@ export default function EditorPage() {
</button>
</div>
<p className="text-xs text-slate-500">
Text / Image / Button / Section .
Text / Image / Button / Divider / List / Section .
</p>
</aside>
<div
@@ -535,6 +553,50 @@ export default function EditorPage() {
);
}
if (selectedBlock.type === "divider") {
const dividerProps = selectedBlock.props as DividerBlockProps;
return (
<>
<div className="space-y-1">
<label className="flex flex-col gap-1 text-xs text-slate-400">
<span></span>
<select
className="rounded border border-slate-700 bg-slate-900 px-2 py-1 text-xs outline-none focus:border-sky-500"
aria-label="구분선 정렬"
value={dividerProps.align}
onChange={(e) => {
const value = e.target.value as DividerBlockProps["align"];
updateBlock(selectedBlockId, { align: value } as any);
}}
>
<option value="left"></option>
<option value="center"></option>
<option value="right"></option>
</select>
</label>
</div>
<div className="space-y-1">
<label className="flex flex-col gap-1 text-xs text-slate-400">
<span></span>
<select
className="rounded border border-slate-700 bg-slate-900 px-2 py-1 text-xs outline-none focus:border-sky-500"
aria-label="구분선 두께"
value={dividerProps.thickness}
onChange={(e) => {
const value = e.target.value as DividerBlockProps["thickness"];
updateBlock(selectedBlockId, { thickness: value } as any);
}}
>
<option value="thin"></option>
<option value="medium"></option>
</select>
</label>
</div>
</>
);
}
if (selectedBlock.type === "image") {
const imageProps = selectedBlock.props as ImageBlockProps;
@@ -664,6 +726,63 @@ export default function EditorPage() {
);
}
if (selectedBlock.type === "list") {
const listProps = selectedBlock.props as ListBlockProps;
const firstItem = listProps.items && listProps.items.length > 0 ? listProps.items[0] : "";
return (
<>
<div className="space-y-1">
<label className="flex flex-col gap-1 text-xs text-slate-400">
<span> </span>
<input
className="w-full rounded border border-slate-700 bg-slate-900 px-2 py-1 text-xs outline-none focus:border-sky-500"
aria-label="리스트 첫 번째 아이템"
value={firstItem}
onChange={(e) => {
const nextItems = [...(listProps.items ?? [])];
if (nextItems.length === 0) {
nextItems.push(e.target.value);
} else {
nextItems[0] = e.target.value;
}
updateBlock(selectedBlockId, { items: nextItems } as any);
}}
/>
</label>
</div>
<div className="flex items-center justify-between text-xs text-slate-400">
<label className="flex items-center gap-2">
<input
type="checkbox"
checked={listProps.ordered}
onChange={(e) => {
updateBlock(selectedBlockId, { ordered: e.target.checked } as any);
}}
/>
<span> </span>
</label>
<label className="flex items-center gap-2">
<span></span>
<select
className="rounded border border-slate-700 bg-slate-900 px-2 py-1 text-xs outline-none focus:border-sky-500"
aria-label="리스트 정렬"
value={listProps.align}
onChange={(e) => {
const value = e.target.value as ListBlockProps["align"];
updateBlock(selectedBlockId, { align: value } as any);
}}
>
<option value="left"></option>
<option value="center"></option>
<option value="right"></option>
</select>
</label>
</div>
</>
);
}
if (selectedBlock.type === "button") {
const buttonProps = selectedBlock.props as ButtonBlockProps;
@@ -872,6 +991,24 @@ function SortableEditorBlock({
} else if (block.type === "image") {
alignClass = "text-left";
sizeClass = "text-base";
} else if (block.type === "divider") {
const dividerProps = block.props as DividerBlockProps;
alignClass =
dividerProps.align === "center"
? "text-center"
: dividerProps.align === "right"
? "text-right"
: "text-left";
sizeClass = "text-base";
} else if (block.type === "list") {
const listProps = block.props as ListBlockProps;
alignClass =
listProps.align === "center"
? "text-center"
: listProps.align === "right"
? "text-right"
: "text-left";
sizeClass = "text-base";
} else {
// 섹션 블록: 텍스트 크기 대신 섹션 자체에 패딩/배경을 입히므로 텍스트 클래스는 기본값만
alignClass = "text-left";
@@ -964,6 +1101,31 @@ function SortableEditorBlock({
</div>
);
})()}
{block.type === "divider" && (() => {
const dividerProps = block.props as DividerBlockProps;
const thicknessClass = dividerProps.thickness === "medium" ? "border-t-2" : "border-t";
return (
<div className="w-full py-2">
<div className={`border-slate-700 ${thicknessClass}`} />
</div>
);
})()}
{block.type === "list" && (() => {
const listProps = block.props as ListBlockProps;
const items = listProps.items && listProps.items.length > 0 ? listProps.items : ["리스트 아이템 1"];
const ListTag = (listProps.ordered ? "ol" : "ul") as "ol" | "ul";
return (
<div className="w-full py-1">
<ListTag className="list-inside list-disc space-y-1 text-xs">
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ListTag>
</div>
);
})()}
{block.type === "section" && (() => {
const sectionProps = block.props as SectionBlockProps;