@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.
@@ -1,215 +1,188 @@
1
1
  /* eslint-disable max-lines */
2
2
  //@flow
3
3
  import * as React from "react";
4
- import {mount} from "enzyme";
5
- import "jest-enzyme";
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
- jest.useFakeTimers();
23
-
24
- jest.mock("../dropdown-core-virtualized.js");
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
- let select;
28
- const allChanges = [];
29
- const saveUpdate = (update) => {
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={(selectedValues) => {
58
- saveUpdate(selectedValues);
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
- afterEach(() => {
74
- // We have to explicitly unmount before clearing mocks, otherwise jest
75
- // timers will throw because we'll try to clear an animation frame that
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
- it("closes/opens the select on mouse click, space, and enter", () => {
84
- const opener = select.find(SelectOpener);
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
- it("selects items as expected", () => {
105
- select.setState({open: true});
106
- const noop = jest.fn();
107
- const nativeEvent = {
108
- nativeEvent: {stopImmediatePropagation: noop},
109
- };
42
+ // Assert
43
+ expect(screen.getByRole("listbox")).toBeInTheDocument();
44
+ });
110
45
 
111
- expect(select.prop("selectedValues")).toEqual(["2"]);
112
-
113
- // Grab the second item in the list
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
- it("displays correct text for opener", () => {
157
- const opener = select.find(SelectOpener);
50
+ userEvent.tab();
51
+ userEvent.keyboard("{enter}"); // open
158
52
 
159
- // No items are selected, display placeholder because there is one
160
- select.setProps({selectedValues: []});
161
- expect(opener.text()).toEqual("Choose");
53
+ // Act
54
+ userEvent.keyboard("{escape}");
162
55
 
163
- // One item is selected, display that item's label
164
- select.setProps({selectedValues: ["1"]});
165
- expect(opener.text()).toEqual("item 1");
56
+ // Assert
57
+ expect(onChange).not.toHaveBeenCalled();
58
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
59
+ });
166
60
 
167
- // More than one item is selected, display n itemTypes
168
- select.setProps({selectedValues: ["1", "2"]});
169
- expect(opener.text()).toEqual("2 students");
61
+ it("displays correct text for opener", () => {
62
+ // Arrange
63
+ render(uncontrolledSingleSelect);
170
64
 
171
- // All items are selected
172
- select.setProps({selectedValues: ["1", "2", "3"]});
173
- expect(opener.text()).toEqual("All students");
174
- });
65
+ // Act
66
+ const opener = screen.getByRole("button");
175
67
 
176
- it("displays All selected text when no items is selected for opener with implicitAllEnabled", () => {
177
- // Arrange, Act
178
- const opener = select.find(SelectOpener);
179
- select.setProps({implicitAllEnabled: true, selectedValues: []});
68
+ // Assert
69
+ // No items are selected, display placeholder because there is one
70
+ expect(opener).toHaveTextContent("Choose");
71
+ });
180
72
 
181
- // Assert
182
- expect(opener.text()).toEqual("All students");
183
- });
73
+ it("displays correct text for opener when there's one item selected", () => {
74
+ // Arrange
184
75
 
185
- it("verifies testId is added to the opener", () => {
186
- // Arrange
187
- const wrapper = mount(
188
- <MultiSelect
189
- selectedValues={["2"]}
190
- onChange={onChange}
191
- testId="some-test-id"
192
- >
193
- <OptionItem label="item 1" value="1" />
194
- <OptionItem label="item 2" value="2" />
195
- </MultiSelect>,
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
- // Act
199
- const opener = wrapper.find(SelectOpener).find("button");
173
+ // Act
174
+ const opener = screen.getByRole("button");
200
175
 
201
- // Assert
202
- expect(opener.prop("data-test-id")).toBe("some-test-id");
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
- class ControlledComponent extends React.Component<Props, State> {
222
- state: State = {
223
- opened: this.props.opened,
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
- this.setState({
228
- opened: opened,
229
- });
198
+ const handleToggleMenu = (opened) => {
199
+ setOpened(opened);
200
+ props.onToggle && props.onToggle(opened);
201
+ };
230
202
 
231
- this.props.onToggle && this.props.onToggle(opened);
203
+ const handleChange = (newValues) => {
204
+ setSelectedValues(newValues);
232
205
  };
233
206
 
234
- render(): React.Node {
235
- return (
236
- <React.Fragment>
237
- <MultiSelect
238
- labels={labels}
239
- onChange={onChange}
240
- opened={this.state.opened}
241
- onToggle={this.handleToggleMenu}
242
- >
243
- <OptionItem label="item 1" value="1" />
244
- <OptionItem label="item 2" value="2" />
245
- <OptionItem label="item 3" value="3" />
246
- </MultiSelect>
247
- <button
248
- data-test-id="parent-button"
249
- onClick={() => this.handleToggleMenu(true)}
250
- />
251
- </React.Fragment>
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
- const wrapper = mount(
260
- <ControlledComponent onToggle={onToggleMock} />,
261
- );
233
+ render(<ControlledComponent onToggle={onToggleMock} />);
262
234
 
263
235
  // Act
264
- wrapper.find(`[data-test-id="parent-button"]`).simulate("click");
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
- const wrapper = mount(
274
- <ControlledComponent onToggle={onToggleMock} />,
275
- );
245
+ render(<ControlledComponent onToggle={onToggleMock} />);
276
246
 
277
247
  // Act
278
248
  // open the menu from the outside
279
- wrapper.find(`[data-test-id="parent-button"]`).simulate("click");
249
+ userEvent.click(screen.getByTestId("parent-button"));
280
250
  // click on the opener
281
- wrapper.find(SelectOpener).simulate("click");
251
+ userEvent.click(screen.getByTestId("multi-select-opener"));
282
252
 
283
253
  // Assert
284
- expect(onToggleMock).toHaveBeenCalledWith(false);
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
- const wrapper = mount(
291
- <ControlledComponent onToggle={onToggleMock} />,
292
- );
262
+ render(<ControlledComponent onToggle={onToggleMock} />);
293
263
 
294
264
  // Act
295
- wrapper.find(SelectOpener).simulate("click");
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
- const wrapper = mount(<ControlledComponent />);
274
+ render(<ControlledComponent />);
304
275
 
305
276
  // Act
306
- // click on the anchor
307
- wrapper.find(SelectOpener).simulate("click");
277
+ userEvent.click(screen.getByTestId("multi-select-opener"));
308
278
 
309
279
  // Assert
310
- expect(wrapper.find(MultiSelect).prop("opened")).toBe(true);
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
- const wrapper = mount(<ControlledComponent />);
285
+ render(<ControlledComponent />);
316
286
 
317
287
  // Act
318
288
  // open the menu from the outside
319
- wrapper.find(`[data-test-id="parent-button"]`).simulate("click");
289
+ userEvent.click(screen.getByTestId("parent-button"));
320
290
  // click on the dropdown anchor to hide the menu
321
- wrapper.find(SelectOpener).simulate("click");
291
+ userEvent.click(screen.getByTestId("multi-select-opener"));
322
292
 
323
293
  // Assert
324
- expect(wrapper.find(MultiSelect).prop("opened")).toBe(false);
294
+ expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
325
295
  });
326
- });
327
296
 
328
- it("displays SearchTextInput when isFilterable is true", () => {
329
- // Arrange
330
- select.setProps({isFilterable: true});
331
- select.setState({open: true});
297
+ it("selects on item as expected", () => {
298
+ // Arrange
299
+ render(<ControlledComponent />);
332
300
 
333
- // Act
334
- const searchInput = select.find(SearchTextInput);
301
+ const opener = screen.getByTestId("multi-select-opener");
302
+ userEvent.click(opener);
335
303
 
336
- // Assert
337
- expect(searchInput.exists()).toBe(true);
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
- it("displays SearchTextInput with dismiss button when search text exists", () => {
341
- // Arrange
342
- select.setProps({isFilterable: true});
343
- select.setState({open: true});
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
- // Act
347
- select.setState({searchText: "text"});
318
+ it("dropdown menu is still open after selection", () => {
319
+ // Arrange
320
+ const onToggleMock = jest.fn();
321
+ render(<ControlledComponent onToggle={onToggleMock} />);
348
322
 
349
- // Assert
350
- expect(select.find(IconButton).exists()).toBe(true);
351
- });
323
+ const opener = screen.getByTestId("multi-select-opener");
324
+ userEvent.click(opener);
352
325
 
353
- it("filters the items by the search input (case insensitive)", () => {
354
- // Arrange
355
- select.setProps({isFilterable: true});
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
- // Act
358
- select.setState({
359
- open: true,
360
- searchText: "Item 1",
335
+ // Assert
336
+ expect(onToggleMock).toHaveBeenCalledTimes(1);
361
337
  });
362
338
 
363
- // Assert
364
- expect(select.find(OptionItem).at(0).text()).toEqual("item 1");
365
- expect(select.find(OptionItem).at(1).exists()).toBe(false);
366
- });
339
+ it("selects two items as expected", () => {
340
+ // Arrange
341
+ render(<ControlledComponent />);
367
342
 
368
- it("Hides shortcuts when there are any text in search text input", () => {
369
- // Arrange
370
- select.setProps({isFilterable: true});
343
+ const opener = screen.getByTestId("multi-select-opener");
344
+ userEvent.click(opener);
371
345
 
372
- // Act
373
- select.setState({open: true});
374
- expect(select.find(ActionItem).at(0).exists()).toBe(true);
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
- // Assert
377
- select.setState({searchText: "2"});
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
- expect(select.find(ActionItem).at(0).exists()).toBe(false);
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
- it("Pressing arrow up from search input moves focus to previous focusable item", () => {
383
- // Arrange
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
- it("Pressing arrow down from search input moves focus to previous focusable item", () => {
404
- // Arrange
405
- select.setProps({isFilterable: true});
406
- select.setState({open: true});
407
- const searchInput = select.find(SearchTextInput);
408
- const selectAll = select.find(ActionItem).at(0).find(ClickableBehavior);
409
- jest.runAllTimers();
410
- expect(searchInput.state("focused")).toBe(true);
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
- it("Selected items are at the top of the items when isFilterable is true", () => {
422
- // Arrange
423
- const opener = select.find(SelectOpener);
424
- select.setProps({
425
- isFilterable: true,
426
- selectedValues: ["3"],
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
- // Act
430
- opener.simulate("click");
396
+ it("selects all the items when the 'All items' shortcut is selected", () => {
397
+ // Arrange
398
+ render(<ControlledComponent shortcuts={true} />);
431
399
 
432
- // Assert
433
- const item3 = select.find(OptionItem).at(0);
434
- expect(item3.text()).toEqual("item 3");
435
- });
400
+ const opener = screen.getByTestId("multi-select-opener");
401
+ userEvent.click(opener);
436
402
 
437
- it("Type something in SearchTextInput should update searchText in MultiSelect", () => {
438
- // Arrange
439
- select.setProps({isFilterable: true});
440
- select.setState({open: true});
441
- const searchInput = select.find(SearchTextInput).find("input");
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
- // Act
444
- searchInput.simulate("change", {target: {value: "Item 1"}});
410
+ // Assert
411
+ expect(opener).toHaveTextContent("All fruits");
412
+ });
445
413
 
446
- // Assert
447
- expect(select.state("searchText")).toEqual("Item 1");
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
- it("Click dismiss button should clear the searchText in MultiSelect", () => {
451
- // Arrange
452
- select.setProps({isFilterable: true});
453
- select.setState({open: true, searchText: "Should be cleared"});
454
- const dismissBtn = select.find(IconButton);
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
- // Act
457
- dismissBtn.simulate("click");
427
+ // Act
428
+ const selectNone = screen.getByText(/Select none/i);
429
+ userEvent.click(selectNone, undefined, {
430
+ skipPointerEventsCheck: true, // Popper compatibility
431
+ });
458
432
 
459
- // Assert
460
- expect(select.state("searchText")).toEqual("");
433
+ // Assert
434
+ expect(opener).toHaveTextContent("0 items");
435
+ });
461
436
  });
462
437
 
463
- it("Open MultiSelect should clear the searchText", () => {
464
- // Arrange
465
- select.setProps({isFilterable: true});
466
- select.setState({searchText: "some text"});
467
- const opener = select.find(SelectOpener);
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
- // Act
470
- opener.simulate("click");
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
- // Assert
473
- expect(select.state("searchText")).toEqual("");
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
- const wrapper = mount(
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 = wrapper.find(DropdownOpener);
494
- opener.simulate("click");
679
+ const opener = screen.getByLabelText("Search");
680
+ userEvent.click(opener);
495
681
 
496
682
  // Assert
497
- expect(wrapper.state("open")).toBe(true);
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
- const wrapper = mount(
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 = wrapper.find(DropdownOpener);
519
- opener.simulate("click");
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 menu = mount(
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 = menu.find(DropdownOpener).find("button");
731
+ const opener = screen.getByLabelText("Custom opener");
544
732
 
545
733
  // Assert
546
- expect(opener.prop("data-test-id")).toBe("custom-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 menu = mount(
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 = menu.find(DropdownOpener).find("button");
753
+ const opener = screen.getByLabelText("Custom opener");
564
754
 
565
755
  // Assert
566
- expect(opener.prop("data-test-id")).not.toBeDefined();
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
- const menu = mount(
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 = menu.find(DropdownOpener);
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(openerElement).toHaveText("No items selected");
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 menu = mount(
607
- <MultiSelect
608
- testId="openTest"
609
- onChange={jest.fn()}
610
- opener={({text}) => (
611
- <button
612
- onClick={jest.fn()}
613
- data-test-id="custom-opener"
614
- >
615
- {text}
616
- </button>
617
- )}
618
- >
619
- <OptionItem label="item 1" value="1" />
620
- <OptionItem label="item 2" value="2" />
621
- <OptionItem label="item 3" value="3" />
622
- </MultiSelect>,
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
- // Act
626
- const opener = menu.find(DropdownOpener);
819
+ render(<ControlledMultiSelect />);
820
+
821
+ const opener = screen.getByTestId("custom-opener");
627
822
  // open dropdown
628
- opener.simulate("click");
629
- // select the first item manually via selectedValues on MultiSelect
630
- menu.setProps({selectedValues: ["1"]});
631
- const openerElement = menu.find(`[data-test-id="custom-opener"]`);
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(openerElement).toHaveText("item 1");
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 menu = mount(
640
- <MultiSelect
641
- testId="openTest"
642
- onChange={jest.fn()}
643
- opener={({text}) => (
644
- <button
645
- onClick={jest.fn()}
646
- data-test-id="custom-opener"
647
- >
648
- {text}
649
- </button>
650
- )}
651
- >
652
- <OptionItem label="item 1" value="1" />
653
- <OptionItem label="item 2" value="2" />
654
- <OptionItem label="item 3" value="3" />
655
- </MultiSelect>,
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
- // Act
659
- const opener = menu.find(DropdownOpener);
860
+ render(<ControlledMultiSelect />);
861
+
862
+ const opener = screen.getByTestId("custom-opener");
660
863
  // open dropdown
661
- opener.simulate("click");
662
- // select the first and second items manually via selectedValues on
663
- // MultiSelect
664
- menu.setProps({selectedValues: ["1", "2"]});
665
- const openerElement = menu.find(`[data-test-id="custom-opener"]`);
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(openerElement).toHaveText("2 items");
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 menu = mount(
674
- <MultiSelect
675
- testId="openTest"
676
- onChange={jest.fn()}
677
- opener={({text}) => (
678
- <button
679
- onClick={jest.fn()}
680
- data-test-id="custom-opener"
681
- >
682
- {text}
683
- </button>
684
- )}
685
- >
686
- <OptionItem label="item 1" value="1" />
687
- <OptionItem label="item 2" value="2" />
688
- <OptionItem label="item 3" value="3" />
689
- </MultiSelect>,
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
- // Act
693
- const opener = menu.find(DropdownOpener);
901
+ render(<ControlledMultiSelect />);
902
+
903
+ const opener = screen.getByTestId("custom-opener");
694
904
  // open dropdown
695
- opener.simulate("click");
696
- // select all items manually via selectedValues on MultiSelect
697
- menu.setProps({selectedValues: ["1", "2", "3"]});
698
- const openerElement = menu.find(`[data-test-id="custom-opener"]`);
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(openerElement).toHaveText("All items");
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
- const wrapper = mount(
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 = wrapper
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).toHaveText("0 escuelas");
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
- const wrapper = mount(
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 = wrapper.find(SelectOpener);
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(openerElement).toHaveText("2 escuelas");
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
- const wrapper = mount(
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 = wrapper.find(SelectOpener);
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(openerElement).toHaveText("Todas las escuelas");
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
- const wrapper = mount(
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 = wrapper.find(SelectOpener);
1012
+ const opener = screen.getByTestId("translated-multi-select");
816
1013
  // open dropdown
817
- opener.simulate("click");
1014
+ userEvent.click(opener);
818
1015
  // search text
819
- wrapper.setState({searchText: "text"});
1016
+ userEvent.type(screen.getByPlaceholderText("Filter"), "school");
820
1017
  // get icon instance
821
- const searchIcon = wrapper.find(SearchTextInput).find(IconButton);
1018
+ const dismissIcon = screen.getByLabelText("Limpiar busqueda");
822
1019
 
823
1020
  // Assert
824
- expect(searchIcon).toHaveProp("aria-label", "Limpiar busqueda");
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
- const wrapper = mount(
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 = wrapper.find(SelectOpener);
1043
+ const opener = screen.getByTestId("translated-multi-select");
847
1044
  // open dropdown
848
- opener.simulate("click");
849
- // search text
850
- wrapper.setState({searchText: "text"});
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).toHaveProp("placeholder", "Filtrar");
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
- const wrapper = mount(
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
- // Act
877
- const opener = wrapper.find(SelectOpener);
1071
+ const opener = screen.getByTestId("translated-multi-select");
878
1072
  // open dropdown
879
- opener.simulate("click");
880
- // search text
881
- wrapper.setState({searchText: "text"});
882
- // get dropdown instance
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(noResultsLabel).toHaveText("No hay resultados");
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
- const wrapper = mount(
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 = wrapper.find(SelectOpener);
1105
+ const opener = screen.getByTestId("translated-multi-select");
913
1106
  // open dropdown
914
- opener.simulate("click");
915
- // `select all` shortcut
916
- const selectAllItem = wrapper.find(ActionItem).at(0);
1107
+ userEvent.click(opener);
917
1108
 
918
1109
  // Assert
919
- expect(selectAllItem).toHaveText(
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
- const wrapper = mount(
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 = wrapper.find(SelectOpener);
1135
+ const opener = screen.getByTestId("translated-multi-select");
945
1136
  // open dropdown
946
- opener.simulate("click");
947
- // `select none` shortcut
948
- const selectNoneItem = wrapper.find(ActionItem).at(1);
1137
+ userEvent.click(opener);
949
1138
 
950
1139
  // Assert
951
- expect(selectNoneItem).toHaveText(
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 labels: $Shape<Labels> = {
1147
+ const initialLabels: $Shape<Labels> = {
959
1148
  selectNoneLabel: "Deseleccionar todas las escuelas",
960
1149
  };
961
- const wrapper = mount(
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 = wrapper.find(SelectOpener);
1180
+ const opener = screen.getByRole("button");
976
1181
  // open dropdown
977
- opener.simulate("click");
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(selectNoneItem).toHaveText("Ninguna seleccionada");
1185
+ expect(
1186
+ screen.getByText(updatedLabels.selectNoneLabel),
1187
+ ).toBeInTheDocument();
989
1188
  });
990
1189
  });
991
1190
  });