@@ -112,12 +112,74 @@ describe('TabStrip interactions', () => {
112112 expect ( onSelect ) . toHaveBeenLastCalledWith ( 'pinned' , 'keyboard' )
113113
114114 act ( ( ) => {
115+ tabButton ( 'two' ) . focus ( )
115116 tabButton ( 'two' ) . dispatchEvent (
116117 new KeyboardEvent ( 'keydown' , { key : 'Delete' , bubbles : true , cancelable : true } )
117118 )
118119 } )
119120 expect ( onClose ) . toHaveBeenCalledWith ( 'two' )
121+ expect ( document . activeElement ) . toBe ( tabButton ( 'two' ) )
122+ } )
123+
124+ it ( 'restores focus to a committed survivor after an asynchronous multi-tab close' , ( ) => {
125+ const onClose = vi . fn ( )
126+ mount ( renderStrip ( tabs , vi . fn ( ) , onClose ) )
127+ act ( ( ) => {
128+ tabButton ( 'one' ) . focus ( )
129+ tabButton ( 'one' ) . dispatchEvent ( new KeyboardEvent ( 'keydown' , { key : 'Delete' , bubbles : true } ) )
130+ } )
120131 expect ( document . activeElement ) . toBe ( tabButton ( 'one' ) )
132+ act ( ( ) => root ?. render ( renderStrip ( tabs , vi . fn ( ) , onClose ) ) )
133+ expect ( document . activeElement ) . toBe ( tabButton ( 'one' ) )
134+
135+ act ( ( ) => root ?. render ( renderStrip ( [ { ...tabs [ 0 ] , active : true } ] , vi . fn ( ) , onClose ) ) )
136+ expect ( document . activeElement ) . toBe ( tabButton ( 'pinned' ) )
137+ } )
138+
139+ it ( 'does not steal focus from another control when a pending close completes' , ( ) => {
140+ mount ( renderStrip ( tabs ) )
141+ act ( ( ) => {
142+ tabButton ( 'one' ) . focus ( )
143+ tabButton ( 'one' ) . dispatchEvent ( new KeyboardEvent ( 'keydown' , { key : 'Delete' , bubbles : true } ) )
144+ } )
145+ const add = container ?. querySelector < HTMLButtonElement > ( '[aria-label="New tab"]' )
146+ act ( ( ) => add ?. focus ( ) )
147+ act ( ( ) => root ?. render ( renderStrip ( [ { ...tabs [ 0 ] , active : true } ] ) ) )
148+ expect ( document . activeElement ) . toBe ( add )
149+ } )
150+
151+ it ( 'exposes a shared DOM ancestor for multi-tab drag images' , ( ) => {
152+ const onTabDragStart = vi . fn ( ( event ) => {
153+ const strip = event . currentTarget . closest ( '[data-tab-strip]' )
154+ expect ( strip ?. querySelectorAll ( '[data-tab-strip-item]' ) ) . toHaveLength ( 3 )
155+ } )
156+ mount ( < TabStrip tabs = { tabs } onSelect = { vi . fn ( ) } onTabDragStart = { onTabDragStart } /> )
157+ act ( ( ) => stripItem ( 'two' ) . dispatchEvent ( dragStartEvent ( ) ) )
158+ expect ( onTabDragStart ) . toHaveBeenCalledOnce ( )
159+ } )
160+
161+ it ( 'follows the active survivor across staggered tab removals' , ( ) => {
162+ mount ( renderStrip ( tabs ) )
163+ act ( ( ) => tabButton ( 'one' ) . focus ( ) )
164+ act ( ( ) => root ?. render ( renderStrip ( [ tabs [ 0 ] , { ...tabs [ 2 ] , active : true } ] ) ) )
165+ expect ( document . activeElement ) . toBe ( tabButton ( 'two' ) )
166+ act ( ( ) => root ?. render ( renderStrip ( [ { ...tabs [ 0 ] , active : true } ] ) ) )
167+ expect ( document . activeElement ) . toBe ( tabButton ( 'pinned' ) )
168+ } )
169+
170+ it ( 'relinquishes focus ownership when the user leaves the strip' , ( ) => {
171+ mount ( renderStrip ( tabs ) )
172+ const outside = document . createElement ( 'button' )
173+ document . body . append ( outside )
174+ try {
175+ act ( ( ) => tabButton ( 'one' ) . focus ( ) )
176+ act ( ( ) => outside . focus ( ) )
177+ act ( ( ) => outside . blur ( ) )
178+ act ( ( ) => root ?. render ( renderStrip ( [ { ...tabs [ 0 ] , active : true } ] ) ) )
179+ expect ( document . activeElement ) . toBe ( document . body )
180+ } finally {
181+ outside . remove ( )
182+ }
121183 } )
122184
123185 it ( 'identifies pointer selection separately from keyboard navigation' , ( ) => {
0 commit comments