import { describe, it, expect } from "vitest"; import { normalizeVideoSourceUrl, resolveVideoPlatform, buildVideoEmbedUrl, computeVideoPublicTokens, computeVideoEditorTokens, computeVideoExportTokens, } from "@/features/editor/utils/videoHelpers"; import type { VideoBlockProps } from "@/features/editor/state/editorStore"; describe("videoHelpers", () => { it("normalizeVideoSourceUrl 은 null/undefined 를 빈 문자열로 만들고 공백을 제거해야 한다", () => { expect(normalizeVideoSourceUrl(undefined)).toBe(""); expect(normalizeVideoSourceUrl(null)).toBe(""); expect(normalizeVideoSourceUrl("")).toBe(""); expect(normalizeVideoSourceUrl(" https://example.com/video.mp4 ")).toBe("https://example.com/video.mp4"); }); it("resolveVideoPlatform 은 platform 이 auto 일 때 URL 로부터 youtube/vimeo/html5 를 판별해야 한다", () => { expect(resolveVideoPlatform("https://www.youtube.com/watch?v=dQw4w9WgXcQ", "auto")).toBe("youtube"); expect(resolveVideoPlatform("https://youtu.be/dQw4w9WgXcQ", "auto")).toBe("youtube"); expect(resolveVideoPlatform("https://vimeo.com/123456", "auto")).toBe("vimeo"); expect(resolveVideoPlatform("/videos/local.mp4", "auto")).toBe("html5"); }); it("resolveVideoPlatform 은 platform 이 명시된 경우 해당 값을 우선 사용해야 한다", () => { expect(resolveVideoPlatform("https://example.com/video", "youtube")).toBe("youtube"); expect(resolveVideoPlatform("https://example.com/video", "vimeo")).toBe("vimeo"); expect(resolveVideoPlatform("https://www.youtube.com/watch?v=dQw4w9WgXcQ", "html5")).toBe("html5"); }); it("buildVideoEmbedUrl 은 YouTube URL 을 embed URL 로 변환해야 한다", () => { expect(buildVideoEmbedUrl("https://www.youtube.com/watch?v=dQw4w9WgXcQ", "youtube")).toBe( "https://www.youtube.com/embed/dQw4w9WgXcQ", ); expect(buildVideoEmbedUrl("https://youtu.be/dQw4w9WgXcQ", "youtube")).toBe( "https://www.youtube.com/embed/dQw4w9WgXcQ", ); expect(buildVideoEmbedUrl("https://www.youtube.com/shorts/dQw4w9WgXcQ", "youtube")).toBe( "https://www.youtube.com/embed/dQw4w9WgXcQ", ); }); it("buildVideoEmbedUrl 은 Vimeo URL 에 대해 옵션에 따라 embed URL 또는 원본 URL 을 반환해야 한다", () => { const url = "https://vimeo.com/123456"; expect(buildVideoEmbedUrl(url, "vimeo", { enableVimeoEmbed: true })).toBe( "https://player.vimeo.com/video/123456", ); expect(buildVideoEmbedUrl(url, "vimeo", { enableVimeoEmbed: false })).toBe("https://vimeo.com/123456"); expect(buildVideoEmbedUrl(url, "vimeo")).toBe("https://vimeo.com/123456"); }); it("buildVideoEmbedUrl 은 html5 플랫폼에서는 정규화된 원본 URL 을 그대로 반환해야 한다", () => { expect(buildVideoEmbedUrl(" /videos/local.mp4 ", "html5")).toBe("/videos/local.mp4"); }); }); describe("videoHelpers.computeVideoPublicTokens", () => { const baseProps: VideoBlockProps = { sourceUrl: "/videos/sample.mp4", align: "center", widthMode: "auto", aspectRatio: "16:9", }; it("fixed width / 카드 스타일을 em 단위로 계산해야 한다", () => { const tokens = computeVideoPublicTokens({ ...baseProps, align: "left", widthMode: "fixed", widthPx: 640, backgroundColorCustom: "#123456", cardPaddingPx: 32, borderRadiusPx: 16, } as any); expect(tokens.justifyClass).toBe("justify-start"); expect(tokens.wrapperStyle.width).toBe("40em"); expect(tokens.videoStyle.width).toBe("40em"); expect(tokens.wrapperStyle.backgroundColor).toBe("#123456"); expect(tokens.wrapperStyle.padding).toBe("2em"); expect(tokens.wrapperStyle.borderRadius).toBe("1em"); }); it("widthMode 가 full 이면 wrapper/video width 를 100% 로 설정해야 한다", () => { const tokens = computeVideoPublicTokens({ ...baseProps, widthMode: "full", widthPx: 640, } as any); expect(tokens.wrapperStyle.width).toBe("100%"); expect(tokens.videoStyle.width).toBe("100%"); }); it("aspectRatio 에 따라 aspectClass 를 설정해야 한다", () => { const defaultTokens = computeVideoPublicTokens({ ...baseProps, aspectRatio: "16:9", } as any); const fourThree = computeVideoPublicTokens({ ...baseProps, aspectRatio: "4:3", } as any); const oneOne = computeVideoPublicTokens({ ...baseProps, aspectRatio: "1:1", } as any); expect(defaultTokens.aspectClass).toBe(""); expect(fourThree.aspectClass).toBe(" pb-video-wrapper--4by3"); expect(oneOne.aspectClass).toBe(" pb-video-wrapper--1by1"); }); }); describe("videoHelpers.computeVideoExportTokens", () => { const baseProps: VideoBlockProps = { sourceUrl: "/videos/sample-export.mp4", align: "center", widthMode: "auto", aspectRatio: "16:9", }; const escapers = { escapeAttr: (value: string) => value }; it("fixed width / 카드 스타일을 px 단위 스타일 파트로 계산해야 한다", () => { const tokens = computeVideoExportTokens( { ...baseProps, align: "left", widthMode: "fixed", widthPx: 640, backgroundColorCustom: "#123456", cardPaddingPx: 24, borderRadiusPx: 16, } as any, escapers, ); expect(tokens.aspectClass).toBe(""); expect(tokens.wrapperStyleParts).toContain("width:640px"); expect(tokens.videoStyleParts).toContain("width:640px"); expect(tokens.wrapperStyleParts).toContain("background-color:#123456"); expect(tokens.wrapperStyleParts).toContain("padding:24px"); expect(tokens.wrapperStyleParts).toContain("border-radius:16px"); expect(tokens.outerStyleParts).toContain("display:flex"); expect(tokens.outerStyleParts).toContain("justify-content:flex-start"); }); it("widthMode 가 full 이면 width:100% 로 설정하고 align 에 따라 justify-content 를 계산해야 한다", () => { const right = computeVideoExportTokens( { ...baseProps, align: "right", widthMode: "full", } as any, escapers, ); expect(right.wrapperStyleParts).toContain("width:100%"); expect(right.videoStyleParts).toContain("width:100%"); expect(right.outerStyleParts).toContain("justify-content:flex-end"); const center = computeVideoExportTokens( { ...baseProps, align: "center", widthMode: "auto", } as any, escapers, ); expect(center.outerStyleParts).toContain("justify-content:center"); }); it("aspectRatio 에 따라 aspectClass 를 설정해야 한다", () => { const def = computeVideoExportTokens( { ...baseProps, aspectRatio: "16:9", } as any, escapers, ); const fourThree = computeVideoExportTokens( { ...baseProps, aspectRatio: "4:3", } as any, escapers, ); const oneOne = computeVideoExportTokens( { ...baseProps, aspectRatio: "1:1", } as any, escapers, ); expect(def.aspectClass).toBe(""); expect(fourThree.aspectClass).toBe(" pb-video-wrapper--4by3"); expect(oneOne.aspectClass).toBe(" pb-video-wrapper--1by1"); }); }); describe("videoHelpers.computeVideoEditorTokens", () => { const baseProps: VideoBlockProps = { sourceUrl: "/videos/sample-editor.mp4", align: "center", widthMode: "auto", aspectRatio: "16:9", }; it("fixed width / 카드 스타일을 px 단위로 계산해야 한다", () => { const tokens = computeVideoEditorTokens({ ...baseProps, align: "left", widthMode: "fixed", widthPx: 640, backgroundColorCustom: "#123456", cardPaddingPx: 24, borderRadiusPx: 16, } as any); expect(tokens.justifyClass).toBe("justify-start"); expect(tokens.wrapperStyle.width).toBe("640px"); expect(tokens.videoStyle.width).toBe("640px"); expect(tokens.wrapperStyle.backgroundColor).toBe("#123456"); expect(tokens.wrapperStyle.padding).toBe("24px"); expect(tokens.wrapperStyle.borderRadius).toBe("16px"); }); it("widthMode 가 full 이면 wrapper/video width 를 100% 로 설정해야 한다", () => { const tokens = computeVideoEditorTokens({ ...baseProps, widthMode: "full", widthPx: 640, } as any); expect(tokens.wrapperStyle.width).toBe("100%"); expect(tokens.videoStyle.width).toBe("100%"); }); it("aspectRatio 에 따라 aspectClass 를 설정해야 한다", () => { const defaultTokens = computeVideoEditorTokens({ ...baseProps, aspectRatio: "16:9", } as any); const fourThree = computeVideoEditorTokens({ ...baseProps, aspectRatio: "4:3", } as any); const oneOne = computeVideoEditorTokens({ ...baseProps, aspectRatio: "1:1", } as any); expect(defaultTokens.aspectClass).toBe(""); expect(fourThree.aspectClass).toBe(" pb-video-wrapper--4by3"); expect(oneOne.aspectClass).toBe(" pb-video-wrapper--1by1"); }); });