@thepalaceproject/circulation-admin 1.41.0-post.37 → 1.41.0-post.38
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
CHANGED
|
@@ -0,0 +1,215 @@
|
|
|
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("shows success feedback after queuing", async () => {
|
|
134
|
+
const user = userEvent.setup();
|
|
135
|
+
renderButton();
|
|
136
|
+
await expandPanel(user);
|
|
137
|
+
await user.click(screen.getByRole("button", { name: "Queue Reap" }));
|
|
138
|
+
await waitFor(() => {
|
|
139
|
+
const feedback = screen.getByText(/reap task queued\./i);
|
|
140
|
+
expect(feedback).toBeInTheDocument();
|
|
141
|
+
expect(feedback).toHaveClass("alert", "alert-success");
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("shows error feedback with alert-danger styling on failure", async () => {
|
|
146
|
+
const user = userEvent.setup();
|
|
147
|
+
const mockReap = jest
|
|
148
|
+
.fn()
|
|
149
|
+
.mockRejectedValue({ response: "Something went wrong" });
|
|
150
|
+
renderButton({ reapCollection: mockReap });
|
|
151
|
+
await expandPanel(user);
|
|
152
|
+
await user.click(screen.getByRole("button", { name: "Queue Reap" }));
|
|
153
|
+
await waitFor(() => {
|
|
154
|
+
const feedback = screen.getByText("Something went wrong");
|
|
155
|
+
expect(feedback).toBeInTheDocument();
|
|
156
|
+
expect(feedback).toHaveClass("alert", "alert-danger");
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("resets feedback when switching collections", async () => {
|
|
161
|
+
const user = userEvent.setup();
|
|
162
|
+
const { rerender, reapCollection } = renderButton();
|
|
163
|
+
await expandPanel(user);
|
|
164
|
+
|
|
165
|
+
await user.click(screen.getByRole("button", { name: "Queue Reap" }));
|
|
166
|
+
await waitFor(() => {
|
|
167
|
+
expect(screen.getByText(/reap task queued\./i)).toBeInTheDocument();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const nextCollection: CollectionData = {
|
|
171
|
+
id: 99,
|
|
172
|
+
protocol: "OPDS 2.0",
|
|
173
|
+
name: "Another Collection",
|
|
174
|
+
};
|
|
175
|
+
rerender(
|
|
176
|
+
<CollectionReapButton
|
|
177
|
+
collection={nextCollection}
|
|
178
|
+
protocols={[protocolWithReap, protocolWithoutReap]}
|
|
179
|
+
reapCollection={reapCollection}
|
|
180
|
+
disabled={false}
|
|
181
|
+
/>
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
await waitFor(() => {
|
|
185
|
+
expect(screen.queryByText(/reap task queued\./i)).not.toBeInTheDocument();
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("disables button when disabled prop is true", async () => {
|
|
190
|
+
const user = userEvent.setup();
|
|
191
|
+
renderButton({ disabled: true });
|
|
192
|
+
await expandPanel(user);
|
|
193
|
+
expect(screen.getByRole("button", { name: "Queue Reap" })).toBeDisabled();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("shows 'Queuing...' text while reaping", async () => {
|
|
197
|
+
const user = userEvent.setup();
|
|
198
|
+
let resolveReap: () => void;
|
|
199
|
+
const pendingReap = new Promise<void>((resolve) => {
|
|
200
|
+
resolveReap = resolve;
|
|
201
|
+
});
|
|
202
|
+
const mockReap = jest.fn().mockReturnValue(pendingReap);
|
|
203
|
+
renderButton({ reapCollection: mockReap });
|
|
204
|
+
await expandPanel(user);
|
|
205
|
+
|
|
206
|
+
await user.click(screen.getByRole("button", { name: "Queue Reap" }));
|
|
207
|
+
|
|
208
|
+
expect(screen.getByRole("button", { name: "Queuing..." })).toBeDisabled();
|
|
209
|
+
|
|
210
|
+
resolveReap();
|
|
211
|
+
await waitFor(() => {
|
|
212
|
+
expect(screen.getByRole("button", { name: "Queue Reap" })).toBeEnabled();
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
});
|
|
@@ -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
|
/>,
|