@@ -7,7 +7,7 @@ import { TabTitleText } from '../TabTitleText';
77import { TabTitleIcon } from '../TabTitleIcon' ;
88import { TabContent } from '../TabContent' ;
99import { TabContentBody } from '../TabContentBody' ;
10- import { createRef } from 'react' ;
10+ import { createRef , useState } from 'react' ;
1111
1212jest . mock ( '../../../helpers/GenerateId/GenerateId' ) ;
1313
@@ -119,6 +119,130 @@ test(`Does not render with class ${styles.modifiers.initializingAccent} when com
119119 jest . useRealTimers ( ) ;
120120} ) ;
121121
122+ describe ( 'hash-based nav selection' , ( ) => {
123+ beforeEach ( ( ) => {
124+ window . location . hash = '#/items/2' ;
125+ } ) ;
126+
127+ afterEach ( ( ) => {
128+ window . location . hash = '' ;
129+ } ) ;
130+
131+ test ( 'should select the nav tab that matches the current URL hash on initial render' , ( ) => {
132+ render (
133+ < Tabs activeKey = { 0 } onSelect = { ( ) => undefined } component = "nav" >
134+ < Tab eventKey = { 0 } title = { < TabTitleText > Tab item 1</ TabTitleText > } href = "#/items/1" >
135+ Tab item 1
136+ </ Tab >
137+ < Tab eventKey = { 1 } title = { < TabTitleText > Tab item 2</ TabTitleText > } href = "#/items/2" >
138+ Tab item 2
139+ </ Tab >
140+ < Tab eventKey = { 2 } title = { < TabTitleText > Tab item 3</ TabTitleText > } href = "#/items/3" >
141+ Tab item 3
142+ </ Tab >
143+ </ Tabs >
144+ ) ;
145+
146+ expect ( screen . getByRole ( 'tab' , { name : 'Tab item 2' } ) ) . toHaveAttribute ( 'aria-selected' , 'true' ) ;
147+ expect ( screen . getByRole ( 'tab' , { name : 'Tab item 1' } ) ) . toHaveAttribute ( 'aria-selected' , 'false' ) ;
148+ } ) ;
149+
150+ test ( 'should respect later controlled selections after the initial hash match' , async ( ) => {
151+ const user = userEvent . setup ( ) ;
152+ const ControlledTabs = ( ) => {
153+ const [ activeKey , setActiveKey ] = useState < string | number > ( 0 ) ;
154+
155+ return (
156+ < Tabs activeKey = { activeKey } onSelect = { ( _event , eventKey ) => setActiveKey ( eventKey ) } component = "nav" >
157+ < Tab eventKey = { 0 } title = { < TabTitleText > Tab item 1</ TabTitleText > } href = "#/items/1" >
158+ Tab item 1
159+ </ Tab >
160+ < Tab eventKey = { 1 } title = { < TabTitleText > Tab item 2</ TabTitleText > } href = "#/items/2" >
161+ Tab item 2
162+ </ Tab >
163+ < Tab eventKey = { 2 } title = { < TabTitleText > Tab item 3</ TabTitleText > } href = "#/items/3" >
164+ Tab item 3
165+ </ Tab >
166+ </ Tabs >
167+ ) ;
168+ } ;
169+
170+ render ( < ControlledTabs /> ) ;
171+
172+ expect ( screen . getByRole ( 'tab' , { name : 'Tab item 2' } ) ) . toHaveAttribute ( 'aria-selected' , 'true' ) ;
173+
174+ await user . click ( screen . getByRole ( 'tab' , { name : 'Tab item 3' } ) ) ;
175+
176+ expect ( screen . getByRole ( 'tab' , { name : 'Tab item 3' } ) ) . toHaveAttribute ( 'aria-selected' , 'true' ) ;
177+ expect ( screen . getByRole ( 'tab' , { name : 'Tab item 2' } ) ) . toHaveAttribute ( 'aria-selected' , 'false' ) ;
178+ } ) ;
179+
180+ test ( 'should use the URL hash to initialize uncontrolled nav tabs' , async ( ) => {
181+ const user = userEvent . setup ( ) ;
182+
183+ render (
184+ < Tabs defaultActiveKey = { 0 } isNav >
185+ < Tab eventKey = { 0 } title = { < TabTitleText > Tab item 1</ TabTitleText > } href = "#/items/1" >
186+ Tab item 1
187+ </ Tab >
188+ < Tab eventKey = { 1 } title = { < TabTitleText > Tab item 2</ TabTitleText > } href = "#/items/2" >
189+ Tab item 2
190+ </ Tab >
191+ < Tab eventKey = { 2 } title = { < TabTitleText > Tab item 3</ TabTitleText > } href = "#/items/3" >
192+ Tab item 3
193+ </ Tab >
194+ </ Tabs >
195+ ) ;
196+
197+ expect ( screen . getByRole ( 'tab' , { name : 'Tab item 2' } ) ) . toHaveAttribute ( 'aria-selected' , 'true' ) ;
198+
199+ await user . click ( screen . getByRole ( 'tab' , { name : 'Tab item 1' } ) ) ;
200+
201+ expect ( screen . getByRole ( 'tab' , { name : 'Tab item 1' } ) ) . toHaveAttribute ( 'aria-selected' , 'true' ) ;
202+ expect ( screen . getByRole ( 'tab' , { name : 'Tab item 2' } ) ) . toHaveAttribute ( 'aria-selected' , 'false' ) ;
203+ } ) ;
204+
205+ test ( 'should ignore hidden, disabled and aria-disabled tabs when matching the current hash' , ( ) => {
206+ render (
207+ < Tabs activeKey = { 0 } onSelect = { ( ) => undefined } component = "nav" >
208+ < Tab eventKey = { 0 } title = { < TabTitleText > Tab item 1</ TabTitleText > } href = "#/items/1" >
209+ Tab item 1
210+ </ Tab >
211+ < Tab eventKey = { 1 } title = { < TabTitleText > Hidden tab</ TabTitleText > } href = "#/items/2" isHidden >
212+ Hidden tab
213+ </ Tab >
214+ < Tab eventKey = { 2 } title = { < TabTitleText > Disabled tab</ TabTitleText > } href = "#/items/2" isDisabled >
215+ Disabled tab
216+ </ Tab >
217+ < Tab eventKey = { 3 } title = { < TabTitleText > Aria disabled tab</ TabTitleText > } href = "#/items/2" isAriaDisabled >
218+ Aria disabled tab
219+ </ Tab >
220+ </ Tabs >
221+ ) ;
222+
223+ expect ( screen . queryByRole ( 'tab' , { name : 'Hidden tab' } ) ) . not . toBeInTheDocument ( ) ;
224+ expect ( screen . getByRole ( 'tab' , { name : 'Tab item 1' } ) ) . toHaveAttribute ( 'aria-selected' , 'true' ) ;
225+ expect ( screen . getByRole ( 'tab' , { name : 'Disabled tab' } ) ) . toHaveAttribute ( 'aria-selected' , 'false' ) ;
226+ expect ( screen . getByRole ( 'tab' , { name : 'Aria disabled tab' } ) ) . toHaveAttribute ( 'aria-selected' , 'false' ) ;
227+ } ) ;
228+
229+ test ( 'should ignore the current URL hash when nav behavior is not enabled' , ( ) => {
230+ render (
231+ < Tabs activeKey = { 0 } onSelect = { ( ) => undefined } >
232+ < Tab eventKey = { 0 } title = { < TabTitleText > Tab item 1</ TabTitleText > } href = "#/items/1" >
233+ Tab item 1
234+ </ Tab >
235+ < Tab eventKey = { 1 } title = { < TabTitleText > Tab item 2</ TabTitleText > } href = "#/items/2" >
236+ Tab item 2
237+ </ Tab >
238+ </ Tabs >
239+ ) ;
240+
241+ expect ( screen . getByRole ( 'tab' , { name : 'Tab item 1' } ) ) . toHaveAttribute ( 'aria-selected' , 'true' ) ;
242+ expect ( screen . getByRole ( 'tab' , { name : 'Tab item 2' } ) ) . toHaveAttribute ( 'aria-selected' , 'false' ) ;
243+ } ) ;
244+ } ) ;
245+
122246test ( `Renders with class ${ styles . modifiers . initializingAccent } when uncontrolled expandable component initially mounts` , async ( ) => {
123247 const user = userEvent . setup ( { advanceTimers : jest . advanceTimersByTime } ) ;
124248
0 commit comments