370 lines
15 KiB
TypeScript
370 lines
15 KiB
TypeScript
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.addInitScript(() => {
|
|
try {
|
|
window.localStorage.clear();
|
|
} catch {
|
|
}
|
|
});
|
|
|
|
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");
|
|
|
|
// 이전 테스트에서 사용한 autosave 스냅샷과 충돌하지 않도록, 이 테스트 전용 slug 를 설정한다.
|
|
await sidebar.getByLabel("Project slug").fill("form-input-parity-e2e");
|
|
|
|
await page.getByRole("button", { name: "Input field" }).click();
|
|
|
|
await sidebar.getByRole("textbox", { name: "Field label" }).fill("에디터-프리뷰 인풋");
|
|
await sidebar.getByLabel("Text alignment").selectOption("center");
|
|
await sidebar.getByRole("combobox", { name: "Width", exact: true }).selectOption("fixed");
|
|
await sidebar.getByLabel("Field fixed width 커스텀").fill("320");
|
|
await sidebar.getByLabel("Field text color HEX").fill("#ff0000");
|
|
await sidebar.getByLabel("Field fill color HEX").fill("#00ff88");
|
|
await sidebar.getByLabel("Field border color 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 Promise.all([
|
|
page.waitForURL(/\/preview/),
|
|
page.getByRole("link", { name: "Open preview" }).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 field" }).click();
|
|
|
|
await sidebar.getByRole("textbox", { name: "Field label" }).fill("에디터-프리뷰 셀렉트");
|
|
await sidebar.getByLabel("Field width").selectOption("fixed");
|
|
await sidebar.getByLabel("Field fixed width 커스텀").fill("360");
|
|
await sidebar.getByLabel("Select text color HEX").fill("#ff00ff");
|
|
await sidebar.getByLabel("Select fill color HEX").fill("#0033ff");
|
|
await sidebar.getByLabel("Select border color 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 Promise.all([
|
|
page.waitForURL(/\/preview/),
|
|
page.getByRole("link", { name: "Open preview" }).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 group" }).click();
|
|
|
|
await sidebar.getByLabel("Group title", { exact: true }).fill("에디터-프리뷰 체크 그룹");
|
|
await sidebar.getByLabel("Group title display mode").selectOption("visible");
|
|
await sidebar.getByLabel(/^Layout/).selectOption("inline");
|
|
await sidebar.getByLabel("Label/field gap 커스텀").fill("32");
|
|
await sidebar.getByLabel("Field width").selectOption("fixed");
|
|
await sidebar.getByLabel("Field fixed width 커스텀").fill("360");
|
|
await sidebar.getByLabel("Checkbox option gap 커스텀").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 group" }).click();
|
|
|
|
await sidebar.getByLabel("Group title", { exact: true }).fill("에디터-프리뷰 라디오 그룹");
|
|
await sidebar.getByLabel("Group title display mode").selectOption("visible");
|
|
await sidebar.getByLabel("Group title layout").selectOption("inline");
|
|
await sidebar.getByLabel("Label/field gap 커스텀").fill("32");
|
|
await sidebar.getByLabel("Field width").selectOption("fixed");
|
|
await sidebar.getByLabel("Field fixed width 커스텀").fill("360");
|
|
await sidebar.getByLabel("Radio option gap 커스텀").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 Promise.all([
|
|
page.waitForURL(/\/preview/),
|
|
page.getByRole("link", { name: "Open preview" }).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(96);
|
|
|
|
const editorCheckboxLabelGap = parsePx(editorCheckboxData.groupColumnGap);
|
|
const previewCheckboxLabelGap = parsePx(previewCheckboxGroupStyles.columnGap);
|
|
expect(Math.abs(previewCheckboxLabelGap - editorCheckboxLabelGap)).toBeLessThanOrEqual(8);
|
|
|
|
const editorCheckboxOptionGap = parsePx(editorCheckboxData.optionsRowGap);
|
|
const previewCheckboxOptionGap = parsePx(previewCheckboxOptionsStyles.rowGap);
|
|
expect(Math.abs(previewCheckboxOptionGap - editorCheckboxOptionGap)).toBeLessThanOrEqual(8);
|
|
|
|
const editorRadioWidth = parsePx(editorRadioData.groupWidth);
|
|
const previewRadioWidth = parsePx(previewRadioGroupStyles.width);
|
|
expect(Math.abs(previewRadioWidth - editorRadioWidth)).toBeLessThanOrEqual(96);
|
|
|
|
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(8);
|
|
|
|
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);
|
|
});
|