@thepalaceproject/circulation-admin 1.43.0 → 1.44.0-post.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/CLAUDE.md +28 -7
  2. package/README.md +11 -5
  3. package/dist/circulation-admin.js +1 -1
  4. package/eslint.config.mjs +9 -1
  5. package/jest.config.js +22 -8
  6. package/package.json +6 -20
  7. package/tests/jest/actions.test.ts +962 -0
  8. package/tests/jest/components/AccountPage.test.tsx +41 -0
  9. package/tests/jest/components/AdvancedSearchBooleanFilter.test.tsx +231 -0
  10. package/tests/jest/components/AdvancedSearchBuilder.test.tsx +93 -20
  11. package/tests/jest/components/AdvancedSearchFilter.test.tsx +142 -0
  12. package/tests/jest/components/AdvancedSearchFilterInput.test.tsx +212 -0
  13. package/tests/jest/components/AdvancedSearchFilterViewer.test.tsx +52 -0
  14. package/tests/jest/components/AdvancedSearchValueFilter.test.tsx +123 -0
  15. package/tests/jest/components/Announcement.test.tsx +76 -0
  16. package/tests/jest/components/AnnouncementForm.test.tsx +229 -0
  17. package/tests/jest/components/AnnouncementsSection.test.tsx +146 -0
  18. package/tests/jest/components/Autocomplete.test.tsx +67 -0
  19. package/tests/jest/components/BookCoverEditor.test.tsx +393 -0
  20. package/tests/jest/components/BookDetails.test.tsx +133 -0
  21. package/tests/jest/components/BookDetailsContainer.test.tsx +116 -0
  22. package/tests/jest/components/BookDetailsEditor.test.tsx +275 -0
  23. package/tests/jest/components/BookDetailsTabContainer.test.tsx +237 -0
  24. package/tests/jest/components/BookEditForm.test.tsx +352 -0
  25. package/tests/jest/components/BookEditor.test.tsx +20 -21
  26. package/tests/jest/components/CatalogPage.test.tsx +158 -0
  27. package/tests/jest/components/CatalogServices.test.tsx +100 -0
  28. package/tests/jest/components/ChangePasswordForm.test.tsx +163 -0
  29. package/tests/jest/components/CirculationEventsDownloadForm.test.tsx +41 -0
  30. package/tests/jest/components/Classifications.test.tsx +166 -0
  31. package/tests/jest/components/ClassificationsForm.test.tsx +458 -0
  32. package/tests/jest/components/ClassificationsTable.test.tsx +57 -0
  33. package/tests/jest/components/Collections.test.tsx +335 -7
  34. package/tests/jest/components/ColorPicker.test.tsx +58 -0
  35. package/tests/jest/components/ComplaintForm.test.tsx +171 -0
  36. package/tests/jest/components/Complaints.test.tsx +259 -0
  37. package/tests/jest/components/ConfigPage.test.tsx +58 -0
  38. package/tests/jest/components/ConfigTabContainer.test.tsx +196 -0
  39. package/tests/jest/components/ContextProvider.test.tsx +138 -0
  40. package/tests/jest/components/Contributors.test.tsx +157 -0
  41. package/tests/jest/components/CustomListEditor.test.tsx +367 -0
  42. package/tests/jest/components/CustomListEntriesEditor.test.tsx +1240 -0
  43. package/tests/jest/components/CustomListPage.test.tsx +57 -0
  44. package/tests/jest/components/CustomListSearch.test.tsx +275 -0
  45. package/tests/jest/components/CustomLists.test.tsx +621 -4
  46. package/tests/jest/components/CustomListsForBook.test.tsx +353 -0
  47. package/tests/jest/components/CustomListsSidebar.test.tsx +251 -0
  48. package/tests/jest/components/DashboardPage.test.tsx +76 -0
  49. package/tests/jest/components/DebugAuthentication.test.tsx +1 -5
  50. package/tests/jest/components/DiagnosticsServiceTabs.test.tsx +141 -0
  51. package/tests/jest/components/DiagnosticsServiceType.test.tsx +75 -0
  52. package/tests/jest/components/DiagnosticsTabContainer.test.tsx +147 -0
  53. package/tests/jest/components/DiscoveryServices.test.tsx +154 -7
  54. package/tests/jest/components/EditableConfigList.test.tsx +606 -9
  55. package/tests/jest/components/EditableInput.test.tsx +250 -1
  56. package/tests/jest/components/EditorField.test.tsx +95 -0
  57. package/tests/jest/components/EntryPointsContainer.test.tsx +97 -0
  58. package/tests/jest/components/EntryPointsTabs.test.tsx +88 -0
  59. package/tests/jest/components/ErrorMessage.test.tsx +97 -0
  60. package/tests/jest/components/Footer.test.tsx +141 -0
  61. package/tests/jest/components/GenreForm.test.tsx +152 -0
  62. package/tests/jest/components/Header.test.tsx +421 -0
  63. package/tests/jest/components/IndividualAdminEditForm.test.tsx +506 -1
  64. package/tests/jest/components/IndividualAdmins.test.tsx +124 -8
  65. package/tests/jest/components/InputList.test.tsx +493 -0
  66. package/tests/jest/components/InventoryReportRequestModal.test.tsx +16 -42
  67. package/tests/jest/components/JsonField.test.tsx +20 -0
  68. package/tests/jest/components/Lane.test.tsx +132 -10
  69. package/tests/jest/components/LaneCustomListsEditor.test.tsx +450 -0
  70. package/tests/jest/components/LaneEditor.test.tsx +377 -13
  71. package/tests/jest/components/LanePage.test.tsx +68 -0
  72. package/tests/jest/components/Lanes.test.tsx +446 -0
  73. package/tests/jest/components/LanesSidebar.test.tsx +621 -0
  74. package/tests/jest/components/LanguageField.test.tsx +40 -0
  75. package/tests/jest/components/Libraries.test.tsx +48 -0
  76. package/tests/jest/components/LibraryEditForm.test.tsx +383 -0
  77. package/tests/jest/components/LibraryRegistration.test.tsx +354 -0
  78. package/tests/jest/components/LibraryRegistrationForm.test.tsx +203 -0
  79. package/tests/jest/components/LoadButton.test.tsx +49 -0
  80. package/tests/jest/components/ManagePatrons.test.tsx +73 -0
  81. package/tests/jest/components/ManagePatronsForm.test.tsx +180 -0
  82. package/tests/jest/components/ManagePatronsTabContainer.test.tsx +143 -0
  83. package/tests/jest/components/MetadataServices.test.tsx +95 -0
  84. package/tests/jest/components/NeighborhoodAnalyticsForm.test.tsx +167 -0
  85. package/tests/jest/components/PairedMenus.test.tsx +220 -0
  86. package/tests/jest/components/PatronAuthServices.test.tsx +114 -0
  87. package/tests/jest/components/PatronBlockingRulesEditor.test.tsx +3 -1
  88. package/tests/jest/components/PatronInfo.test.tsx +54 -0
  89. package/tests/jest/components/ProtocolFormField.test.tsx +479 -7
  90. package/tests/jest/components/ResetAdobeId.test.tsx +302 -0
  91. package/tests/jest/components/SaveButton.test.tsx +47 -0
  92. package/tests/jest/components/SelfTestResult.test.tsx +164 -0
  93. package/tests/jest/components/SelfTests.test.tsx +601 -0
  94. package/tests/jest/components/SelfTestsCategory.test.tsx +200 -0
  95. package/tests/jest/components/SelfTestsTabContainer.test.tsx +216 -0
  96. package/tests/jest/components/ServiceEditForm.test.tsx +1092 -0
  97. package/tests/jest/components/ServiceWithRegistrationsEditForm.test.tsx +110 -0
  98. package/tests/jest/components/SetupPage.test.tsx +41 -0
  99. package/tests/jest/components/SingleStatListItem.test.tsx +62 -0
  100. package/tests/jest/components/SitewideAnnouncements.test.tsx +134 -0
  101. package/tests/jest/components/Stats.test.tsx +4 -15
  102. package/tests/jest/components/TabContainer.test.tsx +68 -0
  103. package/tests/jest/components/TextWithEditMode.test.tsx +218 -0
  104. package/tests/jest/components/Timestamp.test.tsx +74 -0
  105. package/tests/jest/components/ToolTip.test.tsx +46 -0
  106. package/tests/jest/components/TroubleshootingCategoryPage.test.tsx +69 -0
  107. package/tests/jest/components/TroubleshootingPage.test.tsx +94 -0
  108. package/tests/jest/components/TroubleshootingTabContainer.test.tsx +68 -0
  109. package/tests/jest/components/UpdatingLoader.test.tsx +36 -0
  110. package/tests/jest/components/WelcomePage.test.tsx +43 -0
  111. package/tests/jest/components/WithEditButton.test.tsx +55 -0
  112. package/tests/jest/components/WithRemoveButton.test.tsx +95 -0
  113. package/tests/jest/editorAdapter.test.ts +245 -0
  114. package/tests/jest/features/book.test.ts +75 -80
  115. package/tests/jest/fixtures/classificationsData.ts +45 -0
  116. package/tests/jest/fixtures/genreData.ts +811 -0
  117. package/tests/jest/index.test.tsx +50 -0
  118. package/tests/jest/models/Admin.test.ts +206 -0
  119. package/tests/jest/reducers/bookCoverPreview.test.ts +81 -0
  120. package/tests/jest/reducers/classifications.test.ts +161 -0
  121. package/tests/jest/reducers/collections.test.ts +96 -0
  122. package/tests/jest/reducers/complaints.test.ts +166 -0
  123. package/tests/jest/reducers/createFetchEditReducer.test.ts +220 -0
  124. package/tests/jest/reducers/createRegisterLibraryReducer.test.ts +51 -0
  125. package/tests/jest/reducers/customListDetails.test.ts +120 -0
  126. package/tests/jest/reducers/customListEditor.test.ts +2823 -0
  127. package/tests/jest/reducers/managePatrons.test.ts +71 -0
  128. package/tests/jest/testUtils/formDataShim.test.ts +135 -0
  129. package/tests/jest/testUtils/formDataShim.ts +112 -0
  130. package/tests/jest/utils/sharedFunctions.test.ts +121 -0
  131. package/tsconfig.json +3 -4
  132. package/testReporter.js +0 -29
  133. package/tests/jest/README.md +0 -6
