@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
@@ -0,0 +1,383 @@
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 LibraryEditForm, {
7
+ LibraryEditFormProps,
8
+ } from "../../../src/components/LibraryEditForm";
9
+ import {
10
+ LibrariesData,
11
+ LibraryData,
12
+ LibrarySettingField,
13
+ } from "../../../src/interfaces";
14
+ installFormDataShim();
15
+
16
+ describe("LibraryEditForm", () => {
17
+ const libraryData = {
18
+ uuid: "uuid",
19
+ name: "name",
20
+ short_name: "short_name",
21
+ // prettier-ignore
22
+ settings: {
23
+ "privacy-policy": "http://privacy",
24
+ "copyright": "http://copyright",
25
+ "featured_lane_size": "20",
26
+ "announcements": "[{\"content\": \"Announcement #1\", \"start\": \"2020-06-15\", \"finish\": \"2020-08-15\", \"id\": \"1\"}]"
27
+ },
28
+ };
29
+
30
+ const settingFields = [
31
+ { key: "name", label: "Name", category: "Basic Information", level: 3 },
32
+ {
33
+ key: "short_name",
34
+ label: "Short Name",
35
+ category: "Basic Information",
36
+ level: 3,
37
+ },
38
+ { key: "privacy-policy", label: "Privacy Policy", category: "Links" },
39
+ { key: "copyright", label: "Copyright", category: "Links" },
40
+ { key: "logo", label: "Logo", category: "Client Interface Customization" },
41
+ { key: "large_collections", label: "Languages", category: "Languages" },
42
+ {
43
+ key: "featured_lane_size",
44
+ label: "Maximum number of books in the 'featured' lanes",
45
+ category: "Lanes & Filters",
46
+ type: "number",
47
+ },
48
+ {
49
+ key: "service_area",
50
+ label: "Service Area",
51
+ category: "Geographic Areas",
52
+ type: "list",
53
+ },
54
+ {
55
+ key: "announcements",
56
+ label: "Announcements",
57
+ category: "Announcements",
58
+ type: "announcements",
59
+ },
60
+ ];
61
+
62
+ type Overrides = Partial<LibraryEditFormProps> & {
63
+ fetchError?: string;
64
+ };
65
+
66
+ const makeData = (
67
+ settings: LibrarySettingField[] = settingFields as LibrarySettingField[]
68
+ ): LibrariesData => ({
69
+ libraries: [libraryData as LibraryData],
70
+ settings,
71
+ });
72
+
73
+ const makeProps = (overrides: Overrides = {}): LibraryEditFormProps => ({
74
+ data: makeData(),
75
+ disabled: false,
76
+ save: jest.fn(),
77
+ urlBase: "url base",
78
+ listDataKey: "libraries",
79
+ adminLevel: 3,
80
+ ...(overrides as LibraryEditFormProps),
81
+ });
82
+
83
+ const renderForm = (overrides: Overrides = {}) => {
84
+ const props = makeProps(overrides);
85
+ const result = render(<LibraryEditForm {...props} />);
86
+ const rerender = (nextOverrides: Overrides) =>
87
+ result.rerender(<LibraryEditForm {...makeProps(nextOverrides)} />);
88
+ return { ...result, rerender, props };
89
+ };
90
+
91
+ const inputByName = (container: HTMLElement, name: string) =>
92
+ container.querySelector<HTMLInputElement>(`input[name="${name}"]`);
93
+
94
+ const panelByName = (container: HTMLElement, name: string) =>
95
+ Array.from(container.querySelectorAll<HTMLElement>(".panel")).find(
96
+ (panel) =>
97
+ panel.querySelector(".panel-heading")?.textContent?.startsWith(name) ??
98
+ false
99
+ );
100
+
101
+ describe("rendering", () => {
102
+ it("renders hidden uuid", () => {
103
+ const { container, rerender } = renderForm();
104
+ expect(inputByName(container, "uuid")).toHaveValue("");
105
+
106
+ rerender({ item: libraryData as LibraryData });
107
+ expect(inputByName(container, "uuid")).toHaveValue("uuid");
108
+ });
109
+
110
+ it("renders name", () => {
111
+ const { container, rerender } = renderForm();
112
+ expect(inputByName(container, "name")).toHaveValue("");
113
+
114
+ rerender({ item: libraryData as LibraryData });
115
+ expect(inputByName(container, "name")).toHaveValue("name");
116
+ });
117
+
118
+ it("renders short name", () => {
119
+ const { container, rerender } = renderForm();
120
+ expect(inputByName(container, "short_name")).toHaveValue("");
121
+
122
+ rerender({ item: libraryData as LibraryData });
123
+ expect(inputByName(container, "short_name")).toHaveValue("short_name");
124
+ });
125
+
126
+ it("renders settings", () => {
127
+ const { container, rerender } = renderForm();
128
+ expect(inputByName(container, "privacy-policy")).toHaveValue("");
129
+ expect(inputByName(container, "copyright")).toHaveValue("");
130
+
131
+ rerender({ item: libraryData as LibraryData });
132
+ expect(inputByName(container, "privacy-policy")).toHaveValue(
133
+ "http://privacy"
134
+ );
135
+ expect(inputByName(container, "copyright")).toHaveValue(
136
+ "http://copyright"
137
+ );
138
+ });
139
+
140
+ it("does not render settings with the skip attribute", () => {
141
+ const skip = { key: "skip", label: "Skip this setting!", skip: true };
142
+ const { container } = renderForm({
143
+ data: makeData([...settingFields, skip]),
144
+ });
145
+
146
+ // A normal setting still renders, but the skipped one does not.
147
+ expect(inputByName(container, "privacy-policy")).toBeInTheDocument();
148
+ expect(inputByName(container, "skip")).not.toBeInTheDocument();
149
+ });
150
+
151
+ it("renders the PairedMenus component", () => {
152
+ const inputListSetting = {
153
+ key: "inputList",
154
+ label: "Input List",
155
+ paired: "dropdown",
156
+ options: [],
157
+ default: ["opt_1"],
158
+ level: 2,
159
+ };
160
+ const dropdownSetting = {
161
+ key: "dropdown",
162
+ label: "Dropdown List",
163
+ options: [
164
+ { key: "opt_1", label: "Option 1" },
165
+ { key: "opt_2", label: "Option 2" },
166
+ ],
167
+ type: "select",
168
+ level: 2,
169
+ };
170
+ const { container, rerender } = renderForm({
171
+ data: makeData([inputListSetting, dropdownSetting]),
172
+ });
173
+
174
+ // These settings render inside a collapsed (aria-hidden) panel, so query
175
+ // the DOM directly rather than via accessible-role queries.
176
+ const pairedMenus = container.querySelector<HTMLElement>(".paired-menus");
177
+ expect(pairedMenus).toBeInTheDocument();
178
+
179
+ // The dropdown inside the paired menus is filtered to the option that is
180
+ // present in the input list (opt_1).
181
+ const dropdown = pairedMenus.querySelector<HTMLSelectElement>(
182
+ 'select[name="dropdown"]'
183
+ );
184
+ const options = dropdown.querySelectorAll("option");
185
+ expect(options).toHaveLength(1);
186
+ expect(options[0]).toHaveValue("opt_1");
187
+
188
+ // At admin level 3 the setting is editable, so the input list's remove
189
+ // button is enabled.
190
+ expect(pairedMenus.querySelector(".remove-btn")).toBeEnabled();
191
+
192
+ // A librarian (level 1) is not authorized, so the paired menus become
193
+ // read-only and the remove button is disabled.
194
+ rerender({
195
+ data: makeData([inputListSetting, dropdownSetting]),
196
+ adminLevel: 1,
197
+ });
198
+ const readOnlyPaired =
199
+ container.querySelector<HTMLElement>(".paired-menus");
200
+ expect(readOnlyPaired.querySelector(".remove-btn")).toBeDisabled();
201
+ });
202
+
203
+ it("renders the InputList component", () => {
204
+ const inputListSetting = {
205
+ key: "inputList",
206
+ label: "Input List",
207
+ options: [],
208
+ default: ["opt_1"],
209
+ level: 2,
210
+ menuOptions: [],
211
+ type: "menu",
212
+ };
213
+ const { container, rerender } = renderForm({
214
+ data: makeData([inputListSetting]),
215
+ });
216
+
217
+ // This setting renders inside a collapsed (aria-hidden) panel, so query
218
+ // the DOM directly rather than via accessible-role queries.
219
+ expect(container.querySelector(".input-list")).toBeInTheDocument();
220
+ // The existing list item is editable and removable for an authorized admin.
221
+ expect(inputByName(container, "inputList_opt_1")).not.toHaveAttribute(
222
+ "readonly"
223
+ );
224
+ expect(container.querySelector(".remove-btn")).toBeEnabled();
225
+
226
+ // A librarian (level 1) is not authorized: the item becomes read-only and
227
+ // the remove button is disabled.
228
+ rerender({ data: makeData([inputListSetting]), adminLevel: 1 });
229
+ expect(inputByName(container, "inputList_opt_1")).toHaveAttribute(
230
+ "readonly"
231
+ );
232
+ expect(container.querySelector(".remove-btn")).toBeDisabled();
233
+ });
234
+
235
+ it("renders the Announcements component", () => {
236
+ const { container } = renderForm({ item: libraryData as LibraryData });
237
+
238
+ // The announcements section renders the library's saved announcement.
239
+ expect(
240
+ container.querySelector(".announcements-section")
241
+ ).toBeInTheDocument();
242
+ expect(screen.getByText("Scheduled Announcements:")).toBeInTheDocument();
243
+ expect(screen.getByText(/Announcement #1/)).toBeInTheDocument();
244
+ });
245
+
246
+ it("subdivides fields", () => {
247
+ const { container } = renderForm();
248
+
249
+ const panels = container.querySelectorAll(".panel");
250
+ expect(panels).toHaveLength(7);
251
+
252
+ // The first panel is the (required) Basic Information panel; the rest are
253
+ // optional.
254
+ const basic = panels[0].querySelector(".panel-heading");
255
+ expect(basic).toHaveTextContent("Basic Information");
256
+ Array.from(panels)
257
+ .slice(1)
258
+ .forEach((panel) => {
259
+ expect(panel.querySelector(".panel-heading")).toHaveTextContent(
260
+ "(Optional)"
261
+ );
262
+ });
263
+
264
+ // The Links panel holds two settings.
265
+ const links = panelByName(container, "Links");
266
+ expect(inputByName(links, "privacy-policy")).toBeInTheDocument();
267
+ expect(inputByName(links, "copyright")).toBeInTheDocument();
268
+ expect(
269
+ panelByName(container, "Languages").querySelectorAll("input")
270
+ ).toHaveLength(1);
271
+ expect(
272
+ panelByName(
273
+ container,
274
+ "Client Interface Customization"
275
+ ).querySelectorAll("input")
276
+ ).toHaveLength(1);
277
+
278
+ // Geographic Areas is a list input, which renders an add-list-item span
279
+ // and button.
280
+ const geographic = panelByName(container, "Geographic Areas");
281
+ expect(geographic.querySelectorAll(".add-list-item")).toHaveLength(2);
282
+ });
283
+
284
+ it("has a save button", () => {
285
+ renderForm();
286
+ expect(
287
+ screen.getByRole("button", { name: "Submit" })
288
+ ).toBeInTheDocument();
289
+ });
290
+
291
+ it("disables some fields if the user isn't a system admin", () => {
292
+ const sysAdminOnly = {
293
+ key: "level-3",
294
+ label: "Level 3",
295
+ category: "Basic Information",
296
+ level: 3,
297
+ };
298
+ const sysAdminAndLibMgr = {
299
+ key: "level-2",
300
+ label: "Level 2",
301
+ category: "Basic Information",
302
+ level: 2,
303
+ };
304
+ const data = makeData([
305
+ ...settingFields,
306
+ sysAdminOnly,
307
+ sysAdminAndLibMgr,
308
+ ]);
309
+ const { container, rerender } = renderForm({ data, adminLevel: 3 });
310
+
311
+ // System admin: everything is editable.
312
+ ["name", "short_name", "level-3", "level-2"].forEach((name) =>
313
+ expect(inputByName(container, name)).not.toHaveAttribute("readonly")
314
+ );
315
+
316
+ // Library manager (level 2): level-3 (and the always-restricted name/short
317
+ // name) are read-only, level-2 is editable.
318
+ rerender({ data, adminLevel: 2 });
319
+ ["name", "short_name", "level-3"].forEach((name) =>
320
+ expect(inputByName(container, name)).toHaveAttribute("readonly")
321
+ );
322
+ expect(inputByName(container, "level-2")).not.toHaveAttribute("readonly");
323
+
324
+ // Librarian (level 1): level-2 is now read-only too.
325
+ rerender({ data, adminLevel: 1 });
326
+ ["name", "short_name", "level-3", "level-2"].forEach((name) =>
327
+ expect(inputByName(container, name)).toHaveAttribute("readonly")
328
+ );
329
+ });
330
+ });
331
+
332
+ describe("behavior", () => {
333
+ it("calls save when the save button is clicked", async () => {
334
+ const user = userEvent.setup();
335
+ const save = jest.fn();
336
+ renderForm({ save });
337
+
338
+ await user.click(screen.getByRole("button", { name: "Submit" }));
339
+
340
+ expect(save).toHaveBeenCalledTimes(1);
341
+ });
342
+
343
+ it("submits data", async () => {
344
+ const user = userEvent.setup();
345
+ const save = jest.fn();
346
+ renderForm({ save, item: libraryData as LibraryData });
347
+
348
+ await user.click(screen.getByRole("button", { name: "Submit" }));
349
+
350
+ expect(save).toHaveBeenCalledTimes(1);
351
+ const formData = save.mock.calls[0][0];
352
+ expect(formData.get("uuid")).toBe("uuid");
353
+ expect(formData.get("name")).toBe("name");
354
+ expect(formData.get("short_name")).toBe("short_name");
355
+ expect(formData.get("privacy-policy")).toBe("http://privacy");
356
+ expect(formData.get("copyright")).toBe("http://copyright");
357
+ });
358
+
359
+ it("clears the form when a response comes back without an error", async () => {
360
+ const user = userEvent.setup();
361
+ const { container, rerender } = renderForm();
362
+
363
+ await user.type(inputByName(container, "name"), "new name");
364
+ expect(inputByName(container, "name")).toHaveValue("new name");
365
+
366
+ // A successful response (responseBody with no error) clears the form.
367
+ rerender({ responseBody: "new library" });
368
+ expect(inputByName(container, "name")).toHaveValue("");
369
+ });
370
+
371
+ it("doesn't clear the form if there's an error message", async () => {
372
+ const user = userEvent.setup();
373
+ const { container, rerender } = renderForm();
374
+
375
+ await user.type(inputByName(container, "name"), "new name");
376
+ expect(inputByName(container, "name")).toHaveValue("new name");
377
+
378
+ // An error response leaves the entered value in place.
379
+ rerender({ fetchError: "ERROR" });
380
+ expect(inputByName(container, "name")).toHaveValue("new name");
381
+ });
382
+ });
383
+ });
@@ -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
+ });