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 |