에디터 정리 및 버그 수정
This commit is contained in:
@@ -1164,4 +1164,278 @@ describe("editorStore", () => {
|
||||
expect(custom.projectConfig.canvasPreset).toBe("custom");
|
||||
expect(custom.projectConfig.canvasWidthPx).toBe(1024);
|
||||
});
|
||||
|
||||
it("여러 블록/템플릿 추가 액션은 history 에 스냅샷을 기록하고 undo/redo 로 되돌릴 수 있어야 한다", () => {
|
||||
const actionNames = [
|
||||
"addTextBlock",
|
||||
"addButtonBlock",
|
||||
"addImageBlock",
|
||||
"addVideoBlock",
|
||||
"addDividerBlock",
|
||||
"addListBlock",
|
||||
"addSectionBlock",
|
||||
"addFormBlock",
|
||||
"addFormInputBlock",
|
||||
"addFormSelectBlock",
|
||||
"addFormCheckboxBlock",
|
||||
"addFormRadioBlock",
|
||||
"addHeroTemplateSection",
|
||||
"addFeaturesTemplateSection",
|
||||
"addCtaTemplateSection",
|
||||
"addFaqTemplateSection",
|
||||
"addPricingTemplateSection",
|
||||
"addTestimonialsTemplateSection",
|
||||
"addBlogTemplateSection",
|
||||
"addTeamTemplateSection",
|
||||
"addFooterTemplateSection",
|
||||
] as const;
|
||||
|
||||
actionNames.forEach((name) => {
|
||||
const store = createEditorStore();
|
||||
const stateAny = store.getState() as any;
|
||||
|
||||
expect(stateAny.blocks).toHaveLength(0);
|
||||
expect(stateAny.history).toHaveLength(0);
|
||||
|
||||
// 각 액션을 한 번 호출하면 직전 blocks 스냅샷이 history 에 쌓여야 한다.
|
||||
stateAny[name]();
|
||||
|
||||
const after = store.getState() as any;
|
||||
|
||||
// 최소 한 개 이상의 블록이 생성되어야 한다.
|
||||
expect(after.blocks.length).toBeGreaterThan(0);
|
||||
// 직전 상태 스냅샷이 history 에 1개 쌓여야 한다.
|
||||
if (after.history.length !== 1) {
|
||||
throw new Error(`history length mismatch for action ${name}: ${after.history.length}`);
|
||||
}
|
||||
|
||||
const blocksAfterAdd = after.blocks;
|
||||
const expectedLength = blocksAfterAdd.length;
|
||||
|
||||
stateAny.undo();
|
||||
const afterUndo = store.getState() as any;
|
||||
// 처음 상태가 빈 배열이었으므로 undo 후에는 다시 0개가 되어야 한다.
|
||||
expect(afterUndo.blocks.length).toBe(0);
|
||||
|
||||
stateAny.redo();
|
||||
const afterRedo = store.getState() as any;
|
||||
expect(afterRedo.blocks.length).toBe(expectedLength);
|
||||
// redo 이후 블록 타입 배열도 대략 동일해야 한다.
|
||||
expect(afterRedo.blocks.map((b: any) => b.type)).toEqual(
|
||||
blocksAfterAdd.map((b: any) => b.type),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("updateBlock 으로 블록 속성을 변경한 뒤 undo 하면 이전 속성으로 되돌아가야 한다", () => {
|
||||
const store = createEditorStore();
|
||||
|
||||
store.getState().addTextBlock();
|
||||
|
||||
let { blocks, history } = store.getState();
|
||||
expect(blocks).toHaveLength(1);
|
||||
|
||||
const blockId = blocks[0].id;
|
||||
const initialText = (blocks[0].props as TextBlockProps).text;
|
||||
expect(history.length).toBe(1);
|
||||
|
||||
store.getState().updateBlock(blockId, { text: "변경된 텍스트" } as any);
|
||||
|
||||
({ blocks, history } = store.getState());
|
||||
expect((blocks[0].props as TextBlockProps).text).toBe("변경된 텍스트");
|
||||
// updateBlock 이 호출되면서 history 스택이 1개 늘어나야 한다.
|
||||
expect(history.length).toBe(2);
|
||||
|
||||
store.getState().undo();
|
||||
|
||||
({ blocks, history } = store.getState());
|
||||
expect((blocks[0].props as TextBlockProps).text).toBe(initialText);
|
||||
expect(history.length).toBe(1);
|
||||
});
|
||||
|
||||
it("removeBlock 호출 후 undo 하면 삭제된 블록이 복원되어야 한다", () => {
|
||||
const store = createEditorStore();
|
||||
|
||||
store.getState().addTextBlock();
|
||||
store.getState().addTextBlock();
|
||||
|
||||
let { blocks, history } = store.getState();
|
||||
expect(blocks).toHaveLength(2);
|
||||
|
||||
const firstId = blocks[0].id;
|
||||
const secondId = blocks[1].id;
|
||||
|
||||
(store.getState() as any).removeBlock(secondId);
|
||||
|
||||
({ blocks, history } = store.getState());
|
||||
expect(blocks.map((b) => b.id)).toEqual([firstId]);
|
||||
// 텍스트 블록 2개 추가(2번) + removeBlock(1번) 이므로 history 스택 길이는 3이어야 한다.
|
||||
expect(history.length).toBe(3);
|
||||
|
||||
(store.getState() as any).undo();
|
||||
|
||||
({ blocks } = store.getState());
|
||||
expect(blocks.map((b) => b.id)).toEqual([firstId, secondId]);
|
||||
});
|
||||
|
||||
it("duplicateBlock 호출 후 undo 하면 복제된 블록이 제거되어야 한다", () => {
|
||||
const store = createEditorStore();
|
||||
|
||||
store.getState().addTextBlock();
|
||||
|
||||
let { blocks } = store.getState();
|
||||
expect(blocks).toHaveLength(1);
|
||||
|
||||
const originalId = blocks[0].id;
|
||||
|
||||
(store.getState() as any).duplicateBlock(originalId);
|
||||
|
||||
({ blocks } = store.getState());
|
||||
expect(blocks).toHaveLength(2);
|
||||
|
||||
(store.getState() as any).undo();
|
||||
|
||||
({ blocks } = store.getState());
|
||||
expect(blocks).toHaveLength(1);
|
||||
expect(blocks[0].id).toBe(originalId);
|
||||
});
|
||||
|
||||
it("reorderBlocks 호출 후 undo/redo 로 블록 순서를 되돌릴 수 있어야 한다", () => {
|
||||
const store = createEditorStore();
|
||||
|
||||
store.getState().addTextBlock();
|
||||
store.getState().addTextBlock();
|
||||
store.getState().addTextBlock();
|
||||
|
||||
let { blocks } = store.getState();
|
||||
const [aId, bId, cId] = blocks.map((b) => b.id);
|
||||
|
||||
// B 를 맨 앞으로 이동시킨다.
|
||||
store.getState().reorderBlocks(bId, aId);
|
||||
|
||||
({ blocks } = store.getState());
|
||||
expect(blocks.map((b) => b.id)).toEqual([bId, aId, cId]);
|
||||
|
||||
store.getState().undo();
|
||||
|
||||
({ blocks } = store.getState());
|
||||
expect(blocks.map((b) => b.id)).toEqual([aId, bId, cId]);
|
||||
|
||||
store.getState().redo();
|
||||
|
||||
({ blocks } = store.getState());
|
||||
expect(blocks.map((b) => b.id)).toEqual([bId, aId, cId]);
|
||||
});
|
||||
|
||||
it("moveBlock 호출 후 undo/redo 로 블록 위치(sectionId/columnId)를 되돌릴 수 있어야 한다", () => {
|
||||
const store = createEditorStore();
|
||||
|
||||
// 섹션 블록과 텍스트 블록을 준비한다.
|
||||
store.getState().addSectionBlock();
|
||||
let { blocks } = store.getState();
|
||||
const sectionBlock = blocks.find((b) => b.type === "section")!;
|
||||
const sectionProps = sectionBlock.props as any;
|
||||
|
||||
// 섹션을 2컬럼 레이아웃으로 변경한다.
|
||||
const updatedColumns = [
|
||||
{ id: `${sectionBlock.id}_col_1`, span: 6 },
|
||||
{ id: `${sectionBlock.id}_col_2`, span: 6 },
|
||||
];
|
||||
|
||||
store.getState().updateBlock(sectionBlock.id, { columns: updatedColumns } as any);
|
||||
|
||||
store.getState().selectBlock(sectionBlock.id);
|
||||
store.getState().addTextBlock();
|
||||
|
||||
({ blocks } = store.getState());
|
||||
const textBlock = blocks.find((b) => b.type === "text") as any;
|
||||
expect(textBlock.sectionId).toBe(sectionBlock.id);
|
||||
expect(textBlock.columnId).toBe(updatedColumns[0].id ?? sectionProps.columns[0].id);
|
||||
|
||||
// 두 번째 컬럼으로 이동
|
||||
store.getState().moveBlock(textBlock.id, sectionBlock.id, updatedColumns[1].id);
|
||||
|
||||
({ blocks } = store.getState());
|
||||
let moved = blocks.find((b) => b.id === textBlock.id) as any;
|
||||
expect(moved.columnId).toBe(updatedColumns[1].id);
|
||||
|
||||
store.getState().undo();
|
||||
|
||||
({ blocks } = store.getState());
|
||||
moved = blocks.find((b) => b.id === textBlock.id) as any;
|
||||
expect(moved.columnId).toBe(updatedColumns[0].id ?? sectionProps.columns[0].id);
|
||||
|
||||
store.getState().redo();
|
||||
|
||||
({ blocks } = store.getState());
|
||||
moved = blocks.find((b) => b.id === textBlock.id) as any;
|
||||
expect(moved.columnId).toBe(updatedColumns[1].id);
|
||||
});
|
||||
|
||||
it("replaceBlocks 호출 후 undo/redo 로 이전 블록 배열을 복원할 수 있어야 한다", () => {
|
||||
const store = createEditorStore();
|
||||
|
||||
store.getState().addTextBlock();
|
||||
|
||||
let { blocks } = store.getState();
|
||||
expect(blocks).toHaveLength(1);
|
||||
|
||||
const originalIds = blocks.map((b) => b.id);
|
||||
|
||||
const replacedBlocks = blocks.map((b) => ({
|
||||
...b,
|
||||
id: `${b.id}_replaced`,
|
||||
}));
|
||||
const replacedIds = replacedBlocks.map((b) => b.id);
|
||||
|
||||
(store.getState() as any).replaceBlocks(replacedBlocks as any);
|
||||
|
||||
({ blocks } = store.getState());
|
||||
expect(blocks.map((b) => b.id)).toEqual(replacedIds);
|
||||
|
||||
(store.getState() as any).undo();
|
||||
|
||||
({ blocks } = store.getState());
|
||||
expect(blocks.map((b) => b.id)).toEqual(originalIds);
|
||||
|
||||
(store.getState() as any).redo();
|
||||
|
||||
({ blocks } = store.getState());
|
||||
expect(blocks.map((b) => b.id)).toEqual(replacedIds);
|
||||
});
|
||||
|
||||
it("resetHistory 를 호출하면 history/future 가 비워지고 undo/redo 가 이전 스냅샷으로 돌아가지 않아야 한다", () => {
|
||||
const store = createEditorStore();
|
||||
|
||||
// 블록을 여러 개 추가해 history 스택을 만든다.
|
||||
store.getState().addTextBlock();
|
||||
store.getState().addTextBlock();
|
||||
|
||||
let { blocks, history, future } = store.getState();
|
||||
expect(blocks.length).toBe(2);
|
||||
expect(history.length).toBeGreaterThan(0);
|
||||
expect(future.length).toBe(0);
|
||||
|
||||
const snapshotAfterEdits = blocks;
|
||||
|
||||
// 히스토리 초기화
|
||||
(store.getState() as any).resetHistory();
|
||||
|
||||
({ blocks, history, future } = store.getState());
|
||||
expect(history).toHaveLength(0);
|
||||
expect(future).toHaveLength(0);
|
||||
|
||||
// undo/redo 를 호출해도 더 이상 이전 스냅샷으로는 돌아가지 않아야 한다.
|
||||
store.getState().undo();
|
||||
({ blocks, history, future } = store.getState());
|
||||
expect(blocks).toEqual(snapshotAfterEdits);
|
||||
expect(history).toHaveLength(0);
|
||||
expect(future).toHaveLength(0);
|
||||
|
||||
store.getState().redo();
|
||||
({ blocks, history, future } = store.getState());
|
||||
expect(blocks).toEqual(snapshotAfterEdits);
|
||||
expect(history).toHaveLength(0);
|
||||
expect(future).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user