11// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22// SPDX-License-Identifier: Apache-2.0
33
4- import React , { useContext , useEffect , useRef } from 'react' ;
5- import { render } from '@testing-library/react' ;
4+ import React , { useRef } from 'react' ;
5+ import { render , act } from '@testing-library/react' ;
66
7- import {
8- SingleTabStopNavigationAPI ,
9- SingleTabStopNavigationContext ,
10- SingleTabStopNavigationProvider ,
11- SingleTabStopNavigationReset ,
12- useSingleTabStopNavigation ,
13- } from '../' ;
7+ import { SingleTabStopNavigationContext , useSingleTabStopNavigation } from '../' ;
148import { renderWithSingleTabStopNavigation } from './utils' ;
159
16- // Simple STSN subscriber component
1710function Button ( props : React . HTMLAttributes < HTMLButtonElement > ) {
1811 const buttonRef = useRef < HTMLButtonElement > ( null ) ;
1912 const { tabIndex } = useSingleTabStopNavigation ( buttonRef , { tabIndex : props . tabIndex } ) ;
2013 return < button { ...props } ref = { buttonRef } tabIndex = { tabIndex } /> ;
2114}
2215
23- // Simple STSN provider component
24- function Group ( {
25- id,
26- navigationActive,
27- children,
28- } : {
29- id : string ;
30- navigationActive : boolean ;
31- children : React . ReactNode ;
32- } ) {
33- const navigationAPI = useRef < SingleTabStopNavigationAPI > ( null ) ;
34-
35- useEffect ( ( ) => {
36- navigationAPI . current ?. updateFocusTarget ( ) ;
37- } ) ;
38-
39- return (
40- < SingleTabStopNavigationProvider
41- ref = { navigationAPI }
42- navigationActive = { navigationActive }
43- getNextFocusTarget = { ( ) => document . querySelector ( `#${ id } ` ) ! . querySelectorAll ( 'button' ) [ 0 ] as HTMLElement }
44- >
45- < div id = { id } >
46- < Button > First</ Button >
47- < Button > Second</ Button >
48- { children }
49- </ div >
50- </ SingleTabStopNavigationProvider >
51- ) ;
52- }
53-
54- function findGroupButton ( groupId : string , buttonIndex : number ) {
55- return document . querySelector ( `#${ groupId } ` ) ! . querySelectorAll ( 'button' ) [ buttonIndex ] as HTMLElement ;
56- }
16+ test ( 'subscribed components can be rendered outside single tab stop navigation context' , ( ) => {
17+ render ( < Button /> ) ;
18+ expect ( document . querySelector ( 'button' ) ) . not . toHaveAttribute ( 'tabIndex' ) ;
19+ } ) ;
5720
5821test ( 'does not override tab index when keyboard navigation is not active' , ( ) => {
5922 renderWithSingleTabStopNavigation ( < Button id = "button" /> , { navigationActive : false } ) ;
@@ -71,11 +34,14 @@ test('does not override tab index for suppressed elements', () => {
7134 </ div > ,
7235 { navigationActive : true }
7336 ) ;
74- setCurrentTarget ( document . querySelector ( '#button1' ) , [
75- document . querySelector ( '#button1' ) ,
76- document . querySelector ( '#button2' ) ,
77- document . querySelector ( '#button3' ) ,
78- ] ) ;
37+ act ( ( ) => {
38+ setCurrentTarget ( document . querySelector ( '#button1' ) , [
39+ document . querySelector ( '#button1' ) ,
40+ document . querySelector ( '#button2' ) ,
41+ document . querySelector ( '#button3' ) ,
42+ ] ) ;
43+ } ) ;
44+
7945 expect ( document . querySelector ( '#button1' ) ) . toHaveAttribute ( 'tabIndex' , '0' ) ;
8046 expect ( document . querySelector ( '#button2' ) ) . toHaveAttribute ( 'tabIndex' , '0' ) ;
8147 expect ( document . querySelector ( '#button3' ) ) . toHaveAttribute ( 'tabIndex' , '-1' ) ;
@@ -90,7 +56,9 @@ test('overrides tab index when keyboard navigation is active', () => {
9056 < Button id = "button2" />
9157 </ div >
9258 ) ;
93- setCurrentTarget ( document . querySelector ( '#button1' ) ) ;
59+ act ( ( ) => {
60+ setCurrentTarget ( document . querySelector ( '#button1' ) ) ;
61+ } ) ;
9462 expect ( document . querySelector ( '#button1' ) ) . toHaveAttribute ( 'tabIndex' , '0' ) ;
9563 expect ( document . querySelector ( '#button2' ) ) . toHaveAttribute ( 'tabIndex' , '-1' ) ;
9664} ) ;
@@ -102,7 +70,9 @@ test('does not override explicit tab index with 0', () => {
10270 < Button id = "button2" tabIndex = { - 2 } />
10371 </ div >
10472 ) ;
105- setCurrentTarget ( document . querySelector ( '#button1' ) ) ;
73+ act ( ( ) => {
74+ setCurrentTarget ( document . querySelector ( '#button1' ) ) ;
75+ } ) ;
10676 expect ( document . querySelector ( '#button1' ) ) . toHaveAttribute ( 'tabIndex' , '-2' ) ;
10777 expect ( document . querySelector ( '#button2' ) ) . toHaveAttribute ( 'tabIndex' , '-2' ) ;
10878} ) ;
@@ -114,9 +84,7 @@ test('propagates and suppresses navigation active state', () => {
11484 }
11585 function Test ( { navigationActive } : { navigationActive : boolean } ) {
11686 return (
117- < SingleTabStopNavigationContext . Provider
118- value = { { navigationActive, registerFocusable : ( ) => ( ) => { } , resetFocusTarget : ( ) => { } } }
119- >
87+ < SingleTabStopNavigationContext . Provider value = { { navigationActive, registerFocusable : ( ) => ( ) => { } } } >
12088 < Component />
12189 </ SingleTabStopNavigationContext . Provider >
12290 ) ;
@@ -128,162 +96,3 @@ test('propagates and suppresses navigation active state', () => {
12896 rerender ( < Test navigationActive = { false } /> ) ;
12997 expect ( document . querySelector ( 'div' ) ) . toHaveTextContent ( 'false' ) ;
13098} ) ;
131-
132- test ( 'subscriber components can be used without provider' , ( ) => {
133- function TestComponent ( props : React . HTMLAttributes < HTMLButtonElement > ) {
134- const ref = useRef ( null ) ;
135- const contextResult = useContext ( SingleTabStopNavigationContext ) ;
136- const hookResult = useSingleTabStopNavigation ( ref , { tabIndex : props . tabIndex } ) ;
137- useEffect ( ( ) => {
138- contextResult . registerFocusable ( ref . current ! , ( ) => { } ) ;
139- contextResult . resetFocusTarget ( ) ;
140- } ) ;
141- return (
142- < div ref = { ref } >
143- Context: { `${ contextResult . navigationActive } ` } , Hook: { `${ hookResult . navigationActive } :${ hookResult . tabIndex } ` }
144- </ div >
145- ) ;
146- }
147- const { container } = render ( < TestComponent /> ) ;
148- expect ( container . textContent ) . toBe ( 'Context: false, Hook: false:undefined' ) ;
149- } ) ;
150-
151- describe ( 'nested contexts' , ( ) => {
152- test ( 'tab indices are distributed correctly when switching contexts from inner to outer' , ( ) => {
153- const { rerender } = render (
154- < Group id = "outer-most" navigationActive = { false } >
155- < Group id = "outer" navigationActive = { false } >
156- < Group id = "inner" navigationActive = { true } >
157- { null }
158- </ Group >
159- </ Group >
160- </ Group >
161- ) ;
162- expect ( findGroupButton ( 'outer-most' , 0 ) ) . not . toHaveAttribute ( 'tabindex' ) ;
163- expect ( findGroupButton ( 'outer-most' , 1 ) ) . not . toHaveAttribute ( 'tabindex' ) ;
164- expect ( findGroupButton ( 'outer' , 0 ) ) . not . toHaveAttribute ( 'tabindex' ) ;
165- expect ( findGroupButton ( 'outer' , 1 ) ) . not . toHaveAttribute ( 'tabindex' ) ;
166- expect ( findGroupButton ( 'inner' , 0 ) ) . toHaveAttribute ( 'tabindex' , '0' ) ;
167- expect ( findGroupButton ( 'inner' , 1 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
168-
169- rerender (
170- < Group id = "outer-most" navigationActive = { false } >
171- < Group id = "outer" navigationActive = { true } >
172- < Group id = "inner" navigationActive = { true } >
173- { null }
174- </ Group >
175- </ Group >
176- </ Group >
177- ) ;
178- expect ( findGroupButton ( 'outer-most' , 0 ) ) . not . toHaveAttribute ( 'tabindex' ) ;
179- expect ( findGroupButton ( 'outer-most' , 1 ) ) . not . toHaveAttribute ( 'tabindex' ) ;
180- expect ( findGroupButton ( 'outer' , 0 ) ) . toHaveAttribute ( 'tabindex' , '0' ) ;
181- expect ( findGroupButton ( 'outer' , 1 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
182- expect ( findGroupButton ( 'inner' , 0 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
183- expect ( findGroupButton ( 'inner' , 1 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
184-
185- rerender (
186- < Group id = "outer-most" navigationActive = { true } >
187- < Group id = "outer" navigationActive = { true } >
188- < Group id = "inner" navigationActive = { true } >
189- { null }
190- </ Group >
191- </ Group >
192- </ Group >
193- ) ;
194- expect ( findGroupButton ( 'outer-most' , 0 ) ) . toHaveAttribute ( 'tabindex' , '0' ) ;
195- expect ( findGroupButton ( 'outer-most' , 1 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
196- expect ( findGroupButton ( 'outer' , 0 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
197- expect ( findGroupButton ( 'outer' , 1 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
198- expect ( findGroupButton ( 'inner' , 0 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
199- expect ( findGroupButton ( 'inner' , 1 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
200- } ) ;
201-
202- test ( 'tab indices are distributed correctly when switching contexts from outer to inner' , ( ) => {
203- const { rerender } = render (
204- < Group id = "outer-most" navigationActive = { true } >
205- < Group id = "outer" navigationActive = { true } >
206- < Group id = "inner" navigationActive = { true } >
207- { null }
208- </ Group >
209- </ Group >
210- </ Group >
211- ) ;
212- expect ( findGroupButton ( 'outer-most' , 0 ) ) . toHaveAttribute ( 'tabindex' , '0' ) ;
213- expect ( findGroupButton ( 'outer-most' , 1 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
214- expect ( findGroupButton ( 'outer' , 0 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
215- expect ( findGroupButton ( 'outer' , 1 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
216- expect ( findGroupButton ( 'inner' , 0 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
217- expect ( findGroupButton ( 'inner' , 1 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
218-
219- rerender (
220- < Group id = "outer-most" navigationActive = { false } >
221- < Group id = "outer" navigationActive = { true } >
222- < Group id = "inner" navigationActive = { true } >
223- { null }
224- </ Group >
225- </ Group >
226- </ Group >
227- ) ;
228- expect ( findGroupButton ( 'outer-most' , 0 ) ) . not . toHaveAttribute ( 'tabindex' ) ;
229- expect ( findGroupButton ( 'outer-most' , 1 ) ) . not . toHaveAttribute ( 'tabindex' ) ;
230- expect ( findGroupButton ( 'outer' , 0 ) ) . toHaveAttribute ( 'tabindex' , '0' ) ;
231- expect ( findGroupButton ( 'outer' , 1 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
232- expect ( findGroupButton ( 'inner' , 0 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
233- expect ( findGroupButton ( 'inner' , 1 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
234-
235- rerender (
236- < Group id = "outer-most" navigationActive = { false } >
237- < Group id = "outer" navigationActive = { false } >
238- < Group id = "inner" navigationActive = { true } >
239- { null }
240- </ Group >
241- </ Group >
242- </ Group >
243- ) ;
244- expect ( findGroupButton ( 'outer-most' , 0 ) ) . not . toHaveAttribute ( 'tabindex' ) ;
245- expect ( findGroupButton ( 'outer-most' , 1 ) ) . not . toHaveAttribute ( 'tabindex' ) ;
246- expect ( findGroupButton ( 'outer' , 0 ) ) . not . toHaveAttribute ( 'tabindex' ) ;
247- expect ( findGroupButton ( 'outer' , 1 ) ) . not . toHaveAttribute ( 'tabindex' ) ;
248- expect ( findGroupButton ( 'inner' , 0 ) ) . toHaveAttribute ( 'tabindex' , '0' ) ;
249- expect ( findGroupButton ( 'inner' , 1 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
250- } ) ;
251-
252- test ( 'ignores parent context when reset is used' , ( ) => {
253- const { rerender } = render (
254- < Group id = "outer-most" navigationActive = { true } >
255- < SingleTabStopNavigationReset >
256- < Group id = "outer" navigationActive = { true } >
257- < Group id = "inner" navigationActive = { true } >
258- { null }
259- </ Group >
260- </ Group >
261- </ SingleTabStopNavigationReset >
262- </ Group >
263- ) ;
264- expect ( findGroupButton ( 'outer-most' , 0 ) ) . toHaveAttribute ( 'tabindex' , '0' ) ;
265- expect ( findGroupButton ( 'outer-most' , 1 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
266- expect ( findGroupButton ( 'outer' , 0 ) ) . toHaveAttribute ( 'tabindex' , '0' ) ;
267- expect ( findGroupButton ( 'outer' , 1 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
268- expect ( findGroupButton ( 'inner' , 0 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
269- expect ( findGroupButton ( 'inner' , 1 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
270-
271- rerender (
272- < Group id = "outer-most" navigationActive = { true } >
273- < Group id = "outer" navigationActive = { true } >
274- < SingleTabStopNavigationReset >
275- < Group id = "inner" navigationActive = { true } >
276- { null }
277- </ Group >
278- </ SingleTabStopNavigationReset >
279- </ Group >
280- </ Group >
281- ) ;
282- expect ( findGroupButton ( 'outer-most' , 0 ) ) . toHaveAttribute ( 'tabindex' , '0' ) ;
283- expect ( findGroupButton ( 'outer-most' , 1 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
284- expect ( findGroupButton ( 'outer' , 0 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
285- expect ( findGroupButton ( 'outer' , 1 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
286- expect ( findGroupButton ( 'inner' , 0 ) ) . toHaveAttribute ( 'tabindex' , '0' ) ;
287- expect ( findGroupButton ( 'inner' , 1 ) ) . toHaveAttribute ( 'tabindex' , '-1' ) ;
288- } ) ;
289- } ) ;
0 commit comments