@thepalaceproject/circulation-admin 1.43.0-post.8 → 1.43.0-post.9
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/AdvancedSearchBooleanFilter.test.tsx +231 -0
- 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/CatalogServices.test.tsx +63 -0
- package/tests/jest/components/CirculationEventsDownloadForm.test.tsx +41 -0
- package/tests/jest/components/CustomListSearch.test.tsx +275 -0
- package/tests/jest/components/CustomListsSidebar.test.tsx +251 -0
- package/tests/jest/components/DiagnosticsTabContainer.test.tsx +147 -0
- package/tests/jest/components/ErrorMessage.test.tsx +97 -0
- package/tests/jest/components/Footer.test.tsx +141 -0
- package/tests/jest/components/LanguageField.test.tsx +40 -0
- package/tests/jest/components/LoadButton.test.tsx +49 -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 +59 -0
- package/tests/jest/components/SelfTestResult.test.tsx +164 -0
- package/tests/jest/components/SelfTestsCategory.test.tsx +200 -0
- package/tests/jest/components/ServiceWithRegistrationsEditForm.test.tsx +110 -0
- package/tests/jest/components/TabContainer.test.tsx +68 -0
- package/tests/jest/components/Timestamp.test.tsx +74 -0
- package/tests/jest/components/WithEditButton.test.tsx +55 -0
- package/tests/jest/components/WithRemoveButton.test.tsx +95 -0
- package/tests/jest/index.test.tsx +50 -0
- package/tests/jest/components/AdvancedSearchBuilder.test.tsx +0 -38
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { screen } from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
import { renderWithProviders } from "../testUtils/withProviders";
|
|
5
|
+
import buildStore from "../../../src/store";
|
|
6
|
+
|
|
7
|
+
// `SelfTests` is a connected component that fetches self-test results on mount.
|
|
8
|
+
// It is incidental to what `SelfTestsCategory` itself does (build one panel per
|
|
9
|
+
// item, color-code them, decide `openByDefault`, and compute `sortByCollection`),
|
|
10
|
+
// so mock it to a marker that exposes the props the category passes down.
|
|
11
|
+
jest.mock("../../../src/components/SelfTests", () => ({
|
|
12
|
+
__esModule: true,
|
|
13
|
+
default: (props: any) => (
|
|
14
|
+
<div
|
|
15
|
+
data-testid="self-tests"
|
|
16
|
+
data-type={props.type}
|
|
17
|
+
data-item-id={props.item?.id}
|
|
18
|
+
data-item-name={props.item?.name}
|
|
19
|
+
data-sort-by-collection={String(props.sortByCollection)}
|
|
20
|
+
/>
|
|
21
|
+
),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
import SelfTestsCategory from "../../../src/components/SelfTestsCategory";
|
|
25
|
+
|
|
26
|
+
const initialSetupResult = {
|
|
27
|
+
duration: 0.000119,
|
|
28
|
+
end: "2018-08-07T19:34:54Z",
|
|
29
|
+
exception: null,
|
|
30
|
+
name: "Initial setup.",
|
|
31
|
+
result: null,
|
|
32
|
+
start: "2018-08-07T19:34:54Z",
|
|
33
|
+
success: true,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const collections: any[] = [
|
|
37
|
+
{
|
|
38
|
+
id: 1,
|
|
39
|
+
name: "collection with success",
|
|
40
|
+
protocol: "protocol",
|
|
41
|
+
self_test_results: {
|
|
42
|
+
duration: 1.75,
|
|
43
|
+
start: "2018-08-07T19:34:54Z",
|
|
44
|
+
end: "2018-08-07T19:34:55Z",
|
|
45
|
+
results: [initialSetupResult],
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 2,
|
|
50
|
+
name: "collection with failure",
|
|
51
|
+
protocol: "protocol",
|
|
52
|
+
self_test_results: {
|
|
53
|
+
duration: 1.75,
|
|
54
|
+
start: "2018-08-07T19:34:54Z",
|
|
55
|
+
end: "2018-08-07T19:34:55Z",
|
|
56
|
+
results: [
|
|
57
|
+
initialSetupResult,
|
|
58
|
+
{
|
|
59
|
+
duration: 0,
|
|
60
|
+
end: "2018-08-07T19:34:55Z",
|
|
61
|
+
exception: {
|
|
62
|
+
class: "IntegrationException",
|
|
63
|
+
debug_message:
|
|
64
|
+
"Add the collection to a library that has a patron authentication service.",
|
|
65
|
+
message: "Collection is not associated with any libraries.",
|
|
66
|
+
},
|
|
67
|
+
name: "Acquiring test patron credentials.",
|
|
68
|
+
result: null,
|
|
69
|
+
start: "2018-08-07T19:34:55Z",
|
|
70
|
+
success: false,
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 3,
|
|
77
|
+
name: "collection with no results",
|
|
78
|
+
protocol: "protocol",
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
const renderCategory = (items: any[] = collections) =>
|
|
83
|
+
renderWithProviders(
|
|
84
|
+
<SelfTestsCategory
|
|
85
|
+
type="collection"
|
|
86
|
+
linkName="collections"
|
|
87
|
+
csrfToken="token"
|
|
88
|
+
items={items}
|
|
89
|
+
store={buildStore()}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
describe("SelfTestsCategory", () => {
|
|
94
|
+
it("renders a list of items", () => {
|
|
95
|
+
const { container } = renderCategory();
|
|
96
|
+
|
|
97
|
+
const root = container.querySelector(".self-tests-category");
|
|
98
|
+
expect(root).toBeInTheDocument();
|
|
99
|
+
expect(root).toHaveClass("has-additional-content");
|
|
100
|
+
|
|
101
|
+
const titles = Array.from(
|
|
102
|
+
container.querySelectorAll("ul .panel-title")
|
|
103
|
+
).map((el) => el.textContent);
|
|
104
|
+
expect(titles).toStrictEqual([
|
|
105
|
+
"collection with success",
|
|
106
|
+
"collection with failure",
|
|
107
|
+
"collection with no results",
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
// One SelfTests per item, each carrying the category's type and its item.
|
|
111
|
+
const selfTests = screen.getAllByTestId("self-tests");
|
|
112
|
+
expect(selfTests).toHaveLength(3);
|
|
113
|
+
selfTests.forEach((el, idx) => {
|
|
114
|
+
expect(el).toHaveAttribute("data-type", "collection");
|
|
115
|
+
expect(el).toHaveAttribute("data-item-id", String(collections[idx].id));
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("color-codes", () => {
|
|
120
|
+
const { container } = renderCategory();
|
|
121
|
+
const panels = container.querySelectorAll(".panel");
|
|
122
|
+
expect(panels[0]).toHaveClass("panel-success");
|
|
123
|
+
expect(panels[1]).toHaveClass("panel-danger");
|
|
124
|
+
expect(panels[2]).toHaveClass("panel-default");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("opens the panel by default if there's only one", () => {
|
|
128
|
+
// With multiple items, none are open by default.
|
|
129
|
+
const multi = renderCategory();
|
|
130
|
+
expect(
|
|
131
|
+
Array.from(multi.container.querySelectorAll(".panel-heading")).some(
|
|
132
|
+
(h) => h.getAttribute("aria-expanded") === "true"
|
|
133
|
+
)
|
|
134
|
+
).toBe(false);
|
|
135
|
+
multi.unmount();
|
|
136
|
+
|
|
137
|
+
// A fresh render with a single item opens that panel by default.
|
|
138
|
+
// (`openByDefault` seeds the panel's initial state, so a new mount is needed.)
|
|
139
|
+
const single = renderCategory([collections[0]]);
|
|
140
|
+
const singleHeaders = single.container.querySelectorAll(".panel-heading");
|
|
141
|
+
expect(singleHeaders).toHaveLength(1);
|
|
142
|
+
expect(singleHeaders[0]).toHaveAttribute("aria-expanded", "true");
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("passes the sortByCollection prop to SelfTests", () => {
|
|
146
|
+
const { rerender } = renderCategory();
|
|
147
|
+
expect(screen.getAllByTestId("self-tests")[0]).toHaveAttribute(
|
|
148
|
+
"data-sort-by-collection",
|
|
149
|
+
"false"
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const withCollection = {
|
|
153
|
+
id: 99,
|
|
154
|
+
name: "collection with sorting",
|
|
155
|
+
protocol: "protocol",
|
|
156
|
+
self_test_results: {
|
|
157
|
+
duration: 1.75,
|
|
158
|
+
start: "2018-08-07T19:34:54Z",
|
|
159
|
+
end: "2018-08-07T19:34:55Z",
|
|
160
|
+
results: [{ ...initialSetupResult, collection: "sort!" }],
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
rerender(
|
|
165
|
+
<SelfTestsCategory
|
|
166
|
+
type="collection"
|
|
167
|
+
linkName="collections"
|
|
168
|
+
csrfToken="token"
|
|
169
|
+
items={[withCollection, ...collections]}
|
|
170
|
+
store={buildStore()}
|
|
171
|
+
/>
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
expect(screen.getAllByTestId("self-tests")[0]).toHaveAttribute(
|
|
175
|
+
"data-sort-by-collection",
|
|
176
|
+
"true"
|
|
177
|
+
);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("handles unnamed items", () => {
|
|
181
|
+
const nameless: any = {
|
|
182
|
+
id: 1,
|
|
183
|
+
name: null,
|
|
184
|
+
protocol: "protocol",
|
|
185
|
+
self_test_results: {
|
|
186
|
+
duration: 1.75,
|
|
187
|
+
start: "2018-08-07T19:34:54Z",
|
|
188
|
+
end: "2018-08-07T19:34:55Z",
|
|
189
|
+
results: [initialSetupResult],
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
const { container } = renderCategory([nameless]);
|
|
193
|
+
|
|
194
|
+
expect(screen.getByText("Unnamed collection")).toHaveClass("panel-title");
|
|
195
|
+
// Panel id becomes `${name-without-spaces}-${id}`; its body id appends "-panel".
|
|
196
|
+
expect(
|
|
197
|
+
container.querySelector("#Unnamedcollection-1-panel")
|
|
198
|
+
).toBeInTheDocument();
|
|
199
|
+
});
|
|
200
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
import { DiscoveryServicesData } from "../../../src/interfaces";
|
|
5
|
+
import ServiceWithRegistrationsEditForm from "../../../src/components/ServiceWithRegistrationsEditForm";
|
|
6
|
+
|
|
7
|
+
// `ServiceWithRegistrationsEditForm` is generic/abstract over its data type;
|
|
8
|
+
// the legacy test subclassed it to bind the discovery-services data type.
|
|
9
|
+
class DiscoveryServiceEditForm extends ServiceWithRegistrationsEditForm<DiscoveryServicesData> {}
|
|
10
|
+
|
|
11
|
+
const serviceData = {
|
|
12
|
+
id: 1,
|
|
13
|
+
protocol: "protocol 1",
|
|
14
|
+
};
|
|
15
|
+
const protocolsData = [
|
|
16
|
+
{
|
|
17
|
+
name: "protocol 1",
|
|
18
|
+
label: "protocol 1 label",
|
|
19
|
+
supports_registration: true,
|
|
20
|
+
settings: [],
|
|
21
|
+
library_settings: [],
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
const allLibraries = [
|
|
25
|
+
{ short_name: "nypl", name: "New York Public Library" },
|
|
26
|
+
{ short_name: "bpl", name: "Brooklyn Public Library" },
|
|
27
|
+
{ short_name: "qpl", name: "Queens Public Library" },
|
|
28
|
+
];
|
|
29
|
+
const libraryRegistrationsData = [
|
|
30
|
+
{
|
|
31
|
+
id: 1,
|
|
32
|
+
libraries: [
|
|
33
|
+
{ short_name: "nypl", status: "success", stage: "production" },
|
|
34
|
+
{ short_name: "bpl", status: "failure", stage: "testing" },
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
const servicesData: any = {
|
|
39
|
+
discovery_services: [serviceData],
|
|
40
|
+
protocols: protocolsData,
|
|
41
|
+
allLibraries: allLibraries,
|
|
42
|
+
libraryRegistrations: libraryRegistrationsData,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const renderForm = (save = jest.fn()) =>
|
|
46
|
+
render(
|
|
47
|
+
<DiscoveryServiceEditForm
|
|
48
|
+
disabled={false}
|
|
49
|
+
data={servicesData}
|
|
50
|
+
item={serviceData as any}
|
|
51
|
+
save={save}
|
|
52
|
+
urlBase="url base"
|
|
53
|
+
listDataKey="discovery_services"
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
describe("ServiceWithRegistrationsEditForm", () => {
|
|
58
|
+
it("renders the underlying service edit form fields", () => {
|
|
59
|
+
renderForm();
|
|
60
|
+
|
|
61
|
+
// The base ServiceEditForm (via `super.render()`) contributes the Name text
|
|
62
|
+
// field and the Required Fields section.
|
|
63
|
+
expect(screen.getByRole("textbox")).toBeInTheDocument();
|
|
64
|
+
expect(
|
|
65
|
+
screen.getByRole("heading", { name: "Required Fields" })
|
|
66
|
+
).toBeInTheDocument();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("renders a registration section reflecting each library's status", () => {
|
|
70
|
+
renderForm();
|
|
71
|
+
|
|
72
|
+
// The registration UI only renders when the protocol supports registration
|
|
73
|
+
// and the data carries libraries — i.e. those props flowed to LibraryRegistration.
|
|
74
|
+
expect(
|
|
75
|
+
screen.getByRole("heading", { name: "Register libraries" })
|
|
76
|
+
).toBeInTheDocument();
|
|
77
|
+
|
|
78
|
+
expect(
|
|
79
|
+
screen.getByRole("link", { name: "New York Public Library" })
|
|
80
|
+
).toBeInTheDocument();
|
|
81
|
+
expect(
|
|
82
|
+
screen.getByRole("link", { name: "Brooklyn Public Library" })
|
|
83
|
+
).toBeInTheDocument();
|
|
84
|
+
expect(
|
|
85
|
+
screen.getByRole("link", { name: "Queens Public Library" })
|
|
86
|
+
).toBeInTheDocument();
|
|
87
|
+
|
|
88
|
+
// Statuses come from `data.libraryRegistrations`.
|
|
89
|
+
expect(screen.getByText("Registered")).toHaveClass("bg-success");
|
|
90
|
+
expect(screen.getByText("Registration failed")).toHaveClass("bg-danger");
|
|
91
|
+
expect(screen.getByText("Not registered")).toHaveClass("bg-warning");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("enables the per-library registration actions when not disabled", () => {
|
|
95
|
+
renderForm();
|
|
96
|
+
|
|
97
|
+
// `disabled={false}` flows through to each registration button.
|
|
98
|
+
const updateButton = screen.getByRole("button", {
|
|
99
|
+
name: "Update registration",
|
|
100
|
+
});
|
|
101
|
+
const retryButton = screen.getByRole("button", {
|
|
102
|
+
name: "Retry registration",
|
|
103
|
+
});
|
|
104
|
+
const registerButton = screen.getByRole("button", { name: "Register" });
|
|
105
|
+
|
|
106
|
+
expect(updateButton).not.toBeDisabled();
|
|
107
|
+
expect(retryButton).not.toBeDisabled();
|
|
108
|
+
expect(registerButton).not.toBeDisabled();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -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
|
+
import {
|
|
6
|
+
TabContainer,
|
|
7
|
+
TabContainerProps,
|
|
8
|
+
} from "../../../src/components/TabContainer";
|
|
9
|
+
|
|
10
|
+
describe("TabContainer", () => {
|
|
11
|
+
let handleSelectSpy: jest.Mock;
|
|
12
|
+
|
|
13
|
+
// A concrete subclass of the abstract TabContainer, mirroring the legacy test's
|
|
14
|
+
// MockTabContainer: it supplies two trivial tabs and records the selected tab
|
|
15
|
+
// key when handleSelect runs.
|
|
16
|
+
class MockTabContainer extends TabContainer<TabContainerProps> {
|
|
17
|
+
handleSelect(event) {
|
|
18
|
+
handleSelectSpy(event.target.dataset.tabkey);
|
|
19
|
+
}
|
|
20
|
+
tabs() {
|
|
21
|
+
return {
|
|
22
|
+
tab1: <div className="tab1" />,
|
|
23
|
+
tab2: <div className="tab2" />,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const renderContainer = (tab: string = null) =>
|
|
29
|
+
render(
|
|
30
|
+
<MockTabContainer tab={tab} csrfToken="token">
|
|
31
|
+
<div className="test-child">Moby Dick</div>
|
|
32
|
+
</MockTabContainer>
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
handleSelectSpy = jest.fn();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("shows nav tabs", () => {
|
|
40
|
+
const { container } = renderContainer();
|
|
41
|
+
const items = container.querySelectorAll("ul.nav-tabs li");
|
|
42
|
+
const linkTexts = Array.from(items).map((li) => li.textContent);
|
|
43
|
+
expect(linkTexts).toContain("Tab1");
|
|
44
|
+
expect(linkTexts).toContain("Tab2");
|
|
45
|
+
expect(items[0]).toHaveClass("active");
|
|
46
|
+
expect(items[1]).not.toHaveClass("active");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("shows default tab content", () => {
|
|
50
|
+
const { container } = renderContainer();
|
|
51
|
+
expect(container.querySelector("#tab1")).toHaveStyle({ display: "block" });
|
|
52
|
+
expect(container.querySelector("#tab2")).toHaveStyle({ display: "none" });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("shows tab content from tab in props", () => {
|
|
56
|
+
const { container } = renderContainer("tab2");
|
|
57
|
+
expect(container.querySelector("#tab1")).toHaveStyle({ display: "none" });
|
|
58
|
+
expect(container.querySelector("#tab2")).toHaveStyle({ display: "block" });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("uses handleSelect to navigate when tab is clicked", async () => {
|
|
62
|
+
const user = userEvent.setup();
|
|
63
|
+
renderContainer();
|
|
64
|
+
await user.click(screen.getByRole("link", { name: "Tab2" }));
|
|
65
|
+
expect(handleSelectSpy).toHaveBeenCalledTimes(1);
|
|
66
|
+
expect(handleSelectSpy).toHaveBeenCalledWith("tab2");
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
import Timestamp from "../../../src/components/Timestamp";
|
|
5
|
+
|
|
6
|
+
describe("Timestamp", () => {
|
|
7
|
+
const timestamp = {
|
|
8
|
+
service: "test_service",
|
|
9
|
+
id: "1",
|
|
10
|
+
start: "start_time_string",
|
|
11
|
+
duration: "60",
|
|
12
|
+
collection_name: "collection1",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
describe("rendering", () => {
|
|
16
|
+
it("renders a success panel that is not collapsible", () => {
|
|
17
|
+
const { container } = render(<Timestamp timestamp={timestamp} />);
|
|
18
|
+
|
|
19
|
+
expect(container.querySelector(".panel-success")).toBeInTheDocument();
|
|
20
|
+
expect(container.querySelector(".panel-danger")).not.toBeInTheDocument();
|
|
21
|
+
// A non-collapsible panel is a static panel with no toggle button.
|
|
22
|
+
expect(container.querySelector(".static-panel")).toBeInTheDocument();
|
|
23
|
+
expect(screen.queryByRole("button")).not.toBeInTheDocument();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("renders the start time", () => {
|
|
27
|
+
const { container } = render(<Timestamp timestamp={timestamp} />);
|
|
28
|
+
|
|
29
|
+
expect(container.querySelector(".panel-title")).toHaveTextContent(
|
|
30
|
+
"start_time_string"
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("renders the duration", () => {
|
|
35
|
+
const { container } = render(<Timestamp timestamp={timestamp} />);
|
|
36
|
+
|
|
37
|
+
expect(container.querySelectorAll("ul li")).toHaveLength(1);
|
|
38
|
+
expect(screen.getByText("Duration: 60 seconds")).toBeInTheDocument();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("rendering with achievements", () => {
|
|
43
|
+
it("renders achievements", () => {
|
|
44
|
+
const { container } = render(
|
|
45
|
+
<Timestamp timestamp={{ ...timestamp, achievements: "Ran a script" }} />
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
expect(container.querySelectorAll("ul li")).toHaveLength(2);
|
|
49
|
+
const achievements = container.querySelector(".well pre");
|
|
50
|
+
expect(achievements).toBeInTheDocument();
|
|
51
|
+
expect(achievements).toHaveTextContent("Ran a script");
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("rendering with exception", () => {
|
|
56
|
+
it("determines style based on whether there is an exception", () => {
|
|
57
|
+
const { container } = render(
|
|
58
|
+
<Timestamp timestamp={{ ...timestamp, exception: "Stack trace" }} />
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
expect(container.querySelector(".panel-danger")).toBeInTheDocument();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("renders an exception", () => {
|
|
65
|
+
const { container } = render(
|
|
66
|
+
<Timestamp timestamp={{ ...timestamp, exception: "Stack trace" }} />
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const exception = container.querySelector(".exception pre");
|
|
70
|
+
expect(exception).toBeInTheDocument();
|
|
71
|
+
expect(exception).toHaveTextContent("Stack trace");
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
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 WithEditButton from "../../../src/components/WithEditButton";
|
|
6
|
+
|
|
7
|
+
describe("WithEditButton", () => {
|
|
8
|
+
it("shows children", () => {
|
|
9
|
+
render(
|
|
10
|
+
<WithEditButton disabled={false} onEdit={jest.fn()}>
|
|
11
|
+
child
|
|
12
|
+
</WithEditButton>
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
expect(screen.getByText("child")).toBeInTheDocument();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("shows an edit button", () => {
|
|
19
|
+
render(
|
|
20
|
+
<WithEditButton disabled={false} onEdit={jest.fn()}>
|
|
21
|
+
child
|
|
22
|
+
</WithEditButton>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
expect(screen.getByRole("button")).toHaveClass("edit-btn");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("calls onEdit when clicked", async () => {
|
|
29
|
+
const user = userEvent.setup();
|
|
30
|
+
const onEdit = jest.fn();
|
|
31
|
+
render(
|
|
32
|
+
<WithEditButton disabled={false} onEdit={onEdit}>
|
|
33
|
+
child
|
|
34
|
+
</WithEditButton>
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
await user.click(screen.getByRole("button"));
|
|
38
|
+
|
|
39
|
+
expect(onEdit).toHaveBeenCalledTimes(1);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("does nothing when disabled", async () => {
|
|
43
|
+
const user = userEvent.setup();
|
|
44
|
+
const onEdit = jest.fn();
|
|
45
|
+
render(
|
|
46
|
+
<WithEditButton disabled={true} onEdit={onEdit}>
|
|
47
|
+
child
|
|
48
|
+
</WithEditButton>
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
await user.click(screen.getByRole("button"));
|
|
52
|
+
|
|
53
|
+
expect(onEdit).not.toHaveBeenCalled();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
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 WithRemoveButton from "../../../src/components/WithRemoveButton";
|
|
6
|
+
|
|
7
|
+
describe("WithRemoveButton", () => {
|
|
8
|
+
it("shows children", () => {
|
|
9
|
+
render(
|
|
10
|
+
<WithRemoveButton disabled={false} onRemove={jest.fn()}>
|
|
11
|
+
child
|
|
12
|
+
</WithRemoveButton>
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
expect(screen.getByText("child")).toBeInTheDocument();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("shows a remove button", () => {
|
|
19
|
+
render(
|
|
20
|
+
<WithRemoveButton disabled={false} onRemove={jest.fn()}>
|
|
21
|
+
child
|
|
22
|
+
</WithRemoveButton>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
expect(screen.getByRole("button")).toHaveClass("remove-btn");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("calls onRemove when clicked", async () => {
|
|
29
|
+
const user = userEvent.setup();
|
|
30
|
+
const onRemove = jest.fn();
|
|
31
|
+
render(
|
|
32
|
+
<WithRemoveButton disabled={false} onRemove={onRemove}>
|
|
33
|
+
child
|
|
34
|
+
</WithRemoveButton>
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
await user.click(screen.getByRole("button"));
|
|
38
|
+
|
|
39
|
+
expect(onRemove).toHaveBeenCalledTimes(1);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("does nothing when disabled", async () => {
|
|
43
|
+
const user = userEvent.setup();
|
|
44
|
+
const onRemove = jest.fn();
|
|
45
|
+
render(
|
|
46
|
+
<WithRemoveButton disabled={true} onRemove={onRemove}>
|
|
47
|
+
child
|
|
48
|
+
</WithRemoveButton>
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
await user.click(screen.getByRole("button"));
|
|
52
|
+
|
|
53
|
+
expect(onRemove).not.toHaveBeenCalled();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("calls onRemove when confirmation returns true", async () => {
|
|
57
|
+
const user = userEvent.setup();
|
|
58
|
+
const onRemove = jest.fn();
|
|
59
|
+
const confirmRemoval = jest.fn().mockReturnValue(true);
|
|
60
|
+
render(
|
|
61
|
+
<WithRemoveButton
|
|
62
|
+
disabled={false}
|
|
63
|
+
onRemove={onRemove}
|
|
64
|
+
confirmRemoval={confirmRemoval}
|
|
65
|
+
>
|
|
66
|
+
child
|
|
67
|
+
</WithRemoveButton>
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
await user.click(screen.getByRole("button"));
|
|
71
|
+
|
|
72
|
+
expect(confirmRemoval).toHaveBeenCalledTimes(1);
|
|
73
|
+
expect(onRemove).toHaveBeenCalledTimes(1);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("does not call onRemove when confirmation returns false", async () => {
|
|
77
|
+
const user = userEvent.setup();
|
|
78
|
+
const onRemove = jest.fn();
|
|
79
|
+
const confirmRemoval = jest.fn().mockReturnValue(false);
|
|
80
|
+
render(
|
|
81
|
+
<WithRemoveButton
|
|
82
|
+
disabled={false}
|
|
83
|
+
onRemove={onRemove}
|
|
84
|
+
confirmRemoval={confirmRemoval}
|
|
85
|
+
>
|
|
86
|
+
child
|
|
87
|
+
</WithRemoveButton>
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
await user.click(screen.getByRole("button"));
|
|
91
|
+
|
|
92
|
+
expect(confirmRemoval).toHaveBeenCalledTimes(1);
|
|
93
|
+
expect(onRemove).not.toHaveBeenCalled();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as ReactDOM from "react-dom";
|
|
3
|
+
import { Router } from "react-router";
|
|
4
|
+
|
|
5
|
+
import SetupPage from "../../src/components/SetupPage";
|
|
6
|
+
|
|
7
|
+
// index.tsx does `export = CirculationAdmin`.
|
|
8
|
+
const CirculationAdmin = require("../../src/index");
|
|
9
|
+
|
|
10
|
+
/** Walk a React element tree for an element of the given type — the structural
|
|
11
|
+
* analog of enzyme's `wrapper.find(Type)`, used where the tree is the app root
|
|
12
|
+
* and there is no rendered DOM to query against. */
|
|
13
|
+
function containsType(node: any, type: React.ElementType): boolean {
|
|
14
|
+
if (!node || typeof node !== "object") return false;
|
|
15
|
+
if (Array.isArray(node)) return node.some((n) => containsType(n, type));
|
|
16
|
+
if (node.type === type) return true;
|
|
17
|
+
return containsType(node.props && node.props.children, type);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("CirculationAdmin", () => {
|
|
21
|
+
let renderSpy: jest.SpyInstance;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
// Capture what the bootstrap hands to ReactDOM.render without letting it
|
|
25
|
+
// paint into the jsdom document. We assert on the captured element tree
|
|
26
|
+
// (the structural analog of the original enzyme `wrapper.find(...)`); what
|
|
27
|
+
// SetupPage and the routed pages render is covered by their own tests.
|
|
28
|
+
renderSpy = jest
|
|
29
|
+
.spyOn(ReactDOM, "render")
|
|
30
|
+
.mockImplementation(() => undefined);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
jest.restoreAllMocks();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("renders the setup page when setting up", () => {
|
|
38
|
+
new CirculationAdmin({ settingUp: true, csrfToken: "token" });
|
|
39
|
+
|
|
40
|
+
expect(renderSpy).toHaveBeenCalledTimes(1);
|
|
41
|
+
expect(containsType(renderSpy.mock.calls[0][0], SetupPage)).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("renders the router otherwise", () => {
|
|
45
|
+
new CirculationAdmin({ csrfToken: "token" });
|
|
46
|
+
|
|
47
|
+
expect(renderSpy).toHaveBeenCalledTimes(1);
|
|
48
|
+
expect(containsType(renderSpy.mock.calls[0][0], Router)).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { render, screen } from "@testing-library/react";
|
|
3
|
-
import userEvent from "@testing-library/user-event";
|
|
4
|
-
import AdvancedSearchBuilder from "../../../src/components/AdvancedSearchBuilder";
|
|
5
|
-
|
|
6
|
-
describe("AdvancedSearchBuilder", () => {
|
|
7
|
-
it("renders a placeholder and help text when the publication date filter is selected", async () => {
|
|
8
|
-
userEvent.setup();
|
|
9
|
-
|
|
10
|
-
const query = {
|
|
11
|
-
id: "0",
|
|
12
|
-
key: "genre",
|
|
13
|
-
value: "Horror",
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
render(
|
|
17
|
-
<AdvancedSearchBuilder
|
|
18
|
-
isOwner={true}
|
|
19
|
-
name="search"
|
|
20
|
-
query={query}
|
|
21
|
-
selectedQueryId="0"
|
|
22
|
-
/>
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
await userEvent.selectOptions(
|
|
26
|
-
screen.getByRole("combobox", { name: "filter field key" }),
|
|
27
|
-
screen.getByRole("option", { name: "Publication Date" })
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
const filterValueField = screen.getByRole("textbox", {
|
|
31
|
-
name: "filter value",
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
expect(filterValueField).toHaveAttribute("placeholder", "YYYY-MM-DD");
|
|
35
|
-
expect(filterValueField).toHaveAccessibleDescription(/publication date/i);
|
|
36
|
-
expect(filterValueField).toHaveAccessibleDescription(/YYYY-MM-DD/i);
|
|
37
|
-
});
|
|
38
|
-
});
|