@thepalaceproject/circulation-admin 1.43.0-post.6 → 1.43.0-post.8

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.
@@ -0,0 +1,88 @@
1
+ import * as React from "react";
2
+ import { screen } from "@testing-library/react";
3
+ import { FacetData } from "@thepalaceproject/web-opds-client/lib/interfaces";
4
+
5
+ import { renderWithProviders } from "../testUtils/withProviders";
6
+
7
+ // Each tab is a CatalogLink, which renders a React Router `Link`. Router `Link`
8
+ // needs router context that `renderWithProviders` does not supply, so mock it to
9
+ // a marker that exposes its navigation target and children (matching Lane.test.tsx).
10
+ jest.mock("react-router", () => ({
11
+ ...jest.requireActual("react-router"),
12
+ Link: (props) => (
13
+ <div data-testid="Link" data-to={props.to}>
14
+ {props.children}
15
+ </div>
16
+ ),
17
+ }));
18
+
19
+ import { EntryPointsTabs } from "../../../src/components/EntryPointsTabs";
20
+
21
+ const twoFacets: FacetData[] = [
22
+ {
23
+ label: "Ebooks",
24
+ href: "http://circulation.librarysimplified.org/groups/?entrypoint=Book",
25
+ active: true,
26
+ },
27
+ {
28
+ label: "Audiobooks",
29
+ href: "http://circulation.librarysimplified.org/groups/?entrypoint=Audio",
30
+ active: false,
31
+ },
32
+ ];
33
+
34
+ describe("EntryPointsTabs", () => {
35
+ it("should not generate any tabs", () => {
36
+ const { container } = renderWithProviders(<EntryPointsTabs />);
37
+
38
+ expect(container.querySelector(".nav-tabs")).toBeNull();
39
+ });
40
+
41
+ it("should generate two tabs with two entry points", () => {
42
+ const { container } = renderWithProviders(
43
+ <EntryPointsTabs facets={twoFacets} />
44
+ );
45
+
46
+ const list = container.querySelector("ul.nav-tabs");
47
+ expect(list).toBeInTheDocument();
48
+
49
+ const items = list.querySelectorAll("li");
50
+ expect(items).toHaveLength(2);
51
+ expect(items[0]).toHaveClass("active");
52
+ expect(items[1]).not.toHaveClass("active");
53
+
54
+ expect(screen.getByText("Ebooks")).toBeInTheDocument();
55
+ expect(screen.getByText("Audiobooks")).toBeInTheDocument();
56
+ });
57
+
58
+ it("uses router to navigate when a tab is clicked", () => {
59
+ renderWithProviders(<EntryPointsTabs facets={twoFacets} />);
60
+
61
+ const links = screen.getAllByTestId("Link");
62
+ expect(links).toHaveLength(2);
63
+ expect(links[0]).toHaveTextContent("Ebooks");
64
+ expect(links[1]).toHaveTextContent("Audiobooks");
65
+ // Each tab links somewhere for the router to navigate to.
66
+ expect(links[0]).toHaveAttribute("data-to");
67
+ expect(links[1]).toHaveAttribute("data-to");
68
+ });
69
+
70
+ it("should generate one tab with an active entry point", () => {
71
+ const oneFacet: FacetData[] = [
72
+ {
73
+ label: "Audiobooks",
74
+ href: "http://circulation.librarysimplified.org/groups/?entrypoint=Audio",
75
+ active: true,
76
+ },
77
+ ];
78
+ const { container } = renderWithProviders(
79
+ <EntryPointsTabs facets={oneFacet} />
80
+ );
81
+
82
+ const items = container.querySelectorAll("ul.nav-tabs li");
83
+ expect(items).toHaveLength(1);
84
+ expect(items[0]).toHaveClass("active");
85
+
86
+ expect(screen.getByText("Audiobooks")).toBeInTheDocument();
87
+ });
88
+ });
@@ -304,11 +304,7 @@ describe("InventoryReportRequestModal", () => {
304
304
  const NO_EMAIL_CONTEXT_PROVIDER_PROPS = { email: undefined };
305
305
 
306
306
  it("shows the modal when `show` is true", async () => {
307
- const {
308
- getAllByRole,
309
- queryAllByRole,
310
- rerender,
311
- } = renderWithProviders(
307
+ const { getAllByRole, queryAllByRole, rerender } = renderWithProviders(
312
308
  <InventoryReportRequestModal
313
309
  show={false}
314
310
  onHide={onHide}
@@ -385,19 +381,15 @@ describe("InventoryReportRequestModal", () => {
385
381
  let show = true;
386
382
  onHide.mockImplementation(() => (show = false));
387
383
 
388
- const {
389
- getAllByRole,
390
- getByRole,
391
- queryAllByRole,
392
- rerender,
393
- } = renderWithProviders(
394
- <InventoryReportRequestModal
395
- show={show}
396
- onHide={onHide}
397
- library={LIBRARY}
398
- />,
399
- { queryClient }
400
- );
384
+ const { getAllByRole, getByRole, queryAllByRole, rerender } =
385
+ renderWithProviders(
386
+ <InventoryReportRequestModal
387
+ show={show}
388
+ onHide={onHide}
389
+ library={LIBRARY}
390
+ />,
391
+ { queryClient }
392
+ );
401
393
  getAllByRole("dialog");
402
394
  expect(show).toBe(true);
403
395
  expect(onHide).not.toHaveBeenCalled();
@@ -420,10 +412,7 @@ describe("InventoryReportRequestModal", () => {
420
412
  });
421
413
 
422
414
  it("updates confirmation message, if report info request arrives in time", async () => {
423
- const {
424
- getByRole,
425
- rerender,
426
- } = renderWithProviders(
415
+ const { getByRole, rerender } = renderWithProviders(
427
416
  <InventoryReportRequestModal
428
417
  show={true}
429
418
  onHide={onHide}
@@ -457,10 +446,7 @@ describe("InventoryReportRequestModal", () => {
457
446
  )
458
447
  );
459
448
 
460
- const {
461
- getByRole,
462
- rerender,
463
- } = renderWithProviders(
449
+ const { getByRole, rerender } = renderWithProviders(
464
450
  <InventoryReportRequestModal
465
451
  show={true}
466
452
  onHide={onHide}
@@ -505,10 +491,7 @@ describe("InventoryReportRequestModal", () => {
505
491
  )
506
492
  );
507
493
 
508
- const {
509
- getByRole,
510
- rerender,
511
- } = renderWithProviders(
494
+ const { getByRole, rerender } = renderWithProviders(
512
495
  <InventoryReportRequestModal
513
496
  show={true}
514
497
  onHide={onHide}
@@ -538,10 +521,7 @@ describe("InventoryReportRequestModal", () => {
538
521
  it("requests report generation, if request confirmed", async () => {
539
522
  const user = userEvent.setup();
540
523
 
541
- const {
542
- getByRole,
543
- rerender,
544
- } = renderWithProviders(
524
+ const { getByRole, rerender } = renderWithProviders(
545
525
  <InventoryReportRequestModal
546
526
  show={true}
547
527
  onHide={onHide}
@@ -574,10 +554,7 @@ describe("InventoryReportRequestModal", () => {
574
554
  it("displays success message, when request is successful", async () => {
575
555
  const user = userEvent.setup();
576
556
 
577
- const {
578
- getByRole,
579
- rerender,
580
- } = renderWithProviders(
557
+ const { getByRole, rerender } = renderWithProviders(
581
558
  <InventoryReportRequestModal
582
559
  show={true}
583
560
  onHide={onHide}
@@ -615,10 +592,7 @@ describe("InventoryReportRequestModal", () => {
615
592
  );
616
593
  const user = userEvent.setup();
617
594
 
618
- const {
619
- getByRole,
620
- rerender,
621
- } = renderWithProviders(
595
+ const { getByRole, rerender } = renderWithProviders(
622
596
  <InventoryReportRequestModal
623
597
  show={true}
624
598
  onHide={onHide}
@@ -19,13 +19,9 @@ const customListsData = [
19
19
  { id: 1, name: "list 1", entries: [], is_owner: true, is_shared: false },
20
20
  ];
21
21
 
22
- const editLane = stub().returns(
23
- new Promise<void>((resolve) => resolve())
24
- );
22
+ const editLane = stub().returns(new Promise<void>((resolve) => resolve()));
25
23
 
26
- const deleteLane = stub().returns(
27
- new Promise<void>((resolve) => resolve())
28
- );
24
+ const deleteLane = stub().returns(new Promise<void>((resolve) => resolve()));
29
25
 
30
26
  const toggleLaneVisibility = stub();
31
27
 
@@ -0,0 +1,68 @@
1
+ import * as React from "react";
2
+ import { screen, waitForElementToBeRemoved } from "@testing-library/react";
3
+ import * as fetchMock from "fetch-mock-jest";
4
+
5
+ import { renderWithProviders } from "../testUtils/withProviders";
6
+
7
+ // Header and Footer are page chrome; Header pulls in the react-router v3 context
8
+ // that renderWithProviders does not supply. Mock them to markers.
9
+ jest.mock("../../../src/components/Header", () => ({
10
+ __esModule: true,
11
+ default: () => <div data-testid="header" />,
12
+ }));
13
+ jest.mock("../../../src/components/Footer", () => ({
14
+ __esModule: true,
15
+ default: () => <div data-testid="footer" />,
16
+ }));
17
+
18
+ // The real Lanes child (and its sidebar/editor) renders React Router `Link`s,
19
+ // which need router context renderWithProviders does not supply. Mock `Link` to
20
+ // a marker, matching Lane.test.tsx.
21
+ jest.mock("react-router", () => ({
22
+ ...jest.requireActual("react-router"),
23
+ Link: (props) => <div data-testid="Link">{props.children}</div>,
24
+ }));
25
+
26
+ import LanePage from "../../../src/components/LanePage";
27
+
28
+ describe("LanePage", () => {
29
+ beforeAll(() => {
30
+ Object.assign(fetchMock.config, { fetch, Headers, Request, Response });
31
+ });
32
+
33
+ beforeEach(() => {
34
+ // Lanes fetches lanes and custom lists on mount; stub the network so no real
35
+ // request is made.
36
+ fetchMock.mock("*", { lanes: [], custom_lists: [] });
37
+ });
38
+
39
+ afterEach(() => {
40
+ fetchMock.restore();
41
+ });
42
+
43
+ it("shows lanes with info from params", async () => {
44
+ const params = {
45
+ library: "library",
46
+ editOrCreate: "edit",
47
+ identifier: "5",
48
+ };
49
+
50
+ const { container } = renderWithProviders(<LanePage params={params} />);
51
+
52
+ // Header and Footer chrome are composed around the body.
53
+ expect(screen.getByTestId("header")).toBeInTheDocument();
54
+ expect(screen.getByTestId("footer")).toBeInTheDocument();
55
+
56
+ // The real Lanes child renders its container...
57
+ expect(container.querySelector("main.lanes-container")).toBeInTheDocument();
58
+ // ...and, because editOrCreate is "edit", its lane editor panel.
59
+ expect(container.querySelector(".lane-editor")).toBeInTheDocument();
60
+
61
+ // While its on-mount fetches (lanes and custom lists) are in flight, Lanes
62
+ // shows a loading indicator. Wait for it to clear so those fetches settle
63
+ // and their state updates flush, instead of racing a fixed-length timer.
64
+ await waitForElementToBeRemoved(() =>
65
+ screen.queryByRole("dialog", { name: "Loading" })
66
+ );
67
+ });
68
+ });
@@ -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
+ });
@@ -612,7 +612,9 @@ describe("PatronBlockingRulesEditor", () => {
612
612
  });
613
613
 
614
614
  it("disables all editing inputs and buttons when disabled prop is true", () => {
615
- renderEditor(<PatronBlockingRulesEditor value={existingRules} disabled={true} />);
615
+ renderEditor(
616
+ <PatronBlockingRulesEditor value={existingRules} disabled={true} />
617
+ );
616
618
 
617
619
  // The Help button stays enabled even in disabled mode (read-only affordance).
618
620
  const editingButtons = screen
@@ -0,0 +1,54 @@
1
+ import * as React from "react";
2
+ import { render } from "@testing-library/react";
3
+
4
+ import PatronInfo from "../../../src/components/PatronInfo";
5
+ import { PatronData } from "../../../src/interfaces";
6
+
7
+ const patrons: PatronData[] = [
8
+ {
9
+ authorization_expires: "",
10
+ username: "User Name",
11
+ personal_name: "Personal Name",
12
+ email_address: "user@email.com",
13
+ authorization_identifier: "1234",
14
+ authorization_identifiers: [""],
15
+ block_reason: "",
16
+ external_type: "",
17
+ fines: "",
18
+ permanent_id: "",
19
+ },
20
+ {
21
+ authorization_expires: "",
22
+ personal_name: "Personal Name2",
23
+ authorization_identifier: "5678",
24
+ authorization_identifiers: [""],
25
+ block_reason: "",
26
+ external_type: "",
27
+ fines: "",
28
+ permanent_id: "",
29
+ },
30
+ ];
31
+
32
+ describe("PatronInfo", () => {
33
+ it("should display the patron's information", () => {
34
+ const { container } = render(<PatronInfo patron={patrons[0]} />);
35
+
36
+ const items = container.querySelectorAll("ul.patron-data-list > li");
37
+ expect(items).toHaveLength(4);
38
+ expect(items[0]).toHaveTextContent("Username:User Name");
39
+ expect(items[1]).toHaveTextContent("Personal Name:Personal Name");
40
+ expect(items[2]).toHaveTextContent("Email Address:user@email.com");
41
+ expect(items[3]).toHaveTextContent("Identifier:1234");
42
+ });
43
+
44
+ it("should update the patron's information if a new patron was searched and found", () => {
45
+ const { container, rerender } = render(<PatronInfo patron={patrons[0]} />);
46
+
47
+ rerender(<PatronInfo patron={patrons[1]} />);
48
+
49
+ const items = container.querySelectorAll("ul.patron-data-list > li");
50
+ expect(items).toHaveLength(2);
51
+ expect(items[0]).toHaveTextContent("Personal Name:Personal Name2");
52
+ expect(items[1]).toHaveTextContent("Identifier:5678");
53
+ });
54
+ });
@@ -0,0 +1,41 @@
1
+ import * as React from "react";
2
+ import { screen } from "@testing-library/react";
3
+
4
+ import { renderWithProviders } from "../testUtils/withProviders";
5
+ import SetupPage from "../../../src/components/SetupPage";
6
+
7
+ describe("SetupPage", () => {
8
+ beforeEach(() => {
9
+ // SetupPage renders the connected IndividualAdmins list, which fetches the
10
+ // admin list on mount. Stub fetch so mounting does not hit the network.
11
+ jest.spyOn(globalThis, "fetch").mockResolvedValue(
12
+ new Response(JSON.stringify({ individualAdmins: [] }), {
13
+ status: 200,
14
+ headers: { "Content-Type": "application/json" },
15
+ })
16
+ );
17
+ });
18
+
19
+ afterEach(() => {
20
+ jest.restoreAllMocks();
21
+ });
22
+
23
+ it("shows the individual-admins setup (create) form", async () => {
24
+ renderWithProviders(<SetupPage />);
25
+
26
+ // SetupPage wires IndividualAdmins with settingUp + editOrCreate="create".
27
+ // That combination renders the "Welcome!" heading and the
28
+ // "Set up your system admin account" create-form heading; their presence in
29
+ // the real DOM proves the child received those props (the original shallow
30
+ // test asserted the props directly). No identifier is passed, so there is no
31
+ // per-item "Edit ..." heading — only the create form shows.
32
+ expect(
33
+ await screen.findByRole("heading", { name: "Welcome!" })
34
+ ).toBeInTheDocument();
35
+ expect(
36
+ screen.getByRole("heading", {
37
+ name: "Set up your system admin account",
38
+ })
39
+ ).toBeInTheDocument();
40
+ });
41
+ });
@@ -0,0 +1,62 @@
1
+ import * as React from "react";
2
+ import { render, screen } from "@testing-library/react";
3
+
4
+ import SingleStatListItem, {
5
+ SingleStatListItemProps,
6
+ } from "../../../src/components/SingleStatListItem";
7
+
8
+ describe("SingleStatListItem", () => {
9
+ const statWithTooltip: SingleStatListItemProps = {
10
+ label: "Number of Widgets",
11
+ value: 357892,
12
+ tooltip: "Total Widget Count",
13
+ };
14
+ const humanReadableValue = "357.9k";
15
+ const formattedValue = "357,892";
16
+
17
+ it("renders a top-level <li>, with or without a tooltip", () => {
18
+ const { container, rerender } = render(
19
+ <SingleStatListItem {...statWithTooltip} />
20
+ );
21
+ expect(container.firstElementChild?.tagName).toBe("LI");
22
+ expect(container.firstElementChild).toHaveClass("single-stat");
23
+
24
+ // Without a tooltip the <li> is still the top-level element.
25
+ rerender(
26
+ <SingleStatListItem
27
+ label={statWithTooltip.label}
28
+ value={statWithTooltip.value}
29
+ />
30
+ );
31
+ expect(container.firstElementChild?.tagName).toBe("LI");
32
+ expect(container.firstElementChild).toHaveClass("single-stat");
33
+ });
34
+
35
+ it("shows a tooltip wrapping the value and label when one is provided", () => {
36
+ const { container } = render(<SingleStatListItem {...statWithTooltip} />);
37
+
38
+ const tooltip = container.querySelector('[data-toggle="tooltip"]');
39
+ expect(tooltip).not.toBeNull();
40
+
41
+ const title = tooltip?.getAttribute("title") ?? "";
42
+ expect(title).toContain(statWithTooltip.tooltip);
43
+ expect(title).toContain(formattedValue);
44
+
45
+ // When there's a tooltip, it wraps the (rounded) value and the label.
46
+ expect(tooltip).toHaveTextContent(humanReadableValue);
47
+ expect(tooltip).toHaveTextContent(statWithTooltip.label);
48
+ });
49
+
50
+ it("omits the tooltip when none is provided, showing value and label directly", () => {
51
+ const { container } = render(
52
+ <SingleStatListItem
53
+ label={statWithTooltip.label}
54
+ value={statWithTooltip.value}
55
+ />
56
+ );
57
+
58
+ expect(container.querySelector('[data-toggle="tooltip"]')).toBeNull();
59
+ expect(screen.getByText(humanReadableValue)).toBeInTheDocument();
60
+ expect(screen.getByText(statWithTooltip.label)).toBeInTheDocument();
61
+ });
62
+ });
@@ -382,14 +382,10 @@ describe("Dashboard Statistics", () => {
382
382
  roles,
383
383
  quicksightPagePath: fakeQuickSightHref,
384
384
  };
385
- const {
386
- container,
387
- getByRole,
388
- queryByRole,
389
- queryByText,
390
- } = renderWithProviders(<Stats library={sampleLibraryKey} />, {
391
- appConfigSettings,
392
- });
385
+ const { container, getByRole, queryByRole, queryByText } =
386
+ renderWithProviders(<Stats library={sampleLibraryKey} />, {
387
+ appConfigSettings,
388
+ });
393
389
 
394
390
  // We should always render a Usage reports group when a library is specified.
395
391
  getByRole("heading", {
@@ -0,0 +1,68 @@
1
+ import * as React from "react";
2
+ import { render, screen } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+
5
+ // The tab content (TroubleshootingCategoryPage) renders the connected
6
+ // Diagnostics/Self-tests containers, which need Redux store + csrf context this
7
+ // test does not supply. That content is incidental here — the test is about the
8
+ // tab navigation — so mock it to a marker.
9
+ jest.mock("../../../src/components/TroubleshootingCategoryPage", () => ({
10
+ __esModule: true,
11
+ default: () => <div data-testid="category-page" />,
12
+ }));
13
+
14
+ import TroubleshootingTabContainer from "../../../src/components/TroubleshootingTabContainer";
15
+
16
+ describe("TroubleshootingTabContainer", () => {
17
+ let goToTab: jest.Mock;
18
+
19
+ beforeEach(() => {
20
+ goToTab = jest.fn();
21
+ });
22
+
23
+ it("renders tabs and defaults to showing the Diagnostics tab", () => {
24
+ const { container } = render(
25
+ <TroubleshootingTabContainer goToTab={goToTab} tab="diagnostics" />
26
+ );
27
+
28
+ const nav = container.querySelector("ul.nav-tabs");
29
+ expect(nav).not.toBeNull();
30
+
31
+ const tabs = nav.querySelectorAll("li");
32
+ expect(tabs).toHaveLength(2);
33
+
34
+ expect(tabs[0]).toHaveTextContent("Diagnostics");
35
+ expect(tabs[0]).toHaveClass("active");
36
+
37
+ expect(tabs[1]).toHaveTextContent("Self-tests");
38
+ expect(tabs[1]).not.toHaveClass("active");
39
+ });
40
+
41
+ it("calls goToTab when another tab is clicked", async () => {
42
+ const user = userEvent.setup();
43
+ render(<TroubleshootingTabContainer goToTab={goToTab} tab="diagnostics" />);
44
+
45
+ await user.click(screen.getByRole("link", { name: "Self-tests" }));
46
+
47
+ expect(goToTab).toHaveBeenCalledTimes(1);
48
+ expect(goToTab).toHaveBeenCalledWith("self-tests");
49
+ });
50
+
51
+ it("switches the active tab when the tab prop changes", () => {
52
+ const { container, rerender } = render(
53
+ <TroubleshootingTabContainer goToTab={goToTab} tab="diagnostics" />
54
+ );
55
+
56
+ let tabs = container.querySelectorAll("ul.nav-tabs li");
57
+ expect(tabs[0]).toHaveClass("active");
58
+ expect(tabs[1]).not.toHaveClass("active");
59
+
60
+ rerender(
61
+ <TroubleshootingTabContainer goToTab={goToTab} tab="self-tests" />
62
+ );
63
+
64
+ tabs = container.querySelectorAll("ul.nav-tabs li");
65
+ expect(tabs[0]).not.toHaveClass("active");
66
+ expect(tabs[1]).toHaveClass("active");
67
+ });
68
+ });
@@ -0,0 +1,43 @@
1
+ import * as React from "react";
2
+ import { render, screen } from "@testing-library/react";
3
+
4
+ // Header pulls in the react-router v3 context a plain render does not supply, and
5
+ // both Header and Footer are page chrome with no behavior worth exercising here.
6
+ // Mock them to markers so the test stays about what WelcomePage composes.
7
+ jest.mock("../../../src/components/Header", () => ({
8
+ __esModule: true,
9
+ default: () => <div data-testid="header" />,
10
+ }));
11
+ jest.mock("../../../src/components/Footer", () => ({
12
+ __esModule: true,
13
+ default: () => <div data-testid="footer" />,
14
+ }));
15
+
16
+ import WelcomePage from "../../../src/components/WelcomePage";
17
+
18
+ describe("WelcomePage", () => {
19
+ it("shows the header", () => {
20
+ render(<WelcomePage />);
21
+ expect(screen.getByTestId("header")).toBeInTheDocument();
22
+ });
23
+
24
+ it("shows the welcome message", () => {
25
+ render(<WelcomePage />);
26
+ expect(
27
+ screen.getByRole("heading", {
28
+ name: "Welcome to the Circulation Admin Interface!",
29
+ })
30
+ ).toBeInTheDocument();
31
+ });
32
+
33
+ it("shows a link to the library create form", () => {
34
+ render(<WelcomePage />);
35
+ const link = screen.getByRole("link", { name: /create a new library/i });
36
+ expect(link).toHaveAttribute("href", "/admin/web/config/libraries/create");
37
+ });
38
+
39
+ it("shows the footer", () => {
40
+ render(<WelcomePage />);
41
+ expect(screen.getByTestId("footer")).toBeInTheDocument();
42
+ });
43
+ });