@thepalaceproject/circulation-admin 1.43.0 → 1.44.0
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/CLAUDE.md +28 -7
- package/README.md +11 -5
- package/dist/circulation-admin.js +1 -1
- package/eslint.config.mjs +9 -1
- package/jest.config.js +22 -8
- package/package.json +6 -20
- package/tests/jest/actions.test.ts +962 -0
- package/tests/jest/components/AccountPage.test.tsx +41 -0
- package/tests/jest/components/AdvancedSearchBooleanFilter.test.tsx +231 -0
- package/tests/jest/components/AdvancedSearchBuilder.test.tsx +93 -20
- package/tests/jest/components/AdvancedSearchFilter.test.tsx +142 -0
- package/tests/jest/components/AdvancedSearchFilterInput.test.tsx +212 -0
- package/tests/jest/components/AdvancedSearchFilterViewer.test.tsx +52 -0
- package/tests/jest/components/AdvancedSearchValueFilter.test.tsx +123 -0
- package/tests/jest/components/Announcement.test.tsx +76 -0
- package/tests/jest/components/AnnouncementForm.test.tsx +229 -0
- package/tests/jest/components/AnnouncementsSection.test.tsx +146 -0
- package/tests/jest/components/Autocomplete.test.tsx +67 -0
- package/tests/jest/components/BookCoverEditor.test.tsx +393 -0
- package/tests/jest/components/BookDetails.test.tsx +133 -0
- package/tests/jest/components/BookDetailsContainer.test.tsx +116 -0
- package/tests/jest/components/BookDetailsEditor.test.tsx +275 -0
- package/tests/jest/components/BookDetailsTabContainer.test.tsx +237 -0
- package/tests/jest/components/BookEditForm.test.tsx +352 -0
- package/tests/jest/components/BookEditor.test.tsx +20 -21
- package/tests/jest/components/CatalogPage.test.tsx +158 -0
- package/tests/jest/components/CatalogServices.test.tsx +100 -0
- package/tests/jest/components/ChangePasswordForm.test.tsx +163 -0
- package/tests/jest/components/CirculationEventsDownloadForm.test.tsx +41 -0
- package/tests/jest/components/Classifications.test.tsx +166 -0
- package/tests/jest/components/ClassificationsForm.test.tsx +458 -0
- package/tests/jest/components/ClassificationsTable.test.tsx +57 -0
- package/tests/jest/components/Collections.test.tsx +335 -7
- package/tests/jest/components/ColorPicker.test.tsx +58 -0
- package/tests/jest/components/ComplaintForm.test.tsx +171 -0
- package/tests/jest/components/Complaints.test.tsx +259 -0
- package/tests/jest/components/ConfigPage.test.tsx +58 -0
- package/tests/jest/components/ConfigTabContainer.test.tsx +196 -0
- package/tests/jest/components/ContextProvider.test.tsx +138 -0
- package/tests/jest/components/Contributors.test.tsx +157 -0
- package/tests/jest/components/CustomListEditor.test.tsx +367 -0
- package/tests/jest/components/CustomListEntriesEditor.test.tsx +1240 -0
- package/tests/jest/components/CustomListPage.test.tsx +57 -0
- package/tests/jest/components/CustomListSearch.test.tsx +275 -0
- package/tests/jest/components/CustomLists.test.tsx +621 -4
- package/tests/jest/components/CustomListsForBook.test.tsx +353 -0
- package/tests/jest/components/CustomListsSidebar.test.tsx +251 -0
- package/tests/jest/components/DashboardPage.test.tsx +76 -0
- package/tests/jest/components/DebugAuthentication.test.tsx +1 -5
- package/tests/jest/components/DiagnosticsServiceTabs.test.tsx +141 -0
- package/tests/jest/components/DiagnosticsServiceType.test.tsx +75 -0
- package/tests/jest/components/DiagnosticsTabContainer.test.tsx +147 -0
- package/tests/jest/components/DiscoveryServices.test.tsx +154 -7
- package/tests/jest/components/EditableConfigList.test.tsx +606 -9
- package/tests/jest/components/EditableInput.test.tsx +250 -1
- package/tests/jest/components/EditorField.test.tsx +95 -0
- package/tests/jest/components/EntryPointsContainer.test.tsx +97 -0
- package/tests/jest/components/EntryPointsTabs.test.tsx +88 -0
- package/tests/jest/components/ErrorMessage.test.tsx +97 -0
- package/tests/jest/components/Footer.test.tsx +141 -0
- package/tests/jest/components/GenreForm.test.tsx +152 -0
- 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/InputList.test.tsx +493 -0
- package/tests/jest/components/InventoryReportRequestModal.test.tsx +16 -42
- package/tests/jest/components/JsonField.test.tsx +20 -0
- package/tests/jest/components/Lane.test.tsx +132 -10
- package/tests/jest/components/LaneCustomListsEditor.test.tsx +450 -0
- package/tests/jest/components/LaneEditor.test.tsx +377 -13
- package/tests/jest/components/LanePage.test.tsx +68 -0
- package/tests/jest/components/Lanes.test.tsx +446 -0
- package/tests/jest/components/LanesSidebar.test.tsx +621 -0
- package/tests/jest/components/LanguageField.test.tsx +40 -0
- package/tests/jest/components/Libraries.test.tsx +48 -0
- package/tests/jest/components/LibraryEditForm.test.tsx +383 -0
- package/tests/jest/components/LibraryRegistration.test.tsx +354 -0
- package/tests/jest/components/LibraryRegistrationForm.test.tsx +203 -0
- package/tests/jest/components/LoadButton.test.tsx +49 -0
- package/tests/jest/components/ManagePatrons.test.tsx +73 -0
- package/tests/jest/components/ManagePatronsForm.test.tsx +180 -0
- package/tests/jest/components/ManagePatronsTabContainer.test.tsx +143 -0
- package/tests/jest/components/MetadataServices.test.tsx +95 -0
- package/tests/jest/components/NeighborhoodAnalyticsForm.test.tsx +167 -0
- package/tests/jest/components/PairedMenus.test.tsx +220 -0
- package/tests/jest/components/PatronAuthServices.test.tsx +114 -0
- package/tests/jest/components/PatronBlockingRulesEditor.test.tsx +3 -1
- package/tests/jest/components/PatronInfo.test.tsx +54 -0
- package/tests/jest/components/ProtocolFormField.test.tsx +479 -7
- package/tests/jest/components/ResetAdobeId.test.tsx +302 -0
- package/tests/jest/components/SaveButton.test.tsx +47 -0
- package/tests/jest/components/SelfTestResult.test.tsx +164 -0
- package/tests/jest/components/SelfTests.test.tsx +601 -0
- package/tests/jest/components/SelfTestsCategory.test.tsx +200 -0
- package/tests/jest/components/SelfTestsTabContainer.test.tsx +216 -0
- package/tests/jest/components/ServiceEditForm.test.tsx +1092 -0
- package/tests/jest/components/ServiceWithRegistrationsEditForm.test.tsx +110 -0
- package/tests/jest/components/SetupPage.test.tsx +41 -0
- package/tests/jest/components/SingleStatListItem.test.tsx +62 -0
- package/tests/jest/components/SitewideAnnouncements.test.tsx +134 -0
- package/tests/jest/components/Stats.test.tsx +4 -15
- package/tests/jest/components/TabContainer.test.tsx +68 -0
- package/tests/jest/components/TextWithEditMode.test.tsx +218 -0
- package/tests/jest/components/Timestamp.test.tsx +74 -0
- package/tests/jest/components/ToolTip.test.tsx +46 -0
- package/tests/jest/components/TroubleshootingCategoryPage.test.tsx +69 -0
- package/tests/jest/components/TroubleshootingPage.test.tsx +94 -0
- package/tests/jest/components/TroubleshootingTabContainer.test.tsx +68 -0
- package/tests/jest/components/UpdatingLoader.test.tsx +36 -0
- package/tests/jest/components/WelcomePage.test.tsx +43 -0
- package/tests/jest/components/WithEditButton.test.tsx +55 -0
- package/tests/jest/components/WithRemoveButton.test.tsx +95 -0
- package/tests/jest/editorAdapter.test.ts +245 -0
- package/tests/jest/features/book.test.ts +75 -80
- package/tests/jest/fixtures/classificationsData.ts +45 -0
- package/tests/jest/fixtures/genreData.ts +811 -0
- package/tests/jest/index.test.tsx +50 -0
- package/tests/jest/models/Admin.test.ts +206 -0
- package/tests/jest/reducers/bookCoverPreview.test.ts +81 -0
- package/tests/jest/reducers/classifications.test.ts +161 -0
- package/tests/jest/reducers/collections.test.ts +96 -0
- package/tests/jest/reducers/complaints.test.ts +166 -0
- package/tests/jest/reducers/createFetchEditReducer.test.ts +220 -0
- package/tests/jest/reducers/createRegisterLibraryReducer.test.ts +51 -0
- package/tests/jest/reducers/customListDetails.test.ts +120 -0
- package/tests/jest/reducers/customListEditor.test.ts +2823 -0
- package/tests/jest/reducers/managePatrons.test.ts +71 -0
- package/tests/jest/testUtils/formDataShim.test.ts +135 -0
- package/tests/jest/testUtils/formDataShim.ts +112 -0
- package/tests/jest/utils/sharedFunctions.test.ts +121 -0
- package/tsconfig.json +3 -4
- package/testReporter.js +0 -29
- package/tests/jest/README.md +0 -6
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
|
|
5
|
+
import DiagnosticsServiceTabs from "../../../src/components/DiagnosticsServiceTabs";
|
|
6
|
+
|
|
7
|
+
describe("DiagnosticsServiceTabs", () => {
|
|
8
|
+
let goToTab: jest.Mock;
|
|
9
|
+
|
|
10
|
+
const ts1 = {
|
|
11
|
+
service: "test_service_1",
|
|
12
|
+
id: "1",
|
|
13
|
+
start: "start_time_string_1",
|
|
14
|
+
duration: "0",
|
|
15
|
+
collection_name: "collection1",
|
|
16
|
+
};
|
|
17
|
+
const ts2 = {
|
|
18
|
+
service: "test_service_2",
|
|
19
|
+
id: "2",
|
|
20
|
+
start: "start_time_string_2",
|
|
21
|
+
duration: "0",
|
|
22
|
+
collection_name: "collection2",
|
|
23
|
+
exception: "Stack trace",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const content: any = {
|
|
27
|
+
test_service_1: { collection1: [ts1] },
|
|
28
|
+
test_service_2: { collection2: [ts2] },
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const renderTabs = () => {
|
|
32
|
+
goToTab = jest.fn();
|
|
33
|
+
return render(
|
|
34
|
+
<DiagnosticsServiceTabs
|
|
35
|
+
goToTab={goToTab}
|
|
36
|
+
content={content}
|
|
37
|
+
tab="test_service_1"
|
|
38
|
+
/>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Find the collapsible `.panel` whose header text matches `title`. The
|
|
43
|
+
// collection panels are titled by collection name; the timestamp panels nested
|
|
44
|
+
// inside them are titled by their start time.
|
|
45
|
+
const panelByTitle = (container: HTMLElement, title: string) => {
|
|
46
|
+
const titleEl = Array.from(container.querySelectorAll(".panel-title")).find(
|
|
47
|
+
(el) => el.textContent === title
|
|
48
|
+
);
|
|
49
|
+
return titleEl ? titleEl.closest(".panel") : null;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
describe("nav", () => {
|
|
53
|
+
it("renders a tab for each service", () => {
|
|
54
|
+
const { container } = renderTabs();
|
|
55
|
+
expect(container.querySelectorAll("ul.nav-tabs")).toHaveLength(1);
|
|
56
|
+
const tabs = container.querySelectorAll("ul.nav-tabs li a");
|
|
57
|
+
expect(tabs).toHaveLength(2);
|
|
58
|
+
expect(tabs[0]).toHaveTextContent("Test_service_1");
|
|
59
|
+
expect(tabs[1]).toHaveTextContent("Test_service_2");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("displays a badge", () => {
|
|
63
|
+
const { container } = renderTabs();
|
|
64
|
+
const tabs = container.querySelectorAll("ul.nav-tabs li");
|
|
65
|
+
|
|
66
|
+
// The first service has no exception: a plain count badge with a tooltip.
|
|
67
|
+
const tabWithoutException = tabs[0];
|
|
68
|
+
expect(tabWithoutException.querySelector(".danger")).toBeNull();
|
|
69
|
+
const badge = tabWithoutException.querySelector(".badge");
|
|
70
|
+
expect(badge).toHaveTextContent("1");
|
|
71
|
+
const toolTip = tabWithoutException.querySelector(".tool-tip");
|
|
72
|
+
expect(toolTip).toHaveTextContent(
|
|
73
|
+
"Total number of timestamps for this service"
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// The second service contains a timestamp with an exception, so it shows a
|
|
77
|
+
// danger badge (a warning) and no tooltip.
|
|
78
|
+
const tabWithException = tabs[1];
|
|
79
|
+
const dangerBadge = tabWithException.querySelector(".badge.danger");
|
|
80
|
+
expect(dangerBadge).toHaveTextContent("!");
|
|
81
|
+
expect(tabWithException.querySelector(".tool-tip")).toBeNull();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("defaults to showing the first tab", () => {
|
|
85
|
+
const { container } = renderTabs();
|
|
86
|
+
const tabs = container.querySelectorAll("ul.nav-tabs li");
|
|
87
|
+
expect(tabs[0]).toHaveClass("active");
|
|
88
|
+
expect(tabs[1]).not.toHaveClass("active");
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("content", () => {
|
|
93
|
+
it("renders collections", () => {
|
|
94
|
+
const { container } = renderTabs();
|
|
95
|
+
expect(container.querySelectorAll(".tab-content")).toHaveLength(1);
|
|
96
|
+
|
|
97
|
+
// collection1 has no exception, so its panel starts collapsed.
|
|
98
|
+
const collection1 = panelByTitle(container, "collection1");
|
|
99
|
+
expect(collection1).toBeInTheDocument();
|
|
100
|
+
expect(collection1.querySelector(".panel-heading")).toHaveAttribute(
|
|
101
|
+
"aria-expanded",
|
|
102
|
+
"false"
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// collection2 contains a timestamp with an exception, so its panel starts
|
|
106
|
+
// expanded.
|
|
107
|
+
const collection2 = panelByTitle(container, "collection2");
|
|
108
|
+
expect(collection2).toBeInTheDocument();
|
|
109
|
+
expect(collection2.querySelector(".panel-heading")).toHaveAttribute(
|
|
110
|
+
"aria-expanded",
|
|
111
|
+
"true"
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("renders timestamps", () => {
|
|
116
|
+
const { container } = renderTabs();
|
|
117
|
+
// Each timestamp renders as a panel headed by its start time.
|
|
118
|
+
expect(
|
|
119
|
+
panelByTitle(container, "start_time_string_1")
|
|
120
|
+
).toBeInTheDocument();
|
|
121
|
+
expect(
|
|
122
|
+
panelByTitle(container, "start_time_string_2")
|
|
123
|
+
).toBeInTheDocument();
|
|
124
|
+
// Both timestamps render their duration.
|
|
125
|
+
expect(screen.getAllByText("Duration: 0 seconds")).toHaveLength(2);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("behavior", () => {
|
|
130
|
+
it("switches tabs", async () => {
|
|
131
|
+
const user = userEvent.setup();
|
|
132
|
+
const { container } = renderTabs();
|
|
133
|
+
const tabLinks = container.querySelectorAll("ul.nav-tabs li a");
|
|
134
|
+
|
|
135
|
+
await user.click(tabLinks[1]);
|
|
136
|
+
|
|
137
|
+
expect(goToTab).toHaveBeenCalledTimes(1);
|
|
138
|
+
expect(goToTab).toHaveBeenCalledWith("test_service_2");
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
|
|
5
|
+
import DiagnosticsServiceType from "../../../src/components/DiagnosticsServiceType";
|
|
6
|
+
|
|
7
|
+
describe("DiagnosticsServiceType", () => {
|
|
8
|
+
const ts1 = {
|
|
9
|
+
service: "test_service_1",
|
|
10
|
+
id: "1",
|
|
11
|
+
start: "start_time_string_1",
|
|
12
|
+
duration: "0",
|
|
13
|
+
collection_name: "collection1",
|
|
14
|
+
};
|
|
15
|
+
const ts2 = {
|
|
16
|
+
service: "test_service_2",
|
|
17
|
+
id: "2",
|
|
18
|
+
start: "start_time_string_2",
|
|
19
|
+
duration: "0",
|
|
20
|
+
collection_name: "collection2",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const services: any = {
|
|
24
|
+
test_service_1: { collection1: [ts1] },
|
|
25
|
+
test_service_2: { collection2: [ts2] },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
it("renders service tabs", () => {
|
|
29
|
+
const { container } = render(
|
|
30
|
+
<DiagnosticsServiceType type="monitor" services={services} />
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
expect(container.firstChild).toHaveClass("config");
|
|
34
|
+
expect(container.firstChild).toHaveClass("services");
|
|
35
|
+
|
|
36
|
+
// The service tabs are rendered, one per service, defaulting to the first.
|
|
37
|
+
const tabs = container.querySelectorAll("ul.nav-tabs li");
|
|
38
|
+
expect(tabs).toHaveLength(2);
|
|
39
|
+
expect(tabs[0]).toHaveTextContent("Test_service_1");
|
|
40
|
+
expect(tabs[0]).toHaveClass("active");
|
|
41
|
+
expect(tabs[1]).toHaveTextContent("Test_service_2");
|
|
42
|
+
expect(tabs[1]).not.toHaveClass("active");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("switches tabs", async () => {
|
|
46
|
+
const user = userEvent.setup();
|
|
47
|
+
const { container } = render(
|
|
48
|
+
<DiagnosticsServiceType type="monitor" services={services} />
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Starts on the first service tab.
|
|
52
|
+
let tabs = container.querySelectorAll("ul.nav-tabs li");
|
|
53
|
+
expect(tabs[0]).toHaveClass("active");
|
|
54
|
+
expect(tabs[1]).not.toHaveClass("active");
|
|
55
|
+
|
|
56
|
+
// Clicking the second service tab selects it (goToTab updates the state that
|
|
57
|
+
// drives which tab is active).
|
|
58
|
+
await user.click(container.querySelectorAll("ul.nav-tabs li a")[1]);
|
|
59
|
+
|
|
60
|
+
tabs = container.querySelectorAll("ul.nav-tabs li");
|
|
61
|
+
expect(tabs[0]).not.toHaveClass("active");
|
|
62
|
+
expect(tabs[1]).toHaveClass("active");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("displays a message if there are no services", () => {
|
|
66
|
+
const { container } = render(
|
|
67
|
+
<DiagnosticsServiceType type="monitor" services={null} />
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
expect(container.querySelector("ul.nav-tabs")).toBeNull();
|
|
71
|
+
expect(
|
|
72
|
+
screen.getByText("There are currently no monitor services.")
|
|
73
|
+
).toBeInTheDocument();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
|
|
5
|
+
import buildStore from "../../../src/store";
|
|
6
|
+
|
|
7
|
+
// The active tab renders DiagnosticsServiceType, which recurses into the service
|
|
8
|
+
// tab machinery. The legacy test only asserted the `type`/`services` props it
|
|
9
|
+
// received, so mock it to a marker that echoes those.
|
|
10
|
+
jest.mock("../../../src/components/DiagnosticsServiceType", () => ({
|
|
11
|
+
__esModule: true,
|
|
12
|
+
default: (props: any) => (
|
|
13
|
+
<div
|
|
14
|
+
data-testid="service-type"
|
|
15
|
+
data-type={props.type}
|
|
16
|
+
data-has-services={String(props.services !== undefined)}
|
|
17
|
+
/>
|
|
18
|
+
),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
import { DiagnosticsTabContainer } from "../../../src/components/DiagnosticsTabContainer";
|
|
22
|
+
|
|
23
|
+
describe("DiagnosticsTabContainer", () => {
|
|
24
|
+
let goToTab: jest.Mock;
|
|
25
|
+
let fetchDiagnostics: jest.Mock;
|
|
26
|
+
|
|
27
|
+
const ts1 = {
|
|
28
|
+
service: "test_service",
|
|
29
|
+
id: "1",
|
|
30
|
+
start: "start_time_string",
|
|
31
|
+
duration: "0",
|
|
32
|
+
collection_name: "collection1",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const diagnostics = {
|
|
36
|
+
monitor: [{ test_service_1: [{ collection1: [ts1] }] }],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
goToTab = jest.fn();
|
|
41
|
+
fetchDiagnostics = jest.fn();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const element = (overrides = {}) => {
|
|
45
|
+
const props = {
|
|
46
|
+
goToTab,
|
|
47
|
+
tab: "coverage_provider",
|
|
48
|
+
store: buildStore(),
|
|
49
|
+
fetchDiagnostics,
|
|
50
|
+
diagnostics: { coverage_provider: [] },
|
|
51
|
+
isLoaded: true,
|
|
52
|
+
...overrides,
|
|
53
|
+
};
|
|
54
|
+
return <DiagnosticsTabContainer {...props} />;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const renderContainer = (overrides = {}) => render(element(overrides));
|
|
58
|
+
|
|
59
|
+
describe("rendering", () => {
|
|
60
|
+
it("renders a tab container", () => {
|
|
61
|
+
const { container } = renderContainer();
|
|
62
|
+
expect(container.firstChild).toHaveClass("tab-container");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("renders tabs and defaults to showing the Coverage Providers tab", () => {
|
|
66
|
+
const { container } = renderContainer();
|
|
67
|
+
const tabs = container.querySelectorAll("ul.nav-tabs li");
|
|
68
|
+
expect(tabs).toHaveLength(4);
|
|
69
|
+
|
|
70
|
+
expect(tabs[0]).toHaveTextContent("Coverage Providers");
|
|
71
|
+
expect(tabs[0]).toHaveClass("active");
|
|
72
|
+
|
|
73
|
+
expect(tabs[1]).toHaveTextContent("Monitors");
|
|
74
|
+
expect(tabs[1]).not.toHaveClass("active");
|
|
75
|
+
|
|
76
|
+
expect(tabs[2]).toHaveTextContent("Scripts");
|
|
77
|
+
expect(tabs[2]).not.toHaveClass("active");
|
|
78
|
+
|
|
79
|
+
expect(tabs[3]).toHaveTextContent("Other");
|
|
80
|
+
expect(tabs[3]).not.toHaveClass("active");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("renders tab content", () => {
|
|
84
|
+
renderContainer({ diagnostics });
|
|
85
|
+
const serviceTypes = screen.getAllByTestId("service-type");
|
|
86
|
+
expect(serviceTypes).toHaveLength(4);
|
|
87
|
+
|
|
88
|
+
expect(serviceTypes[0]).toHaveAttribute("data-type", "coverage_provider");
|
|
89
|
+
expect(serviceTypes[0]).toHaveAttribute("data-has-services", "false");
|
|
90
|
+
|
|
91
|
+
expect(serviceTypes[1]).toHaveAttribute("data-type", "monitor");
|
|
92
|
+
expect(serviceTypes[1]).toHaveAttribute("data-has-services", "true");
|
|
93
|
+
|
|
94
|
+
expect(serviceTypes[2]).toHaveAttribute("data-type", "script");
|
|
95
|
+
expect(serviceTypes[2]).toHaveAttribute("data-has-services", "false");
|
|
96
|
+
|
|
97
|
+
expect(serviceTypes[3]).toHaveAttribute("data-type", "other");
|
|
98
|
+
expect(serviceTypes[3]).toHaveAttribute("data-has-services", "false");
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("behavior", () => {
|
|
103
|
+
it("calls fetchDiagnostics on mount", () => {
|
|
104
|
+
renderContainer();
|
|
105
|
+
expect(fetchDiagnostics).toHaveBeenCalled();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("shows the loading indicator", () => {
|
|
109
|
+
const { container, rerender } = renderContainer();
|
|
110
|
+
expect(container.querySelectorAll(".loading")).toHaveLength(0);
|
|
111
|
+
rerender(element({ isLoaded: false }));
|
|
112
|
+
expect(container.querySelectorAll(".loading")).toHaveLength(4);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("shows an error message", () => {
|
|
116
|
+
const { container, rerender } = renderContainer();
|
|
117
|
+
expect(container.querySelectorAll(".alert-danger")).toHaveLength(0);
|
|
118
|
+
rerender(
|
|
119
|
+
element({
|
|
120
|
+
fetchError: { status: 401, response: "error fetching diagnostics" },
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
expect(container.querySelectorAll(".alert-danger")).toHaveLength(4);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("calls goToTab when a tab is clicked", async () => {
|
|
127
|
+
const user = userEvent.setup();
|
|
128
|
+
renderContainer({ diagnostics });
|
|
129
|
+
await user.click(screen.getByRole("link", { name: "Monitors" }));
|
|
130
|
+
expect(goToTab).toHaveBeenCalledTimes(1);
|
|
131
|
+
expect(goToTab).toHaveBeenCalledWith("monitor");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("switches tabs when the tab prop changes", () => {
|
|
135
|
+
const { container, rerender } = renderContainer();
|
|
136
|
+
let tabs = container.querySelectorAll("ul.nav-tabs li");
|
|
137
|
+
expect(tabs[0]).toHaveClass("active");
|
|
138
|
+
expect(tabs[1]).not.toHaveClass("active");
|
|
139
|
+
|
|
140
|
+
rerender(element({ tab: "monitor" }));
|
|
141
|
+
|
|
142
|
+
tabs = container.querySelectorAll("ul.nav-tabs li");
|
|
143
|
+
expect(tabs[0]).not.toHaveClass("active");
|
|
144
|
+
expect(tabs[1]).toHaveClass("active");
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { installFormDataShim } from "../testUtils/formDataShim";
|
|
3
|
+
import { fireEvent, screen, waitFor } from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
3
5
|
import { DiscoveryServices } from "../../../src/components/DiscoveryServices";
|
|
6
|
+
import DiscoveryServicesConnected from "../../../src/components/DiscoveryServices";
|
|
4
7
|
import renderWithContext from "../testUtils/renderWithContext";
|
|
8
|
+
import { renderWithProviders } from "../testUtils/withProviders";
|
|
9
|
+
import buildStore from "../../../src/store";
|
|
5
10
|
import {
|
|
6
11
|
ConfigurationSettings,
|
|
7
12
|
DiscoveryServicesData,
|
|
8
13
|
} from "../../../src/interfaces";
|
|
9
14
|
import { defaultFeatureFlags } from "../../../src/utils/featureFlags";
|
|
10
15
|
|
|
11
|
-
// NB: This adds tests to the already existing tests in:
|
|
12
|
-
// - `src/components/__tests__/DiscoveryServices-test.tsx`.
|
|
13
|
-
//
|
|
14
|
-
// Those tests should eventually be migrated here and
|
|
15
|
-
// adapted to the Jest/React Testing Library paradigm.
|
|
16
|
-
|
|
17
16
|
describe("DiscoveryServices - registered library disclosure", () => {
|
|
18
17
|
// ── Shared fixtures ───────────────────────────────────────────────────────
|
|
19
18
|
|
|
@@ -543,3 +542,151 @@ describe("DiscoveryServices - registered library disclosure", () => {
|
|
|
543
542
|
expect(container.querySelectorAll(".associated-items")).toHaveLength(0);
|
|
544
543
|
});
|
|
545
544
|
});
|
|
545
|
+
|
|
546
|
+
// These exercise behavior the disclosure tests above do not: the connect()
|
|
547
|
+
// wiring (mapStateToProps / mapDispatchToProps, which only run when the CONNECTED
|
|
548
|
+
// default export mounts) and the getChildContext().registerLibrary closure
|
|
549
|
+
// (which only runs when a library is registered from the edit form).
|
|
550
|
+
|
|
551
|
+
describe("DiscoveryServices - connected wiring and registration", () => {
|
|
552
|
+
const allLibraries = [
|
|
553
|
+
{ short_name: "nypl", name: "NYPL", uuid: "uuid-nypl" },
|
|
554
|
+
];
|
|
555
|
+
|
|
556
|
+
const sysAdminConfig: Partial<ConfigurationSettings> = {
|
|
557
|
+
csrfToken: "",
|
|
558
|
+
featureFlags: defaultFeatureFlags,
|
|
559
|
+
roles: [{ role: "system" }],
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
// A loaded `libraries` slice persists through the on-mount discovery fetches
|
|
563
|
+
// (the libraries reducer ignores those actions), so mapStateToProps takes its
|
|
564
|
+
// `data.allLibraries` branch on every render.
|
|
565
|
+
const loadedLibrariesState = {
|
|
566
|
+
data: { libraries: allLibraries },
|
|
567
|
+
isFetching: false,
|
|
568
|
+
isEditing: false,
|
|
569
|
+
fetchError: null,
|
|
570
|
+
formError: null,
|
|
571
|
+
isLoaded: true,
|
|
572
|
+
responseBody: null,
|
|
573
|
+
successMessage: null,
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Stub `fetch` so the panel's on-mount fetches resolve with `body`. A fresh
|
|
578
|
+
* Response per call avoids "body already used" when several fetches run.
|
|
579
|
+
*/
|
|
580
|
+
const stubFetch = (body: unknown) =>
|
|
581
|
+
jest.spyOn(globalThis, "fetch").mockImplementation(
|
|
582
|
+
async () =>
|
|
583
|
+
new Response(JSON.stringify(body), {
|
|
584
|
+
status: 200,
|
|
585
|
+
headers: { "Content-Type": "application/json" },
|
|
586
|
+
})
|
|
587
|
+
);
|
|
588
|
+
|
|
589
|
+
afterEach(() => {
|
|
590
|
+
jest.restoreAllMocks();
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
describe("connected default export", () => {
|
|
594
|
+
it("wires mapStateToProps/mapDispatchToProps: fetches on mount and renders the list", async () => {
|
|
595
|
+
const listData = {
|
|
596
|
+
discovery_services: [
|
|
597
|
+
{ id: 2, protocol: "test protocol", name: "Test Discovery" },
|
|
598
|
+
],
|
|
599
|
+
protocols: [{ name: "test protocol", label: "TP", settings: [] }],
|
|
600
|
+
};
|
|
601
|
+
stubFetch(listData);
|
|
602
|
+
// A fresh store, preloaded with a loaded libraries slice, isolates this
|
|
603
|
+
// test and exercises mapStateToProps' allLibraries branch.
|
|
604
|
+
const store = buildStore({
|
|
605
|
+
editor: { libraries: loadedLibrariesState },
|
|
606
|
+
} as any);
|
|
607
|
+
|
|
608
|
+
const { container } = renderWithProviders(
|
|
609
|
+
<DiscoveryServicesConnected csrfToken="token" />,
|
|
610
|
+
{ reduxProviderProps: { store } }
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
// The list appears once the connected component's on-mount fetch resolves
|
|
614
|
+
// and mapStateToProps feeds the fetched data back in as props.
|
|
615
|
+
expect(
|
|
616
|
+
await screen.findByRole("heading", { level: 3, name: "Test Discovery" })
|
|
617
|
+
).toBeInTheDocument();
|
|
618
|
+
|
|
619
|
+
const editLink = container.querySelector("a.edit-item");
|
|
620
|
+
expect(editLink).not.toBeNull();
|
|
621
|
+
expect(editLink.getAttribute("href")).toBe(
|
|
622
|
+
"/admin/web/config/discovery/edit/2"
|
|
623
|
+
);
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
describe("registerLibrary child context (edit mode)", () => {
|
|
628
|
+
// The reusable-components `Form` builds `new FormData(formElement)` on
|
|
629
|
+
// submit, which the unit jsdom env's undici FormData rejects; install the
|
|
630
|
+
// shared shim that reads the form's successful controls.
|
|
631
|
+
installFormDataShim();
|
|
632
|
+
const renderEditMode = (
|
|
633
|
+
registerLibrary: jest.Mock,
|
|
634
|
+
fetchLibraryRegistrations: jest.Mock
|
|
635
|
+
) =>
|
|
636
|
+
renderWithContext(
|
|
637
|
+
<DiscoveryServices
|
|
638
|
+
data={
|
|
639
|
+
{
|
|
640
|
+
discovery_services: [
|
|
641
|
+
{ id: "2", protocol: "test protocol", name: "Test Discovery" },
|
|
642
|
+
],
|
|
643
|
+
protocols: [
|
|
644
|
+
{
|
|
645
|
+
name: "test protocol",
|
|
646
|
+
supports_registration: true,
|
|
647
|
+
settings: [],
|
|
648
|
+
library_settings: [],
|
|
649
|
+
},
|
|
650
|
+
],
|
|
651
|
+
allLibraries,
|
|
652
|
+
} as any
|
|
653
|
+
}
|
|
654
|
+
editOrCreate="edit"
|
|
655
|
+
identifier="2"
|
|
656
|
+
fetchData={jest.fn()}
|
|
657
|
+
editItem={jest.fn().mockResolvedValue(undefined)}
|
|
658
|
+
deleteItem={jest.fn().mockResolvedValue(undefined)}
|
|
659
|
+
registerLibrary={registerLibrary}
|
|
660
|
+
fetchLibraryRegistrations={fetchLibraryRegistrations}
|
|
661
|
+
csrfToken="token"
|
|
662
|
+
isFetching={false}
|
|
663
|
+
/>,
|
|
664
|
+
sysAdminConfig
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
it("fetches library registrations on mount, and registers a library (building the right FormData) and refetches on click", async () => {
|
|
668
|
+
const user = userEvent.setup();
|
|
669
|
+
const registerLibrary = jest.fn().mockResolvedValue(undefined);
|
|
670
|
+
const fetchLibraryRegistrations = jest.fn().mockResolvedValue(undefined);
|
|
671
|
+
renderEditMode(registerLibrary, fetchLibraryRegistrations);
|
|
672
|
+
|
|
673
|
+
// getChildContext supplies registerLibrary to the edit form; the edit form
|
|
674
|
+
// surfaces a "Register" button for each library.
|
|
675
|
+
expect(fetchLibraryRegistrations).toHaveBeenCalledTimes(1);
|
|
676
|
+
expect(registerLibrary).not.toHaveBeenCalled();
|
|
677
|
+
|
|
678
|
+
await user.click(screen.getByRole("button", { name: "Register" }));
|
|
679
|
+
|
|
680
|
+
expect(registerLibrary).toHaveBeenCalledTimes(1);
|
|
681
|
+
const formData = registerLibrary.mock.calls[0][0];
|
|
682
|
+
expect(formData.get("library_short_name")).toBe("nypl");
|
|
683
|
+
expect(formData.get("integration_id")).toBe("2");
|
|
684
|
+
expect(formData.get("registration_stage")).toBe("testing");
|
|
685
|
+
|
|
686
|
+
// After a successful registration, the child context refetches.
|
|
687
|
+
await waitFor(() =>
|
|
688
|
+
expect(fetchLibraryRegistrations).toHaveBeenCalledTimes(2)
|
|
689
|
+
);
|
|
690
|
+
});
|
|
691
|
+
});
|
|
692
|
+
});
|