이미지 파일 업로드 기능
This commit is contained in:
@@ -17,6 +17,59 @@ test("텍스트 블록 추가 버튼을 누르면 캔버스에 '새 텍스트'
|
||||
await expect(canvas.getByText("새 텍스트")).toBeVisible();
|
||||
});
|
||||
|
||||
test("선택된 블록이 없을 때 우측 패널에서 프로젝트 설정 패널이 보여야 한다", async ({ page }) => {
|
||||
await page.goto("/editor");
|
||||
|
||||
const sidebar = page.getByTestId("properties-sidebar");
|
||||
|
||||
await expect(sidebar.getByText("프로젝트 설정")).toBeVisible();
|
||||
await expect(sidebar.getByLabel("프로젝트 제목")).toBeVisible();
|
||||
await expect(sidebar.getByLabel("프로젝트 주소 (slug)")).toBeVisible();
|
||||
await expect(sidebar.getByRole("combobox", { name: "캔버스 너비 프리셋" })).toBeVisible();
|
||||
await expect(sidebar.getByLabel("페이지 배경색 HEX")).toBeVisible();
|
||||
});
|
||||
|
||||
test("프로젝트 설정 패널에서 캔버스 배경색을 변경하면 에디터 캔버스 배경에 반영되어야 한다", async ({ page }) => {
|
||||
await page.goto("/editor");
|
||||
|
||||
const sidebar = page.getByTestId("properties-sidebar");
|
||||
const inner = page.getByTestId("editor-canvas-inner");
|
||||
|
||||
const beforeBg = await inner.evaluate((el) => {
|
||||
const s = window.getComputedStyle(el as HTMLElement);
|
||||
return s.backgroundColor;
|
||||
});
|
||||
|
||||
const bgHexInput = sidebar.getByLabel("캔버스 배경색 HEX");
|
||||
await bgHexInput.fill("#ff0000");
|
||||
|
||||
const afterBg = await inner.evaluate((el) => {
|
||||
const s = window.getComputedStyle(el as HTMLElement);
|
||||
return s.backgroundColor;
|
||||
});
|
||||
|
||||
expect(afterBg).not.toBe(beforeBg);
|
||||
});
|
||||
|
||||
test("프로젝트 설정 패널에서 캔버스 프리셋을 변경하면 에디터 캔버스 너비가 달라져야 한다", async ({ page }) => {
|
||||
await page.goto("/editor");
|
||||
|
||||
const inner = page.getByTestId("editor-canvas-inner");
|
||||
|
||||
const fullWidth = await inner.evaluate((el) => (el as HTMLElement).getBoundingClientRect().width);
|
||||
|
||||
const presetSelect = page.getByRole("combobox", { name: "캔버스 너비 프리셋" });
|
||||
|
||||
await presetSelect.selectOption("mobile");
|
||||
const mobileWidth = await inner.evaluate((el) => (el as HTMLElement).getBoundingClientRect().width);
|
||||
|
||||
await presetSelect.selectOption("desktop");
|
||||
const desktopWidth = await inner.evaluate((el) => (el as HTMLElement).getBoundingClientRect().width);
|
||||
|
||||
expect(mobileWidth).toBeLessThan(desktopWidth);
|
||||
expect(desktopWidth).toBeLessThanOrEqual(fullWidth);
|
||||
});
|
||||
|
||||
test("텍스트 블록을 더블클릭해서 내용을 수정할 수 있어야 한다", async ({ page }) => {
|
||||
test.skip(process.env.CI === "true");
|
||||
await page.goto("/editor");
|
||||
@@ -414,6 +467,60 @@ test("버튼 가로/세로 패딩 px 값을 변경하면 에디터 버튼에도
|
||||
expect(parseFloat(updatedPadding.paddingBlock)).toBeGreaterThan(parseFloat(initialPadding.paddingBlock));
|
||||
});
|
||||
|
||||
test("이미지 블록 고정 너비와 모서리 둥글기 스타일이 에디터에서 적용되어야 한다", async ({ page }) => {
|
||||
await page.goto("/editor");
|
||||
|
||||
await page.getByRole("button", { name: "이미지 블록 추가" }).click();
|
||||
|
||||
const canvas = page.getByTestId("editor-canvas");
|
||||
const propertiesSidebar = page.getByTestId("properties-sidebar");
|
||||
|
||||
await propertiesSidebar.getByLabel("이미지 URL").fill("https://picsum.photos/400");
|
||||
await propertiesSidebar.getByLabel("너비 모드").selectOption("fixed");
|
||||
await propertiesSidebar.getByLabel("고정 너비 (px) 커스텀 (px)").fill("300");
|
||||
|
||||
// 모서리 둥글기를 full 에 해당하는 값(8px)으로 설정한다.
|
||||
await propertiesSidebar.getByLabel("모서리 둥글기 커스텀").fill("8");
|
||||
|
||||
const image = canvas.getByRole("img").first();
|
||||
|
||||
const styles = await image.evaluate((el) => {
|
||||
const s = window.getComputedStyle(el as HTMLImageElement);
|
||||
return {
|
||||
width: s.width,
|
||||
borderRadius: s.borderRadius,
|
||||
};
|
||||
});
|
||||
|
||||
const widthPx = parseFloat(styles.width);
|
||||
expect(widthPx).toBeGreaterThan(200);
|
||||
expect(widthPx).toBeLessThan(360);
|
||||
|
||||
// borderRadius 가 0보다 커야 한다.
|
||||
expect(parseFloat(styles.borderRadius)).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test("에디터 메뉴에서 페이지 파일로 내보내기 (ZIP)를 클릭하면 ZIP 다운로드가 시작되어야 한다", async ({ page }) => {
|
||||
await page.goto("/editor");
|
||||
|
||||
// 최소 한 개의 블록을 추가해 내보낼 데이터가 있도록 한다.
|
||||
await page.getByRole("button", { name: "텍스트 블록 추가" }).click();
|
||||
|
||||
// 헤더 메뉴를 연다.
|
||||
await page.getByRole("button", { name: "메뉴 ▼" }).click();
|
||||
|
||||
const exportButton = page.getByRole("button", { name: "페이지 파일로 내보내기 (ZIP)" });
|
||||
await expect(exportButton).toBeVisible();
|
||||
|
||||
const [download] = await Promise.all([
|
||||
page.waitForEvent("download"),
|
||||
exportButton.click(),
|
||||
]);
|
||||
|
||||
const suggested = download.suggestedFilename();
|
||||
expect(suggested).toMatch(/\.zip$/);
|
||||
});
|
||||
|
||||
test("2컬럼 섹션에서 블록을 다른 컬럼 영역으로 드래그하면 컬럼이 변경되어야 한다", async ({ page }) => {
|
||||
test.skip(true, "섹션 컬럼 드래그 E2E는 불안정하여 임시 스킵");
|
||||
await page.goto("/editor");
|
||||
|
||||
@@ -40,6 +40,21 @@ test("에디터에서 Hero 템플릿을 추가하면 프리뷰에서 동일한 H
|
||||
await expect(page.getByRole("button", { name: "텍스트 블록 추가" })).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("프리뷰 페이지에서 에디터로 돌아가는 링크가 있어야 한다", async ({ page }) => {
|
||||
// 에디터에서 간단히 진입한 뒤 프리뷰로 이동한다.
|
||||
await page.goto("/editor");
|
||||
|
||||
await page.getByRole("link", { name: "프리뷰 열기" }).click();
|
||||
|
||||
// 프리뷰 헤더와 함께 "에디터로 돌아가기" 링크가 보여야 한다.
|
||||
await expect(page.getByRole("heading", { name: "Page Preview" })).toBeVisible();
|
||||
await expect(page.getByRole("link", { name: "에디터로 돌아가기" })).toBeVisible();
|
||||
|
||||
// 링크를 클릭하면 다시 에디터로 돌아가야 한다.
|
||||
await page.getByRole("link", { name: "에디터로 돌아가기" }).click();
|
||||
await expect(page.getByRole("heading", { name: "Page Editor" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("에디터에서 Features 템플릿을 추가하면 프리뷰에서 3개의 Feature 섹션이 보여야 한다", async ({ page }) => {
|
||||
await page.goto("/editor");
|
||||
|
||||
@@ -275,6 +290,137 @@ test("폼 입력/셀렉트 블록도 FormBlock 없이 프리뷰에서 보여야
|
||||
await expect(page.getByText("선택 필드")).toBeVisible();
|
||||
});
|
||||
|
||||
test("폼 체크박스 그룹 라벨 이미지 업로드 후 에디터/프리뷰 모두 /api/image/:id 기반 이미지가 보여야 한다", async ({ page }) => {
|
||||
await page.goto("/editor");
|
||||
|
||||
await page.getByRole("button", { name: "폼 체크박스 추가" }).click();
|
||||
|
||||
const canvas = page.getByTestId("editor-canvas");
|
||||
const propertiesSidebar = page.getByTestId("properties-sidebar");
|
||||
|
||||
await propertiesSidebar.getByLabel("그룹 타이틀 타입").selectOption("image");
|
||||
await propertiesSidebar.getByLabel("그룹 타이틀 이미지 소스").selectOption("upload");
|
||||
await propertiesSidebar.getByLabel("그룹 타이틀", { exact: true }).fill("체크박스 라벨 이미지");
|
||||
|
||||
const fileInput = propertiesSidebar.getByLabel("그룹 타이틀 이미지 파일 업로드");
|
||||
await fileInput.setInputFiles("tests/fixtures/sample-image.png");
|
||||
|
||||
const editorImage = canvas.getByRole("img", { name: "체크박스 라벨 이미지" }).first();
|
||||
await expect(editorImage).toBeVisible();
|
||||
|
||||
const editorSrc = await editorImage.getAttribute("src");
|
||||
expect(editorSrc).not.toBeNull();
|
||||
expect(editorSrc).toMatch(/^\/api\/image\//);
|
||||
|
||||
await page.getByRole("link", { name: "프리뷰 열기" }).click();
|
||||
await expect(page.getByRole("heading", { name: "Page Preview" })).toBeVisible();
|
||||
|
||||
const previewImage = page.getByRole("img", { name: "체크박스 라벨 이미지" }).first();
|
||||
await expect(previewImage).toBeVisible();
|
||||
|
||||
const previewSrc = await previewImage.getAttribute("src");
|
||||
expect(previewSrc).toBe(editorSrc);
|
||||
});
|
||||
|
||||
test("폼 라디오 옵션 라벨 이미지 업로드 후 에디터/프리뷰 모두 /api/image/:id 기반 이미지가 보여야 한다", async ({ page }) => {
|
||||
await page.goto("/editor");
|
||||
|
||||
await page.getByRole("button", { name: "폼 라디오 추가" }).click();
|
||||
|
||||
const canvas = page.getByTestId("editor-canvas");
|
||||
const propertiesSidebar = page.getByTestId("properties-sidebar");
|
||||
|
||||
const firstOptionLabel = propertiesSidebar.getByPlaceholder("라벨").first();
|
||||
await firstOptionLabel.fill("라디오 옵션 이미지");
|
||||
|
||||
await propertiesSidebar.getByLabel("옵션 이미지 소스").selectOption("upload");
|
||||
|
||||
const fileInput = propertiesSidebar.getByLabel("옵션 이미지 파일 업로드").first();
|
||||
await fileInput.setInputFiles("tests/fixtures/sample-image.png");
|
||||
|
||||
const editorImage = canvas.getByRole("img", { name: "라디오 옵션 이미지" }).first();
|
||||
await expect(editorImage).toBeVisible();
|
||||
|
||||
const editorSrc = await editorImage.getAttribute("src");
|
||||
expect(editorSrc).not.toBeNull();
|
||||
expect(editorSrc).toMatch(/^\/api\/image\//);
|
||||
|
||||
await page.getByRole("link", { name: "프리뷰 열기" }).click();
|
||||
await expect(page.getByRole("heading", { name: "Page Preview" })).toBeVisible();
|
||||
|
||||
const previewImage = page.getByRole("img", { name: "라디오 옵션 이미지" }).first();
|
||||
await expect(previewImage).toBeVisible();
|
||||
|
||||
const previewSrc = await previewImage.getAttribute("src");
|
||||
expect(previewSrc).toBe(editorSrc);
|
||||
});
|
||||
|
||||
test("폼 체크박스 옵션 라벨 이미지 업로드 후 에디터/프리뷰 모두 /api/image/:id 기반 이미지가 보여야 한다", async ({ page }) => {
|
||||
await page.goto("/editor");
|
||||
|
||||
await page.getByRole("button", { name: "폼 체크박스 추가" }).click();
|
||||
|
||||
const canvas = page.getByTestId("editor-canvas");
|
||||
const propertiesSidebar = page.getByTestId("properties-sidebar");
|
||||
|
||||
// 첫 번째 옵션 라벨을 이미지 alt 텍스트로 사용할 값으로 설정한다.
|
||||
const firstOptionLabel = propertiesSidebar.getByPlaceholder("라벨").first();
|
||||
await firstOptionLabel.fill("체크박스 옵션 이미지");
|
||||
|
||||
await propertiesSidebar.getByLabel("옵션 이미지 소스").selectOption("upload");
|
||||
|
||||
const fileInput = propertiesSidebar.getByLabel("옵션 이미지 파일 업로드").first();
|
||||
await fileInput.setInputFiles("tests/fixtures/sample-image.png");
|
||||
|
||||
const editorImage = canvas.getByRole("img", { name: "체크박스 옵션 이미지" }).first();
|
||||
await expect(editorImage).toBeVisible();
|
||||
|
||||
const editorSrc = await editorImage.getAttribute("src");
|
||||
expect(editorSrc).not.toBeNull();
|
||||
expect(editorSrc).toMatch(/^\/api\/image\//);
|
||||
|
||||
await page.getByRole("link", { name: "프리뷰 열기" }).click();
|
||||
await expect(page.getByRole("heading", { name: "Page Preview" })).toBeVisible();
|
||||
|
||||
const previewImage = page.getByRole("img", { name: "체크박스 옵션 이미지" }).first();
|
||||
await expect(previewImage).toBeVisible();
|
||||
|
||||
const previewSrc = await previewImage.getAttribute("src");
|
||||
expect(previewSrc).toBe(editorSrc);
|
||||
});
|
||||
|
||||
test("폼 라디오 그룹 라벨 이미지 업로드 후 에디터/프리뷰 모두 /api/image/:id 기반 이미지가 보여야 한다", async ({ page }) => {
|
||||
await page.goto("/editor");
|
||||
|
||||
await page.getByRole("button", { name: "폼 라디오 추가" }).click();
|
||||
|
||||
const canvas = page.getByTestId("editor-canvas");
|
||||
const propertiesSidebar = page.getByTestId("properties-sidebar");
|
||||
|
||||
await propertiesSidebar.getByLabel("그룹 타이틀 타입").selectOption("image");
|
||||
await propertiesSidebar.getByLabel("그룹 타이틀 이미지 소스").selectOption("upload");
|
||||
await propertiesSidebar.getByLabel("그룹 타이틀", { exact: true }).fill("라디오 라벨 이미지");
|
||||
|
||||
const fileInput = propertiesSidebar.getByLabel("그룹 타이틀 이미지 파일 업로드");
|
||||
await fileInput.setInputFiles("tests/fixtures/sample-image.png");
|
||||
|
||||
const editorImage = canvas.getByRole("img", { name: "라디오 라벨 이미지" }).first();
|
||||
await expect(editorImage).toBeVisible();
|
||||
|
||||
const editorSrc = await editorImage.getAttribute("src");
|
||||
expect(editorSrc).not.toBeNull();
|
||||
expect(editorSrc).toMatch(/^\/api\/image\//);
|
||||
|
||||
await page.getByRole("link", { name: "프리뷰 열기" }).click();
|
||||
await expect(page.getByRole("heading", { name: "Page Preview" })).toBeVisible();
|
||||
|
||||
const previewImage = page.getByRole("img", { name: "라디오 라벨 이미지" }).first();
|
||||
await expect(previewImage).toBeVisible();
|
||||
|
||||
const previewSrc = await previewImage.getAttribute("src");
|
||||
expect(previewSrc).toBe(editorSrc);
|
||||
});
|
||||
|
||||
test("폼 입력 필드 스타일 속성이 프리뷰에도 반영되어야 한다", async ({ page }) => {
|
||||
await page.goto("/editor");
|
||||
|
||||
@@ -487,6 +633,43 @@ test("이미지 고정 너비가 프리뷰에서도 em 스케일로 반영되어
|
||||
expect(computed.inlineWidth.endsWith("em")).toBeTruthy();
|
||||
});
|
||||
|
||||
test("이미지 블록에서 로컬 파일 업로드 후 에디터/프리뷰 모두 /api/image/:id 기반 이미지가 보여야 한다", async ({ page }) => {
|
||||
await page.goto("/editor");
|
||||
|
||||
await page.getByRole("button", { name: "이미지 블록 추가" }).click();
|
||||
|
||||
const canvas = page.getByTestId("editor-canvas");
|
||||
const propertiesSidebar = page.getByTestId("properties-sidebar");
|
||||
|
||||
// alt 텍스트를 먼저 설정해 에디터/프리뷰에서 이미지를 안정적으로 찾을 수 있게 한다.
|
||||
await propertiesSidebar.getByLabel("대체 텍스트").fill("업로드 이미지");
|
||||
|
||||
// 이미지 소스를 파일 업로드 모드로 전환한다.
|
||||
await propertiesSidebar.getByLabel("이미지 소스").selectOption("upload");
|
||||
|
||||
// 아직 구현되지 않은 이미지 업로드 인풋(aria-label: "이미지 파일 업로드")을 통해 로컬 파일을 업로드한다고 가정한다.
|
||||
const fileInput = propertiesSidebar.getByLabel("이미지 파일 업로드");
|
||||
await fileInput.setInputFiles("tests/fixtures/sample-image.png");
|
||||
|
||||
// 에디터 캔버스에서 alt 텍스트 기준으로 이미지를 찾는다.
|
||||
const editorImage = canvas.getByRole("img", { name: "업로드 이미지" }).first();
|
||||
await expect(editorImage).toBeVisible();
|
||||
|
||||
const editorSrc = await editorImage.getAttribute("src");
|
||||
expect(editorSrc).not.toBeNull();
|
||||
expect(editorSrc).toMatch(/^\/api\/image\//);
|
||||
|
||||
// 프리뷰로 이동하면 동일한 /api/image/:id URL을 사용하는 이미지가 보여야 한다.
|
||||
await page.getByRole("link", { name: "프리뷰 열기" }).click();
|
||||
await expect(page.getByRole("heading", { name: "Page Preview" })).toBeVisible();
|
||||
|
||||
const previewImage = page.getByRole("img", { name: "업로드 이미지" }).first();
|
||||
await expect(previewImage).toBeVisible();
|
||||
|
||||
const previewSrc = await previewImage.getAttribute("src");
|
||||
expect(previewSrc).toBe(editorSrc);
|
||||
});
|
||||
|
||||
test("구분선 고정 길이와 여백 px 입력이 프리뷰에서도 em 스케일로 반영되어야 한다", async ({ page }) => {
|
||||
await page.goto("/editor");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user