218 lines
7.4 KiB
TypeScript
218 lines
7.4 KiB
TypeScript
import { describe, it, expect, afterEach, vi } from "vitest";
|
|
import { render, cleanup, fireEvent } from "@testing-library/react";
|
|
import { PublicPageRenderer } from "@/features/editor/components/PublicPageRenderer";
|
|
import type { Block } from "@/features/editor/state/editorStore";
|
|
|
|
// PublicPageRenderer 비디오 블록 스타일 TDD
|
|
// - YouTube URL 은 iframe + pb-video-wrapper 로 렌더링되어야 한다.
|
|
// - 업로드(/api/video/:id) URL 은 <video> 태그로 렌더되고 widthPx 가 em 단위로 반영되어야 한다.
|
|
|
|
describe("PublicPageRenderer - 비디오 블록 스타일", () => {
|
|
afterEach(() => {
|
|
cleanup();
|
|
});
|
|
|
|
it("YouTube URL 비디오 블록은 iframe 으로 렌더되고 기본 16:9 wrapper 를 가져야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "video_youtube_preview_1",
|
|
type: "video" as any,
|
|
props: {
|
|
sourceUrl: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
|
align: "center",
|
|
widthMode: "auto",
|
|
aspectRatio: "16:9",
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { container } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const iframe = container.querySelector('iframe[title="비디오"]') as HTMLIFrameElement | null;
|
|
expect(iframe).not.toBeNull();
|
|
expect(iframe!.src).toContain("youtube.com/embed");
|
|
|
|
const wrapper = iframe!.parentElement as HTMLElement | null;
|
|
expect(wrapper).not.toBeNull();
|
|
expect(wrapper!.className).toContain("pb-video-wrapper");
|
|
});
|
|
|
|
it("업로드 비디오 URL 은 video 태그로 렌더되고 widthPx 가 em 단위 너비로 반영되어야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "video_upload_preview_1",
|
|
type: "video" as any,
|
|
props: {
|
|
sourceUrl: "/api/video/abc123",
|
|
align: "left",
|
|
widthMode: "fixed",
|
|
widthPx: 640,
|
|
aspectRatio: "16:9",
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { container } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const video = container.querySelector("video") as HTMLVideoElement | null;
|
|
expect(video).not.toBeNull();
|
|
expect(video!.getAttribute("controls")).not.toBeNull();
|
|
// 640px / 16 = 40em
|
|
expect(video!.style.width).toBe("40em");
|
|
});
|
|
|
|
it("posterImageSrc 가 설정된 HTML5 비디오는 video 태그 poster 속성으로 렌더되어야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "video_poster_preview_1",
|
|
type: "video" as any,
|
|
props: {
|
|
sourceUrl: "/videos/sample-preview.mp4",
|
|
align: "center",
|
|
widthMode: "auto",
|
|
aspectRatio: "16:9",
|
|
posterImageSrc: "/images/sample-poster-preview.png",
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { container } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const video = container.querySelector("video") as HTMLVideoElement | null;
|
|
expect(video).not.toBeNull();
|
|
expect(video!.getAttribute("poster")).toBe("/images/sample-poster-preview.png");
|
|
});
|
|
|
|
it("startTimeSec/endTimeSec 가 설정된 HTML5 비디오는 프리뷰에서도 loadedmetadata/timeupdate 로 시작/종료 범위가 적용되어야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "video_range_preview_1",
|
|
type: "video" as any,
|
|
props: {
|
|
sourceUrl: "/videos/range-preview.mp4",
|
|
align: "center",
|
|
widthMode: "auto",
|
|
aspectRatio: "16:9",
|
|
startTimeSec: 3,
|
|
endTimeSec: 8,
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { container } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const video = container.querySelector("video") as HTMLVideoElement | null;
|
|
expect(video).not.toBeNull();
|
|
|
|
fireEvent(video as HTMLVideoElement, new Event("loadedmetadata"));
|
|
expect((video as HTMLVideoElement).currentTime).toBe(3);
|
|
|
|
(video as any).pause = vi.fn();
|
|
(video as HTMLVideoElement).currentTime = 8.2;
|
|
fireEvent(video as HTMLVideoElement, new Event("timeupdate"));
|
|
expect((video as any).pause).toHaveBeenCalled();
|
|
});
|
|
|
|
it("HTML5 비디오의 ariaLabel/titleText/captionText 는 프리뷰에서 aria-label 및 캡션 요소로 반영되어야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "video_a11y_preview",
|
|
type: "video" as any,
|
|
props: {
|
|
sourceUrl: "/videos/a11y-preview.mp4",
|
|
align: "center",
|
|
widthMode: "auto",
|
|
aspectRatio: "16:9",
|
|
titleText: "프리뷰 접근성 타이틀",
|
|
ariaLabel: "프리뷰 접근성 설명",
|
|
captionText: "프리뷰 비디오 캡션입니다.",
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { container, getByTestId } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const video = container.querySelector("video") as HTMLVideoElement | null;
|
|
expect(video).not.toBeNull();
|
|
expect(video!.getAttribute("aria-label")).toBe("프리뷰 접근성 설명");
|
|
|
|
const caption = getByTestId("preview-video-caption");
|
|
expect(caption.textContent).toBe("프리뷰 비디오 캡션입니다.");
|
|
});
|
|
|
|
it("titleText 가 설정된 YouTube 비디오는 iframe title 로 반영되어야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "video_youtube_a11y_1",
|
|
type: "video" as any,
|
|
props: {
|
|
sourceUrl: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
|
align: "center",
|
|
widthMode: "auto",
|
|
aspectRatio: "16:9",
|
|
titleText: "사용자 정의 비디오 제목",
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { container } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const iframe = container.querySelector('iframe[title="사용자 정의 비디오 제목"]') as HTMLIFrameElement | null;
|
|
expect(iframe).not.toBeNull();
|
|
});
|
|
|
|
it("비디오 카드 스타일 props(backgroundColorCustom/cardPaddingPx/borderRadiusPx)는 wrapper 의 스타일에 반영되어야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "video_card_preview_1",
|
|
type: "video" as any,
|
|
props: {
|
|
sourceUrl: "/api/video/preview-card",
|
|
align: "center",
|
|
widthMode: "fixed",
|
|
widthPx: 320,
|
|
aspectRatio: "16:9",
|
|
backgroundColorCustom: "#123456",
|
|
cardPaddingPx: 32,
|
|
borderRadiusPx: 16,
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { container } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const video = container.querySelector("video") as HTMLVideoElement | null;
|
|
expect(video).not.toBeNull();
|
|
const wrapper = video!.parentElement as HTMLElement | null;
|
|
expect(wrapper).not.toBeNull();
|
|
|
|
// #123456 -> rgb(18, 52, 86)
|
|
expect(wrapper!.style.backgroundColor).toBe("rgb(18, 52, 86)");
|
|
// 32px / 16 = 2em, 16px / 16 = 1em
|
|
expect(wrapper!.style.padding).toBe("2em");
|
|
expect(wrapper!.style.borderRadius).toBe("1em");
|
|
});
|
|
|
|
it("sourceUrl 이 비어 있는 비디오 블록은 프리뷰에서 video/iframe 을 렌더하지 않아야 한다", () => {
|
|
const blocks: Block[] = [
|
|
{
|
|
id: "video_empty_src_preview_1",
|
|
type: "video" as any,
|
|
props: {
|
|
sourceUrl: "",
|
|
align: "center",
|
|
widthMode: "auto",
|
|
aspectRatio: "16:9",
|
|
} as any,
|
|
},
|
|
];
|
|
|
|
const { container } = render(<PublicPageRenderer blocks={blocks} />);
|
|
|
|
const video = container.querySelector("video");
|
|
const iframe = container.querySelector("iframe");
|
|
expect(video).toBeNull();
|
|
expect(iframe).toBeNull();
|
|
});
|
|
});
|