@@ -273,6 +273,14 @@ async function installAuthenticatedMatrixMock(page: Page): Promise<void> {
273273 }
274274 case 'matrix_typing_users' :
275275 return [ ]
276+ case 'matrix_room_pins' :
277+ return {
278+ roomId : String ( args . roomId ) ,
279+ eventIds : [ ] ,
280+ messages : [ ] ,
281+ unavailableEventIds : [ ] ,
282+ canManage : true ,
283+ }
276284 case 'matrix_mark_read' :
277285 case 'matrix_set_typing' :
278286 case 'matrix_save_composer_draft' :
@@ -423,14 +431,38 @@ test.describe('authenticated desktop shell', () => {
423431
424432 const context = page . getByRole ( 'complementary' , { name : 'Room context for general' } )
425433 await expect ( context ) . toBeVisible ( )
426- await expect ( context . getByRole ( 'tab' , { name : 'Ledger' } ) ) . toHaveAttribute ( 'aria-selected' , 'true' )
434+ const peopleTab = context . getByRole ( 'tab' , { name : 'People' } )
435+ const ledgerTab = context . getByRole ( 'tab' , { name : 'Ledger' } )
436+ const filesTab = context . getByRole ( 'tab' , { name : 'Files' } )
437+ const pinsTab = context . getByRole ( 'tab' , { name : 'Pins' } )
438+ await expect ( ledgerTab ) . toHaveAttribute ( 'aria-selected' , 'true' )
439+ await expect ( ledgerTab ) . toHaveAttribute ( 'tabindex' , '0' )
440+ await expect ( peopleTab ) . toHaveAttribute ( 'tabindex' , '-1' )
441+ await expect ( filesTab ) . toHaveAttribute ( 'tabindex' , '-1' )
442+ await expect ( pinsTab ) . toHaveAttribute ( 'tabindex' , '-1' )
427443 await expect ( context . getByText ( 'Protected end to end' ) ) . toBeVisible ( )
428444 await expect ( context . getByText ( '1 need review' ) ) . toBeVisible ( )
429445 await expect ( context . getByText ( 'Ready' , { exact : true } ) ) . toBeVisible ( )
430446
431- await context . getByRole ( 'tab' , { name : 'Pins' } ) . click ( )
447+ await ledgerTab . focus ( )
448+ await page . keyboard . press ( 'ArrowRight' )
449+ await expect ( filesTab ) . toBeFocused ( )
450+ await expect ( filesTab ) . toHaveAttribute ( 'aria-selected' , 'true' )
451+ await expect ( context . getByRole ( 'tabpanel' ) ) . toHaveAttribute (
452+ 'aria-labelledby' ,
453+ await filesTab . getAttribute ( 'id' ) ?? '' ,
454+ )
455+ await page . keyboard . press ( 'End' )
456+ await expect ( pinsTab ) . toBeFocused ( )
457+ await expect ( pinsTab ) . toHaveAttribute ( 'aria-selected' , 'true' )
432458 await expect ( context . getByText ( 'Nothing pinned yet' ) ) . toBeVisible ( )
433- await context . getByRole ( 'tab' , { name : 'Files' } ) . click ( )
459+ await page . keyboard . press ( 'Home' )
460+ await expect ( peopleTab ) . toBeFocused ( )
461+ await expect ( peopleTab ) . toHaveAttribute ( 'aria-selected' , 'true' )
462+ await page . keyboard . press ( 'ArrowLeft' )
463+ await expect ( pinsTab ) . toBeFocused ( )
464+ await page . keyboard . press ( 'ArrowLeft' )
465+ await expect ( filesTab ) . toBeFocused ( )
434466 await expect ( context . getByText ( 'No files shared yet' ) ) . toBeVisible ( )
435467
436468 await context . getByRole ( 'button' , { name : 'Close room context' } ) . click ( )
@@ -473,6 +505,27 @@ test.describe('authenticated desktop shell', () => {
473505 } )
474506 } )
475507
508+ test ( 'hands Security focus back to the persistent User Settings trigger' , async ( { page } ) => {
509+ await openAuthenticatedShell ( page )
510+
511+ const settingsButton = page . getByRole ( 'button' , { name : 'User settings' } )
512+ await settingsButton . click ( )
513+ const settingsDialog = page . getByRole ( 'dialog' , { name : 'User Settings' } )
514+ await expect ( settingsDialog ) . toBeVisible ( )
515+
516+ await settingsDialog . getByRole ( 'button' , { name : 'Open your devices' } ) . click ( )
517+ const securityDialog = page . getByRole ( 'dialog' , { name : 'Your devices' } )
518+ await expect ( settingsDialog ) . toHaveCount ( 0 )
519+ await expect ( securityDialog ) . toBeVisible ( )
520+ await expect ( page . getByRole ( 'dialog' ) ) . toHaveCount ( 1 )
521+
522+ await page . keyboard . press ( 'Escape' )
523+
524+ await expect ( securityDialog ) . toHaveCount ( 0 )
525+ await expect ( settingsButton ) . toBeFocused ( )
526+ expect ( await page . evaluate ( ( ) => document . activeElement === document . body ) ) . toBe ( false )
527+ } )
528+
476529 test ( 'opens community management as an accessible sheet and restores focus' , async ( { page } ) => {
477530 await openAuthenticatedShell ( page )
478531
@@ -567,16 +620,42 @@ test.describe('authenticated desktop shell', () => {
567620} )
568621
569622test . describe ( 'authenticated narrow shell' , ( ) => {
570- test . use ( { viewport : { width : 390 , height : 844 } } )
623+ test . use ( { viewport : { width : 390 , height : 844 } , hasTouch : true } )
571624
572625 test ( '@a11y has no automated WCAG A/AA violations with navigation open' , async ( { page } ) => {
573626 await openAuthenticatedShell ( page )
574627 await page . getByRole ( 'button' , { name : 'Open room navigation' } ) . click ( )
575628 await expect ( page . getByRole ( 'button' , { name : 'Close room navigation' } ) ) . toBeVisible ( )
629+ const navigationDrawer = page . locator ( '#mesh-context-sidebar' )
630+ await expect ( navigationDrawer ) . toHaveAttribute ( 'aria-modal' , 'true' )
631+ expect ( await navigationDrawer . evaluate ( ( drawer ) => drawer . contains ( document . activeElement ) ) ) . toBe ( true )
632+ for ( let index = 0 ; index < 10 ; index += 1 ) await page . keyboard . press ( 'Tab' )
633+ expect ( await navigationDrawer . evaluate ( ( drawer ) => drawer . contains ( document . activeElement ) ) ) . toBe ( true )
576634
577635 await expectNoWcagViolations ( page , 'Authenticated narrow shell with room navigation' )
578636 } )
579637
638+ test ( 'closes room navigation with Escape and restores its trigger' , async ( { page } ) => {
639+ await openAuthenticatedShell ( page )
640+ const drawerButton = page . getByRole ( 'button' , { name : 'Open room navigation' } )
641+ await drawerButton . click ( )
642+ await expect ( page . locator ( '#mesh-context-sidebar' ) ) . toHaveAttribute ( 'data-open' , 'true' )
643+
644+ await page . keyboard . press ( 'Escape' )
645+
646+ await expect ( page . locator ( '#mesh-context-sidebar' ) ) . toHaveAttribute ( 'data-open' , 'false' )
647+ await expect ( drawerButton ) . toBeFocused ( )
648+ } )
649+
650+ test ( 'keeps message actions reachable on touch-only layouts' , async ( { page } ) => {
651+ await openAuthenticatedShell ( page )
652+ const actions = page . locator ( '.mesh-message-actions' ) . first ( )
653+
654+ await expect ( actions ) . toHaveCSS ( 'opacity' , '1' )
655+ await expect ( actions ) . toHaveCSS ( 'pointer-events' , 'auto' )
656+ await expect ( actions . getByRole ( 'button' , { name : / R e a c t t o m e s s a g e / } ) ) . toBeVisible ( )
657+ } )
658+
580659 test ( 'opens the room drawer, changes rooms, and sends a message without overflow' , async ( { page } ) => {
581660 await openAuthenticatedShell ( page )
582661
@@ -617,7 +696,20 @@ test.describe('authenticated narrow shell', () => {
617696 const context = page . getByRole ( 'complementary' , { name : 'Room context for general' } )
618697 await expect ( context ) . toBeVisible ( )
619698 await expect ( page . locator ( '.mesh-room-context-backdrop' ) ) . toBeVisible ( )
620- await context . getByRole ( 'tab' , { name : 'Ledger' } ) . click ( )
699+ expect ( await context . evaluate ( ( panel ) => panel . contains ( document . activeElement ) ) ) . toBe ( true )
700+ for ( let index = 0 ; index < 8 ; index += 1 ) await page . keyboard . press ( 'Tab' )
701+ expect ( await context . evaluate ( ( panel ) => panel . contains ( document . activeElement ) ) ) . toBe ( true )
702+ const ledgerTab = context . getByRole ( 'tab' , { name : 'Ledger' } )
703+ const peopleTab = context . getByRole ( 'tab' , { name : 'People' } )
704+ const pinsTab = context . getByRole ( 'tab' , { name : 'Pins' } )
705+ await ledgerTab . focus ( )
706+ await page . keyboard . press ( 'ArrowLeft' )
707+ await expect ( peopleTab ) . toBeFocused ( )
708+ await page . keyboard . press ( 'ArrowLeft' )
709+ await expect ( pinsTab ) . toBeFocused ( )
710+ await page . keyboard . press ( 'Home' )
711+ await expect ( peopleTab ) . toBeFocused ( )
712+ await ledgerTab . click ( )
621713 await expect ( context . getByText ( 'Protected end to end' ) ) . toBeVisible ( )
622714 await expectNoWcagViolations ( page , 'Authenticated narrow room context drawer' )
623715
@@ -643,6 +735,28 @@ test.describe('authenticated narrow shell', () => {
643735 await expect ( dialog ) . toHaveCount ( 0 )
644736 } )
645737
738+ test ( 'hands narrow Security focus back to the persistent User Settings trigger' , async ( { page } ) => {
739+ await openAuthenticatedShell ( page )
740+
741+ await page . getByRole ( 'button' , { name : 'Open room navigation' } ) . click ( )
742+ const settingsButton = page . getByRole ( 'button' , { name : 'User settings' } )
743+ await settingsButton . click ( )
744+ const settingsDialog = page . getByRole ( 'dialog' , { name : 'User Settings' } )
745+ await expect ( settingsDialog ) . toBeVisible ( )
746+
747+ await settingsDialog . getByRole ( 'button' , { name : 'Open your devices' } ) . click ( )
748+ const securityDialog = page . getByRole ( 'dialog' , { name : 'Your devices' } )
749+ await expect ( settingsDialog ) . toHaveCount ( 0 )
750+ await expect ( securityDialog ) . toBeVisible ( )
751+ await expect ( page . getByRole ( 'dialog' ) ) . toHaveCount ( 1 )
752+
753+ await page . keyboard . press ( 'Escape' )
754+
755+ await expect ( securityDialog ) . toHaveCount ( 0 )
756+ await expect ( settingsButton ) . toBeFocused ( )
757+ expect ( await page . evaluate ( ( ) => document . activeElement === document . body ) ) . toBe ( false )
758+ } )
759+
646760 test ( 'fits community management to the viewport and restores its drawer trigger' , async ( { page } ) => {
647761 await openAuthenticatedShell ( page )
648762
0 commit comments