@dhis2-ui/select 10.16.1 → 10.16.3-alpha.1

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 (278) hide show
  1. package/build/cjs/select/select.js +5 -2
  2. package/build/cjs/simple-single-select/use-handle-key-press/use-handle-key-press-on-filter-input.js +2 -2
  3. package/build/es/select/select.js +5 -2
  4. package/build/es/simple-single-select/use-handle-key-press/use-handle-key-press-on-filter-input.js +2 -2
  5. package/package.json +17 -16
  6. package/src/index.js +9 -0
  7. package/src/locales/ar/translations.json +7 -0
  8. package/src/locales/ar_IQ/translations.json +7 -0
  9. package/src/locales/ckb/translations.json +7 -0
  10. package/src/locales/cs/translations.json +7 -0
  11. package/src/locales/da/translations.json +7 -0
  12. package/src/locales/en/translations.json +11 -0
  13. package/src/locales/en_US/translations.json +9 -0
  14. package/src/locales/es/translations.json +7 -0
  15. package/src/locales/es_419/translations.json +7 -0
  16. package/src/locales/fr/translations.json +7 -0
  17. package/src/locales/hi_IN/translations.json +7 -0
  18. package/src/locales/id/translations.json +7 -0
  19. package/src/locales/index.js +80 -0
  20. package/src/locales/km/translations.json +7 -0
  21. package/src/locales/lo/translations.json +7 -0
  22. package/src/locales/my/translations.json +7 -0
  23. package/src/locales/nb/translations.json +7 -0
  24. package/src/locales/nl/translations.json +7 -0
  25. package/src/locales/prs/translations.json +7 -0
  26. package/src/locales/ps/translations.json +7 -0
  27. package/src/locales/pt/translations.json +7 -0
  28. package/src/locales/pt_BR/translations.json +7 -0
  29. package/src/locales/ru/translations.json +7 -0
  30. package/src/locales/sv/translations.json +7 -0
  31. package/src/locales/tet/translations.json +7 -0
  32. package/src/locales/tg/translations.json +7 -0
  33. package/src/locales/uk/translations.json +7 -0
  34. package/src/locales/ur/translations.json +7 -0
  35. package/src/locales/uz_Latn/translations.json +7 -0
  36. package/src/locales/uz_UZ_Cyrl/translations.json +7 -0
  37. package/src/locales/uz_UZ_Latn/translations.json +7 -0
  38. package/src/locales/vi/translations.json +7 -0
  39. package/src/locales/zh/translations.json +9 -0
  40. package/src/locales/zh_CN/translations.json +7 -0
  41. package/src/multi-select/__tests__/multi-select.test.js +23 -0
  42. package/src/multi-select/__tests__/selection-list.test.js +102 -0
  43. package/src/multi-select/features/accepts_blur_cb/index.js +14 -0
  44. package/src/multi-select/features/accepts_blur_cb.feature +8 -0
  45. package/src/multi-select/features/accepts_focus_cb/index.js +14 -0
  46. package/src/multi-select/features/accepts_focus_cb.feature +6 -0
  47. package/src/multi-select/features/accepts_initial_focus/index.js +5 -0
  48. package/src/multi-select/features/accepts_initial_focus.feature +5 -0
  49. package/src/multi-select/features/accepts_loading/index.js +22 -0
  50. package/src/multi-select/features/accepts_loading.feature +14 -0
  51. package/src/multi-select/features/accepts_max_height/index.js +31 -0
  52. package/src/multi-select/features/accepts_max_height.feature +11 -0
  53. package/src/multi-select/features/accepts_placeholder/index.js +25 -0
  54. package/src/multi-select/features/accepts_placeholder.feature +10 -0
  55. package/src/multi-select/features/accepts_prefix/index.js +19 -0
  56. package/src/multi-select/features/accepts_prefix.feature +10 -0
  57. package/src/multi-select/features/allows_invalid_options/index.js +27 -0
  58. package/src/multi-select/features/allows_invalid_options.feature +12 -0
  59. package/src/multi-select/features/allows_selecting/index.js +81 -0
  60. package/src/multi-select/features/allows_selecting.feature +44 -0
  61. package/src/multi-select/features/can_be_cleared/index.js +22 -0
  62. package/src/multi-select/features/can_be_cleared.feature +6 -0
  63. package/src/multi-select/features/can_be_disabled/index.js +36 -0
  64. package/src/multi-select/features/can_be_disabled.feature +32 -0
  65. package/src/multi-select/features/can_be_empty/index.js +28 -0
  66. package/src/multi-select/features/can_be_empty.feature +16 -0
  67. package/src/multi-select/features/can_be_filtered/index.js +23 -0
  68. package/src/multi-select/features/can_be_filtered.feature +14 -0
  69. package/src/multi-select/features/can_be_opened_and_closed/index.js +32 -0
  70. package/src/multi-select/features/can_be_opened_and_closed.feature +41 -0
  71. package/src/multi-select/features/common/index.js +49 -0
  72. package/src/multi-select/features/duplicate_option_values/index.js +21 -0
  73. package/src/multi-select/features/duplicate_option_values.feature +7 -0
  74. package/src/multi-select/features/menu_width_matches_input/index.js +53 -0
  75. package/src/multi-select/features/menu_width_matches_input.feature +11 -0
  76. package/src/multi-select/features/position/index.js +160 -0
  77. package/src/multi-select/features/position.feature +46 -0
  78. package/src/multi-select/features/shows_selections/index.js +18 -0
  79. package/src/multi-select/features/shows_selections.feature +9 -0
  80. package/src/multi-select/filterable-menu.js +43 -0
  81. package/src/multi-select/index.js +1 -0
  82. package/src/multi-select/input.js +129 -0
  83. package/src/multi-select/menu.js +91 -0
  84. package/src/multi-select/multi-select.e2e.stories.js +407 -0
  85. package/src/multi-select/multi-select.js +148 -0
  86. package/src/multi-select/multi-select.prod.stories.js +408 -0
  87. package/src/multi-select/selection-list.js +79 -0
  88. package/src/multi-select-field/__tests__/multi-select-field.test.js +29 -0
  89. package/src/multi-select-field/features/accepts_help_text/index.js +9 -0
  90. package/src/multi-select-field/features/accepts_help_text.feature +5 -0
  91. package/src/multi-select-field/features/accepts_label/index.js +9 -0
  92. package/src/multi-select-field/features/accepts_label.feature +5 -0
  93. package/src/multi-select-field/features/accepts_validation_text/index.js +9 -0
  94. package/src/multi-select-field/features/accepts_validation_text.feature +5 -0
  95. package/src/multi-select-field/features/can_be_required/index.js +11 -0
  96. package/src/multi-select-field/features/can_be_required.feature +5 -0
  97. package/src/multi-select-field/features/has_default_clear_text/index.js +16 -0
  98. package/src/multi-select-field/features/has_default_clear_text.feature +6 -0
  99. package/src/multi-select-field/features/has_default_empty_text/index.js +13 -0
  100. package/src/multi-select-field/features/has_default_empty_text.feature +6 -0
  101. package/src/multi-select-field/features/has_default_filter_nomatch_text/index.js +17 -0
  102. package/src/multi-select-field/features/has_default_filter_nomatch_text.feature +7 -0
  103. package/src/multi-select-field/features/has_default_filter_placeholder/index.js +15 -0
  104. package/src/multi-select-field/features/has_default_filter_placeholder.feature +6 -0
  105. package/src/multi-select-field/features/has_default_loading_text/index.js +13 -0
  106. package/src/multi-select-field/features/has_default_loading_text.feature +6 -0
  107. package/src/multi-select-field/index.js +1 -0
  108. package/src/multi-select-field/multi-select-field.e2e.stories.js +45 -0
  109. package/src/multi-select-field/multi-select-field.js +178 -0
  110. package/src/multi-select-field/multi-select-field.prod.stories.js +137 -0
  111. package/src/multi-select-option/index.js +1 -0
  112. package/src/multi-select-option/multi-select-option.js +70 -0
  113. package/src/select/__tests__/select.test.js +31 -0
  114. package/src/select/debounce/debounce.js +30 -0
  115. package/src/select/debounce/debounce.spec.js +35 -0
  116. package/src/select/empty.js +28 -0
  117. package/src/select/filter-input.js +42 -0
  118. package/src/select/filterable-menu.js +106 -0
  119. package/src/select/index.js +14 -0
  120. package/src/select/input-clear-button.js +66 -0
  121. package/src/select/input-placeholder.js +30 -0
  122. package/src/select/input-prefix.js +32 -0
  123. package/src/select/input-wrapper.js +114 -0
  124. package/src/select/loading.js +33 -0
  125. package/src/select/menu-wrapper.js +52 -0
  126. package/src/select/no-match.js +28 -0
  127. package/src/select/option-helpers.js +34 -0
  128. package/src/select/select.js +256 -0
  129. package/src/simple-single-select/__stories__/DefaultPosition.js +37 -0
  130. package/src/simple-single-select/__stories__/Dense.js +17 -0
  131. package/src/simple-single-select/__stories__/Empty.js +15 -0
  132. package/src/simple-single-select/__stories__/EmptyWithEmptyNode.js +27 -0
  133. package/src/simple-single-select/__stories__/EmptyWithEmptyText.js +16 -0
  134. package/src/simple-single-select/__stories__/FlippedPosition.js +36 -0
  135. package/src/simple-single-select/__stories__/InfiniteLoading.js +65 -0
  136. package/src/simple-single-select/__stories__/Loading.js +15 -0
  137. package/src/simple-single-select/__stories__/ShiftedIntoView.js +35 -0
  138. package/src/simple-single-select/__stories__/WithClearButton.js +26 -0
  139. package/src/simple-single-select/__stories__/WithCustomLowMaxHeight.js +17 -0
  140. package/src/simple-single-select/__stories__/WithCustomOptions.js +78 -0
  141. package/src/simple-single-select/__stories__/WithDisabledOption.js +25 -0
  142. package/src/simple-single-select/__stories__/WithFilterField.js +30 -0
  143. package/src/simple-single-select/__stories__/WithInitialFocus.js +29 -0
  144. package/src/simple-single-select/__stories__/WithManyOptions.js +19 -0
  145. package/src/simple-single-select/__stories__/WithMultipleSelectsAndScrollableParent.js +65 -0
  146. package/src/simple-single-select/__stories__/WithNoMatchText.js +25 -0
  147. package/src/simple-single-select/__stories__/WithOnBlur.js +22 -0
  148. package/src/simple-single-select/__stories__/WithOnFocus.js +22 -0
  149. package/src/simple-single-select/__stories__/WithOptionsAndDisabled.js +17 -0
  150. package/src/simple-single-select/__stories__/WithOptionsAndLoading.js +23 -0
  151. package/src/simple-single-select/__stories__/WithOptionsAndLoadingText.js +24 -0
  152. package/src/simple-single-select/__stories__/WithPlaceholder.js +18 -0
  153. package/src/simple-single-select/__stories__/WithPlaceholderAndSelection.js +20 -0
  154. package/src/simple-single-select/__stories__/WithPrefix.js +17 -0
  155. package/src/simple-single-select/__stories__/WithPrefixAndSelection.js +20 -0
  156. package/src/simple-single-select/__stories__/WithRTL.js +29 -0
  157. package/src/simple-single-select/__stories__/WithScrollingParent.js +51 -0
  158. package/src/simple-single-select/__stories__/WithSelection.js +19 -0
  159. package/src/simple-single-select/__stories__/WithSelectionAndDisabled.js +20 -0
  160. package/src/simple-single-select/__stories__/WithServerSideFilteringDemo.js +245 -0
  161. package/src/simple-single-select/__stories__/WithSimpleFilteringDemo.js +50 -0
  162. package/src/simple-single-select/__stories__/WithoutOptionsAndLoading.js +16 -0
  163. package/src/simple-single-select/__stories__/WithoutSelection.js +17 -0
  164. package/src/simple-single-select/__stories__/options.js +96 -0
  165. package/src/simple-single-select/__stories__/simple-single-select-server-side-filtering/CustomDataProvider.js +70 -0
  166. package/src/simple-single-select/__stories__/simple-single-select-server-side-filtering/dataElements.js +4271 -0
  167. package/src/simple-single-select/__stories__/simple-single-select-server-side-filtering/index.js +1 -0
  168. package/src/simple-single-select/features/keyboard-interactions.test.e2e.js +581 -0
  169. package/src/simple-single-select/features/menu-positioning.test.e2e.js +187 -0
  170. package/src/simple-single-select/index.js +2 -0
  171. package/src/simple-single-select/is-option-hidden.js +21 -0
  172. package/src/simple-single-select/menu/empty.js +27 -0
  173. package/src/simple-single-select/menu/filter.js +61 -0
  174. package/src/simple-single-select/menu/index.js +1 -0
  175. package/src/simple-single-select/menu/loading.js +38 -0
  176. package/src/simple-single-select/menu/menu.js +171 -0
  177. package/src/simple-single-select/menu/no-match.js +28 -0
  178. package/src/simple-single-select/menu/option.js +161 -0
  179. package/src/simple-single-select/menu/options-list.js +107 -0
  180. package/src/simple-single-select/selected-value/clear-button.js +71 -0
  181. package/src/simple-single-select/selected-value/container.js +146 -0
  182. package/src/simple-single-select/selected-value/index.js +1 -0
  183. package/src/simple-single-select/selected-value/placeholder.js +24 -0
  184. package/src/simple-single-select/selected-value/prefix.js +26 -0
  185. package/src/simple-single-select/selected-value/selected-value.js +174 -0
  186. package/src/simple-single-select/shared-prop-types.js +8 -0
  187. package/src/simple-single-select/simple-single-select.e2e.stories.js +216 -0
  188. package/src/simple-single-select/simple-single-select.js +389 -0
  189. package/src/simple-single-select/simple-single-select.prod.stories.js +41 -0
  190. package/src/simple-single-select/simple-single-select.test.js +1136 -0
  191. package/src/simple-single-select/use-handle-key-press/index.js +2 -0
  192. package/src/simple-single-select/use-handle-key-press/use-focus-option.js +24 -0
  193. package/src/simple-single-select/use-handle-key-press/use-handle-key-press-on-combobox.js +259 -0
  194. package/src/simple-single-select/use-handle-key-press/use-handle-key-press-on-filter-input.js +147 -0
  195. package/src/simple-single-select/use-handle-key-press/use-handle-typing.js +79 -0
  196. package/src/simple-single-select/use-handle-key-press/use-highlight-first-option-on-previous-page.js +62 -0
  197. package/src/simple-single-select/use-handle-key-press/use-highlight-first-visible-option.js +38 -0
  198. package/src/simple-single-select/use-handle-key-press/use-highlight-last-option-on-next-page.js +60 -0
  199. package/src/simple-single-select/use-handle-key-press/use-highlight-last-visible-option.js +52 -0
  200. package/src/simple-single-select/use-handle-key-press/use-page-down.js +50 -0
  201. package/src/simple-single-select/use-handle-key-press/use-page-up.js +50 -0
  202. package/src/simple-single-select/use-handle-key-press/utils.js +23 -0
  203. package/src/simple-single-select-field/index.js +1 -0
  204. package/src/simple-single-select-field/simple-single-select-field.js +198 -0
  205. package/src/simple-single-select-field/simple-single-select-field.prod.stories.js +188 -0
  206. package/src/simple-single-select-field/simple-single-select-field.test.js +144 -0
  207. package/src/single-select/__tests__/selection.test.js +102 -0
  208. package/src/single-select/__tests__/single-select.test.js +23 -0
  209. package/src/single-select/features/accepts_blur_cb/index.js +14 -0
  210. package/src/single-select/features/accepts_blur_cb.feature +8 -0
  211. package/src/single-select/features/accepts_focus_cb/index.js +14 -0
  212. package/src/single-select/features/accepts_focus_cb.feature +6 -0
  213. package/src/single-select/features/accepts_initial_focus/index.js +5 -0
  214. package/src/single-select/features/accepts_initial_focus.feature +5 -0
  215. package/src/single-select/features/accepts_loading/index.js +22 -0
  216. package/src/single-select/features/accepts_loading.feature +14 -0
  217. package/src/single-select/features/accepts_max_height/index.js +31 -0
  218. package/src/single-select/features/accepts_max_height.feature +11 -0
  219. package/src/single-select/features/accepts_placeholder/index.js +25 -0
  220. package/src/single-select/features/accepts_placeholder.feature +10 -0
  221. package/src/single-select/features/accepts_prefix/index.js +19 -0
  222. package/src/single-select/features/accepts_prefix.feature +10 -0
  223. package/src/single-select/features/allows_invalid_options/index.js +28 -0
  224. package/src/single-select/features/allows_invalid_options.feature +12 -0
  225. package/src/single-select/features/allows_selecting/index.js +38 -0
  226. package/src/single-select/features/allows_selecting.feature +21 -0
  227. package/src/single-select/features/can_be_cleared/index.js +22 -0
  228. package/src/single-select/features/can_be_cleared.feature +6 -0
  229. package/src/single-select/features/can_be_disabled/index.js +39 -0
  230. package/src/single-select/features/can_be_disabled.feature +32 -0
  231. package/src/single-select/features/can_be_empty/index.js +28 -0
  232. package/src/single-select/features/can_be_empty.feature +16 -0
  233. package/src/single-select/features/can_be_filtered/index.js +23 -0
  234. package/src/single-select/features/can_be_filtered.feature +14 -0
  235. package/src/single-select/features/can_be_opened_and_closed/index.js +32 -0
  236. package/src/single-select/features/can_be_opened_and_closed.feature +41 -0
  237. package/src/single-select/features/common/index.js +44 -0
  238. package/src/single-select/features/duplicate_option_values/index.js +26 -0
  239. package/src/single-select/features/duplicate_option_values.feature +7 -0
  240. package/src/single-select/features/menu_width_matches_input/index.js +53 -0
  241. package/src/single-select/features/menu_width_matches_input.feature +11 -0
  242. package/src/single-select/features/position/index.js +111 -0
  243. package/src/single-select/features/position.feature +26 -0
  244. package/src/single-select/features/shows_selections/index.js +9 -0
  245. package/src/single-select/features/shows_selections.feature +5 -0
  246. package/src/single-select/filterable-menu.js +41 -0
  247. package/src/single-select/index.js +1 -0
  248. package/src/single-select/input.js +101 -0
  249. package/src/single-select/menu.js +75 -0
  250. package/src/single-select/selection.js +60 -0
  251. package/src/single-select/single-select.e2e.stories.js +376 -0
  252. package/src/single-select/single-select.js +146 -0
  253. package/src/single-select/single-select.prod.stories.js +358 -0
  254. package/src/single-select-field/__tests__/single-select-field.test.js +29 -0
  255. package/src/single-select-field/features/accepts_help_text/index.js +9 -0
  256. package/src/single-select-field/features/accepts_help_text.feature +5 -0
  257. package/src/single-select-field/features/accepts_label/index.js +9 -0
  258. package/src/single-select-field/features/accepts_label.feature +5 -0
  259. package/src/single-select-field/features/accepts_validation_text/index.js +9 -0
  260. package/src/single-select-field/features/accepts_validation_text.feature +5 -0
  261. package/src/single-select-field/features/can_be_required/index.js +11 -0
  262. package/src/single-select-field/features/can_be_required.feature +5 -0
  263. package/src/single-select-field/features/has_default_clear_text/index.js +16 -0
  264. package/src/single-select-field/features/has_default_clear_text.feature +6 -0
  265. package/src/single-select-field/features/has_default_empty_text/index.js +13 -0
  266. package/src/single-select-field/features/has_default_empty_text.feature +6 -0
  267. package/src/single-select-field/features/has_default_filter_nomatch_text/index.js +17 -0
  268. package/src/single-select-field/features/has_default_filter_nomatch_text.feature +7 -0
  269. package/src/single-select-field/features/has_default_filter_placeholder/index.js +15 -0
  270. package/src/single-select-field/features/has_default_filter_placeholder.feature +6 -0
  271. package/src/single-select-field/features/has_default_loading_text/index.js +13 -0
  272. package/src/single-select-field/features/has_default_loading_text.feature +6 -0
  273. package/src/single-select-field/index.js +1 -0
  274. package/src/single-select-field/single-select-field.e2e.stories.js +45 -0
  275. package/src/single-select-field/single-select-field.js +178 -0
  276. package/src/single-select-field/single-select-field.prod.stories.js +146 -0
  277. package/src/single-select-option/index.js +1 -0
  278. package/src/single-select-option/single-select-option.js +84 -0
