@wordpress/components 26.0.1 → 27.0.0

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 (223) hide show
  1. package/.stylelintrc.js +2 -2
  2. package/CHANGELOG.md +23 -0
  3. package/CONTRIBUTING.md +72 -0
  4. package/build/autocomplete/index.js +3 -8
  5. package/build/autocomplete/index.js.map +1 -1
  6. package/build/color-picker/component.js +10 -1
  7. package/build/color-picker/component.js.map +1 -1
  8. package/build/color-picker/styles.js +8 -9
  9. package/build/color-picker/styles.js.map +1 -1
  10. package/build/combobox-control/index.js +4 -9
  11. package/build/combobox-control/index.js.map +1 -1
  12. package/build/custom-select-control/index.js +2 -15
  13. package/build/custom-select-control/index.js.map +1 -1
  14. package/build/custom-select-control-v2/custom-select-item.js +32 -0
  15. package/build/custom-select-control-v2/custom-select-item.js.map +1 -0
  16. package/build/custom-select-control-v2/custom-select.js +91 -0
  17. package/build/custom-select-control-v2/custom-select.js.map +1 -0
  18. package/build/custom-select-control-v2/default-component/index.js +41 -0
  19. package/build/custom-select-control-v2/default-component/index.js.map +1 -0
  20. package/build/custom-select-control-v2/index.js +13 -82
  21. package/build/custom-select-control-v2/index.js.map +1 -1
  22. package/build/custom-select-control-v2/legacy-adapter.js +29 -0
  23. package/build/custom-select-control-v2/legacy-adapter.js.map +1 -0
  24. package/build/custom-select-control-v2/legacy-component/index.js +123 -0
  25. package/build/custom-select-control-v2/legacy-component/index.js.map +1 -0
  26. package/build/custom-select-control-v2/styles.js +73 -50
  27. package/build/custom-select-control-v2/styles.js.map +1 -1
  28. package/build/custom-select-control-v2/types.js.map +1 -1
  29. package/build/font-size-picker/font-size-picker-select.js +0 -1
  30. package/build/font-size-picker/font-size-picker-select.js.map +1 -1
  31. package/build/form-token-field/index.js +3 -8
  32. package/build/form-token-field/index.js.map +1 -1
  33. package/build/form-token-field/suggestions-list.js +5 -12
  34. package/build/form-token-field/suggestions-list.js.map +1 -1
  35. package/build/mobile/keyboard-aware-flat-list/use-scroll-to-section.native.js +1 -1
  36. package/build/mobile/keyboard-aware-flat-list/use-scroll-to-section.native.js.map +1 -1
  37. package/build/modal/index.js +2 -10
  38. package/build/modal/index.js.map +1 -1
  39. package/build/progress-bar/styles.js +5 -5
  40. package/build/progress-bar/styles.js.map +1 -1
  41. package/build/utils/with-ignore-ime-events.js +34 -0
  42. package/build/utils/with-ignore-ime-events.js.map +1 -0
  43. package/build-module/autocomplete/index.js +3 -8
  44. package/build-module/autocomplete/index.js.map +1 -1
  45. package/build-module/color-picker/component.js +11 -2
  46. package/build-module/color-picker/component.js.map +1 -1
  47. package/build-module/color-picker/styles.js +8 -9
  48. package/build-module/color-picker/styles.js.map +1 -1
  49. package/build-module/combobox-control/index.js +4 -9
  50. package/build-module/combobox-control/index.js.map +1 -1
  51. package/build-module/custom-select-control/index.js +2 -15
  52. package/build-module/custom-select-control/index.js.map +1 -1
  53. package/build-module/custom-select-control-v2/custom-select-item.js +26 -0
  54. package/build-module/custom-select-control-v2/custom-select-item.js.map +1 -0
  55. package/build-module/custom-select-control-v2/custom-select.js +82 -0
  56. package/build-module/custom-select-control-v2/custom-select.js.map +1 -0
  57. package/build-module/custom-select-control-v2/default-component/index.js +30 -0
  58. package/build-module/custom-select-control-v2/default-component/index.js.map +1 -0
  59. package/build-module/custom-select-control-v2/index.js +2 -74
  60. package/build-module/custom-select-control-v2/index.js.map +1 -1
  61. package/build-module/custom-select-control-v2/legacy-adapter.js +21 -0
  62. package/build-module/custom-select-control-v2/legacy-adapter.js.map +1 -0
  63. package/build-module/custom-select-control-v2/legacy-component/index.js +111 -0
  64. package/build-module/custom-select-control-v2/legacy-component/index.js.map +1 -0
  65. package/build-module/custom-select-control-v2/styles.js +73 -42
  66. package/build-module/custom-select-control-v2/styles.js.map +1 -1
  67. package/build-module/custom-select-control-v2/types.js.map +1 -1
  68. package/build-module/font-size-picker/font-size-picker-select.js +0 -1
  69. package/build-module/font-size-picker/font-size-picker-select.js.map +1 -1
  70. package/build-module/form-token-field/index.js +3 -8
  71. package/build-module/form-token-field/index.js.map +1 -1
  72. package/build-module/form-token-field/suggestions-list.js +5 -12
  73. package/build-module/form-token-field/suggestions-list.js.map +1 -1
  74. package/build-module/mobile/keyboard-aware-flat-list/use-scroll-to-section.native.js +1 -1
  75. package/build-module/mobile/keyboard-aware-flat-list/use-scroll-to-section.native.js.map +1 -1
  76. package/build-module/modal/index.js +3 -10
  77. package/build-module/modal/index.js.map +1 -1
  78. package/build-module/progress-bar/styles.js +5 -5
  79. package/build-module/progress-bar/styles.js.map +1 -1
  80. package/build-module/utils/with-ignore-ime-events.js +28 -0
  81. package/build-module/utils/with-ignore-ime-events.js.map +1 -0
  82. package/build-style/style-rtl.css +8 -1
  83. package/build-style/style.css +8 -1
  84. package/build-types/alignment-matrix-control/stories/index.story.d.ts +1 -1
  85. package/build-types/angle-picker-control/stories/index.story.d.ts +1 -1
  86. package/build-types/animate/stories/index.story.d.ts +7 -7
  87. package/build-types/autocomplete/index.d.ts.map +1 -1
  88. package/build-types/base-control/stories/index.story.d.ts +1 -1
  89. package/build-types/border-box-control/stories/index.story.d.ts +1 -1
  90. package/build-types/border-control/stories/index.story.d.ts +6 -6
  91. package/build-types/box-control/stories/index.story.d.ts +6 -6
  92. package/build-types/button/stories/e2e/index.story.d.ts +1 -1
  93. package/build-types/button/stories/index.story.d.ts +7 -7
  94. package/build-types/card/stories/index.story.d.ts +2 -2
  95. package/build-types/circular-option-picker/stories/index.story.d.ts +5 -5
  96. package/build-types/color-palette/stories/index.story.d.ts +3 -3
  97. package/build-types/color-picker/component.d.ts.map +1 -1
  98. package/build-types/color-picker/stories/index.story.d.ts +1 -1
  99. package/build-types/color-picker/styles.d.ts.map +1 -1
  100. package/build-types/combobox-control/index.d.ts.map +1 -1
  101. package/build-types/combobox-control/stories/index.story.d.ts +2 -2
  102. package/build-types/confirm-dialog/stories/index.story.d.ts +2 -2
  103. package/build-types/custom-gradient-picker/stories/index.story.d.ts +1 -1
  104. package/build-types/custom-select-control/index.d.ts.map +1 -1
  105. package/build-types/custom-select-control-v2/custom-select-item.d.ts +9 -0
  106. package/build-types/custom-select-control-v2/custom-select-item.d.ts.map +1 -0
  107. package/build-types/custom-select-control-v2/custom-select.d.ts +6 -0
  108. package/build-types/custom-select-control-v2/custom-select.d.ts.map +1 -0
  109. package/build-types/custom-select-control-v2/default-component/index.d.ts +5 -0
  110. package/build-types/custom-select-control-v2/default-component/index.d.ts.map +1 -0
  111. package/build-types/custom-select-control-v2/index.d.ts +5 -6
  112. package/build-types/custom-select-control-v2/index.d.ts.map +1 -1
  113. package/build-types/custom-select-control-v2/legacy-adapter.d.ts +6 -0
  114. package/build-types/custom-select-control-v2/legacy-adapter.d.ts.map +1 -0
  115. package/build-types/custom-select-control-v2/legacy-component/index.d.ts +5 -0
  116. package/build-types/custom-select-control-v2/legacy-component/index.d.ts.map +1 -0
  117. package/build-types/custom-select-control-v2/stories/default.story.d.ts +29 -0
  118. package/build-types/custom-select-control-v2/stories/default.story.d.ts.map +1 -0
  119. package/build-types/custom-select-control-v2/stories/legacy.story.d.ts +12 -0
  120. package/build-types/custom-select-control-v2/stories/legacy.story.d.ts.map +1 -0
  121. package/build-types/custom-select-control-v2/styles.d.ts +31 -6
  122. package/build-types/custom-select-control-v2/styles.d.ts.map +1 -1
  123. package/build-types/custom-select-control-v2/types.d.ts +137 -14
  124. package/build-types/custom-select-control-v2/types.d.ts.map +1 -1
  125. package/build-types/dimension-control/stories/index.story.d.ts +2 -2
  126. package/build-types/drop-zone/stories/index.story.d.ts +1 -1
  127. package/build-types/dropdown/stories/index.story.d.ts +3 -3
  128. package/build-types/dropdown-menu/stories/index.story.d.ts +2 -2
  129. package/build-types/dropdown-menu-v2/stories/index.story.d.ts.map +1 -1
  130. package/build-types/duotone-picker/stories/duotone-picker.story.d.ts +1 -1
  131. package/build-types/duotone-picker/stories/duotone-swatch.story.d.ts +3 -3
  132. package/build-types/focal-point-picker/stories/index.story.d.ts +4 -4
  133. package/build-types/font-size-picker/font-size-picker-select.d.ts.map +1 -1
  134. package/build-types/form-file-upload/stories/index.story.d.ts +5 -5
  135. package/build-types/form-token-field/index.d.ts.map +1 -1
  136. package/build-types/form-token-field/suggestions-list.d.ts.map +1 -1
  137. package/build-types/gradient-picker/stories/index.story.d.ts +3 -3
  138. package/build-types/guide/stories/index.story.d.ts +1 -1
  139. package/build-types/icon/stories/index.story.d.ts +4 -4
  140. package/build-types/input-control/stories/index.story.d.ts +6 -6
  141. package/build-types/keyboard-shortcuts/stories/index.story.d.ts +1 -1
  142. package/build-types/menu-group/stories/index.story.d.ts +1 -1
  143. package/build-types/menu-item/stories/index.story.d.ts +4 -4
  144. package/build-types/modal/index.d.ts.map +1 -1
  145. package/build-types/navigation/stories/index.story.d.ts +6 -6
  146. package/build-types/notice/stories/index.story.d.ts +4 -4
  147. package/build-types/number-control/stories/index.story.d.ts +1 -1
  148. package/build-types/palette-edit/stories/index.story.d.ts +2 -2
  149. package/build-types/progress-bar/stories/index.story.d.ts.map +1 -1
  150. package/build-types/query-controls/stories/index.story.d.ts +1 -1
  151. package/build-types/resizable-box/stories/index.story.d.ts +2 -2
  152. package/build-types/responsive-wrapper/stories/index.story.d.ts +1 -1
  153. package/build-types/sandbox/stories/index.story.d.ts +1 -1
  154. package/build-types/search-control/stories/index.story.d.ts +2 -2
  155. package/build-types/select-control/stories/index.story.d.ts +2 -2
  156. package/build-types/shortcut/stories/index.story.d.ts +1 -1
  157. package/build-types/tab-panel/stories/index.story.d.ts +4 -4
  158. package/build-types/tabs/stories/index.story.d.ts +9 -9
  159. package/build-types/tabs/stories/index.story.d.ts.map +1 -1
  160. package/build-types/text/stories/index.story.d.ts +3 -3
  161. package/build-types/theme/stories/index.story.d.ts +1 -1
  162. package/build-types/theme/stories/index.story.d.ts.map +1 -1
  163. package/build-types/toggle-control/stories/index.story.d.ts +2 -2
  164. package/build-types/toolbar/stories/index.story.d.ts +3 -3
  165. package/build-types/tooltip/stories/index.story.d.ts +1 -1
  166. package/build-types/tree-grid/stories/index.story.d.ts +1 -1
  167. package/build-types/tree-select/stories/index.story.d.ts +1 -1
  168. package/build-types/utils/with-ignore-ime-events.d.ts +15 -0
  169. package/build-types/utils/with-ignore-ime-events.d.ts.map +1 -0
  170. package/build-types/v-stack/stories/index.story.d.ts +1 -1
  171. package/package.json +19 -20
  172. package/src/alignment-matrix-control/test/index.tsx +3 -1
  173. package/src/autocomplete/index.tsx +3 -10
  174. package/src/circular-option-picker/test/index.tsx +4 -1
  175. package/src/color-picker/component.tsx +22 -11
  176. package/src/color-picker/styles.ts +1 -15
  177. package/src/combobox-control/index.tsx +33 -41
  178. package/src/composite/legacy/test/index.tsx +18 -2
  179. package/src/custom-select-control/README.md +0 -10
  180. package/src/custom-select-control/index.js +3 -22
  181. package/src/custom-select-control/stories/index.story.js +0 -1
  182. package/src/custom-select-control/test/index.js +17 -17
  183. package/src/custom-select-control-v2/README.md +97 -7
  184. package/src/custom-select-control-v2/custom-select-item.tsx +29 -0
  185. package/src/custom-select-control-v2/custom-select.tsx +122 -0
  186. package/src/custom-select-control-v2/default-component/index.tsx +24 -0
  187. package/src/custom-select-control-v2/index.tsx +2 -102
  188. package/src/custom-select-control-v2/legacy-adapter.tsx +25 -0
  189. package/src/custom-select-control-v2/legacy-component/index.tsx +133 -0
  190. package/src/custom-select-control-v2/stories/{index.story.tsx → default.story.tsx} +27 -33
  191. package/src/custom-select-control-v2/stories/legacy.story.tsx +88 -0
  192. package/src/custom-select-control-v2/styles.ts +82 -38
  193. package/src/custom-select-control-v2/test/index.tsx +808 -148
  194. package/src/custom-select-control-v2/types.ts +148 -20
  195. package/src/dropdown-menu-v2/stories/index.story.tsx +1 -0
  196. package/src/dropdown-menu-v2/test/index.tsx +4 -1
  197. package/src/font-size-picker/font-size-picker-select.tsx +0 -1
  198. package/src/form-token-field/index.tsx +3 -10
  199. package/src/form-token-field/suggestions-list.tsx +5 -17
  200. package/src/mobile/keyboard-aware-flat-list/use-scroll-to-section.native.js +1 -1
  201. package/src/modal/index.tsx +2 -12
  202. package/src/modal/style.scss +1 -0
  203. package/src/progress-bar/stories/index.story.tsx +1 -0
  204. package/src/progress-bar/styles.ts +2 -2
  205. package/src/tab-panel/test/index.tsx +8 -1
  206. package/src/tabs/stories/index.story.tsx +1 -0
  207. package/src/tabs/test/index.tsx +36 -6
  208. package/src/theme/stories/index.story.tsx +1 -0
  209. package/src/toggle-group-control/test/index.tsx +4 -0
  210. package/src/toolbar/toolbar-group/style.scss +1 -0
  211. package/src/tooltip/test/index.tsx +5 -0
  212. package/src/utils/with-ignore-ime-events.ts +32 -0
  213. package/tsconfig.json +0 -1
  214. package/tsconfig.tsbuildinfo +1 -1
  215. package/build/custom-select-control/styles.js +0 -27
  216. package/build/custom-select-control/styles.js.map +0 -1
  217. package/build-module/custom-select-control/styles.js +0 -18
  218. package/build-module/custom-select-control/styles.js.map +0 -1
  219. package/build-types/custom-select-control/styles.d.ts +0 -11
  220. package/build-types/custom-select-control/styles.d.ts.map +0 -1
  221. package/build-types/custom-select-control-v2/stories/index.story.d.ts +0 -20
  222. package/build-types/custom-select-control-v2/stories/index.story.d.ts.map +0 -1
  223. package/src/custom-select-control/styles.ts +0 -28
