@khanacademy/wonder-blocks-dropdown 2.6.7 → 2.6.8
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/CHANGELOG.md +6 -0
- package/dist/es/index.js +202 -79
- package/dist/index.js +310 -174
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +1 -1
- package/src/__tests__/generated-snapshot.test.js +1 -1
- package/src/components/__tests__/action-menu.test.js +126 -166
- package/src/components/__tests__/dropdown-core-virtualized.test.js +20 -35
- package/src/components/__tests__/dropdown-core.test.js +724 -652
- package/src/components/__tests__/dropdown-popper.test.js +51 -0
- package/src/components/__tests__/multi-select.test.js +717 -518
- package/src/components/__tests__/single-select.test.js +52 -41
- package/src/components/action-item.js +2 -0
- package/src/components/dropdown-core.js +159 -90
- package/src/components/dropdown-popper.js +105 -0
- package/src/components/single-select.md +1 -1
- package/src/components/single-select.stories.js +49 -1
|
@@ -1,215 +1,188 @@
|
|
|
1
1
|
/* eslint-disable max-lines */
|
|
2
2
|
//@flow
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import {
|
|
5
|
-
import "
|
|
4
|
+
import {render, screen} from "@testing-library/react";
|
|
5
|
+
import userEvent from "@testing-library/user-event";
|
|
6
6
|
|
|
7
|
-
import {ClickableBehavior} from "@khanacademy/wonder-blocks-clickable";
|
|
8
|
-
import IconButton from "@khanacademy/wonder-blocks-icon-button";
|
|
9
|
-
|
|
10
|
-
import DropdownOpener from "../dropdown-opener.js";
|
|
11
|
-
import SelectOpener from "../select-opener.js";
|
|
12
|
-
import ActionItem from "../action-item.js";
|
|
13
7
|
import OptionItem from "../option-item.js";
|
|
14
8
|
import MultiSelect from "../multi-select.js";
|
|
15
|
-
import {keyCodes} from "../../util/constants.js";
|
|
16
|
-
import SearchTextInput from "../search-text-input.js";
|
|
17
|
-
|
|
18
|
-
import {unmountAll} from "../../../../../utils/testing/enzyme-shim.js";
|
|
19
9
|
|
|
20
10
|
import type {Labels} from "../multi-select.js";
|
|
21
11
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
12
|
+
const labels: $Shape<Labels> = {
|
|
13
|
+
selectAllLabel: (numOptions) => `Sellect all (${numOptions})`,
|
|
14
|
+
noneSelected: "Choose",
|
|
15
|
+
someSelected: (numSelectedValues) => `${numSelectedValues} students`,
|
|
16
|
+
allSelected: "All students",
|
|
17
|
+
};
|
|
25
18
|
|
|
26
19
|
describe("MultiSelect", () => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
allChanges.push(update);
|
|
31
|
-
};
|
|
32
|
-
const onChange = jest.fn();
|
|
33
|
-
|
|
34
|
-
beforeEach(() => {
|
|
35
|
-
jest.useFakeTimers();
|
|
36
|
-
|
|
37
|
-
// Jest doesn't fake out the animation frame API, so we're going to do
|
|
38
|
-
// it here and map it to timeouts, that way we can use the fake timer
|
|
39
|
-
// API to test our animation frame things.
|
|
40
|
-
jest.spyOn(global, "requestAnimationFrame").mockImplementation((fn) =>
|
|
41
|
-
setTimeout(fn, 0),
|
|
42
|
-
);
|
|
43
|
-
jest.spyOn(global, "cancelAnimationFrame").mockImplementation((id) =>
|
|
44
|
-
clearTimeout(id),
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
const labels: $Shape<Labels> = {
|
|
48
|
-
selectAllLabel: (numOptions) => `Sellect all (${numOptions})`,
|
|
49
|
-
noneSelected: "Choose",
|
|
50
|
-
someSelected: (numSelectedValues) =>
|
|
51
|
-
`${numSelectedValues} students`,
|
|
52
|
-
allSelected: "All students",
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
select = mount(
|
|
20
|
+
describe("uncontrolled", () => {
|
|
21
|
+
const onChange = jest.fn();
|
|
22
|
+
const uncontrolledSingleSelect = (
|
|
56
23
|
<MultiSelect
|
|
57
|
-
onChange={
|
|
58
|
-
|
|
59
|
-
onChange();
|
|
60
|
-
}}
|
|
61
|
-
selectedValues={["2"]}
|
|
62
|
-
shortcuts={true}
|
|
24
|
+
onChange={onChange}
|
|
25
|
+
selectedValues={[]}
|
|
63
26
|
labels={labels}
|
|
64
27
|
>
|
|
65
28
|
<OptionItem label="item 1" value="1" />
|
|
66
29
|
<OptionItem label="item 2" value="2" />
|
|
67
30
|
<OptionItem label="item 3" value="3" />
|
|
68
31
|
{false && <OptionItem label="item 4" value="4" />}
|
|
69
|
-
</MultiSelect
|
|
32
|
+
</MultiSelect>
|
|
70
33
|
);
|
|
71
|
-
});
|
|
72
34
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
// we set with a setTimeout but are clearing with clearAnimationFrame
|
|
77
|
-
// because we restored the clearAnimationFrame mock (and we won't
|
|
78
|
-
// have cleared the timeout we actually set!)
|
|
79
|
-
unmountAll();
|
|
80
|
-
jest.restoreAllMocks();
|
|
81
|
-
});
|
|
35
|
+
it("opens the select on mouse click", () => {
|
|
36
|
+
// Arrange
|
|
37
|
+
render(uncontrolledSingleSelect);
|
|
82
38
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
expect(select.state("open")).toEqual(false);
|
|
86
|
-
|
|
87
|
-
// Open select with mouse
|
|
88
|
-
opener.simulate("mousedown");
|
|
89
|
-
opener.simulate("mouseup");
|
|
90
|
-
opener.simulate("click");
|
|
91
|
-
expect(select.state("open")).toEqual(true);
|
|
92
|
-
|
|
93
|
-
// Close select with space
|
|
94
|
-
opener.simulate("keydown", {keyCode: keyCodes.space});
|
|
95
|
-
opener.simulate("keyup", {keyCode: keyCodes.space});
|
|
96
|
-
expect(select.state("open")).toEqual(false);
|
|
97
|
-
|
|
98
|
-
// Should open with enter
|
|
99
|
-
opener.simulate("keydown", {keyCode: keyCodes.enter});
|
|
100
|
-
opener.simulate("keyup", {keyCode: keyCodes.enter});
|
|
101
|
-
expect(select.state("open")).toEqual(true);
|
|
102
|
-
});
|
|
39
|
+
// Act
|
|
40
|
+
userEvent.click(screen.getByRole("button"));
|
|
103
41
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const nativeEvent = {
|
|
108
|
-
nativeEvent: {stopImmediatePropagation: noop},
|
|
109
|
-
};
|
|
42
|
+
// Assert
|
|
43
|
+
expect(screen.getByRole("listbox")).toBeInTheDocument();
|
|
44
|
+
});
|
|
110
45
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const item = select.find(OptionItem).at(0);
|
|
115
|
-
expect(item.text()).toEqual("item 1");
|
|
116
|
-
// Click the item 2, deselecting it
|
|
117
|
-
item.simulate("mousedown");
|
|
118
|
-
item.simulate("mouseup", nativeEvent);
|
|
119
|
-
item.simulate("click");
|
|
120
|
-
|
|
121
|
-
// Expect select's onChange callback to have been called
|
|
122
|
-
expect(onChange).toHaveBeenCalledTimes(1);
|
|
123
|
-
expect(allChanges.length).toEqual(1);
|
|
124
|
-
const currentlySelected = allChanges.pop();
|
|
125
|
-
|
|
126
|
-
// Expected selected items are now 1 and 2
|
|
127
|
-
expect(currentlySelected.length).toEqual(2);
|
|
128
|
-
expect(currentlySelected.includes("1")).toEqual(true);
|
|
129
|
-
expect(currentlySelected.includes("2")).toEqual(true);
|
|
130
|
-
expect(currentlySelected.includes("3")).toEqual(false);
|
|
131
|
-
|
|
132
|
-
// Now manually set the selectedValues like clients would
|
|
133
|
-
select.setProps({selectedValues: ["1", "2"]});
|
|
134
|
-
|
|
135
|
-
// This select should still be open afer a selection
|
|
136
|
-
expect(select.state("open")).toEqual(true);
|
|
137
|
-
|
|
138
|
-
// Select all of the items
|
|
139
|
-
const selectAll = select.find(ActionItem).at(0);
|
|
140
|
-
selectAll.simulate("mousedown");
|
|
141
|
-
selectAll.simulate("mouseup", nativeEvent);
|
|
142
|
-
selectAll.simulate("click");
|
|
143
|
-
expect(allChanges.pop().length).toEqual(3);
|
|
144
|
-
|
|
145
|
-
// Select none of the items
|
|
146
|
-
const selectNone = select.find(ActionItem).at(1);
|
|
147
|
-
selectNone.simulate("mousedown");
|
|
148
|
-
selectNone.simulate("mouseup", nativeEvent);
|
|
149
|
-
selectNone.simulate("click");
|
|
150
|
-
expect(allChanges.pop().length).toEqual(0);
|
|
151
|
-
|
|
152
|
-
// Menu should still be open
|
|
153
|
-
expect(select.state("open")).toEqual(true);
|
|
154
|
-
});
|
|
46
|
+
it("closes the select on {escape}", () => {
|
|
47
|
+
// Arrange
|
|
48
|
+
render(uncontrolledSingleSelect);
|
|
155
49
|
|
|
156
|
-
|
|
157
|
-
|
|
50
|
+
userEvent.tab();
|
|
51
|
+
userEvent.keyboard("{enter}"); // open
|
|
158
52
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
expect(opener.text()).toEqual("Choose");
|
|
53
|
+
// Act
|
|
54
|
+
userEvent.keyboard("{escape}");
|
|
162
55
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
56
|
+
// Assert
|
|
57
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
58
|
+
expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
|
|
59
|
+
});
|
|
166
60
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
61
|
+
it("displays correct text for opener", () => {
|
|
62
|
+
// Arrange
|
|
63
|
+
render(uncontrolledSingleSelect);
|
|
170
64
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
expect(opener.text()).toEqual("All students");
|
|
174
|
-
});
|
|
65
|
+
// Act
|
|
66
|
+
const opener = screen.getByRole("button");
|
|
175
67
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
68
|
+
// Assert
|
|
69
|
+
// No items are selected, display placeholder because there is one
|
|
70
|
+
expect(opener).toHaveTextContent("Choose");
|
|
71
|
+
});
|
|
180
72
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
});
|
|
73
|
+
it("displays correct text for opener when there's one item selected", () => {
|
|
74
|
+
// Arrange
|
|
184
75
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
76
|
+
// Act
|
|
77
|
+
render(
|
|
78
|
+
<MultiSelect
|
|
79
|
+
onChange={onChange}
|
|
80
|
+
selectedValues={["1"]}
|
|
81
|
+
labels={labels}
|
|
82
|
+
>
|
|
83
|
+
<OptionItem label="item 1" value="1" />
|
|
84
|
+
<OptionItem label="item 2" value="2" />
|
|
85
|
+
<OptionItem label="item 3" value="3" />
|
|
86
|
+
</MultiSelect>,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Assert
|
|
90
|
+
expect(screen.getByRole("button")).toHaveTextContent("item 1");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("displays correct text for opener when there's more than one item selected", () => {
|
|
94
|
+
// Arrange
|
|
95
|
+
|
|
96
|
+
// Act
|
|
97
|
+
render(
|
|
98
|
+
<MultiSelect
|
|
99
|
+
onChange={onChange}
|
|
100
|
+
selectedValues={["1", "2"]}
|
|
101
|
+
labels={labels}
|
|
102
|
+
>
|
|
103
|
+
<OptionItem label="item 1" value="1" />
|
|
104
|
+
<OptionItem label="item 2" value="2" />
|
|
105
|
+
<OptionItem label="item 3" value="3" />
|
|
106
|
+
</MultiSelect>,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Assert
|
|
110
|
+
// More than one item is selected, display n itemTypes
|
|
111
|
+
expect(screen.getByRole("button")).toHaveTextContent("2 students");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("displays correct text for opener when all items are selected", () => {
|
|
115
|
+
// Arrange
|
|
116
|
+
|
|
117
|
+
// Act
|
|
118
|
+
render(
|
|
119
|
+
<MultiSelect
|
|
120
|
+
onChange={onChange}
|
|
121
|
+
selectedValues={["1", "2", "3"]}
|
|
122
|
+
labels={labels}
|
|
123
|
+
>
|
|
124
|
+
<OptionItem label="item 1" value="1" />
|
|
125
|
+
<OptionItem label="item 2" value="2" />
|
|
126
|
+
<OptionItem label="item 3" value="3" />
|
|
127
|
+
</MultiSelect>,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// Assert
|
|
131
|
+
// All items are selected
|
|
132
|
+
expect(screen.getByRole("button")).toHaveTextContent(
|
|
133
|
+
"All students",
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("displays All selected text when no items is selected for opener with implicitAllEnabled", () => {
|
|
138
|
+
// Arrange
|
|
139
|
+
|
|
140
|
+
// Act
|
|
141
|
+
render(
|
|
142
|
+
<MultiSelect
|
|
143
|
+
onChange={onChange}
|
|
144
|
+
selectedValues={[]}
|
|
145
|
+
labels={labels}
|
|
146
|
+
implicitAllEnabled={true}
|
|
147
|
+
>
|
|
148
|
+
<OptionItem label="item 1" value="1" />
|
|
149
|
+
<OptionItem label="item 2" value="2" />
|
|
150
|
+
<OptionItem label="item 3" value="3" />
|
|
151
|
+
</MultiSelect>,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// Assert
|
|
155
|
+
expect(screen.getByRole("button")).toHaveTextContent(
|
|
156
|
+
"All students",
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("verifies testId is added to the opener", () => {
|
|
161
|
+
// Arrange
|
|
162
|
+
render(
|
|
163
|
+
<MultiSelect
|
|
164
|
+
selectedValues={["2"]}
|
|
165
|
+
onChange={onChange}
|
|
166
|
+
testId="some-test-id"
|
|
167
|
+
>
|
|
168
|
+
<OptionItem label="item 1" value="1" />
|
|
169
|
+
<OptionItem label="item 2" value="2" />
|
|
170
|
+
</MultiSelect>,
|
|
171
|
+
);
|
|
197
172
|
|
|
198
|
-
|
|
199
|
-
|
|
173
|
+
// Act
|
|
174
|
+
const opener = screen.getByRole("button");
|
|
200
175
|
|
|
201
|
-
|
|
202
|
-
|
|
176
|
+
// Assert
|
|
177
|
+
expect(opener).toHaveAttribute("data-test-id", "some-test-id");
|
|
178
|
+
});
|
|
203
179
|
});
|
|
204
180
|
|
|
205
181
|
describe("Controlled component", () => {
|
|
206
182
|
type Props = {|
|
|
207
183
|
opened?: boolean,
|
|
208
184
|
onToggle?: (opened: boolean) => mixed,
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
type State = {|
|
|
212
|
-
opened?: boolean,
|
|
185
|
+
shortcuts?: boolean,
|
|
213
186
|
|};
|
|
214
187
|
|
|
215
188
|
const labels: $Shape<Labels> = {
|
|
@@ -218,50 +191,49 @@ describe("MultiSelect", () => {
|
|
|
218
191
|
someSelected: (numSelectedValues) => `${numSelectedValues} fruits`,
|
|
219
192
|
};
|
|
220
193
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
};
|
|
194
|
+
function ControlledComponent(props: Props) {
|
|
195
|
+
const [opened, setOpened] = React.useState(props.opened ?? false);
|
|
196
|
+
const [selectedValues, setSelectedValues] = React.useState([]);
|
|
225
197
|
|
|
226
|
-
handleToggleMenu = (opened) => {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
198
|
+
const handleToggleMenu = (opened) => {
|
|
199
|
+
setOpened(opened);
|
|
200
|
+
props.onToggle && props.onToggle(opened);
|
|
201
|
+
};
|
|
230
202
|
|
|
231
|
-
|
|
203
|
+
const handleChange = (newValues) => {
|
|
204
|
+
setSelectedValues(newValues);
|
|
232
205
|
};
|
|
233
206
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
<
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
<
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
207
|
+
return (
|
|
208
|
+
<React.Fragment>
|
|
209
|
+
<MultiSelect
|
|
210
|
+
labels={labels}
|
|
211
|
+
onChange={handleChange}
|
|
212
|
+
opened={opened}
|
|
213
|
+
onToggle={handleToggleMenu}
|
|
214
|
+
testId="multi-select-opener"
|
|
215
|
+
selectedValues={selectedValues}
|
|
216
|
+
shortcuts={props.shortcuts}
|
|
217
|
+
>
|
|
218
|
+
<OptionItem label="item 1" value="1" />
|
|
219
|
+
<OptionItem label="item 2" value="2" />
|
|
220
|
+
<OptionItem label="item 3" value="3" />
|
|
221
|
+
</MultiSelect>
|
|
222
|
+
<button
|
|
223
|
+
data-test-id="parent-button"
|
|
224
|
+
onClick={() => handleToggleMenu(true)}
|
|
225
|
+
/>
|
|
226
|
+
</React.Fragment>
|
|
227
|
+
);
|
|
254
228
|
}
|
|
255
229
|
|
|
256
230
|
it("opens the menu when the parent updates its state", () => {
|
|
257
231
|
// Arrange
|
|
258
232
|
const onToggleMock = jest.fn();
|
|
259
|
-
|
|
260
|
-
<ControlledComponent onToggle={onToggleMock} />,
|
|
261
|
-
);
|
|
233
|
+
render(<ControlledComponent onToggle={onToggleMock} />);
|
|
262
234
|
|
|
263
235
|
// Act
|
|
264
|
-
|
|
236
|
+
userEvent.click(screen.getByTestId("parent-button"));
|
|
265
237
|
|
|
266
238
|
// Assert
|
|
267
239
|
expect(onToggleMock).toHaveBeenCalledWith(true);
|
|
@@ -270,29 +242,28 @@ describe("MultiSelect", () => {
|
|
|
270
242
|
it("closes the menu when the parent updates its state", () => {
|
|
271
243
|
// Arrange
|
|
272
244
|
const onToggleMock = jest.fn();
|
|
273
|
-
|
|
274
|
-
<ControlledComponent onToggle={onToggleMock} />,
|
|
275
|
-
);
|
|
245
|
+
render(<ControlledComponent onToggle={onToggleMock} />);
|
|
276
246
|
|
|
277
247
|
// Act
|
|
278
248
|
// open the menu from the outside
|
|
279
|
-
|
|
249
|
+
userEvent.click(screen.getByTestId("parent-button"));
|
|
280
250
|
// click on the opener
|
|
281
|
-
|
|
251
|
+
userEvent.click(screen.getByTestId("multi-select-opener"));
|
|
282
252
|
|
|
283
253
|
// Assert
|
|
284
|
-
expect(onToggleMock).
|
|
254
|
+
expect(onToggleMock).toHaveBeenCalledTimes(2);
|
|
255
|
+
expect(onToggleMock).toHaveBeenNthCalledWith(1, true);
|
|
256
|
+
expect(onToggleMock).toHaveBeenNthCalledWith(2, false);
|
|
285
257
|
});
|
|
286
258
|
|
|
287
259
|
it("should still allow the opener to open the menu", () => {
|
|
288
260
|
// Arrange
|
|
289
261
|
const onToggleMock = jest.fn();
|
|
290
|
-
|
|
291
|
-
<ControlledComponent onToggle={onToggleMock} />,
|
|
292
|
-
);
|
|
262
|
+
render(<ControlledComponent onToggle={onToggleMock} />);
|
|
293
263
|
|
|
294
264
|
// Act
|
|
295
|
-
|
|
265
|
+
// click on the opener
|
|
266
|
+
userEvent.click(screen.getByTestId("multi-select-opener"));
|
|
296
267
|
|
|
297
268
|
// Assert
|
|
298
269
|
expect(onToggleMock).toHaveBeenCalledWith(true);
|
|
@@ -300,183 +271,398 @@ describe("MultiSelect", () => {
|
|
|
300
271
|
|
|
301
272
|
it("opens the menu when the anchor is clicked once", () => {
|
|
302
273
|
// Arrange
|
|
303
|
-
|
|
274
|
+
render(<ControlledComponent />);
|
|
304
275
|
|
|
305
276
|
// Act
|
|
306
|
-
|
|
307
|
-
wrapper.find(SelectOpener).simulate("click");
|
|
277
|
+
userEvent.click(screen.getByTestId("multi-select-opener"));
|
|
308
278
|
|
|
309
279
|
// Assert
|
|
310
|
-
expect(
|
|
280
|
+
expect(screen.getByRole("listbox")).toBeInTheDocument();
|
|
311
281
|
});
|
|
312
282
|
|
|
313
283
|
it("closes the menu when the anchor is clicked", () => {
|
|
314
284
|
// Arrange
|
|
315
|
-
|
|
285
|
+
render(<ControlledComponent />);
|
|
316
286
|
|
|
317
287
|
// Act
|
|
318
288
|
// open the menu from the outside
|
|
319
|
-
|
|
289
|
+
userEvent.click(screen.getByTestId("parent-button"));
|
|
320
290
|
// click on the dropdown anchor to hide the menu
|
|
321
|
-
|
|
291
|
+
userEvent.click(screen.getByTestId("multi-select-opener"));
|
|
322
292
|
|
|
323
293
|
// Assert
|
|
324
|
-
expect(
|
|
294
|
+
expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
|
|
325
295
|
});
|
|
326
|
-
});
|
|
327
296
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
select.setState({open: true});
|
|
297
|
+
it("selects on item as expected", () => {
|
|
298
|
+
// Arrange
|
|
299
|
+
render(<ControlledComponent />);
|
|
332
300
|
|
|
333
|
-
|
|
334
|
-
|
|
301
|
+
const opener = screen.getByTestId("multi-select-opener");
|
|
302
|
+
userEvent.click(opener);
|
|
335
303
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
304
|
+
// Act
|
|
305
|
+
// Grab the second item in the list
|
|
306
|
+
const item = screen.getByRole("option", {
|
|
307
|
+
name: "item 2",
|
|
308
|
+
});
|
|
309
|
+
userEvent.click(item, undefined, {
|
|
310
|
+
skipPointerEventsCheck: true, // Popper compatibility
|
|
311
|
+
});
|
|
339
312
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
expect(select.find(IconButton).exists()).toBe(false);
|
|
313
|
+
// Assert
|
|
314
|
+
expect(item).toHaveAttribute("aria-selected", "true");
|
|
315
|
+
expect(opener).toHaveTextContent("item 2");
|
|
316
|
+
});
|
|
345
317
|
|
|
346
|
-
|
|
347
|
-
|
|
318
|
+
it("dropdown menu is still open after selection", () => {
|
|
319
|
+
// Arrange
|
|
320
|
+
const onToggleMock = jest.fn();
|
|
321
|
+
render(<ControlledComponent onToggle={onToggleMock} />);
|
|
348
322
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
});
|
|
323
|
+
const opener = screen.getByTestId("multi-select-opener");
|
|
324
|
+
userEvent.click(opener);
|
|
352
325
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
326
|
+
// Act
|
|
327
|
+
// Grab the second item in the list
|
|
328
|
+
const item = screen.getByRole("option", {
|
|
329
|
+
name: "item 2",
|
|
330
|
+
});
|
|
331
|
+
userEvent.click(item, undefined, {
|
|
332
|
+
skipPointerEventsCheck: true, // Popper compatibility
|
|
333
|
+
});
|
|
356
334
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
open: true,
|
|
360
|
-
searchText: "Item 1",
|
|
335
|
+
// Assert
|
|
336
|
+
expect(onToggleMock).toHaveBeenCalledTimes(1);
|
|
361
337
|
});
|
|
362
338
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
});
|
|
339
|
+
it("selects two items as expected", () => {
|
|
340
|
+
// Arrange
|
|
341
|
+
render(<ControlledComponent />);
|
|
367
342
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
select.setProps({isFilterable: true});
|
|
343
|
+
const opener = screen.getByTestId("multi-select-opener");
|
|
344
|
+
userEvent.click(opener);
|
|
371
345
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
346
|
+
// Act
|
|
347
|
+
// Select the second and third items in the list
|
|
348
|
+
const secondOption = screen.getByText("item 2");
|
|
349
|
+
userEvent.click(secondOption, undefined, {
|
|
350
|
+
skipPointerEventsCheck: true, // Popper compatibility
|
|
351
|
+
});
|
|
352
|
+
const thirdOption = screen.getByText("item 3");
|
|
353
|
+
userEvent.click(thirdOption, undefined, {
|
|
354
|
+
skipPointerEventsCheck: true, // Popper compatibility
|
|
355
|
+
});
|
|
375
356
|
|
|
376
|
-
|
|
377
|
-
|
|
357
|
+
// Assert
|
|
358
|
+
expect(opener).toHaveTextContent("2 fruits");
|
|
359
|
+
|
|
360
|
+
// // Select none of the items
|
|
361
|
+
// const selectNone = select.find(ActionItem).at(1);
|
|
362
|
+
// selectNone.simulate("mousedown");
|
|
363
|
+
// selectNone.simulate("mouseup", nativeEvent);
|
|
364
|
+
// selectNone.simulate("click");
|
|
365
|
+
// expect(allChanges.pop().length).toEqual(0)
|
|
366
|
+
});
|
|
378
367
|
|
|
379
|
-
|
|
380
|
-
|
|
368
|
+
it("selects two items, then unselect one as expected", () => {
|
|
369
|
+
// Arrange
|
|
370
|
+
const onToggleMock = jest.fn();
|
|
371
|
+
render(<ControlledComponent onToggle={onToggleMock} />);
|
|
381
372
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
select.setProps({isFilterable: true, shortcuts: false});
|
|
385
|
-
select.setState({open: true});
|
|
386
|
-
const searchInput = select.find(SearchTextInput);
|
|
387
|
-
const lastOption = select
|
|
388
|
-
.find(OptionItem)
|
|
389
|
-
.at(2)
|
|
390
|
-
.find(ClickableBehavior);
|
|
391
|
-
jest.runAllTimers();
|
|
392
|
-
expect(searchInput.state("focused")).toBe(true);
|
|
393
|
-
|
|
394
|
-
// Act
|
|
395
|
-
select.simulate("keydown", {keyCode: keyCodes.up});
|
|
396
|
-
select.simulate("keyup", {keyCode: keyCodes.up});
|
|
397
|
-
jest.runAllTimers();
|
|
398
|
-
|
|
399
|
-
// Assert
|
|
400
|
-
expect(lastOption.state("focused")).toBe(true);
|
|
401
|
-
});
|
|
373
|
+
const opener = screen.getByTestId("multi-select-opener");
|
|
374
|
+
userEvent.click(opener);
|
|
402
375
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
// Act
|
|
413
|
-
select.simulate("keydown", {keyCode: keyCodes.down});
|
|
414
|
-
select.simulate("keyup", {keyCode: keyCodes.down});
|
|
415
|
-
jest.runAllTimers();
|
|
416
|
-
|
|
417
|
-
// Assert
|
|
418
|
-
expect(selectAll.state("focused")).toBe(true);
|
|
419
|
-
});
|
|
376
|
+
// Select the second and third items in the list
|
|
377
|
+
const secondOption = screen.getByText("item 2");
|
|
378
|
+
userEvent.click(secondOption, undefined, {
|
|
379
|
+
skipPointerEventsCheck: true, // Popper compatibility
|
|
380
|
+
});
|
|
381
|
+
const thirdOption = screen.getByText("item 3");
|
|
382
|
+
userEvent.click(thirdOption, undefined, {
|
|
383
|
+
skipPointerEventsCheck: true, // Popper compatibility
|
|
384
|
+
});
|
|
420
385
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
386
|
+
// Act
|
|
387
|
+
// Unselect the first item selected
|
|
388
|
+
userEvent.click(secondOption, undefined, {
|
|
389
|
+
skipPointerEventsCheck: true, // Popper compatibility
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Assert
|
|
393
|
+
expect(opener).toHaveTextContent("item 3");
|
|
427
394
|
});
|
|
428
395
|
|
|
429
|
-
|
|
430
|
-
|
|
396
|
+
it("selects all the items when the 'All items' shortcut is selected", () => {
|
|
397
|
+
// Arrange
|
|
398
|
+
render(<ControlledComponent shortcuts={true} />);
|
|
431
399
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
expect(item3.text()).toEqual("item 3");
|
|
435
|
-
});
|
|
400
|
+
const opener = screen.getByTestId("multi-select-opener");
|
|
401
|
+
userEvent.click(opener);
|
|
436
402
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
403
|
+
// Act
|
|
404
|
+
// Select all of the items
|
|
405
|
+
const selectAll = screen.getByText(/Select all/i);
|
|
406
|
+
userEvent.click(selectAll, undefined, {
|
|
407
|
+
skipPointerEventsCheck: true, // Popper compatibility
|
|
408
|
+
});
|
|
442
409
|
|
|
443
|
-
|
|
444
|
-
|
|
410
|
+
// Assert
|
|
411
|
+
expect(opener).toHaveTextContent("All fruits");
|
|
412
|
+
});
|
|
445
413
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
414
|
+
it("deselects all the items when the 'Select none' shortcut is selected", () => {
|
|
415
|
+
// Arrange
|
|
416
|
+
render(<ControlledComponent shortcuts={true} />);
|
|
417
|
+
|
|
418
|
+
const opener = screen.getByTestId("multi-select-opener");
|
|
419
|
+
userEvent.click(opener);
|
|
449
420
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
421
|
+
// First, select all of the items
|
|
422
|
+
const selectAll = screen.getByText(/Select all/i);
|
|
423
|
+
userEvent.click(selectAll, undefined, {
|
|
424
|
+
skipPointerEventsCheck: true, // Popper compatibility
|
|
425
|
+
});
|
|
455
426
|
|
|
456
|
-
|
|
457
|
-
|
|
427
|
+
// Act
|
|
428
|
+
const selectNone = screen.getByText(/Select none/i);
|
|
429
|
+
userEvent.click(selectNone, undefined, {
|
|
430
|
+
skipPointerEventsCheck: true, // Popper compatibility
|
|
431
|
+
});
|
|
458
432
|
|
|
459
|
-
|
|
460
|
-
|
|
433
|
+
// Assert
|
|
434
|
+
expect(opener).toHaveTextContent("0 items");
|
|
435
|
+
});
|
|
461
436
|
});
|
|
462
437
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
438
|
+
describe("isFilterable", () => {
|
|
439
|
+
const onChange = jest.fn();
|
|
440
|
+
|
|
441
|
+
const filterableSingleSelect = (
|
|
442
|
+
<MultiSelect
|
|
443
|
+
onChange={onChange}
|
|
444
|
+
selectedValues={[]}
|
|
445
|
+
labels={labels}
|
|
446
|
+
isFilterable={true}
|
|
447
|
+
>
|
|
448
|
+
<OptionItem label="item 1" value="1" />
|
|
449
|
+
<OptionItem label="item 2" value="2" />
|
|
450
|
+
<OptionItem label="item 3" value="3" />
|
|
451
|
+
{false && <OptionItem label="item 4" value="4" />}
|
|
452
|
+
</MultiSelect>
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
it("displays SearchTextInput when isFilterable is true", () => {
|
|
456
|
+
// Arrange
|
|
457
|
+
render(filterableSingleSelect);
|
|
458
|
+
// open the dropdown menu
|
|
459
|
+
userEvent.click(screen.getByRole("button"));
|
|
460
|
+
|
|
461
|
+
// Act
|
|
462
|
+
const searchInput = screen.getByPlaceholderText("Filter");
|
|
463
|
+
|
|
464
|
+
// Assert
|
|
465
|
+
expect(searchInput).toBeInTheDocument();
|
|
466
|
+
});
|
|
468
467
|
|
|
469
|
-
|
|
470
|
-
|
|
468
|
+
it("displays SearchTextInput with dismiss button when search text exists", () => {
|
|
469
|
+
// Arrange
|
|
470
|
+
render(filterableSingleSelect);
|
|
471
|
+
// open the dropdown menu
|
|
472
|
+
userEvent.click(screen.getByRole("button"));
|
|
473
|
+
|
|
474
|
+
const searchInput = screen.getByPlaceholderText("Filter");
|
|
475
|
+
|
|
476
|
+
// Act
|
|
477
|
+
userEvent.type(searchInput, "text");
|
|
478
|
+
|
|
479
|
+
// Assert
|
|
480
|
+
expect(screen.getByLabelText("Clear search")).toBeInTheDocument();
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it("filters the items by the search input (case insensitive)", () => {
|
|
484
|
+
// Arrange
|
|
485
|
+
render(filterableSingleSelect);
|
|
486
|
+
// open the dropdown menu
|
|
487
|
+
userEvent.click(screen.getByRole("button"));
|
|
488
|
+
|
|
489
|
+
const searchInput = screen.getByPlaceholderText("Filter");
|
|
490
|
+
|
|
491
|
+
// Act
|
|
492
|
+
userEvent.type(searchInput, "Item 1");
|
|
471
493
|
|
|
472
|
-
|
|
473
|
-
|
|
494
|
+
// Assert
|
|
495
|
+
expect(screen.getAllByRole("option")).toHaveLength(1);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it("Hides shortcuts when there are any text in search text input", () => {
|
|
499
|
+
// Arrange
|
|
500
|
+
render(
|
|
501
|
+
<MultiSelect
|
|
502
|
+
onChange={onChange}
|
|
503
|
+
selectedValues={[]}
|
|
504
|
+
labels={labels}
|
|
505
|
+
isFilterable={true}
|
|
506
|
+
shortcuts={true}
|
|
507
|
+
>
|
|
508
|
+
<OptionItem label="item 1" value="1" />
|
|
509
|
+
<OptionItem label="item 2" value="2" />
|
|
510
|
+
</MultiSelect>,
|
|
511
|
+
);
|
|
512
|
+
// open the dropdown menu
|
|
513
|
+
userEvent.click(screen.getByRole("button"));
|
|
514
|
+
|
|
515
|
+
const searchInput = screen.getByPlaceholderText("Filter");
|
|
516
|
+
|
|
517
|
+
// Act
|
|
518
|
+
userEvent.type(searchInput, "2");
|
|
519
|
+
|
|
520
|
+
// Assert
|
|
521
|
+
expect(screen.queryByText(/Select all/i)).not.toBeInTheDocument();
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it("should focus on the search input after opening the dropdown", () => {
|
|
525
|
+
// Arrange
|
|
526
|
+
render(filterableSingleSelect);
|
|
527
|
+
// open the dropdown menu
|
|
528
|
+
userEvent.click(screen.getByRole("button"));
|
|
529
|
+
|
|
530
|
+
// Act
|
|
531
|
+
const searchInput = screen.getByPlaceholderText("Filter");
|
|
532
|
+
|
|
533
|
+
// Assert
|
|
534
|
+
expect(searchInput).toHaveFocus();
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it("Selected items are at the top of the items when isFilterable is true", () => {
|
|
538
|
+
// Arrange
|
|
539
|
+
render(
|
|
540
|
+
<MultiSelect
|
|
541
|
+
onChange={onChange}
|
|
542
|
+
selectedValues={["3"]}
|
|
543
|
+
labels={labels}
|
|
544
|
+
isFilterable={true}
|
|
545
|
+
shortcuts={true}
|
|
546
|
+
>
|
|
547
|
+
<OptionItem label="item 1" value="1" />
|
|
548
|
+
<OptionItem label="item 2" value="2" />
|
|
549
|
+
<OptionItem label="item 3" value="3" />
|
|
550
|
+
</MultiSelect>,
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
// Act
|
|
554
|
+
// open the dropdown menu
|
|
555
|
+
userEvent.click(screen.getByRole("button"));
|
|
556
|
+
|
|
557
|
+
// Assert
|
|
558
|
+
expect(screen.getByRole("listbox")).toHaveTextContent(
|
|
559
|
+
/item 3.*item 1.*item 2/i,
|
|
560
|
+
);
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it("Type something in SearchTextInput should update searchText in MultiSelect", () => {
|
|
564
|
+
// Arrange
|
|
565
|
+
render(
|
|
566
|
+
<MultiSelect
|
|
567
|
+
onChange={onChange}
|
|
568
|
+
selectedValues={[]}
|
|
569
|
+
labels={labels}
|
|
570
|
+
isFilterable={true}
|
|
571
|
+
shortcuts={true}
|
|
572
|
+
>
|
|
573
|
+
<OptionItem label="item 1" value="1" />
|
|
574
|
+
<OptionItem label="item 2" value="2" />
|
|
575
|
+
<OptionItem label="item 3" value="3" />
|
|
576
|
+
</MultiSelect>,
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
userEvent.click(screen.getByRole("button"));
|
|
580
|
+
|
|
581
|
+
const searchInput = screen.getByPlaceholderText("Filter");
|
|
582
|
+
userEvent.paste(searchInput, "Item 2");
|
|
583
|
+
|
|
584
|
+
// Act
|
|
585
|
+
userEvent.clear(searchInput);
|
|
586
|
+
userEvent.paste(searchInput, "Item 1");
|
|
587
|
+
|
|
588
|
+
// Assert
|
|
589
|
+
const options = screen.getAllByRole("option");
|
|
590
|
+
expect(options).toHaveLength(1);
|
|
591
|
+
expect(options[0]).toHaveTextContent("item 1");
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
it("should move focus to the dismiss button after pressing {tab} on the text input", () => {
|
|
595
|
+
// Arrange
|
|
596
|
+
render(
|
|
597
|
+
<MultiSelect
|
|
598
|
+
onChange={onChange}
|
|
599
|
+
selectedValues={["1"]}
|
|
600
|
+
labels={labels}
|
|
601
|
+
isFilterable={true}
|
|
602
|
+
>
|
|
603
|
+
<OptionItem label="item 1" value="1" />
|
|
604
|
+
<OptionItem label="item 2" value="2" />
|
|
605
|
+
<OptionItem label="item 3" value="3" />
|
|
606
|
+
</MultiSelect>,
|
|
607
|
+
);
|
|
608
|
+
// open the dropdown menu
|
|
609
|
+
userEvent.click(screen.getByRole("button"));
|
|
610
|
+
|
|
611
|
+
const searchInput = screen.getByPlaceholderText("Filter");
|
|
612
|
+
userEvent.paste(searchInput, "some text");
|
|
613
|
+
|
|
614
|
+
// Act
|
|
615
|
+
userEvent.tab();
|
|
616
|
+
|
|
617
|
+
// Assert
|
|
618
|
+
const dismissBtn = screen.getByLabelText("Clear search");
|
|
619
|
+
expect(dismissBtn).toHaveFocus();
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
it("Click dismiss button should clear the searchText in MultiSelect", () => {
|
|
623
|
+
// Arrange
|
|
624
|
+
render(filterableSingleSelect);
|
|
625
|
+
// open the dropdown menu
|
|
626
|
+
userEvent.click(screen.getByRole("button"));
|
|
627
|
+
|
|
628
|
+
const searchInput = screen.getByPlaceholderText("Filter");
|
|
629
|
+
userEvent.paste(searchInput, "Should be cleared");
|
|
630
|
+
|
|
631
|
+
const dismissBtn = screen.getByLabelText("Clear search");
|
|
632
|
+
|
|
633
|
+
// Act
|
|
634
|
+
userEvent.click(dismissBtn);
|
|
635
|
+
|
|
636
|
+
// Assert
|
|
637
|
+
expect(searchInput).toHaveValue("");
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
it("Open MultiSelect should clear the searchText", () => {
|
|
641
|
+
// Arrange
|
|
642
|
+
render(filterableSingleSelect);
|
|
643
|
+
// open the dropdown menu
|
|
644
|
+
const opener = screen.getByRole("button");
|
|
645
|
+
userEvent.click(opener);
|
|
646
|
+
|
|
647
|
+
const searchInput = screen.getByPlaceholderText("Filter");
|
|
648
|
+
userEvent.paste(searchInput, "Should be cleared");
|
|
649
|
+
|
|
650
|
+
// Close the dropdown menu
|
|
651
|
+
userEvent.click(opener);
|
|
652
|
+
|
|
653
|
+
// Act
|
|
654
|
+
// Reopen it
|
|
655
|
+
userEvent.click(opener);
|
|
656
|
+
|
|
657
|
+
// Assert
|
|
658
|
+
expect(screen.getByPlaceholderText("Filter")).toHaveValue("");
|
|
659
|
+
});
|
|
474
660
|
});
|
|
475
661
|
|
|
476
662
|
describe("Custom Opener", () => {
|
|
477
663
|
it("opens the menu when clicking on the custom opener", () => {
|
|
478
664
|
// Arrange
|
|
479
|
-
|
|
665
|
+
render(
|
|
480
666
|
<MultiSelect
|
|
481
667
|
onChange={jest.fn()}
|
|
482
668
|
opener={(eventState) => (
|
|
@@ -490,18 +676,18 @@ describe("MultiSelect", () => {
|
|
|
490
676
|
);
|
|
491
677
|
|
|
492
678
|
// Act
|
|
493
|
-
const opener =
|
|
494
|
-
|
|
679
|
+
const opener = screen.getByLabelText("Search");
|
|
680
|
+
userEvent.click(opener);
|
|
495
681
|
|
|
496
682
|
// Assert
|
|
497
|
-
expect(
|
|
683
|
+
expect(screen.getByRole("listbox")).toBeInTheDocument();
|
|
498
684
|
});
|
|
499
685
|
|
|
500
686
|
it("calls the custom onClick handler", () => {
|
|
501
687
|
// Arrange
|
|
502
688
|
const onClickMock = jest.fn();
|
|
503
689
|
|
|
504
|
-
|
|
690
|
+
render(
|
|
505
691
|
<MultiSelect
|
|
506
692
|
onChange={jest.fn()}
|
|
507
693
|
opener={() => (
|
|
@@ -515,8 +701,8 @@ describe("MultiSelect", () => {
|
|
|
515
701
|
);
|
|
516
702
|
|
|
517
703
|
// Act
|
|
518
|
-
const opener =
|
|
519
|
-
|
|
704
|
+
const opener = screen.getByLabelText("Search");
|
|
705
|
+
userEvent.click(opener);
|
|
520
706
|
|
|
521
707
|
// Assert
|
|
522
708
|
expect(onClickMock).toHaveBeenCalledTimes(1);
|
|
@@ -524,7 +710,9 @@ describe("MultiSelect", () => {
|
|
|
524
710
|
|
|
525
711
|
it("verifies testId is passed from the custom opener", () => {
|
|
526
712
|
// Arrange
|
|
527
|
-
const
|
|
713
|
+
const onChange = jest.fn();
|
|
714
|
+
|
|
715
|
+
render(
|
|
528
716
|
<MultiSelect
|
|
529
717
|
onChange={onChange}
|
|
530
718
|
opener={() => (
|
|
@@ -540,15 +728,17 @@ describe("MultiSelect", () => {
|
|
|
540
728
|
);
|
|
541
729
|
|
|
542
730
|
// Act
|
|
543
|
-
const opener =
|
|
731
|
+
const opener = screen.getByLabelText("Custom opener");
|
|
544
732
|
|
|
545
733
|
// Assert
|
|
546
|
-
expect(opener.
|
|
734
|
+
expect(opener).toHaveAttribute("data-test-id", "custom-opener");
|
|
547
735
|
});
|
|
548
736
|
|
|
549
737
|
it("verifies testId is not passed from the parent element", () => {
|
|
550
738
|
// Arrange
|
|
551
|
-
const
|
|
739
|
+
const onChange = jest.fn();
|
|
740
|
+
|
|
741
|
+
render(
|
|
552
742
|
<MultiSelect
|
|
553
743
|
onChange={onChange}
|
|
554
744
|
testId="custom-opener"
|
|
@@ -560,10 +750,10 @@ describe("MultiSelect", () => {
|
|
|
560
750
|
);
|
|
561
751
|
|
|
562
752
|
// Act
|
|
563
|
-
const opener =
|
|
753
|
+
const opener = screen.getByLabelText("Custom opener");
|
|
564
754
|
|
|
565
755
|
// Assert
|
|
566
|
-
expect(opener.
|
|
756
|
+
expect(opener).not.toHaveAttribute("data-test-id", "custom-opener");
|
|
567
757
|
});
|
|
568
758
|
|
|
569
759
|
it("passes the current label to the custom opener (no items selected)", () => {
|
|
@@ -571,7 +761,8 @@ describe("MultiSelect", () => {
|
|
|
571
761
|
const labels: $Shape<Labels> = {
|
|
572
762
|
noneSelected: "No items selected",
|
|
573
763
|
};
|
|
574
|
-
|
|
764
|
+
|
|
765
|
+
render(
|
|
575
766
|
<MultiSelect
|
|
576
767
|
labels={labels}
|
|
577
768
|
testId="openTest"
|
|
@@ -592,113 +783,134 @@ describe("MultiSelect", () => {
|
|
|
592
783
|
);
|
|
593
784
|
|
|
594
785
|
// Act
|
|
595
|
-
const opener =
|
|
596
|
-
// open dropdown
|
|
597
|
-
opener.simulate("click");
|
|
598
|
-
const openerElement = menu.find(`[data-test-id="custom-opener"]`);
|
|
786
|
+
const opener = screen.getByTestId("custom-opener");
|
|
599
787
|
|
|
600
788
|
// Assert
|
|
601
|
-
expect(
|
|
789
|
+
expect(opener).toHaveTextContent("No items selected");
|
|
602
790
|
});
|
|
603
791
|
|
|
604
792
|
it("passes the current label to the custom opener (1 item selected)", () => {
|
|
605
793
|
// Arrange
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
794
|
+
const ControlledMultiSelect = () => {
|
|
795
|
+
const [selected, setSelected] = React.useState([]);
|
|
796
|
+
return (
|
|
797
|
+
<MultiSelect
|
|
798
|
+
testId="openTest"
|
|
799
|
+
onChange={(values) => {
|
|
800
|
+
setSelected(values);
|
|
801
|
+
}}
|
|
802
|
+
selectedValues={selected}
|
|
803
|
+
opener={({text}) => (
|
|
804
|
+
<button
|
|
805
|
+
onClick={jest.fn()}
|
|
806
|
+
data-test-id="custom-opener"
|
|
807
|
+
>
|
|
808
|
+
{text}
|
|
809
|
+
</button>
|
|
810
|
+
)}
|
|
811
|
+
>
|
|
812
|
+
<OptionItem label="item 1" value="1" />
|
|
813
|
+
<OptionItem label="item 2" value="2" />
|
|
814
|
+
<OptionItem label="item 3" value="3" />
|
|
815
|
+
</MultiSelect>
|
|
816
|
+
);
|
|
817
|
+
};
|
|
624
818
|
|
|
625
|
-
|
|
626
|
-
|
|
819
|
+
render(<ControlledMultiSelect />);
|
|
820
|
+
|
|
821
|
+
const opener = screen.getByTestId("custom-opener");
|
|
627
822
|
// open dropdown
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
const
|
|
823
|
+
userEvent.click(opener);
|
|
824
|
+
|
|
825
|
+
// Act
|
|
826
|
+
const option = screen.getByText("item 1");
|
|
827
|
+
userEvent.click(option);
|
|
632
828
|
|
|
633
829
|
// Assert
|
|
634
|
-
expect(
|
|
830
|
+
expect(opener).toHaveTextContent("item 1");
|
|
635
831
|
});
|
|
636
832
|
|
|
637
833
|
it("passes the current label to the custom opener (2 items selected)", () => {
|
|
638
834
|
// Arrange
|
|
639
|
-
const
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
835
|
+
const ControlledMultiSelect = () => {
|
|
836
|
+
const [selected, setSelected] = React.useState([]);
|
|
837
|
+
return (
|
|
838
|
+
<MultiSelect
|
|
839
|
+
testId="openTest"
|
|
840
|
+
onChange={(values) => {
|
|
841
|
+
setSelected(values);
|
|
842
|
+
}}
|
|
843
|
+
selectedValues={selected}
|
|
844
|
+
opener={({text}) => (
|
|
845
|
+
<button
|
|
846
|
+
onClick={jest.fn()}
|
|
847
|
+
data-test-id="custom-opener"
|
|
848
|
+
>
|
|
849
|
+
{text}
|
|
850
|
+
</button>
|
|
851
|
+
)}
|
|
852
|
+
>
|
|
853
|
+
<OptionItem label="item 1" value="1" />
|
|
854
|
+
<OptionItem label="item 2" value="2" />
|
|
855
|
+
<OptionItem label="item 3" value="3" />
|
|
856
|
+
</MultiSelect>
|
|
857
|
+
);
|
|
858
|
+
};
|
|
657
859
|
|
|
658
|
-
|
|
659
|
-
|
|
860
|
+
render(<ControlledMultiSelect />);
|
|
861
|
+
|
|
862
|
+
const opener = screen.getByTestId("custom-opener");
|
|
660
863
|
// open dropdown
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
//
|
|
664
|
-
|
|
665
|
-
|
|
864
|
+
userEvent.click(opener);
|
|
865
|
+
|
|
866
|
+
// Act
|
|
867
|
+
userEvent.click(screen.getByText("item 1"));
|
|
868
|
+
userEvent.click(screen.getByText("item 2"));
|
|
666
869
|
|
|
667
870
|
// Assert
|
|
668
|
-
expect(
|
|
871
|
+
expect(opener).toHaveTextContent("2 items");
|
|
669
872
|
});
|
|
670
873
|
|
|
671
874
|
it("passes the current label to the custom opener (all items selected)", () => {
|
|
672
875
|
// Arrange
|
|
673
|
-
const
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
876
|
+
const ControlledMultiSelect = () => {
|
|
877
|
+
const [selected, setSelected] = React.useState([]);
|
|
878
|
+
return (
|
|
879
|
+
<MultiSelect
|
|
880
|
+
testId="openTest"
|
|
881
|
+
onChange={(values) => {
|
|
882
|
+
setSelected(values);
|
|
883
|
+
}}
|
|
884
|
+
selectedValues={selected}
|
|
885
|
+
opener={({text}) => (
|
|
886
|
+
<button
|
|
887
|
+
onClick={jest.fn()}
|
|
888
|
+
data-test-id="custom-opener"
|
|
889
|
+
>
|
|
890
|
+
{text}
|
|
891
|
+
</button>
|
|
892
|
+
)}
|
|
893
|
+
>
|
|
894
|
+
<OptionItem label="item 1" value="1" />
|
|
895
|
+
<OptionItem label="item 2" value="2" />
|
|
896
|
+
<OptionItem label="item 3" value="3" />
|
|
897
|
+
</MultiSelect>
|
|
898
|
+
);
|
|
899
|
+
};
|
|
691
900
|
|
|
692
|
-
|
|
693
|
-
|
|
901
|
+
render(<ControlledMultiSelect />);
|
|
902
|
+
|
|
903
|
+
const opener = screen.getByTestId("custom-opener");
|
|
694
904
|
// open dropdown
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
905
|
+
userEvent.click(opener);
|
|
906
|
+
|
|
907
|
+
// Act
|
|
908
|
+
userEvent.click(screen.getByText("item 1"));
|
|
909
|
+
userEvent.click(screen.getByText("item 2"));
|
|
910
|
+
userEvent.click(screen.getByText("item 3"));
|
|
699
911
|
|
|
700
912
|
// Assert
|
|
701
|
-
expect(
|
|
913
|
+
expect(opener).toHaveTextContent("All items");
|
|
702
914
|
});
|
|
703
915
|
});
|
|
704
916
|
|
|
@@ -708,7 +920,7 @@ describe("MultiSelect", () => {
|
|
|
708
920
|
const labels: $Shape<Labels> = {
|
|
709
921
|
noneSelected: "0 escuelas",
|
|
710
922
|
};
|
|
711
|
-
|
|
923
|
+
render(
|
|
712
924
|
<MultiSelect
|
|
713
925
|
onChange={jest.fn()}
|
|
714
926
|
testId="translated-multi-select"
|
|
@@ -721,12 +933,10 @@ describe("MultiSelect", () => {
|
|
|
721
933
|
);
|
|
722
934
|
|
|
723
935
|
// Act
|
|
724
|
-
const opener =
|
|
725
|
-
.find(`[data-test-id="translated-multi-select"]`)
|
|
726
|
-
.last();
|
|
936
|
+
const opener = screen.getByTestId("translated-multi-select");
|
|
727
937
|
|
|
728
938
|
// Assert
|
|
729
|
-
expect(opener).
|
|
939
|
+
expect(opener).toHaveTextContent("0 escuelas");
|
|
730
940
|
});
|
|
731
941
|
|
|
732
942
|
it("passes the custom label to the opener (2 items selected)", () => {
|
|
@@ -735,11 +945,12 @@ describe("MultiSelect", () => {
|
|
|
735
945
|
someSelected: (numSelectedValues) =>
|
|
736
946
|
`${numSelectedValues} escuelas`,
|
|
737
947
|
};
|
|
738
|
-
|
|
948
|
+
render(
|
|
739
949
|
<MultiSelect
|
|
740
950
|
onChange={jest.fn()}
|
|
741
951
|
testId="translated-multi-select"
|
|
742
952
|
labels={labels}
|
|
953
|
+
selectedValues={["1", "2"]}
|
|
743
954
|
>
|
|
744
955
|
<OptionItem label="school 1" value="1" />
|
|
745
956
|
<OptionItem label="school 2" value="2" />
|
|
@@ -748,18 +959,10 @@ describe("MultiSelect", () => {
|
|
|
748
959
|
);
|
|
749
960
|
|
|
750
961
|
// Act
|
|
751
|
-
const opener =
|
|
752
|
-
// open dropdown
|
|
753
|
-
opener.simulate("click");
|
|
754
|
-
// select the first and second items manually via selectedValues on
|
|
755
|
-
// MultiSelect
|
|
756
|
-
wrapper.setProps({selectedValues: ["1", "2"]});
|
|
757
|
-
const openerElement = wrapper
|
|
758
|
-
.find(`[data-test-id="translated-multi-select"]`)
|
|
759
|
-
.last();
|
|
962
|
+
const opener = screen.getByTestId("translated-multi-select");
|
|
760
963
|
|
|
761
964
|
// Assert
|
|
762
|
-
expect(
|
|
965
|
+
expect(opener).toHaveTextContent("2 escuelas");
|
|
763
966
|
});
|
|
764
967
|
|
|
765
968
|
it("passes the custom label to the opener (all items selected)", () => {
|
|
@@ -767,11 +970,12 @@ describe("MultiSelect", () => {
|
|
|
767
970
|
const labels: $Shape<Labels> = {
|
|
768
971
|
allSelected: "Todas las escuelas",
|
|
769
972
|
};
|
|
770
|
-
|
|
973
|
+
render(
|
|
771
974
|
<MultiSelect
|
|
772
975
|
onChange={jest.fn()}
|
|
773
976
|
testId="translated-multi-select"
|
|
774
977
|
labels={labels}
|
|
978
|
+
selectedValues={["1", "2", "3"]}
|
|
775
979
|
>
|
|
776
980
|
<OptionItem label="school 1" value="1" />
|
|
777
981
|
<OptionItem label="school 2" value="2" />
|
|
@@ -780,17 +984,10 @@ describe("MultiSelect", () => {
|
|
|
780
984
|
);
|
|
781
985
|
|
|
782
986
|
// Act
|
|
783
|
-
const opener =
|
|
784
|
-
// open dropdown
|
|
785
|
-
opener.simulate("click");
|
|
786
|
-
// select all items manually via selectedValues on MultiSelect
|
|
787
|
-
wrapper.setProps({selectedValues: ["1", "2", "3"]});
|
|
788
|
-
const openerElement = wrapper
|
|
789
|
-
.find(`[data-test-id="translated-multi-select"]`)
|
|
790
|
-
.last();
|
|
987
|
+
const opener = screen.getByTestId("translated-multi-select");
|
|
791
988
|
|
|
792
989
|
// Assert
|
|
793
|
-
expect(
|
|
990
|
+
expect(opener).toHaveTextContent("Todas las escuelas");
|
|
794
991
|
});
|
|
795
992
|
|
|
796
993
|
it("passes the custom label to the dismiss icon", () => {
|
|
@@ -798,7 +995,7 @@ describe("MultiSelect", () => {
|
|
|
798
995
|
const labels: $Shape<Labels> = {
|
|
799
996
|
clearSearch: "Limpiar busqueda",
|
|
800
997
|
};
|
|
801
|
-
|
|
998
|
+
render(
|
|
802
999
|
<MultiSelect
|
|
803
1000
|
onChange={jest.fn()}
|
|
804
1001
|
isFilterable={true}
|
|
@@ -812,16 +1009,16 @@ describe("MultiSelect", () => {
|
|
|
812
1009
|
);
|
|
813
1010
|
|
|
814
1011
|
// Act
|
|
815
|
-
const opener =
|
|
1012
|
+
const opener = screen.getByTestId("translated-multi-select");
|
|
816
1013
|
// open dropdown
|
|
817
|
-
|
|
1014
|
+
userEvent.click(opener);
|
|
818
1015
|
// search text
|
|
819
|
-
|
|
1016
|
+
userEvent.type(screen.getByPlaceholderText("Filter"), "school");
|
|
820
1017
|
// get icon instance
|
|
821
|
-
const
|
|
1018
|
+
const dismissIcon = screen.getByLabelText("Limpiar busqueda");
|
|
822
1019
|
|
|
823
1020
|
// Assert
|
|
824
|
-
expect(
|
|
1021
|
+
expect(dismissIcon).toBeInTheDocument();
|
|
825
1022
|
});
|
|
826
1023
|
|
|
827
1024
|
it("passes the custom label to the search input field", () => {
|
|
@@ -829,7 +1026,7 @@ describe("MultiSelect", () => {
|
|
|
829
1026
|
const labels: $Shape<Labels> = {
|
|
830
1027
|
filter: "Filtrar",
|
|
831
1028
|
};
|
|
832
|
-
|
|
1029
|
+
render(
|
|
833
1030
|
<MultiSelect
|
|
834
1031
|
onChange={jest.fn()}
|
|
835
1032
|
isFilterable={true}
|
|
@@ -843,16 +1040,14 @@ describe("MultiSelect", () => {
|
|
|
843
1040
|
);
|
|
844
1041
|
|
|
845
1042
|
// Act
|
|
846
|
-
const opener =
|
|
1043
|
+
const opener = screen.getByTestId("translated-multi-select");
|
|
847
1044
|
// open dropdown
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
// get text field instance
|
|
852
|
-
const searchInput = wrapper.find(SearchTextInput).find("input");
|
|
1045
|
+
userEvent.click(opener);
|
|
1046
|
+
|
|
1047
|
+
const searchInput = screen.getByPlaceholderText("Filtrar");
|
|
853
1048
|
|
|
854
1049
|
// Assert
|
|
855
|
-
expect(searchInput).
|
|
1050
|
+
expect(searchInput).toBeInTheDocument();
|
|
856
1051
|
});
|
|
857
1052
|
|
|
858
1053
|
it("passes the custom label to the no results label", () => {
|
|
@@ -860,7 +1055,7 @@ describe("MultiSelect", () => {
|
|
|
860
1055
|
const labels: $Shape<Labels> = {
|
|
861
1056
|
noResults: "No hay resultados",
|
|
862
1057
|
};
|
|
863
|
-
|
|
1058
|
+
render(
|
|
864
1059
|
<MultiSelect
|
|
865
1060
|
onChange={jest.fn()}
|
|
866
1061
|
isFilterable={true}
|
|
@@ -873,19 +1068,17 @@ describe("MultiSelect", () => {
|
|
|
873
1068
|
</MultiSelect>,
|
|
874
1069
|
);
|
|
875
1070
|
|
|
876
|
-
|
|
877
|
-
const opener = wrapper.find(SelectOpener);
|
|
1071
|
+
const opener = screen.getByTestId("translated-multi-select");
|
|
878
1072
|
// open dropdown
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
const noResultsLabel = wrapper.find(
|
|
884
|
-
`[data-test-id="dropdown-core-no-results"]`,
|
|
885
|
-
);
|
|
1073
|
+
userEvent.click(opener);
|
|
1074
|
+
|
|
1075
|
+
// Act
|
|
1076
|
+
userEvent.type(screen.getByPlaceholderText("Filter"), "other");
|
|
886
1077
|
|
|
887
1078
|
// Assert
|
|
888
|
-
expect(
|
|
1079
|
+
expect(screen.getByRole("listbox")).toHaveTextContent(
|
|
1080
|
+
"No hay resultados",
|
|
1081
|
+
);
|
|
889
1082
|
});
|
|
890
1083
|
|
|
891
1084
|
it("passes the custom label to the select all shortcut", () => {
|
|
@@ -894,7 +1087,7 @@ describe("MultiSelect", () => {
|
|
|
894
1087
|
selectAllLabel: (numOptions) =>
|
|
895
1088
|
`Seleccionar todas las escuelas (${numOptions})`,
|
|
896
1089
|
};
|
|
897
|
-
|
|
1090
|
+
render(
|
|
898
1091
|
<MultiSelect
|
|
899
1092
|
onChange={jest.fn()}
|
|
900
1093
|
isFilterable={true}
|
|
@@ -909,16 +1102,14 @@ describe("MultiSelect", () => {
|
|
|
909
1102
|
);
|
|
910
1103
|
|
|
911
1104
|
// Act
|
|
912
|
-
const opener =
|
|
1105
|
+
const opener = screen.getByTestId("translated-multi-select");
|
|
913
1106
|
// open dropdown
|
|
914
|
-
|
|
915
|
-
// `select all` shortcut
|
|
916
|
-
const selectAllItem = wrapper.find(ActionItem).at(0);
|
|
1107
|
+
userEvent.click(opener);
|
|
917
1108
|
|
|
918
1109
|
// Assert
|
|
919
|
-
expect(
|
|
920
|
-
"Seleccionar todas las escuelas (3)",
|
|
921
|
-
);
|
|
1110
|
+
expect(
|
|
1111
|
+
screen.getByText("Seleccionar todas las escuelas (3)"),
|
|
1112
|
+
).toBeInTheDocument();
|
|
922
1113
|
});
|
|
923
1114
|
|
|
924
1115
|
it("passes the custom label to the select none shortcut", () => {
|
|
@@ -926,7 +1117,7 @@ describe("MultiSelect", () => {
|
|
|
926
1117
|
const labels: $Shape<Labels> = {
|
|
927
1118
|
selectNoneLabel: "Deseleccionar todas las escuelas",
|
|
928
1119
|
};
|
|
929
|
-
|
|
1120
|
+
render(
|
|
930
1121
|
<MultiSelect
|
|
931
1122
|
onChange={jest.fn()}
|
|
932
1123
|
isFilterable={true}
|
|
@@ -941,24 +1132,27 @@ describe("MultiSelect", () => {
|
|
|
941
1132
|
);
|
|
942
1133
|
|
|
943
1134
|
// Act
|
|
944
|
-
const opener =
|
|
1135
|
+
const opener = screen.getByTestId("translated-multi-select");
|
|
945
1136
|
// open dropdown
|
|
946
|
-
|
|
947
|
-
// `select none` shortcut
|
|
948
|
-
const selectNoneItem = wrapper.find(ActionItem).at(1);
|
|
1137
|
+
userEvent.click(opener);
|
|
949
1138
|
|
|
950
1139
|
// Assert
|
|
951
|
-
expect(
|
|
952
|
-
"Deseleccionar todas las escuelas",
|
|
953
|
-
);
|
|
1140
|
+
expect(
|
|
1141
|
+
screen.getByText("Deseleccionar todas las escuelas"),
|
|
1142
|
+
).toBeInTheDocument();
|
|
954
1143
|
});
|
|
955
1144
|
|
|
956
1145
|
it("verifies a custom label is updated when props change", () => {
|
|
957
1146
|
// Arrange
|
|
958
|
-
const
|
|
1147
|
+
const initialLabels: $Shape<Labels> = {
|
|
959
1148
|
selectNoneLabel: "Deseleccionar todas las escuelas",
|
|
960
1149
|
};
|
|
961
|
-
|
|
1150
|
+
|
|
1151
|
+
const TranslatedComponent = ({
|
|
1152
|
+
labels,
|
|
1153
|
+
}: {|
|
|
1154
|
+
labels: $Shape<Labels>,
|
|
1155
|
+
|}) => (
|
|
962
1156
|
<MultiSelect
|
|
963
1157
|
onChange={jest.fn()}
|
|
964
1158
|
isFilterable={true}
|
|
@@ -968,24 +1162,29 @@ describe("MultiSelect", () => {
|
|
|
968
1162
|
<OptionItem label="school 1" value="1" />
|
|
969
1163
|
<OptionItem label="school 2" value="2" />
|
|
970
1164
|
<OptionItem label="school 3" value="3" />
|
|
971
|
-
</MultiSelect
|
|
1165
|
+
</MultiSelect>
|
|
1166
|
+
);
|
|
1167
|
+
|
|
1168
|
+
const {rerender} = render(
|
|
1169
|
+
<TranslatedComponent labels={initialLabels} />,
|
|
972
1170
|
);
|
|
973
1171
|
|
|
1172
|
+
// update label value
|
|
1173
|
+
const updatedLabels: $Shape<Labels> = {
|
|
1174
|
+
selectNoneLabel: "Ninguna seleccionada",
|
|
1175
|
+
};
|
|
1176
|
+
|
|
1177
|
+
rerender(<TranslatedComponent labels={updatedLabels} />);
|
|
1178
|
+
|
|
974
1179
|
// Act
|
|
975
|
-
const opener =
|
|
1180
|
+
const opener = screen.getByRole("button");
|
|
976
1181
|
// open dropdown
|
|
977
|
-
|
|
978
|
-
// update label value
|
|
979
|
-
wrapper.setProps({
|
|
980
|
-
labels: {
|
|
981
|
-
selectNoneLabel: "Ninguna seleccionada",
|
|
982
|
-
},
|
|
983
|
-
});
|
|
984
|
-
// `select none` shortcut
|
|
985
|
-
const selectNoneItem = wrapper.find(ActionItem).at(1);
|
|
1182
|
+
userEvent.click(opener);
|
|
986
1183
|
|
|
987
1184
|
// Assert
|
|
988
|
-
expect(
|
|
1185
|
+
expect(
|
|
1186
|
+
screen.getByText(updatedLabels.selectNoneLabel),
|
|
1187
|
+
).toBeInTheDocument();
|
|
989
1188
|
});
|
|
990
1189
|
});
|
|
991
1190
|
});
|