@@ -0,0 +1,1136 @@
1
+ import '@testing-library/jest-dom'
2
+ import { render, fireEvent, screen } from '@testing-library/react'
3
+ import React, { useState } from 'react'
4
+ import { SimpleSingleSelect } from './simple-single-select.js'
5
+
6
+ describe('<SimpleSingleSelect />', () => {
7
+ beforeAll(() => {
8
+ const consoleError = console.error
9
+ jest.spyOn(console, 'error').mockImplementation((...args) => {
10
+ const [message, dynamicContent] = args
11
+
12
+ if (
13
+ message.startsWith(
14
+ 'Warning: An update to %s inside a test was not wrapped in act(...)'
15
+ ) &&
16
+ dynamicContent === 'Popper'
17
+ ) {
18
+ return
19
+ }
20
+
21
+ consoleError(...args)
22
+ })
23
+ })
24
+
25
+ afterAll(() => {
26
+ console.error.mockRestore()
27
+ })
28
+
29
+ it('should accept an onBlur handler', () => {
30
+ const onBlur = jest.fn()
31
+
32
+ render(
33
+ <SimpleSingleSelect
34
+ onBlur={onBlur}
35
+ name="simple"
36
+ selected={{ value: 'foo', label: 'Foo' }}
37
+ onChange={() => null}
38
+ options={[{ value: 'foo', label: 'Foo' }]}
39
+ />
40
+ )
41
+
42
+ fireEvent.blur(screen.getByRole('combobox'))
43
+
44
+ // first argument passed to onBlur is a react event
45
+ //
46
+ // Reg. eslint: Eslint complains about using hasOwnProperty,
47
+ // but accessing `.nativeEvent` directly causes react to log an error
48
+ // eslint-disable-next-line no-prototype-builtins
49
+ expect(onBlur.mock.calls[0][0].hasOwnProperty('nativeEvent')).toBe(true)
50
+ expect(onBlur).toHaveBeenCalledTimes(1)
51
+ })
52
+
53
+ it('should accept an onFocus handler', () => {
54
+ const onFocus = jest.fn()
55
+
56
+ render(
57
+ <SimpleSingleSelect
58
+ onFocus={onFocus}
59
+ name="simple"
60
+ selected={{ value: 'foo', label: 'Foo' }}
61
+ onChange={() => null}
62
+ options={[{ value: 'foo', label: 'Foo' }]}
63
+ />
64
+ )
65
+
66
+ fireEvent.focus(screen.getByRole('combobox'))
67
+
68
+ // first argument passed to onFocus is a react event
69
+ //
70
+ // Reg. eslint: Eslint complains about using hasOwnProperty,
71
+ // but accessing `.nativeEvent` directly causes react to log an error
72
+ // eslint-disable-next-line no-prototype-builtins
73
+ expect(onFocus.mock.calls[0][0].hasOwnProperty('nativeEvent')).toBe(
74
+ true
75
+ )
76
+ expect(onFocus).toHaveBeenCalledTimes(1)
77
+ })
78
+
79
+ it('should accept autoFocus', () => {
80
+ const onFocus = jest.fn()
81
+
82
+ render(
83
+ <SimpleSingleSelect
84
+ autoFocus
85
+ onFocus={onFocus}
86
+ name="simple"
87
+ selected={{ value: 'foo', label: 'Foo' }}
88
+ onChange={() => null}
89
+ options={[{ value: 'foo', label: 'Foo' }]}
90
+ />
91
+ )
92
+
93
+ expect(onFocus).toHaveBeenCalledTimes(1)
94
+ })
95
+
96
+ it('should accept loading', () => {
97
+ render(
98
+ <SimpleSingleSelect
99
+ loading
100
+ name="simple"
101
+ selected={{ value: 'foo', label: 'Foo' }}
102
+ onChange={() => null}
103
+ options={[{ value: 'foo', label: 'Foo' }]}
104
+ />
105
+ )
106
+
107
+ fireEvent.click(screen.getByRole('combobox'))
108
+
109
+ const loadingIndicator = screen.getByRole('progressbar')
110
+
111
+ expect(loadingIndicator).toBeTruthy()
112
+ })
113
+
114
+ it('should display a loading text', () => {
115
+ render(
116
+ <SimpleSingleSelect
117
+ loading
118
+ menuLoadingText="Loading text"
119
+ name="simple"
120
+ selected={{ value: 'foo', label: 'Foo' }}
121
+ onChange={() => null}
122
+ options={[{ value: 'foo', label: 'Foo' }]}
123
+ />
124
+ )
125
+
126
+ fireEvent.click(screen.getByRole('combobox'))
127
+
128
+ const loadingIndicator = screen.getByRole('progressbar')
129
+ expect(loadingIndicator).toBeTruthy()
130
+
131
+ const loadingText = screen.getByText('Loading text')
132
+ expect(loadingText).toBeTruthy()
133
+ })
134
+
135
+ it('should accept a max height', () => {
136
+ render(
137
+ <SimpleSingleSelect
138
+ menuMaxHeight="100px"
139
+ name="simple"
140
+ selected={{ value: 'foo', label: 'Foo' }}
141
+ onChange={() => null}
142
+ options={[{ value: 'foo', label: 'Foo' }]}
143
+ />
144
+ )
145
+
146
+ fireEvent.click(screen.getByRole('combobox'))
147
+
148
+ const listbox = screen.getByRole('listbox')
149
+ const menu = listbox.parentNode.parentNode.parentNode
150
+ expect(menu.style.maxHeight).toBe('100px')
151
+ })
152
+
153
+ it('should accept a placeholder', () => {
154
+ render(
155
+ <SimpleSingleSelect
156
+ name="simple"
157
+ selected={null}
158
+ placeholder="Placeholder text"
159
+ onChange={() => null}
160
+ options={[{ value: 'foo', label: 'Foo' }]}
161
+ />
162
+ )
163
+
164
+ const placeholder = screen.getByText('Placeholder text')
165
+ const dataTestValue =
166
+ placeholder.attributes.getNamedItem('data-test').value
167
+ expect(dataTestValue).toBe(
168
+ 'dhis2-simplesingleselect-selectedvalue-placeholder'
169
+ )
170
+ })
171
+
172
+ it('should accept a prefix', () => {
173
+ render(
174
+ <SimpleSingleSelect
175
+ prefix="Prefix text"
176
+ name="simple"
177
+ selected={{ value: 'foo', label: 'Foo' }}
178
+ onChange={() => null}
179
+ options={[{ value: 'foo', label: 'Foo' }]}
180
+ />
181
+ )
182
+
183
+ const placeholder = screen.getByText('Prefix text')
184
+ const dataTestValue =
185
+ placeholder.attributes.getNamedItem('data-test').value
186
+ expect(dataTestValue).toBe(
187
+ 'dhis2-simplesingleselect-selectedvalue-prefix'
188
+ )
189
+ })
190
+
191
+ it('should allow options to be selected', () => {
192
+ const onChange = jest.fn()
193
+
194
+ render(
195
+ <SimpleSingleSelect
196
+ name="simple"
197
+ selected={null}
198
+ onChange={onChange}
199
+ options={[
200
+ { value: '', label: 'None' },
201
+ { value: 'foo', label: 'Foo' },
202
+ { value: 'bar', label: 'Bar' },
203
+ ]}
204
+ />
205
+ )
206
+
207
+ fireEvent.click(screen.getByRole('combobox'))
208
+
209
+ const option = screen.getByText('Foo').parentNode
210
+ expect(option.attributes.getNamedItem('role').value).toBe('option')
211
+
212
+ fireEvent.click(option)
213
+
214
+ expect(onChange).toHaveBeenCalledTimes(1)
215
+ expect(onChange).toHaveBeenCalledWith({ value: 'foo', label: 'Foo' })
216
+ })
217
+
218
+ it('should allow custom options to be selected', () => {
219
+ const onChange = jest.fn()
220
+
221
+ // eslint-disable-next-line react/prop-types
222
+ const CustomOption = ({ value, label }) => (
223
+ <span data-test={`custom-option-${value}`}>{label}</span>
224
+ )
225
+
226
+ render(
227
+ <SimpleSingleSelect
228
+ name="simple"
229
+ selected={null}
230
+ optionComponent={CustomOption}
231
+ valueLabel=""
232
+ onChange={onChange}
233
+ options={[
234
+ { value: '', label: 'None' },
235
+ { value: 'foo', label: 'Foo' },
236
+ ]}
237
+ />
238
+ )
239
+
240
+ fireEvent.click(screen.getByRole('combobox'))
241
+
242
+ const optionComponent = screen.getByTestId('custom-option-foo')
243
+ const option = screen.getByTestId('custom-option-foo').parentNode
244
+ expect(option.attributes.getNamedItem('role').value).toBe('option')
245
+
246
+ fireEvent.click(optionComponent)
247
+
248
+ expect(onChange).toHaveBeenCalledTimes(1)
249
+ expect(onChange).toHaveBeenCalledWith({ value: 'foo', label: 'Foo' })
250
+ })
251
+
252
+ it('should allow individual custom options to be selected', () => {
253
+ const onChange = jest.fn()
254
+
255
+ // eslint-disable-next-line react/prop-types
256
+ const CustomOption = ({ value, label }) => (
257
+ <span data-test={`custom-option-${value}`}>{label}</span>
258
+ )
259
+
260
+ render(
261
+ <SimpleSingleSelect
262
+ name="simple"
263
+ selected={null}
264
+ valueLabel=""
265
+ onChange={onChange}
266
+ options={[
267
+ { value: '', label: 'None' },
268
+ { value: 'foo', label: 'Foo', component: CustomOption },
269
+ ]}
270
+ />
271
+ )
272
+
273
+ fireEvent.click(screen.getByRole('combobox'))
274
+
275
+ const optionComponent = screen.getByTestId('custom-option-foo')
276
+ const option = screen.getByTestId('custom-option-foo').parentNode
277
+ expect(option.attributes.getNamedItem('role').value).toBe('option')
278
+
279
+ fireEvent.click(optionComponent)
280
+
281
+ expect(onChange).toHaveBeenCalledTimes(1)
282
+ expect(onChange).toHaveBeenCalledWith({ value: 'foo', label: 'Foo' })
283
+ })
284
+
285
+ it('should be clearable when there is a selected value', () => {
286
+ const onClear = jest.fn()
287
+
288
+ render(
289
+ <SimpleSingleSelect
290
+ clearable
291
+ clearText="Clear simple select"
292
+ name="simple"
293
+ selected={{ value: 'foo', label: 'Foo' }}
294
+ valueLabel="Foo"
295
+ onChange={() => null}
296
+ onClear={onClear}
297
+ options={[{ value: 'foo', label: 'Foo' }]}
298
+ />
299
+ )
300
+
301
+ const clearButton = screen.getByLabelText('Clear simple select')
302
+ expect(clearButton).toBeInstanceOf(HTMLElement)
303
+
304
+ fireEvent.click(clearButton)
305
+
306
+ expect(onClear).toHaveBeenCalledTimes(1)
307
+ })
308
+
309
+ it('should not be clearable when there is no selected value', () => {
310
+ render(
311
+ <SimpleSingleSelect
312
+ clearable
313
+ clearText="Clear simple select"
314
+ name="simple"
315
+ selected={null}
316
+ onChange={jest.fn()}
317
+ options={[
318
+ { value: '', label: 'None' },
319
+ { value: 'foo', label: 'Foo' },
320
+ ]}
321
+ />
322
+ )
323
+
324
+ const clearButton = screen.queryByLabelText('Clear simple select')
325
+ expect(clearButton).toBeNull()
326
+ })
327
+
328
+ it('should be disabled', () => {
329
+ render(
330
+ <SimpleSingleSelect
331
+ disabled
332
+ name="simple"
333
+ selected={{ value: 'foo', label: 'Foo' }}
334
+ onChange={jest.fn()}
335
+ options={[{ value: 'foo', label: 'Foo' }]}
336
+ />
337
+ )
338
+
339
+ fireEvent.click(screen.getByRole('combobox'))
340
+
341
+ // Element is in the dom, but invisible, which makes it inaccessible
342
+ const listbox = screen.queryByRole('listbox')
343
+ expect(listbox).toBeNull()
344
+
345
+ const clearButton = screen.queryByLabelText('Clear simple select')
346
+ expect(clearButton).toBeNull()
347
+ })
348
+
349
+ it('should have an empty-text in the menu', () => {
350
+ render(
351
+ <SimpleSingleSelect
352
+ empty="Empty"
353
+ name="simple"
354
+ selected={null}
355
+ onChange={jest.fn()}
356
+ options={[]}
357
+ />
358
+ )
359
+
360
+ const emptyTextBeforeOpen = screen.queryByText('Empty')
361
+ expect(emptyTextBeforeOpen).toBeNull()
362
+
363
+ fireEvent.click(screen.getByRole('combobox'))
364
+
365
+ const emptyTextAfterOpen = screen.queryByText('Empty')
366
+ expect(emptyTextAfterOpen).toBeVisible()
367
+ })
368
+
369
+ it('should provide updates about the filter value', () => {
370
+ const onFilterChange = jest.fn()
371
+
372
+ render(
373
+ <SimpleSingleSelect
374
+ filterable
375
+ onFilterChange={onFilterChange}
376
+ noMatchText="No options found"
377
+ name="simple"
378
+ selected={{ value: 'foo', label: 'Foo' }}
379
+ onChange={jest.fn()}
380
+ options={[{ value: 'foo', label: 'Foo' }]}
381
+ />
382
+ )
383
+
384
+ expect(screen.queryByLabelText('Search options')).toBeNull()
385
+
386
+ fireEvent.click(screen.getByRole('combobox'))
387
+
388
+ const searchInput = screen.getByLabelText('Search options')
389
+ expect(searchInput).toBeInstanceOf(HTMLInputElement)
390
+ expect(searchInput).toBeVisible()
391
+
392
+ fireEvent.change(searchInput, { target: { value: 'Search term' } })
393
+
394
+ expect(onFilterChange).toHaveBeenCalledTimes(1)
395
+ expect(onFilterChange).toHaveBeenCalledWith('Search term')
396
+ })
397
+
398
+ it('should apply a custom filter label', () => {
399
+ const onFilterChange = jest.fn()
400
+
401
+ render(
402
+ <SimpleSingleSelect
403
+ filterable
404
+ filterLabel="Custom filter label"
405
+ onFilterChange={onFilterChange}
406
+ noMatchText="No options found"
407
+ name="simple"
408
+ selected={null}
409
+ valueLabel=""
410
+ onChange={jest.fn()}
411
+ options={[
412
+ { value: '', label: 'None' },
413
+ { value: 'foo', label: 'Foo' },
414
+ ]}
415
+ />
416
+ )
417
+
418
+ fireEvent.click(screen.getByRole('combobox'))
419
+
420
+ expect(screen.getByLabelText('Custom filter label')).not.toBeNull()
421
+ })
422
+
423
+ it('should display the selected option', () => {
424
+ const onChange = jest.fn()
425
+
426
+ render(
427
+ <SimpleSingleSelect
428
+ name="simple"
429
+ selected={{ value: 'bar', label: 'Bar' }}
430
+ onChange={onChange}
431
+ options={[
432
+ { value: 'foo', label: 'Foo' },
433
+ { value: 'bar', label: 'Bar' },
434
+ ]}
435
+ />
436
+ )
437
+
438
+ const combobox = screen.getByRole('combobox')
439
+
440
+ const withTextBar = screen.getByText('Bar', {
441
+ selector: '.selected-option-label',
442
+ })
443
+
444
+ expect(combobox).toContainElement(withTextBar)
445
+ })
446
+
447
+ /***************************
448
+ * *
449
+ * ===================== *
450
+ * Keyboard interactions *
451
+ * ===================== *
452
+ * *
453
+ **************************/
454
+ describe.each([
455
+ { key: ' ' },
456
+ { key: 'Enter' },
457
+ { key: 'ArrowDown', altKey: true },
458
+ { key: 'ArrowUp', altKey: true },
459
+ ])('$key ($altKey)', (keyDownOptions) => {
460
+ test('open the menu', () => {
461
+ const onChange = jest.fn()
462
+
463
+ render(
464
+ <SimpleSingleSelect
465
+ name="simple"
466
+ selected={null}
467
+ onChange={onChange}
468
+ options={[
469
+ { value: '', label: 'None' },
470
+ { value: 'foo', label: 'Foo' },
471
+ { value: 'bar', label: 'Bar' },
472
+ ]}
473
+ />
474
+ )
475
+
476
+ fireEvent.keyDown(screen.getByRole('combobox'), keyDownOptions)
477
+ expect(screen.queryByRole('listbox')).not.toBeNull()
478
+ expect(onChange).not.toHaveBeenCalled()
479
+ })
480
+ })
481
+
482
+ describe.each([
483
+ { key: ' ' },
484
+ { key: 'Enter' },
485
+ { key: 'ArrowDown', altKey: true },
486
+ { key: 'ArrowUp', altKey: true },
487
+ ])('disabled & $key ($altKey)', (keyDownOptions) => {
488
+ test('not open the menu', () => {
489
+ const onChange = jest.fn()
490
+
491
+ render(
492
+ <SimpleSingleSelect
493
+ disabled
494
+ name="simple"
495
+ selected={null}
496
+ onChange={onChange}
497
+ options={[
498
+ { value: '', label: 'None' },
499
+ { value: 'foo', label: 'Foo' },
500
+ { value: 'bar', label: 'Bar' },
501
+ ]}
502
+ />
503
+ )
504
+
505
+ fireEvent.keyDown(screen.getByRole('combobox'), keyDownOptions)
506
+ expect(screen.queryByRole('listbox')).toBeNull()
507
+ expect(onChange).not.toHaveBeenCalled()
508
+ })
509
+ })
510
+
511
+ describe.each([
512
+ { key: ' ' },
513
+ { key: 'Enter' },
514
+ { key: 'ArrowDown', altKey: true },
515
+ { key: 'ArrowUp', altKey: true },
516
+ ])('$key ($altKey)', (keyDownOptions) => {
517
+ test('close the menu', () => {
518
+ const onChange = jest.fn()
519
+
520
+ render(
521
+ <SimpleSingleSelect
522
+ name="simple"
523
+ selected={null}
524
+ onChange={onChange}
525
+ options={[
526
+ { value: '', label: 'None' },
527
+ { value: 'foo', label: 'Foo' },
528
+ { value: 'bar', label: 'Bar' },
529
+ ]}
530
+ />
531
+ )
532
+
533
+ fireEvent.click(screen.getByRole('combobox'))
534
+ expect(screen.queryByRole('listbox')).not.toBeNull()
535
+ fireEvent.keyDown(screen.getByRole('combobox'), keyDownOptions)
536
+ expect(screen.queryByRole('listbox')).toBeNull()
537
+ expect(onChange).not.toHaveBeenCalled()
538
+ })
539
+ })
540
+
541
+ describe.each([
542
+ { key: 'Escape' },
543
+ { key: 'Enter' },
544
+ { key: 'ArrowDown', altKey: true },
545
+ { key: 'ArrowUp', altKey: true },
546
+ ])('$key ($altKey)', (keyDownOptions) => {
547
+ test('close the menu and select the highlighted option after having highlighted another option', () => {
548
+ const onChange = jest.fn()
549
+
550
+ render(
551
+ <SimpleSingleSelect
552
+ name="simple"
553
+ selected={null}
554
+ onChange={onChange}
555
+ options={[
556
+ { value: '', label: 'None' },
557
+ { value: 'foo', label: 'Foo' },
558
+ { value: 'bar', label: 'Bar' },
559
+ ]}
560
+ />
561
+ )
562
+
563
+ fireEvent.click(screen.getByRole('combobox'))
564
+ expect(screen.queryByRole('listbox')).not.toBeNull()
565
+
566
+ // highlighting the next option
567
+ fireEvent.keyDown(screen.getByRole('combobox'), {
568
+ key: 'ArrowDown',
569
+ })
570
+
571
+ fireEvent.keyDown(screen.getByRole('combobox'), keyDownOptions)
572
+
573
+ expect(screen.queryByRole('listbox')).toBeNull()
574
+ expect(onChange).toHaveBeenCalledTimes(1)
575
+ expect(onChange).toHaveBeenCalledWith({
576
+ value: 'foo',
577
+ label: 'Foo',
578
+ })
579
+ })
580
+ })
581
+
582
+ it('should select the next option when closed and user presses ArrowDown', () => {
583
+ const onChange = jest.fn()
584
+
585
+ render(
586
+ <SimpleSingleSelect
587
+ name="simple"
588
+ selected={null}
589
+ onChange={onChange}
590
+ options={[
591
+ { value: '', label: 'None' },
592
+ { value: 'foo', label: 'Foo' },
593
+ { value: 'bar', label: 'Bar' },
594
+ ]}
595
+ />
596
+ )
597
+
598
+ expect(screen.queryByRole('listbox')).toBeNull()
599
+
600
+ // highlighting the next option
601
+ fireEvent.keyDown(screen.getByRole('combobox'), { key: 'ArrowDown' })
602
+ expect(screen.queryByRole('listbox')).toBeNull()
603
+ expect(onChange).toHaveBeenCalledTimes(1)
604
+ expect(onChange).toHaveBeenCalledWith({ value: 'foo', label: 'Foo' })
605
+ })
606
+
607
+ describe('Clear the selected value when ESCAPE is pressed', () => {
608
+ it('should clear the selected value when closed, clearable and user presses Escape', () => {
609
+ function TestSelect() {
610
+ const [selected, setSelected] = useState({
611
+ label: 'Foo',
612
+ value: 'foo',
613
+ })
614
+
615
+ return (
616
+ <SimpleSingleSelect
617
+ clearable
618
+ name="simple"
619
+ selected={selected}
620
+ onClear={() =>
621
+ setSelected({
622
+ label: '',
623
+ value: '',
624
+ })
625
+ }
626
+ options={[
627
+ { value: '', label: 'None' },
628
+ { value: 'foo', label: 'Foo' },
629
+ { value: 'bar', label: 'Bar' },
630
+ ]}
631
+ />
632
+ )
633
+ }
634
+ render(<TestSelect />)
635
+
636
+ // menu is closed
637
+ expect(screen.queryByRole('listbox')).toBeNull()
638
+
639
+ // there are selected value
640
+ expect(screen.getByText('Foo')).toBeInTheDocument()
641
+
642
+ // press Escape once to clear the selected value
643
+ fireEvent.keyDown(screen.getByRole('combobox'), { key: 'Escape' })
644
+
645
+ // the value is cleared
646
+ expect(screen.queryByText('Foo')).toBeNull()
647
+ })
648
+
649
+ it('should clear the selected value when open and ESCAPE is pressed twice', () => {
650
+ const onClear = jest.fn()
651
+
652
+ render(
653
+ <SimpleSingleSelect
654
+ clearable
655
+ name="simple"
656
+ selected={{ value: 'bar', label: 'Bar' }}
657
+ onChange={() => {}}
658
+ onClear={onClear}
659
+ clearText="Clear"
660
+ options={[
661
+ { value: '', label: 'None' },
662
+ { value: 'foo', label: 'Foo' },
663
+ { value: 'bar', label: 'Bar' },
664
+ ]}
665
+ />
666
+ )
667
+
668
+ const comboBox = screen.getByRole('combobox')
669
+
670
+ // open the menu
671
+ expect(
672
+ comboBox.attributes.getNamedItem('aria-expanded').value
673
+ ).toBe('false')
674
+ fireEvent.click(comboBox)
675
+ expect(
676
+ comboBox.attributes.getNamedItem('aria-expanded').value
677
+ ).toBe('true')
678
+
679
+ // The first escape should close the combo BUT not call clear
680
+ fireEvent.keyDown(screen.getByRole('combobox'), { key: 'Escape' })
681
+ expect(
682
+ comboBox.attributes.getNamedItem('aria-expanded').value
683
+ ).toBe('false')
684
+ expect(onClear).not.toHaveBeenCalledWith()
685
+
686
+ // The second escape should call clear
687
+ fireEvent.keyDown(screen.getByRole('combobox'), { key: 'Escape' })
688
+ expect(onClear).toHaveBeenCalledWith()
689
+ })
690
+ })
691
+
692
+ it('should not select the next option when closed, disabled and user presses ArrowDown', () => {
693
+ const onChange = jest.fn()
694
+
695
+ render(
696
+ <SimpleSingleSelect
697
+ disabled
698
+ name="simple"
699
+ selected={null}
700
+ onChange={onChange}
701
+ options={[
702
+ { value: '', label: 'None' },
703
+ { value: 'foo', label: 'Foo' },
704
+ { value: 'bar', label: 'Bar' },
705
+ ]}
706
+ />
707
+ )
708
+
709
+ expect(screen.queryByRole('listbox')).toBeNull()
710
+
711
+ // highlighting the next option
712
+ fireEvent.keyDown(screen.getByRole('combobox'), { key: 'ArrowDown' })
713
+ expect(screen.queryByRole('listbox')).toBeNull()
714
+ expect(onChange).toHaveBeenCalledTimes(0)
715
+ })
716
+
717
+ it('should select the second-next option when closed, next option is disabled and user presses ArrowDown', () => {
718
+ const onChange = jest.fn()
719
+
720
+ render(
721
+ <SimpleSingleSelect
722
+ name="simple"
723
+ selected={null}
724
+ onChange={onChange}
725
+ options={[
726
+ { value: '', label: 'None' },
727
+ { value: 'foo', label: 'Foo', disabled: true },
728
+ { value: 'bar', label: 'Bar' },
729
+ ]}
730
+ />
731
+ )
732
+
733
+ expect(screen.queryByRole('listbox')).toBeNull()
734
+
735
+ // highlighting the next option
736
+ fireEvent.keyDown(screen.getByRole('combobox'), { key: 'ArrowDown' })
737
+ expect(screen.queryByRole('listbox')).toBeNull()
738
+ expect(onChange).toHaveBeenCalledTimes(1)
739
+ expect(onChange).toHaveBeenCalledWith({ value: 'bar', label: 'Bar' })
740
+ })
741
+
742
+ it('should select the previous option when closed and user presses ArrowUp', () => {
743
+ const onChange = jest.fn()
744
+
745
+ render(
746
+ <SimpleSingleSelect
747
+ name="simple"
748
+ selected={{ value: 'bar', label: 'Bar' }}
749
+ onChange={onChange}
750
+ options={[
751
+ { value: '', label: 'None' },
752
+ { value: 'foo', label: 'Foo' },
753
+ { value: 'bar', label: 'Bar' },
754
+ ]}
755
+ />
756
+ )
757
+
758
+ expect(screen.queryByRole('listbox')).toBeNull()
759
+
760
+ // highlighting the next option
761
+ fireEvent.keyDown(screen.getByRole('combobox'), { key: 'ArrowUp' })
762
+ expect(screen.queryByRole('listbox')).toBeNull()
763
+ expect(onChange).toHaveBeenCalledTimes(1)
764
+ expect(onChange).toHaveBeenCalledWith({ value: 'foo', label: 'Foo' })
765
+ })
766
+
767
+ it('should not select the previous option when closed, disabled and user presses ArrowUp', () => {
768
+ const onChange = jest.fn()
769
+
770
+ render(
771
+ <SimpleSingleSelect
772
+ disabled
773
+ name="simple"
774
+ selected={{ value: 'bar', label: 'Bar' }}
775
+ onChange={onChange}
776
+ options={[
777
+ { value: '', label: 'None' },
778
+ { value: 'foo', label: 'Foo' },
779
+ { value: 'bar', label: 'Bar' },
780
+ ]}
781
+ />
782
+ )
783
+
784
+ expect(screen.queryByRole('listbox')).toBeNull()
785
+
786
+ // highlighting the next option
787
+ fireEvent.keyDown(screen.getByRole('combobox'), { key: 'ArrowUp' })
788
+ expect(screen.queryByRole('listbox')).toBeNull()
789
+ expect(onChange).toHaveBeenCalledTimes(0)
790
+ })
791
+
792
+ it('should select the second-previous option when closed, previous option disabled and user presses ArrowUp', () => {
793
+ const onChange = jest.fn()
794
+
795
+ render(
796
+ <SimpleSingleSelect
797
+ name="simple"
798
+ selected={{ value: 'bar', label: 'Bar' }}
799
+ onChange={onChange}
800
+ options={[
801
+ { value: '', label: 'None' },
802
+ { value: 'foo', label: 'Foo', disabled: true },
803
+ { value: 'bar', label: 'Bar' },
804
+ ]}
805
+ />
806
+ )
807
+
808
+ expect(screen.queryByRole('listbox')).toBeNull()
809
+
810
+ // highlighting the next option
811
+ fireEvent.keyDown(screen.getByRole('combobox'), { key: 'ArrowUp' })
812
+ expect(screen.queryByRole('listbox')).toBeNull()
813
+ expect(onChange).toHaveBeenCalledTimes(1)
814
+ expect(onChange).toHaveBeenCalledWith({ value: '', label: 'None' })
815
+ })
816
+
817
+ it('should highlight the next option', () => {
818
+ const onChange = jest.fn()
819
+
820
+ render(
821
+ <SimpleSingleSelect
822
+ name="simple"
823
+ selected={null}
824
+ onChange={onChange}
825
+ options={[
826
+ { value: '', label: 'None' },
827
+ { value: 'foo', label: 'Foo' },
828
+ { value: 'bar', label: 'Bar' },
829
+ ]}
830
+ />
831
+ )
832
+
833
+ // open the menu
834
+ expect(screen.queryByRole('listbox')).toBeNull()
835
+ const comboBox = screen.getByRole('combobox')
836
+ fireEvent.click(comboBox)
837
+ expect(screen.queryByRole('listbox')).not.toBeNull()
838
+
839
+ // the first option should be highlighted
840
+ const highlightedOptionBefore = screen.getByRole('option', {
841
+ selected: true,
842
+ })
843
+ expect(
844
+ highlightedOptionBefore.attributes.getNamedItem('aria-label').value
845
+ ).toBe('None')
846
+
847
+ // The second option should be highlighted
848
+ fireEvent.keyDown(comboBox, { key: 'ArrowDown' })
849
+ const highlightedOptionAfter = screen.getByRole('option', {
850
+ selected: true,
851
+ })
852
+ expect(
853
+ highlightedOptionAfter.attributes.getNamedItem('aria-label').value
854
+ ).toBe('Foo')
855
+ })
856
+
857
+ it('should highlight the second next option when the next option is disabled', () => {
858
+ const onChange = jest.fn()
859
+
860
+ render(
861
+ <SimpleSingleSelect
862
+ name="simple"
863
+ selected={null}
864
+ onChange={onChange}
865
+ options={[
866
+ { value: '', label: 'None' },
867
+ { value: 'foo', label: 'Foo', disabled: true },
868
+ { value: 'bar', label: 'Bar' },
869
+ ]}
870
+ />
871
+ )
872
+
873
+ // open the menu
874
+ expect(screen.queryByRole('listbox')).toBeNull()
875
+ const comboBox = screen.getByRole('combobox')
876
+ fireEvent.click(comboBox)
877
+ expect(screen.queryByRole('listbox')).not.toBeNull()
878
+
879
+ // the first option should be highlighted
880
+ const highlightedOptionBefore = screen.getByRole('option', {
881
+ selected: true,
882
+ })
883
+ expect(
884
+ highlightedOptionBefore.attributes.getNamedItem('aria-label').value
885
+ ).toBe('None')
886
+
887
+ // The second option should be highlighted
888
+ fireEvent.keyDown(comboBox, { key: 'ArrowDown' })
889
+ const highlightedOptionAfter = screen.getByRole('option', {
890
+ selected: true,
891
+ })
892
+ expect(
893
+ highlightedOptionAfter.attributes.getNamedItem('aria-label').value
894
+ ).toBe('Bar')
895
+ })
896
+
897
+ it('should highlight the previous option', () => {
898
+ const onChange = jest.fn()
899
+
900
+ render(
901
+ <SimpleSingleSelect
902
+ name="simple"
903
+ selected={{ value: 'bar', label: 'Bar' }}
904
+ onChange={onChange}
905
+ options={[
906
+ { value: '', label: 'None' },
907
+ { value: 'foo', label: 'Foo' },
908
+ { value: 'bar', label: 'Bar' },
909
+ ]}
910
+ />
911
+ )
912
+
913
+ // open the menu
914
+ expect(screen.queryByRole('listbox')).toBeNull()
915
+ const comboBox = screen.getByRole('combobox')
916
+ fireEvent.click(comboBox)
917
+ expect(screen.queryByRole('listbox')).not.toBeNull()
918
+
919
+ // the last option should be highlighted
920
+ const highlightedOptionBefore = screen.getByRole('option', {
921
+ selected: true,
922
+ })
923
+ expect(
924
+ highlightedOptionBefore.attributes.getNamedItem('aria-label').value
925
+ ).toBe('Bar')
926
+
927
+ // The second option should be highlighted
928
+ fireEvent.keyDown(comboBox, { key: 'ArrowUp' })
929
+ const highlightedOptionAfter = screen.getByRole('option', {
930
+ selected: true,
931
+ })
932
+ expect(
933
+ highlightedOptionAfter.attributes.getNamedItem('aria-label').value
934
+ ).toBe('Foo')
935
+ })
936
+
937
+ it('should highlight the second previous option when previous option is disabled', () => {
938
+ const onChange = jest.fn()
939
+
940
+ render(
941
+ <SimpleSingleSelect
942
+ name="simple"
943
+ selected={{ value: 'bar', label: 'Bar' }}
944
+ onChange={onChange}
945
+ options={[
946
+ { value: '', label: 'None' },
947
+ { value: 'foo', label: 'Foo', disabled: true },
948
+ { value: 'bar', label: 'Bar' },
949
+ ]}
950
+ />
951
+ )
952
+
953
+ // open the menu
954
+ expect(screen.queryByRole('listbox')).toBeNull()
955
+ const comboBox = screen.getByRole('combobox')
956
+ fireEvent.click(comboBox)
957
+ expect(screen.queryByRole('listbox')).not.toBeNull()
958
+
959
+ // the last option should be highlighted
960
+ const highlightedOptionBefore = screen.getByRole('option', {
961
+ selected: true,
962
+ })
963
+ expect(
964
+ highlightedOptionBefore.attributes.getNamedItem('aria-label').value
965
+ ).toBe('Bar')
966
+
967
+ // The second option should be highlighted
968
+ fireEvent.keyDown(comboBox, { key: 'ArrowUp' })
969
+ const highlightedOptionAfter = screen.getByRole('option', {
970
+ selected: true,
971
+ })
972
+ expect(
973
+ highlightedOptionAfter.attributes.getNamedItem('aria-label').value
974
+ ).toBe('None')
975
+ })
976
+
977
+ it('should highlight the first option', () => {
978
+ const onChange = jest.fn()
979
+
980
+ render(
981
+ <SimpleSingleSelect
982
+ name="simple"
983
+ selected={{ value: 'bar', label: 'Bar' }}
984
+ onChange={onChange}
985
+ options={[
986
+ { value: '', label: 'None' },
987
+ { value: 'foo', label: 'Foo' },
988
+ { value: 'bar', label: 'Bar' },
989
+ ]}
990
+ />
991
+ )
992
+
993
+ // open the menu
994
+ expect(screen.queryByRole('listbox')).toBeNull()
995
+ const comboBox = screen.getByRole('combobox')
996
+ fireEvent.click(comboBox)
997
+ expect(screen.queryByRole('listbox')).not.toBeNull()
998
+
999
+ // the last option should be highlighted
1000
+ const highlightedOptionBefore = screen.getByRole('option', {
1001
+ selected: true,
1002
+ })
1003
+ expect(
1004
+ highlightedOptionBefore.attributes.getNamedItem('aria-label').value
1005
+ ).toBe('Bar')
1006
+
1007
+ // The first option should be highlighted
1008
+ fireEvent.keyDown(comboBox, { key: 'Home' })
1009
+ const highlightedOptionAfter = screen.getByRole('option', {
1010
+ selected: true,
1011
+ })
1012
+ expect(
1013
+ highlightedOptionAfter.attributes.getNamedItem('aria-label').value
1014
+ ).toBe('None')
1015
+ })
1016
+
1017
+ it('should highlight the first enabled option', () => {
1018
+ const onChange = jest.fn()
1019
+
1020
+ render(
1021
+ <SimpleSingleSelect
1022
+ name="simple"
1023
+ selected={{ value: 'bar', label: 'Bar' }}
1024
+ onChange={onChange}
1025
+ options={[
1026
+ { value: '', label: 'None', disabled: true },
1027
+ { value: 'foo', label: 'Foo' },
1028
+ { value: 'bar', label: 'Bar' },
1029
+ ]}
1030
+ />
1031
+ )
1032
+
1033
+ // open the menu
1034
+ expect(screen.queryByRole('listbox')).toBeNull()
1035
+ const comboBox = screen.getByRole('combobox')
1036
+ fireEvent.click(comboBox)
1037
+ expect(screen.queryByRole('listbox')).not.toBeNull()
1038
+
1039
+ // the last option should be highlighted
1040
+ const highlightedOptionBefore = screen.getByRole('option', {
1041
+ selected: true,
1042
+ })
1043
+ expect(
1044
+ highlightedOptionBefore.attributes.getNamedItem('aria-label').value
1045
+ ).toBe('Bar')
1046
+
1047
+ // The first option should be highlighted
1048
+ fireEvent.keyDown(comboBox, { key: 'Home' })
1049
+ const highlightedOptionAfter = screen.getByRole('option', {
1050
+ selected: true,
1051
+ })
1052
+ expect(
1053
+ highlightedOptionAfter.attributes.getNamedItem('aria-label').value
1054
+ ).toBe('Foo')
1055
+ })
1056
+
1057
+ it('should highlight the last option', () => {
1058
+ const onChange = jest.fn()
1059
+
1060
+ render(
1061
+ <SimpleSingleSelect
1062
+ name="simple"
1063
+ selected={null}
1064
+ onChange={onChange}
1065
+ options={[
1066
+ { value: '', label: 'None' },
1067
+ { value: 'foo', label: 'Foo' },
1068
+ { value: 'bar', label: 'Bar' },
1069
+ ]}
1070
+ />
1071
+ )
1072
+
1073
+ // open the menu
1074
+ expect(screen.queryByRole('listbox')).toBeNull()
1075
+ const comboBox = screen.getByRole('combobox')
1076
+ fireEvent.click(comboBox)
1077
+ expect(screen.queryByRole('listbox')).not.toBeNull()
1078
+
1079
+ // the first option should be highlighted
1080
+ const highlightedOptionBefore = screen.getByRole('option', {
1081
+ selected: true,
1082
+ })
1083
+ expect(
1084
+ highlightedOptionBefore.attributes.getNamedItem('aria-label').value
1085
+ ).toBe('None')
1086
+
1087
+ // The last option should be highlighted
1088
+ fireEvent.keyDown(comboBox, { key: 'End' })
1089
+ const highlightedOptionAfter = screen.getByRole('option', {
1090
+ selected: true,
1091
+ })
1092
+ expect(
1093
+ highlightedOptionAfter.attributes.getNamedItem('aria-label').value
1094
+ ).toBe('Bar')
1095
+ })
1096
+
1097
+ it('should highlight the last enabled option', () => {
1098
+ const onChange = jest.fn()
1099
+
1100
+ render(
1101
+ <SimpleSingleSelect
1102
+ name="simple"
1103
+ selected={null}
1104
+ onChange={onChange}
1105
+ options={[
1106
+ { value: '', label: 'None' },
1107
+ { value: 'foo', label: 'Foo' },
1108
+ { value: 'bar', label: 'Bar', disabled: true },
1109
+ ]}
1110
+ />
1111
+ )
1112
+
1113
+ // open the menu
1114
+ expect(screen.queryByRole('listbox')).toBeNull()
1115
+ const comboBox = screen.getByRole('combobox')
1116
+ fireEvent.click(comboBox)
1117
+ expect(screen.queryByRole('listbox')).not.toBeNull()
1118
+
1119
+ // the first option should be highlighted
1120
+ const highlightedOptionBefore = screen.getByRole('option', {
1121
+ selected: true,
1122
+ })
1123
+ expect(
1124
+ highlightedOptionBefore.attributes.getNamedItem('aria-label').value
1125
+ ).toBe('None')
1126
+
1127
+ // The last option should be highlighted
1128
+ fireEvent.keyDown(comboBox, { key: 'End' })
1129
+ const highlightedOptionAfter = screen.getByRole('option', {
1130
+ selected: true,
1131
+ })
1132
+ expect(
1133
+ highlightedOptionAfter.attributes.getNamedItem('aria-label').value
1134
+ ).toBe('Foo')
1135
+ })
1136
+ })