@thepalaceproject/circulation-admin 1.41.0 → 1.42.0-post.6
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 +3 -2
- package/README.md +1 -1
- package/dist/circulation-admin.css +1 -1
- package/dist/circulation-admin.js +1 -1
- package/dist/circulation-admin.js.LICENSE.txt +1 -9
- package/eslint.config.mjs +90 -0
- package/jest.polyfills.js +0 -1
- package/package.json +35 -36
- package/scripts/syncPatronBlockingDocs.js +0 -1
- package/testReporter.js +0 -2
- package/tests/browser/commands/goHome.js +3 -4
- package/tests/browser/navigate.js +114 -124
- package/tests/browser/redirect.js +26 -41
- package/tests/jest/components/AdvancedSearchBuilder.test.tsx +1 -1
- package/tests/jest/components/CollectionReapButton.test.tsx +240 -0
- package/tests/jest/components/Collections.test.tsx +1 -0
- package/tests/jest/components/CustomLists.test.tsx +0 -1
- package/tests/jest/components/DebugAuthentication.test.tsx +0 -2
- package/tests/jest/components/InventoryReportRequestModal.test.tsx +3 -5
- package/tests/jest/components/JsonField.test.tsx +0 -1
- package/tests/jest/components/LaneEditor.test.tsx +1 -1
- package/tests/jest/components/PatronBlockingRulesHelpModal.test.tsx +6 -8
- package/tests/jest/components/QuicksightDashboard.test.tsx +0 -1
- package/tests/jest/components/Stats.test.tsx +7 -13
- package/tests/jest/features/book.test.ts +2 -7
- package/webpack.common.js +0 -2
- package/webpack.dev-server.config.js +1 -3
- package/webpack.dev.config.js +3 -3
- package/webpack.prod.config.js +4 -4
- package/.eslintrc +0 -67
- package/tslint.json +0 -56
|
@@ -1,44 +1,30 @@
|
|
|
1
|
-
const http = require(
|
|
1
|
+
const http = require("follow-redirects").http;
|
|
2
2
|
|
|
3
|
-
const fetchGroupPage = (
|
|
3
|
+
const fetchGroupPage = () => {
|
|
4
4
|
return new Promise((resolve, reject) => {
|
|
5
|
-
http.get(
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
http.get(
|
|
6
|
+
{
|
|
7
|
+
host: "localhost",
|
|
8
|
+
port: 6500,
|
|
9
|
+
path: "/groups/",
|
|
10
|
+
},
|
|
11
|
+
(response) => {
|
|
12
|
+
let body = "";
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
response.on("data", (chunk) => {
|
|
15
|
+
body += chunk;
|
|
16
|
+
});
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
response.on("error", (err) => {
|
|
19
|
+
reject(err);
|
|
20
|
+
});
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
const getAdminBookUrlAndTitle = (body, browser) => {
|
|
27
|
-
const bookTitle = body.match(/<entry [\s\S]+?<title>([^<]+)<\/title>/i)[1];
|
|
28
|
-
const link = body.match(/<link [^>]*rel="alternate[^>]+\/>/i)[0];
|
|
29
|
-
const id = body.match(/<id>[\s\S]+?<\/id>/i)[0];
|
|
30
|
-
const lib = id.match(/\/[\w]+/i)[0]; // includes forward slash
|
|
31
|
-
const bookUrl = link.match(/href="([^"]+)"/)[1];
|
|
32
|
-
const entryBaseUrl = browser.globals.homeUrl.replace("/admin/web/", lib + "/works");
|
|
33
|
-
|
|
34
|
-
// transform the book url into an admin book url
|
|
35
|
-
const adminBookUrl =
|
|
36
|
-
browser.globals.homeUrl + "collection" + lib + "/book" + lib +
|
|
37
|
-
encodeURIComponent(
|
|
38
|
-
bookUrl.replace(entryBaseUrl, "")
|
|
22
|
+
response.on("end", () => {
|
|
23
|
+
resolve(body);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
39
26
|
);
|
|
40
|
-
|
|
41
|
-
return { adminBookUrl, bookTitle };
|
|
27
|
+
});
|
|
42
28
|
};
|
|
43
29
|
const getAdminListUrl = (body, browser) => {
|
|
44
30
|
const listBaseUrl = `${browser.globals.homeUrl}lists`;
|
|
@@ -56,11 +42,10 @@ module.exports = {
|
|
|
56
42
|
loginPage = browser.page.login();
|
|
57
43
|
loadingSelector = browser.page.catalog().elements.loadingSelector;
|
|
58
44
|
|
|
59
|
-
fetchGroupPage(browser)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
});
|
|
45
|
+
fetchGroupPage(browser).then((data) => {
|
|
46
|
+
body = data;
|
|
47
|
+
done();
|
|
48
|
+
});
|
|
64
49
|
},
|
|
65
50
|
|
|
66
51
|
"attempt to view the admin list page before signing in": (browser) => {
|
|
@@ -100,5 +85,5 @@ module.exports = {
|
|
|
100
85
|
|
|
101
86
|
afterEach: (browser) => {
|
|
102
87
|
browser.end();
|
|
103
|
-
}
|
|
88
|
+
},
|
|
104
89
|
};
|
|
@@ -5,7 +5,7 @@ import AdvancedSearchBuilder from "../../../src/components/AdvancedSearchBuilder
|
|
|
5
5
|
|
|
6
6
|
describe("AdvancedSearchBuilder", () => {
|
|
7
7
|
it("renders a placeholder and help text when the publication date filter is selected", async () => {
|
|
8
|
-
|
|
8
|
+
userEvent.setup();
|
|
9
9
|
|
|
10
10
|
const query = {
|
|
11
11
|
id: "0",
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { render, screen, waitFor } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
import CollectionReapButton, {
|
|
5
|
+
CollectionReapButtonProps,
|
|
6
|
+
} from "../../../src/components/CollectionReapButton";
|
|
7
|
+
import { CollectionData, ProtocolData } from "../../../src/interfaces";
|
|
8
|
+
|
|
9
|
+
const protocolWithReap: ProtocolData = {
|
|
10
|
+
name: "OPDS 2.0",
|
|
11
|
+
label: "OPDS 2.0",
|
|
12
|
+
supports_reap: true,
|
|
13
|
+
settings: [],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const protocolWithoutReap: ProtocolData = {
|
|
17
|
+
name: "Boundless",
|
|
18
|
+
label: "Boundless",
|
|
19
|
+
supports_reap: false,
|
|
20
|
+
settings: [],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const savedCollection: CollectionData = {
|
|
24
|
+
id: 42,
|
|
25
|
+
protocol: "OPDS 2.0",
|
|
26
|
+
name: "Test Collection",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const unsavedCollection: CollectionData = {
|
|
30
|
+
protocol: "OPDS 2.0",
|
|
31
|
+
name: "Unsaved Collection",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function renderButton(overrides: Partial<CollectionReapButtonProps> = {}) {
|
|
35
|
+
const defaultProps: CollectionReapButtonProps = {
|
|
36
|
+
collection: savedCollection,
|
|
37
|
+
protocols: [protocolWithReap, protocolWithoutReap],
|
|
38
|
+
reapCollection: jest.fn().mockResolvedValue(undefined),
|
|
39
|
+
disabled: false,
|
|
40
|
+
...overrides,
|
|
41
|
+
};
|
|
42
|
+
return {
|
|
43
|
+
...render(<CollectionReapButton {...defaultProps} />),
|
|
44
|
+
reapCollection: defaultProps.reapCollection as jest.Mock,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Expand the collapsed Reap panel by clicking its header. */
|
|
49
|
+
async function expandPanel(user: ReturnType<typeof userEvent.setup>) {
|
|
50
|
+
await user.click(screen.getByText("Reap"));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe("CollectionReapButton", () => {
|
|
54
|
+
it("does not render when protocol lacks supports_reap", () => {
|
|
55
|
+
const collection: CollectionData = {
|
|
56
|
+
id: 1,
|
|
57
|
+
protocol: "Boundless",
|
|
58
|
+
name: "Boundless Collection",
|
|
59
|
+
};
|
|
60
|
+
const { container } = renderButton({ collection });
|
|
61
|
+
expect(container.innerHTML).toBe("");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("does not render for unsaved collection (no id)", () => {
|
|
65
|
+
const { container } = renderButton({ collection: unsavedCollection });
|
|
66
|
+
expect(container.innerHTML).toBe("");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("does not render when the backend omits supports_reap (older CM)", () => {
|
|
70
|
+
// A circulation manager that predates reaping won't include the
|
|
71
|
+
// supports_reap flag at all. The admin must degrade gracefully and hide
|
|
72
|
+
// the panel rather than offering a button that would 404.
|
|
73
|
+
const legacyProtocol: ProtocolData = {
|
|
74
|
+
name: "OPDS 2.0",
|
|
75
|
+
label: "OPDS 2.0",
|
|
76
|
+
settings: [],
|
|
77
|
+
};
|
|
78
|
+
const { container } = renderButton({ protocols: [legacyProtocol] });
|
|
79
|
+
expect(container.innerHTML).toBe("");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("renders panel header when supported", () => {
|
|
83
|
+
renderButton();
|
|
84
|
+
expect(screen.getByText("Reap")).toBeInTheDocument();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("renders button when panel is expanded and has no force checkbox", async () => {
|
|
88
|
+
const user = userEvent.setup();
|
|
89
|
+
renderButton();
|
|
90
|
+
await expandPanel(user);
|
|
91
|
+
expect(
|
|
92
|
+
screen.getByRole("button", { name: "Queue Reap" })
|
|
93
|
+
).toBeInTheDocument();
|
|
94
|
+
expect(screen.queryByRole("checkbox")).not.toBeInTheDocument();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("shows compact summary by default; detailed docs are hidden", async () => {
|
|
98
|
+
const user = userEvent.setup();
|
|
99
|
+
renderButton();
|
|
100
|
+
await expandPanel(user);
|
|
101
|
+
expect(
|
|
102
|
+
screen.getByText(/queue reap removes titles that are no longer/i)
|
|
103
|
+
).toBeInTheDocument();
|
|
104
|
+
expect(
|
|
105
|
+
screen.getByText(/schedules a background job that re-reads/i)
|
|
106
|
+
).not.toBeVisible();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("clicking 'More details' reveals the detailed docs", async () => {
|
|
110
|
+
const user = userEvent.setup();
|
|
111
|
+
renderButton();
|
|
112
|
+
await expandPanel(user);
|
|
113
|
+
|
|
114
|
+
const details = screen.getByText("More details").closest("details");
|
|
115
|
+
expect(details).not.toHaveAttribute("open");
|
|
116
|
+
|
|
117
|
+
await user.click(screen.getByText("More details"));
|
|
118
|
+
|
|
119
|
+
expect(details).toHaveAttribute("open");
|
|
120
|
+
expect(
|
|
121
|
+
screen.getByText(/schedules a background job that re-reads/i)
|
|
122
|
+
).toBeVisible();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("button triggers reap with the collection id", async () => {
|
|
126
|
+
const user = userEvent.setup();
|
|
127
|
+
const { reapCollection } = renderButton();
|
|
128
|
+
await expandPanel(user);
|
|
129
|
+
await user.click(screen.getByRole("button", { name: "Queue Reap" }));
|
|
130
|
+
expect(reapCollection).toHaveBeenCalledWith(42);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("keeps both feedback live regions mounted before any feedback appears", async () => {
|
|
134
|
+
const user = userEvent.setup();
|
|
135
|
+
const { container } = renderButton();
|
|
136
|
+
await expandPanel(user);
|
|
137
|
+
// Both regions must already exist at a fixed politeness when their content
|
|
138
|
+
// mutates; several screen readers won't announce a region inserted
|
|
139
|
+
// alongside its text, nor a politeness change on an already-mounted node.
|
|
140
|
+
const politeRegion = container.querySelector('[aria-live="polite"]');
|
|
141
|
+
const assertiveRegion = container.querySelector('[aria-live="assertive"]');
|
|
142
|
+
expect(politeRegion).toBeInTheDocument();
|
|
143
|
+
expect(politeRegion).toBeEmptyDOMElement();
|
|
144
|
+
expect(assertiveRegion).toBeInTheDocument();
|
|
145
|
+
expect(assertiveRegion).toBeEmptyDOMElement();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("shows success feedback after queuing", async () => {
|
|
149
|
+
const user = userEvent.setup();
|
|
150
|
+
renderButton();
|
|
151
|
+
await expandPanel(user);
|
|
152
|
+
await user.click(screen.getByRole("button", { name: "Queue Reap" }));
|
|
153
|
+
await waitFor(() => {
|
|
154
|
+
const feedback = screen.getByText(/reap task queued\./i);
|
|
155
|
+
expect(feedback).toBeInTheDocument();
|
|
156
|
+
expect(feedback).toHaveClass("alert", "alert-success");
|
|
157
|
+
// Success routes to the permanently-polite region.
|
|
158
|
+
expect(feedback.closest("[aria-live]")).toHaveAttribute(
|
|
159
|
+
"aria-live",
|
|
160
|
+
"polite"
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("shows error feedback with alert-danger styling on failure", async () => {
|
|
166
|
+
const user = userEvent.setup();
|
|
167
|
+
const mockReap = jest
|
|
168
|
+
.fn()
|
|
169
|
+
.mockRejectedValue({ response: "Something went wrong" });
|
|
170
|
+
renderButton({ reapCollection: mockReap });
|
|
171
|
+
await expandPanel(user);
|
|
172
|
+
await user.click(screen.getByRole("button", { name: "Queue Reap" }));
|
|
173
|
+
await waitFor(() => {
|
|
174
|
+
const feedback = screen.getByText("Something went wrong");
|
|
175
|
+
expect(feedback).toBeInTheDocument();
|
|
176
|
+
expect(feedback).toHaveClass("alert", "alert-danger");
|
|
177
|
+
// Errors route to the permanently-assertive region.
|
|
178
|
+
expect(feedback.closest("[aria-live]")).toHaveAttribute(
|
|
179
|
+
"aria-live",
|
|
180
|
+
"assertive"
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("resets feedback when switching collections", async () => {
|
|
186
|
+
const user = userEvent.setup();
|
|
187
|
+
const { rerender, reapCollection } = renderButton();
|
|
188
|
+
await expandPanel(user);
|
|
189
|
+
|
|
190
|
+
await user.click(screen.getByRole("button", { name: "Queue Reap" }));
|
|
191
|
+
await waitFor(() => {
|
|
192
|
+
expect(screen.getByText(/reap task queued\./i)).toBeInTheDocument();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const nextCollection: CollectionData = {
|
|
196
|
+
id: 99,
|
|
197
|
+
protocol: "OPDS 2.0",
|
|
198
|
+
name: "Another Collection",
|
|
199
|
+
};
|
|
200
|
+
rerender(
|
|
201
|
+
<CollectionReapButton
|
|
202
|
+
collection={nextCollection}
|
|
203
|
+
protocols={[protocolWithReap, protocolWithoutReap]}
|
|
204
|
+
reapCollection={reapCollection}
|
|
205
|
+
disabled={false}
|
|
206
|
+
/>
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
await waitFor(() => {
|
|
210
|
+
expect(screen.queryByText(/reap task queued\./i)).not.toBeInTheDocument();
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("disables button when disabled prop is true", async () => {
|
|
215
|
+
const user = userEvent.setup();
|
|
216
|
+
renderButton({ disabled: true });
|
|
217
|
+
await expandPanel(user);
|
|
218
|
+
expect(screen.getByRole("button", { name: "Queue Reap" })).toBeDisabled();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("shows 'Queuing...' text while reaping", async () => {
|
|
222
|
+
const user = userEvent.setup();
|
|
223
|
+
let resolveReap: () => void;
|
|
224
|
+
const pendingReap = new Promise<void>((resolve) => {
|
|
225
|
+
resolveReap = resolve;
|
|
226
|
+
});
|
|
227
|
+
const mockReap = jest.fn().mockReturnValue(pendingReap);
|
|
228
|
+
renderButton({ reapCollection: mockReap });
|
|
229
|
+
await expandPanel(user);
|
|
230
|
+
|
|
231
|
+
await user.click(screen.getByRole("button", { name: "Queue Reap" }));
|
|
232
|
+
|
|
233
|
+
expect(screen.getByRole("button", { name: "Queuing..." })).toBeDisabled();
|
|
234
|
+
|
|
235
|
+
resolveReap();
|
|
236
|
+
await waitFor(() => {
|
|
237
|
+
expect(screen.getByRole("button", { name: "Queue Reap" })).toBeEnabled();
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
});
|
|
@@ -46,6 +46,7 @@ describe("Collections - associated library disclosure", () => {
|
|
|
46
46
|
deleteItem={jest.fn().mockResolvedValue(undefined)}
|
|
47
47
|
registerLibrary={jest.fn().mockResolvedValue(undefined)}
|
|
48
48
|
importCollection={jest.fn().mockResolvedValue(undefined)}
|
|
49
|
+
reapCollection={jest.fn().mockResolvedValue(undefined)}
|
|
49
50
|
csrfToken="token"
|
|
50
51
|
isFetching={false}
|
|
51
52
|
/>,
|
|
@@ -11,7 +11,6 @@ import { ConfigurationSettings } from "../../../src/interfaces";
|
|
|
11
11
|
describe("CustomLists", () => {
|
|
12
12
|
// Stub scrollTo, since a component in the render tree will try to call it, and it is not
|
|
13
13
|
// provided by JSDOM.
|
|
14
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
15
14
|
Element.prototype.scrollTo = () => {};
|
|
16
15
|
|
|
17
16
|
const server = setupServer(
|
|
@@ -56,7 +56,6 @@ const MOCK_DEBUG_RESULTS: PatronDebugResponse = {
|
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
describe("DebugAuthentication", () => {
|
|
59
|
-
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
60
59
|
const queryClient = new QueryClient({
|
|
61
60
|
defaultOptions: {
|
|
62
61
|
queries: {
|
|
@@ -69,7 +68,6 @@ describe("DebugAuthentication", () => {
|
|
|
69
68
|
error: process.env.NODE_ENV === "test" ? () => {} : console.error,
|
|
70
69
|
},
|
|
71
70
|
});
|
|
72
|
-
/* eslint-enable @typescript-eslint/no-empty-function */
|
|
73
71
|
|
|
74
72
|
const server = setupServer(
|
|
75
73
|
http.get(AUTH_METHODS_PATH, () =>
|
|
@@ -55,7 +55,6 @@ const setupMockServer = () => {
|
|
|
55
55
|
};
|
|
56
56
|
|
|
57
57
|
describe("InventoryReportRequestModal", () => {
|
|
58
|
-
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
59
58
|
const queryClient = new QueryClient({
|
|
60
59
|
defaultOptions: {
|
|
61
60
|
queries: {
|
|
@@ -68,7 +67,6 @@ describe("InventoryReportRequestModal", () => {
|
|
|
68
67
|
error: process.env.NODE_ENV === "test" ? () => {} : console.error,
|
|
69
68
|
},
|
|
70
69
|
});
|
|
71
|
-
/* eslint-enable @typescript-eslint/no-empty-function */
|
|
72
70
|
|
|
73
71
|
describe("query hooks call correct api methods", () => {
|
|
74
72
|
const wrapper = componentWithProviders({ queryClient });
|
|
@@ -90,7 +88,7 @@ describe("InventoryReportRequestModal", () => {
|
|
|
90
88
|
() => useReportInfo(show, API_ENDPOINT_PARAMS),
|
|
91
89
|
{ wrapper }
|
|
92
90
|
);
|
|
93
|
-
await waitFor(() => result.current.fetchStatus
|
|
91
|
+
await waitFor(() => result.current.fetchStatus === "idle");
|
|
94
92
|
const { isSuccess, isError, error, collections } = result.current;
|
|
95
93
|
|
|
96
94
|
expect(mock_info_api).toHaveBeenCalledWith(API_ENDPOINT_PARAMS);
|
|
@@ -109,7 +107,7 @@ describe("InventoryReportRequestModal", () => {
|
|
|
109
107
|
() => useReportInfo(show, API_ENDPOINT_PARAMS),
|
|
110
108
|
{ wrapper }
|
|
111
109
|
);
|
|
112
|
-
await waitFor(() => result.current.fetchStatus
|
|
110
|
+
await waitFor(() => result.current.fetchStatus === "idle");
|
|
113
111
|
const { isSuccess, isError, error, collections } = result.current;
|
|
114
112
|
|
|
115
113
|
expect(mock_info_api).not.toHaveBeenCalled();
|
|
@@ -129,7 +127,7 @@ describe("InventoryReportRequestModal", () => {
|
|
|
129
127
|
() => useReportInfo(show, API_ENDPOINT_PARAMS),
|
|
130
128
|
{ wrapper }
|
|
131
129
|
);
|
|
132
|
-
await waitFor(() => result.current.fetchStatus
|
|
130
|
+
await waitFor(() => result.current.fetchStatus === "idle");
|
|
133
131
|
const { isSuccess, isError, error, collections } = result.current;
|
|
134
132
|
|
|
135
133
|
expect(mock_info_api).toHaveBeenCalledWith(API_ENDPOINT_PARAMS);
|
|
@@ -12,7 +12,7 @@ import LaneEditor from "../../../src/components/LaneEditor";
|
|
|
12
12
|
|
|
13
13
|
jest.mock("../../../src/components/LaneCustomListsEditor", () => ({
|
|
14
14
|
__esModule: true,
|
|
15
|
-
default: (
|
|
15
|
+
default: () => <div data-testid="LaneCustomListsEditor" />,
|
|
16
16
|
}));
|
|
17
17
|
|
|
18
18
|
const customListsData = [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { render, screen
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
3
|
import userEvent from "@testing-library/user-event";
|
|
4
4
|
import { PatronBlockingRulesHelpModal } from "../../../src/components/PatronBlockingRulesHelpModal";
|
|
5
5
|
|
|
@@ -29,9 +29,7 @@ describe("PatronBlockingRulesHelpModal", () => {
|
|
|
29
29
|
|
|
30
30
|
it("does not render when show is false", () => {
|
|
31
31
|
render(<PatronBlockingRulesHelpModal {...baseProps} show={false} />);
|
|
32
|
-
expect(
|
|
33
|
-
screen.queryByText(/Patron Blocking Rules — Help/i)
|
|
34
|
-
).toBeNull();
|
|
32
|
+
expect(screen.queryByText(/Patron Blocking Rules — Help/i)).toBeNull();
|
|
35
33
|
});
|
|
36
34
|
|
|
37
35
|
it("shows a loading indicator when fieldsLoading is true", () => {
|
|
@@ -84,7 +82,9 @@ describe("PatronBlockingRulesHelpModal", () => {
|
|
|
84
82
|
/>
|
|
85
83
|
);
|
|
86
84
|
expect(
|
|
87
|
-
screen.getByText(
|
|
85
|
+
screen.getByText(
|
|
86
|
+
/Save the service before template variables can be fetched/i
|
|
87
|
+
)
|
|
88
88
|
).toBeTruthy();
|
|
89
89
|
expect(screen.queryByText("fines")).toBeNull();
|
|
90
90
|
});
|
|
@@ -97,9 +97,7 @@ describe("PatronBlockingRulesHelpModal", () => {
|
|
|
97
97
|
fieldsError={null}
|
|
98
98
|
/>
|
|
99
99
|
);
|
|
100
|
-
expect(
|
|
101
|
-
screen.getByText(/No field data available/i)
|
|
102
|
-
).toBeTruthy();
|
|
100
|
+
expect(screen.getByText(/No field data available/i)).toBeTruthy();
|
|
103
101
|
});
|
|
104
102
|
|
|
105
103
|
it("renders null values as italic 'null'", () => {
|
|
@@ -6,7 +6,6 @@ import { LibrariesData } from "../../../src/interfaces";
|
|
|
6
6
|
import buildStore from "../../../src/store";
|
|
7
7
|
import { setupServer } from "msw/node";
|
|
8
8
|
import { http, HttpResponse } from "msw";
|
|
9
|
-
import renderWithContext from "../testUtils/renderWithContext";
|
|
10
9
|
import { renderWithProviders } from "../testUtils/withProviders";
|
|
11
10
|
import QuicksightDashboardPage from "../../../src/components/QuicksightDashboardPage";
|
|
12
11
|
|
|
@@ -21,11 +21,7 @@ import { renderHook } from "@testing-library/react-hooks";
|
|
|
21
21
|
import { FetchErrorData } from "@thepalaceproject/web-opds-client/lib/interfaces";
|
|
22
22
|
import { store } from "../../../src/store";
|
|
23
23
|
import { api } from "../../../src/features/api/apiSlice";
|
|
24
|
-
import {
|
|
25
|
-
AdminRoleData,
|
|
26
|
-
ConfigurationSettings,
|
|
27
|
-
RolesData,
|
|
28
|
-
} from "../../../src/interfaces";
|
|
24
|
+
import { AdminRoleData, ConfigurationSettings } from "../../../src/interfaces";
|
|
29
25
|
|
|
30
26
|
const normalizedData = normalizeStatistics(statisticsApiResponseData);
|
|
31
27
|
|
|
@@ -442,14 +438,12 @@ describe("Dashboard Statistics", () => {
|
|
|
442
438
|
roles,
|
|
443
439
|
quicksightPagePath: fakeQuickSightHref,
|
|
444
440
|
};
|
|
445
|
-
const {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
appConfigSettings,
|
|
452
|
-
});
|
|
441
|
+
const { container, getByRole, queryByRole } = renderWithProviders(
|
|
442
|
+
<Stats library={sampleLibraryKey} />,
|
|
443
|
+
{
|
|
444
|
+
appConfigSettings,
|
|
445
|
+
}
|
|
446
|
+
);
|
|
453
447
|
|
|
454
448
|
// We should always render a Usage reports group when a library is specified.
|
|
455
449
|
getByRole("heading", {
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import reducer, {
|
|
2
2
|
initialState,
|
|
3
3
|
getBookData,
|
|
4
|
-
GetBookDataArgs,
|
|
5
4
|
submitBookData,
|
|
6
5
|
} from "../../../src/features/book/bookEditorSlice";
|
|
7
6
|
import { expect } from "chai";
|
|
8
7
|
import * as fetchMock from "fetch-mock-jest";
|
|
9
|
-
import { store } from "../../../src/store";
|
|
10
8
|
import { BookData } from "@thepalaceproject/web-opds-client/lib/interfaces";
|
|
11
9
|
import { RequestError } from "@thepalaceproject/web-opds-client/lib/DataFetcher";
|
|
12
|
-
import { AsyncThunkAction, Dispatch } from "@reduxjs/toolkit";
|
|
13
10
|
import ActionCreator from "@thepalaceproject/web-opds-client/lib/actions";
|
|
14
11
|
|
|
15
12
|
const SAMPLE_BOOK_ADMIN_DETAIL = `
|
|
@@ -274,8 +271,6 @@ describe("Redux bookEditorSlice...", () => {
|
|
|
274
271
|
describe("submitBookData...", () => {
|
|
275
272
|
const goodBookUrl = "https://example.com/book";
|
|
276
273
|
const editBookUrl = `${goodBookUrl}/edit`;
|
|
277
|
-
const brokenBookUrl = "https://example.com/broken-book";
|
|
278
|
-
const errorBookUrl = "https://example.com/error-book";
|
|
279
274
|
const csrfTokenHeader = "X-CSRF-Token";
|
|
280
275
|
const validCsrfToken = "valid-csrf-token";
|
|
281
276
|
|
|
@@ -328,7 +323,7 @@ describe("Redux bookEditorSlice...", () => {
|
|
|
328
323
|
csrfToken,
|
|
329
324
|
});
|
|
330
325
|
|
|
331
|
-
|
|
326
|
+
await action(dispatch, getState, undefined);
|
|
332
327
|
const dispatchCalls = dispatch.mock.calls;
|
|
333
328
|
const fetchCalls = fetchMock.calls();
|
|
334
329
|
|
|
@@ -366,7 +361,7 @@ describe("Redux bookEditorSlice...", () => {
|
|
|
366
361
|
csrfToken,
|
|
367
362
|
});
|
|
368
363
|
|
|
369
|
-
|
|
364
|
+
await action(dispatch, getState, undefined);
|
|
370
365
|
const dispatchCalls = dispatch.mock.calls;
|
|
371
366
|
const fetchCalls = fetchMock.calls();
|
|
372
367
|
|
package/webpack.common.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
2
1
|
const path = require("path");
|
|
3
2
|
const CleanWebpackPlugin = require("clean-webpack-plugin");
|
|
4
3
|
const webpack = require("webpack");
|
|
5
4
|
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
|
6
5
|
const TerserPlugin = require("terser-webpack-plugin");
|
|
7
6
|
const CopyPlugin = require("copy-webpack-plugin");
|
|
8
|
-
/* eslint-enable @typescript-eslint/no-var-requires */
|
|
9
7
|
|
|
10
8
|
module.exports = {
|
|
11
9
|
entry: {
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* This webpack config establishes a proxy server to enable a local build of circulation-admin to
|
|
5
3
|
* connect to a remote Circulation Manager back-end. Requests for webpack assets are served from
|
|
@@ -87,7 +85,7 @@ module.exports = (env) => {
|
|
|
87
85
|
if (redirectParam) {
|
|
88
86
|
const redirectUrl = new URL(redirectParam);
|
|
89
87
|
|
|
90
|
-
if (redirectUrl.host
|
|
88
|
+
if (redirectUrl.host === backendUrl.host) {
|
|
91
89
|
redirectUrl.protocol = "http";
|
|
92
90
|
redirectUrl.host = requestHost;
|
|
93
91
|
|
package/webpack.dev.config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const { merge } = require(
|
|
2
|
-
const common = require(
|
|
1
|
+
const { merge } = require("webpack-merge");
|
|
2
|
+
const common = require("./webpack.common.js");
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const config = merge(common, {
|
|
5
5
|
mode: "development",
|
|
6
6
|
devtool: "eval-source-map",
|
|
7
7
|
});
|
package/webpack.prod.config.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const { merge } = require(
|
|
2
|
-
const common = require(
|
|
1
|
+
const { merge } = require("webpack-merge");
|
|
2
|
+
const common = require("./webpack.common.js");
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
mode: "production"
|
|
4
|
+
const config = merge(common, {
|
|
5
|
+
mode: "production",
|
|
6
6
|
});
|
|
7
7
|
|
|
8
8
|
module.exports = config;
|