@@ -35,6 +35,18 @@ const {
3535 TABLE_ROW_NAVIGABLE : { defaultText : NAVIGABLE } ,
3636 TABLE_ROW_NAVIGATED : { defaultText : NAVIGATED } ,
3737 TABLE_ROW_ACTIVE : { defaultText : ACTIVE } ,
38+ TABLE_ROW_ACTION : { defaultText : ACTION_TEMPLATE } ,
39+ TABLE_ROW_ACTIONS_LIST : { defaultText : ACTIONS_LIST_TEMPLATE } ,
40+ TABLE_ROW_MORE_ACTIONS : { defaultText : MORE_ACTIONS } ,
41+ TABLE_ENTERING : { defaultText : ENTERING_TEMPLATE } ,
42+ TABLE_ENTERING_MULTI_SELECTABLE : { defaultText : MULTI_SELECTABLE } ,
43+ TABLE_ENTERING_SELECTED : { defaultText : ENTERING_SELECTED_TEMPLATE } ,
44+ TABLE_ROW_SELECTED_LIVE : { defaultText : SELECTED_LIVE_TEMPLATE } ,
45+ TABLE_ROW_NOT_SELECTED_LIVE : { defaultText : NOT_SELECTED_LIVE_TEMPLATE } ,
46+ TABLE_HIGHLIGHT_NEGATIVE : { defaultText : HIGHLIGHT_NEGATIVE } ,
47+ TABLE_HIGHLIGHT_CRITICAL : { defaultText : HIGHLIGHT_CRITICAL } ,
48+ TABLE_HIGHLIGHT_POSITIVE : { defaultText : HIGHLIGHT_POSITIVE } ,
49+ TABLE_HIGHLIGHT_INFORMATION : { defaultText : HIGHLIGHT_INFORMATION } ,
3850} = Translations ;
3951
4052describe ( "Cell Custom Announcement - More details" , ( ) => {
@@ -301,28 +313,23 @@ describe("Row Custom Announcement - Less details", () => {
301313
302314 it ( "should announce table rows" , ( ) => {
303315 cy . get ( "@row1" ) . realClick ( ) ;
304- checkAnnouncement ( `Row . 2 of 2 . ${ SELECTED } . ${ NAVIGABLE } . H1` ) ;
305- checkAnnouncement ( `H1 . R1C1 . H2 . ${ CONTAINS_CONTROLS } . H3 . ${ EMPTY } . H4 . C4 Button C4Button` ) ;
306- checkAnnouncement ( ONE_ROW_ACTION ) ;
307- cy . focused ( ) . should ( "have.attr" , "aria-rowindex" , "2" )
308- . should ( "have.attr" , "role" , "row" ) ;
316+ // No identifier column → legacy format: Row → position → selected → navigable/active → cells → actions → navigated
317+ checkAnnouncement ( `Row . 2 of 2 . ${ SELECTED } . ${ NAVIGABLE } . H1 . R1C1 . H2 . ${ CONTAINS_CONTROLS } . H3 . ${ EMPTY } . H4 . C4 Button C4Button . ${ ONE_ROW_ACTION } . ${ NAVIGATED } ` ) ;
309318
310319 cy . get ( "#selection" ) . invoke ( "attr" , "selected" , "" ) ;
311- checkAnnouncement ( `Row . 2 of 2 . ${ NAVIGABLE } ` , true ) ;
320+ checkAnnouncement ( `Row . 2 of 2 . ${ NAVIGABLE } . H1 . R1C1 . H2 . ${ CONTAINS_CONTROLS } . H3 . ${ EMPTY } . H4 . C4 Button C4Button . ${ ONE_ROW_ACTION } . ${ NAVIGATED } ` , true ) ;
312321
313322 cy . get ( "#row1-nav-action" ) . invoke ( "prop" , "interactive" , true ) ;
314- checkAnnouncement ( `Row . 2 of 2 . ${ ACTIVE } . H1` , true ) ;
315- checkAnnouncement ( Table . i18nBundle . getText ( MULTIPLE_ACTIONS , 2 ) ) ;
323+ checkAnnouncement ( `Row . 2 of 2 . ${ ACTIVE } . H1 . R1C1 . H2 . ${ CONTAINS_CONTROLS } . H3 . ${ EMPTY } . H4 . C4 Button C4Button . ${ MULTIPLE_ACTIONS . replace ( "{0}" , 2 ) } . ${ NAVIGATED } ` , true ) ;
316324
317325 cy . get ( "@row1" ) . invoke ( "prop" , "interactive" , false ) ;
318- checkAnnouncement ( `Row . 2 of 2 . H1` , true ) ;
326+ checkAnnouncement ( `Row . 2 of 2 . H1 . R1C1 . H2 . ${ CONTAINS_CONTROLS } . H3 . ${ EMPTY } . H4 . C4 Button C4Button . ${ MULTIPLE_ACTIONS . replace ( "{0}" , 2 ) } . ${ NAVIGATED } ` , true ) ;
319327
320328 cy . get ( "#table0" ) . invoke ( "css" , "width" , "301px" ) ;
321- checkAnnouncement ( `Row . 2 of 2 . H1` , true ) ;
322- checkAnnouncement ( `H1 . R1C1 . H2 . ${ CONTAINS_CONTROLS } . H3 . ${ EMPTY } . H4Popin . C4 Button C4Button` ) ;
329+ checkAnnouncement ( `Row . 2 of 2 . H1 . R1C1 . H2 . ${ CONTAINS_CONTROLS } . H3 . ${ EMPTY } . H4Popin . C4 Button C4Button . ${ MULTIPLE_ACTIONS . replace ( "{0}" , 2 ) } . ${ NAVIGATED } ` , true ) ;
323330
324331 cy . get ( "#Header3" ) . invoke ( "prop" , "popinHidden" , true ) ;
325- checkAnnouncement ( `H1 . R1C1 . H2 . ${ CONTAINS_CONTROLS } . H4Popin . C4 Button C4Button` , true ) ;
332+ checkAnnouncement ( `Row . 2 of 2 . H1 . R1C1 . H2 . ${ CONTAINS_CONTROLS } . H4Popin . C4 Button C4Button . ${ MULTIPLE_ACTIONS . replace ( "{0}" , 2 ) } . ${ NAVIGATED } ` , true ) ;
326333
327334 cy . get ( "#row1-nav-action" ) . invoke ( "remove" ) ;
328335 cy . get ( "#row1-add-action" ) . invoke ( "remove" ) ;
@@ -423,3 +430,219 @@ describe("Row Custom Announcement - Less details", () => {
423430 } ) ;
424431 } ) ;
425432} ) ;
433+
434+ describe ( "Identifier Column Announcement" , ( ) => {
435+ function checkAnnouncement ( expectedText : string , check = "equal" ) {
436+ cy . get ( "body" ) . then ( $body => {
437+ expect ( $body . find ( "#ui5-invisible-text" ) . text ( ) ) [ check ] ( expectedText ) ;
438+ } ) ;
439+ }
440+
441+ it ( "should announce only identifier column when set" , ( ) => {
442+ cy . mount (
443+ < Table id = "table0" >
444+ < TableHeaderRow slot = "headerRow" >
445+ < TableHeaderCell id = "docNumHeader" identifier > Document Number</ TableHeaderCell >
446+ < TableHeaderCell > Company</ TableHeaderCell >
447+ < TableHeaderCell > City</ TableHeaderCell >
448+ </ TableHeaderRow >
449+ < TableRow rowKey = "Row1" >
450+ < TableCell > 305382373</ TableCell >
451+ < TableCell > SAP SE</ TableCell >
452+ < TableCell > Walldorf</ TableCell >
453+ </ TableRow >
454+ < TableRow rowKey = "Row2" >
455+ < TableCell > 123456789</ TableCell >
456+ < TableCell > Acme Corp</ TableCell >
457+ < TableCell > Berlin</ TableCell >
458+ </ TableRow >
459+ </ Table >
460+ ) ;
461+
462+ // First focus on row1 — entering text + identifier + Row + position
463+ cy . get ( "[ui5-table-row]" ) . first ( ) . realClick ( ) ;
464+ checkAnnouncement ( "with 2 rows" , "contains" ) ;
465+ checkAnnouncement ( "Document Number 305382373 . Row . 2 of 3" , "contains" ) ;
466+
467+ // Focus row2 — no entering announcement
468+ cy . realPress ( "ArrowDown" ) ;
469+ checkAnnouncement ( "Document Number 123456789 . Row . 3 of 3" ) ;
470+
471+ // Remove identifier — fallback to legacy format (Row first)
472+ cy . get ( "#docNumHeader" ) . invoke ( "prop" , "identifier" , false ) ;
473+ cy . realPress ( "ArrowUp" ) ;
474+ cy . realPress ( "ArrowDown" ) ;
475+ checkAnnouncement ( "Row . 3 of 3 . Document Number . 123456789 . Company . Acme Corp . City . Berlin" ) ;
476+ } ) ;
477+
478+ it ( "should set role=rowheader on identifier column cells" , ( ) => {
479+ cy . mount (
480+ < Table id = "table0" >
481+ < TableHeaderRow slot = "headerRow" >
482+ < TableHeaderCell identifier > ID</ TableHeaderCell >
483+ < TableHeaderCell > Name</ TableHeaderCell >
484+ </ TableHeaderRow >
485+ < TableRow rowKey = "Row1" >
486+ < TableCell id = "idCell" > 001</ TableCell >
487+ < TableCell id = "nameCell" > Alice</ TableCell >
488+ </ TableRow >
489+ </ Table >
490+ ) ;
491+
492+ cy . get ( "#idCell" ) . should ( "have.attr" , "role" , "rowheader" ) ;
493+ cy . get ( "#nameCell" ) . should ( "have.attr" , "role" , "gridcell" ) ;
494+ } ) ;
495+ } ) ;
496+
497+ describe ( "Row Highlight Announcement" , ( ) => {
498+ function checkAnnouncement ( expectedText : string , focusAgain = false , check = "equal" ) {
499+ if ( focusAgain ) {
500+ cy . realPress ( "ArrowUp" ) ;
501+ cy . realPress ( "ArrowDown" ) ;
502+ }
503+
504+ cy . get ( "body" ) . then ( $body => {
505+ expect ( $body . find ( "#ui5-invisible-text" ) . text ( ) ) [ check ] ( expectedText ) ;
506+ } ) ;
507+ }
508+
509+ it ( "should announce highlight state and text" , ( ) => {
510+ cy . mount (
511+ < Table id = "table0" >
512+ < TableHeaderRow slot = "headerRow" >
513+ < TableHeaderCell identifier > Order</ TableHeaderCell >
514+ < TableHeaderCell > Status</ TableHeaderCell >
515+ </ TableHeaderRow >
516+ < TableRow id = "row1" rowKey = "Row1" highlight = "Negative" highlightText = "Cancelled" >
517+ < TableCell > 12345</ TableCell >
518+ < TableCell > Cancelled</ TableCell >
519+ </ TableRow >
520+ < TableRow id = "row2" rowKey = "Row2" highlight = "Critical" highlightText = "Return Initiated" >
521+ < TableCell > 67890</ TableCell >
522+ < TableCell > Pending</ TableCell >
523+ </ TableRow >
524+ < TableRow id = "row3" rowKey = "Row3" highlight = "Positive" >
525+ < TableCell > 11111</ TableCell >
526+ < TableCell > Complete</ TableCell >
527+ </ TableRow >
528+ < TableRow id = "row4" rowKey = "Row4" highlight = "Information" highlightText = "Unread" >
529+ < TableCell > 22222</ TableCell >
530+ < TableCell > New</ TableCell >
531+ </ TableRow >
532+ </ Table >
533+ ) ;
534+
535+ // Row 1: Negative + "Cancelled" (first focus includes entering text)
536+ cy . get ( "#row1" ) . realClick ( ) ;
537+ checkAnnouncement ( `Order 12345 . Row . ${ HIGHLIGHT_NEGATIVE } . Cancelled . 2 of 5` , false , "contains" ) ;
538+
539+ // Row 2: Critical + "Return Initiated"
540+ cy . realPress ( "ArrowDown" ) ;
541+ checkAnnouncement ( `Order 67890 . Row . ${ HIGHLIGHT_CRITICAL } . Return Initiated . 3 of 5` ) ;
542+
543+ // Row 3: Positive, no text
544+ cy . realPress ( "ArrowDown" ) ;
545+ checkAnnouncement ( `Order 11111 . Row . ${ HIGHLIGHT_POSITIVE } . 4 of 5` ) ;
546+
547+ // Row 4: Information + "Unread"
548+ cy . realPress ( "ArrowDown" ) ;
549+ checkAnnouncement ( `Order 22222 . Row . ${ HIGHLIGHT_INFORMATION } . Unread . 5 of 5` ) ;
550+
551+ // Change highlight to None — no highlight announcement
552+ cy . get ( "#row4" ) . invoke ( "prop" , "highlight" , "None" ) ;
553+ checkAnnouncement ( "Order 22222 . Row . 5 of 5" , true ) ;
554+ } ) ;
555+ } ) ;
556+
557+ describe ( "Entering-the-Table Announcement" , ( ) => {
558+ function checkAnnouncement ( expectedText : string , check = "contains" ) {
559+ cy . get ( "body" ) . then ( $body => {
560+ expect ( $body . find ( "#ui5-invisible-text" ) . text ( ) ) [ check ] ( expectedText ) ;
561+ } ) ;
562+ }
563+
564+ it ( "should announce table info on first focus only" , ( ) => {
565+ cy . mount (
566+ < Table id = "table0" >
567+ < TableSelectionMulti slot = "features" selected = "Row1" > </ TableSelectionMulti >
568+ < TableHeaderRow slot = "headerRow" >
569+ < TableHeaderCell identifier > Name</ TableHeaderCell >
570+ </ TableHeaderRow >
571+ < TableRow rowKey = "Row1" >
572+ < TableCell > Alice</ TableCell >
573+ </ TableRow >
574+ < TableRow rowKey = "Row2" >
575+ < TableCell > Bob</ TableCell >
576+ </ TableRow >
577+ </ Table >
578+ ) ;
579+
580+ // Add an external button to allow focusing outside the table
581+ cy . document ( ) . then ( doc => {
582+ const btn = doc . createElement ( "button" ) ;
583+ btn . id = "outside-btn" ;
584+ btn . textContent = "Outside" ;
585+ doc . body . appendChild ( btn ) ;
586+ } ) ;
587+
588+ // First focus — entering announcement is part of the custom announcement
589+ cy . get ( "[ui5-table-row]" ) . first ( ) . realClick ( ) ;
590+ cy . focused ( ) . should ( "have.attr" , "ui5-table-row" ) ;
591+ checkAnnouncement ( `with 2 rows` ) ;
592+ checkAnnouncement ( `${ MULTI_SELECTABLE } ` ) ;
593+ checkAnnouncement ( `1 rows selected` ) ;
594+ checkAnnouncement ( `Name Alice . Row . 2 of 3` ) ;
595+
596+ // Navigate to next row — no entering announcement, just row content
597+ cy . realPress ( "ArrowDown" ) ;
598+ checkAnnouncement ( "Name Bob . Row . 3 of 3" , "equal" ) ;
599+
600+ // Focus outside the table and back — entering announcement appears again
601+ cy . get ( "#outside-btn" ) . realClick ( ) ;
602+ cy . get ( "[ui5-table-row]" ) . first ( ) . realClick ( ) ;
603+ cy . focused ( ) . should ( "have.attr" , "ui5-table-row" ) ;
604+ checkAnnouncement ( `with 2 rows` ) ;
605+ checkAnnouncement ( `${ MULTI_SELECTABLE } ` ) ;
606+ } ) ;
607+ } ) ;
608+
609+ describe ( "Row Actions Announcement Format" , ( ) => {
610+ function checkAnnouncement ( expectedText : string , focusAgain = false , check = "contains" ) {
611+ if ( focusAgain ) {
612+ cy . realPress ( "ArrowUp" ) ;
613+ cy . realPress ( "ArrowDown" ) ;
614+ }
615+
616+ cy . get ( "body" ) . then ( $body => {
617+ expect ( $body . find ( "#ui5-invisible-text" ) . text ( ) ) [ check ] ( expectedText ) ;
618+ } ) ;
619+ }
620+
621+ it ( "should announce action text in row announcement" , ( ) => {
622+ cy . mount (
623+ < Table id = "table0" rowActionCount = { 3 } >
624+ < TableHeaderRow slot = "headerRow" >
625+ < TableHeaderCell identifier > Name</ TableHeaderCell >
626+ </ TableHeaderRow >
627+ < TableRow id = "row1" rowKey = "Row1" >
628+ < TableCell > Alice</ TableCell >
629+ < TableRowActionNavigation slot = "actions" interactive > </ TableRowActionNavigation >
630+ < TableRowAction slot = "actions" icon = { add } text = "Add" > </ TableRowAction >
631+ < TableRowAction slot = "actions" icon = { edit } text = "Edit" > </ TableRowAction >
632+ </ TableRow >
633+ </ Table >
634+ ) ;
635+
636+ // Multiple actions
637+ cy . get ( "#row1" ) . realClick ( ) ;
638+ checkAnnouncement ( `${ ACTIONS_LIST_TEMPLATE . replace ( "{0}" , "Add, Edit, Navigation" ) } ` ) ;
639+
640+ // Remove an action — format changes
641+ cy . get ( "#row1" ) . find ( "[ui5-table-row-action]" ) . last ( ) . invoke ( "remove" ) ;
642+ checkAnnouncement ( `${ ACTIONS_LIST_TEMPLATE . replace ( "{0}" , "Add, Navigation" ) } ` , true ) ;
643+
644+ // Remove another — single action
645+ cy . get ( "#row1" ) . find ( "[ui5-table-row-action]" ) . first ( ) . invoke ( "remove" ) ;
646+ checkAnnouncement ( `${ ACTION_TEMPLATE . replace ( "{0}" , "Navigation" ) } ` , true ) ;
647+ } ) ;
648+ } ) ;
0 commit comments