1import { describe, expect, it } from "vite-plus/test";
2
3import { prepareVideoFirstFrame } from "./videoFirstFrame";
4
5type PreviewVideo = Parameters<typeof prepareVideoFirstFrame>[0];
6
7function previewVideo(overrides: Partial<PreviewVideo> = {}): PreviewVideo {
8 return {
9 autoplay: false,
10 paused: true,
11 seeking: false,
12 currentTime: 0,
13 duration: 5,
14 played: { length: 0, start: () => 0, end: () => 0 },
15 src: "https://environment.test/api/assets/signed/video.mp4?signature=example",
16 ...overrides,
17 };
18}
19
20describe("prepareVideoFirstFrame", () => {
21 it.each([
22 [5, 0.1],
23 [0.05, 0.025],
24 ])(
25 "seeks within a %s second video only once when metadata repeats",
26 (duration, expectedPosition) => {
27 const video = previewVideo({ duration });
28 const seeks: number[] = [];
29 Object.defineProperty(video, "currentTime", {
30 get: () => seeks.at(-1) ?? 0,
31 set: (value: number) => seeks.push(value),
32 });
33 prepareVideoFirstFrame(video);
34 prepareVideoFirstFrame(video);
35
36 expect(seeks).toEqual([expectedPosition]);
37 },
38 );
39
40 it.each<Partial<PreviewVideo>>([
41 { autoplay: true },
42 { paused: false },
43 { seeking: true },
44 { currentTime: 2 },
45 { played: { length: 1, start: () => 0, end: () => 2 } },
46 { duration: 0 },
47 { duration: Number.POSITIVE_INFINITY },
48 ])("does not seek over playback or unavailable metadata: %j", (state) => {
49 const video = previewVideo(state);
50 const position = video.currentTime;
51
52 prepareVideoFirstFrame(video);
53
54 expect(video.currentTime).toBe(position);
55 });
56
57 it.each([
58 ["video.mp4#t=0,4", 0],
59 ["video.mp4#xywh=0,0,100,100&%74=3", 0],
60 ["video%23t=3.mp4", 0.1],
61 ])(
62 "distinguishes temporal fragments from encoded filename hashes: %s",
63 (path, expectedPosition) => {
64 const video = previewVideo({ src: `https://environment.test/${path}` });
65
66 prepareVideoFirstFrame(video);
67
68 expect(video.currentTime).toBe(expectedPosition);
69 },
70 );
71
72 it("tolerates a browser rejecting the preview seek", () => {
73 const video = previewVideo();
74 Object.defineProperty(video, "currentTime", {
75 get: () => 0,
76 set: () => {
77 throw new Error("The stream is not seekable yet");
78 },
79 });
80
81 expect(() => prepareVideoFirstFrame(video)).not.toThrow();
82 });
83});