@thepalaceproject/circulation-admin 1.43.0-post.11 → 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.
@@ -1,12 +1,30 @@
1
1
  import * as React from "react";
2
- import { screen, waitFor } from "@testing-library/react";
2
+ import { screen, waitFor, within } from "@testing-library/react";
3
3
  import userEvent from "@testing-library/user-event";
4
4
  import { setupServer } from "msw/node";
5
5
  import { http, HttpResponse } from "msw";
6
- import CustomLists from "../../../src/components/CustomLists";
6
+ import CustomLists, {
7
+ CustomLists as UnconnectedCustomLists,
8
+ } from "../../../src/components/CustomLists";
9
+ import * as CustomListEditorModule from "../../../src/components/CustomListEditor";
10
+ import * as navigate from "../../../src/utils/navigate";
7
11
  import renderWithContext from "../testUtils/renderWithContext";
8
12
  import buildStore from "../../../src/store";
9
- import { ConfigurationSettings } from "../../../src/interfaces";
13
+ import { ConfigurationSettings, LaneData } from "../../../src/interfaces";
14
+ import { defaultFeatureFlags } from "../../../src/utils/featureFlags";
15
+
16
+ // Render react-router's Link as a marker div so the sidebar's edit/create
17
+ // targets can be asserted via `data-to` without a Router in the test. Only
18
+ // CustomListsSidebar uses Link in this tree (CustomListEditor does not), so the
19
+ // existing connected tests are unaffected.
20
+ jest.mock("react-router", () => ({
21
+ ...jest.requireActual("react-router"),
22
+ Link: (props) => (
23
+ <div data-testid="Link" data-to={props.to} className={props.className}>
24
+ {props.children}
25
+ </div>
26
+ ),
27
+ }));
10
28
 
11
29
  describe("CustomLists", () => {
12
30
  // Stub scrollTo, since a component in the render tree will try to call it, and it is not
@@ -181,7 +199,7 @@ describe("CustomLists", () => {
181
199
  appConfigSettings
182
200
  );
183
201
 
184
- await userEvent.selectOptions(
202
+ await user.selectOptions(
185
203
  screen.getByRole("combobox", { name: "filter field key" }),
186
204
  screen.getByRole("option", { name: "Language" })
187
205
  );
@@ -199,4 +217,603 @@ describe("CustomLists", () => {
199
217
  });
200
218
  });
201
219
  });
