@primer/components 30.3.0-rc.2010c7d4 → 30.3.0-rc.9dbc85a9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/CHANGELOG.md +4 -2
  2. package/dist/browser.esm.js +717 -718
  3. package/dist/browser.esm.js.map +1 -1
  4. package/dist/browser.umd.js +320 -321
  5. package/dist/browser.umd.js.map +1 -1
  6. package/docs/content/Autocomplete.mdx +627 -0
  7. package/docs/content/TextInputTokens.mdx +89 -0
  8. package/docs/src/@primer/gatsby-theme-doctocat/nav.yml +2 -0
  9. package/lib/AnchoredOverlay/AnchoredOverlay.d.ts +2 -1
  10. package/lib/AnchoredOverlay/AnchoredOverlay.js +11 -3
  11. package/lib/Autocomplete/Autocomplete.d.ts +304 -0
  12. package/lib/Autocomplete/Autocomplete.js +145 -0
  13. package/lib/Autocomplete/AutocompleteContext.d.ts +17 -0
  14. package/lib/Autocomplete/AutocompleteContext.js +11 -0
  15. package/lib/Autocomplete/AutocompleteInput.d.ts +292 -0
  16. package/lib/Autocomplete/AutocompleteInput.js +157 -0
  17. package/lib/Autocomplete/AutocompleteMenu.d.ts +72 -0
  18. package/lib/Autocomplete/AutocompleteMenu.js +224 -0
  19. package/lib/Autocomplete/AutocompleteOverlay.d.ts +20 -0
  20. package/lib/Autocomplete/AutocompleteOverlay.js +80 -0
  21. package/lib/Autocomplete/index.d.ts +2 -0
  22. package/lib/Autocomplete/index.js +15 -0
  23. package/lib/FilteredActionList/FilteredActionList.js +5 -31
  24. package/lib/Overlay.d.ts +1 -0
  25. package/lib/Overlay.js +3 -1
  26. package/lib/__tests__/Autocomplete.test.d.ts +1 -0
  27. package/lib/__tests__/Autocomplete.test.js +528 -0
  28. package/lib/__tests__/behaviors/scrollIntoViewingArea.test.d.ts +1 -0
  29. package/lib/__tests__/behaviors/scrollIntoViewingArea.test.js +226 -0
  30. package/lib/behaviors/scrollIntoViewingArea.d.ts +1 -0
  31. package/lib/behaviors/scrollIntoViewingArea.js +39 -0
  32. package/lib/hooks/useOpenAndCloseFocus.d.ts +2 -1
  33. package/lib/hooks/useOpenAndCloseFocus.js +7 -2
  34. package/lib/hooks/useOverlay.d.ts +2 -1
  35. package/lib/hooks/useOverlay.js +4 -2
  36. package/lib/index.d.ts +2 -0
  37. package/lib/index.js +8 -0
  38. package/lib/stories/Autocomplete.stories.js +608 -0
  39. package/lib/utils/types/MandateProps.d.ts +3 -0
  40. package/lib/utils/types/MandateProps.js +1 -0
  41. package/lib/utils/types/index.d.ts +1 -0
  42. package/lib/utils/types/index.js +13 -0
  43. package/lib-esm/AnchoredOverlay/AnchoredOverlay.d.ts +2 -1
  44. package/lib-esm/AnchoredOverlay/AnchoredOverlay.js +11 -3
  45. package/lib-esm/Autocomplete/Autocomplete.d.ts +304 -0
  46. package/lib-esm/Autocomplete/Autocomplete.js +123 -0
  47. package/lib-esm/Autocomplete/AutocompleteContext.d.ts +17 -0
  48. package/lib-esm/Autocomplete/AutocompleteContext.js +2 -0
  49. package/lib-esm/Autocomplete/AutocompleteInput.d.ts +292 -0
  50. package/lib-esm/Autocomplete/AutocompleteInput.js +138 -0
  51. package/lib-esm/Autocomplete/AutocompleteMenu.d.ts +72 -0
  52. package/lib-esm/Autocomplete/AutocompleteMenu.js +205 -0
  53. package/lib-esm/Autocomplete/AutocompleteOverlay.d.ts +20 -0
  54. package/lib-esm/Autocomplete/AutocompleteOverlay.js +62 -0
  55. package/lib-esm/Autocomplete/index.d.ts +2 -0
  56. package/lib-esm/Autocomplete/index.js +1 -0
  57. package/lib-esm/FilteredActionList/FilteredActionList.js +3 -31
  58. package/lib-esm/Overlay.d.ts +1 -0
  59. package/lib-esm/Overlay.js +3 -1
  60. package/lib-esm/__tests__/Autocomplete.test.d.ts +1 -0
  61. package/lib-esm/__tests__/Autocomplete.test.js +494 -0
  62. package/lib-esm/__tests__/behaviors/scrollIntoViewingArea.test.d.ts +1 -0
  63. package/lib-esm/__tests__/behaviors/scrollIntoViewingArea.test.js +224 -0
  64. package/lib-esm/behaviors/scrollIntoViewingArea.d.ts +1 -0
  65. package/lib-esm/behaviors/scrollIntoViewingArea.js +30 -0
  66. package/lib-esm/hooks/useOpenAndCloseFocus.d.ts +2 -1
  67. package/lib-esm/hooks/useOpenAndCloseFocus.js +7 -2
  68. package/lib-esm/hooks/useOverlay.d.ts +2 -1
  69. package/lib-esm/hooks/useOverlay.js +4 -2
  70. package/lib-esm/index.d.ts +2 -0
  71. package/lib-esm/index.js +1 -0
  72. package/lib-esm/stories/Autocomplete.stories.js +549 -0
  73. package/lib-esm/utils/types/MandateProps.d.ts +3 -0
  74. package/lib-esm/utils/types/MandateProps.js +1 -0
  75. package/lib-esm/utils/types/index.d.ts +1 -0
  76. package/lib-esm/utils/types/index.js +2 -1
  77. package/package.json +1 -1
  78. package/src/AnchoredOverlay/AnchoredOverlay.tsx +14 -3
  79. package/src/Autocomplete/Autocomplete.tsx +103 -0
  80. package/src/Autocomplete/AutocompleteContext.tsx +19 -0
  81. package/src/Autocomplete/AutocompleteInput.tsx +179 -0
  82. package/src/Autocomplete/AutocompleteMenu.tsx +341 -0
  83. package/src/Autocomplete/AutocompleteOverlay.tsx +68 -0
  84. package/src/Autocomplete/index.ts +2 -0
  85. package/src/FilteredActionList/FilteredActionList.tsx +10 -25
  86. package/src/Overlay.tsx +4 -1
  87. package/src/__tests__/Autocomplete.test.tsx +444 -0
  88. package/src/__tests__/__snapshots__/Autocomplete.test.tsx.snap +3414 -0
  89. package/src/__tests__/behaviors/scrollIntoViewingArea.test.ts +195 -0
  90. package/src/behaviors/scrollIntoViewingArea.ts +27 -0
  91. package/src/hooks/useOpenAndCloseFocus.ts +7 -2
  92. package/src/hooks/useOverlay.tsx +4 -2
  93. package/src/index.ts +2 -0
  94. package/src/stories/Autocomplete.stories.tsx +572 -0
  95. package/src/utils/types/MandateProps.ts +19 -0
  96. package/src/utils/types/index.ts +1 -0
  97. package/stats.html +1 -1
