@thepalaceproject/circulation-admin 1.43.0-post.7 → 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.
Files changed (48) hide show
  1. package/package.json +1 -1
  2. package/tests/jest/components/AccountPage.test.tsx +41 -0
  3. package/tests/jest/components/AdvancedSearchBooleanFilter.test.tsx +231 -0
  4. package/tests/jest/components/AdvancedSearchFilter.test.tsx +142 -0
  5. package/tests/jest/components/AdvancedSearchFilterInput.test.tsx +212 -0
  6. package/tests/jest/components/AdvancedSearchFilterViewer.test.tsx +52 -0
  7. package/tests/jest/components/AdvancedSearchValueFilter.test.tsx +123 -0
  8. package/tests/jest/components/BookDetails.test.tsx +133 -0
  9. package/tests/jest/components/BookDetailsContainer.test.tsx +116 -0
  10. package/tests/jest/components/CatalogServices.test.tsx +63 -0
  11. package/tests/jest/components/CirculationEventsDownloadForm.test.tsx +41 -0
  12. package/tests/jest/components/ConfigPage.test.tsx +58 -0
  13. package/tests/jest/components/CustomListPage.test.tsx +57 -0
  14. package/tests/jest/components/CustomListSearch.test.tsx +275 -0
  15. package/tests/jest/components/CustomListsSidebar.test.tsx +251 -0
  16. package/tests/jest/components/DashboardPage.test.tsx +76 -0
  17. package/tests/jest/components/DebugAuthentication.test.tsx +1 -5
  18. package/tests/jest/components/DiagnosticsTabContainer.test.tsx +147 -0
  19. package/tests/jest/components/EditableConfigList.test.tsx +8 -2
  20. package/tests/jest/components/EntryPointsContainer.test.tsx +97 -0
  21. package/tests/jest/components/EntryPointsTabs.test.tsx +88 -0
  22. package/tests/jest/components/ErrorMessage.test.tsx +97 -0
  23. package/tests/jest/components/Footer.test.tsx +141 -0
  24. package/tests/jest/components/InventoryReportRequestModal.test.tsx +16 -42
  25. package/tests/jest/components/LaneEditor.test.tsx +2 -6
  26. package/tests/jest/components/LanePage.test.tsx +68 -0
  27. package/tests/jest/components/LanguageField.test.tsx +40 -0
  28. package/tests/jest/components/LoadButton.test.tsx +49 -0
  29. package/tests/jest/components/ManagePatrons.test.tsx +73 -0
  30. package/tests/jest/components/ManagePatronsForm.test.tsx +180 -0
  31. package/tests/jest/components/ManagePatronsTabContainer.test.tsx +143 -0
  32. package/tests/jest/components/MetadataServices.test.tsx +59 -0
  33. package/tests/jest/components/PatronBlockingRulesEditor.test.tsx +3 -1
  34. package/tests/jest/components/PatronInfo.test.tsx +54 -0
  35. package/tests/jest/components/SelfTestResult.test.tsx +164 -0
  36. package/tests/jest/components/SelfTestsCategory.test.tsx +200 -0
  37. package/tests/jest/components/ServiceWithRegistrationsEditForm.test.tsx +110 -0
  38. package/tests/jest/components/SetupPage.test.tsx +41 -0
  39. package/tests/jest/components/SingleStatListItem.test.tsx +62 -0
  40. package/tests/jest/components/Stats.test.tsx +4 -8
  41. package/tests/jest/components/TabContainer.test.tsx +68 -0
  42. package/tests/jest/components/Timestamp.test.tsx +74 -0
  43. package/tests/jest/components/TroubleshootingTabContainer.test.tsx +68 -0
  44. package/tests/jest/components/WelcomePage.test.tsx +43 -0
  45. package/tests/jest/components/WithEditButton.test.tsx +55 -0
  46. package/tests/jest/components/WithRemoveButton.test.tsx +95 -0
  47. package/tests/jest/index.test.tsx +50 -0
  48. package/tests/jest/components/AdvancedSearchBuilder.test.tsx +0 -38
package/package.json CHANGED
@@ -143,5 +143,5 @@
143
143
  "*.{js,jsx,ts,tsx,css,md}": "prettier --write",
144
144
  "*.{js,css,md}": "prettier --write"
145
145
  },
146
- "version": "1.43.0-post.7"
146
+ "version": "1.43.0-post.9"
147
147
  }
@@ -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
+ });
@@ -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
+ });