@thepalaceproject/circulation-admin 1.43.0-post.2 → 1.43.0-post.20

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 (132) hide show
  1. package/CLAUDE.md +26 -12
  2. package/README.md +11 -5
  3. package/dist/circulation-admin.js +1 -1
  4. package/jest.config.js +17 -18
  5. package/package.json +4 -22
  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/AdvancedSearchBuilder.test.tsx +93 -20
  10. package/tests/jest/components/AdvancedSearchFilter.test.tsx +142 -0
  11. package/tests/jest/components/AdvancedSearchFilterInput.test.tsx +212 -0
  12. package/tests/jest/components/AdvancedSearchFilterViewer.test.tsx +52 -0
  13. package/tests/jest/components/AdvancedSearchValueFilter.test.tsx +123 -0
  14. package/tests/jest/components/Announcement.test.tsx +76 -0
  15. package/tests/jest/components/AnnouncementForm.test.tsx +229 -0
  16. package/tests/jest/components/AnnouncementsSection.test.tsx +146 -0
  17. package/tests/jest/components/Autocomplete.test.tsx +67 -0
  18. package/tests/jest/components/BookCoverEditor.test.tsx +393 -0
  19. package/tests/jest/components/BookDetails.test.tsx +133 -0
  20. package/tests/jest/components/BookDetailsContainer.test.tsx +116 -0
  21. package/tests/jest/components/BookDetailsEditor.test.tsx +275 -0
  22. package/tests/jest/components/BookDetailsTabContainer.test.tsx +237 -0
  23. package/tests/jest/components/BookEditForm.test.tsx +352 -0
  24. package/tests/jest/components/BookEditor.test.tsx +20 -21
  25. package/tests/jest/components/CatalogPage.test.tsx +158 -0
  26. package/tests/jest/components/CatalogServices.test.tsx +100 -0
  27. package/tests/jest/components/ChangePasswordForm.test.tsx +163 -0
  28. package/tests/jest/components/CirculationEventsDownloadForm.test.tsx +41 -0
  29. package/tests/jest/components/Classifications.test.tsx +166 -0
  30. package/tests/jest/components/ClassificationsForm.test.tsx +458 -0
  31. package/tests/jest/components/ClassificationsTable.test.tsx +57 -0
  32. package/tests/jest/components/Collections.test.tsx +335 -7
  33. package/tests/jest/components/ColorPicker.test.tsx +58 -0
  34. package/tests/jest/components/ComplaintForm.test.tsx +171 -0
  35. package/tests/jest/components/Complaints.test.tsx +259 -0
  36. package/tests/jest/components/ConfigPage.test.tsx +58 -0
  37. package/tests/jest/components/ConfigTabContainer.test.tsx +196 -0
  38. package/tests/jest/components/ContextProvider.test.tsx +138 -0
  39. package/tests/jest/components/Contributors.test.tsx +157 -0
  40. package/tests/jest/components/CustomListEditor.test.tsx +367 -0
  41. package/tests/jest/components/CustomListEntriesEditor.test.tsx +1240 -0
  42. package/tests/jest/components/CustomListPage.test.tsx +57 -0
  43. package/tests/jest/components/CustomListSearch.test.tsx +275 -0
  44. package/tests/jest/components/CustomLists.test.tsx +621 -4
  45. package/tests/jest/components/CustomListsForBook.test.tsx +353 -0
  46. package/tests/jest/components/CustomListsSidebar.test.tsx +251 -0
  47. package/tests/jest/components/DashboardPage.test.tsx +76 -0
  48. package/tests/jest/components/DebugAuthentication.test.tsx +1 -5
  49. package/tests/jest/components/DiagnosticsServiceTabs.test.tsx +141 -0
  50. package/tests/jest/components/DiagnosticsServiceType.test.tsx +75 -0
  51. package/tests/jest/components/DiagnosticsTabContainer.test.tsx +147 -0
  52. package/tests/jest/components/DiscoveryServices.test.tsx +154 -7
  53. package/tests/jest/components/EditableConfigList.test.tsx +606 -9
  54. package/tests/jest/components/EditableInput.test.tsx +250 -1
  55. package/tests/jest/components/EditorField.test.tsx +95 -0
  56. package/tests/jest/components/EntryPointsContainer.test.tsx +97 -0
  57. package/tests/jest/components/EntryPointsTabs.test.tsx +88 -0
  58. package/tests/jest/components/ErrorMessage.test.tsx +97 -0
  59. package/tests/jest/components/Footer.test.tsx +141 -0
  60. package/tests/jest/components/GenreForm.test.tsx +152 -0
  61. package/tests/jest/components/Header.test.tsx +421 -0
  62. package/tests/jest/components/IndividualAdminEditForm.test.tsx +506 -1
  63. package/tests/jest/components/IndividualAdmins.test.tsx +124 -8
  64. package/tests/jest/components/InputList.test.tsx +493 -0
  65. package/tests/jest/components/InventoryReportRequestModal.test.tsx +16 -42
  66. package/tests/jest/components/Lane.test.tsx +132 -10
  67. package/tests/jest/components/LaneCustomListsEditor.test.tsx +450 -0
  68. package/tests/jest/components/LaneEditor.test.tsx +377 -13
  69. package/tests/jest/components/LanePage.test.tsx +68 -0
  70. package/tests/jest/components/Lanes.test.tsx +446 -0
  71. package/tests/jest/components/LanesSidebar.test.tsx +621 -0
  72. package/tests/jest/components/LanguageField.test.tsx +40 -0
  73. package/tests/jest/components/Libraries.test.tsx +48 -0
  74. package/tests/jest/components/LibraryEditForm.test.tsx +383 -0
  75. package/tests/jest/components/LibraryRegistration.test.tsx +354 -0
  76. package/tests/jest/components/LibraryRegistrationForm.test.tsx +203 -0
  77. package/tests/jest/components/LoadButton.test.tsx +49 -0
  78. package/tests/jest/components/ManagePatrons.test.tsx +73 -0
  79. package/tests/jest/components/ManagePatronsForm.test.tsx +180 -0
  80. package/tests/jest/components/ManagePatronsTabContainer.test.tsx +143 -0
  81. package/tests/jest/components/MetadataServices.test.tsx +95 -0
  82. package/tests/jest/components/NeighborhoodAnalyticsForm.test.tsx +167 -0
  83. package/tests/jest/components/PairedMenus.test.tsx +220 -0
  84. package/tests/jest/components/PatronAuthServices.test.tsx +114 -0
  85. package/tests/jest/components/PatronBlockingRulesEditor.test.tsx +3 -1
  86. package/tests/jest/components/PatronInfo.test.tsx +54 -0
  87. package/tests/jest/components/ProtocolFormField.test.tsx +443 -7
  88. package/tests/jest/components/ResetAdobeId.test.tsx +302 -0
  89. package/tests/jest/components/SaveButton.test.tsx +47 -0
  90. package/tests/jest/components/SelfTestResult.test.tsx +164 -0
  91. package/tests/jest/components/SelfTests.test.tsx +601 -0
  92. package/tests/jest/components/SelfTestsCategory.test.tsx +200 -0
  93. package/tests/jest/components/SelfTestsTabContainer.test.tsx +216 -0
  94. package/tests/jest/components/ServiceEditForm.test.tsx +1092 -0
  95. package/tests/jest/components/ServiceWithRegistrationsEditForm.test.tsx +110 -0
  96. package/tests/jest/components/SetupPage.test.tsx +41 -0
  97. package/tests/jest/components/SingleStatListItem.test.tsx +62 -0
  98. package/tests/jest/components/SitewideAnnouncements.test.tsx +134 -0
  99. package/tests/jest/components/Stats.test.tsx +4 -15
  100. package/tests/jest/components/TabContainer.test.tsx +68 -0
  101. package/tests/jest/components/TextWithEditMode.test.tsx +218 -0
  102. package/tests/jest/components/Timestamp.test.tsx +74 -0
  103. package/tests/jest/components/ToolTip.test.tsx +46 -0
  104. package/tests/jest/components/TroubleshootingCategoryPage.test.tsx +69 -0
  105. package/tests/jest/components/TroubleshootingPage.test.tsx +94 -0
  106. package/tests/jest/components/TroubleshootingTabContainer.test.tsx +68 -0
  107. package/tests/jest/components/UpdatingLoader.test.tsx +36 -0
  108. package/tests/jest/components/WelcomePage.test.tsx +43 -0
  109. package/tests/jest/components/WithEditButton.test.tsx +55 -0
  110. package/tests/jest/components/WithRemoveButton.test.tsx +95 -0
  111. package/tests/jest/editorAdapter.test.ts +245 -0
  112. package/tests/jest/features/book.test.ts +75 -80
  113. package/tests/jest/fixtures/classificationsData.ts +45 -0
  114. package/tests/jest/fixtures/genreData.ts +811 -0
  115. package/tests/jest/index.test.tsx +50 -0
  116. package/tests/jest/models/Admin.test.ts +206 -0
  117. package/tests/jest/reducers/bookCoverPreview.test.ts +81 -0
  118. package/tests/jest/reducers/classifications.test.ts +161 -0
  119. package/tests/jest/reducers/collections.test.ts +96 -0
  120. package/tests/jest/reducers/complaints.test.ts +166 -0
  121. package/tests/jest/reducers/createFetchEditReducer.test.ts +220 -0
  122. package/tests/jest/reducers/createRegisterLibraryReducer.test.ts +51 -0
  123. package/tests/jest/reducers/customListDetails.test.ts +120 -0
  124. package/tests/jest/reducers/customListEditor.test.ts +2823 -0
  125. package/tests/jest/reducers/managePatrons.test.ts +71 -0
  126. package/tests/jest/testUtils/formDataShim.test.ts +135 -0
  127. package/tests/jest/testUtils/formDataShim.ts +112 -0
  128. package/tests/jest/utils/sharedFunctions.test.ts +121 -0
  129. package/tsconfig.json +3 -4
  130. package/.nycrc.json +0 -8
  131. package/testReporter.js +0 -29
  132. package/tests/jest/README.md +0 -6
