@thepalaceproject/circulation-admin 1.45.0 → 1.46.0-post.2

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 CHANGED
@@ -139,5 +139,5 @@
139
139
  "*.{js,jsx,ts,tsx,css,md}": "prettier --write",
140
140
  "*.{js,css,md}": "prettier --write"
141
141
  },
142
- "version": "1.45.0"
142
+ "version": "1.46.0-post.2"
143
143
  }
@@ -364,4 +364,19 @@ describe("CustomListEditor", () => {
364
364
  container.querySelector(".sharing-info button")
365
365
  ).not.toBeInTheDocument();
366
366
  });
367
+
368
+ it("does not render a share button once the list is already shared, and shows the shared-state copy", () => {
369
+ const { container } = renderEditor({ isShared: true });
370
+
371
+ expect(
372
+ container.querySelector(".sharing-info button")
373
+ ).not.toBeInTheDocument();
374
+
375
+ expect(
376
+ screen.getByText(
377
+ "This list has been shared with other libraries, and will automatically be made available to any libraries registered in this Collection Manager, including libraries registered in the future.",
378
+ { exact: false }
379
+ )
380
+ ).toBeInTheDocument();
381
+ });
367
382
  });
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import { render, screen, waitFor } from "@testing-library/react";
2
+ import { render, screen, waitFor, within } from "@testing-library/react";
3
3
  import userEvent from "@testing-library/user-event";
4
4
  import * as fetchMock from "fetch-mock-jest";
5
5
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
@@ -217,8 +217,11 @@ describe("PatronAuthServiceEditForm – serialization", () => {
217
217
 
218
218
  await user.click(saveButton);
219
219
 
220
- // The library should now be collapsed (indicating editLibrary was called)
220
+ // Saving collapses the library. Reopening it shows the rule was stored in
221
+ // library state, not just read from the editor.
221
222
  expect(screen.queryByRole("button", { name: /Add Rule/i })).toBeNull();
223
+ await user.click(screen.getByRole("button", { name: /Edit/i }));
224
+ expect(screen.getByLabelText(/Rule Name/i)).toHaveValue("Test Rule");
222
225
  });
223
226
 
224
227
  it("includes patron_blocking_rules in payload when adding a new library", async () => {
@@ -253,8 +256,99 @@ describe("PatronAuthServiceEditForm – serialization", () => {
253
256
 
254
257
  await user.click(addButton);
255
258
 
256
- // After adding, the library should appear in the list, editor no longer in "new library" form
259
+ // The new-library editor is gone. Opening the added library shows the rule
260
+ // was stored in library state.
257
261
  expect(screen.queryByLabelText(/Rule Name/i)).toBeNull();
262
+ await user.click(screen.getByRole("button", { name: /Edit/i }));
263
+ expect(screen.getByLabelText(/Rule Name/i)).toHaveValue("New Rule");
264
+ });
265
+ });
266
+
267
+ describe("PatronAuthServiceEditForm – per-library settings", () => {
268
+ const LIB_SETTING_KEY = "lib_setting";
269
+ const protocolWithLibrarySettings = {
270
+ ...SIP2_PROTOCOL_DATA,
271
+ library_settings: [{ key: LIB_SETTING_KEY, label: "Library setting" }],
272
+ };
273
+ const dataWithLibrarySettings: PatronAuthServicesData = {
274
+ ...servicesData,
275
+ protocols: [protocolWithLibrarySettings, OTHER_PROTOCOL_DATA],
276
+ };
277
+ const settingInput = (root: HTMLElement) =>
278
+ within(root).getByRole("textbox", {
279
+ name: /Library setting/i,
280
+ }) as HTMLInputElement;
281
+
282
+ it("keeps a library setting entered when adding a new library", async () => {
283
+ const user = userEvent.setup();
284
+ const item = {
285
+ id: 3,
286
+ protocol: SIP2_PROTOCOL,
287
+ settings: {},
288
+ libraries: [],
289
+ };
290
+ const { container } = renderForm(
291
+ <PatronAuthServiceEditForm
292
+ {...baseProps}
293
+ data={dataWithLibrarySettings}
294
+ item={item}
295
+ />
296
+ );
297
+
298
+ await expandLibrariesPanel(user);
299
+ await user.selectOptions(
300
+ screen.getByRole("combobox", { name: /Add Library/i }),
301
+ LIBRARY_SHORT_NAME
302
+ );
303
+ await user.type(settingInput(container), "added value");
304
+ await user.click(screen.getByRole("button", { name: /Add Library/i }));
305
+
306
+ // Reopen the new library's settings; the value entered on add is there.
307
+ await user.click(screen.getByRole("button", { name: /Edit/i }));
308
+ const editSection = container.querySelector(
309
+ ".edit-library-settings"
310
+ ) as HTMLElement;
311
+ expect(settingInput(editSection).value).toBe("added value");
312
+ });
313
+
314
+ it("keeps a library setting changed when editing an existing library", async () => {
315
+ const user = userEvent.setup();
316
+ const item = {
317
+ ...buildSIP2Item(),
318
+ libraries: [
319
+ { short_name: LIBRARY_SHORT_NAME, [LIB_SETTING_KEY]: "old value" },
320
+ ],
321
+ };
322
+ const { container } = renderForm(
323
+ <PatronAuthServiceEditForm
324
+ {...baseProps}
325
+ data={dataWithLibrarySettings}
326
+ item={item}
327
+ />
328
+ );
329
+
330
+ await expandLibrariesPanel(user);
331
+ const clickEdit = () =>
332
+ user.click(screen.getByRole("button", { name: /Edit/i }));
333
+ await clickEdit();
334
+ let editSection = container.querySelector(
335
+ ".edit-library-settings"
336
+ ) as HTMLElement;
337
+ expect(settingInput(editSection).value).toBe("old value");
338
+
339
+ await user.clear(settingInput(editSection));
340
+ await user.type(settingInput(editSection), "new value");
341
+ await user.click(
342
+ editSection.querySelector("button.edit-library") as HTMLElement
343
+ );
344
+
345
+ // Saving collapses the library; reopening shows the saved value.
346
+ expect(container.querySelector(".edit-library-settings")).toBeNull();
347
+ await clickEdit();
348
+ editSection = container.querySelector(
349
+ ".edit-library-settings"
350
+ ) as HTMLElement;
351
+ expect(settingInput(editSection).value).toBe("new value");
258
352
  });
259
353
  });