@@ -2,7 +2,7 @@
2
2
  * External dependencies
3
3
  */
4
4
  import { render, screen } from '@testing-library/react';
5
- import userEvent from '@testing-library/user-event';
5
+ import { click, press, sleep, type, waitFor } from '@ariakit/test';
6
6
 
7
7
  /**
8
8
  * WordPress dependencies
@@ -12,208 +12,868 @@ import { useState } from '@wordpress/element';
12
12
  /**
13
13
  * Internal dependencies
14
14
  */
15
- import { CustomSelect, CustomSelectItem } from '..';
16
- import type { CustomSelectProps } from '../types';
15
+ import { CustomSelect as UncontrolledCustomSelect, CustomSelectItem } from '..';
16
+ import type { CustomSelectProps, LegacyCustomSelectProps } from '../types';
17
+
18
+ const customClass = 'amber-skies';
19
+
20
+ const legacyProps = {
21
+ label: 'label!',
22
+ options: [
23
+ {
24
+ key: 'flower1',
25
+ name: 'violets',
26
+ },
27
+ {
28
+ key: 'flower2',
29
+ name: 'crimson clover',
30
+ className: customClass,
31
+ },
32
+ {
33
+ key: 'flower3',
34
+ name: 'poppy',
35
+ },
36
+ {
37
+ key: 'color1',
38
+ name: 'amber',
39
+ className: customClass,
40
+ },
41
+ {
42
+ key: 'color2',
43
+ name: 'aquamarine',
44
+ style: {
45
+ backgroundColor: 'rgb(127, 255, 212)',
46
+ rotate: '13deg',
47
+ },
48
+ },
49
+ ],
50
+ };
17
51
 
