@thepalaceproject/circulation-admin 1.43.0-post.10 → 1.43.0-post.14

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 (37) hide show
  1. package/package.json +1 -1
  2. package/tests/jest/components/Announcement.test.tsx +76 -0
  3. package/tests/jest/components/Autocomplete.test.tsx +67 -0
  4. package/tests/jest/components/BookCoverEditor.test.tsx +393 -0
  5. package/tests/jest/components/BookDetailsEditor.test.tsx +275 -0
  6. package/tests/jest/components/BookEditForm.test.tsx +352 -0
  7. package/tests/jest/components/CatalogPage.test.tsx +158 -0
  8. package/tests/jest/components/Classifications.test.tsx +166 -0
  9. package/tests/jest/components/ClassificationsForm.test.tsx +458 -0
  10. package/tests/jest/components/ClassificationsTable.test.tsx +57 -0
  11. package/tests/jest/components/ColorPicker.test.tsx +58 -0
  12. package/tests/jest/components/ComplaintForm.test.tsx +171 -0
  13. package/tests/jest/components/Complaints.test.tsx +259 -0
  14. package/tests/jest/components/ContextProvider.test.tsx +138 -0
  15. package/tests/jest/components/Contributors.test.tsx +157 -0
  16. package/tests/jest/components/CustomListEditor.test.tsx +367 -0
  17. package/tests/jest/components/CustomListEntriesEditor.test.tsx +1240 -0
  18. package/tests/jest/components/CustomLists.test.tsx +621 -4
  19. package/tests/jest/components/CustomListsForBook.test.tsx +315 -0
  20. package/tests/jest/components/DiagnosticsServiceTabs.test.tsx +141 -0
  21. package/tests/jest/components/DiagnosticsServiceType.test.tsx +75 -0
  22. package/tests/jest/components/EditorField.test.tsx +95 -0
  23. package/tests/jest/components/GenreForm.test.tsx +152 -0
  24. package/tests/jest/components/InputList.test.tsx +493 -0
  25. package/tests/jest/components/Lane.test.tsx +130 -7
  26. package/tests/jest/components/LaneCustomListsEditor.test.tsx +450 -0
  27. package/tests/jest/components/LaneEditor.test.tsx +367 -2
  28. package/tests/jest/components/Lanes.test.tsx +446 -0
  29. package/tests/jest/components/LanesSidebar.test.tsx +621 -0
  30. package/tests/jest/components/LibraryEditForm.test.tsx +383 -0
  31. package/tests/jest/components/LibraryRegistrationForm.test.tsx +203 -0
  32. package/tests/jest/components/PairedMenus.test.tsx +220 -0
  33. package/tests/jest/components/PatronAuthServices.test.tsx +114 -0
  34. package/tests/jest/components/ResetAdobeId.test.tsx +302 -0
  35. package/tests/jest/components/SitewideAnnouncements.test.tsx +134 -0
  36. package/tests/jest/components/TroubleshootingCategoryPage.test.tsx +69 -0
  37. package/tests/jest/components/TroubleshootingPage.test.tsx +94 -0
@@ -1,18 +1,61 @@
1
1
  import * as React from "react";
2
- import { render, screen } from "@testing-library/react";
2
+ import { render, screen, waitFor, within } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
3
4
  import { stub } from "sinon";
4
5
  import { LaneData } from "../../../src/interfaces";
5
6
  import LaneEditor from "../../../src/components/LaneEditor";
7
+ import * as navigate from "../../../src/utils/navigate";
6
8
 
7
9
  // Mock the LaneCustomListsEditor so we can verify that it is or isn't rendered. This serves as an
8
10
  // example of how to do something analogous to Enzyme's shallow rendering, when we don't want/need
9
11
  // to render the whole component tree down to HTML elements to test something. This technique is
10
12
  // useful for testing components in isolation (unit testing), instead of the integration testing
11
13
  // that RTL focuses on.
14
+ //
15
+ // The mock is a class component (not a bare function) so that LaneEditor's ref to
16
+ // it resolves to an instance exposing the imperative API that `save()`/`reset()`
17
+ // call (`getCustomListIds()` / `reset()`). It also renders its `filteredCustomLists`
18
+ // and a few buttons that invoke the `changeFilter` / `onUpdate` callbacks, so tests
19
+ // can drive the parent's filtering and custom-list-selection behavior without the
20
+ // real drag-and-drop editor.
12
21
 