260
354
 
@@ -4,6 +4,7 @@ import { render, within } from "@testing-library/react";
4
4
  import userEvent from "@testing-library/user-event";
5
5
 
6
6
  import ServiceEditForm, {
7
+ libraryRefKey,
7
8
  ServiceEditFormProps,
8
9
  ServiceEditFormState,
9
10
  } from "../../../src/components/ServiceEditForm";
@@ -896,6 +897,27 @@ describe("ServiceEditForm", () => {
896
897
  expect(libraries).toHaveLength(0);
897
898
  });
898
899
 
900
+ it("removes an expanded library and closes its settings", async () => {
901
+ const user = userEvent.setup();
902
+ const { container } = renderForm({ item: serviceData });
903
+ const libraryItem = container.querySelector(
904
+ ".with-remove-button"
905
+ ) as HTMLElement;
906
+
907
+ await user.click(
908
+ libraryItem.querySelector("button.edit-btn") as HTMLElement
909
+ );
910
+ expect(
911
+ container.querySelector(".edit-library-settings")
912
+ ).toBeInTheDocument();
913
+
914
+ await user.click(
915
+ libraryItem.querySelector("button.remove-btn") as HTMLElement
916
+ );
917
+ expect(container.querySelectorAll(".with-remove-button")).toHaveLength(0);
918
+ expect(container.querySelector(".edit-library-settings")).toBeNull();
919
+ });
920
+
899
921
  it("edits a library", async () => {
900
922
  const user = userEvent.setup();
901
923
  const { container } = renderForm({ item: serviceData });
@@ -978,6 +1000,15 @@ describe("ServiceEditForm", () => {
978
1000
  ).toBe("option3");
979
1001
  });
980
1002
 
1003
+ it("builds library ref keys for add and edit mode", () => {
1004
+ expect(libraryRefKey("key", "add")).toBe("key");
1005
+ expect(libraryRefKey("key", "edit", "nypl")).toBe("nypl_key");
1006
+ // Compile-time guard: ts-jest fails this file if the overloads stop
1007
+ // rejecting an edit-mode call with no short name.
1008
+ // @ts-expect-error edit mode must pass the library short name
1009
+ libraryRefKey("key", "edit");
1010
+ });
1011
+
981
1012
  it("calls save when the form is submitted", async () => {
982
1013
  // The reusable Form wires submission solely through its submit button's
983
1014
  // onClick, so clicking it is the only way to submit; assert the resulting
@@ -1059,6 +1090,28 @@ describe("ServiceEditForm", () => {
1059
1090
  expect(nameInput().value).toBe("");
1060
1091
  });
1061
1092
 
1093
+ it("clears every associated library after a successful save (PP-5153)", () => {
1094
+ const threeLibraries = {
1095
+ ...serviceData,
1096
+ libraries: [
1097
+ { short_name: "nypl" },
1098
+ { short_name: "bpl" },
1099
+ { short_name: "qpl" },
1100
+ ],
1101
+ };
1102
+ const { container, rerender } = renderForm({ item: threeLibraries });
1103
+ expect(container.querySelectorAll(".with-remove-button")).toHaveLength(3);
1104
+
1105
+ // clearForm fires onRemove once per library inside one React batch.
1106
+ // Each removal must build on the previous one, otherwise only the last
1107
+ // one survives and the other libraries stay in the list.
1108
+ rerenderForm(rerender, {
1109
+ item: threeLibraries,
1110
+ responseBody: "new service",
1111
+ });
1112
+ expect(container.querySelectorAll(".with-remove-button")).toHaveLength(0);
1113
+ });
1114
+
1062
1115
  it("doesn't clear the form if there's an error message", async () => {
1063
1116
  const user = userEvent.setup();
1064
1117
  const { container, rerender } = renderForm();