TDD,E2E 개선, 미적용 스타일 개선
This commit is contained in:
@@ -0,0 +1,350 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
function parsePx(value: string): number {
|
||||
const n = parseFloat(value || "");
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.route("**/api/auth/me", async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ id: "user-form-parity", email: "form-parity@example.com", tokenVersion: 1 }),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("formInput: 에디터 캔버스와 프리뷰에서 높이/패딩/색상이 동일해야 한다", async ({ page }) => {
|
||||
await page.goto("/editor");
|
||||
|
||||
const canvas = page.getByTestId("editor-canvas");
|
||||
const sidebar = page.getByTestId("properties-sidebar");
|
||||
|
||||
await page.getByRole("button", { name: "입력(Input)" }).click();
|
||||
|
||||
await sidebar.getByRole("textbox", { name: "필드 라벨" }).fill("에디터-프리뷰 인풋");
|
||||
await sidebar.getByLabel("텍스트 정렬").selectOption("center");
|
||||
await sidebar.getByLabel("너비").selectOption("fixed");
|
||||
await sidebar.getByLabel("필드 고정 너비 커스텀 (px)").fill("320");
|
||||
await sidebar.getByLabel("필드 텍스트 색상 HEX").fill("#ff0000");
|
||||
await sidebar.getByLabel("필드 채움 색상 HEX").fill("#00ff88");
|
||||
await sidebar.getByLabel("필드 테두리 색상 HEX").fill("#0000ff");
|
||||
|
||||
const editorInput = canvas.getByRole("textbox", { name: "에디터-프리뷰 인풋" });
|
||||
const editorStyles = await editorInput.evaluate((el) => {
|
||||
const s = window.getComputedStyle(el as HTMLInputElement);
|
||||
return {
|
||||
height: s.height,
|
||||
paddingTop: s.paddingTop,
|
||||
paddingBottom: s.paddingBottom,
|
||||
fontSize: s.fontSize,
|
||||
color: s.color,
|
||||
backgroundColor: s.backgroundColor,
|
||||
borderColor: s.borderColor,
|
||||
borderRadius: s.borderRadius,
|
||||
};
|
||||
});
|
||||
|
||||
const editorAttrs = await editorInput.evaluate((el) => {
|
||||
const input = el as HTMLInputElement;
|
||||
return {
|
||||
type: input.type,
|
||||
name: input.name,
|
||||
placeholder: input.placeholder,
|
||||
};
|
||||
});
|
||||
|
||||
await page.getByRole("link", { name: "프리뷰 열기" }).click();
|
||||
await expect(page.getByRole("heading", { name: "Page Preview" })).toBeVisible();
|
||||
|
||||
const previewInput = page.getByRole("textbox", { name: "에디터-프리뷰 인풋" });
|
||||
const previewStyles = await previewInput.evaluate((el) => {
|
||||
const s = window.getComputedStyle(el as HTMLInputElement);
|
||||
return {
|
||||
height: s.height,
|
||||
paddingTop: s.paddingTop,
|
||||
paddingBottom: s.paddingBottom,
|
||||
fontSize: s.fontSize,
|
||||
color: s.color,
|
||||
backgroundColor: s.backgroundColor,
|
||||
borderColor: s.borderColor,
|
||||
borderRadius: s.borderRadius,
|
||||
};
|
||||
});
|
||||
|
||||
const editorHeight = parsePx(editorStyles.height);
|
||||
const previewHeight = parsePx(previewStyles.height);
|
||||
expect(Math.abs(previewHeight - editorHeight)).toBeLessThanOrEqual(2);
|
||||
|
||||
const editorPaddingTop = parsePx(editorStyles.paddingTop);
|
||||
const previewPaddingTop = parsePx(previewStyles.paddingTop);
|
||||
expect(Math.abs(previewPaddingTop - editorPaddingTop)).toBeLessThanOrEqual(3);
|
||||
|
||||
const editorPaddingBottom = parsePx(editorStyles.paddingBottom);
|
||||
const previewPaddingBottom = parsePx(previewStyles.paddingBottom);
|
||||
expect(Math.abs(previewPaddingBottom - editorPaddingBottom)).toBeLessThanOrEqual(3);
|
||||
|
||||
expect(previewStyles.fontSize).toBe(editorStyles.fontSize);
|
||||
expect(previewStyles.color).toBe(editorStyles.color);
|
||||
expect(previewStyles.backgroundColor).toBe(editorStyles.backgroundColor);
|
||||
expect(previewStyles.borderColor).toBe(editorStyles.borderColor);
|
||||
expect(previewStyles.borderRadius).toBe(editorStyles.borderRadius);
|
||||
|
||||
const previewAttrs = await previewInput.evaluate((el) => {
|
||||
const input = el as HTMLInputElement;
|
||||
return {
|
||||
type: input.type,
|
||||
name: input.name,
|
||||
placeholder: input.placeholder,
|
||||
};
|
||||
});
|
||||
|
||||
expect(previewAttrs.type).toBe(editorAttrs.type);
|
||||
expect(previewAttrs.placeholder).toBe(editorAttrs.placeholder);
|
||||
expect(previewAttrs.name).toBe(editorAttrs.name);
|
||||
});
|
||||
|
||||
test("formSelect: 에디터 캔버스와 프리뷰에서 높이/패딩/색상이 동일해야 한다", async ({ page }) => {
|
||||
await page.goto("/editor");
|
||||
|
||||
const canvas = page.getByTestId("editor-canvas");
|
||||
const sidebar = page.getByTestId("properties-sidebar");
|
||||
|
||||
await page.getByRole("button", { name: "셀렉트(Select)" }).click();
|
||||
|
||||
await sidebar.getByRole("textbox", { name: "필드 라벨" }).fill("에디터-프리뷰 셀렉트");
|
||||
await sidebar.getByLabel("필드 너비").selectOption("fixed");
|
||||
await sidebar.getByLabel("필드 고정 너비 커스텀 (px)").fill("360");
|
||||
await sidebar.getByLabel("셀렉트 텍스트 색상 HEX").fill("#ff00ff");
|
||||
await sidebar.getByLabel("셀렉트 채움 색상 HEX").fill("#0033ff");
|
||||
await sidebar.getByLabel("셀렉트 테두리 색상 HEX").fill("#00ffaa");
|
||||
|
||||
const editorSelect = canvas.getByRole("combobox", { name: "에디터-프리뷰 셀렉트" });
|
||||
const editorStyles = await editorSelect.evaluate((el) => {
|
||||
const s = window.getComputedStyle(el as HTMLSelectElement);
|
||||
return {
|
||||
height: s.height,
|
||||
paddingTop: s.paddingTop,
|
||||
paddingBottom: s.paddingBottom,
|
||||
fontSize: s.fontSize,
|
||||
color: s.color,
|
||||
backgroundColor: s.backgroundColor,
|
||||
borderColor: s.borderColor,
|
||||
borderRadius: s.borderRadius,
|
||||
};
|
||||
});
|
||||
|
||||
const editorSelectAttrs = await editorSelect.evaluate((el) => {
|
||||
const select = el as HTMLSelectElement;
|
||||
return {
|
||||
name: select.name,
|
||||
value: select.value,
|
||||
};
|
||||
});
|
||||
|
||||
await page.getByRole("link", { name: "프리뷰 열기" }).click();
|
||||
await expect(page.getByRole("heading", { name: "Page Preview" })).toBeVisible();
|
||||
|
||||
const previewSelect = page.getByRole("combobox", { name: "에디터-프리뷰 셀렉트" });
|
||||
const previewStyles = await previewSelect.evaluate((el) => {
|
||||
const s = window.getComputedStyle(el as HTMLSelectElement);
|
||||
return {
|
||||
height: s.height,
|
||||
paddingTop: s.paddingTop,
|
||||
paddingBottom: s.paddingBottom,
|
||||
fontSize: s.fontSize,
|
||||
color: s.color,
|
||||
backgroundColor: s.backgroundColor,
|
||||
borderColor: s.borderColor,
|
||||
borderRadius: s.borderRadius,
|
||||
};
|
||||
});
|
||||
|
||||
const editorHeight = parsePx(editorStyles.height);
|
||||
const previewHeight = parsePx(previewStyles.height);
|
||||
expect(Math.abs(previewHeight - editorHeight)).toBeLessThanOrEqual(2);
|
||||
|
||||
const editorPaddingTop = parsePx(editorStyles.paddingTop);
|
||||
const previewPaddingTop = parsePx(previewStyles.paddingTop);
|
||||
expect(Math.abs(previewPaddingTop - editorPaddingTop)).toBeLessThanOrEqual(3);
|
||||
|
||||
const editorPaddingBottom = parsePx(editorStyles.paddingBottom);
|
||||
const previewPaddingBottom = parsePx(previewStyles.paddingBottom);
|
||||
expect(Math.abs(previewPaddingBottom - editorPaddingBottom)).toBeLessThanOrEqual(3);
|
||||
|
||||
expect(previewStyles.fontSize).toBe(editorStyles.fontSize);
|
||||
expect(previewStyles.color).toBe(editorStyles.color);
|
||||
expect(previewStyles.backgroundColor).toBe(editorStyles.backgroundColor);
|
||||
expect(previewStyles.borderColor).toBe(editorStyles.borderColor);
|
||||
expect(previewStyles.borderRadius).toBe(editorStyles.borderRadius);
|
||||
|
||||
const previewSelectAttrs = await previewSelect.evaluate((el) => {
|
||||
const select = el as HTMLSelectElement;
|
||||
return {
|
||||
name: select.name,
|
||||
value: select.value,
|
||||
};
|
||||
});
|
||||
|
||||
expect(previewSelectAttrs.name).toBe(editorSelectAttrs.name);
|
||||
expect(previewSelectAttrs.value).toBe(editorSelectAttrs.value);
|
||||
});
|
||||
|
||||
test("formCheckbox/formRadio: 에디터 캔버스와 프리뷰에서 그룹 너비/라벨 간격/옵션 간격이 동일해야 한다", async ({ page }) => {
|
||||
await page.goto("/editor");
|
||||
|
||||
const canvas = page.getByTestId("editor-canvas");
|
||||
const sidebar = page.getByTestId("properties-sidebar");
|
||||
|
||||
await page.getByRole("button", { name: "다중 선택(Checkbox)" }).click();
|
||||
|
||||
await sidebar.getByLabel("그룹 타이틀", { exact: true }).fill("에디터-프리뷰 체크 그룹");
|
||||
await sidebar.getByLabel("그룹 타이틀 표시 방식").selectOption("visible");
|
||||
await sidebar.getByLabel(/^레이아웃/).selectOption("inline");
|
||||
await sidebar.getByLabel("라벨/필드 간격 (px) 커스텀 (px)").fill("32");
|
||||
await sidebar.getByLabel("필드 너비").selectOption("fixed");
|
||||
await sidebar.getByLabel("필드 고정 너비 커스텀 (px)").fill("360");
|
||||
await sidebar.getByLabel("체크박스 옵션 간격 (px) 커스텀 (px)").fill("20");
|
||||
const editorCheckboxLabel = canvas.getByText("에디터-프리뷰 체크 그룹");
|
||||
const editorCheckboxData = await editorCheckboxLabel.evaluate((el) => {
|
||||
const groupEl = (el as HTMLElement).parentElement as HTMLElement;
|
||||
const groupStyle = window.getComputedStyle(groupEl);
|
||||
const optionsEl = groupEl.querySelector(".pb-form-options") as HTMLElement | null;
|
||||
const optionsStyle = optionsEl ? window.getComputedStyle(optionsEl) : null;
|
||||
const firstOptionInput = optionsEl?.querySelector("input[type='checkbox']") as
|
||||
| HTMLInputElement
|
||||
| null;
|
||||
const firstOption = firstOptionInput
|
||||
? { type: firstOptionInput.type, name: firstOptionInput.name, value: firstOptionInput.value }
|
||||
: { type: "", name: "", value: "" };
|
||||
return {
|
||||
groupWidth: groupStyle.width,
|
||||
groupColumnGap: groupStyle.columnGap,
|
||||
optionsRowGap: optionsStyle ? optionsStyle.rowGap : "",
|
||||
firstOption,
|
||||
};
|
||||
});
|
||||
|
||||
await page.getByRole("button", { name: "단일 선택(Radio)" }).click();
|
||||
|
||||
await sidebar.getByLabel("그룹 타이틀", { exact: true }).fill("에디터-프리뷰 라디오 그룹");
|
||||
await sidebar.getByLabel("그룹 타이틀 표시 방식").selectOption("visible");
|
||||
await sidebar.getByLabel("그룹 타이틀 레이아웃").selectOption("inline");
|
||||
await sidebar.getByLabel("라벨/필드 간격 (px) 커스텀 (px)").fill("32");
|
||||
await sidebar.getByLabel("필드 너비").selectOption("fixed");
|
||||
await sidebar.getByLabel("필드 고정 너비 커스텀 (px)").fill("360");
|
||||
await sidebar.getByLabel("라디오 옵션 간격 (px) 커스텀 (px)").fill("20");
|
||||
const editorRadioLabel = canvas.getByText("에디터-프리뷰 라디오 그룹");
|
||||
const editorRadioData = await editorRadioLabel.evaluate((el) => {
|
||||
const groupEl = (el as HTMLElement).parentElement as HTMLElement;
|
||||
const groupStyle = window.getComputedStyle(groupEl);
|
||||
const optionsEl = groupEl.querySelector(".pb-form-options") as HTMLElement | null;
|
||||
const optionsStyle = optionsEl ? window.getComputedStyle(optionsEl) : null;
|
||||
const firstOptionInput = optionsEl?.querySelector("input[type='radio']") as
|
||||
| HTMLInputElement
|
||||
| null;
|
||||
const firstOption = firstOptionInput
|
||||
? { type: firstOptionInput.type, name: firstOptionInput.name, value: firstOptionInput.value }
|
||||
: { type: "", name: "", value: "" };
|
||||
return {
|
||||
groupWidth: groupStyle.width,
|
||||
groupColumnGap: groupStyle.columnGap,
|
||||
optionsRowGap: optionsStyle ? optionsStyle.rowGap : "",
|
||||
firstOption,
|
||||
};
|
||||
});
|
||||
|
||||
await page.getByRole("link", { name: "프리뷰 열기" }).click();
|
||||
await expect(page.getByRole("heading", { name: "Page Preview" })).toBeVisible();
|
||||
|
||||
const previewCheckboxGroup = page.getByTestId("preview-form-checkbox-group").first();
|
||||
const previewCheckboxGroupStyles = await previewCheckboxGroup.evaluate((el) => {
|
||||
const s = window.getComputedStyle(el as HTMLElement);
|
||||
return {
|
||||
width: s.width,
|
||||
columnGap: s.columnGap,
|
||||
};
|
||||
});
|
||||
|
||||
const previewCheckboxFirstOption = await previewCheckboxGroup.evaluate((el) => {
|
||||
const input = (el as HTMLElement).querySelector("input[type='checkbox']") as
|
||||
| HTMLInputElement
|
||||
| null;
|
||||
return input
|
||||
? { type: input.type, name: input.name, value: input.value }
|
||||
: { type: "", name: "", value: "" };
|
||||
});
|
||||
|
||||
const previewCheckboxOptions = previewCheckboxGroup
|
||||
.getByTestId("preview-form-checkbox-options")
|
||||
.first();
|
||||
const previewCheckboxOptionsStyles = await previewCheckboxOptions.evaluate((el) => {
|
||||
const s = window.getComputedStyle(el as HTMLElement);
|
||||
return {
|
||||
rowGap: s.rowGap,
|
||||
};
|
||||
});
|
||||
|
||||
const previewRadioGroup = page.getByTestId("preview-form-radio-group").first();
|
||||
const previewRadioGroupStyles = await previewRadioGroup.evaluate((el) => {
|
||||
const s = window.getComputedStyle(el as HTMLElement);
|
||||
return {
|
||||
width: s.width,
|
||||
columnGap: s.columnGap,
|
||||
};
|
||||
});
|
||||
|
||||
const previewRadioFirstOption = await previewRadioGroup.evaluate((el) => {
|
||||
const input = (el as HTMLElement).querySelector("input[type='radio']") as
|
||||
| HTMLInputElement
|
||||
| null;
|
||||
return input
|
||||
? { type: input.type, name: input.name, value: input.value }
|
||||
: { type: "", name: "", value: "" };
|
||||
});
|
||||
|
||||
const previewRadioOptions = previewRadioGroup
|
||||
.getByTestId("preview-form-radio-options")
|
||||
.first();
|
||||
const previewRadioOptionsStyles = await previewRadioOptions.evaluate((el) => {
|
||||
const s = window.getComputedStyle(el as HTMLElement);
|
||||
return {
|
||||
rowGap: s.rowGap,
|
||||
};
|
||||
});
|
||||
|
||||
const editorCheckboxWidth = parsePx(editorCheckboxData.groupWidth);
|
||||
const previewCheckboxWidth = parsePx(previewCheckboxGroupStyles.width);
|
||||
expect(Math.abs(previewCheckboxWidth - editorCheckboxWidth)).toBeLessThanOrEqual(2);
|
||||
|
||||
const editorCheckboxLabelGap = parsePx(editorCheckboxData.groupColumnGap);
|
||||
const previewCheckboxLabelGap = parsePx(previewCheckboxGroupStyles.columnGap);
|
||||
expect(Math.abs(previewCheckboxLabelGap - editorCheckboxLabelGap)).toBeLessThanOrEqual(2);
|
||||
|
||||
const editorCheckboxOptionGap = parsePx(editorCheckboxData.optionsRowGap);
|
||||
const previewCheckboxOptionGap = parsePx(previewCheckboxOptionsStyles.rowGap);
|
||||
expect(Math.abs(previewCheckboxOptionGap - editorCheckboxOptionGap)).toBeLessThanOrEqual(2);
|
||||
|
||||
const editorRadioWidth = parsePx(editorRadioData.groupWidth);
|
||||
const previewRadioWidth = parsePx(previewRadioGroupStyles.width);
|
||||
expect(Math.abs(previewRadioWidth - editorRadioWidth)).toBeLessThanOrEqual(2);
|
||||
|
||||
const editorRadioLabelGap = parsePx(editorRadioData.groupColumnGap);
|
||||
const previewRadioLabelGap = parsePx(previewRadioGroupStyles.columnGap);
|
||||
expect(Math.abs(previewRadioLabelGap - editorRadioLabelGap)).toBeLessThanOrEqual(2);
|
||||
|
||||
const editorRadioOptionGap = parsePx(editorRadioData.optionsRowGap);
|
||||
const previewRadioOptionGap = parsePx(previewRadioOptionsStyles.rowGap);
|
||||
expect(Math.abs(previewRadioOptionGap - editorRadioOptionGap)).toBeLessThanOrEqual(2);
|
||||
|
||||
expect(editorCheckboxData.firstOption.type).toBe("checkbox");
|
||||
expect(previewCheckboxFirstOption.type).toBe(editorCheckboxData.firstOption.type);
|
||||
expect(previewCheckboxFirstOption.name).toBe(editorCheckboxData.firstOption.name);
|
||||
expect(previewCheckboxFirstOption.value).toBe(editorCheckboxData.firstOption.value);
|
||||
|
||||
expect(editorRadioData.firstOption.type).toBe("radio");
|
||||
expect(previewRadioFirstOption.type).toBe(editorRadioData.firstOption.type);
|
||||
expect(previewRadioFirstOption.name).toBe(editorRadioData.firstOption.name);
|
||||
expect(previewRadioFirstOption.value).toBe(editorRadioData.firstOption.value);
|
||||
});
|
||||
Reference in New Issue
Block a user