13
22
  jest.mock("../../../src/components/LaneCustomListsEditor", () => ({
14
23
  __esModule: true,
15
- default: () => <div data-testid="LaneCustomListsEditor" />,
24
+ default: class MockLaneCustomListsEditor extends React.Component<any> {
25
+ getCustomListIds() {
26
+ return this.props.customListIds;
27
+ }
28
+ reset() {
29
+ // No-op stand-in; its presence lets LaneEditor.reset() call it via the ref.
30
+ }
31
+ render() {
32
+ const { filteredCustomLists = [], changeFilter, onUpdate } = this.props;
33
+ return (
34
+ <div data-testid="LaneCustomListsEditor">
35
+ <ul data-testid="filtered-custom-lists">
36
+ {filteredCustomLists.map((list: any) => (
37
+ <li key={list.id}>{list.name}</li>
38
+ ))}
39
+ </ul>
40
+ <button type="button" onClick={() => changeFilter("shared-in")}>
41
+ filter shared-in
42
+ </button>
43
+ <button type="button" onClick={() => changeFilter("shared-out")}>
44
+ filter shared-out
45
+ </button>
46
+ <button type="button" onClick={() => changeFilter("")}>
47
+ filter all
48
+ </button>
49
+ <button type="button" onClick={() => onUpdate([1, 2])}>
50
+ set lists 1 2
51
+ </button>
52
+ <button type="button" onClick={() => onUpdate([99])}>
53
+ set lists 99
54
+ </button>
55
+ </div>
56
+ );
57
+ }
58
+ },
16
59
  }));
17
60
 
18
61
  const customListsData = [
@@ -142,3 +185,325 @@ describe("LaneEditor", () => {
142
185
  expect(screen.queryByTestId("LaneCustomListsEditor")).toBeNull();
143
186
  });
144
187
  });
