@thepalaceproject/circulation-admin 1.43.0-post.10 → 1.43.0-post.11
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/Announcement.test.tsx +76 -0
- package/tests/jest/components/Autocomplete.test.tsx +67 -0
- package/tests/jest/components/BookDetailsEditor.test.tsx +275 -0
- package/tests/jest/components/BookEditForm.test.tsx +352 -0
- package/tests/jest/components/Classifications.test.tsx +166 -0
- package/tests/jest/components/ClassificationsTable.test.tsx +57 -0
- package/tests/jest/components/ColorPicker.test.tsx +58 -0
- package/tests/jest/components/ComplaintForm.test.tsx +171 -0
- package/tests/jest/components/Complaints.test.tsx +259 -0
- package/tests/jest/components/ContextProvider.test.tsx +138 -0
- package/tests/jest/components/CustomListEditor.test.tsx +367 -0
- package/tests/jest/components/CustomListsForBook.test.tsx +315 -0
- package/tests/jest/components/DiagnosticsServiceTabs.test.tsx +141 -0
- package/tests/jest/components/DiagnosticsServiceType.test.tsx +75 -0
- package/tests/jest/components/LibraryEditForm.test.tsx +383 -0
- package/tests/jest/components/LibraryRegistrationForm.test.tsx +203 -0
- package/tests/jest/components/PatronAuthServices.test.tsx +114 -0
- package/tests/jest/components/ResetAdobeId.test.tsx +302 -0
- package/tests/jest/components/SitewideAnnouncements.test.tsx +134 -0
- package/tests/jest/components/TroubleshootingCategoryPage.test.tsx +69 -0
- package/tests/jest/components/TroubleshootingPage.test.tsx +94 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { installFormDataShim } from "../testUtils/formDataShim";
|
|
3
|
+
import { render, screen } from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
5
|
+
|
|
6
|
+
import LibraryRegistrationForm from "../../../src/components/LibraryRegistrationForm";
|
|
7
|
+
installFormDataShim();
|
|
8
|
+
|
|
9
|
+
describe("LibraryRegistrationForm", () => {
|
|
10
|
+
const library = {
|
|
11
|
+
name: "Test Library",
|
|
12
|
+
status: "success",
|
|
13
|
+
stage: "production",
|
|
14
|
+
};
|
|
15
|
+
const registrationData = {
|
|
16
|
+
id: 1,
|
|
17
|
+
libraries: [library],
|
|
18
|
+
access_problem: null,
|
|
19
|
+
terms_of_service_html:
|
|
20
|
+
"Here are the <a href='terms.html'>terms of service</a>!",
|
|
21
|
+
terms_of_service_link: "terms.html",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type Props = React.ComponentProps<typeof LibraryRegistrationForm>;
|
|
25
|
+
|
|
26
|
+
const makeProps = (overrides: Partial<Props> = {}): Props => ({
|
|
27
|
+
library: library as Props["library"],
|
|
28
|
+
checked: true,
|
|
29
|
+
buttonText: "Update registration",
|
|
30
|
+
register: jest.fn(),
|
|
31
|
+
disabled: false,
|
|
32
|
+
registrationData: registrationData as Props["registrationData"],
|
|
33
|
+
...overrides,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const renderForm = (overrides: Partial<Props> = {}) => {
|
|
37
|
+
const props = makeProps(overrides);
|
|
38
|
+
const result = render(<LibraryRegistrationForm {...props} />);
|
|
39
|
+
const rerender = (nextOverrides: Partial<Props>) =>
|
|
40
|
+
result.rerender(
|
|
41
|
+
<LibraryRegistrationForm {...makeProps(nextOverrides)} />
|
|
42
|
+
);
|
|
43
|
+
return { ...result, rerender, props };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
it("does not display a label and checkbox if there is no terms-of-service data", () => {
|
|
47
|
+
const { container } = renderForm({ registrationData: null });
|
|
48
|
+
|
|
49
|
+
expect(
|
|
50
|
+
container.querySelector(".registration-checkbox")
|
|
51
|
+
).not.toBeInTheDocument();
|
|
52
|
+
expect(
|
|
53
|
+
screen.queryByText("I agree to the terms of service.")
|
|
54
|
+
).not.toBeInTheDocument();
|
|
55
|
+
expect(screen.queryByRole("link")).not.toBeInTheDocument();
|
|
56
|
+
expect(screen.queryByRole("checkbox")).not.toBeInTheDocument();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("displays an error message if there is a problem with the terms-of-service data", () => {
|
|
60
|
+
const dataWithProblem = {
|
|
61
|
+
...registrationData,
|
|
62
|
+
access_problem: { detail: "Something went wrong." },
|
|
63
|
+
};
|
|
64
|
+
const { container } = renderForm({
|
|
65
|
+
registrationData: dataWithProblem as Props["registrationData"],
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const danger = container.querySelector(".registration-checkbox .bg-danger");
|
|
69
|
+
expect(danger).toHaveTextContent("Something went wrong.");
|
|
70
|
+
// If the terms can't be displayed, there's no point showing the checkbox
|
|
71
|
+
// and button.
|
|
72
|
+
expect(screen.queryByRole("checkbox")).not.toBeInTheDocument();
|
|
73
|
+
expect(screen.queryByRole("button")).not.toBeInTheDocument();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("displays content and a checkbox with a label if there is terms-of-service HTML", () => {
|
|
77
|
+
const { container } = renderForm();
|
|
78
|
+
|
|
79
|
+
const termsSection = container.querySelector(".registration-checkbox");
|
|
80
|
+
expect(termsSection).toBeInTheDocument();
|
|
81
|
+
|
|
82
|
+
const content = termsSection.querySelector("p");
|
|
83
|
+
expect(content).toHaveTextContent("Here are the terms of service!");
|
|
84
|
+
|
|
85
|
+
const checkbox = screen.getByRole("checkbox", {
|
|
86
|
+
name: "I agree to the terms of service.",
|
|
87
|
+
});
|
|
88
|
+
expect(checkbox).toBeInTheDocument();
|
|
89
|
+
expect(checkbox).toHaveAttribute("type", "checkbox");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("displays content and a checkbox with a label if there is a terms-of-service link", () => {
|
|
93
|
+
const { container } = renderForm({
|
|
94
|
+
registrationData: {
|
|
95
|
+
...registrationData,
|
|
96
|
+
terms_of_service_html: null,
|
|
97
|
+
} as Props["registrationData"],
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const termsSection = container.querySelector(".registration-checkbox");
|
|
101
|
+
expect(termsSection).toBeInTheDocument();
|
|
102
|
+
|
|
103
|
+
const content = termsSection.querySelector("p");
|
|
104
|
+
expect(content).toHaveTextContent(
|
|
105
|
+
"You must read the terms of service before registering your library."
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const link = screen.getByRole("link", { name: "terms of service" });
|
|
109
|
+
expect(link).toHaveAttribute("href", "terms.html");
|
|
110
|
+
|
|
111
|
+
const checkbox = screen.getByRole("checkbox", {
|
|
112
|
+
name: "I agree to the terms of service.",
|
|
113
|
+
});
|
|
114
|
+
expect(checkbox).toBeInTheDocument();
|
|
115
|
+
expect(checkbox).toHaveAttribute("type", "checkbox");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("displays a registration button", () => {
|
|
119
|
+
const { rerender } = renderForm();
|
|
120
|
+
expect(
|
|
121
|
+
screen.getByRole("button", { name: "Update registration" })
|
|
122
|
+
).toBeInTheDocument();
|
|
123
|
+
|
|
124
|
+
rerender({ buttonText: "Register" });
|
|
125
|
+
expect(
|
|
126
|
+
screen.getByRole("button", { name: "Register" })
|
|
127
|
+
).toBeInTheDocument();
|
|
128
|
+
|
|
129
|
+
rerender({ buttonText: "Retry registration" });
|
|
130
|
+
expect(
|
|
131
|
+
screen.getByRole("button", { name: "Retry registration" })
|
|
132
|
+
).toBeInTheDocument();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("disables the button if there is a checkbox and it is unchecked", async () => {
|
|
136
|
+
const user = userEvent.setup();
|
|
137
|
+
renderForm();
|
|
138
|
+
|
|
139
|
+
const button = screen.getByRole("button", { name: "Update registration" });
|
|
140
|
+
expect(button).toBeEnabled();
|
|
141
|
+
|
|
142
|
+
// Unchecking the terms-of-service checkbox disables the button.
|
|
143
|
+
await user.click(
|
|
144
|
+
screen.getByRole("checkbox", {
|
|
145
|
+
name: "I agree to the terms of service.",
|
|
146
|
+
})
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
expect(
|
|
150
|
+
screen.getByRole("button", { name: "Update registration" })
|
|
151
|
+
).toBeDisabled();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("disables the button if the disabled prop is set to true, regardless of the checkbox", () => {
|
|
155
|
+
const { rerender } = renderForm();
|
|
156
|
+
|
|
157
|
+
rerender({ disabled: true });
|
|
158
|
+
expect(
|
|
159
|
+
screen.getByRole("button", { name: "Update registration" })
|
|
160
|
+
).toBeDisabled();
|
|
161
|
+
|
|
162
|
+
rerender({ disabled: false });
|
|
163
|
+
expect(
|
|
164
|
+
screen.getByRole("button", { name: "Update registration" })
|
|
165
|
+
).toBeEnabled();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("determines whether to pre-check the checkbox", () => {
|
|
169
|
+
const { rerender } = renderForm();
|
|
170
|
+
|
|
171
|
+
expect(
|
|
172
|
+
screen.getByRole("checkbox", {
|
|
173
|
+
name: "I agree to the terms of service.",
|
|
174
|
+
})
|
|
175
|
+
).toBeChecked();
|
|
176
|
+
|
|
177
|
+
rerender({ checked: false });
|
|
178
|
+
expect(
|
|
179
|
+
screen.getByRole("checkbox", {
|
|
180
|
+
name: "I agree to the terms of service.",
|
|
181
|
+
})
|
|
182
|
+
).not.toBeChecked();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("calls register", async () => {
|
|
186
|
+
const user = userEvent.setup();
|
|
187
|
+
const register = jest.fn();
|
|
188
|
+
const { rerender } = renderForm({ register });
|
|
189
|
+
|
|
190
|
+
await user.click(
|
|
191
|
+
screen.getByRole("button", { name: "Update registration" })
|
|
192
|
+
);
|
|
193
|
+
expect(register).toHaveBeenCalledTimes(1);
|
|
194
|
+
expect(register).toHaveBeenCalledWith(library);
|
|
195
|
+
|
|
196
|
+
// A disabled button does not fire register again.
|
|
197
|
+
rerender({ register, disabled: true });
|
|
198
|
+
await user.click(
|
|
199
|
+
screen.getByRole("button", { name: "Update registration" })
|
|
200
|
+
);
|
|
201
|
+
expect(register).toHaveBeenCalledTimes(1);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { screen, waitFor } from "@testing-library/react";
|
|
3
|
+
|
|
4
|
+
import { renderWithProviders } from "../testUtils/withProviders";
|
|
5
|
+
import buildStore from "../../../src/store";
|
|
6
|
+
// Render the CONNECTED default export so that mapStateToProps / mapDispatchToProps
|
|
7
|
+
// are exercised: the panel fetches its data on mount, the fetch populates the
|
|
8
|
+
// Redux store, and mapStateToProps feeds it back in as props.
|
|
9
|
+
import PatronAuthServices from "../../../src/components/PatronAuthServices";
|
|
10
|
+
|
|
11
|
+
const listData = {
|
|
12
|
+
patron_auth_services: [
|
|
13
|
+
{
|
|
14
|
+
id: 2,
|
|
15
|
+
protocol: "test protocol",
|
|
16
|
+
settings: { test_setting: "test setting" },
|
|
17
|
+
libraries: [
|
|
18
|
+
{ short_name: "nypl", test_library_setting: "test library setting" },
|
|
19
|
+
],
|
|
20
|
+
name: "nypl protocol",
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
protocols: [
|
|
24
|
+
{ name: "test protocol", label: "test protocol label", settings: [] },
|
|
25
|
+
],
|
|
26
|
+
allLibraries: [{ short_name: "nypl" }],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Stub `fetch` so the panel's on-mount fetch resolves with `body`. Returning a
|
|
31
|
+
* fresh Response per call avoids "body already used" when several fetches run.
|
|
32
|
+
*/
|
|
33
|
+
const stubFetch = (body: unknown) =>
|
|
34
|
+
jest.spyOn(globalThis, "fetch").mockImplementation(
|
|
35
|
+
async () =>
|
|
36
|
+
new Response(JSON.stringify(body), {
|
|
37
|
+
status: 200,
|
|
38
|
+
headers: { "Content-Type": "application/json" },
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// A fresh store per test keeps the fetched data isolated between tests.
|
|
43
|
+
const renderConnected = (element: React.ReactElement) =>
|
|
44
|
+
renderWithProviders(element, {
|
|
45
|
+
reduxProviderProps: { store: buildStore() },
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("PatronAuthServices", () => {
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
jest.restoreAllMocks();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("shows the patron auth service list", async () => {
|
|
54
|
+
stubFetch(listData);
|
|
55
|
+
const { container } = renderConnected(
|
|
56
|
+
<PatronAuthServices csrfToken="token" />
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// The list appears once the connected component's on-mount fetch resolves.
|
|
60
|
+
// Its heading is the protocol-aware label built by PatronAuthServices.label().
|
|
61
|
+
expect(
|
|
62
|
+
await screen.findByRole("heading", {
|
|
63
|
+
level: 3,
|
|
64
|
+
name: /nypl protocol: test protocol label/,
|
|
65
|
+
})
|
|
66
|
+
).toBeInTheDocument();
|
|
67
|
+
|
|
68
|
+
const editLink = container.querySelector("a.edit-item");
|
|
69
|
+
expect(editLink).not.toBeNull();
|
|
70
|
+
expect(editLink.getAttribute("href")).toBe(
|
|
71
|
+
"/admin/web/config/patronAuth/edit/2"
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// No neighborhood analytics panel is shown in list mode.
|
|
75
|
+
expect(
|
|
76
|
+
screen.queryByText("Patron Neighborhood Analytics: Disabled")
|
|
77
|
+
).not.toBeInTheDocument();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("shows the neighborhood analytics panel when creating a service whose protocol has a neighborhood_mode setting", async () => {
|
|
81
|
+
const neighborhoodSetting = {
|
|
82
|
+
key: "neighborhood_mode",
|
|
83
|
+
label: "Neighborhood Mode",
|
|
84
|
+
description: "",
|
|
85
|
+
options: [],
|
|
86
|
+
};
|
|
87
|
+
const createData = {
|
|
88
|
+
patron_auth_services: [],
|
|
89
|
+
protocols: [
|
|
90
|
+
{
|
|
91
|
+
name: "test protocol",
|
|
92
|
+
label: "test protocol label",
|
|
93
|
+
settings: [neighborhoodSetting],
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
allLibraries: [{ short_name: "nypl" }],
|
|
97
|
+
};
|
|
98
|
+
stubFetch(createData);
|
|
99
|
+
|
|
100
|
+
const { container } = renderConnected(
|
|
101
|
+
<PatronAuthServices csrfToken="token" editOrCreate="create" />
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Once the protocol data loads, the create form's extra section (the
|
|
105
|
+
// NeighborhoodAnalyticsForm) renders because the selected protocol carries a
|
|
106
|
+
// "neighborhood_mode" setting.
|
|
107
|
+
await waitFor(() =>
|
|
108
|
+
expect(container.querySelector("#neighborhood-panel")).toBeInTheDocument()
|
|
109
|
+
);
|
|
110
|
+
expect(
|
|
111
|
+
screen.getByText("Patron Neighborhood Analytics: Disabled")
|
|
112
|
+
).toBeInTheDocument();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { screen, waitFor } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
|
|
5
|
+
import buildStore from "../../../src/store";
|
|
6
|
+
import { renderWithProviders } from "../testUtils/withProviders";
|
|
7
|
+
import ConnectedResetAdobeId, {
|
|
8
|
+
ResetAdobeId,
|
|
9
|
+
} from "../../../src/components/ResetAdobeId";
|
|
10
|
+
|
|
11
|
+
const patrons = [
|
|
12
|
+
{
|
|
13
|
+
authorization_expires: "",
|
|
14
|
+
username: "User Name",
|
|
15
|
+
personal_name: "Personal Name",
|
|
16
|
+
email_address: "user@email.com",
|
|
17
|
+
authorization_identifier: "1234",
|
|
18
|
+
authorization_identifiers: [""],
|
|
19
|
+
block_reason: "",
|
|
20
|
+
external_type: "",
|
|
21
|
+
fines: "",
|
|
22
|
+
permanent_id: "",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
authorization_expires: "",
|
|
26
|
+
personal_name: "Personal Name2",
|
|
27
|
+
authorization_identifier: "5678",
|
|
28
|
+
authorization_identifiers: [""],
|
|
29
|
+
block_reason: "",
|
|
30
|
+
external_type: "",
|
|
31
|
+
fines: "",
|
|
32
|
+
permanent_id: "",
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
describe("ResetAdobeId", () => {
|
|
37
|
+
let store: ReturnType<typeof buildStore>;
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
store = buildStore();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
jest.restoreAllMocks();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Render the unconnected component with explicit props (patron / resetAdobeId /
|
|
48
|
+
// responseBody / fetchError are what `connect` would otherwise source from the
|
|
49
|
+
// store, so tests inject them directly). Wrapped in providers because the
|
|
50
|
+
// ManagePatronsForm it renders is connected.
|
|
51
|
+
const resetElement = (props: Record<string, unknown> = {}) => (
|
|
52
|
+
<ResetAdobeId store={store} csrfToken="token" library="nypl" {...props} />
|
|
53
|
+
);
|
|
54
|
+
const renderReset = (props: Record<string, unknown> = {}) =>
|
|
55
|
+
renderWithProviders(resetElement(props));
|
|
56
|
+
|
|
57
|
+
describe("rendering without patron", () => {
|
|
58
|
+
it("has the patron-actions class", () => {
|
|
59
|
+
const { container } = renderReset({ patron: null });
|
|
60
|
+
expect(container.firstChild).toHaveClass("patron-actions");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("has a header", () => {
|
|
64
|
+
renderReset({ patron: null });
|
|
65
|
+
expect(
|
|
66
|
+
screen.getByRole("heading", { name: "Reset Adobe ID" })
|
|
67
|
+
).toBeInTheDocument();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("does not render a patron-info section", () => {
|
|
71
|
+
const { container } = renderReset({ patron: null });
|
|
72
|
+
expect(container.querySelector(".patron-info")).not.toBeInTheDocument();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("prompts the user to search for a patron", () => {
|
|
76
|
+
renderReset({ patron: null });
|
|
77
|
+
expect(
|
|
78
|
+
screen.getByText("Search for a patron to begin.")
|
|
79
|
+
).toBeInTheDocument();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("rendering with patron", () => {
|
|
84
|
+
it("shows a warning before the submit button", () => {
|
|
85
|
+
const { container } = renderReset({ patron: patrons[0] });
|
|
86
|
+
expect(container.querySelector(".patron-warning")).toHaveTextContent(
|
|
87
|
+
"IMPORTANT: Patron User Name will lose access to any existing loans. Loans will still appear in the patron's book list until they expire, but the patron will be unable to read or return them."
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("gives the submit button a danger class", () => {
|
|
92
|
+
renderReset({ patron: patrons[0] });
|
|
93
|
+
expect(
|
|
94
|
+
screen.getByRole("button", { name: "Reset Adobe ID" })
|
|
95
|
+
).toHaveClass("danger");
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("behavior", () => {
|
|
100
|
+
it("has a disabled submit button initially", () => {
|
|
101
|
+
renderReset({ patron: patrons[0], resetAdobeId: jest.fn() });
|
|
102
|
+
expect(
|
|
103
|
+
screen.getByRole("button", { name: "Reset Adobe ID" })
|
|
104
|
+
).toBeDisabled();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("enables the submit button when the checkbox is checked", async () => {
|
|
108
|
+
const user = userEvent.setup();
|
|
109
|
+
renderReset({ patron: patrons[0], resetAdobeId: jest.fn() });
|
|
110
|
+
const button = screen.getByRole("button", { name: "Reset Adobe ID" });
|
|
111
|
+
expect(button).toBeDisabled();
|
|
112
|
+
|
|
113
|
+
await user.click(screen.getByRole("checkbox"));
|
|
114
|
+
|
|
115
|
+
expect(button).not.toBeDisabled();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("disables the submit button when the checkbox is unchecked again", async () => {
|
|
119
|
+
const user = userEvent.setup();
|
|
120
|
+
renderReset({ patron: patrons[0], resetAdobeId: jest.fn() });
|
|
121
|
+
const button = screen.getByRole("button", { name: "Reset Adobe ID" });
|
|
122
|
+
const checkbox = screen.getByRole("checkbox");
|
|
123
|
+
|
|
124
|
+
await user.click(checkbox);
|
|
125
|
+
expect(button).not.toBeDisabled();
|
|
126
|
+
|
|
127
|
+
await user.click(checkbox);
|
|
128
|
+
expect(button).toBeDisabled();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("calls the Adobe ID action when the button is clicked", async () => {
|
|
132
|
+
const user = userEvent.setup();
|
|
133
|
+
const resetAdobeId = jest.fn().mockResolvedValue(undefined);
|
|
134
|
+
renderReset({ patron: patrons[0], resetAdobeId });
|
|
135
|
+
expect(resetAdobeId).not.toHaveBeenCalled();
|
|
136
|
+
|
|
137
|
+
await user.click(screen.getByRole("checkbox"));
|
|
138
|
+
const button = screen.getByRole("button", { name: "Reset Adobe ID" });
|
|
139
|
+
expect(button).not.toBeDisabled();
|
|
140
|
+
await user.click(button);
|
|
141
|
+
|
|
142
|
+
await waitFor(() => expect(resetAdobeId).toHaveBeenCalledTimes(1));
|
|
143
|
+
const [data, library] = resetAdobeId.mock.calls[0];
|
|
144
|
+
expect(data.get("identifier")).toBe("1234");
|
|
145
|
+
expect(library).toBe("nypl");
|
|
146
|
+
|
|
147
|
+
// After the reset resolves the component clears the checkbox, re-disabling
|
|
148
|
+
// the button; awaiting that settles the pending promise.
|
|
149
|
+
await waitFor(() => expect(button).toBeDisabled());
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("shows a success alert message if the reset is successful", () => {
|
|
153
|
+
const { container, rerender } = renderReset({
|
|
154
|
+
patron: patrons[0],
|
|
155
|
+
resetAdobeId: jest.fn().mockResolvedValue(undefined),
|
|
156
|
+
});
|
|
157
|
+
expect(container.querySelector(".alert-success")).not.toBeInTheDocument();
|
|
158
|
+
|
|
159
|
+
// The response now comes from the server (via the responseBody prop).
|
|
160
|
+
rerender(
|
|
161
|
+
resetElement({
|
|
162
|
+
patron: patrons[0],
|
|
163
|
+
responseBody: "Adobe ID for patron has been reset.",
|
|
164
|
+
})
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const alert = container.querySelector(".alert-success");
|
|
168
|
+
expect(alert).toBeInTheDocument();
|
|
169
|
+
expect(alert).toHaveTextContent("Adobe ID for patron has been reset.");
|
|
170
|
+
expect(alert).toHaveTextContent(
|
|
171
|
+
"Please instruct the patron to sign back into their account."
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("hides the checkbox and the reset button if the reset is successful", () => {
|
|
176
|
+
const { container, rerender } = renderReset({
|
|
177
|
+
patron: patrons[0],
|
|
178
|
+
resetAdobeId: jest.fn().mockResolvedValue(undefined),
|
|
179
|
+
});
|
|
180
|
+
expect(
|
|
181
|
+
container.querySelector(".reset-adobe-id input")
|
|
182
|
+
).toBeInTheDocument();
|
|
183
|
+
expect(
|
|
184
|
+
container.querySelector(".reset-adobe-id button")
|
|
185
|
+
).toBeInTheDocument();
|
|
186
|
+
|
|
187
|
+
rerender(
|
|
188
|
+
resetElement({
|
|
189
|
+
patron: patrons[0],
|
|
190
|
+
responseBody: "Adobe ID for patron has been reset.",
|
|
191
|
+
})
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
expect(
|
|
195
|
+
container.querySelector(".reset-adobe-id input")
|
|
196
|
+
).not.toBeInTheDocument();
|
|
197
|
+
expect(
|
|
198
|
+
container.querySelector(".reset-adobe-id button")
|
|
199
|
+
).not.toBeInTheDocument();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("shows a failure alert message if the reset fails", () => {
|
|
203
|
+
const fetchError = { status: 400, response: "", url: "" };
|
|
204
|
+
// The legacy test modeled the failing action with a rejecting stub; the
|
|
205
|
+
// failure alert itself is driven by the fetchError prop the reducer sets.
|
|
206
|
+
const { container, rerender } = renderReset({
|
|
207
|
+
patron: patrons[0],
|
|
208
|
+
resetAdobeId: jest.fn().mockRejectedValue(fetchError),
|
|
209
|
+
});
|
|
210
|
+
expect(container.querySelector(".alert-danger")).not.toBeInTheDocument();
|
|
211
|
+
|
|
212
|
+
rerender(
|
|
213
|
+
resetElement({
|
|
214
|
+
patron: patrons[0],
|
|
215
|
+
resetAdobeId: jest.fn().mockRejectedValue(fetchError),
|
|
216
|
+
fetchError,
|
|
217
|
+
})
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
const alert = container.querySelector(".alert-danger");
|
|
221
|
+
expect(alert).toBeInTheDocument();
|
|
222
|
+
expect(alert).toHaveTextContent(
|
|
223
|
+
"Error: failed to reset Adobe ID for patron 1234"
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("updates the warning message if a new patron is searched and found", () => {
|
|
228
|
+
const { container, rerender } = renderReset({
|
|
229
|
+
patron: patrons[0],
|
|
230
|
+
resetAdobeId: jest.fn(),
|
|
231
|
+
});
|
|
232
|
+
expect(container.querySelector(".patron-warning")).toHaveTextContent(
|
|
233
|
+
"IMPORTANT: Patron User Name will lose access"
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
rerender(resetElement({ patron: patrons[1], resetAdobeId: jest.fn() }));
|
|
237
|
+
|
|
238
|
+
expect(container.querySelector(".patron-warning")).toHaveTextContent(
|
|
239
|
+
"IMPORTANT: Patron Personal Name2 will lose access"
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("restores the checkbox and the reset button if a new patron is searched and found", () => {
|
|
244
|
+
const { container, rerender } = renderReset({
|
|
245
|
+
patron: patrons[0],
|
|
246
|
+
responseBody: "Adobe ID for patron has been reset.",
|
|
247
|
+
resetAdobeId: jest.fn(),
|
|
248
|
+
});
|
|
249
|
+
expect(
|
|
250
|
+
container.querySelector(".reset-adobe-id input")
|
|
251
|
+
).not.toBeInTheDocument();
|
|
252
|
+
expect(
|
|
253
|
+
container.querySelector(".reset-adobe-id button")
|
|
254
|
+
).not.toBeInTheDocument();
|
|
255
|
+
|
|
256
|
+
rerender(
|
|
257
|
+
resetElement({
|
|
258
|
+
patron: patrons[0],
|
|
259
|
+
responseBody: "",
|
|
260
|
+
resetAdobeId: jest.fn(),
|
|
261
|
+
})
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
expect(
|
|
265
|
+
container.querySelector(".reset-adobe-id input")
|
|
266
|
+
).toBeInTheDocument();
|
|
267
|
+
expect(
|
|
268
|
+
container.querySelector(".reset-adobe-id button")
|
|
269
|
+
).toBeInTheDocument();
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
describe("connected (default export)", () => {
|
|
274
|
+
it("wires state and dispatch through connect", async () => {
|
|
275
|
+
// Defensive: nothing fetches on this mount, but stub fetch so any stray
|
|
276
|
+
// request settles rather than crashing the strict worker.
|
|
277
|
+
jest.spyOn(globalThis, "fetch").mockResolvedValue(
|
|
278
|
+
new Response(JSON.stringify({}), {
|
|
279
|
+
status: 200,
|
|
280
|
+
headers: { "Content-Type": "application/json" },
|
|
281
|
+
})
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
renderWithProviders(
|
|
285
|
+
<ConnectedResetAdobeId
|
|
286
|
+
store={buildStore()}
|
|
287
|
+
csrfToken="token"
|
|
288
|
+
library="nypl"
|
|
289
|
+
/>
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
// With no patron in the store, mapStateToProps yields patron=undefined and
|
|
293
|
+
// the connected component prompts the user to search.
|
|
294
|
+
expect(
|
|
295
|
+
await screen.findByRole("heading", { name: "Reset Adobe ID" })
|
|
296
|
+
).toBeInTheDocument();
|
|
297
|
+
expect(
|
|
298
|
+
screen.getByText("Search for a patron to begin.")
|
|
299
|
+
).toBeInTheDocument();
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
});
|