220
+
221
+ // These render the unconnected component with explicit props, drive the real
222
+ // sidebar, and stub the heavy CustomListEditor child so the component's own
223
+ // render/lifecycle/methods are exercised observably. Nested inside the
224
+ // top-level describe so the MSW server and scrollTo stub apply.
225
+ describe("unconnected component with explicit props", () => {
226
+ const listsUrl = "/admin/web/lists/library";
227
+
228
+ const customListEditorProperties = {
229
+ name: "",
230
+ collections: [],
231
+ autoUpdate: false,
232
+ };
233
+ const customListEditorEntries = {
234
+ baseline: [],
235
+ baselineTotalCount: 0,
236
+ added: {},
237
+ removed: {},
238
+ current: [],
239
+ currentTotalCount: 0,
240
+ };
241
+ const customListEditorSearchParams = {
242
+ entryPoint: "All",
243
+ terms: "",
244
+ sort: null,
245
+ language: "all",
246
+ advanced: {
247
+ include: { query: null, selectedQueryId: null, clearFilters: null },
248
+ exclude: { query: null, selectedQueryId: null, clearFilters: null },
249
+ },
250
+ };
251
+
252
+ const listsData = [
253
+ {
254
+ id: 1,
255
+ name: "a list",
256
+ entry_count: 0,
257
+ collections: [],
258
+ is_owner: true,
259
+ is_shared: false,
260
+ },
261
+ {
262
+ id: 2,
263
+ name: "z list",
264
+ entry_count: 1,
265
+ collections: [{ id: 3, name: "collection 3", protocol: "protocol" }],
266
+ is_owner: true,
267
+ is_shared: false,
268
+ },
269
+ ];
270
+
271
+ const entry = { pwid: "1", title: "title", authors: [] };
272
+
273
+ const searchResults = {
274
+ id: "id",
275
+ url: "url",
276
+ title: "title",
277
+ lanes: [],
278
+ books: [],
279
+ navigationLinks: [],
280
+ nextPageUrl: "http://next.page",
281
+ };
282
+
283
+ const collections = [
284
+ {
285
+ id: 1,
286
+ name: "collection 1",
287
+ protocol: "protocol",
288
+ libraries: [{ short_name: "other library" }],
289
+ },
290
+ {
291
+ id: 2,
292
+ name: "collection 2",
293
+ protocol: "protocol",
294
+ libraries: [{ short_name: "library" }],
295
+ },
296
+ {
297
+ id: 3,
298
+ name: "collection 3",
299
+ protocol: "protocol",
300
+ libraries: [{ short_name: "library" }],
301
+ },
302
+ ];
303
+
304
+ const libraries = [
305
+ {
306
+ short_name: "library",
307
+ settings: { enabled_entry_points: ["Book", "Audio"] },
308
+ },
309
+ {
310
+ short_name: "another library",
311
+ settings: { enabled_entry_points: ["Audio"] },
312
+ },
313
+ ];
314
+
315
+ const languages = {
316
+ eng: ["English"],
317
+ spa: ["Spanish", "Castilian"],
318
+ fre: ["French"],
319
+ };
320
+
321
+ const lane1: LaneData = {
322
+ id: 1,
323
+ display_name: "lane 1",
324
+ visible: false,
325
+ count: 1,
326
+ sublanes: [],
327
+ custom_list_ids: [2],
328
+ inherit_parent_restrictions: false,
329
+ };
330
+ const lane2: LaneData = {
331
+ id: 2,
332
+ display_name: "lane 2",
333
+ visible: false,
334
+ count: 1,
335
+ sublanes: [],
336
+ custom_list_ids: [2],
337
+ inherit_parent_restrictions: false,
338
+ };
339
+ const lane3: LaneData = {
340
+ id: 3,
341
+ display_name: "lane 3",
342
+ visible: false,
343
+ count: 1,
344
+ sublanes: [],
345
+ custom_list_ids: [],
346
+ inherit_parent_restrictions: false,
347
+ };
348
+ const allLanes = [lane1, lane2, lane3];
349
+
350
+ // Test doubles for the dispatch props.
351
+ let fetchCustomLists: jest.Mock;
352
+ let fetchCustomListDetails: jest.Mock;
353
+ let deleteCustomList: jest.Mock;
354
+ let shareCustomList: jest.Mock;
355
+ let openCustomListEditor: jest.Mock;
356
+ let saveCustomListEditor: jest.Mock;
357
+ let executeCustomListEditorSearch: jest.Mock;
358
+ let loadMoreSearchResults: jest.Mock;
359
+ let loadMoreEntries: jest.Mock;
360
+ let fetchCollections: jest.Mock;
361
+ let fetchLibraries: jest.Mock;
362
+ let fetchLanes: jest.Mock;
363
+ let fetchLanguages: jest.Mock;
364
+ let navigateSpy: jest.SpyInstance;
365
+
366
+ const configWithRoles = (
367
+ roles: Partial<ConfigurationSettings>["roles"]
368
+ ): Partial<ConfigurationSettings> => ({
369
+ csrfToken: "token",
370
+ featureFlags: defaultFeatureFlags,
371
+ roles,
372
+ });
373
+
374
+ const managerConfig = configWithRoles([
375
+ { role: "manager", library: "library" },
376
+ ]);
377
+ const librarianConfig = configWithRoles([
378
+ { role: "librarian", library: "library" },
379
+ ]);
380
+
381
+ const baseCustomListsProps = (overrides: Record<string, any> = {}) => ({
382
+ customListEditorProperties,
383
+ customListEditorEntries,
384
+ customListEditorSearchParams,
385
+ csrfToken: "token",
386
+ library: "library",
387
+ lists: listsData,
388
+ searchResults,
389
+ collections,
390
+ isFetching: false,
391
+ isFetchingSearchResults: false,
392
+ isFetchingMoreSearchResults: false,
393
+ isFetchingMoreCustomListEntries: false,
394
+ fetchLanguages,
395
+ fetchCustomLists,
396
+ fetchCustomListDetails,
397
+ saveCustomListEditor,
398
+ deleteCustomList,
399
+ shareCustomList,
400
+ executeCustomListEditorSearch,
401
+ openCustomListEditor,
402
+ loadMoreSearchResults,
403
+ loadMoreEntries,
404
+ fetchCollections,
405
+ fetchLibraries,
406
+ fetchLanes,
407
+ libraries,
408
+ languages,
409
+ ...overrides,
410
+ });
411
+
412
+ const renderCustomLists = (
413
+ overrides: Record<string, any> = {},
414
+ config: Partial<ConfigurationSettings> = managerConfig
415
+ ) =>
416
+ renderWithContext(
417
+ <UnconnectedCustomLists
418
+ {...(baseCustomListsProps(overrides) as any)}
419
+ />,
420
+ config
421
+ );
422
+
423
+ const sidebarListNames = (container: HTMLElement) =>
424
+ Array.from(
425
+ container.querySelectorAll(
426
+ ".custom-lists-sidebar ul > li .custom-list-info > div:first-child"
427
+ )
428
+ // The name is the trailing text node; a ShareIcon (with title text) may
429
+ // precede it for lists that are not owned by this library.
430
+ ).map((el) => el.lastChild?.textContent);
431
+
432
+ const lastNavigation = () =>
433
+ navigateSpy.mock.calls[navigateSpy.mock.calls.length - 1]?.[0];
434
+
435
+ beforeEach(() => {
436
+ fetchCustomLists = jest.fn();
437
+ fetchCustomListDetails = jest.fn();
438
+ deleteCustomList = jest.fn().mockResolvedValue(undefined);
439
+ shareCustomList = jest.fn().mockResolvedValue(undefined);
440
+ openCustomListEditor = jest.fn();
441
+ saveCustomListEditor = jest.fn().mockResolvedValue(undefined);
442
+ executeCustomListEditorSearch = jest.fn();
443
+ loadMoreSearchResults = jest.fn();
444
+ loadMoreEntries = jest.fn();
445
+ fetchCollections = jest.fn();
446
+ fetchLibraries = jest.fn();
447
+ fetchLanes = jest.fn().mockResolvedValue(undefined);
448
+ fetchLanguages = jest.fn();
449
+
450
+ navigateSpy = jest
451
+ .spyOn(navigate, "navigateTo")
452
+ .mockImplementation(() => undefined);
453
+ jest.spyOn(navigate, "currentHref").mockReturnValue(listsUrl);
454
+
455
+ // Stub the heavy editor child so we can assert the props CustomLists
456
+ // hands it and drive its `save` callback without its full render tree.
457
+ jest
458
+ .spyOn(CustomListEditorModule, "default")
459
+ .mockImplementation((props: any) => (
460
+ <div data-testid="custom-list-editor">
461
+ <span data-testid="editor-library">
462
+ {props.library ? props.library.short_name : "none"}
463
+ </span>
464
+ <span data-testid="editor-listid">{props.listId ?? "none"}</span>
465
+ <span data-testid="editor-collections">
466
+ {(props.collections || []).map((c) => c.name).join(",")}
467
+ </span>
468
+ <span data-testid="editor-entrypoints">
469
+ {(props.entryPoints || []).join(",")}
470
+ </span>
471
+ <button
472
+ type="button"
473
+ data-testid="editor-save"
474
+ onClick={() => props.save()}
475
+ >
476
+ save
477
+ </button>
478
+ <button
479
+ type="button"
480
+ data-testid="editor-share"
481
+ onClick={() => props.share()}
482
+ >
483
+ share
484
+ </button>
485
+ </div>
486
+ ));
487
+ });
488
+
489
+ afterEach(() => {
490
+ jest.restoreAllMocks();
491
+ });
492
+
493
+ it("fetches libraries, languages, lists, and collections on mount", () => {
494
+ renderCustomLists();
495
+ expect(fetchLibraries).toHaveBeenCalledTimes(1);
496
+ expect(fetchLanguages).toHaveBeenCalledTimes(1);
497
+ expect(fetchCustomLists).toHaveBeenCalledTimes(1);
498
+ expect(fetchCollections).toHaveBeenCalledTimes(1);
499
+ });
500
+
501
+ it("renders an error message when there is a fetch error", () => {
502
+ const { container, rerender } = renderCustomLists();
503
+ expect(container.querySelector(".alert-danger")).toBeNull();
504
+
505
+ rerender(
506
+ <UnconnectedCustomLists
507
+ {...(baseCustomListsProps({
508
+ fetchError: { status: 500, response: "Error", url: "url" },
509
+ }) as any)}
510
+ />
511
+ );
512
+ expect(container.querySelector(".alert-danger")).not.toBeNull();
513
+ });
514
+
515
+ it("renders a loading indicator while fetching", () => {
516
+ const notLoading = renderCustomLists();
517
+ expect(notLoading.queryByRole("dialog")).toBeNull();
518
+ notLoading.unmount();
519
+
520
+ const loading = renderCustomLists({ isFetching: true });
521
+ expect(loading.getByRole("dialog")).toBeInTheDocument();
522
+ });
523
+
524
+ it("navigates to the create or edit page on initial load", () => {
525
+ // No lists → create page.
526
+ const first = renderCustomLists({ lists: undefined });
527
+ first.rerender(
528
+ <UnconnectedCustomLists
529
+ {...(baseCustomListsProps({ lists: [] }) as any)}
530
+ />
531
+ );
532
+ expect(lastNavigation()).toEqual(`${listsUrl}/create`);
533
+ first.unmount();
534
+
535
+ // Owned lists → edit page for the first sorted list.
536
+ const second = renderCustomLists({ lists: undefined });
537
+ second.rerender(
538
+ <UnconnectedCustomLists
539
+ {...(baseCustomListsProps({ lists: listsData }) as any)}
540
+ />
541
+ );
542
+ expect(lastNavigation()).toEqual(`${listsUrl}/edit/1`);
543
+ second.unmount();
544
+
545
+ // Lists that are not owned → create page (filtered out by the "owned" filter).
546
+ const noOwnedListsData = [
547
+ {
548
+ id: 1,
549
+ name: "a list",
550
+ entry_count: 0,
551
+ collections: [],
552
+ is_owner: false,
553
+ is_shared: true,
554
+ },
555
+ ];
556
+ const third = renderCustomLists({ lists: undefined });
557
+ third.rerender(
558
+ <UnconnectedCustomLists
559
+ {...(baseCustomListsProps({ lists: noOwnedListsData }) as any)}
560
+ />
561
+ );
562
+ expect(lastNavigation()).toEqual(`${listsUrl}/create`);
563
+ });
564
+
565
+ it("sorts the lists when the sort order changes", async () => {
566
+ const user = userEvent.setup();
567
+ const { container } = renderCustomLists();
568
+ expect(sidebarListNames(container)).toEqual(["a list", "z list"]);
569
+
570
+ const sortSelect = container.querySelector<HTMLSelectElement>(
571
+ 'select[name="sort"]'
572
+ );
573
+ await user.selectOptions(sortSelect, "desc");
574
+ expect(sidebarListNames(container)).toEqual(["z list", "a list"]);
575
+
576
+ await user.selectOptions(sortSelect, "asc");
577
+ expect(sidebarListNames(container)).toEqual(["a list", "z list"]);
578
+ });
579
+
580
+ it("filters the lists when the filter changes", async () => {
581
+ const user = userEvent.setup();
582
+ const sharedListsData = [
583
+ {
584
+ id: 1,
585
+ name: "owned unshared",
586
+ entry_count: 0,
587
+ collections: [],
588
+ is_owner: true,
589
+ is_shared: false,
590
+ },
591
+ {
592
+ id: 2,
593
+ name: "owned shared",
594
+ entry_count: 1,
595
+ collections: [],
596
+ is_owner: true,
597
+ is_shared: true,
598
+ },
599
+ {
600
+ id: 3,
601
+ name: "not owned",
602
+ entry_count: 1,
603
+ collections: [],
604
+ is_owner: false,
605
+ is_shared: true,
606
+ },
607
+ ];
608
+ const { container } = renderCustomLists({ lists: sharedListsData });
609
+ const filterSelect = container.querySelector<HTMLSelectElement>(
610
+ 'select[name="filter"]'
611
+ );
612
+
613
+ // Default "owned" filter.
614
+ expect(sidebarListNames(container)).toEqual([
615
+ "owned shared",
616
+ "owned unshared",
617
+ ]);
618
+
619
+ await user.selectOptions(filterSelect, "shared-in");
620
+ expect(sidebarListNames(container)).toEqual(["not owned"]);
621
+
622
+ await user.selectOptions(filterSelect, "shared-out");
623
+ expect(sidebarListNames(container)).toEqual(["owned shared"]);
624
+
625
+ await user.selectOptions(filterSelect, "");
626
+ expect(sidebarListNames(container)).toEqual([
627
+ "not owned",
628
+ "owned shared",
629
+ "owned unshared",
630
+ ]);
631
+
632
+ await user.selectOptions(filterSelect, "owned");
633
+ expect(sidebarListNames(container)).toEqual([
634
+ "owned shared",
635
+ "owned unshared",
636
+ ]);
637
+ });
638
+
639
+ it("renders edit links but no delete buttons for a librarian", () => {
640
+ const { container } = renderCustomLists({}, librarianConfig);
641
+ const items = container.querySelectorAll(".custom-lists-sidebar ul > li");
642
+ expect(items).toHaveLength(2);
643
+
644
+ const firstButtons = items[0].querySelector<HTMLElement>(
645
+ ".custom-list-buttons"
646
+ );
647
+ const firstEdit = within(firstButtons).getByTestId("Link");
648
+ expect(firstEdit).toHaveTextContent("Edit");
649
+ expect(firstEdit).toHaveAttribute(
650
+ "data-to",
651
+ "/admin/web/lists/library/edit/1"
652
+ );
653
+
654
+ const secondButtons = items[1].querySelector<HTMLElement>(
655
+ ".custom-list-buttons"
656
+ );
657
+ const secondEdit = within(secondButtons).getByTestId("Link");
658
+ expect(secondEdit).toHaveAttribute(
659
+ "data-to",
660
+ "/admin/web/lists/library/edit/2"
661
+ );
662
+
663
+ expect(
664
+ container.querySelectorAll(".custom-list-buttons button")
665
+ ).toHaveLength(0);
666
+ });
667
+
668
+ it("deletes a list only after the user confirms, fetching lanes first", async () => {
669
+ const user = userEvent.setup();
670
+ const confirmSpy = jest.spyOn(window, "confirm").mockReturnValue(false);
671
+ const { container } = renderCustomLists();
672
+
673
+ const deleteButton = () =>
674
+ container.querySelectorAll<HTMLButtonElement>(
675
+ ".custom-list-buttons button"
676
+ )[0];
677
+
678
+ await user.click(deleteButton());
679
+ // No lanes were supplied, so the component fetched them.
680
+ await waitFor(() => expect(fetchLanes).toHaveBeenCalled());
681
+ expect(deleteCustomList).toHaveBeenCalledTimes(0);
682
+
683
+ confirmSpy.mockReturnValue(true);
684
+ await user.click(deleteButton());
685
+ await waitFor(() => expect(deleteCustomList).toHaveBeenCalledTimes(1));
686
+ expect(deleteCustomList).toHaveBeenCalledWith("1");
687
+ await waitFor(() => expect(fetchCustomLists).toHaveBeenCalledTimes(2));
688
+ });
689
+
690
+ it("warns which lanes will be deleted along with the list", async () => {
691
+ const user = userEvent.setup();
692
+ const confirmSpy = jest.spyOn(window, "confirm").mockReturnValue(true);
693
+ const { container } = renderCustomLists({ lanes: allLanes });
694
+
695
+ const deleteButtons = () =>
696
+ container.querySelectorAll<HTMLButtonElement>(
697
+ ".custom-list-buttons button"
698
+ );
699
+
700
+ // "a list" (id 1) has no lanes referencing it exclusively.
701
+ await user.click(deleteButtons()[0]);
702
+ await waitFor(() => expect(confirmSpy).toHaveBeenCalledTimes(1));
703
+ expect(confirmSpy.mock.calls[0][0]).toEqual('Delete list "a list"? ');
704
+
705
+ // "z list" (id 2) is the only list in lane 1 and lane 2.
706
+ await user.click(deleteButtons()[1]);
707
+ await waitFor(() => expect(confirmSpy).toHaveBeenCalledTimes(2));
708
+ expect(confirmSpy.mock.calls[1][0]).toEqual(
709
+ 'Delete list "z list"? ' +
710
+ "Deleting this list will delete the following lanes:\n" +
711
+ "\nLane name: lane 1\nLane name: lane 2"
712
+ );
713
+ // The lanes were supplied as a prop, so no fetch was needed.
714
+ expect(fetchLanes).not.toHaveBeenCalled();
715
+ });
716
+
717
+ it("renders the create form, passing the right library and collections, and saves", async () => {
718
+ const user = userEvent.setup();
719
+ renderCustomLists({ editOrCreate: "create" });
720
+
721
+ expect(screen.getByTestId("editor-library")).toHaveTextContent("library");
722
+ expect(screen.getByTestId("editor-listid")).toHaveTextContent("none");
723
+ expect(screen.getByTestId("editor-collections")).toHaveTextContent(
724
+ "collection 2,collection 3"
725
+ );
726
+ expect(fetchCustomLists).toHaveBeenCalledTimes(1);
727
+
728
+ await user.click(screen.getByTestId("editor-save"));
729
+ expect(saveCustomListEditor).toHaveBeenCalledTimes(1);
730
+ await waitFor(() => expect(fetchCustomLists).toHaveBeenCalledTimes(2));
731
+ });
732
+
733
+ it("renders the edit form and fetches details when the selected list changes", () => {
734
+ const listDetails = Object.assign({}, listsData[1], { entries: [entry] });
735
+ const { rerender } = renderCustomLists({
736
+ editOrCreate: "edit",
737
+ identifier: "2",
738
+ listDetails,
739
+ });
740
+
741
+ expect(screen.getByTestId("editor-listid")).toHaveTextContent("2");
742
+ expect(screen.getByTestId("editor-library")).toHaveTextContent("library");
743
+ expect(fetchCustomListDetails).toHaveBeenCalledTimes(1);
744
+
745
+ rerender(
746
+ <UnconnectedCustomLists
747
+ {...(baseCustomListsProps({
748
+ editOrCreate: "edit",
749
+ identifier: "1",
750
+ listDetails,
751
+ }) as any)}
752
+ />
753
+ );
754
+ expect(fetchCustomListDetails).toHaveBeenCalledTimes(2);
755
+ expect(screen.getByTestId("editor-listid")).toHaveTextContent("1");
756
+ });
757
+
758
+ it("shares the current list through the editor's share action", async () => {
759
+ const user = userEvent.setup();
760
+ renderCustomLists({ editOrCreate: "edit", identifier: "2" });
761
+ await user.click(screen.getByTestId("editor-share"));
762
+ expect(shareCustomList).toHaveBeenCalledTimes(1);
763
+ expect(shareCustomList).toHaveBeenCalledWith("2");
764
+ });
765
+
766
+ it("refetches the list details after saving in edit mode", async () => {
767
+ const user = userEvent.setup();
768
+ renderCustomLists({ editOrCreate: "edit", identifier: "2" });
769
+ expect(fetchCustomListDetails).toHaveBeenCalledTimes(1);
770
+
771
+ await user.click(screen.getByTestId("editor-save"));
772
+ expect(saveCustomListEditor).toHaveBeenCalledTimes(1);
773
+ await waitFor(() =>
774
+ expect(fetchCustomListDetails).toHaveBeenCalledTimes(2)
775
+ );
776
+ });
777
+
778
+ it("keeps lists with identical names in a stable order", () => {
779
+ const sameNameLists = [
780
+ {
781
+ id: 1,
782
+ name: "same name",
783
+ entry_count: 0,
784
+ collections: [],
785
+ is_owner: true,
786
+ is_shared: false,
787
+ },
788
+ {
789
+ id: 2,
790
+ name: "same name",
791
+ entry_count: 0,
792
+ collections: [],
793
+ is_owner: true,
794
+ is_shared: false,
795
+ },
796
+ ];
797
+ const { container } = renderCustomLists({ lists: sameNameLists });
798
+ expect(
799
+ container.querySelectorAll(".custom-lists-sidebar ul > li")
800
+ ).toHaveLength(2);
801
+ });
802
+
803
+ it("gets the enabled entry points for the current library", () => {
804
+ const forLibrary = renderCustomLists({ editOrCreate: "create" });
805
+ expect(forLibrary.getByTestId("editor-entrypoints")).toHaveTextContent(
806
+ "Book,Audio"
807
+ );
808
+ forLibrary.unmount();
809
+
810
+ const forAnotherLibrary = renderCustomLists(
811
+ { editOrCreate: "create", library: "another library" },
812
+ librarianConfig
813
+ );
814
+ expect(
815
+ forAnotherLibrary.getByTestId("editor-entrypoints")
816
+ ).toHaveTextContent("Audio");
817
+ });
818
+ });
202
819
  });