@@ -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
+ });
@@ -0,0 +1,152 @@
1
+ import * as React from "react";
2
+ import { render, screen, fireEvent } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+
5
+ import GenreForm from "../../../src/components/GenreForm";
6
+ import genreData from "../fixtures/genreData";
7
+
8
+ const genreOptions = Object.keys(genreData["Fiction"]).map(
9
+ (name) => genreData["Fiction"][name]
10
+ );
11
+ const nonfictionOptions = Object.keys(genreData["Nonfiction"]).map(
12
+ (name) => genreData["Nonfiction"][name]
13
+ );
14
+ const bookGenres = ["Adventure", "Epic Fantasy"];
15
+
16
+ const renderForm = (props: Record<string, unknown> = {}) => {
17
+ const addGenre = jest.fn();
18
+ const result = render(
19
+ <GenreForm
20
+ genreOptions={genreOptions}
21
+ bookGenres={bookGenres}
22
+ addGenre={addGenre}
23
+ {...props}
24
+ />
25
+ );
26
+ return { ...result, addGenre };
27
+ };
28
+
29
+ const genreSelect = (container: HTMLElement) =>
30
+ container.querySelector("select[name='genre']") as HTMLSelectElement;
31
+ const genreOptionEls = (container: HTMLElement) =>
32
+ Array.from(
33
+ genreSelect(container).querySelectorAll<HTMLOptionElement>("option")
34
+ );
35
+ const subgenreSelect = (container: HTMLElement) =>
36
+ container.querySelector(
37
+ "select[name='subgenre']"
38
+ ) as HTMLSelectElement | null;
39
+
40
+ describe("GenreForm", () => {
41
+ describe("rendering", () => {
42
+ it("shows multiple select with top-level genres", () => {
43
+ const { container } = renderForm();
44
+ const topLevelGenres = genreOptions.filter(
45
+ (genre) => genre.parents.length === 0
46
+ );
47
+ const options = genreOptionEls(container);
48
+
49
+ expect(options).toHaveLength(topLevelGenres.length);
50
+ options.forEach((option, i) => {
51
+ const name = topLevelGenres[i].name;
52
+ const hasGenre = bookGenres.indexOf(name) !== -1;
53
+ const hasSubgenre = topLevelGenres[i].subgenres.length > 0;
54
+ const displayName = name + (hasSubgenre ? " >" : "");
55
+ expect(option.value).toBe(name);
56
+ expect(option.disabled).toBe(hasGenre);
57
+ expect(option.textContent).toBe(displayName);
58
+ });
59
+ });
60
+ });
61
+
62
+ describe("behavior", () => {
63
+ it("shows multiple select with subgenres when genre is selected", () => {
64
+ const { container, rerender } = renderForm();
65
+
66
+ // Nothing is selected yet: no subgenre select and no Add button.
67
+ expect(subgenreSelect(container)).toBeNull();
68
+ expect(screen.queryByRole("button", { name: "Add" })).toBeNull();
69
+
70
+ // Select a genre with no subgenres (the first option). The component wires
71
+ // selection to `onClick` on each <option>, so fire a click there.
72
+ const firstGenre = genreOptionEls(container)[0];
73
+ fireEvent.click(firstGenre);
74
+ expect(firstGenre).toHaveAttribute("aria-selected", "true");
75
+ expect(subgenreSelect(container)).toBeNull();
76
+ expect(screen.getByRole("button", { name: "Add" })).toBeInTheDocument();
77
+
78
+ // Select a genre that has subgenres.
79
+ const fantasy = genreOptionEls(container).find(
80
+ (option) => option.value === "Fantasy"
81
+ );
82
+ fireEvent.click(fantasy);
83
+ expect(fantasy).toHaveAttribute("aria-selected", "true");
84
+
85
+ const subSelect = subgenreSelect(container);
86
+ expect(subSelect).not.toBeNull();
87
+ const subOptions = Array.from(
88
+ subSelect.querySelectorAll<HTMLOptionElement>("option")
89
+ );
90
+ const subgenres = [...genreData["Fiction"]["Fantasy"].subgenres].sort();
91
+
92
+ expect(subOptions).toHaveLength(subgenres.length);
93
+ subOptions.forEach((option, i) => {
94
+ const name = subgenres[i];
95
+ const hasGenre = bookGenres.indexOf(name) !== -1;
96
+ expect(option.value).toBe(name);
97
+ expect(option.disabled).toBe(hasGenre);
98
+ expect(option.textContent).toBe(name);
99
+ });
100
+
101
+ // Selecting a subgenre marks it as selected.
102
+ fireEvent.click(subOptions[0]);
103
+ expect(
104
+ subgenreSelect(container).querySelector('option[value="Epic Fantasy"]')
105
+ ).toHaveAttribute("aria-selected", "true");
106
+
107
+ // Changing the genre options (fiction -> nonfiction) drops the subgenres,
108
+ // since the selected genre no longer exists in the new options.
109
+ rerender(
110
+ <GenreForm
111
+ genreOptions={nonfictionOptions}
112
+ bookGenres={bookGenres}
113
+ addGenre={jest.fn()}
114
+ />
115
+ );
116
+ expect(subgenreSelect(container)).toBeNull();
117
+ });
118
+
119
+ it("shows add button only if genre is selected", () => {
120
+ const { container } = renderForm();
121
+ expect(screen.queryByRole("button", { name: "Add" })).toBeNull();
122
+
123
+ const womensFiction = genreOptionEls(container).find(
124
+ (option) => option.value === "Women's Fiction"
125
+ );
126
+ fireEvent.click(womensFiction);
127
+
128
+ expect(screen.getByRole("button", { name: "Add" })).toBeInTheDocument();
129
+ });
130
+
131
+ it("adds genre", async () => {
132
+ const user = userEvent.setup();
133
+ const { container, addGenre } = renderForm();
134
+
135
+ const scienceFiction = genreOptionEls(container).find(
136
+ (option) => option.value === "Science Fiction"
137
+ );
138
+ fireEvent.click(scienceFiction);
139
+
140
+ const spaceOpera = Array.from(
141
+ subgenreSelect(container).querySelectorAll<HTMLOptionElement>("option")
142
+ ).find((option) => option.value === "Space Opera");
143
+ fireEvent.click(spaceOpera);
144
+
145
+ await user.click(screen.getByRole("button", { name: "Add" }));
146
+
147
+ // The subgenre takes precedence over the top-level genre.
148
+ expect(addGenre).toHaveBeenCalledTimes(1);
149
+ expect(addGenre.mock.calls[0][0]).toBe("Space Opera");
150
+ });
151
+ });
152
+ });