@thepalaceproject/circulation-admin 1.43.0 → 1.44.0-post.3
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 +28 -7
- package/README.md +11 -5
- package/dist/circulation-admin.js +1 -1
- package/eslint.config.mjs +9 -1
- package/jest.config.js +22 -8
- package/package.json +6 -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/AdvancedSearchBuilder.test.tsx +93 -20
- 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/Announcement.test.tsx +76 -0
- package/tests/jest/components/AnnouncementForm.test.tsx +229 -0
- package/tests/jest/components/AnnouncementsSection.test.tsx +146 -0
- package/tests/jest/components/Autocomplete.test.tsx +67 -0
- package/tests/jest/components/BookCoverEditor.test.tsx +393 -0
- package/tests/jest/components/BookDetails.test.tsx +133 -0
- package/tests/jest/components/BookDetailsContainer.test.tsx +116 -0
- package/tests/jest/components/BookDetailsEditor.test.tsx +275 -0
- package/tests/jest/components/BookDetailsTabContainer.test.tsx +237 -0
- package/tests/jest/components/BookEditForm.test.tsx +352 -0
- package/tests/jest/components/BookEditor.test.tsx +20 -21
- package/tests/jest/components/CatalogPage.test.tsx +158 -0
- package/tests/jest/components/CatalogServices.test.tsx +100 -0
- package/tests/jest/components/ChangePasswordForm.test.tsx +163 -0
- package/tests/jest/components/CirculationEventsDownloadForm.test.tsx +41 -0
- package/tests/jest/components/Classifications.test.tsx +166 -0
- package/tests/jest/components/ClassificationsForm.test.tsx +458 -0
- package/tests/jest/components/ClassificationsTable.test.tsx +57 -0
- package/tests/jest/components/Collections.test.tsx +335 -7
- 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/ConfigPage.test.tsx +58 -0
- package/tests/jest/components/ConfigTabContainer.test.tsx +196 -0
- package/tests/jest/components/ContextProvider.test.tsx +138 -0
- package/tests/jest/components/Contributors.test.tsx +157 -0
- package/tests/jest/components/CustomListEditor.test.tsx +367 -0
- package/tests/jest/components/CustomListEntriesEditor.test.tsx +1240 -0
- package/tests/jest/components/CustomListPage.test.tsx +57 -0
- package/tests/jest/components/CustomListSearch.test.tsx +275 -0
- package/tests/jest/components/CustomLists.test.tsx +621 -4
- package/tests/jest/components/CustomListsForBook.test.tsx +353 -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/DiagnosticsServiceTabs.test.tsx +141 -0
- package/tests/jest/components/DiagnosticsServiceType.test.tsx +75 -0
- package/tests/jest/components/DiagnosticsTabContainer.test.tsx +147 -0
- package/tests/jest/components/DiscoveryServices.test.tsx +154 -7
- package/tests/jest/components/EditableConfigList.test.tsx +606 -9
- package/tests/jest/components/EditableInput.test.tsx +250 -1
- package/tests/jest/components/EditorField.test.tsx +95 -0
- 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/GenreForm.test.tsx +152 -0
- package/tests/jest/components/Header.test.tsx +421 -0
- package/tests/jest/components/IndividualAdminEditForm.test.tsx +506 -1
- package/tests/jest/components/IndividualAdmins.test.tsx +124 -8
- package/tests/jest/components/InputList.test.tsx +493 -0
- package/tests/jest/components/InventoryReportRequestModal.test.tsx +16 -42
- package/tests/jest/components/JsonField.test.tsx +20 -0
- package/tests/jest/components/Lane.test.tsx +132 -10
- package/tests/jest/components/LaneCustomListsEditor.test.tsx +450 -0
- package/tests/jest/components/LaneEditor.test.tsx +377 -13
- package/tests/jest/components/LanePage.test.tsx +68 -0
- package/tests/jest/components/Lanes.test.tsx +446 -0
- package/tests/jest/components/LanesSidebar.test.tsx +621 -0
- package/tests/jest/components/LanguageField.test.tsx +40 -0
- package/tests/jest/components/Libraries.test.tsx +48 -0
- package/tests/jest/components/LibraryEditForm.test.tsx +383 -0
- package/tests/jest/components/LibraryRegistration.test.tsx +354 -0
- package/tests/jest/components/LibraryRegistrationForm.test.tsx +203 -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 +95 -0
- package/tests/jest/components/NeighborhoodAnalyticsForm.test.tsx +167 -0
- package/tests/jest/components/PairedMenus.test.tsx +220 -0
- package/tests/jest/components/PatronAuthServices.test.tsx +114 -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 +479 -7
- package/tests/jest/components/ResetAdobeId.test.tsx +302 -0
- package/tests/jest/components/SaveButton.test.tsx +47 -0
- package/tests/jest/components/SelfTestResult.test.tsx +164 -0
- package/tests/jest/components/SelfTests.test.tsx +601 -0
- package/tests/jest/components/SelfTestsCategory.test.tsx +200 -0
- package/tests/jest/components/SelfTestsTabContainer.test.tsx +216 -0
- package/tests/jest/components/ServiceEditForm.test.tsx +1092 -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/SitewideAnnouncements.test.tsx +134 -0
- package/tests/jest/components/Stats.test.tsx +4 -15
- package/tests/jest/components/TabContainer.test.tsx +68 -0
- package/tests/jest/components/TextWithEditMode.test.tsx +218 -0
- package/tests/jest/components/Timestamp.test.tsx +74 -0
- package/tests/jest/components/ToolTip.test.tsx +46 -0
- package/tests/jest/components/TroubleshootingCategoryPage.test.tsx +69 -0
- package/tests/jest/components/TroubleshootingPage.test.tsx +94 -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/features/book.test.ts +75 -80
- package/tests/jest/fixtures/classificationsData.ts +45 -0
- package/tests/jest/fixtures/genreData.ts +811 -0
- package/tests/jest/index.test.tsx +50 -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/README.md +0 -6
|
@@ -0,0 +1,41 @@
|
|
|
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 with no behavior worth exercising here, and
|
|
7
|
+
// Header pulls in the react-router v3 context that `renderWithProviders` does not
|
|
8
|
+
// supply. Mock them to markers so the test stays about what AccountPage composes.
|
|
9
|
+
jest.mock("../../../src/components/Header", () => ({
|
|
10
|
+
__esModule: true,
|
|
11
|
+
default: () => <div data-testid="header" />,
|
|
12
|
+
}));
|
|
13
|
+
jest.mock("../../../src/components/Footer", () => ({
|
|
14
|
+
__esModule: true,
|
|
15
|
+
default: () => <div data-testid="footer" />,
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
import AccountPage from "../../../src/components/AccountPage";
|
|
19
|
+
|
|
20
|
+
describe("AccountPage", () => {
|
|
21
|
+
it("renders the header and footer chrome", () => {
|
|
22
|
+
renderWithProviders(<AccountPage />);
|
|
23
|
+
|
|
24
|
+
expect(screen.getByTestId("header")).toBeInTheDocument();
|
|
25
|
+
expect(screen.getByTestId("footer")).toBeInTheDocument();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("renders the change-password form", () => {
|
|
29
|
+
renderWithProviders(<AccountPage />);
|
|
30
|
+
|
|
31
|
+
// ChangePasswordForm is connected; its heading and fields appearing proves
|
|
32
|
+
// it received a working store and csrf token from context. EditableInput
|
|
33
|
+
// renders its label and input as siblings with no htmlFor, so there is no
|
|
34
|
+
// accessible label association to query — assert the label text instead.
|
|
35
|
+
expect(
|
|
36
|
+
screen.getByRole("heading", { name: "Change Password" })
|
|
37
|
+
).toBeInTheDocument();
|
|
38
|
+
expect(screen.getByText("New Password")).toBeInTheDocument();
|
|
39
|
+
expect(screen.getByText("Confirm New Password")).toBeInTheDocument();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import userEvent from "@testing-library/user-event";
|
|
4
|
+
import { DndProvider } from "react-dnd";
|
|
5
|
+
import { HTML5Backend } from "react-dnd-html5-backend";
|
|
6
|
+
import AdvancedSearchBooleanFilter from "../../../src/components/AdvancedSearchBooleanFilter";
|
|
7
|
+
|
|
8
|
+
describe("AdvancedSearchBooleanFilter", () => {
|
|
9
|
+
let onBooleanChange;
|
|
10
|
+
let onMove;
|
|
11
|
+
let onRemove;
|
|
12
|
+
let onSelect;
|
|
13
|
+
let query;
|
|
14
|
+
|
|
15
|
+
const renderFilter = (props = {}) =>
|
|
16
|
+
render(
|
|
17
|
+
<DndProvider backend={HTML5Backend}>
|
|
18
|
+
<AdvancedSearchBooleanFilter
|
|
19
|
+
query={query}
|
|
20
|
+
selectedQueryId="91"
|
|
21
|
+
onBooleanChange={onBooleanChange}
|
|
22
|
+
onMove={onMove}
|
|
23
|
+
onSelect={onSelect}
|
|
24
|
+
onRemove={onRemove}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
</DndProvider>
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
onBooleanChange = jest.fn();
|
|
32
|
+
onMove = jest.fn();
|
|
33
|
+
onRemove = jest.fn();
|
|
34
|
+
onSelect = jest.fn();
|
|
35
|
+
|
|
36
|
+
query = {
|
|
37
|
+
id: "90",
|
|
38
|
+
and: [
|
|
39
|
+
{
|
|
40
|
+
id: "91",
|
|
41
|
+
key: "title",
|
|
42
|
+
value: "foo",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "92",
|
|
46
|
+
key: "title",
|
|
47
|
+
value: "bar",
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should render a select containing an option for each boolean operator, with the current operator selected", () => {
|
|
54
|
+
renderFilter();
|
|
55
|
+
|
|
56
|
+
const select = screen.getByRole("combobox");
|
|
57
|
+
const options = screen.getAllByRole("option");
|
|
58
|
+
|
|
59
|
+
expect(options).toHaveLength(2);
|
|
60
|
+
|
|
61
|
+
expect(options[0]).toHaveValue("and");
|
|
62
|
+
expect(options[0]).toHaveTextContent(
|
|
63
|
+
"All of these filters must be matched:"
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
expect(options[1]).toHaveValue("or");
|
|
67
|
+
expect(options[1]).toHaveTextContent(
|
|
68
|
+
"Any of these filters may be matched:"
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
expect(select).toHaveValue("and");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should disable the boolean operator select when readOnly is true", () => {
|
|
75
|
+
renderFilter({ readOnly: true });
|
|
76
|
+
|
|
77
|
+
expect(screen.getByRole("combobox")).toBeDisabled();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should call onBooleanChange when the boolean operator changes", async () => {
|
|
81
|
+
const user = userEvent.setup();
|
|
82
|
+
renderFilter();
|
|
83
|
+
|
|
84
|
+
await user.selectOptions(screen.getByRole("combobox"), "or");
|
|
85
|
+
|
|
86
|
+
expect(onBooleanChange).toHaveBeenCalledTimes(1);
|
|
87
|
+
expect(onBooleanChange).toHaveBeenCalledWith("90", "or");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("should render a remove button", () => {
|
|
91
|
+
const { container } = renderFilter();
|
|
92
|
+
|
|
93
|
+
const button = container.querySelector(
|
|
94
|
+
".advanced-search-boolean-filter > header button"
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
expect(button).toBeInTheDocument();
|
|
98
|
+
expect(button).toHaveTextContent("×");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should not render a remove button when readOnly is true", () => {
|
|
102
|
+
const { container } = renderFilter({ readOnly: true });
|
|
103
|
+
|
|
104
|
+
// readOnly propagates to all descendants, so there are no remove buttons at all.
|
|
105
|
+
expect(
|
|
106
|
+
container.querySelector(".advanced-search-boolean-filter > header button")
|
|
107
|
+
).not.toBeInTheDocument();
|
|
108
|
+
expect(screen.queryByRole("button")).not.toBeInTheDocument();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("should call onRemove when the remove button is clicked", async () => {
|
|
112
|
+
const user = userEvent.setup();
|
|
113
|
+
const { container } = renderFilter();
|
|
114
|
+
|
|
115
|
+
await user.click(
|
|
116
|
+
container.querySelector(".advanced-search-boolean-filter > header button")
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
expect(onRemove).toHaveBeenCalledTimes(1);
|
|
120
|
+
expect(onRemove).toHaveBeenCalledWith("90");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should call onSelect when clicked", async () => {
|
|
124
|
+
const user = userEvent.setup();
|
|
125
|
+
const { container } = renderFilter();
|
|
126
|
+
|
|
127
|
+
await user.click(
|
|
128
|
+
container.querySelector(".advanced-search-boolean-filter")
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
expect(onSelect).toHaveBeenCalledTimes(1);
|
|
132
|
+
expect(onSelect).toHaveBeenCalledWith("90");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("should call onSelect when the space bar is depressed", async () => {
|
|
136
|
+
const user = userEvent.setup();
|
|
137
|
+
const { container } = renderFilter();
|
|
138
|
+
|
|
139
|
+
// The filter is a focusable treeitem; focus it and press space, as a
|
|
140
|
+
// keyboard user would.
|
|
141
|
+
const item = container.querySelector(
|
|
142
|
+
".advanced-search-boolean-filter"
|
|
143
|
+
) as HTMLElement;
|
|
144
|
+
item.focus();
|
|
145
|
+
await user.keyboard(" ");
|
|
146
|
+
|
|
147
|
+
expect(onSelect).toHaveBeenCalledTimes(1);
|
|
148
|
+
expect(onSelect).toHaveBeenCalledWith("90");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("should render an AdvancedSearchFilter for each child", () => {
|
|
152
|
+
const { container } = renderFilter();
|
|
153
|
+
|
|
154
|
+
// Each child value query renders an AdvancedSearchValueFilter showing its label.
|
|
155
|
+
const childFilters = container.querySelectorAll(
|
|
156
|
+
".advanced-search-value-filter"
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
expect(childFilters).toHaveLength(2);
|
|
160
|
+
expect(screen.getByText("Title foo")).toBeInTheDocument();
|
|
161
|
+
expect(screen.getByText("Title bar")).toBeInTheDocument();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("should propagate the readOnly prop to AdvancedSearchFilter children", () => {
|
|
165
|
+
const { container } = renderFilter({ readOnly: true });
|
|
166
|
+
|
|
167
|
+
// The two children still render, but readOnly means none of them (or the
|
|
168
|
+
// parent) render a remove button.
|
|
169
|
+
expect(
|
|
170
|
+
container.querySelectorAll(".advanced-search-value-filter")
|
|
171
|
+
).toHaveLength(2);
|
|
172
|
+
expect(screen.queryAllByRole("button")).toHaveLength(0);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should separate child filters with "and" when the boolean operator is AND', () => {
|
|
176
|
+
const { container } = renderFilter();
|
|
177
|
+
|
|
178
|
+
const separators = container.querySelectorAll(
|
|
179
|
+
".advanced-search-boolean-filter > ul > li > span"
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
expect(separators).toHaveLength(1);
|
|
183
|
+
expect(separators[0]).toHaveTextContent("and");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should separate child filters with "or" when the boolean operator is OR', () => {
|
|
187
|
+
query = {
|
|
188
|
+
id: "90",
|
|
189
|
+
or: [
|
|
190
|
+
{
|
|
191
|
+
id: "91",
|
|
192
|
+
key: "title",
|
|
193
|
+
value: "foo",
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: "92",
|
|
197
|
+
key: "title",
|
|
198
|
+
value: "bar",
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const { container } = renderFilter();
|
|
204
|
+
|
|
205
|
+
const separators = container.querySelectorAll(
|
|
206
|
+
".advanced-search-boolean-filter > ul > li > span"
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
expect(separators).toHaveLength(1);
|
|
210
|
+
expect(separators[0]).toHaveTextContent("or");
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("should apply the selected class when the query id equals the selectedQueryId", () => {
|
|
214
|
+
const { container } = renderFilter({ selectedQueryId: "90" });
|
|
215
|
+
|
|
216
|
+
expect(
|
|
217
|
+
container.querySelector(".advanced-search-boolean-filter")
|
|
218
|
+
).toHaveClass("selected");
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("should not apply the selected class when readOnly is true", () => {
|
|
222
|
+
const { container } = renderFilter({
|
|
223
|
+
readOnly: true,
|
|
224
|
+
selectedQueryId: "90",
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
expect(
|
|
228
|
+
container.querySelector(".advanced-search-boolean-filter")
|
|
229
|
+
).not.toHaveClass("selected");
|
|
230
|
+
});
|
|
231
|
+
});
|
|
@@ -1,38 +1,111 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { render, screen } from "@testing-library/react";
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
// AdvancedSearchBuilder is a pure forwarding component: it renders an
|
|
5
|
+
// AdvancedSearchFilterInput and an AdvancedSearchFilterViewer and threads their
|
|
6
|
+
// callbacks up to the builder's handlers, prefixed with the builder `name`. Mock
|
|
7
|
+
// both children to markers that capture the props they receive, so those
|
|
8
|
+
// forwarding closures can be exercised directly — including `onMove`, which is
|
|
9
|
+
// otherwise only reachable through a real react-dnd drag that jsdom can't perform.
|
|
10
|
+
let inputProps: any;
|
|
11
|
+
let viewerProps: any;
|
|
12
|
+
jest.mock("../../../src/components/AdvancedSearchFilterInput", () => ({
|
|
13
|
+
__esModule: true,
|
|
14
|
+
default: (props: any) => {
|
|
15
|
+
inputProps = props;
|
|
16
|
+
return <div data-testid="filter-input" data-name={props.name} />;
|
|
17
|
+
},
|
|
18
|
+
}));
|
|
19
|
+
jest.mock("../../../src/components/AdvancedSearchFilterViewer", () => ({
|
|
20
|
+
__esModule: true,
|
|
21
|
+
default: (props: any) => {
|
|
22
|
+
viewerProps = props;
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
data-testid="filter-viewer"
|
|
26
|
+
data-read-only={String(props.readOnly)}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
},
|
|
30
|
+
}));
|
|
31
|
+
|
|
4
32
|
import AdvancedSearchBuilder from "../../../src/components/AdvancedSearchBuilder";
|
|
5
33
|
|
|
6
34
|
describe("AdvancedSearchBuilder", () => {
|
|
7
|
-
|
|
8
|
-
userEvent.setup();
|
|
35
|
+
const query = { id: "0", key: "genre", value: "Horror" };
|
|
9
36
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
37
|
+
const renderBuilder = (overrides: Record<string, unknown> = {}) => {
|
|
38
|
+
const handlers = {
|
|
39
|
+
addQuery: jest.fn(),
|
|
40
|
+
updateClearFiltersFlag: jest.fn(),
|
|
41
|
+
updateQueryBoolean: jest.fn(),
|
|
42
|
+
moveQuery: jest.fn(),
|
|
43
|
+
removeQuery: jest.fn(),
|
|
44
|
+
selectQuery: jest.fn(),
|
|
14
45
|
};
|
|
15
|
-
|
|
16
46
|
render(
|
|
17
47
|
<AdvancedSearchBuilder
|
|
18
|
-
isOwner
|
|
19
|
-
name="
|
|
20
|
-
query={query}
|
|
48
|
+
isOwner
|
|
49
|
+
name="include"
|
|
50
|
+
query={query as any}
|
|
21
51
|
selectedQueryId="0"
|
|
52
|
+
{...handlers}
|
|
53
|
+
{...overrides}
|
|
22
54
|
/>
|
|
23
55
|
);
|
|
56
|
+
return handlers;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
inputProps = undefined;
|
|
61
|
+
viewerProps = undefined;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("renders the filter input and the viewer", () => {
|
|
65
|
+
renderBuilder();
|
|
66
|
+
|
|
67
|
+
expect(screen.getByTestId("filter-input")).toHaveAttribute(
|
|
68
|
+
"data-name",
|
|
69
|
+
"include"
|
|
70
|
+
);
|
|
71
|
+
expect(screen.getByTestId("filter-viewer")).toBeInTheDocument();
|
|
72
|
+
expect(viewerProps.query).toBe(query);
|
|
73
|
+
expect(viewerProps.selectedQueryId).toBe("0");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("hides the filter input and marks the viewer read-only when not the owner", () => {
|
|
77
|
+
renderBuilder({ isOwner: false });
|
|
24
78
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
79
|
+
expect(screen.queryByTestId("filter-input")).not.toBeInTheDocument();
|
|
80
|
+
expect(screen.getByTestId("filter-viewer")).toHaveAttribute(
|
|
81
|
+
"data-read-only",
|
|
82
|
+
"true"
|
|
28
83
|
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("forwards the input's onAdd and clear-filters callbacks with the builder name", () => {
|
|
87
|
+
const { addQuery, updateClearFiltersFlag } = renderBuilder();
|
|
88
|
+
|
|
89
|
+
const newQuery = { key: "title", value: "The Shining" };
|
|
90
|
+
inputProps.onAdd(newQuery);
|
|
91
|
+
inputProps.onClearFiltersFlagChange(true);
|
|
92
|
+
|
|
93
|
+
expect(addQuery).toHaveBeenCalledWith("include", newQuery);
|
|
94
|
+
expect(updateClearFiltersFlag).toHaveBeenCalledWith("include", true);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("forwards the viewer's callbacks to their handlers with the builder name", () => {
|
|
98
|
+
const { updateQueryBoolean, moveQuery, removeQuery, selectQuery } =
|
|
99
|
+
renderBuilder();
|
|
29
100
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
101
|
+
viewerProps.onBooleanChange("2", "or");
|
|
102
|
+
viewerProps.onMove("2", "33");
|
|
103
|
+
viewerProps.onRemove("2");
|
|
104
|
+
viewerProps.onSelect("2");
|
|
33
105
|
|
|
34
|
-
expect(
|
|
35
|
-
expect(
|
|
36
|
-
expect(
|
|
106
|
+
expect(updateQueryBoolean).toHaveBeenCalledWith("include", "2", "or");
|
|
107
|
+
expect(moveQuery).toHaveBeenCalledWith("include", "2", "33");
|
|
108
|
+
expect(removeQuery).toHaveBeenCalledWith("include", "2");
|
|
109
|
+
expect(selectQuery).toHaveBeenCalledWith("include", "2");
|
|
37
110
|
});
|
|
38
111
|
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { DndProvider } from "react-dnd";
|
|
4
|
+
import { HTML5Backend } from "react-dnd-html5-backend";
|
|
5
|
+
import AdvancedSearchFilter from "../../../src/components/AdvancedSearchFilter";
|
|
6
|
+
|
|
7
|
+
describe("AdvancedSearchFilter", () => {
|
|
8
|
+
let onBooleanChange;
|
|
9
|
+
let onMove;
|
|
10
|
+
let onSelect;
|
|
11
|
+
let onRemove;
|
|
12
|
+
|
|
13
|
+
const renderFilter = (props) =>
|
|
14
|
+
render(
|
|
15
|
+
<DndProvider backend={HTML5Backend}>
|
|
16
|
+
<AdvancedSearchFilter
|
|
17
|
+
onBooleanChange={onBooleanChange}
|
|
18
|
+
onMove={onMove}
|
|
19
|
+
onSelect={onSelect}
|
|
20
|
+
onRemove={onRemove}
|
|
21
|
+
{...props}
|
|
22
|
+
/>
|
|
23
|
+
</DndProvider>
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
onBooleanChange = jest.fn();
|
|
28
|
+
onMove = jest.fn();
|
|
29
|
+
onSelect = jest.fn();
|
|
30
|
+
onRemove = jest.fn();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should render an AdvancedSearchBooleanFilter if the query is a boolean AND", () => {
|
|
34
|
+
const query = {
|
|
35
|
+
id: "90",
|
|
36
|
+
and: [
|
|
37
|
+
{
|
|
38
|
+
id: "91",
|
|
39
|
+
key: "title",
|
|
40
|
+
value: "foo",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "92",
|
|
44
|
+
key: "title",
|
|
45
|
+
value: "bar",
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const { container } = renderFilter({ query, selectedQueryId: "90" });
|
|
51
|
+
|
|
52
|
+
// The boolean filter is the only thing with a select (the operator picker),
|
|
53
|
+
// and it renders one value filter per child.
|
|
54
|
+
const select = screen.getByRole("combobox");
|
|
55
|
+
expect(select).toHaveValue("and");
|
|
56
|
+
expect(
|
|
57
|
+
container.querySelectorAll(".advanced-search-value-filter")
|
|
58
|
+
).toHaveLength(2);
|
|
59
|
+
expect(screen.getByText("Title foo")).toBeInTheDocument();
|
|
60
|
+
expect(screen.getByText("Title bar")).toBeInTheDocument();
|
|
61
|
+
|
|
62
|
+
// selectedQueryId matches the boolean query, so it is selected.
|
|
63
|
+
expect(
|
|
64
|
+
container.querySelector(".advanced-search-boolean-filter")
|
|
65
|
+
).toHaveClass("selected");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should render an AdvancedSearchBooleanFilter if the query is a boolean OR", () => {
|
|
69
|
+
const query = {
|
|
70
|
+
id: "90",
|
|
71
|
+
or: [
|
|
72
|
+
{
|
|
73
|
+
id: "91",
|
|
74
|
+
key: "title",
|
|
75
|
+
value: "foo",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: "92",
|
|
79
|
+
key: "title",
|
|
80
|
+
value: "bar",
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
renderFilter({ query, readOnly: true, selectedQueryId: "90" });
|
|
86
|
+
|
|
87
|
+
const select = screen.getByRole("combobox");
|
|
88
|
+
expect(select).toHaveValue("or");
|
|
89
|
+
// readOnly disables the operator select and removes all remove buttons.
|
|
90
|
+
expect(select).toBeDisabled();
|
|
91
|
+
expect(screen.queryByRole("button")).not.toBeInTheDocument();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should render an AdvancedSearchValueFilter if the query is a not a boolean", () => {
|
|
95
|
+
const query = {
|
|
96
|
+
id: "91",
|
|
97
|
+
key: "title",
|
|
98
|
+
value: "foo",
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
renderFilter({
|
|
102
|
+
query,
|
|
103
|
+
readOnly: true,
|
|
104
|
+
selectedQueryId: "90",
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// A value filter has no operator select; readOnly removes its remove button.
|
|
108
|
+
expect(screen.getByText("Title foo")).toBeInTheDocument();
|
|
109
|
+
expect(screen.queryByRole("combobox")).not.toBeInTheDocument();
|
|
110
|
+
expect(screen.queryByRole("button")).not.toBeInTheDocument();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("should not select the AdvancedSearchValueFilter when selectedQueryId does not match the query id", () => {
|
|
114
|
+
const query = {
|
|
115
|
+
id: "91",
|
|
116
|
+
key: "title",
|
|
117
|
+
value: "foo",
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Not readOnly, so selection is driven purely by the id comparison:
|
|
121
|
+
// selectedQueryId ("90") does not match the query id ("91").
|
|
122
|
+
const { container } = renderFilter({ query, selectedQueryId: "90" });
|
|
123
|
+
|
|
124
|
+
expect(
|
|
125
|
+
container.querySelector(".advanced-search-value-filter")
|
|
126
|
+
).not.toHaveClass("selected");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("should select the AdvancedSearchValueFilter if the query is a not a boolean and selectedQueryId is the id of the query", () => {
|
|
130
|
+
const query = {
|
|
131
|
+
id: "91",
|
|
132
|
+
key: "title",
|
|
133
|
+
value: "foo",
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const { container } = renderFilter({ query, selectedQueryId: "91" });
|
|
137
|
+
|
|
138
|
+
expect(
|
|
139
|
+
container.querySelector(".advanced-search-value-filter")
|
|
140
|
+
).toHaveClass("selected");
|
|
141
|
+
});
|
|
142
|
+
});
|