@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.
- package/CLAUDE.md +26 -12
- package/README.md +11 -5
- package/dist/circulation-admin.js +1 -1
- package/jest.config.js +17 -18
- package/package.json +4 -22
- package/tests/jest/actions.test.ts +962 -0
- package/tests/jest/components/AccountPage.test.tsx +41 -0
- package/tests/jest/components/AdvancedSearchBooleanFilter.test.tsx +231 -0
- package/tests/jest/components/AdvancedSearchBuilder.test.tsx +93 -20
- package/tests/jest/components/AdvancedSearchFilter.test.tsx +142 -0
- package/tests/jest/components/AdvancedSearchFilterInput.test.tsx +212 -0
- package/tests/jest/components/AdvancedSearchFilterViewer.test.tsx +52 -0
- package/tests/jest/components/AdvancedSearchValueFilter.test.tsx +123 -0
- package/tests/jest/components/Announcement.test.tsx +76 -0
- package/tests/jest/components/AnnouncementForm.test.tsx +229 -0
- package/tests/jest/components/AnnouncementsSection.test.tsx +146 -0
- package/tests/jest/components/Autocomplete.test.tsx +67 -0
- package/tests/jest/components/BookCoverEditor.test.tsx +393 -0
- package/tests/jest/components/BookDetails.test.tsx +133 -0
- package/tests/jest/components/BookDetailsContainer.test.tsx +116 -0
- package/tests/jest/components/BookDetailsEditor.test.tsx +275 -0
- package/tests/jest/components/BookDetailsTabContainer.test.tsx +237 -0
- package/tests/jest/components/BookEditForm.test.tsx +352 -0
- package/tests/jest/components/BookEditor.test.tsx +20 -21
- package/tests/jest/components/CatalogPage.test.tsx +158 -0
- package/tests/jest/components/CatalogServices.test.tsx +100 -0
- package/tests/jest/components/ChangePasswordForm.test.tsx +163 -0
- package/tests/jest/components/CirculationEventsDownloadForm.test.tsx +41 -0
- package/tests/jest/components/Classifications.test.tsx +166 -0
- package/tests/jest/components/ClassificationsForm.test.tsx +458 -0
- package/tests/jest/components/ClassificationsTable.test.tsx +57 -0
- package/tests/jest/components/Collections.test.tsx +335 -7
- package/tests/jest/components/ColorPicker.test.tsx +58 -0
- package/tests/jest/components/ComplaintForm.test.tsx +171 -0
- package/tests/jest/components/Complaints.test.tsx +259 -0
- package/tests/jest/components/ConfigPage.test.tsx +58 -0
- package/tests/jest/components/ConfigTabContainer.test.tsx +196 -0
- package/tests/jest/components/ContextProvider.test.tsx +138 -0
- package/tests/jest/components/Contributors.test.tsx +157 -0
- package/tests/jest/components/CustomListEditor.test.tsx +367 -0
- package/tests/jest/components/CustomListEntriesEditor.test.tsx +1240 -0
- package/tests/jest/components/CustomListPage.test.tsx +57 -0
- package/tests/jest/components/CustomListSearch.test.tsx +275 -0
- package/tests/jest/components/CustomLists.test.tsx +621 -4
- package/tests/jest/components/CustomListsForBook.test.tsx +353 -0
- package/tests/jest/components/CustomListsSidebar.test.tsx +251 -0
- package/tests/jest/components/DashboardPage.test.tsx +76 -0
- package/tests/jest/components/DebugAuthentication.test.tsx +1 -5
- package/tests/jest/components/DiagnosticsServiceTabs.test.tsx +141 -0
- package/tests/jest/components/DiagnosticsServiceType.test.tsx +75 -0
- package/tests/jest/components/DiagnosticsTabContainer.test.tsx +147 -0
- package/tests/jest/components/DiscoveryServices.test.tsx +154 -7
- package/tests/jest/components/EditableConfigList.test.tsx +606 -9
- package/tests/jest/components/EditableInput.test.tsx +250 -1
- package/tests/jest/components/EditorField.test.tsx +95 -0
- package/tests/jest/components/EntryPointsContainer.test.tsx +97 -0
- package/tests/jest/components/EntryPointsTabs.test.tsx +88 -0
- package/tests/jest/components/ErrorMessage.test.tsx +97 -0
- package/tests/jest/components/Footer.test.tsx +141 -0
- package/tests/jest/components/GenreForm.test.tsx +152 -0
- package/tests/jest/components/Header.test.tsx +421 -0
- package/tests/jest/components/IndividualAdminEditForm.test.tsx +506 -1
- package/tests/jest/components/IndividualAdmins.test.tsx +124 -8
- package/tests/jest/components/InputList.test.tsx +493 -0
- package/tests/jest/components/InventoryReportRequestModal.test.tsx +16 -42
- package/tests/jest/components/Lane.test.tsx +132 -10
- package/tests/jest/components/LaneCustomListsEditor.test.tsx +450 -0
- package/tests/jest/components/LaneEditor.test.tsx +377 -13
- package/tests/jest/components/LanePage.test.tsx +68 -0
- package/tests/jest/components/Lanes.test.tsx +446 -0
- package/tests/jest/components/LanesSidebar.test.tsx +621 -0
- package/tests/jest/components/LanguageField.test.tsx +40 -0
- package/tests/jest/components/Libraries.test.tsx +48 -0
- package/tests/jest/components/LibraryEditForm.test.tsx +383 -0
- package/tests/jest/components/LibraryRegistration.test.tsx +354 -0
- package/tests/jest/components/LibraryRegistrationForm.test.tsx +203 -0
- package/tests/jest/components/LoadButton.test.tsx +49 -0
- package/tests/jest/components/ManagePatrons.test.tsx +73 -0
- package/tests/jest/components/ManagePatronsForm.test.tsx +180 -0
- package/tests/jest/components/ManagePatronsTabContainer.test.tsx +143 -0
- package/tests/jest/components/MetadataServices.test.tsx +95 -0
- package/tests/jest/components/NeighborhoodAnalyticsForm.test.tsx +167 -0
- package/tests/jest/components/PairedMenus.test.tsx +220 -0
- package/tests/jest/components/PatronAuthServices.test.tsx +114 -0
- package/tests/jest/components/PatronBlockingRulesEditor.test.tsx +3 -1
- package/tests/jest/components/PatronInfo.test.tsx +54 -0
- package/tests/jest/components/ProtocolFormField.test.tsx +443 -7
- package/tests/jest/components/ResetAdobeId.test.tsx +302 -0
- package/tests/jest/components/SaveButton.test.tsx +47 -0
- package/tests/jest/components/SelfTestResult.test.tsx +164 -0
- package/tests/jest/components/SelfTests.test.tsx +601 -0
- package/tests/jest/components/SelfTestsCategory.test.tsx +200 -0
- package/tests/jest/components/SelfTestsTabContainer.test.tsx +216 -0
- package/tests/jest/components/ServiceEditForm.test.tsx +1092 -0
- package/tests/jest/components/ServiceWithRegistrationsEditForm.test.tsx +110 -0
- package/tests/jest/components/SetupPage.test.tsx +41 -0
- package/tests/jest/components/SingleStatListItem.test.tsx +62 -0
- package/tests/jest/components/SitewideAnnouncements.test.tsx +134 -0
- package/tests/jest/components/Stats.test.tsx +4 -15
- package/tests/jest/components/TabContainer.test.tsx +68 -0
- package/tests/jest/components/TextWithEditMode.test.tsx +218 -0
- package/tests/jest/components/Timestamp.test.tsx +74 -0
- package/tests/jest/components/ToolTip.test.tsx +46 -0
- package/tests/jest/components/TroubleshootingCategoryPage.test.tsx +69 -0
- package/tests/jest/components/TroubleshootingPage.test.tsx +94 -0
- package/tests/jest/components/TroubleshootingTabContainer.test.tsx +68 -0
- package/tests/jest/components/UpdatingLoader.test.tsx +36 -0
- package/tests/jest/components/WelcomePage.test.tsx +43 -0
- package/tests/jest/components/WithEditButton.test.tsx +55 -0
- package/tests/jest/components/WithRemoveButton.test.tsx +95 -0
- package/tests/jest/editorAdapter.test.ts +245 -0
- package/tests/jest/features/book.test.ts +75 -80
- package/tests/jest/fixtures/classificationsData.ts +45 -0
- package/tests/jest/fixtures/genreData.ts +811 -0
- package/tests/jest/index.test.tsx +50 -0
- package/tests/jest/models/Admin.test.ts +206 -0
- package/tests/jest/reducers/bookCoverPreview.test.ts +81 -0
- package/tests/jest/reducers/classifications.test.ts +161 -0
- package/tests/jest/reducers/collections.test.ts +96 -0
- package/tests/jest/reducers/complaints.test.ts +166 -0
- package/tests/jest/reducers/createFetchEditReducer.test.ts +220 -0
- package/tests/jest/reducers/createRegisterLibraryReducer.test.ts +51 -0
- package/tests/jest/reducers/customListDetails.test.ts +120 -0
- package/tests/jest/reducers/customListEditor.test.ts +2823 -0
- package/tests/jest/reducers/managePatrons.test.ts +71 -0
- package/tests/jest/testUtils/formDataShim.test.ts +135 -0
- package/tests/jest/testUtils/formDataShim.ts +112 -0
- package/tests/jest/utils/sharedFunctions.test.ts +121 -0
- package/tsconfig.json +3 -4
- package/.nycrc.json +0 -8
- package/testReporter.js +0 -29
- package/tests/jest/README.md +0 -6
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { installFormDataShim } from "../testUtils/formDataShim";
|
|
3
|
+
import { render } from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
5
|
+
|
|
6
|
+
import LibraryRegistration from "../../../src/components/LibraryRegistration";
|
|
7
|
+
// The reusable-components `Form` builds `new FormData(formElement)` on
|
|
8
|
+
// submit, which the unit jsdom env's undici FormData rejects; install the
|
|
9
|
+
// shared shim that reads the form's successful controls.
|
|
10
|
+
installFormDataShim();
|
|
11
|
+
const protocol = "protocol 1";
|
|
12
|
+
const serviceData = { id: 1, protocol };
|
|
13
|
+
|
|
14
|
+
const makeProtocols = (overrides: Record<string, unknown> = {}) => [
|
|
15
|
+
{
|
|
16
|
+
name: protocol,
|
|
17
|
+
label: "protocol 1 label",
|
|
18
|
+
supports_registration: true,
|
|
19
|
+
supports_staging: true,
|
|
20
|
+
settings: [],
|
|
21
|
+
library_settings: [],
|
|
22
|
+
...overrides,
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const allLibraries = [
|
|
27
|
+
{ short_name: "nypl", name: "New York Public Library", uuid: "1" },
|
|
28
|
+
{ short_name: "bpl", name: "Brooklyn Public Library", uuid: "2" },
|
|
29
|
+
{ short_name: "qpl", name: "Queens Public Library", uuid: "3" },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const libraryRegistrations = [
|
|
33
|
+
{
|
|
34
|
+
id: 1,
|
|
35
|
+
libraries: [
|
|
36
|
+
{ short_name: "nypl", status: "success", stage: "production" },
|
|
37
|
+
{ short_name: "bpl", status: "failure", stage: "testing" },
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const makeData = (overrides: Record<string, unknown> = {}) => ({
|
|
43
|
+
discovery_services: [serviceData],
|
|
44
|
+
protocols: makeProtocols(),
|
|
45
|
+
allLibraries,
|
|
46
|
+
libraryRegistrations,
|
|
47
|
+
...overrides,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const renderReg = ({
|
|
51
|
+
data,
|
|
52
|
+
registerLibrary,
|
|
53
|
+
...props
|
|
54
|
+
}: Record<string, any> = {}) => {
|
|
55
|
+
const register = registerLibrary ?? jest.fn();
|
|
56
|
+
const result = render(
|
|
57
|
+
<LibraryRegistration
|
|
58
|
+
disabled={false}
|
|
59
|
+
data={(data ?? makeData()) as any}
|
|
60
|
+
save={jest.fn()}
|
|
61
|
+
urlBase="url base"
|
|
62
|
+
listDataKey="discovery_services"
|
|
63
|
+
registerLibrary={register}
|
|
64
|
+
protocol={protocol}
|
|
65
|
+
{...props}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
return { ...result, registerLibrary: register };
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const libraries = (container: HTMLElement) =>
|
|
72
|
+
Array.from(
|
|
73
|
+
container.querySelectorAll<HTMLElement>(
|
|
74
|
+
".service-with-registrations-library"
|
|
75
|
+
)
|
|
76
|
+
);
|
|
77
|
+
const registrationInfos = (container: HTMLElement) =>
|
|
78
|
+
Array.from(
|
|
79
|
+
container.querySelectorAll<HTMLElement>(".library-registration-info")
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
describe("LibraryRegistration", () => {
|
|
83
|
+
describe("rendering", () => {
|
|
84
|
+
it("doesn't render libraries in create form", () => {
|
|
85
|
+
// No `item` prop => nothing rendered.
|
|
86
|
+
const { container } = renderReg();
|
|
87
|
+
expect(libraries(container)).toHaveLength(0);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("doesn't render libraries in edit form if protocol doesn't support registration", () => {
|
|
91
|
+
const { container } = renderReg({
|
|
92
|
+
item: serviceData,
|
|
93
|
+
data: makeData({
|
|
94
|
+
protocols: makeProtocols({ supports_registration: false }),
|
|
95
|
+
}),
|
|
96
|
+
});
|
|
97
|
+
expect(libraries(container)).toHaveLength(0);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("treats a protocol that is absent from the list as unsupported", () => {
|
|
101
|
+
// protocolSupportsType falls through to `false` when the selected protocol
|
|
102
|
+
// matches no entry in data.protocols, so no registration UI renders.
|
|
103
|
+
const { container } = renderReg({
|
|
104
|
+
item: serviceData,
|
|
105
|
+
protocol: "protocol-not-in-the-list",
|
|
106
|
+
});
|
|
107
|
+
expect(libraries(container)).toHaveLength(0);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("renders all libraries in edit form, with registration status", () => {
|
|
111
|
+
const { container } = renderReg({ item: serviceData });
|
|
112
|
+
const libs = libraries(container);
|
|
113
|
+
expect(libs).toHaveLength(3);
|
|
114
|
+
|
|
115
|
+
expect(libs[0]).toHaveTextContent("New York Public Library");
|
|
116
|
+
expect(libs[0]).toHaveTextContent("Registered");
|
|
117
|
+
expect(libs[1]).toHaveTextContent("Brooklyn Public Library");
|
|
118
|
+
expect(libs[1]).toHaveTextContent("Registration failed");
|
|
119
|
+
expect(libs[2]).toHaveTextContent("Queens Public Library");
|
|
120
|
+
expect(libs[2]).toHaveTextContent("Not registered");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("provides links to the libraries' edit forms", () => {
|
|
124
|
+
const { container } = renderReg({ item: serviceData });
|
|
125
|
+
const libs = libraries(container);
|
|
126
|
+
expect(libs).toHaveLength(3);
|
|
127
|
+
|
|
128
|
+
expect(libs[0].querySelector("a")).toHaveAttribute(
|
|
129
|
+
"href",
|
|
130
|
+
"/admin/web/config/libraries/edit/1"
|
|
131
|
+
);
|
|
132
|
+
expect(libs[1].querySelector("a")).toHaveAttribute(
|
|
133
|
+
"href",
|
|
134
|
+
"/admin/web/config/libraries/edit/2"
|
|
135
|
+
);
|
|
136
|
+
expect(libs[2].querySelector("a")).toHaveAttribute(
|
|
137
|
+
"href",
|
|
138
|
+
"/admin/web/config/libraries/edit/3"
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("should not render the staging dropdown if it is not supported", () => {
|
|
143
|
+
const { container } = renderReg({
|
|
144
|
+
item: serviceData,
|
|
145
|
+
data: makeData({
|
|
146
|
+
protocols: makeProtocols({ supports_staging: false }),
|
|
147
|
+
}),
|
|
148
|
+
});
|
|
149
|
+
const infos = registrationInfos(container);
|
|
150
|
+
expect(infos).toHaveLength(3);
|
|
151
|
+
|
|
152
|
+
// No current-stage section and no stage dropdown for any library.
|
|
153
|
+
infos.forEach((info) => {
|
|
154
|
+
expect(info.querySelector(".current-stage")).not.toBeInTheDocument();
|
|
155
|
+
expect(info.querySelector("select")).not.toBeInTheDocument();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Registration status and button still appear. The first span in each
|
|
159
|
+
// info is the status span.
|
|
160
|
+
const [nypl, bpl, qpl] = infos;
|
|
161
|
+
expect(nypl.querySelector("span")).toHaveTextContent("Registered");
|
|
162
|
+
expect(nypl.querySelector("span")).toHaveClass("bg-success");
|
|
163
|
+
expect(bpl.querySelector("span")).toHaveTextContent(
|
|
164
|
+
"Registration failed"
|
|
165
|
+
);
|
|
166
|
+
expect(bpl.querySelector("span")).toHaveClass("bg-danger");
|
|
167
|
+
expect(qpl.querySelector("span")).toHaveTextContent("Not registered");
|
|
168
|
+
expect(qpl.querySelector("span")).toHaveClass("bg-warning");
|
|
169
|
+
|
|
170
|
+
expect(nypl.querySelector("button")).toHaveTextContent(
|
|
171
|
+
"Update registration"
|
|
172
|
+
);
|
|
173
|
+
expect(bpl.querySelector("button")).toHaveTextContent(
|
|
174
|
+
"Retry registration"
|
|
175
|
+
);
|
|
176
|
+
expect(qpl.querySelector("button")).toHaveTextContent("Register");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("should render the current registration stage", () => {
|
|
180
|
+
// An additional library shows the current stage when it is in the
|
|
181
|
+
// "testing" stage and its registration was successful.
|
|
182
|
+
const libraryRegistrationsWBoston = [
|
|
183
|
+
{
|
|
184
|
+
id: 1,
|
|
185
|
+
libraries: [
|
|
186
|
+
{ short_name: "nypl", status: "success", stage: "production" },
|
|
187
|
+
{ short_name: "bpl", status: "failure", stage: "testing" },
|
|
188
|
+
{ short_name: "boston", status: "success", stage: "testing" },
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
const allLibrariesWBoston = [
|
|
193
|
+
...allLibraries,
|
|
194
|
+
{ short_name: "boston", name: "Boston Public Library", uuid: "4" },
|
|
195
|
+
];
|
|
196
|
+
const { container } = renderReg({
|
|
197
|
+
item: serviceData,
|
|
198
|
+
data: makeData({
|
|
199
|
+
allLibraries: allLibrariesWBoston,
|
|
200
|
+
libraryRegistrations: libraryRegistrationsWBoston,
|
|
201
|
+
}),
|
|
202
|
+
});
|
|
203
|
+
const [nypl, bpl, qpl, boston] = registrationInfos(container);
|
|
204
|
+
|
|
205
|
+
expect(nypl.querySelector(".current-stage span")).toHaveTextContent(
|
|
206
|
+
"Current Stage: production"
|
|
207
|
+
);
|
|
208
|
+
expect(bpl.querySelector(".current-stage span")).toHaveTextContent(
|
|
209
|
+
"No current stage"
|
|
210
|
+
);
|
|
211
|
+
expect(qpl.querySelector(".current-stage span")).toHaveTextContent(
|
|
212
|
+
"No current stage"
|
|
213
|
+
);
|
|
214
|
+
expect(boston.querySelector(".current-stage span")).toHaveTextContent(
|
|
215
|
+
"Current Stage: testing"
|
|
216
|
+
);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("should render a registration stage type dropdown with two options", () => {
|
|
220
|
+
const { container } = renderReg({ item: serviceData });
|
|
221
|
+
const bpl = registrationInfos(container)[1];
|
|
222
|
+
const options = bpl.querySelectorAll("option");
|
|
223
|
+
expect(options).toHaveLength(2);
|
|
224
|
+
expect(options[0]).toHaveTextContent("Testing");
|
|
225
|
+
expect(options[1]).toHaveTextContent("Production");
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("should render a registration stage select if the library is not in production", () => {
|
|
229
|
+
const { container } = renderReg({ item: serviceData });
|
|
230
|
+
const [nypl, bpl, qpl] = registrationInfos(container);
|
|
231
|
+
|
|
232
|
+
// Once a library is registered in production, the dropdown is removed.
|
|
233
|
+
expect(nypl.querySelectorAll("select")).toHaveLength(0);
|
|
234
|
+
expect(bpl.querySelectorAll("select")).toHaveLength(1);
|
|
235
|
+
expect(qpl.querySelectorAll("select")).toHaveLength(1);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("should render a form", () => {
|
|
239
|
+
// The status of each library determines its button text.
|
|
240
|
+
const { container } = renderReg({ item: serviceData });
|
|
241
|
+
const libs = libraries(container);
|
|
242
|
+
|
|
243
|
+
expect(libs[0]).toHaveTextContent("New York Public Library");
|
|
244
|
+
expect(libs[0].querySelector("button")).toHaveTextContent(
|
|
245
|
+
"Update registration"
|
|
246
|
+
);
|
|
247
|
+
expect(libs[1]).toHaveTextContent("Brooklyn Public Library");
|
|
248
|
+
expect(libs[1].querySelector("button")).toHaveTextContent(
|
|
249
|
+
"Retry registration"
|
|
250
|
+
);
|
|
251
|
+
expect(libs[2]).toHaveTextContent("Queens Public Library");
|
|
252
|
+
expect(libs[2].querySelector("button")).toHaveTextContent("Register");
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Each form's `checked` prop is derived from the library's status. It only
|
|
256
|
+
// reaches the DOM when the registration carries terms of service, since
|
|
257
|
+
// that is what makes LibraryRegistrationForm render the checkbox at all.
|
|
258
|
+
//
|
|
259
|
+
// Each case renders a single library, because libraryRegistrationItem
|
|
260
|
+
// resolves the registration data as `libraryRegistrations[idx]` — indexing
|
|
261
|
+
// the per-service array by the library's position — so only a library at
|
|
262
|
+
// index 0 finds the terms.
|
|
263
|
+
const renderWithTerms = (library, status: string) =>
|
|
264
|
+
renderReg({
|
|
265
|
+
item: serviceData,
|
|
266
|
+
data: makeData({
|
|
267
|
+
allLibraries: [library],
|
|
268
|
+
libraryRegistrations: [
|
|
269
|
+
{
|
|
270
|
+
id: 1,
|
|
271
|
+
terms_of_service_html: "<p>Terms</p>",
|
|
272
|
+
libraries: [{ short_name: library.short_name, status }],
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
}),
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const termsCheckbox = (container: HTMLElement) =>
|
|
279
|
+
container.querySelector<HTMLInputElement>('input[type="checkbox"]');
|
|
280
|
+
|
|
281
|
+
it("pre-checks the terms-of-service box for an already-registered library", () => {
|
|
282
|
+
const { container } = renderWithTerms(allLibraries[0], "success");
|
|
283
|
+
expect(termsCheckbox(container)).toBeChecked();
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("leaves the terms-of-service box unchecked when registration failed", () => {
|
|
287
|
+
const { container } = renderWithTerms(allLibraries[1], "failure");
|
|
288
|
+
expect(termsCheckbox(container)).not.toBeChecked();
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe("behavior", () => {
|
|
293
|
+
it("registers a library", async () => {
|
|
294
|
+
const user = userEvent.setup();
|
|
295
|
+
const registerLibrary = jest.fn();
|
|
296
|
+
const { container } = renderReg({ item: serviceData, registerLibrary });
|
|
297
|
+
const libs = libraries(container);
|
|
298
|
+
expect(registerLibrary).not.toHaveBeenCalled();
|
|
299
|
+
|
|
300
|
+
await user.click(libs[0].querySelector("button"));
|
|
301
|
+
expect(registerLibrary).toHaveBeenCalledTimes(1);
|
|
302
|
+
// nypl is registered in production, so it always registers in production.
|
|
303
|
+
expect(registerLibrary.mock.calls[0][0]).toMatchObject({
|
|
304
|
+
short_name: "nypl",
|
|
305
|
+
});
|
|
306
|
+
expect(registerLibrary.mock.calls[0][1]).toBe("production");
|
|
307
|
+
|
|
308
|
+
await user.click(libs[1].querySelector("button"));
|
|
309
|
+
expect(registerLibrary).toHaveBeenCalledTimes(2);
|
|
310
|
+
// bpl defaults to the "testing" stage.
|
|
311
|
+
expect(registerLibrary.mock.calls[1][0]).toMatchObject({
|
|
312
|
+
short_name: "bpl",
|
|
313
|
+
});
|
|
314
|
+
expect(registerLibrary.mock.calls[1][1]).toBe("testing");
|
|
315
|
+
|
|
316
|
+
await user.click(libs[2].querySelector("button"));
|
|
317
|
+
expect(registerLibrary).toHaveBeenCalledTimes(3);
|
|
318
|
+
expect(registerLibrary.mock.calls[2][0]).toMatchObject({
|
|
319
|
+
short_name: "qpl",
|
|
320
|
+
});
|
|
321
|
+
expect(registerLibrary.mock.calls[2][1]).toBe("testing");
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it("registers each library with the stage selected from its own dropdown", async () => {
|
|
325
|
+
const user = userEvent.setup();
|
|
326
|
+
const registerLibrary = jest.fn();
|
|
327
|
+
const { container } = renderReg({ item: serviceData, registerLibrary });
|
|
328
|
+
const [, bpl, qpl] = registrationInfos(container);
|
|
329
|
+
|
|
330
|
+
// Choosing "production" for bpl registers bpl in production...
|
|
331
|
+
await user.selectOptions(bpl.querySelector("select"), "production");
|
|
332
|
+
await user.click(bpl.querySelector("button"));
|
|
333
|
+
expect(registerLibrary.mock.calls[0][0]).toMatchObject({
|
|
334
|
+
short_name: "bpl",
|
|
335
|
+
});
|
|
336
|
+
expect(registerLibrary.mock.calls[0][1]).toBe("production");
|
|
337
|
+
|
|
338
|
+
// ...without affecting qpl, which still defaults to "testing".
|
|
339
|
+
await user.click(qpl.querySelector("button"));
|
|
340
|
+
expect(registerLibrary.mock.calls[1][0]).toMatchObject({
|
|
341
|
+
short_name: "qpl",
|
|
342
|
+
});
|
|
343
|
+
expect(registerLibrary.mock.calls[1][1]).toBe("testing");
|
|
344
|
+
|
|
345
|
+
// Each library's dropdown updates its own stage independently.
|
|
346
|
+
await user.selectOptions(qpl.querySelector("select"), "production");
|
|
347
|
+
await user.click(qpl.querySelector("button"));
|
|
348
|
+
expect(registerLibrary.mock.calls[2][0]).toMatchObject({
|
|
349
|
+
short_name: "qpl",
|
|
350
|
+
});
|
|
351
|
+
expect(registerLibrary.mock.calls[2][1]).toBe("production");
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
});
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { installFormDataShim } from "../testUtils/formDataShim";
|
|
3
|
+
import { render, screen } from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
5
|
+
|
|
6
|
+
import LibraryRegistrationForm from "../../../src/components/LibraryRegistrationForm";
|
|
7
|
+
installFormDataShim();
|
|
8
|
+
|
|
9
|
+
describe("LibraryRegistrationForm", () => {
|
|
10
|
+
const library = {
|
|
11
|
+
name: "Test Library",
|
|
12
|
+
status: "success",
|
|
13
|
+
stage: "production",
|
|
14
|
+
};
|
|
15
|
+
const registrationData = {
|
|
16
|
+
id: 1,
|
|
17
|
+
libraries: [library],
|
|
18
|
+
access_problem: null,
|
|
19
|
+
terms_of_service_html:
|
|
20
|
+
"Here are the <a href='terms.html'>terms of service</a>!",
|
|
21
|
+
terms_of_service_link: "terms.html",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type Props = React.ComponentProps<typeof LibraryRegistrationForm>;
|
|
25
|
+
|
|
26
|
+
const makeProps = (overrides: Partial<Props> = {}): Props => ({
|
|
27
|
+
library: library as Props["library"],
|
|
28
|
+
checked: true,
|
|
29
|
+
buttonText: "Update registration",
|
|
30
|
+
register: jest.fn(),
|
|
31
|
+
disabled: false,
|
|
32
|
+
registrationData: registrationData as Props["registrationData"],
|
|
33
|
+
...overrides,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const renderForm = (overrides: Partial<Props> = {}) => {
|
|
37
|
+
const props = makeProps(overrides);
|
|
38
|
+
const result = render(<LibraryRegistrationForm {...props} />);
|
|
39
|
+
const rerender = (nextOverrides: Partial<Props>) =>
|
|
40
|
+
result.rerender(
|
|
41
|
+
<LibraryRegistrationForm {...makeProps(nextOverrides)} />
|
|
42
|
+
);
|
|
43
|
+
return { ...result, rerender, props };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
it("does not display a label and checkbox if there is no terms-of-service data", () => {
|
|
47
|
+
const { container } = renderForm({ registrationData: null });
|
|
48
|
+
|
|
49
|
+
expect(
|
|
50
|
+
container.querySelector(".registration-checkbox")
|
|
51
|
+
).not.toBeInTheDocument();
|
|
52
|
+
expect(
|
|
53
|
+
screen.queryByText("I agree to the terms of service.")
|
|
54
|
+
).not.toBeInTheDocument();
|
|
55
|
+
expect(screen.queryByRole("link")).not.toBeInTheDocument();
|
|
56
|
+
expect(screen.queryByRole("checkbox")).not.toBeInTheDocument();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("displays an error message if there is a problem with the terms-of-service data", () => {
|
|
60
|
+
const dataWithProblem = {
|
|
61
|
+
...registrationData,
|
|
62
|
+
access_problem: { detail: "Something went wrong." },
|
|
63
|
+
};
|
|
64
|
+
const { container } = renderForm({
|
|
65
|
+
registrationData: dataWithProblem as Props["registrationData"],
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const danger = container.querySelector(".registration-checkbox .bg-danger");
|
|
69
|
+
expect(danger).toHaveTextContent("Something went wrong.");
|
|
70
|
+
// If the terms can't be displayed, there's no point showing the checkbox
|
|
71
|
+
// and button.
|
|
72
|
+
expect(screen.queryByRole("checkbox")).not.toBeInTheDocument();
|
|
73
|
+
expect(screen.queryByRole("button")).not.toBeInTheDocument();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("displays content and a checkbox with a label if there is terms-of-service HTML", () => {
|
|
77
|
+
const { container } = renderForm();
|
|
78
|
+
|
|
79
|
+
const termsSection = container.querySelector(".registration-checkbox");
|
|
80
|
+
expect(termsSection).toBeInTheDocument();
|
|
81
|
+
|
|
82
|
+
const content = termsSection.querySelector("p");
|
|
83
|
+
expect(content).toHaveTextContent("Here are the terms of service!");
|
|
84
|
+
|
|
85
|
+
const checkbox = screen.getByRole("checkbox", {
|
|
86
|
+
name: "I agree to the terms of service.",
|
|
87
|
+
});
|
|
88
|
+
expect(checkbox).toBeInTheDocument();
|
|
89
|
+
expect(checkbox).toHaveAttribute("type", "checkbox");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("displays content and a checkbox with a label if there is a terms-of-service link", () => {
|
|
93
|
+
const { container } = renderForm({
|
|
94
|
+
registrationData: {
|
|
95
|
+
...registrationData,
|
|
96
|
+
terms_of_service_html: null,
|
|
97
|
+
} as Props["registrationData"],
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const termsSection = container.querySelector(".registration-checkbox");
|
|
101
|
+
expect(termsSection).toBeInTheDocument();
|
|
102
|
+
|
|
103
|
+
const content = termsSection.querySelector("p");
|
|
104
|
+
expect(content).toHaveTextContent(
|
|
105
|
+
"You must read the terms of service before registering your library."
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const link = screen.getByRole("link", { name: "terms of service" });
|
|
109
|
+
expect(link).toHaveAttribute("href", "terms.html");
|
|
110
|
+
|
|
111
|
+
const checkbox = screen.getByRole("checkbox", {
|
|
112
|
+
name: "I agree to the terms of service.",
|
|
113
|
+
});
|
|
114
|
+
expect(checkbox).toBeInTheDocument();
|
|
115
|
+
expect(checkbox).toHaveAttribute("type", "checkbox");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("displays a registration button", () => {
|
|
119
|
+
const { rerender } = renderForm();
|
|
120
|
+
expect(
|
|
121
|
+
screen.getByRole("button", { name: "Update registration" })
|
|
122
|
+
).toBeInTheDocument();
|
|
123
|
+
|
|
124
|
+
rerender({ buttonText: "Register" });
|
|
125
|
+
expect(
|
|
126
|
+
screen.getByRole("button", { name: "Register" })
|
|
127
|
+
).toBeInTheDocument();
|
|
128
|
+
|
|
129
|
+
rerender({ buttonText: "Retry registration" });
|
|
130
|
+
expect(
|
|
131
|
+
screen.getByRole("button", { name: "Retry registration" })
|
|
132
|
+
).toBeInTheDocument();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("disables the button if there is a checkbox and it is unchecked", async () => {
|
|
136
|
+
const user = userEvent.setup();
|
|
137
|
+
renderForm();
|
|
138
|
+
|
|
139
|
+
const button = screen.getByRole("button", { name: "Update registration" });
|
|
140
|
+
expect(button).toBeEnabled();
|
|
141
|
+
|
|
142
|
+
// Unchecking the terms-of-service checkbox disables the button.
|
|
143
|
+
await user.click(
|
|
144
|
+
screen.getByRole("checkbox", {
|
|
145
|
+
name: "I agree to the terms of service.",
|
|
146
|
+
})
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
expect(
|
|
150
|
+
screen.getByRole("button", { name: "Update registration" })
|
|
151
|
+
).toBeDisabled();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("disables the button if the disabled prop is set to true, regardless of the checkbox", () => {
|
|
155
|
+
const { rerender } = renderForm();
|
|
156
|
+
|
|
157
|
+
rerender({ disabled: true });
|
|
158
|
+
expect(
|
|
159
|
+
screen.getByRole("button", { name: "Update registration" })
|
|
160
|
+
).toBeDisabled();
|
|
161
|
+
|
|
162
|
+
rerender({ disabled: false });
|
|
163
|
+
expect(
|
|
164
|
+
screen.getByRole("button", { name: "Update registration" })
|
|
165
|
+
).toBeEnabled();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("determines whether to pre-check the checkbox", () => {
|
|
169
|
+
const { rerender } = renderForm();
|
|
170
|
+
|
|
171
|
+
expect(
|
|
172
|
+
screen.getByRole("checkbox", {
|
|
173
|
+
name: "I agree to the terms of service.",
|
|
174
|
+
})
|
|
175
|
+
).toBeChecked();
|
|
176
|
+
|
|
177
|
+
rerender({ checked: false });
|
|
178
|
+
expect(
|
|
179
|
+
screen.getByRole("checkbox", {
|
|
180
|
+
name: "I agree to the terms of service.",
|
|
181
|
+
})
|
|
182
|
+
).not.toBeChecked();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("calls register", async () => {
|
|
186
|
+
const user = userEvent.setup();
|
|
187
|
+
const register = jest.fn();
|
|
188
|
+
const { rerender } = renderForm({ register });
|
|
189
|
+
|
|
190
|
+
await user.click(
|
|
191
|
+
screen.getByRole("button", { name: "Update registration" })
|
|
192
|
+
);
|
|
193
|
+
expect(register).toHaveBeenCalledTimes(1);
|
|
194
|
+
expect(register).toHaveBeenCalledWith(library);
|
|
195
|
+
|
|
196
|
+
// A disabled button does not fire register again.
|
|
197
|
+
rerender({ register, disabled: true });
|
|
198
|
+
await user.click(
|
|
199
|
+
screen.getByRole("button", { name: "Update registration" })
|
|
200
|
+
);
|
|
201
|
+
expect(register).toHaveBeenCalledTimes(1);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
|
|
5
|
+
import LoadButton from "../../../src/components/LoadButton";
|
|
6
|
+
|
|
7
|
+
describe("LoadButton", () => {
|
|
8
|
+
it("displays 'Load more' text when not fetching", () => {
|
|
9
|
+
render(<LoadButton isFetching={false} loadMore={jest.fn()} />);
|
|
10
|
+
|
|
11
|
+
expect(
|
|
12
|
+
screen.getByRole("button", { name: "Load more" })
|
|
13
|
+
).toBeInTheDocument();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("renders the MoreDotsIcon only while fetching", () => {
|
|
17
|
+
const { container, rerender } = render(
|
|
18
|
+
<LoadButton isFetching={false} loadMore={jest.fn()} />
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
// The MoreDotsIcon is a Font Awesome-style <svg>, so query for it directly.
|
|
22
|
+
expect(container.querySelector("svg")).not.toBeInTheDocument();
|
|
23
|
+
|
|
24
|
+
rerender(<LoadButton isFetching={true} loadMore={jest.fn()} />);
|
|
25
|
+
|
|
26
|
+
expect(container.querySelector("svg")).toBeInTheDocument();
|
|
27
|
+
expect(screen.queryByText("Load more")).not.toBeInTheDocument();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("calls loadMore when clicked", async () => {
|
|
31
|
+
const user = userEvent.setup();
|
|
32
|
+
const loadMore = jest.fn();
|
|
33
|
+
render(<LoadButton isFetching={false} loadMore={loadMore} />);
|
|
34
|
+
|
|
35
|
+
await user.click(screen.getByRole("button"));
|
|
36
|
+
|
|
37
|
+
expect(loadMore).toHaveBeenCalledTimes(1);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("does nothing when disabled", async () => {
|
|
41
|
+
const user = userEvent.setup();
|
|
42
|
+
const loadMore = jest.fn();
|
|
43
|
+
render(<LoadButton isFetching={true} loadMore={loadMore} />);
|
|
44
|
+
|
|
45
|
+
await user.click(screen.getByRole("button"));
|
|
46
|
+
|
|
47
|
+
expect(loadMore).not.toHaveBeenCalled();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { screen } from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
import { renderWithProviders } from "../testUtils/withProviders";
|
|
5
|
+
|
|
6
|
+
// Header and Footer are page chrome; Header pulls in the react-router v3 context
|
|
7
|
+
// that renderWithProviders does not supply. Mock them to markers.
|
|
8
|
+
jest.mock("../../../src/components/Header", () => ({
|
|
9
|
+
__esModule: true,
|
|
10
|
+
default: () => <div data-testid="header" />,
|
|
11
|
+
}));
|
|
12
|
+
jest.mock("../../../src/components/Footer", () => ({
|
|
13
|
+
__esModule: true,
|
|
14
|
+
default: () => <div data-testid="footer" />,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
import ManagePatrons from "../../../src/components/ManagePatrons";
|
|
18
|
+
|
|
19
|
+
describe("ManagePatrons", () => {
|
|
20
|
+
it("shows Header", () => {
|
|
21
|
+
renderWithProviders(<ManagePatrons params={{ library: "", tab: "" }} />);
|
|
22
|
+
|
|
23
|
+
expect(screen.getByTestId("header")).toBeInTheDocument();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should have a .manage-patrons-page class", () => {
|
|
27
|
+
const { container } = renderWithProviders(
|
|
28
|
+
<ManagePatrons params={{ library: "", tab: "" }} />
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
expect(container.querySelector(".manage-patrons-page")).toBeInTheDocument();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("shows heading", () => {
|
|
35
|
+
renderWithProviders(<ManagePatrons params={{ library: "", tab: "" }} />);
|
|
36
|
+
|
|
37
|
+
expect(
|
|
38
|
+
screen.getByRole("heading", { name: "Patron Manager" })
|
|
39
|
+
).toBeInTheDocument();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("shows ManagePatronsTabContainer", () => {
|
|
43
|
+
// A library manager for "NYPL" sees a tab per patron action, which proves
|
|
44
|
+
// both the library and tab props reached the tab container.
|
|
45
|
+
const { container } = renderWithProviders(
|
|
46
|
+
<ManagePatrons params={{ library: "NYPL", tab: "resetAdobeId" }} />,
|
|
47
|
+
{ appConfigSettings: { roles: [{ role: "manager", library: "NYPL" }] } }
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const tabContainer = container.querySelector(".tab-container");
|
|
51
|
+
expect(tabContainer).toBeInTheDocument();
|
|
52
|
+
|
|
53
|
+
// The tab <li>s carry role="presentation", so query them directly.
|
|
54
|
+
const tabs = (tabContainer as HTMLElement).querySelectorAll(
|
|
55
|
+
"ul.nav-tabs > li"
|
|
56
|
+
);
|
|
57
|
+
expect(tabs).toHaveLength(2);
|
|
58
|
+
expect(
|
|
59
|
+
screen.getByRole("link", { name: "Reset Adobe ID" })
|
|
60
|
+
).toBeInTheDocument();
|
|
61
|
+
expect(
|
|
62
|
+
screen.getByRole("link", { name: "Debug Authentication" })
|
|
63
|
+
).toBeInTheDocument();
|
|
64
|
+
// The `tab` prop selects the active tab.
|
|
65
|
+
expect(tabs[0]).toHaveClass("active");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("shows Footer", () => {
|
|
69
|
+
renderWithProviders(<ManagePatrons params={{ library: "", tab: "" }} />);
|
|
70
|
+
|
|
71
|
+
expect(screen.getByTestId("footer")).toBeInTheDocument();
|
|
72
|
+
});
|
|
73
|
+
});
|