@thepalaceproject/circulation-admin 1.43.0-post.1 → 1.43.0-post.11

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 (94) hide show
  1. package/CLAUDE.md +16 -7
  2. package/dist/circulation-admin.js +1 -1
  3. package/eslint.config.mjs +9 -1
  4. package/jest.config.js +56 -9
  5. package/package.json +6 -16
  6. package/tests/jest/actions.test.ts +962 -0
  7. package/tests/jest/components/AccountPage.test.tsx +41 -0
  8. package/tests/jest/components/AdvancedSearchBooleanFilter.test.tsx +231 -0
  9. package/tests/jest/components/AdvancedSearchFilter.test.tsx +142 -0
  10. package/tests/jest/components/AdvancedSearchFilterInput.test.tsx +212 -0
  11. package/tests/jest/components/AdvancedSearchFilterViewer.test.tsx +52 -0
  12. package/tests/jest/components/AdvancedSearchValueFilter.test.tsx +123 -0
  13. package/tests/jest/components/Announcement.test.tsx +76 -0
  14. package/tests/jest/components/Autocomplete.test.tsx +67 -0
  15. package/tests/jest/components/BookDetails.test.tsx +133 -0
  16. package/tests/jest/components/BookDetailsContainer.test.tsx +116 -0
  17. package/tests/jest/components/BookDetailsEditor.test.tsx +275 -0
  18. package/tests/jest/components/BookEditForm.test.tsx +352 -0
  19. package/tests/jest/components/CatalogServices.test.tsx +63 -0
  20. package/tests/jest/components/CirculationEventsDownloadForm.test.tsx +41 -0
  21. package/tests/jest/components/Classifications.test.tsx +166 -0
  22. package/tests/jest/components/ClassificationsTable.test.tsx +57 -0
  23. package/tests/jest/components/ColorPicker.test.tsx +58 -0
  24. package/tests/jest/components/ComplaintForm.test.tsx +171 -0
  25. package/tests/jest/components/Complaints.test.tsx +259 -0
  26. package/tests/jest/components/ConfigPage.test.tsx +58 -0
  27. package/tests/jest/components/ContextProvider.test.tsx +138 -0
  28. package/tests/jest/components/CustomListEditor.test.tsx +367 -0
  29. package/tests/jest/components/CustomListPage.test.tsx +57 -0
  30. package/tests/jest/components/CustomListSearch.test.tsx +275 -0
  31. package/tests/jest/components/CustomListsForBook.test.tsx +315 -0
  32. package/tests/jest/components/CustomListsSidebar.test.tsx +251 -0
  33. package/tests/jest/components/DashboardPage.test.tsx +76 -0
  34. package/tests/jest/components/DebugAuthentication.test.tsx +1 -5
  35. package/tests/jest/components/DiagnosticsServiceTabs.test.tsx +141 -0
  36. package/tests/jest/components/DiagnosticsServiceType.test.tsx +75 -0
  37. package/tests/jest/components/DiagnosticsTabContainer.test.tsx +147 -0
  38. package/tests/jest/components/EditableConfigList.test.tsx +8 -2
  39. package/tests/jest/components/EntryPointsContainer.test.tsx +97 -0
  40. package/tests/jest/components/EntryPointsTabs.test.tsx +88 -0
  41. package/tests/jest/components/ErrorMessage.test.tsx +97 -0
  42. package/tests/jest/components/Footer.test.tsx +141 -0
  43. package/tests/jest/components/InventoryReportRequestModal.test.tsx +16 -42
  44. package/tests/jest/components/LaneEditor.test.tsx +2 -6
  45. package/tests/jest/components/LanePage.test.tsx +68 -0
  46. package/tests/jest/components/LanguageField.test.tsx +40 -0
  47. package/tests/jest/components/LibraryEditForm.test.tsx +383 -0
  48. package/tests/jest/components/LibraryRegistrationForm.test.tsx +203 -0
  49. package/tests/jest/components/LoadButton.test.tsx +49 -0
  50. package/tests/jest/components/ManagePatrons.test.tsx +73 -0
  51. package/tests/jest/components/ManagePatronsForm.test.tsx +180 -0
  52. package/tests/jest/components/ManagePatronsTabContainer.test.tsx +143 -0
  53. package/tests/jest/components/MetadataServices.test.tsx +59 -0
  54. package/tests/jest/components/PatronAuthServices.test.tsx +114 -0
  55. package/tests/jest/components/PatronBlockingRulesEditor.test.tsx +3 -1
  56. package/tests/jest/components/PatronInfo.test.tsx +54 -0
  57. package/tests/jest/components/ResetAdobeId.test.tsx +302 -0
  58. package/tests/jest/components/SaveButton.test.tsx +47 -0
  59. package/tests/jest/components/SelfTestResult.test.tsx +164 -0
  60. package/tests/jest/components/SelfTestsCategory.test.tsx +200 -0
  61. package/tests/jest/components/ServiceWithRegistrationsEditForm.test.tsx +110 -0
  62. package/tests/jest/components/SetupPage.test.tsx +41 -0
  63. package/tests/jest/components/SingleStatListItem.test.tsx +62 -0
  64. package/tests/jest/components/SitewideAnnouncements.test.tsx +134 -0
  65. package/tests/jest/components/Stats.test.tsx +4 -8
  66. package/tests/jest/components/TabContainer.test.tsx +68 -0
  67. package/tests/jest/components/Timestamp.test.tsx +74 -0
  68. package/tests/jest/components/TroubleshootingCategoryPage.test.tsx +69 -0
  69. package/tests/jest/components/TroubleshootingPage.test.tsx +94 -0
  70. package/tests/jest/components/TroubleshootingTabContainer.test.tsx +68 -0
  71. package/tests/jest/components/UpdatingLoader.test.tsx +36 -0
  72. package/tests/jest/components/WelcomePage.test.tsx +43 -0
  73. package/tests/jest/components/WithEditButton.test.tsx +55 -0
  74. package/tests/jest/components/WithRemoveButton.test.tsx +95 -0
  75. package/tests/jest/editorAdapter.test.ts +245 -0
  76. package/tests/jest/index.test.tsx +50 -0
  77. package/tests/jest/legacy-setup.ts +13 -0
  78. package/tests/jest/legacyJsdomEnvironment.js +49 -0
  79. package/tests/jest/models/Admin.test.ts +206 -0
  80. package/tests/jest/reducers/bookCoverPreview.test.ts +81 -0
  81. package/tests/jest/reducers/classifications.test.ts +161 -0
  82. package/tests/jest/reducers/collections.test.ts +96 -0
  83. package/tests/jest/reducers/complaints.test.ts +166 -0
  84. package/tests/jest/reducers/createFetchEditReducer.test.ts +220 -0
  85. package/tests/jest/reducers/createRegisterLibraryReducer.test.ts +51 -0
  86. package/tests/jest/reducers/customListDetails.test.ts +120 -0
  87. package/tests/jest/reducers/customListEditor.test.ts +2823 -0
  88. package/tests/jest/reducers/managePatrons.test.ts +71 -0
  89. package/tests/jest/testUtils/formDataShim.test.ts +135 -0
  90. package/tests/jest/testUtils/formDataShim.ts +112 -0
  91. package/tests/jest/utils/sharedFunctions.test.ts +121 -0
  92. package/tsconfig.json +3 -4
  93. package/testReporter.js +0 -29
  94. package/tests/jest/components/AdvancedSearchBuilder.test.tsx +0 -38
