@thepalaceproject/circulation-admin 1.43.0-post.15 → 1.43.0-post.16
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/package.json +1 -1
- package/tests/jest/components/AnnouncementForm.test.tsx +229 -0
- package/tests/jest/components/AnnouncementsSection.test.tsx +146 -0
- package/tests/jest/components/ChangePasswordForm.test.tsx +163 -0
- package/tests/jest/components/DiscoveryServices.test.tsx +154 -7
- package/tests/jest/components/EditableInput.test.tsx +250 -1
- 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/LibraryRegistration.test.tsx +354 -0
- package/tests/jest/components/NeighborhoodAnalyticsForm.test.tsx +167 -0
- package/tests/jest/components/TextWithEditMode.test.tsx +218 -0
- package/tests/jest/components/ToolTip.test.tsx +46 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { installFormDataShim } from "../testUtils/formDataShim";
|
|
2
3
|
import { screen } from "@testing-library/react";
|
|
3
4
|
import userEvent from "@testing-library/user-event";
|
|
4
5
|
import IndividualAdminEditForm from "../../../src/components/IndividualAdminEditForm";
|
|
5
6
|
import renderWithContext from "../testUtils/renderWithContext";
|
|
6
|
-
import { ConfigurationSettings } from "../../../src/interfaces";
|
|
7
|
+
import { AdminRoleData, ConfigurationSettings } from "../../../src/interfaces";
|
|
7
8
|
|
|
8
9
|
describe("IndividualAdminEditForm", () => {
|
|
9
10
|
it("clears the role checkboxes after save", async () => {
|
|
@@ -126,3 +127,507 @@ describe("IndividualAdminEditForm", () => {
|
|
|
126
127
|
expect(gammaUserCheckbox).not.toBeChecked();
|
|
127
128
|
});
|
|
128
129
|
});
|
|
130
|
+
|
|
131
|
+
// Assertions on props (checked/disabled/value) are made against the rendered
|
|
132
|
+
// checkboxes and inputs; role-change behavior is driven with real clicks.
|
|
133
|
+
describe("IndividualAdminEditForm - rendered inputs and role changes", () => {
|
|
134
|
+
// The reusable-components `Form` builds `new FormData(formElement)` on
|
|
135
|
+
// submit, which the unit jsdom env's undici FormData rejects; install the
|
|
136
|
+
// shared shim that reads the form's successful controls.
|
|
137
|
+
installFormDataShim();
|
|
138
|
+
const adminData = { email: "test@nypl.org", password: "password" };
|
|
139
|
+
const allLibraries = [
|
|
140
|
+
{ name: "NYPL", short_name: "nypl" },
|
|
141
|
+
{ name: "BPL", short_name: "bpl" },
|
|
142
|
+
];
|
|
143
|
+
const baseData = { individualAdmins: [adminData], allLibraries };
|
|
144
|
+
|
|
145
|
+
const systemAdmin: AdminRoleData[] = [{ role: "system" }];
|
|
146
|
+
const managerAll: AdminRoleData[] = [{ role: "manager-all" }];
|
|
147
|
+
const librarianAll: AdminRoleData[] = [{ role: "librarian-all" }];
|
|
148
|
+
const nyplManager: AdminRoleData[] = [{ role: "manager", library: "nypl" }];
|
|
149
|
+
const bplManager: AdminRoleData[] = [{ role: "manager", library: "bpl" }];
|
|
150
|
+
const nyplLibrarian: AdminRoleData[] = [
|
|
151
|
+
{ role: "librarian", library: "nypl" },
|
|
152
|
+
];
|
|
153
|
+
const bplLibrarian: AdminRoleData[] = [{ role: "librarian", library: "bpl" }];
|
|
154
|
+
const nyplManagerLibrarianAll: AdminRoleData[] = [
|
|
155
|
+
{ role: "manager", library: "nypl" },
|
|
156
|
+
{ role: "librarian-all" },
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
const configFor = (
|
|
160
|
+
roles: AdminRoleData[],
|
|
161
|
+
settingUp = false
|
|
162
|
+
): Partial<ConfigurationSettings> => ({
|
|
163
|
+
csrfToken: "",
|
|
164
|
+
featureFlags: {},
|
|
165
|
+
roles,
|
|
166
|
+
settingUp,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const systemConfig = configFor(systemAdmin);
|
|
170
|
+
|
|
171
|
+
let save: jest.Mock;
|
|
172
|
+
|
|
173
|
+
const baseProps = (overrides: Record<string, any> = {}) => ({
|
|
174
|
+
data: baseData,
|
|
175
|
+
disabled: false,
|
|
176
|
+
save,
|
|
177
|
+
urlBase: "url base",
|
|
178
|
+
listDataKey: "admins",
|
|
179
|
+
...overrides,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const renderForm = (
|
|
183
|
+
overrides: Record<string, any> = {},
|
|
184
|
+
config: Partial<ConfigurationSettings> = systemConfig
|
|
185
|
+
) =>
|
|
186
|
+
renderWithContext(
|
|
187
|
+
<IndividualAdminEditForm {...(baseProps(overrides) as any)} />,
|
|
188
|
+
config
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const roleNames: Record<string, RegExp> = {
|
|
192
|
+
system: /^system admin$/i,
|
|
193
|
+
"manager-all": /^administrator$/i,
|
|
194
|
+
"librarian-all": /^user$/i,
|
|
195
|
+
"manager-nypl": /administrator of nypl/i,
|
|
196
|
+
"manager-bpl": /administrator of bpl/i,
|
|
197
|
+
"librarian-nypl": /user of nypl/i,
|
|
198
|
+
"librarian-bpl": /user of bpl/i,
|
|
199
|
+
};
|
|
200
|
+
const allRoles = Object.keys(roleNames);
|
|
201
|
+
|
|
202
|
+
const roleCheckbox = (role: string) =>
|
|
203
|
+
screen.getByRole("checkbox", { name: roleNames[role] });
|
|
204
|
+
|
|
205
|
+
const expectRoles = (expected: string[]) => {
|
|
206
|
+
for (const role of allRoles) {
|
|
207
|
+
if (expected.includes(role)) {
|
|
208
|
+
expect(roleCheckbox(role)).toBeChecked();
|
|
209
|
+
} else {
|
|
210
|
+
expect(roleCheckbox(role)).not.toBeChecked();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// A fresh item object each time, so the component's
|
|
216
|
+
// UNSAFE_componentWillReceiveProps resets its admin state from the new roles.
|
|
217
|
+
const setRoles = (rerender: (ui: React.ReactElement) => void, roles) =>
|
|
218
|
+
rerender(
|
|
219
|
+
<IndividualAdminEditForm
|
|
220
|
+
{...(baseProps({ item: { ...adminData, roles } }) as any)}
|
|
221
|
+
/>
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const saveButton = (container: HTMLElement) =>
|
|
225
|
+
container.querySelector<HTMLButtonElement>("button:not(.panel-heading)");
|
|
226
|
+
|
|
227
|
+
beforeEach(() => {
|
|
228
|
+
save = jest.fn().mockResolvedValue(undefined);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// ── Rendering ──────────────────────────────────────────────────────────────
|
|
232
|
+
|
|
233
|
+
it("renders the email field, read-only with a value when editing an admin", () => {
|
|
234
|
+
const { container, rerender } = renderForm();
|
|
235
|
+
let email = container.querySelector<HTMLInputElement>(
|
|
236
|
+
'input[name="email"]'
|
|
237
|
+
);
|
|
238
|
+
expect(email).not.toBeNull();
|
|
239
|
+
expect(email.value).toBe("");
|
|
240
|
+
|
|
241
|
+
rerender(
|
|
242
|
+
<IndividualAdminEditForm {...(baseProps({ item: adminData }) as any)} />
|
|
243
|
+
);
|
|
244
|
+
email = container.querySelector<HTMLInputElement>('input[name="email"]');
|
|
245
|
+
expect(email.value).toBe("test@nypl.org");
|
|
246
|
+
expect(email.readOnly).toBe(true);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("requires the email and password fields", () => {
|
|
250
|
+
const { container } = renderForm();
|
|
251
|
+
expect(container.querySelectorAll(".required-field")).toHaveLength(2);
|
|
252
|
+
expect(container.innerHTML).not.toContain("(Optional)");
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("shows the password field only when the editor may change it", () => {
|
|
256
|
+
const passwordShownFor = (
|
|
257
|
+
targetRoles: AdminRoleData[] | undefined,
|
|
258
|
+
editingRoles: AdminRoleData[]
|
|
259
|
+
) => {
|
|
260
|
+
const overrides = targetRoles ? { item: { roles: targetRoles } } : {};
|
|
261
|
+
const { container, unmount } = renderForm(
|
|
262
|
+
overrides,
|
|
263
|
+
configFor(editingRoles)
|
|
264
|
+
);
|
|
265
|
+
const shown = !!container.querySelector('input[name="password"]');
|
|
266
|
+
unmount();
|
|
267
|
+
return shown;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
// No target admin at all → always editable.
|
|
271
|
+
expect(passwordShownFor(undefined, systemAdmin)).toBe(true);
|
|
272
|
+
// Target has no roles → editable iff the editor manages some library.
|
|
273
|
+
expect(passwordShownFor([], managerAll)).toBe(true);
|
|
274
|
+
expect(passwordShownFor([], librarianAll)).toBe(false);
|
|
275
|
+
// Target is a system admin → editable only by a system admin.
|
|
276
|
+
expect(passwordShownFor(systemAdmin, systemAdmin)).toBe(true);
|
|
277
|
+
expect(passwordShownFor(systemAdmin, managerAll)).toBe(false);
|
|
278
|
+
// Target is a sitewide manager → editable only by a sitewide manager.
|
|
279
|
+
expect(passwordShownFor(managerAll, managerAll)).toBe(true);
|
|
280
|
+
expect(passwordShownFor(managerAll, librarianAll)).toBe(false);
|
|
281
|
+
// Target manages a single library → editable by a manager of that library.
|
|
282
|
+
expect(passwordShownFor(nyplManager, nyplManager)).toBe(true);
|
|
283
|
+
expect(passwordShownFor(nyplManager, bplManager)).toBe(false);
|
|
284
|
+
// Target is a sitewide librarian → its role carries no library, so the
|
|
285
|
+
// per-role check falls to isLibraryManager(undefined): only a sitewide
|
|
286
|
+
// manager qualifies, never a manager of one particular library.
|
|
287
|
+
expect(passwordShownFor(librarianAll, systemAdmin)).toBe(true);
|
|
288
|
+
expect(passwordShownFor(librarianAll, managerAll)).toBe(true);
|
|
289
|
+
expect(passwordShownFor(librarianAll, librarianAll)).toBe(false);
|
|
290
|
+
expect(passwordShownFor(librarianAll, nyplManager)).toBe(false);
|
|
291
|
+
expect(passwordShownFor(librarianAll, nyplLibrarian)).toBe(false);
|
|
292
|
+
expect(passwordShownFor(librarianAll, nyplManagerLibrarianAll)).toBe(false);
|
|
293
|
+
// Target holds roles at two libraries → a manager of either one qualifies.
|
|
294
|
+
expect(
|
|
295
|
+
passwordShownFor([...nyplManager, ...bplLibrarian], bplManager)
|
|
296
|
+
).toBe(true);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("never pre-fills the existing password", () => {
|
|
300
|
+
const { container } = renderForm({
|
|
301
|
+
item: { ...adminData, roles: nyplManager },
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
const password = container.querySelector<HTMLInputElement>(
|
|
305
|
+
'input[name="password"]'
|
|
306
|
+
);
|
|
307
|
+
expect(password).not.toBeNull();
|
|
308
|
+
expect(password.value).toBe("");
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("has a save button only when a save function is supplied", () => {
|
|
312
|
+
const withSave = renderForm();
|
|
313
|
+
expect(saveButton(withSave.container)).not.toBeNull();
|
|
314
|
+
withSave.unmount();
|
|
315
|
+
|
|
316
|
+
const withoutSave = renderForm({ save: undefined });
|
|
317
|
+
expect(saveButton(withoutSave.container)).toBeNull();
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("does not render role checkboxes when setting up the first admin", () => {
|
|
321
|
+
renderForm({}, configFor(systemAdmin, true));
|
|
322
|
+
for (const role of [
|
|
323
|
+
"system",
|
|
324
|
+
"manager-all",
|
|
325
|
+
"librarian-all",
|
|
326
|
+
"manager-nypl",
|
|
327
|
+
"librarian-nypl",
|
|
328
|
+
]) {
|
|
329
|
+
expect(
|
|
330
|
+
screen.queryByRole("checkbox", { name: roleNames[role] })
|
|
331
|
+
).toBeNull();
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// ── Disabled state (isDisabled) ──────────────────────────────────────────────
|
|
336
|
+
|
|
337
|
+
it("disables every role checkbox when the form is disabled", () => {
|
|
338
|
+
renderForm({ disabled: true });
|
|
339
|
+
expect(roleCheckbox("system")).toBeDisabled();
|
|
340
|
+
expect(roleCheckbox("manager-all")).toBeDisabled();
|
|
341
|
+
expect(roleCheckbox("manager-nypl")).toBeDisabled();
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it("lets a system-admin editor edit every role", () => {
|
|
345
|
+
renderForm();
|
|
346
|
+
for (const role of allRoles) {
|
|
347
|
+
expect(roleCheckbox(role)).toBeEnabled();
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it("restricts a sitewide-manager editor from the system-admin role", () => {
|
|
352
|
+
renderForm({}, configFor(managerAll));
|
|
353
|
+
expect(roleCheckbox("system")).toBeDisabled();
|
|
354
|
+
expect(roleCheckbox("manager-all")).toBeEnabled();
|
|
355
|
+
expect(roleCheckbox("librarian-all")).toBeEnabled();
|
|
356
|
+
expect(roleCheckbox("manager-nypl")).toBeEnabled();
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it("restricts a sitewide-librarian editor from the manager roles", () => {
|
|
360
|
+
renderForm({}, configFor(librarianAll));
|
|
361
|
+
expect(roleCheckbox("system")).toBeDisabled();
|
|
362
|
+
expect(roleCheckbox("manager-all")).toBeDisabled();
|
|
363
|
+
expect(roleCheckbox("librarian-all")).toBeDisabled();
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it("restricts a single-library manager editor to that library", () => {
|
|
367
|
+
renderForm({}, configFor(nyplManager));
|
|
368
|
+
expect(roleCheckbox("manager-all")).toBeDisabled();
|
|
369
|
+
expect(roleCheckbox("manager-nypl")).toBeEnabled();
|
|
370
|
+
expect(roleCheckbox("manager-bpl")).toBeDisabled();
|
|
371
|
+
expect(roleCheckbox("librarian-nypl")).toBeEnabled();
|
|
372
|
+
expect(roleCheckbox("librarian-bpl")).toBeDisabled();
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it("disables a per-library box when a sitewide role is selected but the editor is not sitewide", () => {
|
|
376
|
+
const { rerender } = renderForm(
|
|
377
|
+
{ item: { ...adminData, roles: managerAll } },
|
|
378
|
+
configFor(nyplManager)
|
|
379
|
+
);
|
|
380
|
+
// manager-all is selected on the target; a non-sitewide editor can't touch it.
|
|
381
|
+
expect(roleCheckbox("manager-nypl")).toBeDisabled();
|
|
382
|
+
expect(roleCheckbox("manager-bpl")).toBeDisabled();
|
|
383
|
+
|
|
384
|
+
rerender(
|
|
385
|
+
<IndividualAdminEditForm
|
|
386
|
+
{...(baseProps({ item: { ...adminData, roles: librarianAll } }) as any)}
|
|
387
|
+
/>
|
|
388
|
+
);
|
|
389
|
+
expect(roleCheckbox("librarian-nypl")).toBeDisabled();
|
|
390
|
+
expect(roleCheckbox("librarian-bpl")).toBeDisabled();
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it("also restricts an editor when the target admin is a system admin", () => {
|
|
394
|
+
renderForm(
|
|
395
|
+
{ item: { ...adminData, roles: systemAdmin } },
|
|
396
|
+
configFor(managerAll)
|
|
397
|
+
);
|
|
398
|
+
// isSelected("system") is true, so a non-system editor can edit nothing.
|
|
399
|
+
expect(roleCheckbox("manager-all")).toBeDisabled();
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// ── Role-change behavior (handleRoleChange) ──────────────────────────────────
|
|
403
|
+
|
|
404
|
+
it("changes the system admin role", async () => {
|
|
405
|
+
const user = userEvent.setup();
|
|
406
|
+
renderForm({ item: { ...adminData, roles: nyplManagerLibrarianAll } });
|
|
407
|
+
|
|
408
|
+
await user.click(roleCheckbox("system"));
|
|
409
|
+
expectRoles(allRoles);
|
|
410
|
+
|
|
411
|
+
await user.click(roleCheckbox("system"));
|
|
412
|
+
expectRoles([]);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it("changes the manager-all role", async () => {
|
|
416
|
+
const user = userEvent.setup();
|
|
417
|
+
const { rerender } = renderForm();
|
|
418
|
+
|
|
419
|
+
await user.click(roleCheckbox("manager-all"));
|
|
420
|
+
expectRoles([
|
|
421
|
+
"manager-all",
|
|
422
|
+
"librarian-all",
|
|
423
|
+
"manager-nypl",
|
|
424
|
+
"manager-bpl",
|
|
425
|
+
"librarian-nypl",
|
|
426
|
+
"librarian-bpl",
|
|
427
|
+
]);
|
|
428
|
+
|
|
429
|
+
await user.click(roleCheckbox("manager-all"));
|
|
430
|
+
expectRoles(["librarian-all", "librarian-nypl", "librarian-bpl"]);
|
|
431
|
+
|
|
432
|
+
setRoles(rerender, systemAdmin);
|
|
433
|
+
await user.click(roleCheckbox("manager-all"));
|
|
434
|
+
expectRoles(["librarian-all", "librarian-nypl", "librarian-bpl"]);
|
|
435
|
+
|
|
436
|
+
setRoles(rerender, nyplManagerLibrarianAll);
|
|
437
|
+
await user.click(roleCheckbox("manager-all"));
|
|
438
|
+
expectRoles([
|
|
439
|
+
"manager-all",
|
|
440
|
+
"librarian-all",
|
|
441
|
+
"manager-nypl",
|
|
442
|
+
"manager-bpl",
|
|
443
|
+
"librarian-nypl",
|
|
444
|
+
"librarian-bpl",
|
|
445
|
+
]);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it("changes the librarian-all role", async () => {
|
|
449
|
+
const user = userEvent.setup();
|
|
450
|
+
const { rerender } = renderForm();
|
|
451
|
+
|
|
452
|
+
await user.click(roleCheckbox("librarian-all"));
|
|
453
|
+
expectRoles(["librarian-all", "librarian-nypl", "librarian-bpl"]);
|
|
454
|
+
|
|
455
|
+
await user.click(roleCheckbox("librarian-all"));
|
|
456
|
+
expectRoles([]);
|
|
457
|
+
|
|
458
|
+
setRoles(rerender, systemAdmin);
|
|
459
|
+
await user.click(roleCheckbox("librarian-all"));
|
|
460
|
+
expectRoles([]);
|
|
461
|
+
|
|
462
|
+
setRoles(rerender, nyplManagerLibrarianAll);
|
|
463
|
+
await user.click(roleCheckbox("librarian-all"));
|
|
464
|
+
expectRoles(["manager-nypl", "librarian-nypl"]);
|
|
465
|
+
|
|
466
|
+
setRoles(rerender, nyplLibrarian);
|
|
467
|
+
await user.click(roleCheckbox("librarian-all"));
|
|
468
|
+
expectRoles(["librarian-all", "librarian-nypl", "librarian-bpl"]);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
it("changes the manager role for each library", async () => {
|
|
472
|
+
const user = userEvent.setup();
|
|
473
|
+
const { rerender } = renderForm();
|
|
474
|
+
|
|
475
|
+
await user.click(roleCheckbox("manager-nypl"));
|
|
476
|
+
expectRoles(["manager-nypl", "librarian-nypl"]);
|
|
477
|
+
|
|
478
|
+
await user.click(roleCheckbox("manager-nypl"));
|
|
479
|
+
expectRoles(["librarian-nypl"]);
|
|
480
|
+
|
|
481
|
+
setRoles(rerender, systemAdmin);
|
|
482
|
+
await user.click(roleCheckbox("manager-nypl"));
|
|
483
|
+
expectRoles([
|
|
484
|
+
"librarian-all",
|
|
485
|
+
"manager-bpl",
|
|
486
|
+
"librarian-nypl",
|
|
487
|
+
"librarian-bpl",
|
|
488
|
+
]);
|
|
489
|
+
|
|
490
|
+
setRoles(rerender, managerAll);
|
|
491
|
+
await user.click(roleCheckbox("manager-nypl"));
|
|
492
|
+
expectRoles([
|
|
493
|
+
"librarian-all",
|
|
494
|
+
"manager-bpl",
|
|
495
|
+
"librarian-nypl",
|
|
496
|
+
"librarian-bpl",
|
|
497
|
+
]);
|
|
498
|
+
|
|
499
|
+
setRoles(rerender, nyplManagerLibrarianAll);
|
|
500
|
+
await user.click(roleCheckbox("manager-nypl"));
|
|
501
|
+
expectRoles(["librarian-all", "librarian-nypl", "librarian-bpl"]);
|
|
502
|
+
|
|
503
|
+
setRoles(rerender, nyplLibrarian);
|
|
504
|
+
await user.click(roleCheckbox("manager-nypl"));
|
|
505
|
+
expectRoles(["manager-nypl", "librarian-nypl"]);
|
|
506
|
+
|
|
507
|
+
setRoles(rerender, bplLibrarian);
|
|
508
|
+
await user.click(roleCheckbox("manager-nypl"));
|
|
509
|
+
expectRoles(["manager-nypl", "librarian-nypl", "librarian-bpl"]);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it("changes the librarian role for each library", async () => {
|
|
513
|
+
const user = userEvent.setup();
|
|
514
|
+
const { rerender } = renderForm();
|
|
515
|
+
|
|
516
|
+
await user.click(roleCheckbox("librarian-nypl"));
|
|
517
|
+
expectRoles(["librarian-nypl"]);
|
|
518
|
+
|
|
519
|
+
await user.click(roleCheckbox("librarian-nypl"));
|
|
520
|
+
expectRoles([]);
|
|
521
|
+
|
|
522
|
+
setRoles(rerender, systemAdmin);
|
|
523
|
+
await user.click(roleCheckbox("librarian-nypl"));
|
|
524
|
+
expectRoles(["manager-bpl", "librarian-bpl"]);
|
|
525
|
+
|
|
526
|
+
setRoles(rerender, managerAll);
|
|
527
|
+
await user.click(roleCheckbox("librarian-nypl"));
|
|
528
|
+
expectRoles(["manager-bpl", "librarian-bpl"]);
|
|
529
|
+
|
|
530
|
+
setRoles(rerender, nyplManagerLibrarianAll);
|
|
531
|
+
await user.click(roleCheckbox("librarian-nypl"));
|
|
532
|
+
expectRoles(["librarian-bpl"]);
|
|
533
|
+
|
|
534
|
+
setRoles(rerender, bplLibrarian);
|
|
535
|
+
await user.click(roleCheckbox("librarian-nypl"));
|
|
536
|
+
expectRoles(["librarian-nypl", "librarian-bpl"]);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
// ── Submission (submit / handleData) ─────────────────────────────────────────
|
|
540
|
+
|
|
541
|
+
it("calls save when the save button is clicked", async () => {
|
|
542
|
+
const user = userEvent.setup();
|
|
543
|
+
const { container } = renderForm();
|
|
544
|
+
await user.click(saveButton(container));
|
|
545
|
+
expect(save).toHaveBeenCalledTimes(1);
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
it("submits the entered email, password, and selected roles", async () => {
|
|
549
|
+
const user = userEvent.setup();
|
|
550
|
+
const { container } = renderForm();
|
|
551
|
+
|
|
552
|
+
await user.type(
|
|
553
|
+
container.querySelector<HTMLInputElement>('input[name="email"]'),
|
|
554
|
+
"newEmail"
|
|
555
|
+
);
|
|
556
|
+
await user.type(
|
|
557
|
+
container.querySelector<HTMLInputElement>('input[name="password"]'),
|
|
558
|
+
"newPassword"
|
|
559
|
+
);
|
|
560
|
+
await user.click(roleCheckbox("librarian-all"));
|
|
561
|
+
await user.click(roleCheckbox("manager-nypl"));
|
|
562
|
+
|
|
563
|
+
await user.click(saveButton(container));
|
|
564
|
+
|
|
565
|
+
expect(save).toHaveBeenCalledTimes(1);
|
|
566
|
+
const formData = save.mock.calls[0][0];
|
|
567
|
+
expect(formData.get("email")).toBe("newEmail");
|
|
568
|
+
expect(formData.get("password")).toBe("newPassword");
|
|
569
|
+
expect(formData.get("roles")).toBe(
|
|
570
|
+
JSON.stringify([
|
|
571
|
+
{ role: "librarian-all" },
|
|
572
|
+
{ role: "manager", library: "nypl" },
|
|
573
|
+
])
|
|
574
|
+
);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
it("submits only a system-admin role when setting up the first admin", async () => {
|
|
578
|
+
const user = userEvent.setup();
|
|
579
|
+
const { container } = renderForm({}, configFor(systemAdmin, true));
|
|
580
|
+
|
|
581
|
+
await user.type(
|
|
582
|
+
container.querySelector<HTMLInputElement>('input[name="email"]'),
|
|
583
|
+
"newEmail"
|
|
584
|
+
);
|
|
585
|
+
await user.type(
|
|
586
|
+
container.querySelector<HTMLInputElement>('input[name="password"]'),
|
|
587
|
+
"newPassword"
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
await user.click(saveButton(container));
|
|
591
|
+
|
|
592
|
+
expect(save).toHaveBeenCalledTimes(1);
|
|
593
|
+
const formData = save.mock.calls[0][0];
|
|
594
|
+
expect(formData.get("email")).toBe("newEmail");
|
|
595
|
+
expect(formData.get("password")).toBe("newPassword");
|
|
596
|
+
expect(formData.get("roles")).toBe(JSON.stringify([{ role: "system" }]));
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
it("clears the form after a successful save", async () => {
|
|
600
|
+
const user = userEvent.setup();
|
|
601
|
+
const { container, rerender } = renderForm();
|
|
602
|
+
const email = () =>
|
|
603
|
+
container.querySelector<HTMLInputElement>('input[name="email"]');
|
|
604
|
+
|
|
605
|
+
await user.type(email(), "newEmail");
|
|
606
|
+
expect(email().value).toBe("newEmail");
|
|
607
|
+
|
|
608
|
+
// The presence of responseBody (and no error) signals a successful save.
|
|
609
|
+
rerender(
|
|
610
|
+
<IndividualAdminEditForm
|
|
611
|
+
{...(baseProps({ responseBody: "new admin" }) as any)}
|
|
612
|
+
/>
|
|
613
|
+
);
|
|
614
|
+
expect(email().value).toBe("");
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
it("does not clear the form when there is an error", async () => {
|
|
618
|
+
const user = userEvent.setup();
|
|
619
|
+
const { container, rerender } = renderForm();
|
|
620
|
+
const email = () =>
|
|
621
|
+
container.querySelector<HTMLInputElement>('input[name="email"]');
|
|
622
|
+
|
|
623
|
+
await user.type(email(), "newEmail");
|
|
624
|
+
expect(email().value).toBe("newEmail");
|
|
625
|
+
|
|
626
|
+
rerender(
|
|
627
|
+
<IndividualAdminEditForm
|
|
628
|
+
{...(baseProps({ fetchError: "ERROR" }) as any)}
|
|
629
|
+
/>
|
|
630
|
+
);
|
|
631
|
+
expect(email().value).toBe("newEmail");
|
|
632
|
+
});
|
|
633
|
+
});
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { fireEvent } from "@testing-library/react";
|
|
3
|
-
import {
|
|
2
|
+
import { fireEvent, screen } from "@testing-library/react";
|
|
3
|
+
import ConnectedIndividualAdmins, {
|
|
4
|
+
IndividualAdmins,
|
|
5
|
+
} from "../../../src/components/IndividualAdmins";
|
|
4
6
|
import renderWithContext from "../testUtils/renderWithContext";
|
|
7
|
+
import { renderWithProviders } from "../testUtils/withProviders";
|
|
5
8
|
import {
|
|
9
|
+
AdminRoleData,
|
|
6
10
|
ConfigurationSettings,
|
|
7
11
|
IndividualAdminsData,
|
|
8
12
|
} from "../../../src/interfaces";
|
|
9
13
|
import { defaultFeatureFlags } from "../../../src/utils/featureFlags";
|
|
10
14
|
|
|
11
|
-
// NB: This adds tests to the already existing tests in:
|
|
12
|
-
// - `src/components/__tests__/IndividualAdmins-test.tsx`.
|
|
13
|
-
//
|
|
14
|
-
// Those tests should eventually be migrated here and
|
|
15
|
-
// adapted to the Jest/React Testing Library paradigm.
|
|
16
|
-
|
|
17
15
|
describe("IndividualAdmins - role association disclosure", () => {
|
|
18
16
|
// ── Shared fixtures ───────────────────────────────────────────────────────
|
|
19
17
|
|
|
@@ -388,3 +386,121 @@ describe("IndividualAdmins - role association disclosure", () => {
|
|
|
388
386
|
expect(container.querySelectorAll(".associated-items")).toHaveLength(0);
|
|
389
387
|
});
|
|
390
388
|
});
|
|
389
|
+
|
|
390
|
+
// The settingUp headers ("Welcome!" / "Set up your system admin account") are
|
|
391
|
+
// covered by SetupPage.test.tsx, which exercises the real prop wiring. Only the
|
|
392
|
+
// non-setup branch needs covering here.
|
|
393
|
+
describe("IndividualAdmins - headers", () => {
|
|
394
|
+
it("shows the configuration messages when not setting up", () => {
|
|
395
|
+
renderWithContext(
|
|
396
|
+
<IndividualAdmins
|
|
397
|
+
settingUp={false}
|
|
398
|
+
editOrCreate="create"
|
|
399
|
+
csrfToken="token"
|
|
400
|
+
data={{ individualAdmins: [] }}
|
|
401
|
+
fetchData={jest.fn()}
|
|
402
|
+
editItem={jest.fn().mockResolvedValue(undefined)}
|
|
403
|
+
deleteItem={jest.fn().mockResolvedValue(undefined)}
|
|
404
|
+
isFetching={false}
|
|
405
|
+
/>,
|
|
406
|
+
{ csrfToken: "", featureFlags: defaultFeatureFlags, roles: [] }
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
expect(
|
|
410
|
+
screen.getByRole("heading", {
|
|
411
|
+
level: 2,
|
|
412
|
+
name: "Individual admin configuration",
|
|
413
|
+
})
|
|
414
|
+
).toBeInTheDocument();
|
|
415
|
+
expect(
|
|
416
|
+
screen.getByRole("heading", {
|
|
417
|
+
level: 3,
|
|
418
|
+
name: "Create a new individual admin",
|
|
419
|
+
})
|
|
420
|
+
).toBeInTheDocument();
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
describe("IndividualAdmins - access control", () => {
|
|
425
|
+
const renderAs = (roles: AdminRoleData[]) =>
|
|
426
|
+
renderWithContext(
|
|
427
|
+
<IndividualAdmins
|
|
428
|
+
data={{
|
|
429
|
+
individualAdmins: [{ email: "someone@example.com", roles: [] }],
|
|
430
|
+
}}
|
|
431
|
+
fetchData={jest.fn()}
|
|
432
|
+
editItem={jest.fn().mockResolvedValue(undefined)}
|
|
433
|
+
deleteItem={jest.fn().mockResolvedValue(undefined)}
|
|
434
|
+
csrfToken="token"
|
|
435
|
+
isFetching={false}
|
|
436
|
+
/>,
|
|
437
|
+
{ csrfToken: "", featureFlags: defaultFeatureFlags, roles }
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
const createLink = () => screen.queryByText("Create new individual admin");
|
|
441
|
+
const deleteButton = () => screen.queryByRole("button", { name: /Delete/ });
|
|
442
|
+
|
|
443
|
+
it("lets library managers create, edit and view, but not delete", () => {
|
|
444
|
+
renderAs([{ role: "manager", library: "alpha" }]);
|
|
445
|
+
expect(createLink()).toBeInTheDocument();
|
|
446
|
+
expect(screen.getByText("Edit")).toBeInTheDocument();
|
|
447
|
+
expect(deleteButton()).toBeNull();
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it("lets sitewide library managers create and edit", () => {
|
|
451
|
+
renderAs([{ role: "manager-all" }]);
|
|
452
|
+
expect(createLink()).toBeInTheDocument();
|
|
453
|
+
expect(screen.getByText("Edit")).toBeInTheDocument();
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it("lets system admins create, edit and delete", () => {
|
|
457
|
+
renderAs([{ role: "system" }]);
|
|
458
|
+
expect(createLink()).toBeInTheDocument();
|
|
459
|
+
expect(screen.getByText("Edit")).toBeInTheDocument();
|
|
460
|
+
expect(deleteButton()).toBeInTheDocument();
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it("does not let librarians create, edit or delete", () => {
|
|
464
|
+
renderAs([{ role: "librarian", library: "alpha" }]);
|
|
465
|
+
expect(createLink()).toBeNull();
|
|
466
|
+
expect(screen.queryByText("Edit")).toBeNull();
|
|
467
|
+
// Non-editors get a read-only View link in place of Edit.
|
|
468
|
+
expect(screen.getByText("View")).toBeInTheDocument();
|
|
469
|
+
expect(deleteButton()).toBeNull();
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it("does not let sitewide librarians create, edit or delete", () => {
|
|
473
|
+
renderAs([{ role: "librarian-all" }]);
|
|
474
|
+
expect(createLink()).toBeNull();
|
|
475
|
+
expect(screen.getByText("View")).toBeInTheDocument();
|
|
476
|
+
expect(deleteButton()).toBeNull();
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// Exercises the connected default export so mapStateToProps/mapDispatchToProps
|
|
481
|
+
// are covered.
|
|
482
|
+
describe("IndividualAdmins - connect wiring", () => {
|
|
483
|
+
afterEach(() => jest.restoreAllMocks());
|
|
484
|
+
|
|
485
|
+
it("renders the connected default export, fetching on mount", async () => {
|
|
486
|
+
jest.spyOn(globalThis, "fetch").mockResolvedValue(
|
|
487
|
+
new Response(JSON.stringify({ individualAdmins: [] }), {
|
|
488
|
+
headers: { "Content-Type": "application/json" },
|
|
489
|
+
})
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
renderWithProviders(
|
|
493
|
+
<ConnectedIndividualAdmins
|
|
494
|
+
csrfToken="token"
|
|
495
|
+
editOrCreate={undefined}
|
|
496
|
+
identifier={undefined}
|
|
497
|
+
/>,
|
|
498
|
+
{ appConfigSettings: { roles: [{ role: "system" }] } }
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
// A system admin sees the create link once the mount fetch settles.
|
|
502
|
+
expect(
|
|
503
|
+
await screen.findByText("Create new individual admin")
|
|
504
|
+
).toBeInTheDocument();
|
|
505
|
+
});
|
|
506
|
+
});
|