188
+
189
+ // These drive the real name field and the mocked custom-lists editor's callbacks,
190
+ // asserting on the resulting DOM and on the props passed to the injected
191
+ // `editLane` / `deleteLane` / `toggleLaneVisibility` handlers. Navigation is
192
+ // asserted by spying on `navigate.navigateTo`.
193
+ describe("LaneEditor — name, list editing, and actions", () => {
194
+ const twoLists = [
195
+ { id: 1, name: "list 1", entries: [], is_owner: true, is_shared: false },
196
+ { id: 2, name: "list 2", entries: [], is_owner: false, is_shared: true },
197
+ ];
198
+
199
+ const resolved = () => jest.fn().mockResolvedValue(undefined);
200
+
201
+ it("shows the lane name, id, and visible status", () => {
202
+ const lane = createLaneData("Custom Lane", false);
203
+ render(
204
+ <LaneEditor
205
+ library="library"
206
+ lane={lane}
207
+ customLists={customListsData}
208
+ editOrCreate="edit"
209
+ editLane={resolved()}
210
+ deleteLane={resolved()}
211
+ findParentOfLane={() => null}
212
+ toggleLaneVisibility={resolved()}
213
+ />
214
+ );
215
+
216
+ expect(screen.getByText("Custom Lane")).toBeInTheDocument();
217
+ expect(screen.getByText("ID-1")).toBeInTheDocument();
218
+ expect(screen.getByText(/currently visible/i)).toBeInTheDocument();
219
+ });
220
+
221
+ it("updates the visible status when the lane prop changes", () => {
222
+ const visibleLane = createLaneData("Lane", false);
223
+ const hiddenLane = { ...visibleLane, visible: false };
224
+ const shared = {
225
+ library: "library",
226
+ customLists: customListsData,
227
+ editOrCreate: "edit",
228
+ editLane: resolved(),
229
+ deleteLane: resolved(),
230
+ findParentOfLane: () => null,
231
+ toggleLaneVisibility: resolved(),
232
+ };
233
+
234
+ const { rerender } = render(<LaneEditor {...shared} lane={visibleLane} />);
235
+ expect(screen.getByText(/currently visible/i)).toBeInTheDocument();
236
+
237
+ rerender(<LaneEditor {...shared} lane={hiddenLane} />);
238
+ expect(screen.getByText(/currently hidden/i)).toBeInTheDocument();
239
+ });
240
+
241
+ it("shows when the lane's parent is hidden", () => {
242
+ const hiddenLane = { ...createLaneData("Lane", false), visible: false };
243
+ const hiddenParent = {
244
+ ...createLaneData("Parent", false),
245
+ id: 5,
246
+ visible: false,
247
+ };
248
+ render(
249
+ <LaneEditor
250
+ library="library"
251
+ lane={hiddenLane}
252
+ customLists={customListsData}
253
+ editOrCreate="edit"
254
+ editLane={resolved()}
255
+ deleteLane={resolved()}
256
+ findParentOfLane={() => hiddenParent}
257
+ toggleLaneVisibility={resolved()}
258
+ />
259
+ );
260
+
261
+ expect(screen.getByText(/parent is currently hidden/i)).toBeInTheDocument();
262
+ });
263
+
264
+ it("toggles the inherit-parent-restrictions checkbox", async () => {
265
+ const user = userEvent.setup();
266
+ const lane = createLaneData("Custom Lane", false);
267
+ const parent = createLaneData("Parent", false);
268
+ render(
269
+ <LaneEditor
270
+ library="library"
271
+ lane={lane}
272
+ customLists={customListsData}
273
+ editOrCreate="edit"
274
+ editLane={resolved()}
275
+ deleteLane={resolved()}
276
+ findParentOfLane={() => parent}
277
+ toggleLaneVisibility={resolved()}
278
+ />
279
+ );
280
+
281
+ const checkbox = screen.getByRole("checkbox", {
282
+ name: /inherit restrictions from parent/i,
283
+ });
284
+ expect(checkbox).toBeChecked();
285
+ expect(
286
+ screen.queryByRole("button", { name: /cancel changes/i })
287
+ ).toBeNull();
288
+
289
+ await user.click(checkbox);
290
+
291
+ expect(checkbox).not.toBeChecked();
292
+ expect(
293
+ screen.getByRole("button", { name: /cancel changes/i })
294
+ ).toBeInTheDocument();
295
+ });
296
+
297
+ it("filters the custom lists shown in the editor", async () => {
298
+ const user = userEvent.setup();
299
+ const lane = createLaneData("Custom Lane", false);
300
+ render(
301
+ <LaneEditor
302
+ library="library"
303
+ lane={lane}
304
+ customLists={twoLists}
305
+ editOrCreate="edit"
306
+ editLane={resolved()}
307
+ deleteLane={resolved()}
308
+ findParentOfLane={() => null}
309
+ toggleLaneVisibility={resolved()}
310
+ />
311
+ );
312
+
313
+ const filtered = () => within(screen.getByTestId("filtered-custom-lists"));
314
+
315
+ // The default filter is "owned".
316
+ expect(filtered().getByText("list 1")).toBeInTheDocument();
317
+ expect(filtered().queryByText("list 2")).toBeNull();
318
+
319
+ await user.click(screen.getByRole("button", { name: /filter shared-in/i }));
320
+ expect(filtered().getByText("list 2")).toBeInTheDocument();
321
+ expect(filtered().queryByText("list 1")).toBeNull();
322
+
323
+ // "shared-out" keeps only owned-and-shared lists; neither qualifies.
324
+ await user.click(
325
+ screen.getByRole("button", { name: /filter shared-out/i })
326
+ );
327
+ expect(filtered().queryByText("list 1")).toBeNull();
328
+ expect(filtered().queryByText("list 2")).toBeNull();
329
+
330
+ await user.click(screen.getByRole("button", { name: /filter all/i }));
331
+ expect(filtered().getByText("list 1")).toBeInTheDocument();
332
+ expect(filtered().getByText("list 2")).toBeInTheDocument();
333
+ });
334
+
335
+ it("marks changes when the selected custom lists change", async () => {
336
+ const user = userEvent.setup();
337
+ const lane = createLaneData("Custom Lane", false);
338
+ render(
339
+ <LaneEditor
340
+ library="library"
341
+ lane={lane}
342
+ customLists={twoLists}
343
+ editOrCreate="edit"
344
+ editLane={resolved()}
345
+ deleteLane={resolved()}
346
+ findParentOfLane={() => null}
347
+ toggleLaneVisibility={resolved()}
348
+ />
349
+ );
350
+
351
+ expect(
352
+ screen.queryByRole("button", { name: /cancel changes/i })
353
+ ).toBeNull();
354
+
355
+ // Same number of lists, different contents.
356
+ await user.click(screen.getByRole("button", { name: /set lists 99/i }));
357
+ expect(
358
+ screen.getByRole("button", { name: /cancel changes/i })
359
+ ).toBeInTheDocument();
360
+
361
+ // Different number of lists.
362
+ await user.click(screen.getByRole("button", { name: /set lists 1 2/i }));
363
+ expect(
364
+ screen.getByRole("button", { name: /cancel changes/i })
365
+ ).toBeInTheDocument();
366
+ });
367
+
368
+ it("deletes the lane when the delete button is clicked", async () => {
369
+ const user = userEvent.setup();
370
+ const lane = createLaneData("Custom Lane", false);
371
+ const deleteLane = resolved();
372
+ render(
373
+ <LaneEditor
374
+ library="library"
375
+ lane={lane}
376
+ customLists={customListsData}
377
+ editOrCreate="edit"
378
+ editLane={resolved()}
379
+ deleteLane={deleteLane}
380
+ findParentOfLane={() => null}
381
+ toggleLaneVisibility={resolved()}
382
+ />
383
+ );
384
+
385
+ await user.click(screen.getByRole("button", { name: /delete lane/i }));
386
+
387
+ expect(deleteLane).toHaveBeenCalledTimes(1);
388
+ expect(deleteLane).toHaveBeenCalledWith(lane);
389
+ });
390
+
391
+ it("toggles lane visibility when the hide button is clicked", async () => {
392
+ const user = userEvent.setup();
393
+ const lane = createLaneData("Custom Lane", false);
394
+ const toggleLaneVisibility = resolved();
395
+ render(
396
+ <LaneEditor
397
+ library="library"
398
+ lane={lane}
399
+ customLists={customListsData}
400
+ editOrCreate="edit"
401
+ editLane={resolved()}
402
+ deleteLane={resolved()}
403
+ findParentOfLane={() => null}
404
+ toggleLaneVisibility={toggleLaneVisibility}
405
+ />
406
+ );
407
+
408
+ await user.click(screen.getByRole("button", { name: /hide lane/i }));
409
+
410
+ expect(toggleLaneVisibility).toHaveBeenCalledWith(lane, false);
411
+ });
412
+
413
+ it("saves the lane, building a FormData payload", async () => {
414
+ const user = userEvent.setup();
415
+ const lane = createLaneData("Custom Lane", false);
416
+ const parent = { ...createLaneData("Parent", false), id: 7 };
417
+ const editLane = resolved();
418
+ render(
419
+ <LaneEditor
420
+ library="library"
421
+ lane={lane}
422
+ customLists={customListsData}
423
+ editOrCreate="edit"
424
+ editLane={editLane}
425
+ deleteLane={resolved()}
426
+ findParentOfLane={() => parent}
427
+ toggleLaneVisibility={resolved()}
428
+ />
429
+ );
430
+
431
+ await user.click(screen.getByRole("button", { name: /save lane/i }));
432
+ await waitFor(() => expect(editLane).toHaveBeenCalledTimes(1));
433
+
434
+ const formData = editLane.mock.calls[0][0];
435
+ expect(formData.get("id")).toBe("1");
436
+ expect(formData.get("parent_id")).toBe("7");
437
+ expect(formData.get("display_name")).toBe("Custom Lane");
438
+ expect(formData.get("custom_list_ids")).toBe(JSON.stringify([1]));
439
+ expect(formData.get("inherit_parent_restrictions")).toBe("true");
440
+ });
441
+
442
+ it("navigates to the edit page after creating a new lane", async () => {
443
+ const user = userEvent.setup();
444
+ const editLane = resolved();
445
+ const navSpy = jest
446
+ .spyOn(navigate, "navigateTo")
447
+ .mockImplementation(() => undefined);
448
+ render(
449
+ <LaneEditor
450
+ library="library"
451
+ customLists={twoLists}
452
+ responseBody="5"
453
+ editLane={editLane}
454
+ deleteLane={resolved()}
455
+ findParentOfLane={() => null}
456
+ toggleLaneVisibility={resolved()}
457
+ />
458
+ );
459
+
460
+ // A new lane's name field starts in edit mode.
461
+ await user.type(screen.getByRole("textbox"), "new lane name");
462
+ await user.click(screen.getByRole("button", { name: /save lane/i }));
463
+
464
+ await waitFor(() =>
465
+ expect(navSpy).toHaveBeenCalledWith("/admin/web/lanes/library/edit/5")
466
+ );
467
+
468
+ navSpy.mockRestore();
469
+ });
470
+
471
+ it("cancels changes, resetting the form", async () => {
472
+ const user = userEvent.setup();
473
+ const lane = createLaneData("Custom Lane", false);
474
+ const parent = createLaneData("Parent", false);
475
+ render(
476
+ <LaneEditor
477
+ library="library"
478
+ lane={lane}
479
+ customLists={customListsData}
480
+ editOrCreate="edit"
481
+ editLane={resolved()}
482
+ deleteLane={resolved()}
483
+ findParentOfLane={() => parent}
484
+ toggleLaneVisibility={resolved()}
485
+ />
486
+ );
487
+
488
+ const checkbox = screen.getByRole("checkbox", {
489
+ name: /inherit restrictions from parent/i,
490
+ });
491
+ expect(checkbox).toBeChecked();
492
+
493
+ await user.click(checkbox);
494
+ expect(checkbox).not.toBeChecked();
495
+
496
+ await user.click(screen.getByRole("button", { name: /cancel changes/i }));
497
+
498
+ // reset() restores the inherited-restrictions state...
499
+ expect(
500
+ screen.getByRole("checkbox", {
501
+ name: /inherit restrictions from parent/i,
502
+ })
503
+ ).toBeChecked();
504
+ // ...and, with no remaining changes, hides the cancel button.
505
+ expect(
506
+ screen.queryByRole("button", { name: /cancel changes/i })
507
+ ).toBeNull();
508
+ });
509
+ });