@@ -0,0 +1,141 @@
1
+ import * as React from "react";
2
+ import { renderWithProviders } from "../testUtils/withProviders";
3
+ import Footer, {
4
+ makeFooterSupportContactText,
5
+ } from "../../../src/components/Footer";
6
+ import {
7
+ DEFAULT_SUPPORT_CONTACT_TEXT,
8
+ SupportContactLink,
9
+ } from "../../../src/context/appContext";
10
+
11
+ const expectedTosText = "Terms of Service Text";
12
+ const expectedTosHref = "terms_of_service";
13
+ const testSupportContactEmail = "helpdesk@example.com";
14
+ const expectedSupportContact: SupportContactLink = {
15
+ href: `mailto:${testSupportContactEmail}?subject=Support+request`,
16
+ text: `Email ${testSupportContactEmail}.`,
17
+ };
18
+ const expectedSupportContactText = makeFooterSupportContactText(
19
+ expectedSupportContact.text
20
+ );
21
+
22
+ // Builds the app config the ContextProvider transforms into terms-of-service and
23
+ // support-contact context, mirroring the legacy test's `getFooterProviders`.
24
+ const footerConfig = ({ hasSupportContactUrl = false } = {}) => ({
25
+ tos_link_text: expectedTosText,
26
+ tos_link_href: expectedTosHref,
27
+ // Include support contact URL only if we ask for it.
28
+ support_contact_url: hasSupportContactUrl
29
+ ? "https://example.com/link-from-deprecated-setting"
30
+ : undefined,
31
+ supportContactUrl: hasSupportContactUrl
32
+ ? expectedSupportContact.href
33
+ : undefined,
34
+ });
35
+
36
+ describe("Footer", () => {
37
+ it("accepts an optional className prop", () => {
38
+ const { container } = renderWithProviders(
39
+ <Footer className="customClass" />,
40
+ { appConfigSettings: footerConfig() }
41
+ );
42
+ expect(container.querySelector("footer")).toHaveClass("customClass");
43
+ });
44
+
45
+ it("displays a terms of service link", () => {
46
+ const { container } = renderWithProviders(<Footer />, {
47
+ appConfigSettings: footerConfig(),
48
+ });
49
+ const links = container.querySelectorAll("a");
50
+ expect(links).toHaveLength(1);
51
+
52
+ expect(links[0]).toHaveTextContent(expectedTosText);
53
+ expect(links[0]).toHaveAttribute("href", expectedTosHref);
54
+ });
55
+
56
+ describe("support contact link", () => {
57
+ it("displays a support contact link, when URL present", () => {
58
+ const { container } = renderWithProviders(<Footer />, {
59
+ appConfigSettings: footerConfig({ hasSupportContactUrl: true }),
60
+ });
61
+ const links = container.querySelectorAll("a");
62
+ expect(links).toHaveLength(2);
63
+
64
+ // The second link should be the support contact link.
65
+ expect(links[1]).toHaveAttribute("href", expectedSupportContact.href);
66
+ expect(links[1]).toHaveTextContent(expectedSupportContactText);
67
+ });
68
+
69
+ it("omits contact link, when no support URL provided", () => {
70
+ const { container } = renderWithProviders(<Footer />, {
71
+ appConfigSettings: footerConfig({ hasSupportContactUrl: false }),
72
+ });
73
+ // There should only be one link....
74
+ const links = container.querySelectorAll("a");
75
+ expect(links).toHaveLength(1);
76
+
77
+ // ... and it should be the TOS link, not a support contact link.
78
+ expect(links[0]).not.toHaveAttribute("href", expectedSupportContact.href);
79
+ expect(links[0]).toHaveAttribute("href", expectedTosHref);
80
+ });
81
+
82
+ it("displays a default label for an https url", () => {
83
+ const { container } = renderWithProviders(<Footer />, {
84
+ appConfigSettings: { supportContactUrl: "https://example.com/support" },
85
+ });
86
+ const links = container.querySelectorAll("a");
87
+ expect(links).toHaveLength(2);
88
+ expect(links[1]).toHaveAttribute("href", "https://example.com/support");
89
+ expect(links[1]).toHaveTextContent(
90
+ makeFooterSupportContactText(DEFAULT_SUPPORT_CONTACT_TEXT)
91
+ );
92
+ });
93
+
94
+ it("displays a custom label for an https url", () => {
95
+ const { container } = renderWithProviders(<Footer />, {
96
+ appConfigSettings: {
97
+ supportContactUrl: "https://example.com/support",
98
+ supportContactText: "Custom Support Text",
99
+ },
100
+ });
101
+ const links = container.querySelectorAll("a");
102
+ expect(links).toHaveLength(2);
103
+ expect(links[1]).toHaveAttribute("href", "https://example.com/support");
104
+ expect(links[1]).toHaveTextContent(
105
+ makeFooterSupportContactText("Custom Support Text")
106
+ );
107
+ });
108
+
109
+ it("uses the deprecated support_contact_url if supportContactUrl is missing", () => {
110
+ const { container } = renderWithProviders(<Footer />, {
111
+ appConfigSettings: {
112
+ support_contact_url: "https://example.com/deprecated-support",
113
+ },
114
+ });
115
+ const links = container.querySelectorAll("a");
116
+ expect(links).toHaveLength(2);
117
+ expect(links[1]).toHaveAttribute(
118
+ "href",
119
+ "https://example.com/deprecated-support"
120
+ );
121
+ expect(links[1]).toHaveTextContent(
122
+ makeFooterSupportContactText(DEFAULT_SUPPORT_CONTACT_TEXT)
123
+ );
124
+ });
125
+
126
+ it("displays a custom label for a mailto url", () => {
127
+ const { container } = renderWithProviders(<Footer />, {
128
+ appConfigSettings: {
129
+ supportContactUrl: "mailto:custom@example.com",
130
+ supportContactText: "Email Us Now",
131
+ },
132
+ });
133
+ const links = container.querySelectorAll("a");
134
+ expect(links).toHaveLength(2);
135
+ expect(links[1]).toHaveAttribute("href", "mailto:custom@example.com");
136
+ expect(links[1]).toHaveTextContent(
137
+ makeFooterSupportContactText("Email Us Now")
138
+ );
139
+ });
140
+ });
141
+ });
@@ -304,11 +304,7 @@ describe("InventoryReportRequestModal", () => {
304
304
  const NO_EMAIL_CONTEXT_PROVIDER_PROPS = { email: undefined };
305
305
 
306
306
  it("shows the modal when `show` is true", async () => {
307
- const {
308
- getAllByRole,
309
- queryAllByRole,
310
- rerender,
311
- } = renderWithProviders(
307
+ const { getAllByRole, queryAllByRole, rerender } = renderWithProviders(
312
308
  <InventoryReportRequestModal
313
309
  show={false}
314
310
  onHide={onHide}
@@ -385,19 +381,15 @@ describe("InventoryReportRequestModal", () => {
385
381
  let show = true;
386
382
  onHide.mockImplementation(() => (show = false));
387
383
 
388
- const {
389
- getAllByRole,
390
- getByRole,
391
- queryAllByRole,
392
- rerender,
393
- } = renderWithProviders(
394
- <InventoryReportRequestModal
395
- show={show}
396
- onHide={onHide}
397
- library={LIBRARY}
398
- />,
399
- { queryClient }
400
- );
384
+ const { getAllByRole, getByRole, queryAllByRole, rerender } =
385
+ renderWithProviders(
386
+ <InventoryReportRequestModal
387
+ show={show}
388
+ onHide={onHide}
389
+ library={LIBRARY}
390
+ />,
391
+ { queryClient }
392
+ );
401
393
  getAllByRole("dialog");
402
394
  expect(show).toBe(true);
403
395
  expect(onHide).not.toHaveBeenCalled();
@@ -420,10 +412,7 @@ describe("InventoryReportRequestModal", () => {
420
412
  });
421
413
 
422
414
  it("updates confirmation message, if report info request arrives in time", async () => {
423
- const {
424
- getByRole,
425
- rerender,
426
- } = renderWithProviders(
415
+ const { getByRole, rerender } = renderWithProviders(
427
416
  <InventoryReportRequestModal
428
417
  show={true}
429
418
  onHide={onHide}
@@ -457,10 +446,7 @@ describe("InventoryReportRequestModal", () => {
457
446
  )
458
447
  );
459
448
 
460
- const {
461
- getByRole,
462
- rerender,
463
- } = renderWithProviders(
449
+ const { getByRole, rerender } = renderWithProviders(
464
450
  <InventoryReportRequestModal
465
451
  show={true}
466
452
  onHide={onHide}
@@ -505,10 +491,7 @@ describe("InventoryReportRequestModal", () => {
505
491
  )
506
492
  );
507
493
 
508
- const {
509
- getByRole,
510
- rerender,
511
- } = renderWithProviders(
494
+ const { getByRole, rerender } = renderWithProviders(
512
495
  <InventoryReportRequestModal
513
496
  show={true}
514
497
  onHide={onHide}
@@ -538,10 +521,7 @@ describe("InventoryReportRequestModal", () => {
538
521
  it("requests report generation, if request confirmed", async () => {
539
522
  const user = userEvent.setup();
540
523
 
541
- const {
542
- getByRole,
543
- rerender,
544
- } = renderWithProviders(
524
+ const { getByRole, rerender } = renderWithProviders(
545
525
  <InventoryReportRequestModal
546
526
  show={true}
547
527
  onHide={onHide}
@@ -574,10 +554,7 @@ describe("InventoryReportRequestModal", () => {
574
554
  it("displays success message, when request is successful", async () => {
575
555
  const user = userEvent.setup();
576
556
 
577
- const {
578
- getByRole,
579
- rerender,
580
- } = renderWithProviders(
557
+ const { getByRole, rerender } = renderWithProviders(
581
558
  <InventoryReportRequestModal
582
559
  show={true}
583
560
  onHide={onHide}
@@ -615,10 +592,7 @@ describe("InventoryReportRequestModal", () => {
615
592
  );
616
593
  const user = userEvent.setup();
617
594
 
618
- const {
619
- getByRole,
620
- rerender,
621
- } = renderWithProviders(
595
+ const { getByRole, rerender } = renderWithProviders(
622
596
  <InventoryReportRequestModal
623
597
  show={true}
624
598
  onHide={onHide}
@@ -19,13 +19,9 @@ const customListsData = [
19
19
  { id: 1, name: "list 1", entries: [], is_owner: true, is_shared: false },
20
20
  ];
21
21
 
22
- const editLane = stub().returns(
23
- new Promise<void>((resolve) => resolve())
24
- );
22
+ const editLane = stub().returns(new Promise<void>((resolve) => resolve()));
25
23
 
26
- const deleteLane = stub().returns(
27
- new Promise<void>((resolve) => resolve())
28
- );
24
+ const deleteLane = stub().returns(new Promise<void>((resolve) => resolve()));
29
25
 
30
26
  const toggleLaneVisibility = stub();
31
27
 
@@ -0,0 +1,68 @@
1
+ import * as React from "react";
2
+ import { screen, waitForElementToBeRemoved } from "@testing-library/react";
3
+ import * as fetchMock from "fetch-mock-jest";
4
+
5
+ import { renderWithProviders } from "../testUtils/withProviders";
6
+
7
+ // Header and Footer are page chrome; Header pulls in the react-router v3 context
8
+ // that renderWithProviders does not supply. Mock them to markers.
9
+ jest.mock("../../../src/components/Header", () => ({
10
+ __esModule: true,
11
+ default: () => <div data-testid="header" />,
12
+ }));
13
+ jest.mock("../../../src/components/Footer", () => ({
14
+ __esModule: true,
15
+ default: () => <div data-testid="footer" />,
16
+ }));
17
+
18
+ // The real Lanes child (and its sidebar/editor) renders React Router `Link`s,
19
+ // which need router context renderWithProviders does not supply. Mock `Link` to
20
+ // a marker, matching Lane.test.tsx.
21
+ jest.mock("react-router", () => ({
22
+ ...jest.requireActual("react-router"),
23
+ Link: (props) => <div data-testid="Link">{props.children}</div>,
24
+ }));
25
+
26
+ import LanePage from "../../../src/components/LanePage";
27
+
28
+ describe("LanePage", () => {
29
+ beforeAll(() => {
30
+ Object.assign(fetchMock.config, { fetch, Headers, Request, Response });
31
+ });
32
+
33
+ beforeEach(() => {
34
+ // Lanes fetches lanes and custom lists on mount; stub the network so no real
35
+ // request is made.
36
+ fetchMock.mock("*", { lanes: [], custom_lists: [] });
37
+ });
38
+
39
+ afterEach(() => {
40
+ fetchMock.restore();
41
+ });
42
+
43
+ it("shows lanes with info from params", async () => {
44
+ const params = {
45
+ library: "library",
46
+ editOrCreate: "edit",
47
+ identifier: "5",
48
+ };
49
+
50
+ const { container } = renderWithProviders(<LanePage params={params} />);
51
+
52
+ // Header and Footer chrome are composed around the body.
53
+ expect(screen.getByTestId("header")).toBeInTheDocument();
54
+ expect(screen.getByTestId("footer")).toBeInTheDocument();
55
+
56
+ // The real Lanes child renders its container...
57
+ expect(container.querySelector("main.lanes-container")).toBeInTheDocument();
58
+ // ...and, because editOrCreate is "edit", its lane editor panel.
59
+ expect(container.querySelector(".lane-editor")).toBeInTheDocument();
60
+
61
+ // While its on-mount fetches (lanes and custom lists) are in flight, Lanes
62
+ // shows a loading indicator. Wait for it to clear so those fetches settle
63
+ // and their state updates flush, instead of racing a fixed-length timer.
64
+ await waitForElementToBeRemoved(() =>
65
+ screen.queryByRole("dialog", { name: "Loading" })
66
+ );
67
+ });
68
+ });
@@ -0,0 +1,40 @@
1
+ import * as React from "react";
2
+ import { render, screen } from "@testing-library/react";
3
+
4
+ import LanguageField from "../../../src/components/LanguageField";
5
+
6
+ describe("LanguageField", () => {
7
+ const languages = {
8
+ eng: ["English"],
9
+ es: ["Spanish", "Castilian"],
10
+ spa: ["Spanish", "Castilian"],
11
+ };
12
+
13
+ it("renders an autocomplete field", () => {
14
+ const { container } = render(
15
+ <LanguageField name="language" languages={languages} />
16
+ );
17
+
18
+ // An <input> with a `list` attribute has the implicit ARIA role "combobox".
19
+ const input = screen.getByRole("combobox");
20
+ expect(input).toHaveAttribute("name", "language");
21
+ expect(input).toHaveAttribute("list", "language-autocomplete-list");
22
+ expect(
23
+ container.querySelector("datalist#language-autocomplete-list")
24
+ ).toBeInTheDocument();
25
+ });
26
+
27
+ it("renders a unique, ordered list of language names as options", () => {
28
+ const { container } = render(
29
+ <LanguageField name="language" languages={languages} />
30
+ );
31
+
32
+ const options = Array.from(
33
+ container.querySelectorAll<HTMLOptionElement>(
34
+ "#language-autocomplete-list option"
35
+ )
36
+ ).map((option) => option.value);
37
+
38
+ expect(options).toStrictEqual(["English", "Spanish", "Castilian"]);
39
+ });
40
+ });