@thepalaceproject/circulation-admin 1.42.2 → 1.43.0-post.10
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/.node-version +1 -1
- package/.nvmrc +1 -1
- package/CLAUDE.md +17 -8
- package/README.md +7 -7
- package/dist/circulation-admin.js +1 -1
- package/eslint.config.mjs +9 -1
- package/jest.config.js +56 -9
- package/package.json +10 -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/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/BookDetails.test.tsx +133 -0
- package/tests/jest/components/BookDetailsContainer.test.tsx +116 -0
- package/tests/jest/components/CatalogServices.test.tsx +63 -0
- package/tests/jest/components/CirculationEventsDownloadForm.test.tsx +41 -0
- package/tests/jest/components/ConfigPage.test.tsx +58 -0
- package/tests/jest/components/CustomListPage.test.tsx +57 -0
- package/tests/jest/components/CustomListSearch.test.tsx +275 -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/DiagnosticsTabContainer.test.tsx +147 -0
- package/tests/jest/components/EditableConfigList.test.tsx +8 -2
- 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/InventoryReportRequestModal.test.tsx +16 -42
- package/tests/jest/components/JsonField.test.tsx +20 -0
- package/tests/jest/components/LaneEditor.test.tsx +2 -6
- package/tests/jest/components/LanePage.test.tsx +68 -0
- package/tests/jest/components/LanguageField.test.tsx +40 -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 +59 -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 +36 -0
- package/tests/jest/components/SaveButton.test.tsx +47 -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/SetupPage.test.tsx +41 -0
- package/tests/jest/components/SingleStatListItem.test.tsx +62 -0
- package/tests/jest/components/Stats.test.tsx +4 -8
- package/tests/jest/components/TabContainer.test.tsx +68 -0
- package/tests/jest/components/Timestamp.test.tsx +74 -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/index.test.tsx +50 -0
- package/tests/jest/legacy-setup.ts +13 -0
- package/tests/jest/legacyJsdomEnvironment.js +49 -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/components/AdvancedSearchBuilder.test.tsx +0 -38
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { render, screen, within } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
import CustomListSearch from "../../../src/components/CustomListSearch";
|
|
5
|
+
|
|
6
|
+
describe("CustomListSearch", () => {
|
|
7
|
+
const library = {
|
|
8
|
+
uuid: "uuid",
|
|
9
|
+
name: "name",
|
|
10
|
+
short_name: "short_name",
|
|
11
|
+
settings: {
|
|
12
|
+
large_collections: ["eng", "fre", "spa"],
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const entryPoints = ["All", "Book", "Audio"];
|
|
17
|
+
|
|
18
|
+
const searchParams = {
|
|
19
|
+
entryPoint: "all",
|
|
20
|
+
terms: "foo bar",
|
|
21
|
+
sort: "title",
|
|
22
|
+
language: "English",
|
|
23
|
+
advanced: {
|
|
24
|
+
include: {
|
|
25
|
+
query: null,
|
|
26
|
+
selectedQueryId: null,
|
|
27
|
+
clearFilters: null,
|
|
28
|
+
},
|
|
29
|
+
exclude: {
|
|
30
|
+
query: null,
|
|
31
|
+
selectedQueryId: null,
|
|
32
|
+
clearFilters: null,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const languages = {
|
|
38
|
+
eng: ["English"],
|
|
39
|
+
spa: ["Spanish", "Castilian"],
|
|
40
|
+
fre: ["French"],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const renderSearch = (
|
|
44
|
+
overrides: Partial<React.ComponentProps<typeof CustomListSearch>> = {}
|
|
45
|
+
) =>
|
|
46
|
+
render(
|
|
47
|
+
<CustomListSearch
|
|
48
|
+
entryPoints={entryPoints}
|
|
49
|
+
isOwner={true}
|
|
50
|
+
languages={languages}
|
|
51
|
+
library={library}
|
|
52
|
+
listId="123"
|
|
53
|
+
search={jest.fn()}
|
|
54
|
+
searchParams={searchParams}
|
|
55
|
+
updateAutoUpdate={jest.fn()}
|
|
56
|
+
updateSearchParam={jest.fn()}
|
|
57
|
+
{...overrides}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
it("renders a radio button for each entry point", () => {
|
|
62
|
+
const { container } = renderSearch();
|
|
63
|
+
const entryPointsSection =
|
|
64
|
+
container.querySelector<HTMLElement>(".entry-points");
|
|
65
|
+
|
|
66
|
+
const radios = within(entryPointsSection).getAllByRole("radio");
|
|
67
|
+
expect(radios).toHaveLength(3);
|
|
68
|
+
|
|
69
|
+
const all = within(entryPointsSection).getByRole("radio", { name: "All" });
|
|
70
|
+
expect(all).toHaveAttribute("name", "entry-points-selection");
|
|
71
|
+
expect(all).toHaveAttribute("value", "All");
|
|
72
|
+
expect(all).toBeEnabled();
|
|
73
|
+
expect(all).toBeChecked();
|
|
74
|
+
|
|
75
|
+
const book = within(entryPointsSection).getByRole("radio", {
|
|
76
|
+
name: "Book",
|
|
77
|
+
});
|
|
78
|
+
expect(book).toHaveAttribute("name", "entry-points-selection");
|
|
79
|
+
expect(book).toHaveAttribute("value", "Book");
|
|
80
|
+
expect(book).toBeEnabled();
|
|
81
|
+
expect(book).not.toBeChecked();
|
|
82
|
+
|
|
83
|
+
const audio = within(entryPointsSection).getByRole("radio", {
|
|
84
|
+
name: "Audio",
|
|
85
|
+
});
|
|
86
|
+
expect(audio).toHaveAttribute("name", "entry-points-selection");
|
|
87
|
+
expect(audio).toHaveAttribute("value", "Audio");
|
|
88
|
+
expect(audio).toBeEnabled();
|
|
89
|
+
expect(audio).not.toBeChecked();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("disables the entry point radio buttons when isOwner is false", () => {
|
|
93
|
+
const { container } = renderSearch({ isOwner: false });
|
|
94
|
+
const entryPointsSection =
|
|
95
|
+
container.querySelector<HTMLElement>(".entry-points");
|
|
96
|
+
|
|
97
|
+
within(entryPointsSection)
|
|
98
|
+
.getAllByRole("radio")
|
|
99
|
+
.forEach((radio) => expect(radio).toBeDisabled());
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("calls updateSearchParam when an entry point radio button is changed", async () => {
|
|
103
|
+
const user = userEvent.setup();
|
|
104
|
+
const updateSearchParam = jest.fn();
|
|
105
|
+
const { container } = renderSearch({ updateSearchParam });
|
|
106
|
+
const entryPointsSection =
|
|
107
|
+
container.querySelector<HTMLElement>(".entry-points");
|
|
108
|
+
|
|
109
|
+
await user.click(
|
|
110
|
+
within(entryPointsSection).getByRole("radio", { name: "Audio" })
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
expect(updateSearchParam).toHaveBeenCalledTimes(1);
|
|
114
|
+
expect(updateSearchParam).toHaveBeenCalledWith("entryPoint", "Audio");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("renders a radio button for each sort option", () => {
|
|
118
|
+
const { container } = renderSearch();
|
|
119
|
+
const sortSection = container.querySelector<HTMLElement>(".search-options");
|
|
120
|
+
|
|
121
|
+
const radios = within(sortSection).getAllByRole("radio");
|
|
122
|
+
expect(radios).toHaveLength(3);
|
|
123
|
+
|
|
124
|
+
const relevance = within(sortSection).getByRole("radio", {
|
|
125
|
+
name: "Relevance",
|
|
126
|
+
});
|
|
127
|
+
expect(relevance).toHaveAttribute("name", "sort-selection");
|
|
128
|
+
expect(relevance).toHaveAttribute("value", "");
|
|
129
|
+
expect(relevance).toBeEnabled();
|
|
130
|
+
expect(relevance).not.toBeChecked();
|
|
131
|
+
|
|
132
|
+
const title = within(sortSection).getByRole("radio", { name: "Title" });
|
|
133
|
+
expect(title).toHaveAttribute("name", "sort-selection");
|
|
134
|
+
expect(title).toHaveAttribute("value", "title");
|
|
135
|
+
expect(title).toBeEnabled();
|
|
136
|
+
expect(title).toBeChecked();
|
|
137
|
+
|
|
138
|
+
const author = within(sortSection).getByRole("radio", { name: "Author" });
|
|
139
|
+
expect(author).toHaveAttribute("name", "sort-selection");
|
|
140
|
+
expect(author).toHaveAttribute("value", "author");
|
|
141
|
+
expect(author).toBeEnabled();
|
|
142
|
+
expect(author).not.toBeChecked();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("disables the sort option radio buttons when isOwner is false", () => {
|
|
146
|
+
const { container } = renderSearch({ isOwner: false });
|
|
147
|
+
const sortSection = container.querySelector<HTMLElement>(".search-options");
|
|
148
|
+
|
|
149
|
+
within(sortSection)
|
|
150
|
+
.getAllByRole("radio")
|
|
151
|
+
.forEach((radio) => expect(radio).toBeDisabled());
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("calls updateSearchParam when a sort radio button is changed", async () => {
|
|
155
|
+
const user = userEvent.setup();
|
|
156
|
+
const updateSearchParam = jest.fn();
|
|
157
|
+
const { container } = renderSearch({ updateSearchParam });
|
|
158
|
+
const sortSection = container.querySelector<HTMLElement>(".search-options");
|
|
159
|
+
|
|
160
|
+
await user.click(
|
|
161
|
+
within(sortSection).getByRole("radio", { name: "Author" })
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
expect(updateSearchParam).toHaveBeenCalledTimes(1);
|
|
165
|
+
expect(updateSearchParam).toHaveBeenCalledWith("sort", "author");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("calls addAdvSearchQuery and search when mounted if there is a startingTitle", () => {
|
|
169
|
+
const addAdvSearchQuery = jest.fn();
|
|
170
|
+
const search = jest.fn();
|
|
171
|
+
|
|
172
|
+
renderSearch({ startingTitle: "test", addAdvSearchQuery, search });
|
|
173
|
+
|
|
174
|
+
expect(addAdvSearchQuery).toHaveBeenCalledTimes(1);
|
|
175
|
+
expect(addAdvSearchQuery).toHaveBeenCalledWith("include", {
|
|
176
|
+
key: "title",
|
|
177
|
+
op: "eq",
|
|
178
|
+
value: "test",
|
|
179
|
+
});
|
|
180
|
+
expect(search).toHaveBeenCalledTimes(1);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe("when showAutoUpdate is false", () => {
|
|
184
|
+
it("does not render radio buttons for auto update on and off", () => {
|
|
185
|
+
const { container } = renderSearch();
|
|
186
|
+
|
|
187
|
+
expect(container.querySelector(".auto-update")).toBeNull();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe("when showAutoUpdate is true", () => {
|
|
192
|
+
it("renders radio buttons for auto update on and off", () => {
|
|
193
|
+
const { container } = renderSearch({
|
|
194
|
+
showAutoUpdate: true,
|
|
195
|
+
listId: null,
|
|
196
|
+
});
|
|
197
|
+
const autoSection = container.querySelector<HTMLElement>(".auto-update");
|
|
198
|
+
|
|
199
|
+
const radios = within(autoSection).getAllByRole("radio");
|
|
200
|
+
expect(radios).toHaveLength(2);
|
|
201
|
+
|
|
202
|
+
const on = within(autoSection).getByRole("radio", {
|
|
203
|
+
name: "Automatically update this list",
|
|
204
|
+
});
|
|
205
|
+
expect(on).toHaveAttribute("name", "auto-update");
|
|
206
|
+
expect(on).toBeEnabled();
|
|
207
|
+
|
|
208
|
+
const off = within(autoSection).getByRole("radio", {
|
|
209
|
+
name: "Manually select titles",
|
|
210
|
+
});
|
|
211
|
+
expect(off).toHaveAttribute("name", "auto-update");
|
|
212
|
+
expect(off).toBeEnabled();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("disables the radio buttons for auto update when isOwner is false", () => {
|
|
216
|
+
const { container } = renderSearch({
|
|
217
|
+
showAutoUpdate: true,
|
|
218
|
+
isOwner: false,
|
|
219
|
+
listId: null,
|
|
220
|
+
});
|
|
221
|
+
const autoSection = container.querySelector<HTMLElement>(".auto-update");
|
|
222
|
+
|
|
223
|
+
within(autoSection)
|
|
224
|
+
.getAllByRole("radio")
|
|
225
|
+
.forEach((radio) => expect(radio).toBeDisabled());
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("disables the radio buttons for auto update when listId is not null", () => {
|
|
229
|
+
// isOwner: true keeps readOnly false, so listId is the only thing that can
|
|
230
|
+
// disable the radios — otherwise readOnly alone would, masking this path.
|
|
231
|
+
const { container } = renderSearch({
|
|
232
|
+
showAutoUpdate: true,
|
|
233
|
+
isOwner: true,
|
|
234
|
+
listId: "123",
|
|
235
|
+
});
|
|
236
|
+
const autoSection = container.querySelector<HTMLElement>(".auto-update");
|
|
237
|
+
|
|
238
|
+
within(autoSection)
|
|
239
|
+
.getAllByRole("radio")
|
|
240
|
+
.forEach((radio) => expect(radio).toBeDisabled());
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("calls updateAutoUpdate when an auto update radio button is changed", async () => {
|
|
244
|
+
const user = userEvent.setup();
|
|
245
|
+
// Clicking the currently-unchecked "on" radio reports true.
|
|
246
|
+
const updateAutoUpdateOn = jest.fn();
|
|
247
|
+
const { unmount } = renderSearch({
|
|
248
|
+
showAutoUpdate: true,
|
|
249
|
+
listId: null,
|
|
250
|
+
autoUpdate: false,
|
|
251
|
+
updateAutoUpdate: updateAutoUpdateOn,
|
|
252
|
+
});
|
|
253
|
+
await user.click(
|
|
254
|
+
screen.getByRole("radio", { name: "Automatically update this list" })
|
|
255
|
+
);
|
|
256
|
+
expect(updateAutoUpdateOn).toHaveBeenCalledTimes(1);
|
|
257
|
+
expect(updateAutoUpdateOn).toHaveBeenCalledWith(true);
|
|
258
|
+
unmount();
|
|
259
|
+
|
|
260
|
+
// Clicking the currently-unchecked "off" radio reports false.
|
|
261
|
+
const updateAutoUpdateOff = jest.fn();
|
|
262
|
+
renderSearch({
|
|
263
|
+
showAutoUpdate: true,
|
|
264
|
+
listId: null,
|
|
265
|
+
autoUpdate: true,
|
|
266
|
+
updateAutoUpdate: updateAutoUpdateOff,
|
|
267
|
+
});
|
|
268
|
+
await user.click(
|
|
269
|
+
screen.getByRole("radio", { name: "Manually select titles" })
|
|
270
|
+
);
|
|
271
|
+
expect(updateAutoUpdateOff).toHaveBeenCalledTimes(1);
|
|
272
|
+
expect(updateAutoUpdateOff).toHaveBeenCalledWith(false);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
});
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { render, screen, within } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
import CustomListsSidebar from "../../../src/components/CustomListsSidebar";
|
|
5
|
+
|
|
6
|
+
// Render react-router's Link as a marker anchor so we can assert its target
|
|
7
|
+
// (`data-to`), text, and className without needing a Router in the test.
|
|
8
|
+
jest.mock("react-router", () => ({
|
|
9
|
+
...jest.requireActual("react-router"),
|
|
10
|
+
Link: (props) => (
|
|
11
|
+
<a
|
|
12
|
+
data-testid="Link"
|
|
13
|
+
data-to={props.to}
|
|
14
|
+
href={props.to}
|
|
15
|
+
className={props.className}
|
|
16
|
+
>
|
|
17
|
+
{props.children}
|
|
18
|
+
</a>
|
|
19
|
+
),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
describe("CustomListsSidebar", () => {
|
|
23
|
+
const makeLists = () => [
|
|
24
|
+
{
|
|
25
|
+
id: 1,
|
|
26
|
+
name: "First List",
|
|
27
|
+
entry_count: 5,
|
|
28
|
+
is_owner: true,
|
|
29
|
+
is_shared: false,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 2,
|
|
33
|
+
name: "Second List",
|
|
34
|
+
entry_count: 10,
|
|
35
|
+
is_owner: true,
|
|
36
|
+
is_shared: false,
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const baseProps = () => ({
|
|
41
|
+
filter: "owned",
|
|
42
|
+
lists: makeLists(),
|
|
43
|
+
library: "library_name",
|
|
44
|
+
identifier: "123",
|
|
45
|
+
isLibraryManager: true,
|
|
46
|
+
deleteCustomList: jest.fn(),
|
|
47
|
+
changeSort: jest.fn(),
|
|
48
|
+
sortOrder: "asc",
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("renders a sidebar with a header and a create button", () => {
|
|
52
|
+
const { container } = render(<CustomListsSidebar {...baseProps()} />);
|
|
53
|
+
|
|
54
|
+
expect(
|
|
55
|
+
container.querySelector(".custom-lists-sidebar")
|
|
56
|
+
).toBeInTheDocument();
|
|
57
|
+
expect(screen.getByRole("heading", { level: 2 })).toHaveTextContent(
|
|
58
|
+
"List Manager"
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const createButton = screen.getAllByTestId("Link")[0];
|
|
62
|
+
expect(createButton).toHaveTextContent("Create New List");
|
|
63
|
+
expect(createButton).toHaveAttribute(
|
|
64
|
+
"data-to",
|
|
65
|
+
"/admin/web/lists/library_name/create"
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("renders filter select", () => {
|
|
70
|
+
const { container } = render(<CustomListsSidebar {...baseProps()} />);
|
|
71
|
+
|
|
72
|
+
const select = container.querySelector<HTMLSelectElement>(
|
|
73
|
+
'select[name="filter"]'
|
|
74
|
+
);
|
|
75
|
+
expect(select).toHaveValue("owned");
|
|
76
|
+
|
|
77
|
+
const options = within(select).getAllByRole("option");
|
|
78
|
+
expect(options).toHaveLength(4);
|
|
79
|
+
expect(options[0]).toHaveValue("");
|
|
80
|
+
expect(options[1]).toHaveValue("owned");
|
|
81
|
+
expect(options[2]).toHaveValue("shared-out");
|
|
82
|
+
expect(options[3]).toHaveValue("shared-in");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("renders sort select", () => {
|
|
86
|
+
const { container } = render(<CustomListsSidebar {...baseProps()} />);
|
|
87
|
+
|
|
88
|
+
const select = container.querySelector<HTMLSelectElement>(
|
|
89
|
+
'select[name="sort"]'
|
|
90
|
+
);
|
|
91
|
+
expect(select).toHaveValue("asc");
|
|
92
|
+
|
|
93
|
+
const options = within(select).getAllByRole("option");
|
|
94
|
+
expect(options).toHaveLength(2);
|
|
95
|
+
expect(options[0]).toHaveValue("asc");
|
|
96
|
+
expect(options[1]).toHaveValue("desc");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("renders a list of custom list info items", async () => {
|
|
100
|
+
const user = userEvent.setup();
|
|
101
|
+
const deleteCustomList = jest.fn();
|
|
102
|
+
const { container } = render(
|
|
103
|
+
<CustomListsSidebar
|
|
104
|
+
{...baseProps()}
|
|
105
|
+
deleteCustomList={deleteCustomList}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const listItems = container.querySelectorAll("ul > li");
|
|
110
|
+
expect(listItems).toHaveLength(2);
|
|
111
|
+
const [firstList, secondList] = Array.from(listItems);
|
|
112
|
+
|
|
113
|
+
const firstInfo = firstList.querySelectorAll(".custom-list-info > div");
|
|
114
|
+
expect(firstInfo[0]).toHaveTextContent("First List");
|
|
115
|
+
expect(firstInfo[1]).toHaveTextContent("Books in list: 5");
|
|
116
|
+
expect(firstInfo[2]).toHaveTextContent("ID-1");
|
|
117
|
+
|
|
118
|
+
const firstButtons = firstList.querySelector<HTMLElement>(
|
|
119
|
+
".custom-list-buttons"
|
|
120
|
+
);
|
|
121
|
+
const firstEdit = within(firstButtons).getByTestId("Link");
|
|
122
|
+
expect(firstEdit).toHaveTextContent("Edit");
|
|
123
|
+
expect(firstEdit).toHaveAttribute(
|
|
124
|
+
"data-to",
|
|
125
|
+
"/admin/web/lists/library_name/edit/1"
|
|
126
|
+
);
|
|
127
|
+
const firstDelete = within(firstButtons).getByRole("button");
|
|
128
|
+
expect(firstDelete).toHaveTextContent("Delete");
|
|
129
|
+
await user.click(firstDelete);
|
|
130
|
+
expect(deleteCustomList).toHaveBeenCalledTimes(1);
|
|
131
|
+
|
|
132
|
+
const secondInfo = secondList.querySelectorAll(".custom-list-info > div");
|
|
133
|
+
expect(secondInfo[0]).toHaveTextContent("Second List");
|
|
134
|
+
expect(secondInfo[1]).toHaveTextContent("Books in list: 10");
|
|
135
|
+
expect(secondInfo[2]).toHaveTextContent("ID-2");
|
|
136
|
+
|
|
137
|
+
const secondButtons = secondList.querySelector<HTMLElement>(
|
|
138
|
+
".custom-list-buttons"
|
|
139
|
+
);
|
|
140
|
+
const secondEdit = within(secondButtons).getByTestId("Link");
|
|
141
|
+
expect(secondEdit).toHaveTextContent("Edit");
|
|
142
|
+
expect(secondEdit).toHaveAttribute(
|
|
143
|
+
"data-to",
|
|
144
|
+
"/admin/web/lists/library_name/edit/2"
|
|
145
|
+
);
|
|
146
|
+
const secondDelete = within(secondButtons).getByRole("button");
|
|
147
|
+
expect(secondDelete).toHaveTextContent("Delete");
|
|
148
|
+
await user.click(secondDelete);
|
|
149
|
+
expect(deleteCustomList).toHaveBeenCalledTimes(2);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("disables the edit button if the list is already being edited", () => {
|
|
153
|
+
const props = baseProps();
|
|
154
|
+
const { container, rerender } = render(<CustomListsSidebar {...props} />);
|
|
155
|
+
|
|
156
|
+
const firstButtons = () =>
|
|
157
|
+
container.querySelectorAll<HTMLElement>(".custom-list-buttons")[0];
|
|
158
|
+
|
|
159
|
+
const editLink = within(firstButtons()).getByTestId("Link");
|
|
160
|
+
expect(editLink).not.toHaveClass("disabled");
|
|
161
|
+
expect(editLink).toHaveTextContent("EditPencil Icon");
|
|
162
|
+
|
|
163
|
+
rerender(<CustomListsSidebar {...props} identifier="1" />);
|
|
164
|
+
|
|
165
|
+
const editingButton = within(firstButtons()).getByRole("button", {
|
|
166
|
+
name: "Editing",
|
|
167
|
+
});
|
|
168
|
+
expect(editingButton).toBeDisabled();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("renders a view button instead of an edit button if a list is not owned", () => {
|
|
172
|
+
const { container } = render(
|
|
173
|
+
<CustomListsSidebar
|
|
174
|
+
{...baseProps()}
|
|
175
|
+
lists={[
|
|
176
|
+
{
|
|
177
|
+
id: 1,
|
|
178
|
+
name: "First List",
|
|
179
|
+
entry_count: 5,
|
|
180
|
+
is_owner: false,
|
|
181
|
+
is_shared: false,
|
|
182
|
+
},
|
|
183
|
+
]}
|
|
184
|
+
/>
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const firstButtons = container.querySelector<HTMLElement>(
|
|
188
|
+
".custom-list-buttons"
|
|
189
|
+
);
|
|
190
|
+
const viewLink = within(firstButtons).getByTestId("Link");
|
|
191
|
+
expect(viewLink).not.toHaveClass("disabled");
|
|
192
|
+
expect(viewLink).toHaveTextContent("ViewVisible Icon");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("displays the delete button only to library managers", () => {
|
|
196
|
+
const props = baseProps();
|
|
197
|
+
const { container, rerender } = render(<CustomListsSidebar {...props} />);
|
|
198
|
+
|
|
199
|
+
expect(
|
|
200
|
+
container.querySelectorAll(".custom-list-buttons button")
|
|
201
|
+
).toHaveLength(2);
|
|
202
|
+
|
|
203
|
+
rerender(<CustomListsSidebar {...props} isLibraryManager={false} />);
|
|
204
|
+
|
|
205
|
+
expect(
|
|
206
|
+
container.querySelectorAll(".custom-list-buttons button")
|
|
207
|
+
).toHaveLength(0);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("does not render a delete button if a list is not owned", () => {
|
|
211
|
+
const { container } = render(
|
|
212
|
+
<CustomListsSidebar
|
|
213
|
+
{...baseProps()}
|
|
214
|
+
lists={[
|
|
215
|
+
{
|
|
216
|
+
id: 1,
|
|
217
|
+
name: "First List",
|
|
218
|
+
entry_count: 5,
|
|
219
|
+
is_owner: false,
|
|
220
|
+
is_shared: false,
|
|
221
|
+
},
|
|
222
|
+
]}
|
|
223
|
+
/>
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
expect(
|
|
227
|
+
container.querySelectorAll(".custom-list-buttons button")
|
|
228
|
+
).toHaveLength(0);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("does not render a delete button if a list is shared", () => {
|
|
232
|
+
const { container } = render(
|
|
233
|
+
<CustomListsSidebar
|
|
234
|
+
{...baseProps()}
|
|
235
|
+
lists={[
|
|
236
|
+
{
|
|
237
|
+
id: 1,
|
|
238
|
+
name: "First List",
|
|
239
|
+
entry_count: 5,
|
|
240
|
+
is_owner: true,
|
|
241
|
+
is_shared: true,
|
|
242
|
+
},
|
|
243
|
+
]}
|
|
244
|
+
/>
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
expect(
|
|
248
|
+
container.querySelectorAll(".custom-list-buttons button")
|
|
249
|
+
).toHaveLength(0);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
// Stats fetches statistics via RTK Query on mount; DashboardPage's job is just
|
|
18
|
+
// composition, so mock Stats to a marker that echoes the `library` prop it is
|
|
19
|
+
// handed rather than wiring up fetch mocking.
|
|
20
|
+
jest.mock("../../../src/components/Stats", () => ({
|
|
21
|
+
__esModule: true,
|
|
22
|
+
default: (props: any) => (
|
|
23
|
+
<div data-testid="stats" data-library={props.library} />
|
|
24
|
+
),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
// CirculationEventsDownload gates its output on a feature flag (off by default) and
|
|
28
|
+
// keeps the `library` prop inside a collapsed form, so it is not DOM-observable when
|
|
29
|
+
// rendered for real. Mock it to a marker that echoes `library` so the prop-flow the
|
|
30
|
+
// original test checked stays observable.
|
|
31
|
+
jest.mock("../../../src/components/CirculationEventsDownload", () => ({
|
|
32
|
+
__esModule: true,
|
|
33
|
+
default: (props: any) => (
|
|
34
|
+
<div
|
|
35
|
+
data-testid="circulation-events-download"
|
|
36
|
+
data-library={props.library}
|
|
37
|
+
/>
|
|
38
|
+
),
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
import DashboardPage from "../../../src/components/DashboardPage";
|
|
42
|
+
|
|
43
|
+
describe("DashboardPage", () => {
|
|
44
|
+
it("shows Header", () => {
|
|
45
|
+
renderWithProviders(<DashboardPage params={{}} />);
|
|
46
|
+
expect(screen.getByTestId("header")).toBeInTheDocument();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("shows CirculationEventsDownload", () => {
|
|
50
|
+
const { rerender } = renderWithProviders(<DashboardPage params={{}} />);
|
|
51
|
+
|
|
52
|
+
let events = screen.getByTestId("circulation-events-download");
|
|
53
|
+
expect(events).toBeInTheDocument();
|
|
54
|
+
expect(events).not.toHaveAttribute("data-library");
|
|
55
|
+
|
|
56
|
+
rerender(<DashboardPage params={{ library: "NYPL" }} />);
|
|
57
|
+
events = screen.getByTestId("circulation-events-download");
|
|
58
|
+
expect(events).toHaveAttribute("data-library", "NYPL");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("shows Stats", () => {
|
|
62
|
+
const { rerender } = renderWithProviders(<DashboardPage params={{}} />);
|
|
63
|
+
|
|
64
|
+
let stats = screen.getByTestId("stats");
|
|
65
|
+
expect(stats).not.toHaveAttribute("data-library");
|
|
66
|
+
|
|
67
|
+
rerender(<DashboardPage params={{ library: "NYPL" }} />);
|
|
68
|
+
stats = screen.getByTestId("stats");
|
|
69
|
+
expect(stats).toHaveAttribute("data-library", "NYPL");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("shows Footer", () => {
|
|
73
|
+
renderWithProviders(<DashboardPage params={{}} />);
|
|
74
|
+
expect(screen.getByTestId("footer")).toBeInTheDocument();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -212,11 +212,7 @@ describe("DebugAuthentication", () => {
|
|
|
212
212
|
)
|
|
213
213
|
);
|
|
214
214
|
|
|
215
|
-
const {
|
|
216
|
-
getByLabelText,
|
|
217
|
-
queryByLabelText,
|
|
218
|
-
rerender,
|
|
219
|
-
} = renderWithProviders(
|
|
215
|
+
const { getByLabelText, queryByLabelText, rerender } = renderWithProviders(
|
|
220
216
|
<DebugAuthentication library={LIBRARY} csrfToken="test-csrf-token" />,
|
|
221
217
|
{ queryClient }
|
|
222
218
|
);
|