PluginProbe ʕ •ᴥ•ʔ
Presto Player / 4.3.3
Presto Player v4.3.3
4.4.0 4.3.3 4.3.2 4.3.1 4.3.0 4.2.4 4.2.3 4.2.2 4.2.0 4.2.1 trunk 1.10.0 1.10.1 1.10.2 1.11.0 1.12.0 1.13.0 1.14.0 1.14.1 1.5.10 1.5.11 1.5.12 1.5.13 1.5.14 1.5.15 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.10 1.6.11 1.6.12 1.6.13 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.8.0 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.11 2.0.12 2.0.13 2.0.14 2.0.15 2.0.16 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.2.0 2.2.1 2.2.2 2.2.3 2.2.3-beta1 2.3.0 2.3.1 2.3.2 2.3.3 3.0.0 3.0.0-beta1 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.1.0 3.1.1 3.1.2 3.1.3 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4
presto-player / src / admin / dashboard / hooks / test / useMediaDetail.spec.js
presto-player / src / admin / dashboard / hooks / test Last commit date
useCompleteOnboarding.spec.js 2 months ago useDateRangePicker.spec.js 3 months ago useEmail.spec.js 3 months ago useEngagementChartData.spec.js 3 months ago useLicenseSettings.spec.js 3 months ago useLink.spec.js 3 months ago useMediaDetail.spec.js 3 months ago useMediaLibrary.spec.js 3 months ago useMediaList.spec.js 2 months ago usePerformanceSettings.spec.js 3 months ago useRegisterActivePage.spec.js 3 months ago useSettingOption.spec.js 3 months ago useSimpleSettingsPage.spec.js 3 months ago useTopPerforming.spec.js 3 months ago useTopVideosPaginated.spec.js 3 months ago useUpgradeCTA.spec.js 3 months ago useUserDetail.spec.js 3 months ago
useMediaDetail.spec.js
206 lines
1 import { renderHook, act } from "@testing-library/react-hooks";
2 import apiFetch from "@wordpress/api-fetch";
3 import { addQueryArgs } from "@wordpress/url";
4 import useMediaDetail from "../useMediaDetail";
5
6 jest.mock("@wordpress/api-fetch");
7
8 beforeAll(() => {
9 // The hook uses the WordPress URL helper via the global `wp.url` namespace
10 // (not the named import), which webpack provides at runtime.
11 global.wp = global.wp || {};
12 global.wp.url = { addQueryArgs };
13 });
14
15 beforeEach(() => {
16 apiFetch.mockReset();
17 });
18
19 describe("useMediaDetail", () => {
20 it("does not fire any fetches when mediaId is falsy", async () => {
21 const { result } = renderHook(() => useMediaDetail(null));
22 await act(async () => {});
23 expect(apiFetch).not.toHaveBeenCalled();
24 expect(result.current.media).toBeNull();
25 });
26
27 it("fetches the video, then runs all three analytics calls in parallel", async () => {
28 apiFetch
29 // 1) /presto-player/v1/videos/42 — base video
30 .mockResolvedValueOnce({
31 id: 42,
32 title: "Demo",
33 src: "https://cdn/v.mp4",
34 type: "self-hosted",
35 post_id: null,
36 })
37 // 2) views, 3) avg, 4) timeline (Promise.all preserves array order)
38 .mockResolvedValueOnce(7)
39 .mockResolvedValueOnce(180)
40 .mockResolvedValueOnce([{ position: 0, retention: 1 }]);
41
42 const { result, waitForNextUpdate } = renderHook(() => useMediaDetail(42));
43 await waitForNextUpdate();
44
45 expect(result.current.isLoading).toBe(false);
46 expect(result.current.media).toMatchObject({
47 id: 42,
48 title: "Demo",
49 src: "https://cdn/v.mp4",
50 provider: "self-hosted",
51 // No postId on response → shortcode falls back to videoResponse.id.
52 shortcode: "[presto_player id=42]",
53 });
54
55 expect(result.current.stats).toEqual({
56 uniqueViews: 7,
57 avgWatchTime: "00:03:00",
58 retentionData: [{ position: 0, retention: 1 }],
59 });
60
61 // 1 video + 3 analytics = 4 calls (no WP enrichment without post_id).
62 expect(apiFetch).toHaveBeenCalledTimes(4);
63 });
64
65 it("enriches media metadata via WP REST when a post_id is present", async () => {
66 apiFetch
67 .mockResolvedValueOnce({
68 id: 42,
69 title: "Basic Title",
70 src: "https://cdn/v.mp4",
71 post_id: 100,
72 })
73 // /wp/v2/presto-videos?include=100&_embed=1
74 .mockResolvedValueOnce([
75 {
76 post_title: "Rich WP Title",
77 details: {
78 poster: "https://cdn/poster.jpg",
79 provider: "youtube",
80 preset: { id: 9 },
81 branding: { color: "red" },
82 blockAttributes: { foo: 1 },
83 tracks: [{ kind: "captions" }],
84 },
85 },
86 ])
87 .mockResolvedValueOnce(0)
88 .mockResolvedValueOnce(0)
89 .mockResolvedValueOnce([]);
90
91 const { result, waitForNextUpdate } = renderHook(() => useMediaDetail(42));
92 await waitForNextUpdate();
93
94 expect(result.current.media).toMatchObject({
95 title: "Rich WP Title",
96 poster: "https://cdn/poster.jpg",
97 provider: "youtube",
98 preset: { id: 9 },
99 branding: { color: "red" },
100 blockAttributes: { foo: 1 },
101 tracks: [{ kind: "captions" }],
102 // post_id present → shortcode uses it.
103 shortcode: "[presto_player id=100]",
104 });
105 expect(apiFetch).toHaveBeenCalledTimes(5);
106 });
107
108 it("falls back to basic video data when WP REST enrichment fails", async () => {
109 const warnSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
110
111 apiFetch
112 .mockResolvedValueOnce({
113 id: 42,
114 title: "Basic",
115 src: "https://cdn/v.mp4",
116 post_id: 100,
117 })
118 .mockRejectedValueOnce(new Error("404"))
119 .mockResolvedValueOnce(0)
120 .mockResolvedValueOnce(0)
121 .mockResolvedValueOnce([]);
122
123 const { result, waitForNextUpdate } = renderHook(() => useMediaDetail(42));
124 await waitForNextUpdate();
125
126 expect(result.current.media).toMatchObject({
127 title: "Basic",
128 shortcode: "[presto_player id=100]",
129 });
130 expect(warnSpy).toHaveBeenCalled();
131 warnSpy.mockRestore();
132 });
133
134 it("falls back to default analytics defaults when the analytics calls fail (free tier)", async () => {
135 const warnSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
136
137 apiFetch
138 .mockResolvedValueOnce({ id: 42, title: "x", post_id: null })
139 // Promise.all rejects on first failure; the subsequent mocks are
140 // never read.
141 .mockRejectedValueOnce(new Error("403"));
142
143 const { result, waitForNextUpdate } = renderHook(() => useMediaDetail(42));
144 await waitForNextUpdate();
145
146 expect(result.current.stats).toEqual({
147 uniqueViews: 0,
148 avgWatchTime: "00:00:00",
149 retentionData: [],
150 });
151 expect(warnSpy).toHaveBeenCalled();
152 warnSpy.mockRestore();
153 });
154
155 it("surfaces a top-level error message when the video fetch itself fails", async () => {
156 const errorSpy = jest
157 .spyOn(console, "error")
158 .mockImplementation(() => {});
159 apiFetch.mockRejectedValueOnce(new Error("video gone"));
160
161 const { result, waitForNextUpdate } = renderHook(() => useMediaDetail(42));
162 await waitForNextUpdate();
163
164 expect(result.current.error).toBe("video gone");
165 expect(result.current.isLoading).toBe(false);
166 expect(errorSpy).toHaveBeenCalled();
167 errorSpy.mockRestore();
168 });
169
170 it("refetch passes start/end into the analytics path query string", async () => {
171 apiFetch
172 .mockResolvedValueOnce({ id: 42, title: "x", post_id: null })
173 .mockResolvedValueOnce(0)
174 .mockResolvedValueOnce(0)
175 .mockResolvedValueOnce([]);
176
177 const { result, waitForNextUpdate } = renderHook(() => useMediaDetail(42));
178 await waitForNextUpdate();
179
180 apiFetch.mockReset();
181 apiFetch
182 .mockResolvedValueOnce({ id: 42, title: "x", post_id: null })
183 .mockResolvedValueOnce(0)
184 .mockResolvedValueOnce(0)
185 .mockResolvedValueOnce([]);
186
187 const from = new Date(Date.UTC(2026, 0, 5));
188 const to = new Date(Date.UTC(2026, 0, 10));
189 await act(async () => {
190 await result.current.refetch(from, to);
191 });
192
193 // Analytics calls (last 3 of the new 4) carry the date params.
194 const analyticsPaths = apiFetch.mock.calls
195 .slice(1)
196 .map((c) => c[0].path);
197 expect(analyticsPaths.every((p) => p.includes("start=2026-01-05"))).toBe(
198 true
199 );
200 expect(analyticsPaths.every((p) => p.includes("end=2026-01-10"))).toBe(
201 true
202 );
203 });
204
205 });
206