@wordpress/components 27.0.0 → 27.1.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/CHANGELOG.md +22 -0
- package/build/base-control/index.native.js.map +1 -1
- package/build/border-box-control/border-box-control/component.js.map +1 -1
- package/build/border-box-control/border-box-control-split-controls/component.js.map +1 -1
- package/build/border-control/border-control-dropdown/component.js.map +1 -1
- package/build/button/index.js +2 -2
- package/build/button/index.js.map +1 -1
- package/build/button/index.native.js.map +1 -1
- package/build/confirm-dialog/component.js.map +1 -1
- package/build/custom-select-control-v2/default-component/index.js.map +1 -1
- package/build/custom-select-control-v2/index.js +2 -2
- package/build/custom-select-control-v2/index.js.map +1 -1
- package/build/custom-select-control-v2/types.js.map +1 -1
- package/build/draggable/index.native.js +2 -2
- package/build/draggable/index.native.js.map +1 -1
- package/build/dropdown-menu/index.native.js.map +1 -1
- package/build/flex/flex/hook.js +1 -1
- package/build/flex/flex/hook.js.map +1 -1
- package/build/font-size-picker/index.native.js.map +1 -1
- package/build/form-token-field/index.js.map +1 -1
- package/build/h-stack/hook.js +6 -1
- package/build/h-stack/hook.js.map +1 -1
- package/build/mobile/bottom-sheet/button.native.js.map +1 -1
- package/build/mobile/bottom-sheet/index.native.js.map +1 -1
- package/build/mobile/bottom-sheet/range-cell.native.js.map +1 -1
- package/build/mobile/bottom-sheet/stepper-cell/index.native.js.map +1 -1
- package/build/mobile/bottom-sheet-select-control/index.native.js.map +1 -1
- package/build/mobile/bottom-sheet-text-control/index.native.js.map +1 -1
- package/build/mobile/gradient/index.native.js.map +1 -1
- package/build/mobile/image/index.native.js +4 -13
- package/build/mobile/image/index.native.js.map +1 -1
- package/build/mobile/media-edit/index.native.js.map +1 -1
- package/build/palette-edit/index.js.map +1 -1
- package/build/query-controls/index.native.js.map +1 -1
- package/build/range-control/index.js.map +1 -1
- package/build/search-control/index.native.js.map +1 -1
- package/build/snackbar/index.js +3 -2
- package/build/snackbar/index.js.map +1 -1
- package/build/snackbar/list.js +2 -1
- package/build/snackbar/list.js.map +1 -1
- package/build/snackbar/types.js.map +1 -1
- package/build/tabs/index.js +7 -7
- package/build/tabs/index.js.map +1 -1
- package/build/tabs/types.js.map +1 -1
- package/build/utils/hooks/index.js +0 -7
- package/build/utils/hooks/index.js.map +1 -1
- package/build-module/base-control/index.native.js.map +1 -1
- package/build-module/border-box-control/border-box-control/component.js.map +1 -1
- package/build-module/border-box-control/border-box-control-split-controls/component.js.map +1 -1
- package/build-module/border-control/border-control-dropdown/component.js.map +1 -1
- package/build-module/button/index.js +2 -2
- package/build-module/button/index.js.map +1 -1
- package/build-module/button/index.native.js.map +1 -1
- package/build-module/confirm-dialog/component.js.map +1 -1
- package/build-module/custom-select-control-v2/default-component/index.js.map +1 -1
- package/build-module/custom-select-control-v2/index.js +1 -1
- package/build-module/custom-select-control-v2/index.js.map +1 -1
- package/build-module/custom-select-control-v2/types.js.map +1 -1
- package/build-module/draggable/index.native.js +2 -2
- package/build-module/draggable/index.native.js.map +1 -1
- package/build-module/dropdown-menu/index.native.js.map +1 -1
- package/build-module/flex/flex/hook.js +1 -1
- package/build-module/flex/flex/hook.js.map +1 -1
- package/build-module/font-size-picker/index.native.js.map +1 -1
- package/build-module/form-token-field/index.js.map +1 -1
- package/build-module/h-stack/hook.js +6 -1
- package/build-module/h-stack/hook.js.map +1 -1
- package/build-module/mobile/bottom-sheet/button.native.js.map +1 -1
- package/build-module/mobile/bottom-sheet/index.native.js.map +1 -1
- package/build-module/mobile/bottom-sheet/range-cell.native.js.map +1 -1
- package/build-module/mobile/bottom-sheet/stepper-cell/index.native.js.map +1 -1
- package/build-module/mobile/bottom-sheet-select-control/index.native.js.map +1 -1
- package/build-module/mobile/bottom-sheet-text-control/index.native.js.map +1 -1
- package/build-module/mobile/gradient/index.native.js.map +1 -1
- package/build-module/mobile/image/index.native.js +6 -15
- package/build-module/mobile/image/index.native.js.map +1 -1
- package/build-module/mobile/media-edit/index.native.js.map +1 -1
- package/build-module/palette-edit/index.js.map +1 -1
- package/build-module/query-controls/index.native.js.map +1 -1
- package/build-module/range-control/index.js.map +1 -1
- package/build-module/search-control/index.native.js.map +1 -1
- package/build-module/snackbar/index.js +3 -2
- package/build-module/snackbar/index.js.map +1 -1
- package/build-module/snackbar/list.js +2 -1
- package/build-module/snackbar/list.js.map +1 -1
- package/build-module/snackbar/types.js.map +1 -1
- package/build-module/tabs/index.js +7 -7
- package/build-module/tabs/index.js.map +1 -1
- package/build-module/tabs/types.js.map +1 -1
- package/build-module/utils/hooks/index.js +0 -1
- package/build-module/utils/hooks/index.js.map +1 -1
- package/build-style/style-rtl.css +8 -1
- package/build-style/style.css +8 -1
- package/build-types/custom-select-control-v2/default-component/index.d.ts +2 -1
- package/build-types/custom-select-control-v2/default-component/index.d.ts.map +1 -1
- package/build-types/custom-select-control-v2/index.d.ts +1 -1
- package/build-types/custom-select-control-v2/index.d.ts.map +1 -1
- package/build-types/custom-select-control-v2/legacy-component/test/index.d.ts +2 -0
- package/build-types/custom-select-control-v2/legacy-component/test/index.d.ts.map +1 -0
- package/build-types/custom-select-control-v2/stories/default.story.d.ts +4 -3
- package/build-types/custom-select-control-v2/stories/default.story.d.ts.map +1 -1
- package/build-types/custom-select-control-v2/stories/legacy.story.d.ts +2 -2
- package/build-types/custom-select-control-v2/stories/legacy.story.d.ts.map +1 -1
- package/build-types/custom-select-control-v2/types.d.ts +0 -1
- package/build-types/custom-select-control-v2/types.d.ts.map +1 -1
- package/build-types/flex/flex/hook.d.ts +2 -3
- package/build-types/flex/flex/hook.d.ts.map +1 -1
- package/build-types/h-stack/hook.d.ts +2 -4
- package/build-types/h-stack/hook.d.ts.map +1 -1
- package/build-types/navigation/stories/utils/hide-if-empty.d.ts.map +1 -1
- package/build-types/radio-group/stories/index.story.d.ts.map +1 -1
- package/build-types/snackbar/index.d.ts +5 -2
- package/build-types/snackbar/index.d.ts.map +1 -1
- package/build-types/snackbar/list.d.ts.map +1 -1
- package/build-types/snackbar/test/index.d.ts +2 -0
- package/build-types/snackbar/test/index.d.ts.map +1 -0
- package/build-types/snackbar/test/list.d.ts +2 -0
- package/build-types/snackbar/test/list.d.ts.map +1 -0
- package/build-types/snackbar/types.d.ts +18 -2
- package/build-types/snackbar/types.d.ts.map +1 -1
- package/build-types/tabs/index.d.ts +1 -1
- package/build-types/tabs/types.d.ts +1 -1
- package/build-types/utils/hooks/index.d.ts +0 -1
- package/build-types/v-stack/hook.d.ts +2 -4
- package/build-types/v-stack/hook.d.ts.map +1 -1
- package/package.json +19 -19
- package/src/base-control/index.native.js +1 -1
- package/src/base-control/test/index.tsx +1 -1
- package/src/border-box-control/border-box-control/component.tsx +1 -1
- package/src/border-box-control/border-box-control-split-controls/component.tsx +4 -4
- package/src/border-control/border-control-dropdown/component.tsx +1 -1
- package/src/button/index.native.js +1 -1
- package/src/button/index.tsx +1 -1
- package/src/button/style.scss +1 -3
- package/src/circular-option-picker/test/index.tsx +2 -4
- package/src/combobox-control/test/index.tsx +1 -1
- package/src/confirm-dialog/component.tsx +1 -1
- package/src/confirm-dialog/test/index.tsx +5 -21
- package/src/custom-select-control-v2/default-component/index.tsx +4 -1
- package/src/custom-select-control-v2/index.tsx +1 -1
- package/src/custom-select-control-v2/legacy-component/test/index.tsx +457 -0
- package/src/custom-select-control-v2/stories/legacy.story.tsx +5 -6
- package/src/custom-select-control-v2/test/index.tsx +279 -749
- package/src/custom-select-control-v2/types.ts +0 -1
- package/src/disabled/test/index.tsx +1 -1
- package/src/draggable/index.native.js +2 -2
- package/src/draggable/test/index.native.js +6 -2
- package/src/dropdown-menu/index.native.js +2 -2
- package/src/flex/flex/hook.ts +1 -1
- package/src/font-size-picker/index.native.js +2 -2
- package/src/form-token-field/index.tsx +1 -1
- package/src/h-stack/hook.tsx +2 -1
- package/src/h-stack/test/index.tsx +10 -0
- package/src/item-group/test/index.js +2 -2
- package/src/mobile/bottom-sheet/bottom-sheet-navigation/test/navigation-container.native.js +10 -15
- package/src/mobile/bottom-sheet/button.native.js +1 -5
- package/src/mobile/bottom-sheet/index.native.js +2 -2
- package/src/mobile/bottom-sheet/range-cell.native.js +1 -1
- package/src/mobile/bottom-sheet/stepper-cell/index.native.js +2 -2
- package/src/mobile/bottom-sheet-select-control/index.native.js +1 -1
- package/src/mobile/bottom-sheet-text-control/index.native.js +1 -1
- package/src/mobile/gradient/index.native.js +1 -1
- package/src/mobile/image/index.native.js +8 -23
- package/src/mobile/media-edit/index.native.js +1 -1
- package/src/modal/test/index.tsx +1 -1
- package/src/navigation/stories/utils/hide-if-empty.tsx +2 -6
- package/src/palette-edit/index.tsx +2 -2
- package/src/popover/test/index.tsx +1 -4
- package/src/query-controls/index.native.js +2 -2
- package/src/radio-group/stories/index.story.tsx +1 -0
- package/src/range-control/index.tsx +3 -3
- package/src/range-control/test/index.tsx +2 -2
- package/src/search-control/index.native.js +1 -1
- package/src/snackbar/index.tsx +5 -2
- package/src/snackbar/list.tsx +6 -1
- package/src/snackbar/stories/list.story.tsx +0 -3
- package/src/snackbar/test/index.tsx +267 -0
- package/src/snackbar/test/list.tsx +46 -0
- package/src/snackbar/types.ts +31 -3
- package/src/tabs/README.md +18 -18
- package/src/tabs/index.tsx +7 -7
- package/src/tabs/stories/index.story.tsx +1 -1
- package/src/tabs/test/index.tsx +30 -30
- package/src/tabs/types.ts +1 -1
- package/src/toggle-group-control/test/index.tsx +1 -1
- package/src/tools-panel/stories/index.story.tsx +8 -8
- package/src/tools-panel/test/index.tsx +10 -28
- package/src/tooltip/style.scss +2 -1
- package/src/tooltip/test/index.native.js +3 -3
- package/src/tree-grid/test/index.tsx +1 -1
- package/src/utils/hooks/index.js +0 -1
- package/src/v-stack/test/index.tsx +10 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/build/custom-select-control-v2/legacy-adapter.js +0 -29
- package/build/custom-select-control-v2/legacy-adapter.js.map +0 -1
- package/build/utils/hooks/use-latest-ref.js +0 -33
- package/build/utils/hooks/use-latest-ref.js.map +0 -1
- package/build-module/custom-select-control-v2/legacy-adapter.js +0 -21
- package/build-module/custom-select-control-v2/legacy-adapter.js.map +0 -1
- package/build-module/utils/hooks/use-latest-ref.js +0 -27
- package/build-module/utils/hooks/use-latest-ref.js.map +0 -1
- package/build-types/custom-select-control-v2/legacy-adapter.d.ts +0 -6
- package/build-types/custom-select-control-v2/legacy-adapter.d.ts.map +0 -1
- package/build-types/utils/hooks/use-latest-ref.d.ts +0 -15
- package/build-types/utils/hooks/use-latest-ref.d.ts.map +0 -1
- package/src/custom-select-control-v2/legacy-adapter.tsx +0 -25
- package/src/utils/hooks/test/use-latest-ref.js +0 -119
- package/src/utils/hooks/use-latest-ref.ts +0 -29
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
4
|
import { render, screen } from '@testing-library/react';
|
|
5
|
-
import { click, press, sleep, type
|
|
5
|
+
import { click, press, sleep, type } from '@ariakit/test';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* WordPress dependencies
|
|
@@ -13,100 +13,115 @@ import { useState } from '@wordpress/element';
|
|
|
13
13
|
* Internal dependencies
|
|
14
14
|
*/
|
|
15
15
|
import { CustomSelect as UncontrolledCustomSelect, CustomSelectItem } from '..';
|
|
16
|
-
import type { CustomSelectProps
|
|
16
|
+
import type { CustomSelectProps } from '../types';
|
|
17
|
+
|
|
18
|
+
const items = [
|
|
19
|
+
{
|
|
20
|
+
key: 'flower1',
|
|
21
|
+
value: 'violets',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
key: 'flower2',
|
|
25
|
+
value: 'crimson clover',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
key: 'flower3',
|
|
29
|
+
value: 'poppy',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
key: 'color1',
|
|
33
|
+
value: 'amber',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
key: 'color2',
|
|
37
|
+
value: 'aquamarine',
|
|
38
|
+
},
|
|
39
|
+
];
|
|
17
40
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
const legacyProps = {
|
|
41
|
+
const defaultProps = {
|
|
21
42
|
label: 'label!',
|
|
22
|
-
|
|
23
|
-
{
|
|
24
|
-
|
|
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
|
-
],
|
|
43
|
+
children: items.map( ( { value, key } ) => (
|
|
44
|
+
<CustomSelectItem value={ value } key={ key } />
|
|
45
|
+
) ),
|
|
50
46
|
};
|
|
51
47
|
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
onChange,
|
|
55
|
-
...restProps
|
|
56
|
-
}: LegacyCustomSelectProps ) => {
|
|
57
|
-
const [ value, setValue ] = useState( options[ 0 ] );
|
|
48
|
+
const ControlledCustomSelect = ( props: CustomSelectProps ) => {
|
|
49
|
+
const [ value, setValue ] = useState< string | string[] >();
|
|
58
50
|
return (
|
|
59
51
|
<UncontrolledCustomSelect
|
|
60
|
-
{ ...
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
onChange?.(
|
|
64
|
-
setValue( args.selectedItem );
|
|
52
|
+
{ ...props }
|
|
53
|
+
onChange={ ( nextValue: string | string[] ) => {
|
|
54
|
+
setValue( nextValue );
|
|
55
|
+
props.onChange?.( nextValue );
|
|
65
56
|
} }
|
|
66
|
-
value={
|
|
67
|
-
( option: any ) => option.key === value.key
|
|
68
|
-
) }
|
|
57
|
+
value={ value }
|
|
69
58
|
/>
|
|
70
59
|
);
|
|
71
60
|
};
|
|
72
61
|
|
|
73
|
-
describe(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const [ , Component ] = modeAndComponent;
|
|
62
|
+
describe.each( [
|
|
63
|
+
[ 'Uncontrolled', UncontrolledCustomSelect ],
|
|
64
|
+
[ 'Controlled', ControlledCustomSelect ],
|
|
65
|
+
] )( 'CustomSelectControlV2 (%s)', ( ...modeAndComponent ) => {
|
|
66
|
+
const [ , Component ] = modeAndComponent;
|
|
79
67
|
|
|
80
|
-
|
|
81
|
-
|
|
68
|
+
it( 'Should replace the initial selection when a new item is selected', async () => {
|
|
69
|
+
render( <Component { ...defaultProps } /> );
|
|
82
70
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
71
|
+
const currentSelectedItem = screen.getByRole( 'combobox', {
|
|
72
|
+
expanded: false,
|
|
73
|
+
} );
|
|
86
74
|
|
|
87
|
-
|
|
75
|
+
await click( currentSelectedItem );
|
|
88
76
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
77
|
+
await click(
|
|
78
|
+
screen.getByRole( 'option', {
|
|
79
|
+
name: 'crimson clover',
|
|
80
|
+
} )
|
|
81
|
+
);
|
|
94
82
|
|
|
95
|
-
|
|
83
|
+
expect( currentSelectedItem ).toHaveTextContent( 'crimson clover' );
|
|
96
84
|
|
|
97
|
-
|
|
85
|
+
await click( currentSelectedItem );
|
|
98
86
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
87
|
+
await click(
|
|
88
|
+
screen.getByRole( 'option', {
|
|
89
|
+
name: 'poppy',
|
|
90
|
+
} )
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
expect( currentSelectedItem ).toHaveTextContent( 'poppy' );
|
|
94
|
+
} );
|
|
95
|
+
|
|
96
|
+
it( 'Should keep current selection if dropdown is closed without changing selection', async () => {
|
|
97
|
+
render( <Component { ...defaultProps } /> );
|
|
104
98
|
|
|
105
|
-
|
|
99
|
+
const currentSelectedItem = screen.getByRole( 'combobox', {
|
|
100
|
+
expanded: false,
|
|
106
101
|
} );
|
|
107
102
|
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
await sleep();
|
|
104
|
+
await press.Tab();
|
|
105
|
+
await press.Enter();
|
|
106
|
+
expect(
|
|
107
|
+
screen.getByRole( 'listbox', {
|
|
108
|
+
name: 'label!',
|
|
109
|
+
} )
|
|
110
|
+
).toBeVisible();
|
|
111
|
+
|
|
112
|
+
await press.Escape();
|
|
113
|
+
expect(
|
|
114
|
+
screen.queryByRole( 'listbox', {
|
|
115
|
+
name: 'label!',
|
|
116
|
+
} )
|
|
117
|
+
).not.toBeInTheDocument();
|
|
118
|
+
|
|
119
|
+
expect( currentSelectedItem ).toHaveTextContent( items[ 0 ].value );
|
|
120
|
+
} );
|
|
121
|
+
|
|
122
|
+
describe( 'Keyboard behavior and accessibility', () => {
|
|
123
|
+
it( 'Should be able to change selection using keyboard', async () => {
|
|
124
|
+
render( <Component { ...defaultProps } /> );
|
|
110
125
|
|
|
111
126
|
const currentSelectedItem = screen.getByRole( 'combobox', {
|
|
112
127
|
expanded: false,
|
|
@@ -114,417 +129,67 @@ describe( 'With Legacy Props', () => {
|
|
|
114
129
|
|
|
115
130
|
await sleep();
|
|
116
131
|
await press.Tab();
|
|
132
|
+
expect( currentSelectedItem ).toHaveFocus();
|
|
133
|
+
|
|
117
134
|
await press.Enter();
|
|
118
135
|
expect(
|
|
119
136
|
screen.getByRole( 'listbox', {
|
|
120
137
|
name: 'label!',
|
|
121
138
|
} )
|
|
122
|
-
).
|
|
123
|
-
|
|
124
|
-
await press.Escape();
|
|
125
|
-
expect(
|
|
126
|
-
screen.queryByRole( 'listbox', {
|
|
127
|
-
name: 'label!',
|
|
128
|
-
} )
|
|
129
|
-
).not.toBeInTheDocument();
|
|
130
|
-
|
|
131
|
-
expect( currentSelectedItem ).toHaveTextContent(
|
|
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
|
-
} )
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
// return an array of items _with_ a className added
|
|
146
|
-
const itemsWithClass = legacyProps.options.filter(
|
|
147
|
-
( option ) => option.className !== undefined
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
// assert against filtered array
|
|
151
|
-
itemsWithClass.map( ( { name } ) =>
|
|
152
|
-
expect( screen.getByRole( 'option', { name } ) ).toHaveClass(
|
|
153
|
-
customClass
|
|
154
|
-
)
|
|
155
|
-
);
|
|
156
|
-
|
|
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 } ) =>
|
|
164
|
-
expect(
|
|
165
|
-
screen.getByRole( 'option', { name } )
|
|
166
|
-
).not.toHaveClass( customClass )
|
|
167
|
-
);
|
|
168
|
-
} );
|
|
169
|
-
|
|
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;';
|
|
173
|
-
|
|
174
|
-
render( <Component { ...legacyProps } /> );
|
|
175
|
-
|
|
176
|
-
await click(
|
|
177
|
-
screen.getByRole( 'combobox', {
|
|
178
|
-
expanded: false,
|
|
179
|
-
} )
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
// return an array of items _with_ styles added
|
|
183
|
-
const styledItems = legacyProps.options.filter(
|
|
184
|
-
( option ) => option.style !== undefined
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
// assert against filtered array
|
|
188
|
-
styledItems.map( ( { name } ) =>
|
|
189
|
-
expect( screen.getByRole( 'option', { name } ) ).toHaveStyle(
|
|
190
|
-
customStyles
|
|
191
|
-
)
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
// return an array of items _without_ styles added
|
|
195
|
-
const unstyledItems = legacyProps.options.filter(
|
|
196
|
-
( option ) => option.style === undefined
|
|
197
|
-
);
|
|
198
|
-
|
|
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' )
|
|
225
|
-
);
|
|
226
|
-
} );
|
|
227
|
-
|
|
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
|
-
);
|
|
243
|
-
|
|
244
|
-
await waitFor( () =>
|
|
245
|
-
expect(
|
|
246
|
-
screen.getByRole( 'combobox', {
|
|
247
|
-
expanded: false,
|
|
248
|
-
} )
|
|
249
|
-
).toHaveTextContent( /hint/i )
|
|
250
|
-
);
|
|
251
|
-
} );
|
|
252
|
-
|
|
253
|
-
it( 'shows selected hint in list of options when added', async () => {
|
|
254
|
-
render(
|
|
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' } )
|
|
271
|
-
);
|
|
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
|
-
);
|
|
139
|
+
).toHaveFocus();
|
|
300
140
|
|
|
301
|
-
await
|
|
302
|
-
|
|
303
|
-
name: 'aquamarine',
|
|
304
|
-
} )
|
|
305
|
-
);
|
|
141
|
+
await press.ArrowDown();
|
|
142
|
+
await press.Enter();
|
|
306
143
|
|
|
307
|
-
expect(
|
|
308
|
-
2,
|
|
309
|
-
expect.objectContaining( {
|
|
310
|
-
inputValue: '',
|
|
311
|
-
isOpen: false,
|
|
312
|
-
selectedItem: expect.objectContaining( {
|
|
313
|
-
name: 'aquamarine',
|
|
314
|
-
} ),
|
|
315
|
-
type: '',
|
|
316
|
-
} )
|
|
317
|
-
);
|
|
144
|
+
expect( currentSelectedItem ).toHaveTextContent( 'crimson clover' );
|
|
318
145
|
} );
|
|
319
146
|
|
|
320
|
-
it( 'Should
|
|
321
|
-
|
|
322
|
-
( { selectedItem } ) => selectedItem.key
|
|
323
|
-
);
|
|
147
|
+
it( 'Should be able to type characters to select matching options', async () => {
|
|
148
|
+
render( <Component { ...defaultProps } /> );
|
|
324
149
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
);
|
|
150
|
+
const currentSelectedItem = screen.getByRole( 'combobox', {
|
|
151
|
+
expanded: false,
|
|
152
|
+
} );
|
|
328
153
|
|
|
329
154
|
await sleep();
|
|
330
155
|
await press.Tab();
|
|
156
|
+
await press.Enter();
|
|
331
157
|
expect(
|
|
332
|
-
screen.getByRole( '
|
|
333
|
-
|
|
158
|
+
screen.getByRole( 'listbox', {
|
|
159
|
+
name: 'label!',
|
|
334
160
|
} )
|
|
335
161
|
).toHaveFocus();
|
|
336
162
|
|
|
337
|
-
await type( '
|
|
163
|
+
await type( 'a' );
|
|
338
164
|
await press.Enter();
|
|
339
|
-
|
|
340
|
-
expect( mockOnChange ).toHaveReturnedWith( 'poppy' );
|
|
165
|
+
expect( currentSelectedItem ).toHaveTextContent( 'amber' );
|
|
341
166
|
} );
|
|
342
167
|
|
|
343
|
-
|
|
344
|
-
|
|
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();
|
|
168
|
+
it( 'Can change selection with a focused input and closed dropdown if typed characters match an option', async () => {
|
|
169
|
+
render( <Component { ...defaultProps } /> );
|
|
410
170
|
|
|
411
|
-
|
|
412
|
-
|
|
171
|
+
const currentSelectedItem = screen.getByRole( 'combobox', {
|
|
172
|
+
expanded: false,
|
|
413
173
|
} );
|
|
414
174
|
|
|
415
|
-
|
|
416
|
-
|
|
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' } ) );
|
|
175
|
+
await sleep();
|
|
176
|
+
await press.Tab();
|
|
177
|
+
expect( currentSelectedItem ).toHaveFocus();
|
|
446
178
|
|
|
447
|
-
|
|
448
|
-
await click( currentSelectedItem );
|
|
179
|
+
await type( 'aq' );
|
|
449
180
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
).toBeVisible();
|
|
181
|
+
expect(
|
|
182
|
+
screen.queryByRole( 'listbox', {
|
|
183
|
+
name: 'label!',
|
|
184
|
+
hidden: true,
|
|
185
|
+
} )
|
|
186
|
+
).not.toBeInTheDocument();
|
|
457
187
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
screen.getByRole( 'option', {
|
|
461
|
-
name: 'poppy',
|
|
462
|
-
selected: true,
|
|
463
|
-
} )
|
|
464
|
-
).toBeVisible();
|
|
465
|
-
} );
|
|
188
|
+
await press.Enter();
|
|
189
|
+
expect( currentSelectedItem ).toHaveTextContent( 'aquamarine' );
|
|
466
190
|
} );
|
|
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
191
|
|
|
520
|
-
|
|
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 () => {
|
|
192
|
+
it( 'Should have correct aria-selected value for selections', async () => {
|
|
528
193
|
render( <Component { ...defaultProps } /> );
|
|
529
194
|
|
|
530
195
|
const currentSelectedItem = screen.getByRole( 'combobox', {
|
|
@@ -533,320 +198,134 @@ describe( 'With Default Props', () => {
|
|
|
533
198
|
|
|
534
199
|
await click( currentSelectedItem );
|
|
535
200
|
|
|
536
|
-
|
|
201
|
+
// assert that first item has aria-selected="true"
|
|
202
|
+
expect(
|
|
537
203
|
screen.getByRole( 'option', {
|
|
538
|
-
name: '
|
|
204
|
+
name: 'violets',
|
|
205
|
+
selected: true,
|
|
539
206
|
} )
|
|
540
|
-
);
|
|
207
|
+
).toBeVisible();
|
|
541
208
|
|
|
542
|
-
|
|
209
|
+
// change the current selection
|
|
210
|
+
await click( screen.getByRole( 'option', { name: 'poppy' } ) );
|
|
543
211
|
|
|
212
|
+
// click combobox to mount listbox with options again
|
|
544
213
|
await click( currentSelectedItem );
|
|
545
214
|
|
|
546
|
-
|
|
547
|
-
screen.getByRole( 'option', {
|
|
548
|
-
name: 'poppy',
|
|
549
|
-
} )
|
|
550
|
-
);
|
|
551
|
-
|
|
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();
|
|
215
|
+
// check that first item is has aria-selected="false" after new selection
|
|
565
216
|
expect(
|
|
566
|
-
screen.getByRole( '
|
|
567
|
-
name: '
|
|
217
|
+
screen.getByRole( 'option', {
|
|
218
|
+
name: 'violets',
|
|
219
|
+
selected: false,
|
|
568
220
|
} )
|
|
569
221
|
).toBeVisible();
|
|
570
222
|
|
|
571
|
-
|
|
223
|
+
// check that new selected item now has aria-selected="true"
|
|
572
224
|
expect(
|
|
573
|
-
screen.
|
|
574
|
-
name: '
|
|
225
|
+
screen.getByRole( 'option', {
|
|
226
|
+
name: 'poppy',
|
|
227
|
+
selected: true,
|
|
575
228
|
} )
|
|
576
|
-
).
|
|
577
|
-
|
|
578
|
-
expect( currentSelectedItem ).toHaveTextContent(
|
|
579
|
-
legacyProps.options[ 0 ].name
|
|
580
|
-
);
|
|
229
|
+
).toBeVisible();
|
|
581
230
|
} );
|
|
231
|
+
} );
|
|
582
232
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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();
|
|
233
|
+
describe( 'Multiple selection', () => {
|
|
234
|
+
it( 'Should be able to select multiple items when provided an array', async () => {
|
|
235
|
+
const onChangeMock = jest.fn();
|
|
641
236
|
|
|
642
|
-
|
|
237
|
+
// initial selection as defaultValue
|
|
238
|
+
const defaultValues = [
|
|
239
|
+
'incandescent glow',
|
|
240
|
+
'ultraviolet morning light',
|
|
241
|
+
];
|
|
643
242
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
243
|
+
render(
|
|
244
|
+
<Component
|
|
245
|
+
defaultValue={ defaultValues }
|
|
246
|
+
onChange={ onChangeMock }
|
|
247
|
+
label="Multi-select"
|
|
248
|
+
>
|
|
249
|
+
{ [
|
|
250
|
+
'aurora borealis green',
|
|
251
|
+
'flamingo pink sunrise',
|
|
252
|
+
'incandescent glow',
|
|
253
|
+
'rose blush',
|
|
254
|
+
'ultraviolet morning light',
|
|
255
|
+
].map( ( item ) => (
|
|
256
|
+
<CustomSelectItem key={ item } value={ item }>
|
|
257
|
+
{ item }
|
|
258
|
+
</CustomSelectItem>
|
|
259
|
+
) ) }
|
|
260
|
+
</Component>
|
|
261
|
+
);
|
|
650
262
|
|
|
651
|
-
|
|
652
|
-
|
|
263
|
+
const currentSelectedItem = screen.getByRole( 'combobox', {
|
|
264
|
+
expanded: false,
|
|
653
265
|
} );
|
|
654
266
|
|
|
655
|
-
|
|
656
|
-
|
|
267
|
+
// ensure more than one item is selected due to defaultValues
|
|
268
|
+
expect( currentSelectedItem ).toHaveTextContent(
|
|
269
|
+
`${ defaultValues.length } items selected`
|
|
270
|
+
);
|
|
657
271
|
|
|
658
|
-
|
|
659
|
-
expanded: false,
|
|
660
|
-
} );
|
|
272
|
+
await click( currentSelectedItem );
|
|
661
273
|
|
|
662
|
-
|
|
274
|
+
expect( screen.getByRole( 'listbox' ) ).toHaveAttribute(
|
|
275
|
+
'aria-multiselectable'
|
|
276
|
+
);
|
|
663
277
|
|
|
664
|
-
|
|
278
|
+
// ensure defaultValues are selected in list of items
|
|
279
|
+
defaultValues.forEach( ( value ) =>
|
|
665
280
|
expect(
|
|
666
281
|
screen.getByRole( 'option', {
|
|
667
|
-
name:
|
|
282
|
+
name: value,
|
|
668
283
|
selected: true,
|
|
669
284
|
} )
|
|
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 );
|
|
285
|
+
).toBeVisible()
|
|
286
|
+
);
|
|
677
287
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
screen.getByRole( 'option', {
|
|
681
|
-
name: 'violets',
|
|
682
|
-
selected: false,
|
|
683
|
-
} )
|
|
684
|
-
).toBeVisible();
|
|
288
|
+
// name of next selection
|
|
289
|
+
const nextSelectionName = 'rose blush';
|
|
685
290
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
name: 'poppy',
|
|
690
|
-
selected: true,
|
|
691
|
-
} )
|
|
692
|
-
).toBeVisible();
|
|
291
|
+
// element for next selection
|
|
292
|
+
const nextSelection = screen.getByRole( 'option', {
|
|
293
|
+
name: nextSelectionName,
|
|
693
294
|
} );
|
|
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
295
|
|
|
759
|
-
|
|
760
|
-
|
|
296
|
+
// click next selection to add another item to current selection
|
|
297
|
+
await click( nextSelection );
|
|
761
298
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
defaultValues.concat( nextSelectionName );
|
|
299
|
+
// updated array containing defaultValues + the item just selected
|
|
300
|
+
const updatedSelection = defaultValues.concat( nextSelectionName );
|
|
765
301
|
|
|
766
|
-
|
|
302
|
+
expect( onChangeMock ).toHaveBeenCalledWith( updatedSelection );
|
|
767
303
|
|
|
768
|
-
|
|
304
|
+
expect( nextSelection ).toHaveAttribute( 'aria-selected' );
|
|
769
305
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
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
|
-
} );
|
|
306
|
+
// expect increased array length for current selection
|
|
307
|
+
expect( currentSelectedItem ).toHaveTextContent(
|
|
308
|
+
`${ updatedSelection.length } items selected`
|
|
309
|
+
);
|
|
835
310
|
} );
|
|
836
311
|
|
|
837
|
-
it( 'Should
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
312
|
+
it( 'Should be able to deselect items when provided an array', async () => {
|
|
313
|
+
// initial selection as defaultValue
|
|
314
|
+
const defaultValues = [
|
|
315
|
+
'aurora borealis green',
|
|
316
|
+
'incandescent glow',
|
|
317
|
+
'key lime green',
|
|
318
|
+
'rose blush',
|
|
319
|
+
'ultraviolet morning light',
|
|
320
|
+
];
|
|
841
321
|
|
|
842
322
|
render(
|
|
843
|
-
<Component
|
|
844
|
-
|
|
845
|
-
{
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
</CustomSelectItem>
|
|
323
|
+
<Component defaultValue={ defaultValues } label="Multi-select">
|
|
324
|
+
{ defaultValues.map( ( item ) => (
|
|
325
|
+
<CustomSelectItem key={ item } value={ item }>
|
|
326
|
+
{ item }
|
|
327
|
+
</CustomSelectItem>
|
|
328
|
+
) ) }
|
|
850
329
|
</Component>
|
|
851
330
|
);
|
|
852
331
|
|
|
@@ -854,26 +333,77 @@ describe( 'With Default Props', () => {
|
|
|
854
333
|
expanded: false,
|
|
855
334
|
} );
|
|
856
335
|
|
|
857
|
-
|
|
336
|
+
await click( currentSelectedItem );
|
|
858
337
|
|
|
859
|
-
//
|
|
860
|
-
|
|
861
|
-
|
|
338
|
+
// Array containing items to deselect
|
|
339
|
+
const nextSelection = [
|
|
340
|
+
'aurora borealis green',
|
|
341
|
+
'rose blush',
|
|
342
|
+
'incandescent glow',
|
|
343
|
+
];
|
|
344
|
+
|
|
345
|
+
// Deselect some items by clicking them to ensure that changes
|
|
346
|
+
// are reflected correctly
|
|
347
|
+
await Promise.all(
|
|
348
|
+
nextSelection.map( async ( value ) => {
|
|
349
|
+
await click(
|
|
350
|
+
screen.getByRole( 'option', { name: value } )
|
|
351
|
+
);
|
|
352
|
+
expect(
|
|
353
|
+
screen.getByRole( 'option', {
|
|
354
|
+
name: value,
|
|
355
|
+
selected: false,
|
|
356
|
+
} )
|
|
357
|
+
).toBeVisible();
|
|
358
|
+
} )
|
|
862
359
|
);
|
|
863
360
|
|
|
864
|
-
expect
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
361
|
+
// expect different array length from defaultValues due to deselecting items
|
|
362
|
+
expect( currentSelectedItem ).toHaveTextContent(
|
|
363
|
+
`${
|
|
364
|
+
defaultValues.length - nextSelection.length
|
|
365
|
+
} items selected`
|
|
366
|
+
);
|
|
367
|
+
} );
|
|
368
|
+
} );
|
|
869
369
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
370
|
+
it( 'Should allow rendering a custom value when using `renderSelectedValue`', async () => {
|
|
371
|
+
const renderValue = ( value: string | string[] ) => {
|
|
372
|
+
return <img src={ `${ value }.jpg` } alt={ value as string } />;
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
render(
|
|
376
|
+
<Component label="Rendered" renderSelectedValue={ renderValue }>
|
|
377
|
+
<CustomSelectItem value="april-29">
|
|
378
|
+
{ renderValue( 'april-29' ) }
|
|
379
|
+
</CustomSelectItem>
|
|
380
|
+
<CustomSelectItem value="july-9">
|
|
381
|
+
{ renderValue( 'july-9' ) }
|
|
382
|
+
</CustomSelectItem>
|
|
383
|
+
</Component>
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
const currentSelectedItem = screen.getByRole( 'combobox', {
|
|
387
|
+
expanded: false,
|
|
877
388
|
} );
|
|
389
|
+
|
|
390
|
+
expect( currentSelectedItem ).toBeVisible();
|
|
391
|
+
|
|
392
|
+
// expect that the initial selection renders an image
|
|
393
|
+
expect( currentSelectedItem ).toContainElement(
|
|
394
|
+
screen.getByRole( 'img', { name: 'april-29' } )
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
expect(
|
|
398
|
+
screen.queryByRole( 'img', { name: 'july-9' } )
|
|
399
|
+
).not.toBeInTheDocument();
|
|
400
|
+
|
|
401
|
+
await click( currentSelectedItem );
|
|
402
|
+
|
|
403
|
+
// expect that the other image is only visible after opening popover with options
|
|
404
|
+
expect( screen.getByRole( 'img', { name: 'july-9' } ) ).toBeVisible();
|
|
405
|
+
expect(
|
|
406
|
+
screen.getByRole( 'option', { name: 'july-9' } )
|
|
407
|
+
).toBeVisible();
|
|
878
408
|
} );
|
|
879
409
|
} );
|