@thepalaceproject/circulation-admin 1.43.0-post.8 → 1.43.0-post.9
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/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/CatalogServices.test.tsx +63 -0
- package/tests/jest/components/CirculationEventsDownloadForm.test.tsx +41 -0
- package/tests/jest/components/CustomListSearch.test.tsx +275 -0
- package/tests/jest/components/CustomListsSidebar.test.tsx +251 -0
- package/tests/jest/components/DiagnosticsTabContainer.test.tsx +147 -0
- package/tests/jest/components/ErrorMessage.test.tsx +97 -0
- package/tests/jest/components/Footer.test.tsx +141 -0
- package/tests/jest/components/LanguageField.test.tsx +40 -0
- package/tests/jest/components/LoadButton.test.tsx +49 -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/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/TabContainer.test.tsx +68 -0
- package/tests/jest/components/Timestamp.test.tsx +74 -0
- package/tests/jest/components/WithEditButton.test.tsx +55 -0
- package/tests/jest/components/WithRemoveButton.test.tsx +95 -0
- package/tests/jest/index.test.tsx +50 -0
- package/tests/jest/components/AdvancedSearchBuilder.test.tsx +0 -38
package/package.json
CHANGED
|
@@ -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
|
+
});
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,212 @@
|
|
|
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 AdvancedSearchFilterInput from "../../../src/components/AdvancedSearchFilterInput";
|
|
5
|
+
import {
|
|
6
|
+
fields,
|
|
7
|
+
operators,
|
|
8
|
+
} from "../../../src/components/AdvancedSearchBuilder";
|
|
9
|
+
|
|
10
|
+
describe("AdvancedSearchFilterInput", () => {
|
|
11
|
+
let onAdd;
|
|
12
|
+
let onClearFiltersFlagChange;
|
|
13
|
+
|
|
14
|
+
const renderInput = () =>
|
|
15
|
+
render(
|
|
16
|
+
<AdvancedSearchFilterInput
|
|
17
|
+
name="include"
|
|
18
|
+
onAdd={onAdd}
|
|
19
|
+
onClearFiltersFlagChange={onClearFiltersFlagChange}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const fieldKeySelect = () =>
|
|
24
|
+
screen.getByRole("combobox", { name: "filter field key" });
|
|
25
|
+
const operatorSelect = () =>
|
|
26
|
+
screen.getByRole("combobox", { name: "filter operator" });
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
onAdd = jest.fn();
|
|
30
|
+
onClearFiltersFlagChange = jest.fn();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should render select with field names for field selection", () => {
|
|
34
|
+
renderInput();
|
|
35
|
+
|
|
36
|
+
const options = within(fieldKeySelect()).getAllByRole("option");
|
|
37
|
+
|
|
38
|
+
expect(options).toHaveLength(fields.length);
|
|
39
|
+
|
|
40
|
+
fields.forEach((field, index) => {
|
|
41
|
+
expect(options[index]).toHaveValue(field.value);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should render a select with options for operator selection", () => {
|
|
46
|
+
renderInput();
|
|
47
|
+
|
|
48
|
+
const options = within(operatorSelect()).getAllByRole("option");
|
|
49
|
+
|
|
50
|
+
expect(options).toHaveLength(operators.length);
|
|
51
|
+
|
|
52
|
+
operators.forEach((op, index) => {
|
|
53
|
+
expect(options[index]).toHaveValue(op.value);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should restrict the operator selection, if the filter requests it", async () => {
|
|
58
|
+
const user = userEvent.setup();
|
|
59
|
+
renderInput();
|
|
60
|
+
|
|
61
|
+
// Select the fiction field key.
|
|
62
|
+
await user.selectOptions(fieldKeySelect(), "fiction");
|
|
63
|
+
|
|
64
|
+
// Look up the fiction field operator values.
|
|
65
|
+
const fictionField = fields.find((element) => element.value === "fiction");
|
|
66
|
+
const options = within(operatorSelect()).getAllByRole("option");
|
|
67
|
+
|
|
68
|
+
// And make sure the number of options is correct.
|
|
69
|
+
expect(options).toHaveLength(fictionField.operators.length);
|
|
70
|
+
options.forEach((option, index) => {
|
|
71
|
+
expect(option).toHaveValue(fictionField.operators[index]);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should use the currently selected operator when changing filters", async () => {
|
|
76
|
+
const user = userEvent.setup();
|
|
77
|
+
renderInput();
|
|
78
|
+
|
|
79
|
+
const fictionField = fields.find((element) => element.value === "fiction");
|
|
80
|
+
|
|
81
|
+
await user.selectOptions(operatorSelect(), "regex");
|
|
82
|
+
|
|
83
|
+
// Select data source/Distributor as the filter key.
|
|
84
|
+
await user.selectOptions(fieldKeySelect(), "data_source");
|
|
85
|
+
expect(operatorSelect()).toHaveValue("regex");
|
|
86
|
+
|
|
87
|
+
// But the "regex" operator is not supported by the "fiction" field,
|
|
88
|
+
// so we switch to fiction's first available operator.
|
|
89
|
+
await user.selectOptions(fieldKeySelect(), "fiction");
|
|
90
|
+
expect(operatorSelect()).toHaveValue(fictionField.operators[0]);
|
|
91
|
+
|
|
92
|
+
// Since data source/distributor supports fiction's first operator,
|
|
93
|
+
// we'll continue to use it when changing back to that field.
|
|
94
|
+
await user.selectOptions(fieldKeySelect(), "data_source");
|
|
95
|
+
expect(operatorSelect()).toHaveValue(fictionField.operators[0]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should render a text input for value entry", () => {
|
|
99
|
+
renderInput();
|
|
100
|
+
|
|
101
|
+
expect(
|
|
102
|
+
screen.getByRole("textbox", { name: "filter value" })
|
|
103
|
+
).toBeInTheDocument();
|
|
104
|
+
expect(
|
|
105
|
+
screen.queryByRole("combobox", { name: "filter value" })
|
|
106
|
+
).not.toBeInTheDocument();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("surfaces the field's placeholder and help text for the publication date field", async () => {
|
|
110
|
+
const user = userEvent.setup();
|
|
111
|
+
renderInput();
|
|
112
|
+
|
|
113
|
+
// "publication date" is the only field that defines a placeholder and
|
|
114
|
+
// helpText; selecting it surfaces both on the value input (as its
|
|
115
|
+
// placeholder and accessible description). This is the sole coverage for
|
|
116
|
+
// those two props of AdvancedSearchFilterInput.
|
|
117
|
+
await user.selectOptions(fieldKeySelect(), "published");
|
|
118
|
+
|
|
119
|
+
const valueInput = screen.getByRole("textbox", { name: "filter value" });
|
|
120
|
+
expect(valueInput).toHaveAttribute("placeholder", "YYYY-MM-DD");
|
|
121
|
+
expect(valueInput).toHaveAccessibleDescription(/publication date/i);
|
|
122
|
+
expect(valueInput).toHaveAccessibleDescription(/YYYY-MM-DD/i);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("should render a select for value selection, if the filter requests it", async () => {
|
|
126
|
+
const user = userEvent.setup();
|
|
127
|
+
renderInput();
|
|
128
|
+
|
|
129
|
+
await user.selectOptions(fieldKeySelect(), "fiction");
|
|
130
|
+
|
|
131
|
+
expect(
|
|
132
|
+
screen.getByRole("combobox", { name: "filter value" })
|
|
133
|
+
).toBeInTheDocument();
|
|
134
|
+
expect(
|
|
135
|
+
screen.queryByRole("textbox", { name: "filter value" })
|
|
136
|
+
).not.toBeInTheDocument();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should clear the current value when changing the filter", async () => {
|
|
140
|
+
const user = userEvent.setup();
|
|
141
|
+
renderInput();
|
|
142
|
+
|
|
143
|
+
const valueInput = screen.getByRole("textbox", { name: "filter value" });
|
|
144
|
+
|
|
145
|
+
await user.type(valueInput, "ABC");
|
|
146
|
+
|
|
147
|
+
await user.selectOptions(fieldKeySelect(), "data_source");
|
|
148
|
+
|
|
149
|
+
expect(screen.getByRole("textbox", { name: "filter value" })).toHaveValue(
|
|
150
|
+
""
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("should render an Add Filter button", () => {
|
|
155
|
+
renderInput();
|
|
156
|
+
|
|
157
|
+
const buttons = screen.getAllByRole("button");
|
|
158
|
+
|
|
159
|
+
expect(buttons).toHaveLength(1);
|
|
160
|
+
expect(buttons[0]).toHaveTextContent("Add filter");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("should disable the Add Filter button when the value is blank", async () => {
|
|
164
|
+
const user = userEvent.setup();
|
|
165
|
+
renderInput();
|
|
166
|
+
|
|
167
|
+
const valueInput = screen.getByRole("textbox", { name: "filter value" });
|
|
168
|
+
|
|
169
|
+
expect(screen.getByRole("button", { name: "Add filter" })).toBeDisabled();
|
|
170
|
+
|
|
171
|
+
await user.type(valueInput, " ");
|
|
172
|
+
expect(screen.getByRole("button", { name: "Add filter" })).toBeDisabled();
|
|
173
|
+
|
|
174
|
+
await user.clear(valueInput);
|
|
175
|
+
await user.type(valueInput, "hello");
|
|
176
|
+
expect(screen.getByRole("button", { name: "Add filter" })).toBeEnabled();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("should call onAdd when the Add Filter button is clicked", async () => {
|
|
180
|
+
const user = userEvent.setup();
|
|
181
|
+
renderInput();
|
|
182
|
+
|
|
183
|
+
const valueInput = screen.getByRole("textbox", { name: "filter value" });
|
|
184
|
+
|
|
185
|
+
await user.type(valueInput, "hello");
|
|
186
|
+
await user.click(screen.getByRole("button", { name: "Add filter" }));
|
|
187
|
+
|
|
188
|
+
expect(onAdd).toHaveBeenCalledTimes(1);
|
|
189
|
+
expect(onAdd).toHaveBeenCalledWith({
|
|
190
|
+
key: "genre",
|
|
191
|
+
op: "eq",
|
|
192
|
+
value: "hello",
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("should trim whitespace from the value", async () => {
|
|
197
|
+
const user = userEvent.setup();
|
|
198
|
+
renderInput();
|
|
199
|
+
|
|
200
|
+
const valueInput = screen.getByRole("textbox", { name: "filter value" });
|
|
201
|
+
|
|
202
|
+
await user.type(valueInput, " hello ");
|
|
203
|
+
await user.click(screen.getByRole("button", { name: "Add filter" }));
|
|
204
|
+
|
|
205
|
+
expect(onAdd).toHaveBeenCalledTimes(1);
|
|
206
|
+
expect(onAdd).toHaveBeenCalledWith({
|
|
207
|
+
key: "genre",
|
|
208
|
+
op: "eq",
|
|
209
|
+
value: "hello",
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
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 AdvancedSearchFilterViewer from "../../../src/components/AdvancedSearchFilterViewer";
|
|
6
|
+
|
|
7
|
+
describe("AdvancedSearchFilterViewer", () => {
|
|
8
|
+
let onBooleanChange;
|
|
9
|
+
let onMove;
|
|
10
|
+
let onSelect;
|
|
11
|
+
let onRemove;
|
|
12
|
+
let query;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
onBooleanChange = jest.fn();
|
|
16
|
+
onMove = jest.fn();
|
|
17
|
+
onSelect = jest.fn();
|
|
18
|
+
onRemove = jest.fn();
|
|
19
|
+
|
|
20
|
+
query = {
|
|
21
|
+
id: "0",
|
|
22
|
+
key: "genre",
|
|
23
|
+
value: "Horror",
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should render an AdvancedSearchFilter", () => {
|
|
28
|
+
const { container } = render(
|
|
29
|
+
<DndProvider backend={HTML5Backend}>
|
|
30
|
+
<AdvancedSearchFilterViewer
|
|
31
|
+
query={query}
|
|
32
|
+
readOnly={false}
|
|
33
|
+
selectedQueryId="0"
|
|
34
|
+
onBooleanChange={onBooleanChange}
|
|
35
|
+
onMove={onMove}
|
|
36
|
+
onSelect={onSelect}
|
|
37
|
+
onRemove={onRemove}
|
|
38
|
+
/>
|
|
39
|
+
</DndProvider>
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// The viewer wraps its filter in a tree container and passes the query through,
|
|
43
|
+
// so the value filter renders inside it.
|
|
44
|
+
expect(screen.getByRole("tree")).toBeInTheDocument();
|
|
45
|
+
expect(screen.getByText("Genre Horror")).toBeInTheDocument();
|
|
46
|
+
|
|
47
|
+
// readOnly is false and selectedQueryId matches the query id, so it is selected.
|
|
48
|
+
expect(
|
|
49
|
+
container.querySelector(".advanced-search-value-filter")
|
|
50
|
+
).toHaveClass("selected");
|
|
51
|
+
});
|
|
52
|
+
});
|