18
- const ControlledCustomSelect = ( props: CustomSelectProps ) => {
19
- const [ value, setValue ] = useState< string | string[] >();
52
+ const LegacyControlledCustomSelect = ( {
53
+ options,
54
+ onChange,
55
+ ...restProps
56
+ }: LegacyCustomSelectProps ) => {
57
+ const [ value, setValue ] = useState( options[ 0 ] );
20
58
  return (
21
- <CustomSelect
22
- { ...props }
23
- onChange={ ( nextValue ) => {
24
- setValue( nextValue );
25
- props.onChange?.( nextValue );
59
+ <UncontrolledCustomSelect
60
+ { ...restProps }
61
+ options={ options }
62
+ onChange={ ( args: any ) => {
63
+ onChange?.( args );
64
+ setValue( args.selectedItem );
26
65
  } }
27
- value={ value }
66
+ value={ options.find(
67
+ ( option: any ) => option.key === value.key
68
+ ) }
28
69
  />
29
70
  );
30
71
  };
31
72
 
32
- describe.each( [
33
- [ 'uncontrolled', CustomSelect ],
34
- [ 'controlled', ControlledCustomSelect ],
35
- ] )( 'CustomSelect %s', ( ...modeAndComponent ) => {
36
- const [ , Component ] = modeAndComponent;
73
+ describe( 'With Legacy Props', () => {
74
+ describe.each( [
75
+ [ 'Uncontrolled', UncontrolledCustomSelect ],
76
+ [ 'Controlled', LegacyControlledCustomSelect ],
77
+ ] )( '%s', ( ...modeAndComponent ) => {
78
+ const [ , Component ] = modeAndComponent;
37
79
 
38
- describe( 'Multiple selection', () => {
39
- it( 'Should be able to select multiple items when provided an array', async () => {
40
- const user = userEvent.setup();
41
- const onChangeMock = jest.fn();
80
+ it( 'Should replace the initial selection when a new item is selected', async () => {
81
+ render( <Component { ...legacyProps } /> );
42
82
 
43
- // initial selection as defaultValue
44
- const defaultValues = [
45
- 'incandescent glow',
46
- 'ultraviolet morning light',
47
- ];
83
+ const currentSelectedItem = screen.getByRole( 'combobox', {
84
+ expanded: false,
85
+ } );
48
86
 
49
- render(
50
- <Component
51
- defaultValue={ defaultValues }
52
- onChange={ onChangeMock }
53
- label="Multi-select"
54
- >
55
- { [
56
- 'aurora borealis green',
57
- 'flamingo pink sunrise',
58
- 'incandescent glow',
59
- 'rose blush',
60
- 'ultraviolet morning light',
61
- ].map( ( item ) => (
62
- <CustomSelectItem key={ item } value={ item }>
63
- { item }
64
- </CustomSelectItem>
65
- ) ) }
66
- </Component>
87
+ await click( currentSelectedItem );
88
+
89
+ await click(
90
+ screen.getByRole( 'option', {
91
+ name: 'crimson clover',
92
+ } )
67
93
  );
68
94
 
95
+ expect( currentSelectedItem ).toHaveTextContent( 'crimson clover' );
96
+
97
+ await click( currentSelectedItem );
98
+
99
+ await click(
100
+ screen.getByRole( 'option', {
101
+ name: 'poppy',
102
+ } )
103
+ );
104
+
105
+ expect( currentSelectedItem ).toHaveTextContent( 'poppy' );
106
+ } );
107
+
108
+ it( 'Should keep current selection if dropdown is closed without changing selection', async () => {
109
+ render( <Component { ...legacyProps } /> );
110
+
69
111
  const currentSelectedItem = screen.getByRole( 'combobox', {
70
112
  expanded: false,
71
113
  } );
72
114
 
73
- // ensure more than one item is selected due to defaultValues
115
+ await sleep();
116
+ await press.Tab();
117
+ await press.Enter();
118
+ expect(
119
+ screen.getByRole( 'listbox', {
120
+ name: 'label!',
121
+ } )
122
+ ).toBeVisible();
123
+
124
+ await press.Escape();
125
+ expect(
126
+ screen.queryByRole( 'listbox', {
127
+ name: 'label!',
128
+ } )
129
+ ).not.toBeInTheDocument();
130
+
74
131
  expect( currentSelectedItem ).toHaveTextContent(
75
- `${ defaultValues.length } items selected`
132
+ legacyProps.options[ 0 ].name
133
+ );
134
+ } );
135
+
136
+ it( 'Should apply class only to options that have a className defined', async () => {
137
+ render( <Component { ...legacyProps } /> );
138
+
139
+ await click(
140
+ screen.getByRole( 'combobox', {
141
+ expanded: false,
142
+ } )
76
143
  );
77
144
 
78
- await user.click( currentSelectedItem );
145
+ // return an array of items _with_ a className added
146
+ const itemsWithClass = legacyProps.options.filter(
147
+ ( option ) => option.className !== undefined
148
+ );
79
149
 
80
- expect( screen.getByRole( 'listbox' ) ).toHaveAttribute(
81
- 'aria-multiselectable'
150
+ // assert against filtered array
151
+ itemsWithClass.map( ( { name } ) =>
152
+ expect( screen.getByRole( 'option', { name } ) ).toHaveClass(
153
+ customClass
154
+ )
82
155
  );
83
156
 
84
- // ensure defaultValues are selected in list of items
85
- defaultValues.forEach( ( value ) =>
157
+ // return an array of items _without_ a className added
158
+ const itemsWithoutClass = legacyProps.options.filter(
159
+ ( option ) => option.className === undefined
160
+ );
161
+
162
+ // assert against filtered array
163
+ itemsWithoutClass.map( ( { name } ) =>
86
164
  expect(
87
- screen.getByRole( 'option', {
88
- name: value,
89
- selected: true,
90
- } )
91
- ).toBeVisible()
165
+ screen.getByRole( 'option', { name } )
166
+ ).not.toHaveClass( customClass )
92
167
  );
168
+ } );
93
169
 
94
- // name of next selection
95
- const nextSelectionName = 'rose blush';
170
+ it( 'Should apply styles only to options that have styles defined', async () => {
171
+ const customStyles =
172
+ 'background-color: rgb(127, 255, 212); rotate: 13deg;';
96
173
 
97
- // element for next selection
98
- const nextSelection = screen.getByRole( 'option', {
99
- name: nextSelectionName,
100
- } );
174
+ render( <Component { ...legacyProps } /> );
101
175
 
102
- // click next selection to add another item to current selection
103
- await user.click( nextSelection );
176
+ await click(
177
+ screen.getByRole( 'combobox', {
178
+ expanded: false,
179
+ } )
180
+ );
104
181
 
105
- // updated array containing defaultValues + the item just selected
106
- const updatedSelection = defaultValues.concat( nextSelectionName );
182
+ // return an array of items _with_ styles added
183
+ const styledItems = legacyProps.options.filter(
184
+ ( option ) => option.style !== undefined
185
+ );
107
186
 
108
- expect( onChangeMock ).toHaveBeenCalledWith( updatedSelection );
187
+ // assert against filtered array
188
+ styledItems.map( ( { name } ) =>
189
+ expect( screen.getByRole( 'option', { name } ) ).toHaveStyle(
190
+ customStyles
191
+ )
192
+ );
109
193
 
110
- expect( nextSelection ).toHaveAttribute( 'aria-selected' );
194
+ // return an array of items _without_ styles added
195
+ const unstyledItems = legacyProps.options.filter(
196
+ ( option ) => option.style === undefined
197
+ );
111
198
 
112
- // expect increased array length for current selection
113
- expect( currentSelectedItem ).toHaveTextContent(
114
- `${ updatedSelection.length } items selected`
199
+ // assert against filtered array
200
+ unstyledItems.map( ( { name } ) =>
201
+ expect(
202
+ screen.getByRole( 'option', { name } )
203
+ ).not.toHaveStyle( customStyles )
204
+ );
205
+ } );
206
+
207
+ it( 'does not show selected hint by default', async () => {
208
+ render(
209
+ <Component
210
+ { ...legacyProps }
211
+ label="Custom select"
212
+ options={ [
213
+ {
214
+ key: 'one',
215
+ name: 'One',
216
+ __experimentalHint: 'Hint',
217
+ },
218
+ ] }
219
+ />
220
+ );
221
+ await waitFor( () =>
222
+ expect(
223
+ screen.getByRole( 'combobox', { name: 'Custom select' } )
224
+ ).not.toHaveTextContent( 'Hint' )
115
225
  );
116
226
  } );
117
227
 
118
- it( 'Should be able to deselect items when provided an array', async () => {
119
- const user = userEvent.setup();
228
+ it( 'shows selected hint when __experimentalShowSelectedHint is set', async () => {
229
+ render(
230
+ <Component
231
+ { ...legacyProps }
232
+ label="Custom select"
233
+ options={ [
234
+ {
235
+ key: 'one',
236
+ name: 'One',
237
+ __experimentalHint: 'Hint',
238
+ },
239
+ ] }
240
+ __experimentalShowSelectedHint
241
+ />
242
+ );
120
243
 
121
- // initial selection as defaultValue
122
- const defaultValues = [
123
- 'aurora borealis green',
124
- 'incandescent glow',
125
- 'key lime green',
126
- 'rose blush',
127
- 'ultraviolet morning light',
128
- ];
244
+ await waitFor( () =>
245
+ expect(
246
+ screen.getByRole( 'combobox', {
247
+ expanded: false,
248
+ } )
249
+ ).toHaveTextContent( /hint/i )
250
+ );
251
+ } );
129
252
 
253
+ it( 'shows selected hint in list of options when added', async () => {
130
254
  render(
131
- <Component defaultValue={ defaultValues } label="Multi-select">
132
- { defaultValues.map( ( item ) => (
133
- <CustomSelectItem key={ item } value={ item }>
134
- { item }
135
- </CustomSelectItem>
136
- ) ) }
137
- </Component>
255
+ <Component
256
+ { ...legacyProps }
257
+ label="Custom select"
258
+ options={ [
259
+ {
260
+ key: 'one',
261
+ name: 'One',
262
+ __experimentalHint: 'Hint',
263
+ },
264
+ ] }
265
+ __experimentalShowSelectedHint
266
+ />
267
+ );
268
+
269
+ await click(
270
+ screen.getByRole( 'combobox', { name: 'Custom select' } )
138
271
  );
139
272
 
273
+ expect(
274
+ screen.getByRole( 'option', { name: /hint/i } )
275
+ ).toBeVisible();
276
+ } );
277
+
278
+ it( 'Should return object onChange', async () => {
279
+ const mockOnChange = jest.fn();
280
+
281
+ render(
282
+ <Component { ...legacyProps } onChange={ mockOnChange } />
283
+ );
284
+
285
+ await click(
286
+ screen.getByRole( 'combobox', {
287
+ expanded: false,
288
+ } )
289
+ );
290
+
291
+ expect( mockOnChange ).toHaveBeenNthCalledWith(
292
+ 1,
293
+ expect.objectContaining( {
294
+ inputValue: '',
295
+ isOpen: false,
296
+ selectedItem: { key: 'violets', name: 'violets' },
297
+ type: '',
298
+ } )
299
+ );
300
+
301
+ await click(
302
+ screen.getByRole( 'option', {
303
+ name: 'aquamarine',
304
+ } )
305
+ );
306
+
307
+ expect( mockOnChange ).toHaveBeenNthCalledWith(
308
+ 2,
309
+ expect.objectContaining( {
310
+ inputValue: '',
311
+ isOpen: false,
312
+ selectedItem: expect.objectContaining( {
313
+ name: 'aquamarine',
314
+ } ),
315
+ type: '',
316
+ } )
317
+ );
318
+ } );
319
+
320
+ it( 'Should return selectedItem object when specified onChange', async () => {
321
+ const mockOnChange = jest.fn(
322
+ ( { selectedItem } ) => selectedItem.key
323
+ );
324
+
325
+ render(
326
+ <Component { ...legacyProps } onChange={ mockOnChange } />
327
+ );
328
+
329
+ await sleep();
330
+ await press.Tab();
331
+ expect(
332
+ screen.getByRole( 'combobox', {
333
+ expanded: false,
334
+ } )
335
+ ).toHaveFocus();
336
+
337
+ await type( 'p' );
338
+ await press.Enter();
339
+
340
+ expect( mockOnChange ).toHaveReturnedWith( 'poppy' );
341
+ } );
342
+
343
+ describe( 'Keyboard behavior and accessibility', () => {
344
+ it( 'Should be able to change selection using keyboard', async () => {
345
+ render( <Component { ...legacyProps } /> );
346
+
347
+ const currentSelectedItem = screen.getByRole( 'combobox', {
348
+ expanded: false,
349
+ } );
350
+
351
+ await sleep();
352
+ await press.Tab();
353
+ expect( currentSelectedItem ).toHaveFocus();
354
+
355
+ await press.Enter();
356
+ expect(
357
+ screen.getByRole( 'listbox', {
358
+ name: 'label!',
359
+ } )
360
+ ).toHaveFocus();
361
+
362
+ await press.ArrowDown();
363
+ await press.Enter();
364
+
365
+ expect( currentSelectedItem ).toHaveTextContent(
366
+ 'crimson clover'
367
+ );
368
+ } );
369
+
370
+ it( 'Should be able to type characters to select matching options', async () => {
371
+ render( <Component { ...legacyProps } /> );
372
+
373
+ const currentSelectedItem = screen.getByRole( 'combobox', {
374
+ expanded: false,
375
+ } );
376
+
377
+ await sleep();
378
+ await press.Tab();
379
+ await press.Enter();
380
+ expect(
381
+ screen.getByRole( 'listbox', {
382
+ name: 'label!',
383
+ } )
384
+ ).toHaveFocus();
385
+
386
+ await type( 'a' );
387
+ await press.Enter();
388
+ expect( currentSelectedItem ).toHaveTextContent( 'amber' );
389
+ } );
390
+
391
+ it( 'Can change selection with a focused input and closed dropdown if typed characters match an option', async () => {
392
+ render( <Component { ...legacyProps } /> );
393
+
394
+ const currentSelectedItem = screen.getByRole( 'combobox', {
395
+ expanded: false,
396
+ } );
397
+
398
+ await sleep();
399
+ await press.Tab();
400
+ expect( currentSelectedItem ).toHaveFocus();
401
+
402
+ await type( 'aq' );
403
+
404
+ expect(
405
+ screen.queryByRole( 'listbox', {
406
+ name: 'label!',
407
+ hidden: true,
408
+ } )
409
+ ).not.toBeInTheDocument();
410
+
411
+ await press.Enter();
412
+ expect( currentSelectedItem ).toHaveTextContent( 'aquamarine' );
413
+ } );
414
+
415
+ it( 'Should have correct aria-selected value for selections', async () => {
416
+ render( <Component { ...legacyProps } /> );
417
+
418
+ const currentSelectedItem = screen.getByRole( 'combobox', {
419
+ expanded: false,
420
+ } );
421
+
422
+ await click( currentSelectedItem );
423
+
424
+ // get all items except for first option
425
+ const unselectedItems = legacyProps.options.filter(
426
+ ( { name } ) => name !== legacyProps.options[ 0 ].name
427
+ );
428
+
429
+ // assert that all other items have aria-selected="false"
430
+ unselectedItems.map( ( { name } ) =>
431
+ expect(
432
+ screen.getByRole( 'option', { name, selected: false } )
433
+ ).toBeVisible()
434
+ );
435
+
436
+ // assert that first item has aria-selected="true"
437
+ expect(
438
+ screen.getByRole( 'option', {
439
+ name: legacyProps.options[ 0 ].name,
440
+ selected: true,
441
+ } )
442
+ ).toBeVisible();
443
+
444
+ // change the current selection
445
+ await click( screen.getByRole( 'option', { name: 'poppy' } ) );
446
+
447
+ // click combobox to mount listbox with options again
448
+ await click( currentSelectedItem );
449
+
450
+ // check that first item is has aria-selected="false" after new selection
451
+ expect(
452
+ screen.getByRole( 'option', {
453
+ name: legacyProps.options[ 0 ].name,
454
+ selected: false,
455
+ } )
456
+ ).toBeVisible();
457
+
458
+ // check that new selected item now has aria-selected="true"
459
+ expect(
460
+ screen.getByRole( 'option', {
461
+ name: 'poppy',
462
+ selected: true,
463
+ } )
464
+ ).toBeVisible();
465
+ } );
466
+ } );
467
+ } );
468
+ } );
469
+
470
+ describe( 'static typing', () => {
471
+ <>
472
+ { /* @ts-expect-error - when `options` prop is passed, `onChange` should have legacy signature */ }
473
+ <UncontrolledCustomSelect
474
+ label="foo"
475
+ options={ [] }
476
+ onChange={ ( _: string | string[] ) => undefined }
477
+ />
478
+ <UncontrolledCustomSelect
479
+ label="foo"
480
+ options={ [] }
481
+ onChange={ ( _: { selectedItem: unknown } ) => undefined }
482
+ />
483
+ <UncontrolledCustomSelect
484
+ label="foo"
485
+ onChange={ ( _: string | string[] ) => undefined }
486
+ >
487
+ foobar
488
+ </UncontrolledCustomSelect>
489
+ { /* @ts-expect-error - when `children` are passed, `onChange` should have new default signature */ }
490
+ <UncontrolledCustomSelect
491
+ label="foo"
492
+ onChange={ ( _: { selectedItem: unknown } ) => undefined }
493
+ >
494
+ foobar
495
+ </UncontrolledCustomSelect>
496
+ </>;
497
+ } );
498
+
499
+ const defaultProps = {
500
+ label: 'label!',
501
+ children: legacyProps.options.map( ( { name, key } ) => (
502
+ <CustomSelectItem value={ name } key={ key } />
503
+ ) ),
504
+ };
505
+
506
+ const ControlledCustomSelect = ( props: CustomSelectProps ) => {
507
+ const [ value, setValue ] = useState< string | string[] >();
508
+ return (
509
+ <UncontrolledCustomSelect
510
+ { ...props }
511
+ onChange={ ( nextValue: string | string[] ) => {
512
+ setValue( nextValue );
513
+ props.onChange?.( nextValue );
514
+ } }
515
+ value={ value }
516
+ />
517
+ );
518
+ };
519
+
520
+ describe( 'With Default Props', () => {
521
+ describe.each( [
522
+ [ 'Uncontrolled', UncontrolledCustomSelect ],
523
+ [ 'Controlled', ControlledCustomSelect ],
524
+ ] )( '%s', ( ...modeAndComponent ) => {
525
+ const [ , Component ] = modeAndComponent;
526
+
527
+ it( 'Should replace the initial selection when a new item is selected', async () => {
528
+ render( <Component { ...defaultProps } /> );
529
+
140
530
  const currentSelectedItem = screen.getByRole( 'combobox', {
141
531
  expanded: false,
142
532
  } );
143
533
 
144
- await user.click( currentSelectedItem );
145
-
146
- // Array containing items to deselect
147
- const nextSelection = [
148
- 'aurora borealis green',
149
- 'rose blush',
150
- 'incandescent glow',
151
- ];
152
-
153
- // Deselect some items by clicking them to ensure that changes
154
- // are reflected correctly
155
- await Promise.all(
156
- nextSelection.map( async ( value ) => {
157
- await user.click(
158
- screen.getByRole( 'option', { name: value } )
159
- );
160
- expect(
161
- screen.getByRole( 'option', {
162
- name: value,
163
- selected: false,
164
- } )
165
- ).toBeVisible();
534
+ await click( currentSelectedItem );
535
+
536
+ await click(
537
+ screen.getByRole( 'option', {
538
+ name: 'crimson clover',
539
+ } )
540
+ );
541
+
542
+ expect( currentSelectedItem ).toHaveTextContent( 'crimson clover' );
543
+
544
+ await click( currentSelectedItem );
545
+
546
+ await click(
547
+ screen.getByRole( 'option', {
548
+ name: 'poppy',
166
549
  } )
167
550
  );
168
551
 
169
- // expect different array length from defaultValues due to deselecting items
552
+ expect( currentSelectedItem ).toHaveTextContent( 'poppy' );
553
+ } );
554
+
555
+ it( 'Should keep current selection if dropdown is closed without changing selection', async () => {
556
+ render( <Component { ...defaultProps } /> );
557
+
558
+ const currentSelectedItem = screen.getByRole( 'combobox', {
559
+ expanded: false,
560
+ } );
561
+
562
+ await sleep();
563
+ await press.Tab();
564
+ await press.Enter();
565
+ expect(
566
+ screen.getByRole( 'listbox', {
567
+ name: 'label!',
568
+ } )
569
+ ).toBeVisible();
570
+
571
+ await press.Escape();
572
+ expect(
573
+ screen.queryByRole( 'listbox', {
574
+ name: 'label!',
575
+ } )
576
+ ).not.toBeInTheDocument();
577
+
170
578
  expect( currentSelectedItem ).toHaveTextContent(
171
- `${
172
- defaultValues.length - nextSelection.length
173
- } items selected`
579
+ legacyProps.options[ 0 ].name
174
580
  );
175
581
  } );
176
- } );
177
582
 
178
- it( 'Should allow rendering a custom value when using `renderSelectedValue`', async () => {
179
- const user = userEvent.setup();
180
-
181
- const renderValue = ( value: string | string[] ) => {
182
- return <img src={ `${ value }.jpg` } alt={ value as string } />;
183
- };
184
-
185
- render(
186
- <Component label="Rendered" renderSelectedValue={ renderValue }>
187
- <CustomSelectItem value="april-29">
188
- { renderValue( 'april-29' ) }
189
- </CustomSelectItem>
190
- <CustomSelectItem value="july-9">
191
- { renderValue( 'july-9' ) }
192
- </CustomSelectItem>
193
- </Component>
194
- );
195
-
196
- const currentSelectedItem = screen.getByRole( 'combobox', {
197
- expanded: false,
583
+ describe( 'Keyboard behavior and accessibility', () => {
584
+ it( 'Should be able to change selection using keyboard', async () => {
585
+ render( <Component { ...defaultProps } /> );
586
+
587
+ const currentSelectedItem = screen.getByRole( 'combobox', {
588
+ expanded: false,
589
+ } );
590
+
591
+ await sleep();
592
+ await press.Tab();
593
+ expect( currentSelectedItem ).toHaveFocus();
594
+
595
+ await press.Enter();
596
+ expect(
597
+ screen.getByRole( 'listbox', {
598
+ name: 'label!',
599
+ } )
600
+ ).toHaveFocus();
601
+
602
+ await press.ArrowDown();
603
+ await press.Enter();
604
+
605
+ expect( currentSelectedItem ).toHaveTextContent(
606
+ 'crimson clover'
607
+ );
608
+ } );
609
+
610
+ it( 'Should be able to type characters to select matching options', async () => {
611
+ render( <Component { ...defaultProps } /> );
612
+
613
+ const currentSelectedItem = screen.getByRole( 'combobox', {
614
+ expanded: false,
615
+ } );
616
+
617
+ await sleep();
618
+ await press.Tab();
619
+ await press.Enter();
620
+ expect(
621
+ screen.getByRole( 'listbox', {
622
+ name: 'label!',
623
+ } )
624
+ ).toHaveFocus();
625
+
626
+ await type( 'a' );
627
+ await press.Enter();
628
+ expect( currentSelectedItem ).toHaveTextContent( 'amber' );
629
+ } );
630
+
631
+ it( 'Can change selection with a focused input and closed dropdown if typed characters match an option', async () => {
632
+ render( <Component { ...defaultProps } /> );
633
+
634
+ const currentSelectedItem = screen.getByRole( 'combobox', {
635
+ expanded: false,
636
+ } );
637
+
638
+ await sleep();
639
+ await press.Tab();
640
+ expect( currentSelectedItem ).toHaveFocus();
641
+
642
+ await type( 'aq' );
643
+
644
+ expect(
645
+ screen.queryByRole( 'listbox', {
646
+ name: 'label!',
647
+ hidden: true,
648
+ } )
649
+ ).not.toBeInTheDocument();
650
+
651
+ await press.Enter();
652
+ expect( currentSelectedItem ).toHaveTextContent( 'aquamarine' );
653
+ } );
654
+
655
+ it( 'Should have correct aria-selected value for selections', async () => {
656
+ render( <Component { ...defaultProps } /> );
657
+
658
+ const currentSelectedItem = screen.getByRole( 'combobox', {
659
+ expanded: false,
660
+ } );
661
+
662
+ await click( currentSelectedItem );
663
+
664
+ // assert that first item has aria-selected="true"
665
+ expect(
666
+ screen.getByRole( 'option', {
667
+ name: 'violets',
668
+ selected: true,
669
+ } )
670
+ ).toBeVisible();
671
+
672
+ // change the current selection
673
+ await click( screen.getByRole( 'option', { name: 'poppy' } ) );
674
+
675
+ // click combobox to mount listbox with options again
676
+ await click( currentSelectedItem );
677
+
678
+ // check that first item is has aria-selected="false" after new selection
679
+ expect(
680
+ screen.getByRole( 'option', {
681
+ name: 'violets',
682
+ selected: false,
683
+ } )
684
+ ).toBeVisible();
685
+
686
+ // check that new selected item now has aria-selected="true"
687
+ expect(
688
+ screen.getByRole( 'option', {
689
+ name: 'poppy',
690
+ selected: true,
691
+ } )
692
+ ).toBeVisible();
693
+ } );
694
+ } );
695
+
696
+ describe( 'Multiple selection', () => {
697
+ it( 'Should be able to select multiple items when provided an array', async () => {
698
+ const onChangeMock = jest.fn();
699
+
700
+ // initial selection as defaultValue
701
+ const defaultValues = [
702
+ 'incandescent glow',
703
+ 'ultraviolet morning light',
704
+ ];
705
+
706
+ render(
707
+ <Component
708
+ defaultValue={ defaultValues }
709
+ onChange={ onChangeMock }
710
+ label="Multi-select"
711
+ >
712
+ { [
713
+ 'aurora borealis green',
714
+ 'flamingo pink sunrise',
715
+ 'incandescent glow',
716
+ 'rose blush',
717
+ 'ultraviolet morning light',
718
+ ].map( ( item ) => (
719
+ <CustomSelectItem key={ item } value={ item }>
720
+ { item }
721
+ </CustomSelectItem>
722
+ ) ) }
723
+ </Component>
724
+ );
725
+
726
+ const currentSelectedItem = screen.getByRole( 'combobox', {
727
+ expanded: false,
728
+ } );
729
+
730
+ // ensure more than one item is selected due to defaultValues
731
+ expect( currentSelectedItem ).toHaveTextContent(
732
+ `${ defaultValues.length } items selected`
733
+ );
734
+
735
+ await click( currentSelectedItem );
736
+
737
+ expect( screen.getByRole( 'listbox' ) ).toHaveAttribute(
738
+ 'aria-multiselectable'
739
+ );
740
+
741
+ // ensure defaultValues are selected in list of items
742
+ defaultValues.forEach( ( value ) =>
743
+ expect(
744
+ screen.getByRole( 'option', {
745
+ name: value,
746
+ selected: true,
747
+ } )
748
+ ).toBeVisible()
749
+ );
750
+
751
+ // name of next selection
752
+ const nextSelectionName = 'rose blush';
753
+
754
+ // element for next selection
755
+ const nextSelection = screen.getByRole( 'option', {
756
+ name: nextSelectionName,
757
+ } );
758
+
759
+ // click next selection to add another item to current selection
760
+ await click( nextSelection );
761
+
762
+ // updated array containing defaultValues + the item just selected
763
+ const updatedSelection =
764
+ defaultValues.concat( nextSelectionName );
765
+
766
+ expect( onChangeMock ).toHaveBeenCalledWith( updatedSelection );
767
+
768
+ expect( nextSelection ).toHaveAttribute( 'aria-selected' );
769
+
770
+ // expect increased array length for current selection
771
+ expect( currentSelectedItem ).toHaveTextContent(
772
+ `${ updatedSelection.length } items selected`
773
+ );
774
+ } );
775
+
776
+ it( 'Should be able to deselect items when provided an array', async () => {
777
+ // initial selection as defaultValue
778
+ const defaultValues = [
779
+ 'aurora borealis green',
780
+ 'incandescent glow',
781
+ 'key lime green',
782
+ 'rose blush',
783
+ 'ultraviolet morning light',
784
+ ];
785
+
786
+ render(
787
+ <Component
788
+ defaultValue={ defaultValues }
789
+ label="Multi-select"
790
+ >
791
+ { defaultValues.map( ( item ) => (
792
+ <CustomSelectItem key={ item } value={ item }>
793
+ { item }
794
+ </CustomSelectItem>
795
+ ) ) }
796
+ </Component>
797
+ );
798
+
799
+ const currentSelectedItem = screen.getByRole( 'combobox', {
800
+ expanded: false,
801
+ } );
802
+
803
+ await click( currentSelectedItem );
804
+
805
+ // Array containing items to deselect
806
+ const nextSelection = [
807
+ 'aurora borealis green',
808
+ 'rose blush',
809
+ 'incandescent glow',
810
+ ];
811
+
812
+ // Deselect some items by clicking them to ensure that changes
813
+ // are reflected correctly
814
+ await Promise.all(
815
+ nextSelection.map( async ( value ) => {
816
+ await click(
817
+ screen.getByRole( 'option', { name: value } )
818
+ );
819
+ expect(
820
+ screen.getByRole( 'option', {
821
+ name: value,
822
+ selected: false,
823
+ } )
824
+ ).toBeVisible();
825
+ } )
826
+ );
827
+
828
+ // expect different array length from defaultValues due to deselecting items
829
+ expect( currentSelectedItem ).toHaveTextContent(
830
+ `${
831
+ defaultValues.length - nextSelection.length
832
+ } items selected`
833
+ );
834
+ } );
198
835
  } );
199
836
 
200
- expect( currentSelectedItem ).toBeVisible();
837
+ it( 'Should allow rendering a custom value when using `renderSelectedValue`', async () => {
838
+ const renderValue = ( value: string | string[] ) => {
839
+ return <img src={ `${ value }.jpg` } alt={ value as string } />;
840
+ };
841
+
842
+ render(
843
+ <Component label="Rendered" renderSelectedValue={ renderValue }>
844
+ <CustomSelectItem value="april-29">
845
+ { renderValue( 'april-29' ) }
846
+ </CustomSelectItem>
847
+ <CustomSelectItem value="july-9">
848
+ { renderValue( 'july-9' ) }
849
+ </CustomSelectItem>
850
+ </Component>
851
+ );
852
+
853
+ const currentSelectedItem = screen.getByRole( 'combobox', {
854
+ expanded: false,
855
+ } );
856
+
857
+ expect( currentSelectedItem ).toBeVisible();
201
858
 
202
- // expect that the initial selection renders an image
203
- expect( currentSelectedItem ).toContainElement(
204
- screen.getByRole( 'img', { name: 'april-29' } )
205
- );
859
+ // expect that the initial selection renders an image
860
+ expect( currentSelectedItem ).toContainElement(
861
+ screen.getByRole( 'img', { name: 'april-29' } )
862
+ );
206
863
 
207
- expect(
208
- screen.queryByRole( 'img', { name: 'july-9' } )
209
- ).not.toBeInTheDocument();
864
+ expect(
865
+ screen.queryByRole( 'img', { name: 'july-9' } )
866
+ ).not.toBeInTheDocument();
210
867
 
211
- await user.click( currentSelectedItem );
868
+ await click( currentSelectedItem );
212
869
 
213
- // expect that the other image is only visible after opening popover with options
214
- expect( screen.getByRole( 'img', { name: 'july-9' } ) ).toBeVisible();
215
- expect(
216
- screen.getByRole( 'option', { name: 'july-9' } )
217
- ).toBeVisible();
870
+ // expect that the other image is only visible after opening popover with options
871
+ expect(
872
+ screen.getByRole( 'img', { name: 'july-9' } )
873
+ ).toBeVisible();
874
+ expect(
875
+ screen.getByRole( 'option', { name: 'july-9' } )
876
+ ).toBeVisible();
877
+ } );
218
878
  } );
219
879
  } );