@wordpress/components 23.4.0 → 23.5.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 +15 -0
- package/build/autocomplete/autocompleter-ui.js +41 -17
- package/build/autocomplete/autocompleter-ui.js.map +1 -1
- package/build/autocomplete/index.js +31 -33
- package/build/autocomplete/index.js.map +1 -1
- package/build/circular-option-picker/index.js +63 -14
- package/build/circular-option-picker/index.js.map +1 -1
- package/build/circular-option-picker/types.js +6 -0
- package/build/circular-option-picker/types.js.map +1 -0
- package/build/dropdown-menu/index.js +6 -2
- package/build/dropdown-menu/index.js.map +1 -1
- package/build/higher-order/with-constrained-tabbing/index.js +9 -0
- package/build/higher-order/with-constrained-tabbing/index.js.map +1 -1
- package/build/mobile/global-styles-context/utils.native.js +2 -1
- package/build/mobile/global-styles-context/utils.native.js.map +1 -1
- package/build/range-control/index.js +1 -0
- package/build/range-control/index.js.map +1 -1
- package/build/tools-panel/context.js +2 -0
- package/build/tools-panel/context.js.map +1 -1
- package/build/tools-panel/tools-panel/hook.js +18 -12
- package/build/tools-panel/tools-panel/hook.js.map +1 -1
- package/build/tools-panel/tools-panel-item/hook.js +14 -2
- package/build/tools-panel/tools-panel-item/hook.js.map +1 -1
- package/build/ui/context/context-system-provider.js +8 -4
- package/build/ui/context/context-system-provider.js.map +1 -1
- package/build-module/autocomplete/autocompleter-ui.js +40 -19
- package/build-module/autocomplete/autocompleter-ui.js.map +1 -1
- package/build-module/autocomplete/index.js +30 -32
- package/build-module/autocomplete/index.js.map +1 -1
- package/build-module/circular-option-picker/index.js +59 -16
- package/build-module/circular-option-picker/index.js.map +1 -1
- package/build-module/circular-option-picker/types.js +2 -0
- package/build-module/circular-option-picker/types.js.map +1 -0
- package/build-module/dropdown-menu/index.js +6 -2
- package/build-module/dropdown-menu/index.js.map +1 -1
- package/build-module/higher-order/with-constrained-tabbing/index.js +9 -0
- package/build-module/higher-order/with-constrained-tabbing/index.js.map +1 -1
- package/build-module/mobile/global-styles-context/utils.native.js +2 -1
- package/build-module/mobile/global-styles-context/utils.native.js.map +1 -1
- package/build-module/range-control/index.js +1 -0
- package/build-module/range-control/index.js.map +1 -1
- package/build-module/tools-panel/context.js +2 -0
- package/build-module/tools-panel/context.js.map +1 -1
- package/build-module/tools-panel/tools-panel/hook.js +18 -12
- package/build-module/tools-panel/tools-panel/hook.js.map +1 -1
- package/build-module/tools-panel/tools-panel-item/hook.js +14 -2
- package/build-module/tools-panel/tools-panel-item/hook.js.map +1 -1
- package/build-module/ui/context/context-system-provider.js +7 -4
- package/build-module/ui/context/context-system-provider.js.map +1 -1
- package/build-style/style-rtl.css +1 -0
- package/build-style/style.css +1 -0
- package/build-types/circular-option-picker/index.d.ts +56 -7
- package/build-types/circular-option-picker/index.d.ts.map +1 -1
- package/build-types/circular-option-picker/stories/index.d.ts +14 -0
- package/build-types/circular-option-picker/stories/index.d.ts.map +1 -0
- package/build-types/circular-option-picker/types.d.ts +49 -0
- package/build-types/circular-option-picker/types.d.ts.map +1 -0
- package/build-types/dropdown-menu/index.d.ts.map +1 -1
- package/build-types/h-stack/stories/e2e/index.d.ts +9 -0
- package/build-types/h-stack/stories/e2e/index.d.ts.map +1 -0
- package/build-types/higher-order/with-constrained-tabbing/index.d.ts +10 -1
- package/build-types/higher-order/with-constrained-tabbing/index.d.ts.map +1 -1
- package/build-types/range-control/index.d.ts.map +1 -1
- package/build-types/tab-panel/stories/index.d.ts +1 -0
- package/build-types/tab-panel/stories/index.d.ts.map +1 -1
- package/build-types/tools-panel/context.d.ts.map +1 -1
- package/build-types/tools-panel/test/index.d.ts +2 -0
- package/build-types/tools-panel/test/index.d.ts.map +1 -0
- package/build-types/tools-panel/tools-panel/hook.d.ts +3 -1
- package/build-types/tools-panel/tools-panel/hook.d.ts.map +1 -1
- package/build-types/tools-panel/tools-panel-header/hook.d.ts +1 -1
- package/build-types/tools-panel/tools-panel-item/component.d.ts +1 -0
- package/build-types/tools-panel/tools-panel-item/component.d.ts.map +1 -1
- package/build-types/tools-panel/tools-panel-item/hook.d.ts.map +1 -1
- package/build-types/tools-panel/types.d.ts +11 -9
- package/build-types/tools-panel/types.d.ts.map +1 -1
- package/build-types/ui/context/context-system-provider.d.ts.map +1 -1
- package/build-types/v-stack/stories/e2e/index.d.ts +9 -0
- package/build-types/v-stack/stories/e2e/index.d.ts.map +1 -0
- package/package.json +23 -21
- package/src/autocomplete/autocompleter-ui.js +72 -34
- package/src/autocomplete/index.js +36 -36
- package/src/circular-option-picker/README.md +141 -0
- package/src/circular-option-picker/{index.js → index.tsx} +74 -14
- package/src/circular-option-picker/stories/index.tsx +134 -0
- package/src/circular-option-picker/types.ts +69 -0
- package/src/color-palette/test/__snapshots__/index.tsx.snap +1 -1
- package/src/dropdown-menu/index.js +6 -3
- package/src/h-stack/stories/e2e/index.tsx +36 -0
- package/src/higher-order/navigate-regions/style.scss +2 -1
- package/src/higher-order/with-constrained-tabbing/index.tsx +30 -0
- package/src/mobile/global-styles-context/utils.native.js +1 -0
- package/src/range-control/index.tsx +5 -1
- package/src/tab-panel/stories/index.tsx +41 -0
- package/src/tab-panel/test/index.tsx +794 -262
- package/src/tools-panel/context.ts +2 -0
- package/src/tools-panel/test/{index.js → index.tsx} +171 -61
- package/src/tools-panel/tools-panel/hook.ts +30 -11
- package/src/tools-panel/tools-panel-item/hook.ts +18 -2
- package/src/tools-panel/types.ts +12 -9
- package/src/tree-grid/test/__snapshots__/index.tsx.snap +1 -1
- package/src/ui/context/context-system-provider.js +7 -4
- package/src/v-stack/stories/e2e/index.tsx +36 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/src/higher-order/with-constrained-tabbing/index.js +0 -22
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External dependencies
|
|
3
3
|
*/
|
|
4
|
-
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
5
5
|
import userEvent from '@testing-library/user-event';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* WordPress dependencies
|
|
9
|
+
*/
|
|
10
|
+
import { wordpress, category, media } from '@wordpress/icons';
|
|
11
|
+
|
|
7
12
|
/**
|
|
8
13
|
* Internal dependencies
|
|
9
14
|
*/
|
|
10
15
|
import TabPanel from '..';
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
import Popover from '../../popover';
|
|
17
|
+
import { Provider as SlotFillProvider } from '../../slot-fill';
|
|
13
18
|
|
|
14
19
|
const TABS = [
|
|
15
20
|
{
|
|
@@ -33,7 +38,14 @@ const getSelectedTab = () => screen.getByRole( 'tab', { selected: true } );
|
|
|
33
38
|
|
|
34
39
|
let originalGetClientRects: () => DOMRectList;
|
|
35
40
|
|
|
36
|
-
describe(
|
|
41
|
+
describe.each( [
|
|
42
|
+
[ 'uncontrolled', TabPanel ],
|
|
43
|
+
// The controlled component tests will be added once we certify the
|
|
44
|
+
// uncontrolled component's behaviour on trunk.
|
|
45
|
+
// [ 'controlled', TabPanel ],
|
|
46
|
+
] )( 'TabPanel %s', ( ...modeAndComponent ) => {
|
|
47
|
+
const [ , Component ] = modeAndComponent;
|
|
48
|
+
|
|
37
49
|
beforeAll( () => {
|
|
38
50
|
originalGetClientRects = window.HTMLElement.prototype.getClientRects;
|
|
39
51
|
// Mocking `getClientRects()` is necessary to pass a check performed by
|
|
@@ -49,295 +61,589 @@ describe( 'TabPanel', () => {
|
|
|
49
61
|
window.HTMLElement.prototype.getClientRects = originalGetClientRects;
|
|
50
62
|
} );
|
|
51
63
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const mockOnSelect = jest.fn();
|
|
56
|
-
|
|
57
|
-
render(
|
|
58
|
-
<TabPanel
|
|
59
|
-
tabs={ TABS }
|
|
60
|
-
children={ panelRenderFunction }
|
|
61
|
-
onSelect={ mockOnSelect }
|
|
62
|
-
/>
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
66
|
-
expect(
|
|
67
|
-
screen.getByRole( 'tabpanel', { name: 'Alpha' } )
|
|
68
|
-
).toBeInTheDocument();
|
|
69
|
-
expect( panelRenderFunction ).toHaveBeenLastCalledWith( TABS[ 0 ] );
|
|
70
|
-
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
71
|
-
|
|
72
|
-
await user.click( screen.getByRole( 'tab', { name: 'Beta' } ) );
|
|
73
|
-
|
|
74
|
-
expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
75
|
-
expect(
|
|
76
|
-
screen.getByRole( 'tabpanel', { name: 'Beta' } )
|
|
77
|
-
).toBeInTheDocument();
|
|
78
|
-
expect( panelRenderFunction ).toHaveBeenLastCalledWith( TABS[ 1 ] );
|
|
79
|
-
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
|
|
80
|
-
|
|
81
|
-
await user.click( screen.getByRole( 'tab', { name: 'Alpha' } ) );
|
|
82
|
-
|
|
83
|
-
expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
84
|
-
expect(
|
|
85
|
-
screen.getByRole( 'tabpanel', { name: 'Alpha' } )
|
|
86
|
-
).toBeInTheDocument();
|
|
87
|
-
expect( panelRenderFunction ).toHaveBeenLastCalledWith( TABS[ 0 ] );
|
|
88
|
-
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
89
|
-
} );
|
|
64
|
+
describe( 'Accessibility and semantics', () => {
|
|
65
|
+
test( 'should use the correct aria attributes', () => {
|
|
66
|
+
const panelRenderFunction = jest.fn();
|
|
90
67
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
initialTabName="beta"
|
|
95
|
-
tabs={ TABS }
|
|
96
|
-
children={ () => undefined }
|
|
97
|
-
/>
|
|
98
|
-
);
|
|
99
|
-
const selectedTab = screen.getByRole( 'tab', { selected: true } );
|
|
100
|
-
expect( selectedTab ).toHaveTextContent( 'Beta' );
|
|
101
|
-
} );
|
|
68
|
+
render(
|
|
69
|
+
<Component tabs={ TABS } children={ panelRenderFunction } />
|
|
70
|
+
);
|
|
102
71
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
render(
|
|
108
|
-
<TabPanel
|
|
109
|
-
activeClass={ activeClass }
|
|
110
|
-
tabs={ TABS }
|
|
111
|
-
children={ () => undefined }
|
|
112
|
-
/>
|
|
113
|
-
);
|
|
114
|
-
expect( getSelectedTab() ).toHaveClass( activeClass );
|
|
115
|
-
screen
|
|
116
|
-
.getAllByRole( 'tab', { selected: false } )
|
|
117
|
-
.forEach( ( unselectedTab ) => {
|
|
118
|
-
expect( unselectedTab ).not.toHaveClass( activeClass );
|
|
119
|
-
} );
|
|
120
|
-
|
|
121
|
-
await user.click( screen.getByRole( 'tab', { name: 'Beta' } ) );
|
|
122
|
-
|
|
123
|
-
expect( getSelectedTab() ).toHaveClass( activeClass );
|
|
124
|
-
screen
|
|
125
|
-
.getAllByRole( 'tab', { selected: false } )
|
|
126
|
-
.forEach( ( unselectedTab ) => {
|
|
127
|
-
expect( unselectedTab ).not.toHaveClass( activeClass );
|
|
128
|
-
} );
|
|
129
|
-
} );
|
|
72
|
+
const tabList = screen.getByRole( 'tablist' );
|
|
73
|
+
const allTabs = screen.getAllByRole( 'tab' );
|
|
74
|
+
const selectedTabPanel = screen.getByRole( 'tabpanel' );
|
|
130
75
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
);
|
|
137
|
-
expect( screen.getByRole( 'tab', { name: 'Beta' } ) ).toHaveClass(
|
|
138
|
-
'beta-class'
|
|
139
|
-
);
|
|
140
|
-
expect( screen.getByRole( 'tab', { name: 'Gamma' } ) ).toHaveClass(
|
|
141
|
-
'gamma-class'
|
|
142
|
-
);
|
|
143
|
-
} );
|
|
76
|
+
expect( tabList ).toBeVisible();
|
|
77
|
+
expect( tabList ).toHaveAttribute(
|
|
78
|
+
'aria-orientation',
|
|
79
|
+
'horizontal'
|
|
80
|
+
);
|
|
144
81
|
|
|
145
|
-
|
|
146
|
-
const mockOnSelect = jest.fn();
|
|
147
|
-
|
|
148
|
-
render(
|
|
149
|
-
<TabPanel
|
|
150
|
-
tabs={ TABS }
|
|
151
|
-
initialTabName="beta"
|
|
152
|
-
children={ () => undefined }
|
|
153
|
-
onSelect={ mockOnSelect }
|
|
154
|
-
/>
|
|
155
|
-
);
|
|
156
|
-
expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
157
|
-
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
|
|
158
|
-
} );
|
|
82
|
+
expect( allTabs ).toHaveLength( TABS.length );
|
|
159
83
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
// There should be no selected tab.
|
|
173
|
-
expect(
|
|
174
|
-
screen.queryByRole( 'tab', { selected: true } )
|
|
175
|
-
).not.toBeInTheDocument();
|
|
176
|
-
|
|
177
|
-
rerender(
|
|
178
|
-
<TabPanel
|
|
179
|
-
tabs={ [
|
|
180
|
-
{ name: 'delta', title: 'Delta', className: 'delta-class' },
|
|
181
|
-
...TABS,
|
|
182
|
-
] }
|
|
183
|
-
initialTabName="delta"
|
|
184
|
-
children={ () => undefined }
|
|
185
|
-
onSelect={ mockOnSelect }
|
|
186
|
-
/>
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
expect( getSelectedTab() ).toHaveTextContent( 'Delta' );
|
|
190
|
-
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'delta' );
|
|
191
|
-
} );
|
|
84
|
+
// The selected `tab` aria-controls the active `tabpanel`,
|
|
85
|
+
// which is `aria-labelledby` the selected `tab`.
|
|
86
|
+
expect( selectedTabPanel ).toBeVisible();
|
|
87
|
+
expect( allTabs[ 0 ] ).toHaveAttribute(
|
|
88
|
+
'aria-controls',
|
|
89
|
+
selectedTabPanel.getAttribute( 'id' )
|
|
90
|
+
);
|
|
91
|
+
expect( selectedTabPanel ).toHaveAttribute(
|
|
92
|
+
'aria-labelledby',
|
|
93
|
+
allTabs[ 0 ].getAttribute( 'id' )
|
|
94
|
+
);
|
|
95
|
+
} );
|
|
192
96
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const mockOnSelect = jest.fn();
|
|
196
|
-
|
|
197
|
-
render(
|
|
198
|
-
<TabPanel
|
|
199
|
-
tabs={ [
|
|
200
|
-
...TABS,
|
|
201
|
-
{
|
|
202
|
-
name: 'delta',
|
|
203
|
-
title: 'Delta',
|
|
204
|
-
className: 'delta-class',
|
|
205
|
-
disabled: true,
|
|
206
|
-
},
|
|
207
|
-
] }
|
|
208
|
-
children={ () => undefined }
|
|
209
|
-
onSelect={ mockOnSelect }
|
|
210
|
-
/>
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
expect( screen.getByRole( 'tab', { name: 'Delta' } ) ).toHaveAttribute(
|
|
214
|
-
'aria-disabled',
|
|
215
|
-
'true'
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
// onSelect gets called on the initial render.
|
|
219
|
-
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
220
|
-
|
|
221
|
-
// onSelect should not be called since the disabled tab is highlighted, but not selected.
|
|
222
|
-
await user.keyboard( '[ArrowLeft]' );
|
|
223
|
-
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
224
|
-
} );
|
|
97
|
+
test( 'should display a tooltip when hovering tabs provided with an icon', async () => {
|
|
98
|
+
const user = userEvent.setup();
|
|
225
99
|
|
|
226
|
-
|
|
227
|
-
const mockOnSelect = jest.fn();
|
|
228
|
-
|
|
229
|
-
render(
|
|
230
|
-
<TabPanel
|
|
231
|
-
tabs={ [
|
|
232
|
-
{
|
|
233
|
-
name: 'alpha',
|
|
234
|
-
title: 'Alpha',
|
|
235
|
-
className: 'alpha-class',
|
|
236
|
-
disabled: true,
|
|
237
|
-
},
|
|
238
|
-
{
|
|
239
|
-
name: 'beta',
|
|
240
|
-
title: 'Beta',
|
|
241
|
-
className: 'beta-class',
|
|
242
|
-
},
|
|
243
|
-
] }
|
|
244
|
-
initialTabName="alpha"
|
|
245
|
-
children={ () => undefined }
|
|
246
|
-
onSelect={ mockOnSelect }
|
|
247
|
-
/>
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
251
|
-
} );
|
|
100
|
+
const panelRenderFunction = jest.fn();
|
|
252
101
|
|
|
253
|
-
|
|
254
|
-
|
|
102
|
+
const TABS_WITH_ICON = [
|
|
103
|
+
{ ...TABS[ 0 ], icon: wordpress },
|
|
104
|
+
{ ...TABS[ 1 ], icon: category },
|
|
105
|
+
{ ...TABS[ 2 ], icon: media },
|
|
106
|
+
];
|
|
255
107
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
108
|
+
render(
|
|
109
|
+
// In order for the tooltip to display properly, there needs to be
|
|
110
|
+
// `Popover.Slot` in which the `Popover` renders outside of the
|
|
111
|
+
// `TabPanel` component, otherwise the tooltip renders inline.
|
|
112
|
+
<SlotFillProvider>
|
|
113
|
+
<Component
|
|
114
|
+
tabs={ TABS_WITH_ICON }
|
|
115
|
+
children={ panelRenderFunction }
|
|
116
|
+
/>
|
|
117
|
+
{ /* @ts-expect-error The 'Slot' component hasn't been typed yet. */ }
|
|
118
|
+
<Popover.Slot />
|
|
119
|
+
</SlotFillProvider>
|
|
120
|
+
);
|
|
263
121
|
|
|
264
|
-
|
|
122
|
+
const allTabs = screen.getAllByRole( 'tab' );
|
|
265
123
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
124
|
+
for ( let i = 0; i < allTabs.length; i++ ) {
|
|
125
|
+
expect(
|
|
126
|
+
screen.queryByText( TABS_WITH_ICON[ i ].title )
|
|
127
|
+
).not.toBeInTheDocument();
|
|
128
|
+
|
|
129
|
+
await user.hover( allTabs[ i ] );
|
|
130
|
+
|
|
131
|
+
await waitFor( () =>
|
|
132
|
+
expect(
|
|
133
|
+
screen.getByText( TABS_WITH_ICON[ i ].title )
|
|
134
|
+
).toBeVisible()
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
await user.unhover( allTabs[ i ] );
|
|
138
|
+
}
|
|
139
|
+
} );
|
|
140
|
+
|
|
141
|
+
test( 'should display a tooltip when moving the selection via the keyboard on tabs provided with an icon', async () => {
|
|
142
|
+
const user = userEvent.setup();
|
|
143
|
+
|
|
144
|
+
const mockOnSelect = jest.fn();
|
|
145
|
+
const panelRenderFunction = jest.fn();
|
|
146
|
+
|
|
147
|
+
const TABS_WITH_ICON = [
|
|
148
|
+
{ ...TABS[ 0 ], icon: wordpress },
|
|
149
|
+
{ ...TABS[ 1 ], icon: category },
|
|
150
|
+
{ ...TABS[ 2 ], icon: media },
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
render(
|
|
154
|
+
// In order for the tooltip to display properly, there needs to be
|
|
155
|
+
// `Popover.Slot` in which the `Popover` renders outside of the
|
|
156
|
+
// `TabPanel` component, otherwise the tooltip renders inline.
|
|
157
|
+
<SlotFillProvider>
|
|
158
|
+
<Component
|
|
159
|
+
tabs={ TABS_WITH_ICON }
|
|
160
|
+
children={ panelRenderFunction }
|
|
161
|
+
onSelect={ mockOnSelect }
|
|
162
|
+
/>
|
|
163
|
+
{ /* @ts-expect-error The 'Slot' component hasn't been typed yet. */ }
|
|
164
|
+
<Popover.Slot />
|
|
165
|
+
</SlotFillProvider>
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
expect( getSelectedTab() ).not.toHaveTextContent( 'Alpha' );
|
|
169
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
170
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
171
|
+
await expect( getSelectedTab() ).not.toHaveFocus();
|
|
172
|
+
|
|
173
|
+
// Tab to focus the tablist. Make sure alpha is focused, and that the
|
|
174
|
+
// corresponding tooltip is shown.
|
|
175
|
+
expect( screen.queryByText( 'Alpha' ) ).not.toBeInTheDocument();
|
|
176
|
+
await user.keyboard( '[Tab]' );
|
|
177
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
178
|
+
expect( screen.getByText( 'Alpha' ) ).toBeInTheDocument();
|
|
179
|
+
await expect( getSelectedTab() ).toHaveFocus();
|
|
180
|
+
|
|
181
|
+
// Move selection with arrow keys. Make sure beta is focused, and that
|
|
182
|
+
// the corresponding tooltip is shown.
|
|
183
|
+
expect( screen.queryByText( 'Beta' ) ).not.toBeInTheDocument();
|
|
184
|
+
await user.keyboard( '[ArrowRight]' );
|
|
185
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
|
|
186
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
|
|
187
|
+
expect( screen.getByText( 'Beta' ) ).toBeInTheDocument();
|
|
188
|
+
await expect( getSelectedTab() ).toHaveFocus();
|
|
189
|
+
|
|
190
|
+
// Move selection with arrow keys. Make sure gamma is focused, and that
|
|
191
|
+
// the corresponding tooltip is shown.
|
|
192
|
+
expect( screen.queryByText( 'Gamma' ) ).not.toBeInTheDocument();
|
|
193
|
+
await user.keyboard( '[ArrowRight]' );
|
|
194
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
|
|
195
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
|
|
196
|
+
expect( screen.getByText( 'Gamma' ) ).toBeInTheDocument();
|
|
197
|
+
await expect( getSelectedTab() ).toHaveFocus();
|
|
198
|
+
|
|
199
|
+
// Move selection with arrow keys. Make sure beta is focused, and that
|
|
200
|
+
// the corresponding tooltip is shown.
|
|
201
|
+
expect( screen.queryByText( 'Beta' ) ).not.toBeInTheDocument();
|
|
202
|
+
await user.keyboard( '[ArrowLeft]' );
|
|
203
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 4 );
|
|
204
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
|
|
205
|
+
expect( screen.getByText( 'Beta' ) ).toBeInTheDocument();
|
|
206
|
+
await expect( getSelectedTab() ).toHaveFocus();
|
|
207
|
+
} );
|
|
280
208
|
} );
|
|
281
209
|
|
|
282
|
-
describe( '
|
|
283
|
-
it( 'should
|
|
284
|
-
const
|
|
210
|
+
describe( 'Without `initialTabName`', () => {
|
|
211
|
+
it( 'should render first tab', async () => {
|
|
212
|
+
const panelRenderFunction = jest.fn();
|
|
213
|
+
|
|
214
|
+
render(
|
|
215
|
+
<Component tabs={ TABS } children={ panelRenderFunction } />
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
219
|
+
expect(
|
|
220
|
+
screen.getByRole( 'tabpanel', { name: 'Alpha' } )
|
|
221
|
+
).toBeInTheDocument();
|
|
222
|
+
expect( panelRenderFunction ).toHaveBeenLastCalledWith( TABS[ 0 ] );
|
|
223
|
+
} );
|
|
224
|
+
|
|
225
|
+
it( 'should fall back to first enabled tab if the active tab is removed', async () => {
|
|
285
226
|
const mockOnSelect = jest.fn();
|
|
227
|
+
const { rerender } = render(
|
|
228
|
+
<Component
|
|
229
|
+
tabs={ TABS }
|
|
230
|
+
children={ () => undefined }
|
|
231
|
+
onSelect={ mockOnSelect }
|
|
232
|
+
/>
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
rerender(
|
|
236
|
+
<Component
|
|
237
|
+
tabs={ TABS.slice( 1 ) /* remove alpha */ }
|
|
238
|
+
children={ () => undefined }
|
|
239
|
+
onSelect={ mockOnSelect }
|
|
240
|
+
/>
|
|
241
|
+
);
|
|
242
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
243
|
+
} );
|
|
244
|
+
} );
|
|
286
245
|
|
|
246
|
+
describe( 'With `initialTabName`', () => {
|
|
247
|
+
it( 'should render the tab set by initialTabName prop', () => {
|
|
248
|
+
render(
|
|
249
|
+
<Component
|
|
250
|
+
initialTabName="beta"
|
|
251
|
+
tabs={ TABS }
|
|
252
|
+
children={ () => undefined }
|
|
253
|
+
/>
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
257
|
+
} );
|
|
258
|
+
|
|
259
|
+
it( 'should not select a tab when `initialTabName` does not match any known tab', () => {
|
|
260
|
+
render(
|
|
261
|
+
<Component
|
|
262
|
+
initialTabName="does-not-exist"
|
|
263
|
+
tabs={ TABS }
|
|
264
|
+
children={ () => undefined }
|
|
265
|
+
/>
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
// No tab should be selected i.e. it doesn't fall back to first tab.
|
|
269
|
+
expect(
|
|
270
|
+
screen.queryByRole( 'tab', { selected: true } )
|
|
271
|
+
).not.toBeInTheDocument();
|
|
272
|
+
|
|
273
|
+
// No tabpanel should be rendered either
|
|
274
|
+
expect( screen.queryByRole( 'tabpanel' ) ).not.toBeInTheDocument();
|
|
275
|
+
} );
|
|
276
|
+
|
|
277
|
+
it( 'should not change tabs when initialTabName is changed', () => {
|
|
287
278
|
const { rerender } = render(
|
|
288
|
-
<
|
|
279
|
+
<Component
|
|
280
|
+
initialTabName="beta"
|
|
281
|
+
tabs={ TABS }
|
|
282
|
+
children={ () => undefined }
|
|
283
|
+
/>
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
rerender(
|
|
287
|
+
<Component
|
|
288
|
+
initialTabName="alpha"
|
|
289
289
|
tabs={ TABS }
|
|
290
|
+
children={ () => undefined }
|
|
291
|
+
/>
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
295
|
+
} );
|
|
296
|
+
|
|
297
|
+
it( 'should fall back to the tab associated to `initialTabName` if the currently active tab is removed', async () => {
|
|
298
|
+
const user = userEvent.setup();
|
|
299
|
+
const mockOnSelect = jest.fn();
|
|
300
|
+
|
|
301
|
+
const { rerender } = render(
|
|
302
|
+
<Component
|
|
290
303
|
initialTabName="gamma"
|
|
304
|
+
tabs={ TABS }
|
|
291
305
|
children={ () => undefined }
|
|
292
306
|
onSelect={ mockOnSelect }
|
|
293
307
|
/>
|
|
294
308
|
);
|
|
309
|
+
|
|
310
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
311
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
312
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
|
|
313
|
+
|
|
295
314
|
await user.click( screen.getByRole( 'tab', { name: 'Alpha' } ) );
|
|
315
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
316
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
|
|
317
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
296
318
|
|
|
297
319
|
rerender(
|
|
298
|
-
<
|
|
299
|
-
tabs={ TABS.slice( 1 ) /* remove alpha */ }
|
|
320
|
+
<Component
|
|
300
321
|
initialTabName="gamma"
|
|
322
|
+
tabs={ TABS.slice( 1 ) } // Remove alpha
|
|
301
323
|
children={ () => undefined }
|
|
302
324
|
onSelect={ mockOnSelect }
|
|
303
325
|
/>
|
|
304
326
|
);
|
|
327
|
+
|
|
305
328
|
expect( getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
329
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
|
|
306
330
|
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
|
|
307
331
|
} );
|
|
308
332
|
|
|
309
|
-
it( 'should
|
|
310
|
-
const user = setupUser();
|
|
333
|
+
it( 'should have no active tabs when the tab associated to `initialTabName` is removed while being the active tab', () => {
|
|
311
334
|
const mockOnSelect = jest.fn();
|
|
312
335
|
|
|
313
336
|
const { rerender } = render(
|
|
314
|
-
<
|
|
337
|
+
<Component
|
|
338
|
+
initialTabName="gamma"
|
|
315
339
|
tabs={ TABS }
|
|
316
340
|
children={ () => undefined }
|
|
317
341
|
onSelect={ mockOnSelect }
|
|
318
342
|
/>
|
|
319
343
|
);
|
|
320
|
-
|
|
344
|
+
|
|
345
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
346
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
347
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
|
|
321
348
|
|
|
322
349
|
rerender(
|
|
323
|
-
<
|
|
324
|
-
|
|
350
|
+
<Component
|
|
351
|
+
initialTabName="gamma"
|
|
352
|
+
tabs={ TABS.slice( 0, 2 ) } // Remove gamma
|
|
353
|
+
children={ () => undefined }
|
|
354
|
+
onSelect={ mockOnSelect }
|
|
355
|
+
/>
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
expect( screen.getAllByRole( 'tab' ) ).toHaveLength( 2 );
|
|
359
|
+
expect(
|
|
360
|
+
screen.queryByRole( 'tab', { selected: true } )
|
|
361
|
+
).not.toBeInTheDocument();
|
|
362
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
363
|
+
} );
|
|
364
|
+
|
|
365
|
+
it( 'waits for the tab with the `initialTabName` to be present in the `tabs` array before selecting it', () => {
|
|
366
|
+
const mockOnSelect = jest.fn();
|
|
367
|
+
const { rerender } = render(
|
|
368
|
+
<Component
|
|
369
|
+
initialTabName="delta"
|
|
370
|
+
tabs={ TABS }
|
|
371
|
+
children={ () => undefined }
|
|
372
|
+
onSelect={ mockOnSelect }
|
|
373
|
+
/>
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
// There should be no selected tab yet.
|
|
377
|
+
expect(
|
|
378
|
+
screen.queryByRole( 'tab', { selected: true } )
|
|
379
|
+
).not.toBeInTheDocument();
|
|
380
|
+
|
|
381
|
+
rerender(
|
|
382
|
+
<Component
|
|
383
|
+
initialTabName="delta"
|
|
384
|
+
tabs={ [
|
|
385
|
+
{
|
|
386
|
+
name: 'delta',
|
|
387
|
+
title: 'Delta',
|
|
388
|
+
className: 'delta-class',
|
|
389
|
+
},
|
|
390
|
+
...TABS,
|
|
391
|
+
] }
|
|
392
|
+
children={ () => undefined }
|
|
393
|
+
onSelect={ mockOnSelect }
|
|
394
|
+
/>
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Delta' );
|
|
398
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'delta' );
|
|
399
|
+
} );
|
|
400
|
+
} );
|
|
401
|
+
|
|
402
|
+
describe( 'Disabled Tab', () => {
|
|
403
|
+
it( 'should disable the tab when `disabled` is `true`', async () => {
|
|
404
|
+
const user = userEvent.setup();
|
|
405
|
+
const mockOnSelect = jest.fn();
|
|
406
|
+
|
|
407
|
+
render(
|
|
408
|
+
<Component
|
|
409
|
+
tabs={ [
|
|
410
|
+
...TABS,
|
|
411
|
+
{
|
|
412
|
+
name: 'delta',
|
|
413
|
+
title: 'Delta',
|
|
414
|
+
className: 'delta-class',
|
|
415
|
+
disabled: true,
|
|
416
|
+
},
|
|
417
|
+
] }
|
|
418
|
+
children={ () => undefined }
|
|
419
|
+
onSelect={ mockOnSelect }
|
|
420
|
+
/>
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
expect(
|
|
424
|
+
screen.getByRole( 'tab', { name: 'Delta' } )
|
|
425
|
+
).toHaveAttribute( 'aria-disabled', 'true' );
|
|
426
|
+
|
|
427
|
+
// onSelect gets called on the initial render.
|
|
428
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
429
|
+
|
|
430
|
+
// onSelect should not be called since the disabled tab is
|
|
431
|
+
// highlighted, but not selected.
|
|
432
|
+
await user.keyboard( '[ArrowLeft]' );
|
|
433
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
434
|
+
} );
|
|
435
|
+
|
|
436
|
+
it( 'should select first enabled tab when the initial tab is disabled', () => {
|
|
437
|
+
const mockOnSelect = jest.fn();
|
|
438
|
+
|
|
439
|
+
const { rerender } = render(
|
|
440
|
+
<Component
|
|
441
|
+
// Disable alpha
|
|
442
|
+
tabs={ TABS.map( ( tab ) => {
|
|
443
|
+
if ( tab.name !== 'alpha' ) {
|
|
444
|
+
return tab;
|
|
445
|
+
}
|
|
446
|
+
return { ...tab, disabled: true };
|
|
447
|
+
} ) }
|
|
325
448
|
children={ () => undefined }
|
|
326
449
|
onSelect={ mockOnSelect }
|
|
327
450
|
/>
|
|
328
451
|
);
|
|
452
|
+
|
|
453
|
+
// As alpha (first tab) is disabled,
|
|
454
|
+
// the first enabled tab should be gamma.
|
|
329
455
|
expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
456
|
+
|
|
457
|
+
// Re-enable all tabs
|
|
458
|
+
rerender(
|
|
459
|
+
<Component
|
|
460
|
+
tabs={ TABS }
|
|
461
|
+
children={ () => undefined }
|
|
462
|
+
onSelect={ mockOnSelect }
|
|
463
|
+
/>
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
// Even if the initial tab becomes enabled again, the selected tab doesn't
|
|
467
|
+
// change.
|
|
468
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
469
|
+
} );
|
|
470
|
+
|
|
471
|
+
it( 'should select first enabled tab when the tab associated to `initialTabName` is disabled', () => {
|
|
472
|
+
const mockOnSelect = jest.fn();
|
|
473
|
+
|
|
474
|
+
const { rerender } = render(
|
|
475
|
+
<Component
|
|
476
|
+
tabs={ TABS.map( ( tab ) => {
|
|
477
|
+
if ( tab.name === 'gamma' ) {
|
|
478
|
+
return tab;
|
|
479
|
+
}
|
|
480
|
+
return { ...tab, disabled: true };
|
|
481
|
+
} ) }
|
|
482
|
+
initialTabName="beta"
|
|
483
|
+
children={ () => undefined }
|
|
484
|
+
onSelect={ mockOnSelect }
|
|
485
|
+
/>
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
// As alpha (first tab), and beta (the initial tab), are both
|
|
489
|
+
// disabled the first enabled tab should be gamma.
|
|
490
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
491
|
+
|
|
492
|
+
// Re-enable all tabs
|
|
493
|
+
rerender(
|
|
494
|
+
<Component
|
|
495
|
+
tabs={ TABS }
|
|
496
|
+
initialTabName="beta"
|
|
497
|
+
children={ () => undefined }
|
|
498
|
+
onSelect={ mockOnSelect }
|
|
499
|
+
/>
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
// Even if the initial tab becomes enabled again, the selected tab doesn't
|
|
503
|
+
// change.
|
|
504
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
505
|
+
} );
|
|
506
|
+
|
|
507
|
+
it( 'should select the first enabled tab when the selected tab becomes disabled', () => {
|
|
508
|
+
const mockOnSelect = jest.fn();
|
|
509
|
+
const { rerender } = render(
|
|
510
|
+
<Component
|
|
511
|
+
tabs={ TABS }
|
|
512
|
+
children={ () => undefined }
|
|
513
|
+
onSelect={ mockOnSelect }
|
|
514
|
+
/>
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
518
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
519
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
520
|
+
|
|
521
|
+
rerender(
|
|
522
|
+
<Component
|
|
523
|
+
tabs={ TABS.map( ( tab ) => {
|
|
524
|
+
if ( tab.name === 'alpha' ) {
|
|
525
|
+
return { ...tab, disabled: true };
|
|
526
|
+
}
|
|
527
|
+
return tab;
|
|
528
|
+
} ) }
|
|
529
|
+
children={ () => undefined }
|
|
530
|
+
onSelect={ mockOnSelect }
|
|
531
|
+
/>
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
535
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
|
|
330
536
|
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
|
|
537
|
+
|
|
538
|
+
rerender(
|
|
539
|
+
<Component
|
|
540
|
+
tabs={ TABS }
|
|
541
|
+
children={ () => undefined }
|
|
542
|
+
onSelect={ mockOnSelect }
|
|
543
|
+
/>
|
|
544
|
+
);
|
|
545
|
+
|
|
546
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
547
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
|
|
548
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
|
|
549
|
+
} );
|
|
550
|
+
|
|
551
|
+
it( 'should select the first enabled tab when the tab associated to `initialTabName` becomes disabled while being the active tab', () => {
|
|
552
|
+
const mockOnSelect = jest.fn();
|
|
553
|
+
|
|
554
|
+
const { rerender } = render(
|
|
555
|
+
<Component
|
|
556
|
+
initialTabName="gamma"
|
|
557
|
+
tabs={ TABS }
|
|
558
|
+
children={ () => undefined }
|
|
559
|
+
onSelect={ mockOnSelect }
|
|
560
|
+
/>
|
|
561
|
+
);
|
|
562
|
+
|
|
563
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
564
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
565
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
|
|
566
|
+
|
|
567
|
+
rerender(
|
|
568
|
+
<Component
|
|
569
|
+
initialTabName="gamma"
|
|
570
|
+
tabs={ [
|
|
571
|
+
TABS[ 0 ],
|
|
572
|
+
TABS[ 1 ],
|
|
573
|
+
{ ...TABS[ 2 ], disabled: true },
|
|
574
|
+
] } // Disable gamma
|
|
575
|
+
children={ () => undefined }
|
|
576
|
+
onSelect={ mockOnSelect }
|
|
577
|
+
/>
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
581
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
|
|
582
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
583
|
+
|
|
584
|
+
rerender(
|
|
585
|
+
<Component
|
|
586
|
+
initialTabName="gamma"
|
|
587
|
+
tabs={ TABS }
|
|
588
|
+
children={ () => undefined }
|
|
589
|
+
onSelect={ mockOnSelect }
|
|
590
|
+
/>
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
594
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
|
|
331
595
|
} );
|
|
332
596
|
} );
|
|
333
597
|
|
|
334
|
-
describe( '
|
|
335
|
-
it( 'defaults to automatic tab activation', async () => {
|
|
336
|
-
const user =
|
|
598
|
+
describe( 'Tab Activation', () => {
|
|
599
|
+
it( 'defaults to automatic tab activation (pointer clicks)', async () => {
|
|
600
|
+
const user = userEvent.setup();
|
|
601
|
+
const panelRenderFunction = jest.fn();
|
|
337
602
|
const mockOnSelect = jest.fn();
|
|
338
603
|
|
|
339
604
|
render(
|
|
340
|
-
<
|
|
605
|
+
<Component
|
|
606
|
+
tabs={ TABS }
|
|
607
|
+
children={ panelRenderFunction }
|
|
608
|
+
onSelect={ mockOnSelect }
|
|
609
|
+
/>
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
// Alpha is the initially selected tab
|
|
613
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
614
|
+
expect(
|
|
615
|
+
screen.getByRole( 'tabpanel', { name: 'Alpha' } )
|
|
616
|
+
).toBeInTheDocument();
|
|
617
|
+
expect( panelRenderFunction ).toHaveBeenLastCalledWith( TABS[ 0 ] );
|
|
618
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
619
|
+
|
|
620
|
+
// Click on Beta, make sure beta is the selected tab
|
|
621
|
+
await user.click( screen.getByRole( 'tab', { name: 'Beta' } ) );
|
|
622
|
+
|
|
623
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
624
|
+
expect(
|
|
625
|
+
screen.getByRole( 'tabpanel', { name: 'Beta' } )
|
|
626
|
+
).toBeInTheDocument();
|
|
627
|
+
expect( panelRenderFunction ).toHaveBeenLastCalledWith( TABS[ 1 ] );
|
|
628
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
|
|
629
|
+
|
|
630
|
+
// Click on Alpha, make sure beta is the selected tab
|
|
631
|
+
await user.click( screen.getByRole( 'tab', { name: 'Alpha' } ) );
|
|
632
|
+
|
|
633
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
634
|
+
expect(
|
|
635
|
+
screen.getByRole( 'tabpanel', { name: 'Alpha' } )
|
|
636
|
+
).toBeInTheDocument();
|
|
637
|
+
expect( panelRenderFunction ).toHaveBeenLastCalledWith( TABS[ 0 ] );
|
|
638
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
639
|
+
} );
|
|
640
|
+
|
|
641
|
+
it( 'defaults to automatic tab activation (arrow keys)', async () => {
|
|
642
|
+
const user = userEvent.setup();
|
|
643
|
+
const mockOnSelect = jest.fn();
|
|
644
|
+
|
|
645
|
+
render(
|
|
646
|
+
<Component
|
|
341
647
|
tabs={ TABS }
|
|
342
648
|
children={ () => undefined }
|
|
343
649
|
onSelect={ mockOnSelect }
|
|
@@ -347,42 +653,219 @@ describe( 'TabPanel', () => {
|
|
|
347
653
|
// onSelect gets called on the initial render.
|
|
348
654
|
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
349
655
|
|
|
350
|
-
//
|
|
351
|
-
|
|
656
|
+
// Tab to focus the tablist. Make sure alpha is focused.
|
|
657
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
658
|
+
await expect( getSelectedTab() ).not.toHaveFocus();
|
|
659
|
+
await user.keyboard( '[Tab]' );
|
|
660
|
+
await expect( getSelectedTab() ).toHaveFocus();
|
|
661
|
+
|
|
662
|
+
// Navigate forward with arrow keys and make sure the Beta tab is
|
|
663
|
+
// selected automatically.
|
|
664
|
+
await user.keyboard( '[ArrowRight]' );
|
|
665
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
666
|
+
await expect( getSelectedTab() ).toHaveFocus();
|
|
352
667
|
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
|
|
668
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
|
|
669
|
+
|
|
670
|
+
// Navigate backwards with arrow keys. Make sure alpha is
|
|
671
|
+
// selected automatically.
|
|
672
|
+
await user.keyboard( '[ArrowLeft]' );
|
|
673
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
674
|
+
await expect( getSelectedTab() ).toHaveFocus();
|
|
675
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
|
|
353
676
|
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
677
|
+
} );
|
|
678
|
+
|
|
679
|
+
it( 'wraps around the last/first tab when using arrow keys', async () => {
|
|
680
|
+
const user = userEvent.setup();
|
|
681
|
+
const mockOnSelect = jest.fn();
|
|
682
|
+
|
|
683
|
+
render(
|
|
684
|
+
<Component
|
|
685
|
+
tabs={ TABS }
|
|
686
|
+
children={ () => undefined }
|
|
687
|
+
onSelect={ mockOnSelect }
|
|
688
|
+
/>
|
|
689
|
+
);
|
|
690
|
+
|
|
691
|
+
// onSelect gets called on the initial render.
|
|
692
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
693
|
+
|
|
694
|
+
// Tab to focus the tablist. Make sure Alpha is focused.
|
|
695
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
696
|
+
await expect( getSelectedTab() ).not.toHaveFocus();
|
|
697
|
+
await user.keyboard( '[Tab]' );
|
|
698
|
+
await expect( getSelectedTab() ).toHaveFocus();
|
|
699
|
+
|
|
700
|
+
// Navigate backwards with arrow keys and make sure that the Gamma tab
|
|
701
|
+
// (the last tab) is selected automatically.
|
|
702
|
+
await user.keyboard( '[ArrowLeft]' );
|
|
703
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
704
|
+
await expect( getSelectedTab() ).toHaveFocus();
|
|
705
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
|
|
706
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
|
|
354
707
|
|
|
355
|
-
// Navigate forward with arrow keys
|
|
356
|
-
//
|
|
708
|
+
// Navigate forward with arrow keys. Make sure alpha (the first tab) is
|
|
709
|
+
// selected automatically.
|
|
357
710
|
await user.keyboard( '[ArrowRight]' );
|
|
711
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
712
|
+
await expect( getSelectedTab() ).toHaveFocus();
|
|
358
713
|
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
|
|
714
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
715
|
+
} );
|
|
716
|
+
|
|
717
|
+
it( 'should not move tab selection when pressing the up/down arrow keys, unless the orientation is changed to `vertical`', async () => {
|
|
718
|
+
const user = userEvent.setup();
|
|
719
|
+
const mockOnSelect = jest.fn();
|
|
720
|
+
|
|
721
|
+
const { rerender } = render(
|
|
722
|
+
<Component
|
|
723
|
+
tabs={ TABS }
|
|
724
|
+
children={ () => undefined }
|
|
725
|
+
onSelect={ mockOnSelect }
|
|
726
|
+
/>
|
|
727
|
+
);
|
|
728
|
+
|
|
729
|
+
// onSelect gets called on the initial render.
|
|
730
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
731
|
+
|
|
732
|
+
// Tab to focus the tablist. Make sure alpha is focused.
|
|
733
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
734
|
+
await expect( getSelectedTab() ).not.toHaveFocus();
|
|
735
|
+
await user.keyboard( '[Tab]' );
|
|
736
|
+
await expect( getSelectedTab() ).toHaveFocus();
|
|
737
|
+
|
|
738
|
+
// Press the arrow up key, nothing happens.
|
|
739
|
+
await user.keyboard( '[ArrowUp]' );
|
|
740
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
741
|
+
await expect( getSelectedTab() ).toHaveFocus();
|
|
742
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
743
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
744
|
+
|
|
745
|
+
// Press the arrow down key, nothing happens
|
|
746
|
+
await user.keyboard( '[ArrowDown]' );
|
|
747
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
748
|
+
await expect( getSelectedTab() ).toHaveFocus();
|
|
749
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
750
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
751
|
+
|
|
752
|
+
// Change orientation to `vertical`. When the orientation is vertical,
|
|
753
|
+
// left/right arrow keys are replaced by up/down arrow keys.
|
|
754
|
+
rerender(
|
|
755
|
+
<Component
|
|
756
|
+
tabs={ TABS }
|
|
757
|
+
children={ () => undefined }
|
|
758
|
+
onSelect={ mockOnSelect }
|
|
759
|
+
orientation="vertical"
|
|
760
|
+
/>
|
|
761
|
+
);
|
|
762
|
+
|
|
763
|
+
expect( screen.getByRole( 'tablist' ) ).toHaveAttribute(
|
|
764
|
+
'aria-orientation',
|
|
765
|
+
'vertical'
|
|
766
|
+
);
|
|
767
|
+
|
|
768
|
+
// Make sure alpha is still focused.
|
|
769
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
770
|
+
await expect( getSelectedTab() ).toHaveFocus();
|
|
771
|
+
|
|
772
|
+
// Navigate forward with arrow keys and make sure the Beta tab is
|
|
773
|
+
// selected automatically.
|
|
774
|
+
await user.keyboard( '[ArrowDown]' );
|
|
775
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
776
|
+
await expect( getSelectedTab() ).toHaveFocus();
|
|
777
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
|
|
359
778
|
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
|
|
360
779
|
|
|
361
|
-
// Navigate
|
|
362
|
-
//
|
|
363
|
-
await user.keyboard( '[
|
|
780
|
+
// Navigate backwards with arrow keys. Make sure alpha is
|
|
781
|
+
// selected automatically.
|
|
782
|
+
await user.keyboard( '[ArrowUp]' );
|
|
783
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
784
|
+
await expect( getSelectedTab() ).toHaveFocus();
|
|
785
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
|
|
786
|
+
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
787
|
+
|
|
788
|
+
// Navigate backwards with arrow keys. Make sure alpha is
|
|
789
|
+
// selected automatically.
|
|
790
|
+
await user.keyboard( '[ArrowUp]' );
|
|
791
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
792
|
+
await expect( getSelectedTab() ).toHaveFocus();
|
|
364
793
|
expect( mockOnSelect ).toHaveBeenCalledTimes( 4 );
|
|
365
794
|
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
|
|
366
795
|
|
|
367
|
-
// Navigate
|
|
368
|
-
//
|
|
369
|
-
await user.keyboard( '[
|
|
796
|
+
// Navigate backwards with arrow keys. Make sure alpha is
|
|
797
|
+
// selected automatically.
|
|
798
|
+
await user.keyboard( '[ArrowDown]' );
|
|
799
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
800
|
+
await expect( getSelectedTab() ).toHaveFocus();
|
|
370
801
|
expect( mockOnSelect ).toHaveBeenCalledTimes( 5 );
|
|
371
802
|
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
803
|
+
} );
|
|
372
804
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
805
|
+
it( 'should move focus on a tab even if disabled with arrow key, but not with pointer clicks', async () => {
|
|
806
|
+
const user = userEvent.setup();
|
|
807
|
+
const mockOnSelect = jest.fn();
|
|
808
|
+
|
|
809
|
+
render(
|
|
810
|
+
<Component
|
|
811
|
+
tabs={ [
|
|
812
|
+
...TABS,
|
|
813
|
+
{
|
|
814
|
+
name: 'delta',
|
|
815
|
+
title: 'Delta',
|
|
816
|
+
className: 'delta-class',
|
|
817
|
+
disabled: true,
|
|
818
|
+
},
|
|
819
|
+
] }
|
|
820
|
+
children={ () => undefined }
|
|
821
|
+
onSelect={ mockOnSelect }
|
|
822
|
+
/>
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
// onSelect gets called on the initial render.
|
|
826
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
827
|
+
|
|
828
|
+
// Tab to focus the tablist. Make sure Alpha is focused.
|
|
829
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
830
|
+
await expect( getSelectedTab() ).not.toHaveFocus();
|
|
831
|
+
await user.keyboard( '[Tab]' );
|
|
832
|
+
await expect( getSelectedTab() ).toHaveFocus();
|
|
833
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
834
|
+
|
|
835
|
+
// Press the right arrow key three times. Since the delta tab is disabled:
|
|
836
|
+
// - it won't be selected. The gamma tab will be selected instead, since
|
|
837
|
+
// it was the tab that was last selected before delta. Therefore, the
|
|
838
|
+
// `mockOnSelect` function gets called only twice (and not three times)
|
|
839
|
+
// - it will receive focus, when using arrow keys
|
|
840
|
+
await user.keyboard( '[ArrowRight][ArrowRight][ArrowRight]' );
|
|
841
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
842
|
+
await expect(
|
|
843
|
+
screen.getByRole( 'tab', { name: 'Delta' } )
|
|
844
|
+
).toHaveFocus();
|
|
845
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
|
|
377
846
|
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
|
|
847
|
+
|
|
848
|
+
// Navigate backwards with arrow keys. The gamma tab receives focus.
|
|
849
|
+
await user.keyboard( '[ArrowLeft]' );
|
|
850
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
851
|
+
await expect( getSelectedTab() ).toHaveFocus();
|
|
852
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 4 );
|
|
853
|
+
|
|
854
|
+
// Click on on the disabled tab. Compared to using arrow keys to move the
|
|
855
|
+
// focus, disabled tabs ignore pointer clicks — and therefore, they don't
|
|
856
|
+
// receive focus, nor they cause the `mockOnSelect` function to fire.
|
|
857
|
+
await user.click( screen.getByRole( 'tab', { name: 'Delta' } ) );
|
|
858
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Gamma' );
|
|
859
|
+
await expect( getSelectedTab() ).toHaveFocus();
|
|
860
|
+
expect( mockOnSelect ).toHaveBeenCalledTimes( 4 );
|
|
378
861
|
} );
|
|
379
862
|
|
|
380
863
|
it( 'switches to manual tab activation when the `selectOnMove` prop is set to `false`', async () => {
|
|
381
|
-
const user =
|
|
864
|
+
const user = userEvent.setup();
|
|
382
865
|
const mockOnSelect = jest.fn();
|
|
383
866
|
|
|
384
867
|
render(
|
|
385
|
-
<
|
|
868
|
+
<Component
|
|
386
869
|
tabs={ TABS }
|
|
387
870
|
children={ () => undefined }
|
|
388
871
|
onSelect={ mockOnSelect }
|
|
@@ -393,35 +876,84 @@ describe( 'TabPanel', () => {
|
|
|
393
876
|
// onSelect gets called on the initial render.
|
|
394
877
|
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
|
|
395
878
|
|
|
396
|
-
// Click on Alpha
|
|
879
|
+
// Click on Alpha and make sure it is selected.
|
|
397
880
|
await user.click( screen.getByRole( 'tab', { name: 'Alpha' } ) );
|
|
398
881
|
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
|
|
399
882
|
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
|
|
400
883
|
|
|
401
|
-
// Navigate forward with arrow keys.
|
|
402
|
-
//
|
|
403
|
-
//
|
|
884
|
+
// Navigate forward with arrow keys. Make sure Beta is focused, but
|
|
885
|
+
// that the tab selection happens only when pressing the spacebar
|
|
886
|
+
// or enter key.
|
|
404
887
|
await user.keyboard( '[ArrowRight]' );
|
|
405
888
|
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
|
|
406
889
|
expect( screen.getByRole( 'tab', { name: 'Beta' } ) ).toHaveFocus();
|
|
890
|
+
|
|
407
891
|
await user.keyboard( '[Enter]' );
|
|
408
892
|
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
|
|
409
893
|
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
|
|
410
894
|
|
|
411
|
-
// Navigate forward with arrow keys.
|
|
412
|
-
//
|
|
413
|
-
//
|
|
895
|
+
// Navigate forward with arrow keys. Make sure Gamma (last tab) is
|
|
896
|
+
// focused, but that tab selection happens only when pressing the
|
|
897
|
+
// spacebar or enter key.
|
|
414
898
|
await user.keyboard( '[ArrowRight]' );
|
|
415
899
|
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
|
|
416
900
|
expect(
|
|
417
901
|
screen.getByRole( 'tab', { name: 'Gamma' } )
|
|
418
902
|
).toHaveFocus();
|
|
903
|
+
|
|
419
904
|
await user.keyboard( '[Space]' );
|
|
420
905
|
expect( mockOnSelect ).toHaveBeenCalledTimes( 4 );
|
|
421
906
|
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
|
|
907
|
+
} );
|
|
908
|
+
} );
|
|
909
|
+
|
|
910
|
+
describe( 'Tab Attributes', () => {
|
|
911
|
+
it( "should apply the tab's `className` to the tab button", () => {
|
|
912
|
+
render( <Component tabs={ TABS } children={ () => undefined } /> );
|
|
422
913
|
|
|
423
|
-
|
|
424
|
-
|
|
914
|
+
expect( screen.getByRole( 'tab', { name: 'Alpha' } ) ).toHaveClass(
|
|
915
|
+
'alpha-class'
|
|
916
|
+
);
|
|
917
|
+
expect( screen.getByRole( 'tab', { name: 'Beta' } ) ).toHaveClass(
|
|
918
|
+
'beta-class'
|
|
919
|
+
);
|
|
920
|
+
expect( screen.getByRole( 'tab', { name: 'Gamma' } ) ).toHaveClass(
|
|
921
|
+
'gamma-class'
|
|
922
|
+
);
|
|
923
|
+
} );
|
|
924
|
+
|
|
925
|
+
it( 'should apply the `activeClass` to the selected tab', async () => {
|
|
926
|
+
const user = userEvent.setup();
|
|
927
|
+
const activeClass = 'my-active-tab';
|
|
928
|
+
|
|
929
|
+
render(
|
|
930
|
+
<Component
|
|
931
|
+
activeClass={ activeClass }
|
|
932
|
+
tabs={ TABS }
|
|
933
|
+
children={ () => undefined }
|
|
934
|
+
/>
|
|
935
|
+
);
|
|
936
|
+
|
|
937
|
+
// Make sure that only the selected tab has the active class
|
|
938
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
|
|
939
|
+
expect( getSelectedTab() ).toHaveClass( activeClass );
|
|
940
|
+
screen
|
|
941
|
+
.getAllByRole( 'tab', { selected: false } )
|
|
942
|
+
.forEach( ( unselectedTab ) => {
|
|
943
|
+
expect( unselectedTab ).not.toHaveClass( activeClass );
|
|
944
|
+
} );
|
|
945
|
+
|
|
946
|
+
// Click the 'Beta' tab
|
|
947
|
+
await user.click( screen.getByRole( 'tab', { name: 'Beta' } ) );
|
|
948
|
+
|
|
949
|
+
// Make sure that only the selected tab has the active class
|
|
950
|
+
expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
|
|
951
|
+
expect( getSelectedTab() ).toHaveClass( activeClass );
|
|
952
|
+
screen
|
|
953
|
+
.getAllByRole( 'tab', { selected: false } )
|
|
954
|
+
.forEach( ( unselectedTab ) => {
|
|
955
|
+
expect( unselectedTab ).not.toHaveClass( activeClass );
|
|
956
|
+
} );
|
|
425
957
|
} );
|
|
426
958
|
} );
|
|
427
959
|
} );
|