@@ -0,0 +1,962 @@
1
+ import * as fetchMock from "fetch-mock-jest";
2
+
3
+ import ActionCreator from "../../src/actions";
4
+
5
+ class MockDataFetcher {
6
+ resolve: boolean = true;
7
+ testData: any = "test";
8
+
9
+ fetchOPDSData(url) {
10
+ return new Promise((resolve, reject) => {
11
+ if (this.resolve) {
12
+ resolve(this.testData);
13
+ } else {
14
+ reject({
15
+ status: null,
16
+ response: "test error",
17
+ url: url,
18
+ });
19
+ }
20
+ });
21
+ }
22
+
23
+ fetch(url, _options = {}) {
24
+ return new Promise((resolve, reject) => {
25
+ if (this.resolve) {
26
+ resolve(this.testData);
27
+ } else {
28
+ reject({
29
+ message: "test error",
30
+ });
31
+ }
32
+ });
33
+ }
34
+ }
35
+
36
+ const fetcher = new MockDataFetcher() as any;
37
+ const actions = new ActionCreator(fetcher, "token");
38
+
39
+ describe("actions", () => {
40
+ afterEach(() => {
41
+ fetchMock.restore();
42
+ });
43
+
44
+ describe("postForm", () => {
45
+ const type = "TEST";
46
+ const url = "http://example.com/test";
47
+ const formData = new (window as any).FormData();
48
+ formData.append("test", "test");
49
+
50
+ it("dispatches request, success, and load", async () => {
51
+ const dispatch = jest.fn();
52
+ const responseText = "response";
53
+ fetchMock.post(url, responseText);
54
+
55
+ await actions.postForm(type, url, formData)(dispatch);
56
+ const fetchArgs = fetchMock.calls();
57
+
58
+ expect(dispatch.mock.calls.length).toBe(3);
59
+ expect(dispatch.mock.calls[0][0].type).toBe(
60
+ `${type}_${ActionCreator.REQUEST}`
61
+ );
62
+ expect(dispatch.mock.calls[1][0].type).toBe(
63
+ `${type}_${ActionCreator.SUCCESS}`
64
+ );
65
+ expect(dispatch.mock.calls[2][0].type).toBe(
66
+ `${type}_${ActionCreator.LOAD}`
67
+ );
68
+ expect(dispatch.mock.calls[2][0].data).toBe(responseText);
69
+ expect(fetchMock.called()).toBe(true);
70
+ expect(fetchArgs[0][0]).toBe(url);
71
+ expect(fetchArgs[0][1].method).toBe("POST");
72
+ const expectedHeaders = new Headers();
73
+ expectedHeaders.append("X-CSRF-Token", "token");
74
+ expect(fetchArgs[0][1].headers).toStrictEqual(expectedHeaders);
75
+ expect(fetchArgs[0][1].body).toBe(formData);
76
+ });
77
+
78
+ it("postForm with JSON response dispatches request, success, and load", async () => {
79
+ const dispatch = jest.fn();
80
+ // prettier-ignore
81
+ const responseText = "{\"id\": \"test\", \"name\": \"test\"}";
82
+ fetchMock.mock(url, responseText);
83
+
84
+ await actions.postForm(type, url, formData, "POST", "", "JSON")(dispatch);
85
+ const fetchArgs = fetchMock.calls();
86
+
87
+ expect(dispatch.mock.calls.length).toBe(3);
88
+ expect(dispatch.mock.calls[0][0].type).toBe(
89
+ `${type}_${ActionCreator.REQUEST}`
90
+ );
91
+ expect(dispatch.mock.calls[1][0].type).toBe(
92
+ `${type}_${ActionCreator.SUCCESS}`
93
+ );
94
+ expect(dispatch.mock.calls[2][0].type).toBe(
95
+ `${type}_${ActionCreator.LOAD}`
96
+ );
97
+ expect(dispatch.mock.calls[2][0].data).toStrictEqual(
98
+ JSON.parse(responseText)
99
+ );
100
+ expect(fetchMock.called()).toBe(true);
101
+ expect(fetchArgs[0][0]).toBe(url);
102
+ expect(fetchArgs[0][1].method).toBe("POST");
103
+ const expectedHeaders = new Headers();
104
+ expectedHeaders.append("X-CSRF-Token", "token");
105
+ expect(fetchArgs[0][1].headers).toStrictEqual(expectedHeaders);
106
+ expect(fetchArgs[0][1].body).toBe(formData);
107
+ });
108
+
109
+ it("dispatches a DELETE request", async () => {
110
+ const dispatch = jest.fn();
111
+ const responseText = "response";
112
+ fetchMock.mock(url, responseText);
113
+
114
+ await actions.postForm(type, url, formData, "DELETE")(dispatch);
115
+ const fetchArgs = fetchMock.calls();
116
+
117
+ expect(dispatch.mock.calls.length).toBe(3);
118
+ expect(fetchMock.called()).toBe(true);
119
+ expect(fetchArgs[0][0]).toBe(url);
120
+ expect(fetchArgs[0][1].method).toBe("DELETE");
121
+ const expectedHeaders = new Headers();
122
+ expectedHeaders.append("X-CSRF-Token", "token");
123
+ expect(fetchArgs[0][1].headers).toStrictEqual(expectedHeaders);
124
+ expect(fetchArgs[0][1].body).toBe(formData);
125
+ });
126
+
127
+ it("dispatches failure on bad response", async () => {
128
+ const dispatch = jest.fn();
129
+ fetchMock.mock(url, { status: 500, body: { detail: "problem detail" } });
130
+
131
+ try {
132
+ await actions.postForm(type, url, formData)(dispatch);
133
+ // shouldn't get here
134
+ expect(false).toBe(true);
135
+ } catch (err) {
136
+ expect(dispatch.mock.calls.length).toBe(2);
137
+ expect(dispatch.mock.calls[0][0].type).toBe(
138
+ `${type}_${ActionCreator.REQUEST}`
139
+ );
140
+ expect(dispatch.mock.calls[1][0].type).toBe(
141
+ `${type}_${ActionCreator.FAILURE}`
142
+ );
143
+ expect(fetchMock.called()).toBe(true);
144
+ expect(err).toStrictEqual({
145
+ status: 500,
146
+ response: "problem detail",
147
+ url: url,
148
+ });
149
+ }
150
+ });
151
+
152
+ it("dispatches failure on non-JSON response", async () => {
153
+ const dispatch = jest.fn();
154
+ fetchMock.mock(url, { status: 500, body: "Not JSON" });
155
+
156
+ try {
157
+ await actions.postForm(
158
+ type,
159
+ url,
160
+ formData,
161
+ "POST",
162
+ "Default error"
163
+ )(dispatch);
164
+ // shouldn't get here
165
+ expect(false).toBe(true);
166
+ } catch (err) {
167
+ expect(dispatch.mock.calls.length).toBe(2);
168
+ expect(dispatch.mock.calls[0][0].type).toBe(
169
+ `${type}_${ActionCreator.REQUEST}`
170
+ );
171
+ expect(dispatch.mock.calls[1][0].type).toBe(
172
+ `${type}_${ActionCreator.FAILURE}`
173
+ );
174
+ expect(fetchMock.called()).toBe(true);
175
+ expect(err).toStrictEqual({
176
+ status: 500,
177
+ response: "Default error",
178
+ url: url,
179
+ });
180
+ }
181
+ });
182
+
183
+ it("dispatches failure on no response", async () => {
184
+ const dispatch = jest.fn();
185
+ fetchMock.mock(url, () => {
186
+ throw { message: "test error" };
187
+ });
188
+
189
+ try {
190
+ await actions.postForm(type, url, formData)(dispatch);
191
+ // shouldn't get here
192
+ expect(false).toBe(true);
193
+ } catch (err) {
194
+ expect(dispatch.mock.calls.length).toBe(2);
195
+ expect(dispatch.mock.calls[0][0].type).toBe(
196
+ `${type}_${ActionCreator.REQUEST}`
197
+ );
198
+ expect(dispatch.mock.calls[1][0].type).toBe(
199
+ `${type}_${ActionCreator.FAILURE}`
200
+ );
201
+ expect(fetchMock.called()).toBe(true);
202
+ expect(err).toStrictEqual({
203
+ status: null,
204
+ response: "test error",
205
+ url: url,
206
+ });
207
+ }
208
+ });
209
+ });
210
+
211
+ describe("postJSON", () => {
212
+ const type = "TEST";
213
+ const url = "http://example.com/test";
214
+ const jsonData = { test: 1 };
215
+
216
+ it("dispatches request and success", async () => {
217
+ const dispatch = jest.fn();
218
+ fetchMock.mock(url, 200);
219
+
220
+ await actions.postJSON<{ test: number }>(type, url, jsonData)(dispatch);
221
+ const fetchArgs = fetchMock.calls();
222
+
223
+ expect(dispatch.mock.calls.length).toBe(2);
224
+ expect(dispatch.mock.calls[0][0].type).toBe(
225
+ `${type}_${ActionCreator.REQUEST}`
226
+ );
227
+ expect(dispatch.mock.calls[1][0].type).toBe(
228
+ `${type}_${ActionCreator.SUCCESS}`
229
+ );
230
+ expect(fetchMock.called()).toBe(true);
231
+ expect(fetchArgs[0][0]).toBe(url);
232
+ expect(fetchArgs[0][1].method).toBe("POST");
233
+ const expectedHeaders = new Headers();
234
+ expectedHeaders.append("Accept", "application/json");
235
+ expectedHeaders.append("Content-Type", "application/json");
236
+ expectedHeaders.append("X-CSRF-Token", "token");
237
+ expect(fetchArgs[0][1].headers).toStrictEqual(expectedHeaders);
238
+ expect(fetchArgs[0][1].body).toBe(JSON.stringify(jsonData));
239
+ });
240
+
241
+ it("dispatches failure on bad response", async () => {
242
+ const dispatch = jest.fn();
243
+ fetchMock.mock(url, { status: 500, body: { detail: "problem detail" } });
244
+
245
+ try {
246
+ await actions.postJSON<{ test: number }>(type, url, jsonData)(dispatch);
247
+ // shouldn't get here
248
+ expect(false).toBe(true);
249
+ } catch (err) {
250
+ expect(dispatch.mock.calls.length).toBe(2);
251
+ expect(dispatch.mock.calls[0][0].type).toBe(
252
+ `${type}_${ActionCreator.REQUEST}`
253
+ );
254
+ expect(dispatch.mock.calls[1][0].type).toBe(
255
+ `${type}_${ActionCreator.FAILURE}`
256
+ );
257
+ expect(fetchMock.called()).toBe(true);
258
+ expect(err).toStrictEqual({
259
+ status: 500,
260
+ response: "problem detail",
261
+ url: url,
262
+ });
263
+ }
264
+ });
265
+
266
+ it("dispatches failure on no response", async () => {
267
+ const dispatch = jest.fn();
268
+ fetchMock.mock(url, () => {
269
+ throw { message: "test error" };
270
+ });
271
+
272
+ try {
273
+ await actions.postJSON<{ test: number }>(type, url, jsonData)(dispatch);
274
+ // shouldn't get here
275
+ expect(false).toBe(true);
276
+ } catch (err) {
277
+ expect(dispatch.mock.calls.length).toBe(2);
278
+ expect(dispatch.mock.calls[0][0].type).toBe(
279
+ `${type}_${ActionCreator.REQUEST}`
280
+ );
281
+ expect(dispatch.mock.calls[1][0].type).toBe(
282
+ `${type}_${ActionCreator.FAILURE}`
283
+ );
284
+ expect(fetchMock.called()).toBe(true);
285
+ expect(err).toStrictEqual({
286
+ status: null,
287
+ response: "test error",
288
+ url: url,
289
+ });
290
+ }
291
+ });
292
+ });
293
+
294
+ describe("fetchComplaints", () => {
295
+ it("dispatches request, load, and success", async () => {
296
+ const dispatch = jest.fn();
297
+ const complaintsData = {
298
+ book: { id: "test id" },
299
+ complaints: { "test-type": 1 },
300
+ };
301
+ fetcher.testData = {
302
+ ok: true,
303
+ status: 200,
304
+ json: () =>
305
+ new Promise<any>((resolve, _reject) => {
306
+ resolve(complaintsData);
307
+ }),
308
+ };
309
+ fetcher.resolve = true;
310
+
311
+ const data = await actions.fetchComplaints(
312
+ "http://example.com/complaints"
313
+ )(dispatch);
314
+ expect(dispatch.mock.calls.length).toBe(3);
315
+ expect(dispatch.mock.calls[0][0].type).toBe(
316
+ ActionCreator.COMPLAINTS_REQUEST
317
+ );
318
+ expect(dispatch.mock.calls[1][0].type).toBe(
319
+ ActionCreator.COMPLAINTS_SUCCESS
320
+ );
321
+ expect(dispatch.mock.calls[2][0].type).toBe(
322
+ ActionCreator.COMPLAINTS_LOAD
323
+ );
324
+ expect(data).toStrictEqual(complaintsData);
325
+ });
326
+ });
327
+
328
+ describe("postComplaint", () => {
329
+ it("dispatches request and success", async () => {
330
+ const postComplaintUrl = "http://example.com/postComplaint";
331
+ const dispatch = jest.fn();
332
+ const data = {
333
+ type: "test type",
334
+ };
335
+
336
+ fetchMock.mock(postComplaintUrl, 201);
337
+
338
+ await actions.postComplaint(postComplaintUrl, data)(dispatch);
339
+ const fetchArgs = fetchMock.calls();
340
+
341
+ expect(dispatch.mock.calls.length).toBe(2);
342
+ expect(dispatch.mock.calls[0][0].type).toBe(
343
+ ActionCreator.POST_COMPLAINT_REQUEST
344
+ );
345
+ expect(dispatch.mock.calls[1][0].type).toBe(
346
+ ActionCreator.POST_COMPLAINT_SUCCESS
347
+ );
348
+ expect(fetchMock.called()).toBe(true);
349
+ expect(fetchArgs[0][0]).toBe(postComplaintUrl);
350
+ expect(fetchArgs[0][1].method).toBe("POST");
351
+ expect(fetchArgs[0][1].body).toBe(JSON.stringify(data));
352
+ });
353
+ });
354
+
355
+ describe("resolveComplaints", () => {
356
+ it("dispatches request and success", async () => {
357
+ const resolveComplaintsUrl = "http://example.com/resolveComplaints";
358
+ const dispatch = jest.fn();
359
+ const formData = new (window as any).FormData();
360
+ formData.append("type", "test type");
361
+
362
+ fetchMock.mock(resolveComplaintsUrl, "server response");
363
+
364
+ await actions.resolveComplaints(resolveComplaintsUrl, formData)(dispatch);
365
+ const fetchArgs = fetchMock.calls();
366
+
367
+ expect(dispatch.mock.calls.length).toBe(3);
368
+ expect(dispatch.mock.calls[0][0].type).toBe(
369
+ ActionCreator.RESOLVE_COMPLAINTS_REQUEST
370
+ );
371
+ expect(dispatch.mock.calls[1][0].type).toBe(
372
+ ActionCreator.RESOLVE_COMPLAINTS_SUCCESS
373
+ );
374
+ expect(fetchMock.called()).toBe(true);
375
+ expect(fetchArgs[0][0]).toBe(resolveComplaintsUrl);
376
+ expect(fetchArgs[0][1].method).toBe("POST");
377
+ expect(fetchArgs[0][1].body).toBe(formData);
378
+ });
379
+ });
380
+
381
+ describe("fetchGenreTree", () => {
382
+ it("dispatches request, load, and success", async () => {
383
+ const dispatch = jest.fn();
384
+ const genresData = "test data";
385
+ fetcher.testData = {
386
+ ok: true,
387
+ status: 200,
388
+ json: () =>
389
+ new Promise<any>((resolve, _reject) => {
390
+ resolve(genresData);
391
+ }),
392
+ };
393
+ fetcher.resolve = true;
394
+
395
+ const data = await actions.fetchGenreTree("http://example.com/genres")(
396
+ dispatch
397
+ );
398
+ expect(dispatch.mock.calls.length).toBe(3);
399
+ expect(dispatch.mock.calls[0][0].type).toBe(
400
+ ActionCreator.GENRE_TREE_REQUEST
401
+ );
402
+ expect(dispatch.mock.calls[1][0].type).toBe(
403
+ ActionCreator.GENRE_TREE_SUCCESS
404
+ );
405
+ expect(dispatch.mock.calls[2][0].type).toBe(
406
+ ActionCreator.GENRE_TREE_LOAD
407
+ );
408
+ expect(data).toBe(genresData);
409
+ });
410
+ });
411
+
412
+ describe("editClassifications", () => {
413
+ it("dispatches request and success", async () => {
414
+ const editClassificationsUrl = "http://example.com/editClassifications";
415
+ const dispatch = jest.fn();
416
+ const formData = new (window as any).FormData();
417
+ const newGenreTree = ["Drama", "Epic Fantasy", "Women Detectives"];
418
+ newGenreTree.forEach((genre) => formData.append("genres", genre));
419
+
420
+ fetchMock.mock(editClassificationsUrl, "server response");
421
+
422
+ await actions.editClassifications(
423
+ editClassificationsUrl,
424
+ formData
425
+ )(dispatch);
426
+ const fetchArgs = fetchMock.calls();
427
+
428
+ expect(dispatch.mock.calls.length).toBe(3);
429
+ expect(dispatch.mock.calls[0][0].type).toBe(
430
+ ActionCreator.EDIT_CLASSIFICATIONS_REQUEST
431
+ );
432
+ expect(dispatch.mock.calls[1][0].type).toBe(
433
+ ActionCreator.EDIT_CLASSIFICATIONS_SUCCESS
434
+ );
435
+ expect(fetchMock.called()).toBe(true);
436
+ expect(fetchArgs[0][0]).toBe(editClassificationsUrl);
437
+ expect(fetchArgs[0][1].method).toBe("POST");
438
+ expect(fetchArgs[0][1].body).toBe(formData);
439
+ });
440
+ });
441
+
442
+ describe("fetchClassifications", () => {
443
+ it("dispatches request, load, and success", async () => {
444
+ const dispatch = jest.fn();
445
+ const classificationsData = {
446
+ book: { id: "test id" },
447
+ classifications: { "test-type": 1 },
448
+ };
449
+ fetcher.testData = {
450
+ ok: true,
451
+ status: 200,
452
+ json: () =>
453
+ new Promise<any>((resolve, _reject) => {
454
+ resolve(classificationsData);
455
+ }),
456
+ };
457
+ fetcher.resolve = true;
458
+
459
+ const data = await actions.fetchClassifications(
460
+ "http://example.com/classifications"
461
+ )(dispatch);
462
+ expect(dispatch.mock.calls.length).toBe(3);
463
+ expect(dispatch.mock.calls[0][0].type).toBe(
464
+ ActionCreator.CLASSIFICATIONS_REQUEST
465
+ );
466
+ expect(dispatch.mock.calls[1][0].type).toBe(
467
+ ActionCreator.CLASSIFICATIONS_SUCCESS
468
+ );
469
+ expect(dispatch.mock.calls[2][0].type).toBe(
470
+ ActionCreator.CLASSIFICATIONS_LOAD
471
+ );
472
+ expect(data).toBe(classificationsData);
473
+ });
474
+ });
475
+
476
+ describe("fetchDiagnostics", () => {
477
+ it("dispatches request, load, and success", async () => {
478
+ const dispatch = jest.fn();
479
+ const diagnosticsData = "diagnostics";
480
+ fetcher.testData = {
481
+ ok: true,
482
+ status: 200,
483
+ json: () =>
484
+ new Promise<any>((resolve, _reject) => {
485
+ resolve(diagnosticsData);
486
+ }),
487
+ };
488
+ fetcher.resolve = true;
489
+
490
+ const data = await actions.fetchDiagnostics()(dispatch);
491
+ expect(dispatch.mock.calls.length).toBe(3);
492
+ expect(dispatch.mock.calls[0][0].type).toBe(
493
+ `${ActionCreator.DIAGNOSTICS}_${ActionCreator.REQUEST}`
494
+ );
495
+ expect(dispatch.mock.calls[0][0].url).toBe("/admin/diagnostics");
496
+ expect(dispatch.mock.calls[1][0].type).toBe(
497
+ `${ActionCreator.DIAGNOSTICS}_${ActionCreator.SUCCESS}`
498
+ );
499
+ expect(dispatch.mock.calls[2][0].type).toBe(
500
+ `${ActionCreator.DIAGNOSTICS}_${ActionCreator.LOAD}`
501
+ );
502
+ expect(data).toStrictEqual(diagnosticsData);
503
+ });
504
+ });
505
+
506
+ describe("fetchLibraries", () => {
507
+ it("dispatches request, load, and success", async () => {
508
+ const dispatch = jest.fn();
509
+ const librariesData = "libraries";
510
+ fetcher.testData = {
511
+ ok: true,
512
+ status: 200,
513
+ json: () =>
514
+ new Promise<any>((resolve, _reject) => {
515
+ resolve(librariesData);
516
+ }),
517
+ };
518
+ fetcher.resolve = true;
519
+
520
+ const data = await actions.fetchLibraries()(dispatch);
521
+ expect(dispatch.mock.calls.length).toBe(3);
522
+ expect(dispatch.mock.calls[0][0].type).toBe(
523
+ `${ActionCreator.LIBRARIES}_${ActionCreator.REQUEST}`
524
+ );
525
+ expect(dispatch.mock.calls[1][0].type).toBe(
526
+ `${ActionCreator.LIBRARIES}_${ActionCreator.SUCCESS}`
527
+ );
528
+ expect(dispatch.mock.calls[2][0].type).toBe(
529
+ `${ActionCreator.LIBRARIES}_${ActionCreator.LOAD}`
530
+ );
531
+ expect(data).toStrictEqual(librariesData);
532
+ });
533
+ });
534
+
535
+ describe("editLibrary", () => {
536
+ it("dispatches request and success", async () => {
537
+ const editLibraryUrl = "/admin/libraries";
538
+ const dispatch = jest.fn();
539
+ const formData = new (window as any).FormData();
540
+ formData.append("name", "new name");
541
+
542
+ fetchMock.mock(editLibraryUrl, "server response");
543
+
544
+ await actions.editLibrary(formData)(dispatch);
545
+ const fetchArgs = fetchMock.calls();
546
+
547
+ expect(dispatch.mock.calls.length).toBe(3);
548
+ expect(dispatch.mock.calls[0][0].type).toBe(
549
+ `${ActionCreator.EDIT_LIBRARY}_${ActionCreator.REQUEST}`
550
+ );
551
+ expect(dispatch.mock.calls[1][0].type).toBe(
552
+ `${ActionCreator.EDIT_LIBRARY}_${ActionCreator.SUCCESS}`
553
+ );
554
+ expect(fetchMock.called()).toBe(true);
555
+ expect(fetchArgs[0][0]).toBe(editLibraryUrl);
556
+ expect(fetchArgs[0][1].method).toBe("POST");
557
+ expect(fetchArgs[0][1].body).toBe(formData);
558
+ });
559
+ });
560
+
561
+ describe("fetchCollections", () => {
562
+ it("dispatches request, load, and success", async () => {
563
+ const dispatch = jest.fn();
564
+ const collectionsData = "collections";
565
+ fetcher.testData = {
566
+ ok: true,
567
+ status: 200,
568
+ json: () =>
569
+ new Promise<any>((resolve, _reject) => {
570
+ resolve(collectionsData);
571
+ }),
572
+ };
573
+ fetcher.resolve = true;
574
+
575
+ const data = await actions.fetchCollections()(dispatch);
576
+ expect(dispatch.mock.calls.length).toBe(3);
577
+ expect(dispatch.mock.calls[0][0].type).toBe(
578
+ `${ActionCreator.COLLECTIONS}_${ActionCreator.REQUEST}`
579
+ );
580
+ expect(dispatch.mock.calls[1][0].type).toBe(
581
+ `${ActionCreator.COLLECTIONS}_${ActionCreator.SUCCESS}`
582
+ );
583
+ expect(dispatch.mock.calls[2][0].type).toBe(
584
+ `${ActionCreator.COLLECTIONS}_${ActionCreator.LOAD}`
585
+ );
586
+ expect(data).toStrictEqual(collectionsData);
587
+ });
588
+ });
589
+
590
+ describe("editCollection", () => {
591
+ it("dispatches request and success", async () => {
592
+ const editCollectionUrl = "/admin/collections";
593
+ const dispatch = jest.fn();
594
+ const formData = new (window as any).FormData();
595
+ formData.append("name", "new name");
596
+
597
+ fetchMock.mock(editCollectionUrl, "server response");
598
+
599
+ await actions.editCollection(formData)(dispatch);
600
+ const fetchArgs = fetchMock.calls();
601
+
602
+ expect(dispatch.mock.calls.length).toBe(3);
603
+ expect(dispatch.mock.calls[0][0].type).toBe(
604
+ `${ActionCreator.EDIT_COLLECTION}_${ActionCreator.REQUEST}`
605
+ );
606
+ expect(dispatch.mock.calls[1][0].type).toBe(
607
+ `${ActionCreator.EDIT_COLLECTION}_${ActionCreator.SUCCESS}`
608
+ );
609
+ expect(fetchMock.called()).toBe(true);
610
+ expect(fetchArgs[0][0]).toBe(editCollectionUrl);
611
+ expect(fetchArgs[0][1].method).toBe("POST");
612
+ expect(fetchArgs[0][1].body).toBe(formData);
613
+ });
614
+ });
615
+
616
+ describe("importCollection", () => {
617
+ it("dispatches request and success", async () => {
618
+ const importCollectionUrl = "/admin/collection/123/import";
619
+ const dispatch = jest.fn();
620
+
621
+ fetchMock.mock(importCollectionUrl, "server response");
622
+
623
+ await actions.importCollection("123", false)(dispatch);
624
+ const fetchArgs = fetchMock.calls();
625
+
626
+ expect(dispatch.mock.calls.length).toBe(3);
627
+ expect(dispatch.mock.calls[0][0].type).toBe(
628
+ `${ActionCreator.IMPORT_COLLECTION}_${ActionCreator.REQUEST}`
629
+ );
630
+ expect(dispatch.mock.calls[1][0].type).toBe(
631
+ `${ActionCreator.IMPORT_COLLECTION}_${ActionCreator.SUCCESS}`
632
+ );
633
+ expect(dispatch.mock.calls[2][0].type).toBe(
634
+ `${ActionCreator.IMPORT_COLLECTION}_${ActionCreator.LOAD}`
635
+ );
636
+ expect(fetchMock.called()).toBe(true);
637
+ expect(fetchArgs[0][0]).toBe(importCollectionUrl);
638
+ expect(fetchArgs[0][1].method).toBe("POST");
639
+ // `body` is typed as `BodyInit`; this request sends a FormData.
640
+ expect((fetchArgs[0][1].body as FormData).get("force")).toBe("false");
641
+ });
642
+ });
643
+
644
+ describe("fetchIndividualAdmins", () => {
645
+ it("dispatches request, load, and success", async () => {
646
+ const dispatch = jest.fn();
647
+ const individualAdminsData = "individualAdmins";
648
+ fetcher.testData = {
649
+ ok: true,
650
+ status: 200,
651
+ json: () =>
652
+ new Promise<any>((resolve, _reject) => {
653
+ resolve(individualAdminsData);
654
+ }),
655
+ };
656
+ fetcher.resolve = true;
657
+
658
+ const data = await actions.fetchIndividualAdmins()(dispatch);
659
+ expect(dispatch.mock.calls.length).toBe(3);
660
+ expect(dispatch.mock.calls[0][0].type).toBe(
661
+ `${ActionCreator.INDIVIDUAL_ADMINS}_${ActionCreator.REQUEST}`
662
+ );
663
+ expect(dispatch.mock.calls[1][0].type).toBe(
664
+ `${ActionCreator.INDIVIDUAL_ADMINS}_${ActionCreator.SUCCESS}`
665
+ );
666
+ expect(dispatch.mock.calls[2][0].type).toBe(
667
+ `${ActionCreator.INDIVIDUAL_ADMINS}_${ActionCreator.LOAD}`
668
+ );
669
+ expect(data).toStrictEqual(individualAdminsData);
670
+ });
671
+ });
672
+
673
+ describe("editIndividualAdmin", () => {
674
+ it("dispatches request and success", async () => {
675
+ const editIndividualAdminUrl = "/admin/individual_admins";
676
+ const dispatch = jest.fn();
677
+ const formData = new (window as any).FormData();
678
+ formData.append("email", "email");
679
+
680
+ fetchMock.mock(editIndividualAdminUrl, "server response");
681
+
682
+ await actions.editIndividualAdmin(formData)(dispatch);
683
+ const fetchArgs = fetchMock.calls();
684
+
685
+ expect(dispatch.mock.calls.length).toBe(3);
686
+ expect(dispatch.mock.calls[0][0].type).toBe(
687
+ `${ActionCreator.EDIT_INDIVIDUAL_ADMIN}_${ActionCreator.REQUEST}`
688
+ );
689
+ expect(dispatch.mock.calls[1][0].type).toBe(
690
+ `${ActionCreator.EDIT_INDIVIDUAL_ADMIN}_${ActionCreator.SUCCESS}`
691
+ );
692
+ expect(fetchMock.called()).toBe(true);
693
+ expect(fetchArgs[0][0]).toBe(editIndividualAdminUrl);
694
+ expect(fetchArgs[0][1].method).toBe("POST");
695
+ expect(fetchArgs[0][1].body).toBe(formData);
696
+ });
697
+ });
698
+
699
+ describe("getSelfTests", () => {
700
+ it("dispatches request, load, and success", async () => {
701
+ const dispatch = jest.fn();
702
+ const collectionSelfTestURL = "/admin/collection_self_tests";
703
+ const selfTestData = "selfTestData";
704
+ fetcher.testData = {
705
+ ok: true,
706
+ status: 200,
707
+ json: () =>
708
+ new Promise<any>((resolve, _reject) => {
709
+ resolve(selfTestData);
710
+ }),
711
+ };
712
+ fetcher.resolve = true;
713
+
714
+ const data = await actions.getSelfTests(`${collectionSelfTestURL}/1`)(
715
+ dispatch
716
+ );
717
+ expect(dispatch.mock.calls.length).toBe(3);
718
+ expect(dispatch.mock.calls[0][0].type).toBe(
719
+ `${ActionCreator.GET_SELF_TESTS}_${ActionCreator.REQUEST}`
720
+ );
721
+ expect(dispatch.mock.calls[1][0].type).toBe(
722
+ `${ActionCreator.GET_SELF_TESTS}_${ActionCreator.SUCCESS}`
723
+ );
724
+ expect(dispatch.mock.calls[2][0].type).toBe(
725
+ `${ActionCreator.GET_SELF_TESTS}_${ActionCreator.LOAD}`
726
+ );
727
+ expect(data).toStrictEqual(selfTestData);
728
+ });
729
+ });
730
+
731
+ describe("runSelfTests", () => {
732
+ it("dispatches request and success", async () => {
733
+ const collectionUrl = "/admin/collection_self_tests/";
734
+ const collectionSelfTestURL = `${collectionUrl}/1`;
735
+ const dispatch = jest.fn();
736
+
737
+ fetchMock.mock(collectionSelfTestURL, "server response");
738
+
739
+ await actions.runSelfTests(collectionSelfTestURL)(dispatch);
740
+ const fetchArgs = fetchMock.calls();
741
+
742
+ expect(dispatch.mock.calls.length).toBe(3);
743
+ expect(dispatch.mock.calls[0][0].type).toBe(
744
+ `${ActionCreator.RUN_SELF_TESTS}_${ActionCreator.REQUEST}`
745
+ );
746
+ expect(dispatch.mock.calls[1][0].type).toBe(
747
+ `${ActionCreator.RUN_SELF_TESTS}_${ActionCreator.SUCCESS}`
748
+ );
749
+ expect(fetchMock.called()).toBe(true);
750
+ expect(fetchArgs[0][0]).toBe(collectionSelfTestURL);
751
+ expect(fetchArgs[0][1].method).toBe("POST");
752
+ });
753
+ });
754
+
755
+ describe("patronLookup", () => {
756
+ it("dispatches request, success, and load", async () => {
757
+ const formData = new (window as any).FormData();
758
+ formData.append("test", "test");
759
+ const dispatch = jest.fn();
760
+ // prettier-ignore
761
+ const response = "{\"id\": \"test\", \"name\": \"test\"}";
762
+
763
+ fetchMock.mock("/nypl/admin/manage_patrons", response);
764
+
765
+ await actions.patronLookup(formData, "nypl")(dispatch);
766
+ const fetchArgs = fetchMock.calls();
767
+
768
+ expect(dispatch.mock.calls.length).toBe(3);
769
+ expect(dispatch.mock.calls[0][0].type).toBe(
770
+ `${ActionCreator.PATRON_LOOKUP}_${ActionCreator.REQUEST}`
771
+ );
772
+ expect(dispatch.mock.calls[1][0].type).toBe(
773
+ `${ActionCreator.PATRON_LOOKUP}_${ActionCreator.SUCCESS}`
774
+ );
775
+ expect(dispatch.mock.calls[2][0].type).toBe(
776
+ `${ActionCreator.PATRON_LOOKUP}_${ActionCreator.LOAD}`
777
+ );
778
+ expect(dispatch.mock.calls[2][0].data).toStrictEqual(
779
+ JSON.parse(response)
780
+ );
781
+ expect(fetchMock.called()).toBe(true);
782
+ expect(fetchArgs[0][0]).toBe("/nypl/admin/manage_patrons");
783
+ expect(fetchArgs[0][1].method).toBe("POST");
784
+ expect(fetchArgs[0][1].body).toBe(formData);
785
+ });
786
+ });
787
+
788
+ describe("resetAdobeId", () => {
789
+ it("dispatches request and success", async () => {
790
+ const formData = new (window as any).FormData();
791
+ const dispatch = jest.fn();
792
+
793
+ fetchMock.mock(
794
+ "/nypl/admin/manage_patrons/reset_adobe_id",
795
+ "server response"
796
+ );
797
+
798
+ const data = await actions.resetAdobeId(formData, "nypl")(dispatch);
799
+ const fetchArgs = fetchMock.calls();
800
+
801
+ expect(dispatch.mock.calls.length).toBe(3);
802
+ expect(dispatch.mock.calls[0][0].type).toBe(
803
+ `${ActionCreator.RESET_ADOBE_ID}_${ActionCreator.REQUEST}`
804
+ );
805
+ expect(dispatch.mock.calls[1][0].type).toBe(
806
+ `${ActionCreator.RESET_ADOBE_ID}_${ActionCreator.SUCCESS}`
807
+ );
808
+ expect(fetchMock.called()).toBe(true);
809
+ expect(fetchArgs[0][0]).toBe("/nypl/admin/manage_patrons/reset_adobe_id");
810
+ expect(fetchArgs[0][1].method).toBe("POST");
811
+ expect(fetchArgs[0][1].body).toBe(formData);
812
+ expect(data.status).toBe(200);
813
+ });
814
+ });
815
+
816
+ describe("clearPatronData", () => {
817
+ it("dispatches load", () => {
818
+ const dispatch = jest.fn();
819
+
820
+ actions.clearPatronData()(dispatch);
821
+
822
+ expect(dispatch.mock.calls.length).toBe(1);
823
+ expect(dispatch.mock.calls[0][0].type).toBe(
824
+ `${ActionCreator.CLEAR_PATRON_DATA}_${ActionCreator.LOAD}`
825
+ );
826
+ expect(dispatch.mock.calls[0][0].data).toBe(null);
827
+ });
828
+ });
829
+
830
+ describe("fetchMoreCustomListEntries", () => {
831
+ it("dispatches request and load", async () => {
832
+ const dispatch = jest.fn();
833
+
834
+ const getState = jest.fn().mockReturnValue({
835
+ editor: {
836
+ customListDetails: {
837
+ data: {
838
+ nextPageUrl: "url",
839
+ },
840
+ isFetchingMoreEntries: false,
841
+ },
842
+ },
843
+ });
844
+
845
+ const customListDetailsData = {
846
+ title: "custom list",
847
+ };
848
+ fetcher.testData = customListDetailsData;
849
+ fetcher.resolve = true;
850
+
851
+ // FIXME: This is a bit of a hack that requires knowing that the implementation of
852
+ // fetchMoreCustomListEntries calls dispatch internally on another action creator.
853
+ // These tests should all be refactored at some point to account for this case.
854
+ const outerDispatch = (action) => action(dispatch);
855
+
856
+ const data = await actions.fetchMoreCustomListEntries()(
857
+ outerDispatch,
858
+ getState
859
+ );
860
+
861
+ expect(dispatch.mock.calls.length).toBe(3);
862
+ expect(dispatch.mock.calls[0][0].type).toBe(
863
+ `${ActionCreator.CUSTOM_LIST_DETAILS_MORE}_${ActionCreator.REQUEST}`
864
+ );
865
+ expect(dispatch.mock.calls[1][0].type).toBe(
866
+ `${ActionCreator.CUSTOM_LIST_DETAILS_MORE}_${ActionCreator.SUCCESS}`
867
+ );
868
+ expect(dispatch.mock.calls[2][0].type).toBe(
869
+ `${ActionCreator.CUSTOM_LIST_DETAILS_MORE}_${ActionCreator.LOAD}`
870
+ );
871
+ expect(data).toStrictEqual(customListDetailsData);
872
+ });
873
+ });
874
+
875
+ describe("fetchSitewideAnnouncements", () => {
876
+ it("dispatches request, load, and success", async () => {
877
+ const dispatch = jest.fn();
878
+ const sitewideAnnouncementsData = "sitewide announcements";
879
+ fetcher.testData = {
880
+ ok: true,
881
+ status: 200,
882
+ json: () =>
883
+ new Promise<any>((resolve) => {
884
+ resolve(sitewideAnnouncementsData);
885
+ }),
886
+ };
887
+ fetcher.resolve = true;
888
+
889
+ const data = await actions.fetchSitewideAnnouncements()(dispatch);
890
+ expect(dispatch.mock.calls.length).toBe(3);
891
+ expect(dispatch.mock.calls[0][0].type).toBe(
892
+ `${ActionCreator.SITEWIDE_ANNOUNCEMENTS}_${ActionCreator.REQUEST}`
893
+ );
894
+ expect(dispatch.mock.calls[0][0].url).toBe("/admin/announcements");
895
+ expect(dispatch.mock.calls[1][0].type).toBe(
896
+ `${ActionCreator.SITEWIDE_ANNOUNCEMENTS}_${ActionCreator.SUCCESS}`
897
+ );
898
+ expect(dispatch.mock.calls[2][0].type).toBe(
899
+ `${ActionCreator.SITEWIDE_ANNOUNCEMENTS}_${ActionCreator.LOAD}`
900
+ );
901
+ expect(data).toStrictEqual(sitewideAnnouncementsData);
902
+ });
903
+ });
904
+
905
+ describe("editSitewideAnnouncements", () => {
906
+ it("dispatches request and success", async () => {
907
+ const editSitewideAnnouncementsUrl = "/admin/announcements";
908
+ const dispatch = jest.fn();
909
+ const formData = new (window as any).FormData();
910
+ formData.append("announcements", "[]");
911
+
912
+ fetchMock.mock(editSitewideAnnouncementsUrl, "server response");
913
+
914
+ await actions.editSitewideAnnouncements(formData)(dispatch);
915
+ const fetchArgs = fetchMock.calls();
916
+
917
+ expect(dispatch.mock.calls.length).toBe(3);
918
+ expect(dispatch.mock.calls[0][0].type).toBe(
919
+ `${ActionCreator.EDIT_SITEWIDE_ANNOUNCEMENTS}_${ActionCreator.REQUEST}`
920
+ );
921
+ expect(dispatch.mock.calls[1][0].type).toBe(
922
+ `${ActionCreator.EDIT_SITEWIDE_ANNOUNCEMENTS}_${ActionCreator.SUCCESS}`
923
+ );
924
+ expect(fetchMock.called()).toBe(true);
925
+ expect(fetchArgs[0][0]).toBe(editSitewideAnnouncementsUrl);
926
+ expect(fetchArgs[0][1].method).toBe("POST");
927
+ expect(fetchArgs[0][1].body).toBe(formData);
928
+ });
929
+ });
930
+
931
+ describe("openCustomListEditor", () => {
932
+ it("dispatches CUSTOM_LIST_DETAILS_CLEAR and OPEN_CUSTOM_LIST_EDITOR", async () => {
933
+ const dispatch = jest.fn();
934
+
935
+ const getState = () => ({
936
+ editor: {
937
+ customLists: {
938
+ data: {
939
+ foo: "bar",
940
+ },
941
+ },
942
+ },
943
+ });
944
+
945
+ await actions.openCustomListEditor("list_id")(dispatch, getState);
946
+
947
+ expect(dispatch.mock.calls.length).toBe(2);
948
+
949
+ expect(dispatch.mock.calls[0][0].type).toBe(
950
+ `${ActionCreator.CUSTOM_LIST_DETAILS}_${ActionCreator.CLEAR}`
951
+ );
952
+
953
+ expect(dispatch.mock.calls[1][0].type).toBe(
954
+ ActionCreator.OPEN_CUSTOM_LIST_EDITOR
955
+ );
956
+ expect(dispatch.mock.calls[1][0].id).toBe("list_id");
957
+ expect(dispatch.mock.calls[1][0].data).toStrictEqual(
958
+ getState().editor.customLists.data
959
+ );
960
+ });
961
+ });
962
+ });