useCompleteOnboarding.spec.js
1 month ago
useDateRangePicker.spec.js
2 months ago
useEmail.spec.js
2 months ago
useEngagementChartData.spec.js
2 months ago
useLicenseSettings.spec.js
2 months ago
useLink.spec.js
2 months ago
useMediaDetail.spec.js
2 months ago
useMediaLibrary.spec.js
2 months ago
useMediaList.spec.js
1 month ago
usePerformanceSettings.spec.js
2 months ago
useRegisterActivePage.spec.js
2 months ago
useSettingOption.spec.js
2 months ago
useSimpleSettingsPage.spec.js
2 months ago
useTopPerforming.spec.js
2 months ago
useTopVideosPaginated.spec.js
2 months ago
useUpgradeCTA.spec.js
2 months ago
useUserDetail.spec.js
2 months ago
useEmail.spec.js
155 lines
| 1 | import { renderHook, act } from "@testing-library/react-hooks"; |
| 2 | import apiFetch from "@wordpress/api-fetch"; |
| 3 | import useEmail, { EMAIL_SUBMISSIONS_PATH } from "../useEmail"; |
| 4 | |
| 5 | jest.mock("@wordpress/api-fetch"); |
| 6 | |
| 7 | const makeRow = (id, date) => ({ |
| 8 | id, |
| 9 | email: `user${id}@example.com`, |
| 10 | video_title: "Video", |
| 11 | preset_name: "Default", |
| 12 | date, |
| 13 | }); |
| 14 | |
| 15 | // Fake of the Response object the hook gets back when apiFetch is called |
| 16 | // with `parse: false`. Returns a single page; total-pages defaults to 1. |
| 17 | const fakeResponse = (items, totalPages = 1) => ({ |
| 18 | headers: { get: (name) => (name === "X-WP-TotalPages" ? String(totalPages) : null) }, |
| 19 | json: async () => items, |
| 20 | }); |
| 21 | |
| 22 | const mockPagesOnce = (...pages) => { |
| 23 | pages.forEach((page, idx) => { |
| 24 | apiFetch.mockResolvedValueOnce(fakeResponse(page, pages.length)); |
| 25 | }); |
| 26 | }; |
| 27 | |
| 28 | beforeEach(() => { |
| 29 | apiFetch.mockReset(); |
| 30 | }); |
| 31 | |
| 32 | describe("useEmail", () => { |
| 33 | it("fetches /email-submissions on mount and exposes raw + sorted lists", async () => { |
| 34 | mockPagesOnce([ |
| 35 | makeRow(1, "2026-01-01T00:00:00"), |
| 36 | makeRow(2, "2026-03-01T00:00:00"), |
| 37 | ]); |
| 38 | |
| 39 | const { result, waitForNextUpdate } = renderHook(() => useEmail()); |
| 40 | |
| 41 | expect(result.current.loading).toBe(true); |
| 42 | await waitForNextUpdate(); |
| 43 | |
| 44 | expect(apiFetch).toHaveBeenCalledWith({ |
| 45 | path: `${EMAIL_SUBMISSIONS_PATH}?per_page=100&page=1`, |
| 46 | method: "GET", |
| 47 | parse: false, |
| 48 | }); |
| 49 | expect(result.current.loading).toBe(false); |
| 50 | expect(result.current.rawEmails).toHaveLength(2); |
| 51 | // Default sort: date desc → newest first. |
| 52 | expect(result.current.emails.map((r) => r.id)).toEqual([2, 1]); |
| 53 | expect(result.current.sortField).toBe("date"); |
| 54 | expect(result.current.sortOrder).toBe("desc"); |
| 55 | }); |
| 56 | |
| 57 | it("walks pages reported by X-WP-TotalPages and concatenates results", async () => { |
| 58 | mockPagesOnce( |
| 59 | [makeRow(1, "2026-01-01T00:00:00")], |
| 60 | [makeRow(2, "2026-02-01T00:00:00")], |
| 61 | [makeRow(3, "2026-03-01T00:00:00")] |
| 62 | ); |
| 63 | |
| 64 | const { result, waitForNextUpdate } = renderHook(() => useEmail()); |
| 65 | await waitForNextUpdate(); |
| 66 | |
| 67 | expect(apiFetch).toHaveBeenCalledTimes(3); |
| 68 | expect(result.current.rawEmails).toHaveLength(3); |
| 69 | expect(result.current.emails.map((r) => r.id)).toEqual([3, 2, 1]); |
| 70 | }); |
| 71 | |
| 72 | it("returns an empty list and clears loading when the API rejects", async () => { |
| 73 | // Hook logs the failure on its way to a graceful empty state — silence |
| 74 | // the expected console.error so the failure mode is the only signal. |
| 75 | const errorSpy = jest |
| 76 | .spyOn(console, "error") |
| 77 | .mockImplementation(() => {}); |
| 78 | apiFetch.mockRejectedValueOnce(new Error("boom")); |
| 79 | |
| 80 | const { result, waitForNextUpdate } = renderHook(() => useEmail()); |
| 81 | await waitForNextUpdate(); |
| 82 | |
| 83 | expect(result.current.loading).toBe(false); |
| 84 | expect(result.current.emails).toEqual([]); |
| 85 | expect(result.current.rawEmails).toEqual([]); |
| 86 | expect(errorSpy).toHaveBeenCalled(); |
| 87 | errorSpy.mockRestore(); |
| 88 | }); |
| 89 | |
| 90 | it("returns an empty list when the API returns a non-array payload", async () => { |
| 91 | apiFetch.mockResolvedValueOnce(fakeResponse({ unexpected: "shape" })); |
| 92 | |
| 93 | const { result, waitForNextUpdate } = renderHook(() => useEmail()); |
| 94 | await waitForNextUpdate(); |
| 95 | |
| 96 | expect(result.current.emails).toEqual([]); |
| 97 | }); |
| 98 | |
| 99 | it("flips sortOrder when handleSort is called for the active field (date)", async () => { |
| 100 | mockPagesOnce([ |
| 101 | makeRow(1, "2026-01-01T00:00:00"), |
| 102 | makeRow(2, "2026-03-01T00:00:00"), |
| 103 | ]); |
| 104 | const { result, waitForNextUpdate } = renderHook(() => useEmail()); |
| 105 | await waitForNextUpdate(); |
| 106 | |
| 107 | // Currently desc; toggle → asc → oldest first. |
| 108 | act(() => { |
| 109 | result.current.handleSort("date"); |
| 110 | }); |
| 111 | expect(result.current.sortOrder).toBe("asc"); |
| 112 | expect(result.current.emails.map((r) => r.id)).toEqual([1, 2]); |
| 113 | |
| 114 | // Toggle again → desc. |
| 115 | act(() => { |
| 116 | result.current.handleSort("date"); |
| 117 | }); |
| 118 | expect(result.current.sortOrder).toBe("desc"); |
| 119 | }); |
| 120 | |
| 121 | it("switching to a different sort field resets order to asc (or desc for date)", async () => { |
| 122 | mockPagesOnce([ |
| 123 | makeRow(1, "2026-01-01T00:00:00"), |
| 124 | makeRow(2, "2026-03-01T00:00:00"), |
| 125 | ]); |
| 126 | const { result, waitForNextUpdate } = renderHook(() => useEmail()); |
| 127 | await waitForNextUpdate(); |
| 128 | |
| 129 | act(() => { |
| 130 | result.current.handleSort("email"); |
| 131 | }); |
| 132 | expect(result.current.sortField).toBe("email"); |
| 133 | expect(result.current.sortOrder).toBe("asc"); |
| 134 | |
| 135 | act(() => { |
| 136 | result.current.handleSort("date"); |
| 137 | }); |
| 138 | expect(result.current.sortField).toBe("date"); |
| 139 | expect(result.current.sortOrder).toBe("desc"); |
| 140 | }); |
| 141 | |
| 142 | it("treats missing dates as epoch 0 in the comparator", async () => { |
| 143 | mockPagesOnce([ |
| 144 | { ...makeRow(1, ""), date: "" }, |
| 145 | makeRow(2, "2026-01-01T00:00:00"), |
| 146 | ]); |
| 147 | const { result, waitForNextUpdate } = renderHook(() => useEmail()); |
| 148 | await waitForNextUpdate(); |
| 149 | |
| 150 | // desc → real date first, missing-date row last. |
| 151 | expect(result.current.emails.map((r) => r.id)).toEqual([2, 1]); |
| 152 | }); |
| 153 | |
| 154 | }); |
| 155 |