@wordpress/components 26.0.2 → 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.
- package/.stylelintrc.js +2 -2
- package/CHANGELOG.md +23 -0
- package/CONTRIBUTING.md +72 -0
- package/build/autocomplete/index.js +3 -8
- package/build/autocomplete/index.js.map +1 -1
- package/build/color-picker/component.js +10 -1
- package/build/color-picker/component.js.map +1 -1
- package/build/color-picker/styles.js +8 -9
- package/build/color-picker/styles.js.map +1 -1
- package/build/combobox-control/index.js +4 -9
- package/build/combobox-control/index.js.map +1 -1
- package/build/custom-select-control/index.js +2 -15
- package/build/custom-select-control/index.js.map +1 -1
- package/build/custom-select-control-v2/custom-select-item.js +32 -0
- package/build/custom-select-control-v2/custom-select-item.js.map +1 -0
- package/build/custom-select-control-v2/custom-select.js +91 -0
- package/build/custom-select-control-v2/custom-select.js.map +1 -0
- package/build/custom-select-control-v2/default-component/index.js +41 -0
- package/build/custom-select-control-v2/default-component/index.js.map +1 -0
- package/build/custom-select-control-v2/index.js +13 -82
- package/build/custom-select-control-v2/index.js.map +1 -1
- package/build/custom-select-control-v2/legacy-adapter.js +29 -0
- package/build/custom-select-control-v2/legacy-adapter.js.map +1 -0
- package/build/custom-select-control-v2/legacy-component/index.js +123 -0
- package/build/custom-select-control-v2/legacy-component/index.js.map +1 -0
- package/build/custom-select-control-v2/styles.js +73 -50
- package/build/custom-select-control-v2/styles.js.map +1 -1
- package/build/custom-select-control-v2/types.js.map +1 -1
- package/build/font-size-picker/font-size-picker-select.js +0 -1
- package/build/font-size-picker/font-size-picker-select.js.map +1 -1
- package/build/form-token-field/index.js +3 -8
- package/build/form-token-field/index.js.map +1 -1
- package/build/form-token-field/suggestions-list.js +5 -12
- package/build/form-token-field/suggestions-list.js.map +1 -1
- package/build/mobile/keyboard-aware-flat-list/use-scroll-to-section.native.js +1 -1
- package/build/mobile/keyboard-aware-flat-list/use-scroll-to-section.native.js.map +1 -1
- package/build/modal/index.js +2 -10
- package/build/modal/index.js.map +1 -1
- package/build/progress-bar/styles.js +5 -5
- package/build/progress-bar/styles.js.map +1 -1
- package/build/utils/with-ignore-ime-events.js +34 -0
- package/build/utils/with-ignore-ime-events.js.map +1 -0
- package/build-module/autocomplete/index.js +3 -8
- package/build-module/autocomplete/index.js.map +1 -1
- package/build-module/color-picker/component.js +11 -2
- package/build-module/color-picker/component.js.map +1 -1
- package/build-module/color-picker/styles.js +8 -9
- package/build-module/color-picker/styles.js.map +1 -1
- package/build-module/combobox-control/index.js +4 -9
- package/build-module/combobox-control/index.js.map +1 -1
- package/build-module/custom-select-control/index.js +2 -15
- package/build-module/custom-select-control/index.js.map +1 -1
- package/build-module/custom-select-control-v2/custom-select-item.js +26 -0
- package/build-module/custom-select-control-v2/custom-select-item.js.map +1 -0
- package/build-module/custom-select-control-v2/custom-select.js +82 -0
- package/build-module/custom-select-control-v2/custom-select.js.map +1 -0
- package/build-module/custom-select-control-v2/default-component/index.js +30 -0
- package/build-module/custom-select-control-v2/default-component/index.js.map +1 -0
- package/build-module/custom-select-control-v2/index.js +2 -74
- package/build-module/custom-select-control-v2/index.js.map +1 -1
- package/build-module/custom-select-control-v2/legacy-adapter.js +21 -0
- package/build-module/custom-select-control-v2/legacy-adapter.js.map +1 -0
- package/build-module/custom-select-control-v2/legacy-component/index.js +111 -0
- package/build-module/custom-select-control-v2/legacy-component/index.js.map +1 -0
- package/build-module/custom-select-control-v2/styles.js +73 -42
- package/build-module/custom-select-control-v2/styles.js.map +1 -1
- package/build-module/custom-select-control-v2/types.js.map +1 -1
- package/build-module/font-size-picker/font-size-picker-select.js +0 -1
- package/build-module/font-size-picker/font-size-picker-select.js.map +1 -1
- package/build-module/form-token-field/index.js +3 -8
- package/build-module/form-token-field/index.js.map +1 -1
- package/build-module/form-token-field/suggestions-list.js +5 -12
- package/build-module/form-token-field/suggestions-list.js.map +1 -1
- package/build-module/mobile/keyboard-aware-flat-list/use-scroll-to-section.native.js +1 -1
- package/build-module/mobile/keyboard-aware-flat-list/use-scroll-to-section.native.js.map +1 -1
- package/build-module/modal/index.js +3 -10
- package/build-module/modal/index.js.map +1 -1
- package/build-module/progress-bar/styles.js +5 -5
- package/build-module/progress-bar/styles.js.map +1 -1
- package/build-module/utils/with-ignore-ime-events.js +28 -0
- package/build-module/utils/with-ignore-ime-events.js.map +1 -0
- package/build-style/style-rtl.css +8 -1
- package/build-style/style.css +8 -1
- package/build-types/alignment-matrix-control/stories/index.story.d.ts +1 -1
- package/build-types/angle-picker-control/stories/index.story.d.ts +1 -1
- package/build-types/animate/stories/index.story.d.ts +7 -7
- package/build-types/autocomplete/index.d.ts.map +1 -1
- package/build-types/base-control/stories/index.story.d.ts +1 -1
- package/build-types/border-box-control/stories/index.story.d.ts +1 -1
- package/build-types/border-control/stories/index.story.d.ts +6 -6
- package/build-types/box-control/stories/index.story.d.ts +6 -6
- package/build-types/button/stories/e2e/index.story.d.ts +1 -1
- package/build-types/button/stories/index.story.d.ts +7 -7
- package/build-types/card/stories/index.story.d.ts +2 -2
- package/build-types/circular-option-picker/stories/index.story.d.ts +5 -5
- package/build-types/color-palette/stories/index.story.d.ts +3 -3
- package/build-types/color-picker/component.d.ts.map +1 -1
- package/build-types/color-picker/stories/index.story.d.ts +1 -1
- package/build-types/color-picker/styles.d.ts.map +1 -1
- package/build-types/combobox-control/index.d.ts.map +1 -1
- package/build-types/combobox-control/stories/index.story.d.ts +2 -2
- package/build-types/confirm-dialog/stories/index.story.d.ts +2 -2
- package/build-types/custom-gradient-picker/stories/index.story.d.ts +1 -1
- package/build-types/custom-select-control/index.d.ts.map +1 -1
- package/build-types/custom-select-control-v2/custom-select-item.d.ts +9 -0
- package/build-types/custom-select-control-v2/custom-select-item.d.ts.map +1 -0
- package/build-types/custom-select-control-v2/custom-select.d.ts +6 -0
- package/build-types/custom-select-control-v2/custom-select.d.ts.map +1 -0
- package/build-types/custom-select-control-v2/default-component/index.d.ts +5 -0
- package/build-types/custom-select-control-v2/default-component/index.d.ts.map +1 -0
- package/build-types/custom-select-control-v2/index.d.ts +5 -6
- package/build-types/custom-select-control-v2/index.d.ts.map +1 -1
- package/build-types/custom-select-control-v2/legacy-adapter.d.ts +6 -0
- package/build-types/custom-select-control-v2/legacy-adapter.d.ts.map +1 -0
- package/build-types/custom-select-control-v2/legacy-component/index.d.ts +5 -0
- package/build-types/custom-select-control-v2/legacy-component/index.d.ts.map +1 -0
- package/build-types/custom-select-control-v2/stories/default.story.d.ts +29 -0
- package/build-types/custom-select-control-v2/stories/default.story.d.ts.map +1 -0
- package/build-types/custom-select-control-v2/stories/legacy.story.d.ts +12 -0
- package/build-types/custom-select-control-v2/stories/legacy.story.d.ts.map +1 -0
- package/build-types/custom-select-control-v2/styles.d.ts +31 -6
- package/build-types/custom-select-control-v2/styles.d.ts.map +1 -1
- package/build-types/custom-select-control-v2/types.d.ts +137 -14
- package/build-types/custom-select-control-v2/types.d.ts.map +1 -1
- package/build-types/dimension-control/stories/index.story.d.ts +2 -2
- package/build-types/drop-zone/stories/index.story.d.ts +1 -1
- package/build-types/dropdown/stories/index.story.d.ts +3 -3
- package/build-types/dropdown-menu/stories/index.story.d.ts +2 -2
- package/build-types/dropdown-menu-v2/stories/index.story.d.ts.map +1 -1
- package/build-types/duotone-picker/stories/duotone-picker.story.d.ts +1 -1
- package/build-types/duotone-picker/stories/duotone-swatch.story.d.ts +3 -3
- package/build-types/focal-point-picker/stories/index.story.d.ts +4 -4
- package/build-types/font-size-picker/font-size-picker-select.d.ts.map +1 -1
- package/build-types/form-file-upload/stories/index.story.d.ts +5 -5
- package/build-types/form-token-field/index.d.ts.map +1 -1
- package/build-types/form-token-field/suggestions-list.d.ts.map +1 -1
- package/build-types/gradient-picker/stories/index.story.d.ts +3 -3
- package/build-types/guide/stories/index.story.d.ts +1 -1
- package/build-types/icon/stories/index.story.d.ts +4 -4
- package/build-types/input-control/stories/index.story.d.ts +6 -6
- package/build-types/keyboard-shortcuts/stories/index.story.d.ts +1 -1
- package/build-types/menu-group/stories/index.story.d.ts +1 -1
- package/build-types/menu-item/stories/index.story.d.ts +4 -4
- package/build-types/modal/index.d.ts.map +1 -1
- package/build-types/navigation/stories/index.story.d.ts +6 -6
- package/build-types/notice/stories/index.story.d.ts +4 -4
- package/build-types/number-control/stories/index.story.d.ts +1 -1
- package/build-types/palette-edit/stories/index.story.d.ts +2 -2
- package/build-types/progress-bar/stories/index.story.d.ts.map +1 -1
- package/build-types/query-controls/stories/index.story.d.ts +1 -1
- package/build-types/resizable-box/stories/index.story.d.ts +2 -2
- package/build-types/responsive-wrapper/stories/index.story.d.ts +1 -1
- package/build-types/sandbox/stories/index.story.d.ts +1 -1
- package/build-types/search-control/stories/index.story.d.ts +2 -2
- package/build-types/select-control/stories/index.story.d.ts +2 -2
- package/build-types/shortcut/stories/index.story.d.ts +1 -1
- package/build-types/tab-panel/stories/index.story.d.ts +4 -4
- package/build-types/tabs/stories/index.story.d.ts +9 -9
- package/build-types/tabs/stories/index.story.d.ts.map +1 -1
- package/build-types/text/stories/index.story.d.ts +3 -3
- package/build-types/theme/stories/index.story.d.ts +1 -1
- package/build-types/theme/stories/index.story.d.ts.map +1 -1
- package/build-types/toggle-control/stories/index.story.d.ts +2 -2
- package/build-types/toolbar/stories/index.story.d.ts +3 -3
- package/build-types/tooltip/stories/index.story.d.ts +1 -1
- package/build-types/tree-grid/stories/index.story.d.ts +1 -1
- package/build-types/tree-select/stories/index.story.d.ts +1 -1
- package/build-types/utils/with-ignore-ime-events.d.ts +15 -0
- package/build-types/utils/with-ignore-ime-events.d.ts.map +1 -0
- package/build-types/v-stack/stories/index.story.d.ts +1 -1
- package/package.json +19 -20
- package/src/alignment-matrix-control/test/index.tsx +3 -1
- package/src/autocomplete/index.tsx +3 -10
- package/src/circular-option-picker/test/index.tsx +4 -1
- package/src/color-picker/component.tsx +22 -11
- package/src/color-picker/styles.ts +1 -15
- package/src/combobox-control/index.tsx +33 -41
- package/src/composite/legacy/test/index.tsx +18 -2
- package/src/custom-select-control/README.md +0 -10
- package/src/custom-select-control/index.js +3 -22
- package/src/custom-select-control/stories/index.story.js +0 -1
- package/src/custom-select-control/test/index.js +17 -17
- package/src/custom-select-control-v2/README.md +97 -7
- package/src/custom-select-control-v2/custom-select-item.tsx +29 -0
- package/src/custom-select-control-v2/custom-select.tsx +122 -0
- package/src/custom-select-control-v2/default-component/index.tsx +24 -0
- package/src/custom-select-control-v2/index.tsx +2 -102
- package/src/custom-select-control-v2/legacy-adapter.tsx +25 -0
- package/src/custom-select-control-v2/legacy-component/index.tsx +133 -0
- package/src/custom-select-control-v2/stories/{index.story.tsx → default.story.tsx} +27 -33
- package/src/custom-select-control-v2/stories/legacy.story.tsx +88 -0
- package/src/custom-select-control-v2/styles.ts +82 -38
- package/src/custom-select-control-v2/test/index.tsx +808 -148
- package/src/custom-select-control-v2/types.ts +148 -20
- package/src/dropdown-menu-v2/stories/index.story.tsx +1 -0
- package/src/dropdown-menu-v2/test/index.tsx +4 -1
- package/src/font-size-picker/font-size-picker-select.tsx +0 -1
- package/src/form-token-field/index.tsx +3 -10
- package/src/form-token-field/suggestions-list.tsx +5 -17
- package/src/mobile/keyboard-aware-flat-list/use-scroll-to-section.native.js +1 -1
- package/src/modal/index.tsx +2 -12
- package/src/modal/style.scss +1 -0
- package/src/progress-bar/stories/index.story.tsx +1 -0
- package/src/progress-bar/styles.ts +2 -2
- package/src/tab-panel/test/index.tsx +8 -1
- package/src/tabs/stories/index.story.tsx +1 -0
- package/src/tabs/test/index.tsx +36 -6
- package/src/theme/stories/index.story.tsx +1 -0
- package/src/toggle-group-control/test/index.tsx +4 -0
- package/src/toolbar/toolbar-group/style.scss +1 -0
- package/src/tooltip/test/index.tsx +5 -0
- package/src/utils/with-ignore-ime-events.ts +32 -0
- package/tsconfig.json +0 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/build/custom-select-control/styles.js +0 -27
- package/build/custom-select-control/styles.js.map +0 -1
- package/build-module/custom-select-control/styles.js +0 -18
- package/build-module/custom-select-control/styles.js.map +0 -1
- package/build-types/custom-select-control/styles.d.ts +0 -11
- package/build-types/custom-select-control/styles.d.ts.map +0 -1
- package/build-types/custom-select-control-v2/stories/index.story.d.ts +0 -20
- package/build-types/custom-select-control-v2/stories/index.story.d.ts.map +0 -1
- 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
|
|
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
|
|
19
|
-
|
|
52
|
+
const LegacyControlledCustomSelect = ( {
|
|
53
|
+
options,
|
|
54
|
+
onChange,
|
|
55
|
+
...restProps
|
|
56
|
+
}: LegacyCustomSelectProps ) => {
|
|
57
|
+
const [ value, setValue ] = useState( options[ 0 ] );
|
|
20
58
|
return (
|
|
21
|
-
<
|
|
22
|
-
{ ...
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
59
|
+
<UncontrolledCustomSelect
|
|
60
|
+
{ ...restProps }
|
|
61
|
+
options={ options }
|
|
62
|
+
onChange={ ( args: any ) => {
|
|
63
|
+
onChange?.( args );
|
|
64
|
+
setValue( args.selectedItem );
|
|
26
65
|
} }
|
|
27
|
-
value={
|
|
66
|
+
value={ options.find(
|
|
67
|
+
( option: any ) => option.key === value.key
|
|
68
|
+
) }
|
|
28
69
|
/>
|
|
29
70
|
);
|
|
30
71
|
};
|
|
31
72
|
|
|
32
|
-
describe
|
|
33
|
-
[
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
73
|
+
describe( 'With Legacy Props', () => {
|
|
74
|
+
describe.each( [
|
|
75
|
+
[ 'Uncontrolled', UncontrolledCustomSelect ],
|
|
76
|
+
[ 'Controlled', LegacyControlledCustomSelect ],
|
|
77
|
+
] )( '%s', ( ...modeAndComponent ) => {
|
|
78
|
+
const [ , Component ] = modeAndComponent;
|
|
37
79
|
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
'ultraviolet morning light',
|
|
47
|
-
];
|
|
83
|
+
const currentSelectedItem = screen.getByRole( 'combobox', {
|
|
84
|
+
expanded: false,
|
|
85
|
+
} );
|
|
48
86
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81
|
-
|
|
150
|
+
// assert against filtered array
|
|
151
|
+
itemsWithClass.map( ( { name } ) =>
|
|
152
|
+
expect( screen.getByRole( 'option', { name } ) ).toHaveClass(
|
|
153
|
+
customClass
|
|
154
|
+
)
|
|
82
155
|
);
|
|
83
156
|
|
|
84
|
-
//
|
|
85
|
-
|
|
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
|
-
|
|
89
|
-
selected: true,
|
|
90
|
-
} )
|
|
91
|
-
).toBeVisible()
|
|
165
|
+
screen.getByRole( 'option', { name } )
|
|
166
|
+
).not.toHaveClass( customClass )
|
|
92
167
|
);
|
|
168
|
+
} );
|
|
93
169
|
|
|
94
|
-
|
|
95
|
-
const
|
|
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
|
-
|
|
98
|
-
const nextSelection = screen.getByRole( 'option', {
|
|
99
|
-
name: nextSelectionName,
|
|
100
|
-
} );
|
|
174
|
+
render( <Component { ...legacyProps } /> );
|
|
101
175
|
|
|
102
|
-
|
|
103
|
-
|
|
176
|
+
await click(
|
|
177
|
+
screen.getByRole( 'combobox', {
|
|
178
|
+
expanded: false,
|
|
179
|
+
} )
|
|
180
|
+
);
|
|
104
181
|
|
|
105
|
-
//
|
|
106
|
-
const
|
|
182
|
+
// return an array of items _with_ styles added
|
|
183
|
+
const styledItems = legacyProps.options.filter(
|
|
184
|
+
( option ) => option.style !== undefined
|
|
185
|
+
);
|
|
107
186
|
|
|
108
|
-
|
|
187
|
+
// assert against filtered array
|
|
188
|
+
styledItems.map( ( { name } ) =>
|
|
189
|
+
expect( screen.getByRole( 'option', { name } ) ).toHaveStyle(
|
|
190
|
+
customStyles
|
|
191
|
+
)
|
|
192
|
+
);
|
|
109
193
|
|
|
110
|
-
|
|
194
|
+
// return an array of items _without_ styles added
|
|
195
|
+
const unstyledItems = legacyProps.options.filter(
|
|
196
|
+
( option ) => option.style === undefined
|
|
197
|
+
);
|
|
111
198
|
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
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( '
|
|
119
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
|
132
|
-
{
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
864
|
+
expect(
|
|
865
|
+
screen.queryByRole( 'img', { name: 'july-9' } )
|
|
866
|
+
).not.toBeInTheDocument();
|
|
210
867
|
|
|
211
|
-
|
|
868
|
+
await click( currentSelectedItem );
|
|
212
869
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
} );
|