@@ -0,0 +1,494 @@
1
+ function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
+
3
+ import React from 'react';
4
+ import { render } from '../utils/testing';
5
+ import { render as HTMLRender, fireEvent } from '@testing-library/react';
6
+ import { toHaveNoViolations } from 'jest-axe';
7
+ import 'babel-polyfill';
8
+ import Autocomplete from '../Autocomplete';
9
+ import { SSRProvider } from '../index';
10
+ import theme from '../theme';
11
+ import BaseStyles from '../BaseStyles';
12
+ import { ThemeProvider } from '../ThemeProvider';
13
+ import userEvent from '@testing-library/user-event';
14
+ expect.extend(toHaveNoViolations);
15
+ const mockItems = [{
16
+ text: 'zero',
17
+ id: 0
18
+ }, {
19
+ text: 'one',
20
+ id: 1
21
+ }, {
22
+ text: 'two',
23
+ id: 2
24
+ }, {
25
+ text: 'three',
26
+ id: 3
27
+ }, {
28
+ text: 'four',
29
+ id: 4
30
+ }, {
31
+ text: 'five',
32
+ id: 5
33
+ }, {
34
+ text: 'six',
35
+ id: 6
36
+ }, {
37
+ text: 'seven',
38
+ id: 7
39
+ }, {
40
+ text: 'twenty',
41
+ id: 20
42
+ }, {
43
+ text: 'twentyone',
44
+ id: 21
45
+ }]; // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
+
47
+ const AUTOCOMPLETE_LABEL = 'Autocomplete field';
48
+
49
+ const LabelledAutocomplete = ({
50
+ inputProps = {},
51
+ menuProps
52
+ }) => {
53
+ const {
54
+ ['aria-labelledby']: ariaLabelledBy = 'autocompleteLabel',
55
+ ...menuPropsRest
56
+ } = menuProps;
57
+ const {
58
+ id = 'autocompleteInput',
59
+ ...inputPropsRest
60
+ } = inputProps;
61
+ return /*#__PURE__*/React.createElement(ThemeProvider, {
62
+ theme: theme
63
+ }, /*#__PURE__*/React.createElement(SSRProvider, null, /*#__PURE__*/React.createElement(BaseStyles, null, /*#__PURE__*/React.createElement("label", {
64
+ htmlFor: id,
65
+ id: ariaLabelledBy
66
+ }, "Autocomplete field"), /*#__PURE__*/React.createElement(Autocomplete, {
67
+ id: "autocompleteId"
68
+ }, /*#__PURE__*/React.createElement(Autocomplete.Input, _extends({
69
+ id: id
70
+ }, inputPropsRest)), /*#__PURE__*/React.createElement(Autocomplete.Overlay, null, /*#__PURE__*/React.createElement(Autocomplete.Menu, _extends({
71
+ "aria-labelledby": ariaLabelledBy
72
+ }, menuPropsRest)))))));
73
+ };
74
+
75
+ LabelledAutocomplete.displayName = "LabelledAutocomplete";
76
+ describe('Autocomplete', () => {
77
+ describe('snapshots', () => {
78
+ it('renders a single select input', () => {
79
+ expect(render( /*#__PURE__*/React.createElement(SSRProvider, null, /*#__PURE__*/React.createElement(Autocomplete, {
80
+ id: "autocompleteId"
81
+ }, /*#__PURE__*/React.createElement(Autocomplete.Input, null), /*#__PURE__*/React.createElement(Autocomplete.Menu, {
82
+ "aria-labelledby": "labelId",
83
+ items: mockItems,
84
+ selectedItemIds: []
85
+ }))))).toMatchSnapshot();
86
+ });
87
+ it('renders a multiselect input', () => {
88
+ expect(render( /*#__PURE__*/React.createElement(SSRProvider, null, /*#__PURE__*/React.createElement(Autocomplete, {
89
+ id: "autocompleteId"
90
+ }, /*#__PURE__*/React.createElement(Autocomplete.Input, null), /*#__PURE__*/React.createElement(Autocomplete.Menu, {
91
+ "aria-labelledby": "labelId",
92
+ items: mockItems,
93
+ selectedItemIds: [],
94
+ selectionVariant: "multiple"
95
+ }))))).toMatchSnapshot();
96
+ });
97
+ it('renders a multiselect input with selected menu items', () => {
98
+ expect(render( /*#__PURE__*/React.createElement(SSRProvider, null, /*#__PURE__*/React.createElement(Autocomplete, {
99
+ id: "autocompleteId"
100
+ }, /*#__PURE__*/React.createElement(Autocomplete.Input, null), /*#__PURE__*/React.createElement(Autocomplete.Menu, {
101
+ "aria-labelledby": "labelId",
102
+ items: mockItems,
103
+ selectedItemIds: [0, 1, 2],
104
+ selectionVariant: "multiple"
105
+ }))))).toMatchSnapshot();
106
+ });
107
+ it('renders a menu that contains an item to add to the menu', () => {
108
+ const handleAddItemMock = jest.fn();
109
+ expect(render( /*#__PURE__*/React.createElement(SSRProvider, null, /*#__PURE__*/React.createElement(Autocomplete, {
110
+ id: "autocompleteId"
111
+ }, /*#__PURE__*/React.createElement(Autocomplete.Input, null), /*#__PURE__*/React.createElement(Autocomplete.Menu, {
112
+ "aria-labelledby": "labelId",
113
+ items: mockItems,
114
+ selectionVariant: "multiple",
115
+ selectedItemIds: [],
116
+ addNewItem: {
117
+ text: 'Add new item',
118
+ handleAddItem: handleAddItemMock
119
+ }
120
+ }))))).toMatchSnapshot();
121
+ });
122
+ it('renders a custom empty state message', () => {
123
+ expect(render( /*#__PURE__*/React.createElement(SSRProvider, null, /*#__PURE__*/React.createElement(Autocomplete, {
124
+ id: "autocompleteId"
125
+ }, /*#__PURE__*/React.createElement(Autocomplete.Input, null), /*#__PURE__*/React.createElement(Autocomplete.Menu, {
126
+ "aria-labelledby": "labelId",
127
+ items: [],
128
+ selectedItemIds: [],
129
+ emptyStateText: "No results"
130
+ }))))).toMatchSnapshot();
131
+ });
132
+ it('renders a loading state', () => {
133
+ expect(render( /*#__PURE__*/React.createElement(SSRProvider, null, /*#__PURE__*/React.createElement(Autocomplete, {
134
+ id: "autocompleteId"
135
+ }, /*#__PURE__*/React.createElement(Autocomplete.Input, null), /*#__PURE__*/React.createElement(Autocomplete.Menu, {
136
+ "aria-labelledby": "labelId",
137
+ loading: true,
138
+ items: [],
139
+ selectedItemIds: []
140
+ }))))).toMatchSnapshot();
141
+ });
142
+ it('renders with a custom text input component', () => {
143
+ expect(render( /*#__PURE__*/React.createElement(SSRProvider, null, /*#__PURE__*/React.createElement(Autocomplete, {
144
+ id: "autocompleteId"
145
+ }, /*#__PURE__*/React.createElement(Autocomplete.Input, {
146
+ as: () => /*#__PURE__*/React.createElement("input", {
147
+ type: "text",
148
+ id: "customInput"
149
+ })
150
+ }), /*#__PURE__*/React.createElement(Autocomplete.Menu, {
151
+ "aria-labelledby": "labelId",
152
+ items: mockItems,
153
+ selectedItemIds: []
154
+ }))))).toMatchSnapshot();
155
+ });
156
+ it('renders with an input value', () => {
157
+ expect(render( /*#__PURE__*/React.createElement(SSRProvider, null, /*#__PURE__*/React.createElement(Autocomplete, {
158
+ id: "autocompleteId"
159
+ }, /*#__PURE__*/React.createElement(Autocomplete.Input, {
160
+ value: "test"
161
+ }), /*#__PURE__*/React.createElement(Autocomplete.Menu, {
162
+ "aria-labelledby": "labelId",
163
+ items: mockItems,
164
+ selectedItemIds: []
165
+ }))))).toMatchSnapshot();
166
+ });
167
+ });
168
+ describe('Autocomplete.Input', () => {
169
+ it('calls onChange', () => {
170
+ const onChangeMock = jest.fn();
171
+ const {
172
+ container
173
+ } = HTMLRender( /*#__PURE__*/React.createElement(LabelledAutocomplete, {
174
+ inputProps: {
175
+ onChange: onChangeMock
176
+ },
177
+ menuProps: {
178
+ items: mockItems,
179
+ selectedItemIds: []
180
+ }
181
+ }));
182
+ const inputNode = container.querySelector('#autocompleteInput');
183
+ expect(onChangeMock).not.toHaveBeenCalled();
184
+ inputNode && userEvent.type(inputNode, 'z');
185
+ expect(onChangeMock).toHaveBeenCalled();
186
+ });
187
+ it('calls onFocus', () => {
188
+ const onFocusMock = jest.fn();
189
+ const {
190
+ container
191
+ } = HTMLRender( /*#__PURE__*/React.createElement(LabelledAutocomplete, {
192
+ inputProps: {
193
+ onFocus: onFocusMock
194
+ },
195
+ menuProps: {
196
+ items: mockItems,
197
+ selectedItemIds: []
198
+ }
199
+ }));
200
+ const inputNode = container.querySelector('#autocompleteInput');
201
+ expect(onFocusMock).not.toHaveBeenCalled();
202
+ inputNode && fireEvent.focus(inputNode);
203
+ expect(onFocusMock).toHaveBeenCalled();
204
+ });
205
+ it('calls onKeyDown', () => {
206
+ const onKeyDownMock = jest.fn();
207
+ const {
208
+ getByLabelText
209
+ } = HTMLRender( /*#__PURE__*/React.createElement(LabelledAutocomplete, {
210
+ inputProps: {
211
+ onKeyDown: onKeyDownMock
212
+ },
213
+ menuProps: {
214
+ items: [],
215
+ selectedItemIds: []
216
+ }
217
+ }));
218
+ const inputNode = getByLabelText(AUTOCOMPLETE_LABEL);
219
+ expect(onKeyDownMock).not.toHaveBeenCalled();
220
+ fireEvent.keyDown(inputNode, {
221
+ key: 'Shift'
222
+ });
223
+ expect(onKeyDownMock).toHaveBeenCalled();
224
+ });
225
+ it('calls onKeyUp', () => {
226
+ const onKeyUpMock = jest.fn();
227
+ const {
228
+ getByLabelText
229
+ } = HTMLRender( /*#__PURE__*/React.createElement(LabelledAutocomplete, {
230
+ inputProps: {
231
+ onKeyUp: onKeyUpMock
232
+ },
233
+ menuProps: {
234
+ items: [],
235
+ selectedItemIds: []
236
+ }
237
+ }));
238
+ const inputNode = getByLabelText(AUTOCOMPLETE_LABEL);
239
+ expect(onKeyUpMock).not.toHaveBeenCalled();
240
+ fireEvent.keyUp(inputNode, {
241
+ key: 'Shift'
242
+ });
243
+ expect(onKeyUpMock).toHaveBeenCalled();
244
+ });
245
+ it('calls onKeyPress', () => {
246
+ const onKeyPressMock = jest.fn();
247
+ const {
248
+ getByLabelText
249
+ } = HTMLRender( /*#__PURE__*/React.createElement(LabelledAutocomplete, {
250
+ inputProps: {
251
+ onKeyPress: onKeyPressMock
252
+ },
253
+ menuProps: {
254
+ items: [],
255
+ selectedItemIds: []
256
+ }
257
+ }));
258
+ const inputNode = getByLabelText(AUTOCOMPLETE_LABEL);
259
+ expect(onKeyPressMock).not.toHaveBeenCalled();
260
+ userEvent.type(inputNode, '{enter}');
261
+ expect(onKeyPressMock).toHaveBeenCalled();
262
+ });
263
+ it('opens the menu when the input is focused', () => {
264
+ const {
265
+ getByLabelText
266
+ } = HTMLRender( /*#__PURE__*/React.createElement(LabelledAutocomplete, {
267
+ menuProps: {
268
+ items: [],
269
+ selectedItemIds: []
270
+ }
271
+ }));
272
+ const inputNode = getByLabelText(AUTOCOMPLETE_LABEL);
273
+ expect(inputNode.getAttribute('aria-expanded')).not.toBe('true');
274
+ fireEvent.focus(inputNode);
275
+ expect(inputNode.getAttribute('aria-expanded')).toBe('true');
276
+ });
277
+ it('closes the menu when the input is blurred', () => {
278
+ const {
279
+ getByLabelText
280
+ } = HTMLRender( /*#__PURE__*/React.createElement(LabelledAutocomplete, {
281
+ menuProps: {
282
+ items: [],
283
+ selectedItemIds: []
284
+ }
285
+ }));
286
+ const inputNode = getByLabelText(AUTOCOMPLETE_LABEL);
287
+ expect(inputNode.getAttribute('aria-expanded')).not.toBe('true');
288
+ fireEvent.focus(inputNode);
289
+ expect(inputNode.getAttribute('aria-expanded')).toBe('true'); // eslint-disable-next-line github/no-blur
290
+
291
+ fireEvent.blur(inputNode); // wait a tick for blur to finish
292
+
293
+ setTimeout(() => {
294
+ expect(inputNode.getAttribute('aria-expanded')).not.toBe('true');
295
+ }, 0);
296
+ });
297
+ it('sets the input value to the suggested item text and highlights the untyped part of the word', () => {
298
+ const {
299
+ container,
300
+ getByDisplayValue
301
+ } = HTMLRender( /*#__PURE__*/React.createElement(LabelledAutocomplete, {
302
+ menuProps: {
303
+ items: mockItems,
304
+ selectedItemIds: []
305
+ }
306
+ }));
307
+ const inputNode = container.querySelector('#autocompleteInput');
308
+ inputNode && userEvent.type(inputNode, 'ze');
309
+ expect(getByDisplayValue('zero')).toBeDefined();
310
+ });
311
+ it('does not show or highlight suggestion text after the user hits Backspace until they hit another key', () => {
312
+ const {
313
+ container,
314
+ getByDisplayValue
315
+ } = HTMLRender( /*#__PURE__*/React.createElement(LabelledAutocomplete, {
316
+ menuProps: {
317
+ items: mockItems,
318
+ selectedItemIds: []
319
+ }
320
+ }));
321
+ const inputNode = container.querySelector('#autocompleteInput');
322
+ expect(inputNode.selectionStart).toBe(0);
323
+ inputNode && userEvent.type(inputNode, 'ze');
324
+ expect(getByDisplayValue('zero')).toBeDefined();
325
+ expect(inputNode.selectionStart).toBe(2);
326
+ expect(inputNode.selectionEnd).toBe(4);
327
+ inputNode && userEvent.type(inputNode, '{backspace}');
328
+ expect(inputNode.selectionStart).toBe(2);
329
+ expect(getByDisplayValue('ze')).toBeDefined();
330
+ inputNode && userEvent.type(inputNode, 'r');
331
+ expect(inputNode.selectionStart).toBe(3);
332
+ expect(inputNode.selectionEnd).toBe(4);
333
+ expect(getByDisplayValue('zero')).toBeDefined();
334
+ });
335
+ it('clears the input value when when the user hits Escape', () => {
336
+ const {
337
+ container
338
+ } = HTMLRender( /*#__PURE__*/React.createElement(LabelledAutocomplete, {
339
+ menuProps: {
340
+ items: mockItems,
341
+ selectedItemIds: []
342
+ }
343
+ }));
344
+ const inputNode = container.querySelector('#autocompleteInput');
345
+ expect(inputNode === null || inputNode === void 0 ? void 0 : inputNode.getAttribute('aria-expanded')).not.toBe('true');
346
+ inputNode && userEvent.type(inputNode, 'ze');
347
+ expect(inputNode === null || inputNode === void 0 ? void 0 : inputNode.getAttribute('aria-expanded')).toBe('true');
348
+ inputNode && userEvent.type(inputNode, '{esc}');
349
+ expect(inputNode === null || inputNode === void 0 ? void 0 : inputNode.getAttribute('aria-expanded')).not.toBe('true');
350
+ });
351
+ });
352
+ describe('Autocomplete.Menu', () => {
353
+ it('calls a custom filter function', () => {
354
+ const filterFnMock = jest.fn();
355
+ const {
356
+ container
357
+ } = HTMLRender( /*#__PURE__*/React.createElement(LabelledAutocomplete, {
358
+ menuProps: {
359
+ items: mockItems,
360
+ selectedItemIds: [],
361
+ filterFn: filterFnMock
362
+ }
363
+ }));
364
+ const inputNode = container.querySelector('#autocompleteInput');
365
+ inputNode && userEvent.type(inputNode, 'ze');
366
+ expect(filterFnMock).toHaveBeenCalled();
367
+ });
368
+ it('calls a custom sort function when the menu closes', () => {
369
+ const sortOnCloseFnMock = jest.fn();
370
+ const {
371
+ container
372
+ } = HTMLRender( /*#__PURE__*/React.createElement(LabelledAutocomplete, {
373
+ menuProps: {
374
+ items: mockItems,
375
+ selectedItemIds: [],
376
+ sortOnCloseFn: sortOnCloseFnMock
377
+ }
378
+ }));
379
+ const inputNode = container.querySelector('#autocompleteInput'); // `sortOnCloseFnMock` will be called in a `.sort()` on render to check if the
380
+ // current sort order matches the result of `sortOnCloseFnMock`
381
+
382
+ expect(sortOnCloseFnMock).toHaveBeenCalledTimes(mockItems.length - 1);
383
+
384
+ if (inputNode) {
385
+ userEvent.type(inputNode, 'ze'); // eslint-disable-next-line github/no-blur
386
+
387
+ fireEvent.blur(inputNode);
388
+ } // wait a tick for blur to finish
389
+
390
+
391
+ setTimeout(() => {
392
+ expect(sortOnCloseFnMock).toHaveBeenCalledTimes(mockItems.length);
393
+ }, 0);
394
+ });
395
+ it("calls onOpenChange with the menu's open state", () => {
396
+ const onOpenChangeMock = jest.fn();
397
+ const {
398
+ container
399
+ } = HTMLRender( /*#__PURE__*/React.createElement(LabelledAutocomplete, {
400
+ menuProps: {
401
+ items: mockItems,
402
+ selectedItemIds: [],
403
+ onOpenChange: onOpenChangeMock
404
+ }
405
+ }));
406
+ const inputNode = container.querySelector('#autocompleteInput');
407
+ inputNode && userEvent.type(inputNode, 'ze');
408
+ expect(onOpenChangeMock).toHaveBeenCalled();
409
+ });
410
+ it('calls onSelectedChange with the data for the selected items', () => {
411
+ const onSelectedChangeMock = jest.fn();
412
+ const {
413
+ container
414
+ } = HTMLRender( /*#__PURE__*/React.createElement(LabelledAutocomplete, {
415
+ menuProps: {
416
+ items: mockItems,
417
+ selectedItemIds: [],
418
+ onSelectedChange: onSelectedChangeMock
419
+ }
420
+ }));
421
+ const inputNode = container.querySelector('#autocompleteInput');
422
+ expect(onSelectedChangeMock).not.toHaveBeenCalled();
423
+
424
+ if (inputNode) {
425
+ fireEvent.focus(inputNode);
426
+ userEvent.type(inputNode, '{enter}');
427
+ } // wait a tick for the keyboard event to be dispatched to the menu item
428
+
429
+
430
+ setTimeout(() => {
431
+ expect(onSelectedChangeMock).toHaveBeenCalledWith([mockItems[0]]);
432
+ }, 0);
433
+ });
434
+ it('does not close the menu when clicking an item in the menu if selectionVariant=multiple', () => {
435
+ const {
436
+ getByText,
437
+ container
438
+ } = HTMLRender( /*#__PURE__*/React.createElement(LabelledAutocomplete, {
439
+ menuProps: {
440
+ items: mockItems,
441
+ selectedItemIds: [],
442
+ selectionVariant: 'multiple'
443
+ }
444
+ }));
445
+ const inputNode = container.querySelector('#autocompleteInput');
446
+ const itemToClickNode = getByText(mockItems[1].text);
447
+ expect(inputNode === null || inputNode === void 0 ? void 0 : inputNode.getAttribute('aria-expanded')).not.toBe('true');
448
+ inputNode && fireEvent.focus(inputNode);
449
+ expect(inputNode === null || inputNode === void 0 ? void 0 : inputNode.getAttribute('aria-expanded')).toBe('true');
450
+ fireEvent.click(itemToClickNode);
451
+ inputNode && userEvent.type(inputNode, '{enter}');
452
+ expect(inputNode === null || inputNode === void 0 ? void 0 : inputNode.getAttribute('aria-expanded')).toBe('true');
453
+ });
454
+ it('closes the menu when clicking an item in the menu if selectionVariant=single', () => {
455
+ const {
456
+ getByText,
457
+ container
458
+ } = HTMLRender( /*#__PURE__*/React.createElement(LabelledAutocomplete, {
459
+ menuProps: {
460
+ items: mockItems,
461
+ selectedItemIds: [],
462
+ selectionVariant: 'single'
463
+ }
464
+ }));
465
+ const inputNode = container.querySelector('#autocompleteInput');
466
+ const itemToClickNode = getByText(mockItems[1].text);
467
+ expect(inputNode === null || inputNode === void 0 ? void 0 : inputNode.getAttribute('aria-expanded')).not.toBe('true');
468
+ inputNode && fireEvent.focus(inputNode);
469
+ expect(inputNode === null || inputNode === void 0 ? void 0 : inputNode.getAttribute('aria-expanded')).toBe('true');
470
+ fireEvent.click(itemToClickNode);
471
+ expect(inputNode === null || inputNode === void 0 ? void 0 : inputNode.getAttribute('aria-expanded')).not.toBe('true');
472
+ });
473
+ it('calls handleAddItem with new item data when passing addNewItem', () => {
474
+ const handleAddItemMock = jest.fn();
475
+ const {
476
+ getByText
477
+ } = HTMLRender( /*#__PURE__*/React.createElement(LabelledAutocomplete, {
478
+ menuProps: {
479
+ items: mockItems,
480
+ selectedItemIds: [],
481
+ selectionVariant: 'multiple',
482
+ addNewItem: {
483
+ text: 'Add new item',
484
+ handleAddItem: handleAddItemMock
485
+ }
486
+ }
487
+ }));
488
+ const addNewItemNode = getByText('Add new item');
489
+ expect(handleAddItemMock).not.toHaveBeenCalled();
490
+ fireEvent.click(addNewItemNode);
491
+ expect(handleAddItemMock).toHaveBeenCalled();
492
+ });
493
+ });
494
+ });