Playwright E2E CI 설정 및 새 블록 타입 테스트 추가
This commit is contained in:
+163
-1
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user