video 블록 및 리펙터링
CI / pr_and_merge (push) Has been skipped
CI / test (push) Failing after 46m46s
CI / test (pull_request) Failing after 43m8s
CI / pr_and_merge (pull_request) Has been skipped

This commit is contained in:
2025-11-27 10:07:59 +09:00
parent 672cca5271
commit 48e13d2a75
223 changed files with 8979 additions and 2125 deletions
+3 -285
View File
@@ -686,288 +686,10 @@ describe("editorStore", () => {
expect(buttonBlocks.length).toBeGreaterThanOrEqual(1);
const section = sectionBlocks[0] as any;
// 템플릿에서 생성된 텍스트/버튼 블록은 모두 동일 섹션의 첫 컬럼에 배치된다고 가정한다.
const columns = (section.props as any).columns;
expect(Array.isArray(columns)).toBe(true);
expect(columns.length).toBeGreaterThanOrEqual(1);
const firstColumnId = columns[0].id;
textBlocks.forEach((tb: any) => {
expect(tb.sectionId).toBe(section.id);
expect(tb.columnId).toBe(firstColumnId);
});
buttonBlocks.forEach((bb: any) => {
expect(bb.sectionId).toBe(section.id);
expect(bb.columnId).toBe(firstColumnId);
});
// 마지막으로 생성된 블록(예: CTA 버튼)이 선택되어 있다고 가정한다.
expect(selectedBlockId).toBe(blocks[blocks.length - 1].id);
});
it("선택된 섹션이 있을 때 Team 템플릿 섹션 액션을 호출하면 해당 섹션이 새 Team 템플릿으로 교체되어야 한다", () => {
const store = createEditorStore();
store.getState().addTeamTemplateSection();
let { blocks, selectedBlockId } = store.getState();
const sectionBlocks = blocks.filter((b) => b.type === "section");
expect(sectionBlocks).toHaveLength(1);
const section = sectionBlocks[0] as any;
const sectionId = section.id;
const oldTextIds = blocks.filter((b) => b.type === "text").map((b) => b.id);
const originalLastSelectedId = selectedBlockId;
store.getState().selectBlock(sectionId);
store.getState().addTeamTemplateSection();
({ blocks, selectedBlockId } = store.getState());
const sectionBlocksAfter = blocks.filter((b) => b.type === "section");
expect(sectionBlocksAfter).toHaveLength(1);
const replacedSection = sectionBlocksAfter[0] as any;
expect(replacedSection.id).toBe(sectionId);
const textBlocksAfter = blocks.filter((b) => b.type === "text") as any[];
expect(textBlocksAfter.length).toBeGreaterThanOrEqual(1);
textBlocksAfter.forEach((tb) => {
expect(tb.sectionId).toBe(sectionId);
});
const allIdsAfter = blocks.map((b) => b.id);
oldTextIds.forEach((id) => {
expect(allIdsAfter).not.toContain(id);
});
expect(selectedBlockId).toBe(blocks[blocks.length - 1].id);
expect(selectedBlockId).not.toBe(originalLastSelectedId);
});
it("선택된 섹션이 있을 때 Blog 템플릿 섹션 액션을 호출하면 해당 섹션이 새 Blog 템플릿으로 교체되어야 한다", () => {
const store = createEditorStore();
store.getState().addBlogTemplateSection();
let { blocks, selectedBlockId } = store.getState();
const sectionBlocks = blocks.filter((b) => b.type === "section");
expect(sectionBlocks).toHaveLength(1);
const section = sectionBlocks[0] as any;
const sectionId = section.id;
const oldTextIds = blocks.filter((b) => b.type === "text").map((b) => b.id);
const originalLastSelectedId = selectedBlockId;
store.getState().selectBlock(sectionId);
store.getState().addBlogTemplateSection();
({ blocks, selectedBlockId } = store.getState());
const sectionBlocksAfter = blocks.filter((b) => b.type === "section");
expect(sectionBlocksAfter).toHaveLength(1);
const replacedSection = sectionBlocksAfter[0] as any;
expect(replacedSection.id).toBe(sectionId);
const textBlocksAfter = blocks.filter((b) => b.type === "text") as any[];
expect(textBlocksAfter.length).toBeGreaterThanOrEqual(1);
textBlocksAfter.forEach((tb) => {
expect(tb.sectionId).toBe(sectionId);
});
const allIdsAfter = blocks.map((b) => b.id);
oldTextIds.forEach((id) => {
expect(allIdsAfter).not.toContain(id);
});
expect(selectedBlockId).toBe(blocks[blocks.length - 1].id);
expect(selectedBlockId).not.toBe(originalLastSelectedId);
});
it("선택된 섹션이 있을 때 Hero 템플릿 섹션 액션을 호출하면 해당 섹션이 새 Hero 템플릿으로 교체되어야 한다", () => {
const store = createEditorStore();
// 1) 먼저 Hero 템플릿 섹션을 하나 추가한다.
store.getState().addHeroTemplateSection();
let { blocks, selectedBlockId } = store.getState();
const sectionBlocks = blocks.filter((b) => b.type === "section");
expect(sectionBlocks).toHaveLength(1);
const section = sectionBlocks[0] as any;
const sectionId = section.id;
// 기존 텍스트/버튼 블록들의 id 를 기록해 둔다.
const oldTextIds = blocks.filter((b) => b.type === "text").map((b) => b.id);
const oldButtonIds = blocks.filter((b) => b.type === "button").map((b) => b.id);
const originalLastSelectedId = selectedBlockId;
// 2) 해당 섹션을 선택한 상태에서 다시 Hero 템플릿 섹션 액션을 호출한다.
store.getState().selectBlock(sectionId);
store.getState().addHeroTemplateSection();
({ blocks, selectedBlockId } = store.getState());
const sectionBlocksAfter = blocks.filter((b) => b.type === "section");
expect(sectionBlocksAfter).toHaveLength(1);
const replacedSection = sectionBlocksAfter[0] as any;
// 교체 후에도 섹션 id 는 동일하게 유지되어야 한다 (섹션 컨테이너 재사용).
expect(replacedSection.id).toBe(sectionId);
const columns = (replacedSection.props as any).columns;
expect(Array.isArray(columns)).toBe(true);
expect(columns.length).toBeGreaterThanOrEqual(1);
const firstColumnId = columns[0].id;
const textBlocksAfter = blocks.filter((b) => b.type === "text") as any[];
const buttonBlocksAfter = blocks.filter((b) => b.type === "button") as any[];
// 새 텍스트/버튼 블록이 최소 1개 이상 존재해야 한다.
expect(textBlocksAfter.length).toBeGreaterThanOrEqual(1);
expect(buttonBlocksAfter.length).toBeGreaterThanOrEqual(1);
// 새 텍스트/버튼 블록은 모두 동일 섹션의 첫 컬럼에 배치되어야 한다.
textBlocksAfter.forEach((tb) => {
expect(tb.sectionId).toBe(sectionId);
expect(tb.columnId).toBe(firstColumnId);
});
buttonBlocksAfter.forEach((bb) => {
expect(bb.sectionId).toBe(sectionId);
expect(bb.columnId).toBe(firstColumnId);
});
// 기존 텍스트/버튼 블록 id 들은 모두 제거되어야 한다.
const allIdsAfter = blocks.map((b) => b.id);
[...oldTextIds, ...oldButtonIds].forEach((id) => {
expect(allIdsAfter).not.toContain(id);
});
// 선택 상태는 새로 생성된 템플릿의 마지막 블록을 가리켜야 한다.
expect(selectedBlockId).toBe(blocks[blocks.length - 1].id);
expect(selectedBlockId).not.toBe(originalLastSelectedId);
});
it("Blog 템플릿 섹션을 추가하면 3개의 포스트 카드(제목/요약)가 생성되어야 한다", () => {
const store = createEditorStore();
store.getState().addBlogTemplateSection();
const { blocks, selectedBlockId } = store.getState();
const sectionBlocks = blocks.filter((b) => b.type === "section");
expect(sectionBlocks).toHaveLength(1);
const section = sectionBlocks[0] as any;
const columns = (section.props as any).columns;
expect(Array.isArray(columns)).toBe(true);
expect(columns.length).toBe(3);
const textBlocks = blocks.filter((b) => b.type === "text") as any[];
// 3개의 포스트 카드에 대해 각 컬럼에 최소 2개 텍스트(제목 + 요약)를 가정한다.
expect(textBlocks.length).toBeGreaterThanOrEqual(6);
textBlocks.forEach((tb) => {
expect(tb.sectionId).toBe(section.id);
expect(columns.some((col: any) => col.id === tb.columnId)).toBe(true);
});
expect(selectedBlockId).toBe(blocks[blocks.length - 1].id);
});
it("Team 템플릿 섹션을 추가하면 3명의 팀 카드(이름/역할/소개)가 생성되어야 한다", () => {
const store = createEditorStore();
store.getState().addTeamTemplateSection();
const { blocks, selectedBlockId } = store.getState();
const sectionBlocks = blocks.filter((b) => b.type === "section");
expect(sectionBlocks).toHaveLength(1);
const section = sectionBlocks[0] as any;
const columns = (section.props as any).columns;
expect(Array.isArray(columns)).toBe(true);
expect(columns.length).toBe(3);
const textBlocks = blocks.filter((b) => b.type === "text") as any[];
// 3명의 팀원 카드에 대해 각 컬럼에 최소 3개 텍스트(이름/역할/소개)를 가정한다.
expect(textBlocks.length).toBeGreaterThanOrEqual(9);
textBlocks.forEach((tb) => {
expect(tb.sectionId).toBe(section.id);
expect(columns.some((col: any) => col.id === tb.columnId)).toBe(true);
});
expect(selectedBlockId).toBe(blocks[blocks.length - 1].id);
});
it("Footer 템플릿 섹션을 추가하면 링크/카피라이트 텍스트가 포함된 섹션이 생성되어야 한다", () => {
const store = createEditorStore();
store.getState().addFooterTemplateSection();
const { blocks, selectedBlockId } = store.getState();
const sectionBlocks = blocks.filter((b) => b.type === "section");
expect(sectionBlocks).toHaveLength(1);
const section = sectionBlocks[0] as any;
const columns = (section.props as any).columns;
expect(Array.isArray(columns)).toBe(true);
expect(columns.length).toBeGreaterThanOrEqual(1);
const firstColumnId = columns[0].id;
const textBlocks = blocks.filter((b) => b.type === "text") as any[];
expect(textBlocks.length).toBeGreaterThanOrEqual(2);
textBlocks.forEach((tb) => {
expect(tb.sectionId).toBe(section.id);
expect(tb.columnId).toBe(firstColumnId);
});
expect(selectedBlockId).toBe(blocks[blocks.length - 1].id);
});
it("Features 템플릿 섹션을 추가하면 3컬럼 섹션과 각 컬럼의 제목/설명 텍스트가 생성되어야 한다", () => {
const store = createEditorStore();
// Features 템플릿 액션을 호출한다고 가정한다.
store.getState().addFeaturesTemplateSection();
const { blocks, selectedBlockId } = store.getState();
const sectionBlocks = blocks.filter((b) => b.type === "section");
expect(sectionBlocks).toHaveLength(1);
const section = sectionBlocks[0] as any;
const columns = (section.props as any).columns;
expect(Array.isArray(columns)).toBe(true);
expect(columns.length).toBe(3);
const textBlocks = blocks.filter((b) => b.type === "text") as any[];
// 최소 3개의 feature 항목(제목/설명)을 가정하고, 각 컬럼에는 2개의 텍스트(제목+설명)가 있다고 본다.
expect(textBlocks.length).toBeGreaterThanOrEqual(6);
// 모든 텍스트 블록이 동일 섹션에 속하는지 확인한다.
textBlocks.forEach((tb) => {
expect(tb.sectionId).toBe(section.id);
expect(columns.some((col: any) => col.id === tb.columnId)).toBe(true);
@@ -992,8 +714,6 @@ describe("editorStore", () => {
expect(Array.isArray(columns)).toBe(true);
expect(columns.length).toBeGreaterThanOrEqual(1);
const firstColumnId = columns[0].id;
const textBlocks = blocks.filter((b) => b.type === "text") as any[];
const buttonBlocks = blocks.filter((b) => b.type === "button") as any[];
@@ -1002,12 +722,12 @@ describe("editorStore", () => {
textBlocks.forEach((tb) => {
expect(tb.sectionId).toBe(section.id);
expect(tb.columnId).toBe(firstColumnId);
expect(columns.some((col: any) => col.id === tb.columnId)).toBe(true);
});
buttonBlocks.forEach((bb) => {
expect(bb.sectionId).toBe(section.id);
expect(bb.columnId).toBe(firstColumnId);
expect(columns.some((col: any) => col.id === bb.columnId)).toBe(true);
});
expect(selectedBlockId).toBe(blocks[blocks.length - 1].id);
@@ -1028,8 +748,6 @@ describe("editorStore", () => {
expect(Array.isArray(columns)).toBe(true);
expect(columns.length).toBeGreaterThanOrEqual(1);
const firstColumnId = columns[0].id;
const textBlocks = blocks.filter((b) => b.type === "text") as any[];
// 최소 3개의 FAQ 항목(질문+답변 쌍)을 가정한다.
@@ -1037,7 +755,7 @@ describe("editorStore", () => {
textBlocks.forEach((tb) => {
expect(tb.sectionId).toBe(section.id);
expect(tb.columnId).toBe(firstColumnId);
expect(columns.some((col: any) => col.id === tb.columnId)).toBe(true);
});
expect(selectedBlockId).toBe(blocks[blocks.length - 1].id);