@sapudex/web-components 2.61.0 → 2.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/cypress/specs/AccordionSpotlight.cy.tsx +43 -2
  2. package/cypress/specs/Carousel.cy.tsx +88 -0
  3. package/cypress/specs/CountrySelector.cy.tsx +233 -6
  4. package/cypress/specs/FilterMenu.cy.tsx +39 -45
  5. package/cypress/specs/List.cy.tsx +624 -10
  6. package/cypress/specs/ListItem.cy.tsx +50 -0
  7. package/cypress/specs/SelectBox.cy.tsx +239 -131
  8. package/cypress/specs/SelectBoxAccessibility.cy.tsx +3109 -0
  9. package/cypress/specs/TagGroup.cy.tsx +168 -0
  10. package/dist/AccordionSpotlight.d.ts +7 -2
  11. package/dist/AccordionSpotlight.js +31 -6
  12. package/dist/AccordionSpotlight.js.map +1 -1
  13. package/dist/AccordionSpotlightItem.d.ts +3 -1
  14. package/dist/AccordionSpotlightItem.js +3 -1
  15. package/dist/AccordionSpotlightItem.js.map +1 -1
  16. package/dist/Carousel.d.ts +1 -0
  17. package/dist/Carousel.js +19 -3
  18. package/dist/Carousel.js.map +1 -1
  19. package/dist/CountrySelector.js +1 -3
  20. package/dist/CountrySelector.js.map +1 -1
  21. package/dist/List.d.ts +2 -0
  22. package/dist/List.js +22 -4
  23. package/dist/List.js.map +1 -1
  24. package/dist/ListGroupHeader.js +5 -1
  25. package/dist/ListGroupHeader.js.map +1 -1
  26. package/dist/ListGroupHeaderBase.d.ts +2 -0
  27. package/dist/ListGroupHeaderBase.js +15 -2
  28. package/dist/ListGroupHeaderBase.js.map +1 -1
  29. package/dist/ListGroupHeaderTemplate.js +14 -22
  30. package/dist/ListGroupHeaderTemplate.js.map +1 -1
  31. package/dist/ListItem.d.ts +1 -2
  32. package/dist/ListItem.js +14 -12
  33. package/dist/ListItem.js.map +1 -1
  34. package/dist/ListItemBase.d.ts +5 -0
  35. package/dist/ListItemBase.js +11 -1
  36. package/dist/ListItemBase.js.map +1 -1
  37. package/dist/ListItemTemplate.js +1 -1
  38. package/dist/ListItemTemplate.js.map +1 -1
  39. package/dist/ListSelectAllTemplate.js +1 -1
  40. package/dist/ListSelectAllTemplate.js.map +1 -1
  41. package/dist/ListTemplate.js +2 -1
  42. package/dist/ListTemplate.js.map +1 -1
  43. package/dist/Search.d.ts +13 -3
  44. package/dist/Search.js +36 -11
  45. package/dist/Search.js.map +1 -1
  46. package/dist/SearchableListBase.d.ts +2 -0
  47. package/dist/SearchableListBase.js +13 -3
  48. package/dist/SearchableListBase.js.map +1 -1
  49. package/dist/SearchableListBaseTemplate.js +42 -33
  50. package/dist/SearchableListBaseTemplate.js.map +1 -1
  51. package/dist/SelectBase.d.ts +18 -33
  52. package/dist/SelectBase.js +124 -139
  53. package/dist/SelectBase.js.map +1 -1
  54. package/dist/SelectBox.d.ts +3 -0
  55. package/dist/SelectBox.js +19 -7
  56. package/dist/SelectBox.js.map +1 -1
  57. package/dist/SelectBoxTemplate.js +3 -3
  58. package/dist/SelectBoxTemplate.js.map +1 -1
  59. package/dist/SideNavigationItem.js +2 -0
  60. package/dist/SideNavigationItem.js.map +1 -1
  61. package/dist/SideNavigationItemTemplate.d.ts +2 -0
  62. package/dist/SideNavigationItemTemplate.js +15 -0
  63. package/dist/SideNavigationItemTemplate.js.map +1 -0
  64. package/dist/SideNavigationMobile.js +1 -2
  65. package/dist/SideNavigationMobile.js.map +1 -1
  66. package/dist/SideNavigationSubItem.js +2 -1
  67. package/dist/SideNavigationSubItem.js.map +1 -1
  68. package/dist/SideNavigationSubItemTemplate.js +1 -2
  69. package/dist/SideNavigationSubItemTemplate.js.map +1 -1
  70. package/dist/TagGroup.d.ts +3 -0
  71. package/dist/TagGroup.js +31 -0
  72. package/dist/TagGroup.js.map +1 -1
  73. package/dist/TagGroupTemplate.js +1 -1
  74. package/dist/TagGroupTemplate.js.map +1 -1
  75. package/dist/assets/{InputSuggestions-1OnUn6pM.js → InputSuggestions-BPKrPqPb.js} +1 -1
  76. package/dist/assets/{ListItemStandardExpandableTextTemplate-CoBAMp_v.js → ListItemStandardExpandableTextTemplate-DkdOcCxC.js} +1 -1
  77. package/dist/assets/{bundle.esm-BqaVWFKf.js → bundle.esm-17Yt5gDr.js} +232 -233
  78. package/dist/css/themes/AccordionSpotlight.css +1 -1
  79. package/dist/css/themes/ListGroupHeader.css +1 -1
  80. package/dist/css/themes/SideNavigationItem.css +1 -1
  81. package/dist/css/themes/SideNavigationMobile.css +1 -1
  82. package/dist/css/themes/SideNavigationSubItem.css +1 -1
  83. package/dist/css/themes/Table.css +1 -1
  84. package/dist/custom-elements-internal.json +7 -7
  85. package/dist/custom-elements.json +7 -7
  86. package/dist/generated/i18n/i18n-defaults.d.ts +2 -1
  87. package/dist/generated/i18n/i18n-defaults.js +2 -1
  88. package/dist/generated/i18n/i18n-defaults.js.map +1 -1
  89. package/dist/generated/themes/AccordionSpotlight.css.d.ts +1 -1
  90. package/dist/generated/themes/AccordionSpotlight.css.js +1 -1
  91. package/dist/generated/themes/AccordionSpotlight.css.js.map +1 -1
  92. package/dist/generated/themes/ListGroupHeader.css.d.ts +1 -1
  93. package/dist/generated/themes/ListGroupHeader.css.js +1 -1
  94. package/dist/generated/themes/ListGroupHeader.css.js.map +1 -1
  95. package/dist/generated/themes/SideNavigationItem.css.d.ts +1 -1
  96. package/dist/generated/themes/SideNavigationItem.css.js +1 -1
  97. package/dist/generated/themes/SideNavigationItem.css.js.map +1 -1
  98. package/dist/generated/themes/SideNavigationMobile.css.d.ts +1 -1
  99. package/dist/generated/themes/SideNavigationMobile.css.js +1 -1
  100. package/dist/generated/themes/SideNavigationMobile.css.js.map +1 -1
  101. package/dist/generated/themes/SideNavigationSubItem.css.d.ts +1 -1
  102. package/dist/generated/themes/SideNavigationSubItem.css.js +1 -1
  103. package/dist/generated/themes/SideNavigationSubItem.css.js.map +1 -1
  104. package/dist/generated/themes/Table.css.d.ts +1 -1
  105. package/dist/generated/themes/Table.css.js +1 -1
  106. package/dist/generated/themes/Table.css.js.map +1 -1
  107. package/dist/messagebundle.properties +3 -0
  108. package/dist/vscode.html-custom-data.json +4 -4
  109. package/dist/web-types.json +9 -9
  110. package/package.json +1 -1
  111. package/src/AccordionSpotlight.ts +35 -6
  112. package/src/AccordionSpotlightItem.ts +3 -1
  113. package/src/Carousel.ts +22 -3
  114. package/src/CountrySelector.ts +1 -3
  115. package/src/List.ts +21 -4
  116. package/src/ListGroupHeader.ts +5 -1
  117. package/src/ListGroupHeaderBase.ts +16 -2
  118. package/src/ListGroupHeaderTemplate.tsx +29 -47
  119. package/src/ListItem.ts +14 -13
  120. package/src/ListItemBase.ts +14 -1
  121. package/src/ListItemTemplate.tsx +4 -4
  122. package/src/ListSelectAllTemplate.tsx +10 -8
  123. package/src/ListTemplate.tsx +14 -10
  124. package/src/Search.ts +48 -13
  125. package/src/SearchableListBase.ts +15 -3
  126. package/src/SearchableListBaseTemplate.tsx +51 -36
  127. package/src/SelectBase.ts +130 -144
  128. package/src/SelectBox.ts +21 -7
  129. package/src/SelectBoxTemplate.tsx +10 -9
  130. package/src/SideNavigationItem.ts +2 -0
  131. package/src/SideNavigationItemTemplate.tsx +97 -0
  132. package/src/SideNavigationMobile.ts +1 -2
  133. package/src/SideNavigationSubItem.ts +2 -1
  134. package/src/SideNavigationSubItemTemplate.tsx +7 -6
  135. package/src/TagGroup.ts +32 -0
  136. package/src/TagGroupTemplate.tsx +1 -1
  137. package/src/generated/i18n/i18n-defaults.ts +2 -2
  138. package/src/generated/themes/AccordionSpotlight.css.ts +1 -1
  139. package/src/generated/themes/ListGroupHeader.css.ts +1 -1
  140. package/src/generated/themes/SideNavigationItem.css.ts +1 -1
  141. package/src/generated/themes/SideNavigationMobile.css.ts +1 -1
  142. package/src/generated/themes/SideNavigationSubItem.css.ts +1 -1
  143. package/src/generated/themes/Table.css.ts +1 -1
  144. package/src/i18n/messagebundle.properties +3 -0
  145. package/src/themes/AccordionSpotlight.css +9 -9
  146. package/src/themes/ListGroupHeader.css +2 -43
  147. package/src/themes/SideNavigationItem.css +56 -6
  148. package/src/themes/SideNavigationMobile.css +61 -15
  149. package/src/themes/SideNavigationSubItem.css +14 -109
  150. package/src/themes/Table.css +6 -1
  151. package/dist/css/themes/SideNavigationMobileDropdown.css +0 -1
  152. package/dist/generated/themes/SideNavigationMobileDropdown.css.d.ts +0 -2
  153. package/dist/generated/themes/SideNavigationMobileDropdown.css.js +0 -8
  154. package/dist/generated/themes/SideNavigationMobileDropdown.css.js.map +0 -1
  155. package/src/generated/themes/SideNavigationMobileDropdown.css.ts +0 -9
  156. package/src/themes/SideNavigationMobileDropdown.css +0 -36
@@ -362,18 +362,27 @@ describe("List - collapsible group header uses tree pattern", () => {
362
362
  .find(".udex-list__group-content").should("have.attr", "role", "treeitem");
363
363
  });
364
364
 
365
- it("SingleSelect collapsible header: nests the role=\"group\" content region inside the treeitem (conformant tree)", () => {
365
+ // Updated: the header owns a same-shadow sibling group without including its leaves in the visible label.
366
+ it("SingleSelect collapsible header: owns the sibling content group without nesting its slot", () => {
366
367
  cy.mount(collapsibleListSingleSelect);
367
368
  cy.get("#group-header-collapsible").shadow()
368
- .find(".udex-list__group-list > .udex-list__group-content--nested[role='treeitem']").should("exist");
369
- // The content region is a descendant of the treeitem, not a sibling.
370
- cy.get("#group-header-collapsible").shadow()
371
- .find(".udex-list__group-content--nested[role='treeitem'] .udex-list__group-list__content[role='group']").should("exist");
372
- cy.get("#group-header-collapsible").shadow()
373
- .find(".udex-list__group-list > .udex-list__group-list__content[role='group']").should("not.exist");
374
- // The visible header row wrapper now sits inside the treeitem.
375
- cy.get("#group-header-collapsible").shadow()
376
- .find(".udex-list__group-content--nested[role='treeitem'] > .udex-list__group-header-row").should("exist");
369
+ .find(".udex-list__group-list > .udex-list__group-content.udex-list__group-header-wrap[role='treeitem']")
370
+ .should(UDExHeader => {
371
+ const UDExRow = UDExHeader[0];
372
+ const UDExShadow = UDExRow.getRootNode() as ShadowRoot;
373
+ const UDExOwnedId = UDExRow.getAttribute("aria-owns");
374
+ expect(UDExOwnedId).to.match(/^\S+$/);
375
+ expect(UDExRow.getAttribute("aria-controls")).to.equal(UDExOwnedId);
376
+ const UDExGroups = UDExShadow.querySelectorAll(`[id="${UDExOwnedId}"]`);
377
+ expect(UDExGroups).to.have.length(1);
378
+ const UDExGroup = UDExGroups[0];
379
+ expect(UDExGroup.matches(".udex-list__group-list__content[role=group]")).to.equal(true);
380
+ expect(UDExGroup.getRootNode()).to.equal(UDExShadow);
381
+ expect(UDExGroup.parentElement).to.equal(UDExRow.parentElement);
382
+ expect(UDExRow.nextElementSibling).to.equal(UDExGroup);
383
+ expect(UDExRow.querySelector("slot, [role=group], [udex-list-item]")).to.equal(null);
384
+ expect(UDExGroup.querySelector("slot")!.assignedElements()).to.have.length(2);
385
+ });
377
386
  });
378
387
 
379
388
  it("child items expose accessible-role=\"treeitem\" on the host", () => {
@@ -448,6 +457,59 @@ describe("List - flat MultiSelect selection announcement", () => {
448
457
  });
449
458
  });
450
459
 
460
+ describe("List component - accessible role attribute reflection", () => {
461
+ it("preserves the default listbox role for standalone lists", () => {
462
+ cy.mount(<UDExList><UDExListItem>Item 1</UDExListItem></UDExList>);
463
+
464
+ cy.get("[udex-list]").should("have.attr", "accessible-role", "listbox");
465
+ cy.get("[udex-list]").shadow().find("#udex-list").should("have.attr", "role", "listbox");
466
+ });
467
+
468
+ it("reflects an empty role configuration when the host owns the role", () => {
469
+ cy.mount(<UDExList role="tree" accessibleRole=""><UDExListItem>Item 1</UDExListItem></UDExList>);
470
+
471
+ cy.get("[udex-list]").should("have.attr", "role", "tree").and("have.attr", "accessible-role", "");
472
+ cy.get("[udex-list]").shadow().find("#udex-list").should("have.attr", "role", "none");
473
+ });
474
+
475
+ it("updates the inner role when an explicit attribute is set", () => {
476
+ cy.mount(<UDExList><UDExListItem>Item 1</UDExListItem></UDExList>);
477
+
478
+ cy.get("[udex-list]").shadow().find("#udex-list").should("have.attr", "role", "listbox");
479
+ cy.get("[udex-list]").invoke("attr", "accessible-role", "tree");
480
+ cy.get("[udex-list]").shadow().find("#udex-list").should("have.attr", "role", "tree");
481
+ });
482
+ });
483
+
484
+ describe("ListItem - removing an explicit accessible role", () => {
485
+ (["SingleSelect", "MultiSelect", "SingleContent"] as const).forEach(UDExMode => {
486
+ it(`${UDExMode}: restores fallback roles and selection semantics after removeAttr`, () => {
487
+ cy.mount(<UDExListItem mode={UDExMode} accessibleRole="treeitem">Item 1</UDExListItem>);
488
+ const UDExRole = UDExMode === "SingleContent" ? "listitem" : "option";
489
+ const UDExRow = () => cy.get("[udex-list-item]").shadow().find("[part=item]");
490
+
491
+ UDExRow().should("have.attr", "role", "treeitem");
492
+ cy.get("[udex-list-item]").should("have.prop", "effectiveRole", "treeitem");
493
+ // Attribute removal resets accessibleRole to null, not an empty string.
494
+ cy.get("[udex-list-item]").invoke("removeAttr", "accessible-role");
495
+ cy.get("[udex-list-item]").should(UDExItem => {
496
+ expect(UDExItem).not.to.have.attr("accessible-role");
497
+ expect(UDExItem).to.have.prop("roleItem", UDExRole);
498
+ expect(UDExItem).to.have.prop("effectiveRole", "option");
499
+ });
500
+ UDExRow().should(UDExItem => {
501
+ expect(UDExItem).to.have.attr("role", UDExRole);
502
+ expect(UDExItem).not.to.have.attr("aria-checked");
503
+ if (UDExMode === "SingleContent") {
504
+ expect(UDExItem).not.to.have.attr("aria-selected");
505
+ } else {
506
+ expect(UDExItem).to.have.attr("aria-selected", "false");
507
+ }
508
+ });
509
+ });
510
+ });
511
+ });
512
+
451
513
  describe("List component - SingleContent accessible name", () => {
452
514
  it("uses accessible-name as aria-label when only accessible-name is set", () => {
453
515
  cy.mount(
@@ -547,3 +609,555 @@ describe("List component - selection modes keep listbox/option role", () => {
547
609
  });
548
610
  });
549
611
  });
612
+
613
+ describe("List - standalone grouped MultiSelect keyboard regressions", () => {
614
+ const UDExHeaderRow = (UDExId = "keyboard-group-1") =>
615
+ cy.get(`[udex-list-group-header]#${UDExId}`).shadow()
616
+ .find("[udex-list-item]#group-heading").shadow().find("[part=item]");
617
+ const UDExLeafRow = (UDExId: string) =>
618
+ cy.get(`[udex-list-item]#${UDExId}`).shadow().find("[part=item]");
619
+
620
+ const UDExAssertRovingTabStop = (UDExActiveId: string) => {
621
+ cy.get("[udex-list]#keyboard-list").find("[udex-list-group-header]").each(UDExGroup => {
622
+ UDExHeaderRow(UDExGroup[0].id)
623
+ .should("have.attr", "tabindex", UDExGroup[0].id === UDExActiveId ? "0" : "-1");
624
+ });
625
+ cy.get("[udex-list]#keyboard-list").find("[udex-list-item]").each(UDExItem => {
626
+ UDExLeafRow(UDExItem[0].id)
627
+ .should("have.attr", "tabindex", UDExItem[0].id === UDExActiveId ? "0" : "-1");
628
+ });
629
+ };
630
+
631
+ beforeEach(() => {
632
+ cy.mount(
633
+ <div id="list-keyboard-fixture">
634
+ <button id="before-list" type="button">Before list</button>
635
+ <UDExList id="keyboard-list" mode="MultiSelect" accessible-name="Grouped choices">
636
+ <UDExListGroupHeader id="keyboard-group-1" label="First group">
637
+ <UDExListItem id="keyboard-alpha" label="Alpha" />
638
+ <UDExListItem id="keyboard-disabled" label="Unavailable" disabled />
639
+ <UDExListItem id="keyboard-beta" label="Beta" />
640
+ </UDExListGroupHeader>
641
+ <UDExListGroupHeader id="keyboard-group-2" label="Second group">
642
+ <UDExListItem id="keyboard-gamma" label="Gamma" />
643
+ </UDExListGroupHeader>
644
+ </UDExList>
645
+ <button id="after-list" type="button">After list</button>
646
+ </div>
647
+ );
648
+ cy.get("#list-keyboard-fixture").then(UDExFixture => {
649
+ UDExFixture[0].addEventListener("selection-change", cy.stub().as("selectionChange"));
650
+ });
651
+
652
+ // Native entry is essential: focusing a row directly masks a missing tabindex.
653
+ UDExHeaderRow().should("exist");
654
+ cy.get("#before-list").realClick().should("be.focused");
655
+ cy.realPress("Tab");
656
+ UDExHeaderRow().should("be.focused");
657
+ });
658
+
659
+ it("Tab reaches the first actual shadow header row with option semantics", () => {
660
+ cy.get("[udex-list]#keyboard-list").shadow().find("#udex-list")
661
+ .should("have.attr", "role", "listbox");
662
+ UDExHeaderRow().should("have.attr", "role", "option");
663
+ UDExHeaderRow().should("have.attr", "aria-selected", "false");
664
+ UDExHeaderRow().find("[part=title]").should("have.text", "First group");
665
+ cy.get("@selectionChange").should("not.have.been.called");
666
+ });
667
+
668
+ it("ArrowDown visits enabled children, skips the disabled child and reaches the next header", () => {
669
+ cy.realPress("ArrowDown");
670
+ UDExLeafRow("keyboard-alpha").should("be.focused");
671
+ cy.realPress("ArrowDown");
672
+ UDExLeafRow("keyboard-beta").should("be.focused");
673
+ UDExLeafRow("keyboard-disabled").should("have.attr", "aria-disabled", "true");
674
+ UDExLeafRow("keyboard-disabled").should("have.attr", "tabindex", "-1");
675
+ cy.realPress("ArrowDown");
676
+ UDExHeaderRow("keyboard-group-2").should("be.focused");
677
+ cy.realPress("ArrowDown");
678
+ UDExLeafRow("keyboard-gamma").should("be.focused");
679
+ cy.get("@selectionChange").should("not.have.been.called");
680
+ });
681
+
682
+ it("ArrowUp returns through headers and enabled children while skipping the disabled child", () => {
683
+ cy.realPress("End");
684
+ UDExLeafRow("keyboard-gamma").should("be.focused");
685
+ cy.realPress("ArrowUp");
686
+ UDExHeaderRow("keyboard-group-2").should("be.focused");
687
+ cy.realPress("ArrowUp");
688
+ UDExLeafRow("keyboard-beta").should("be.focused");
689
+ cy.realPress("ArrowUp");
690
+ UDExLeafRow("keyboard-alpha").should("be.focused");
691
+ cy.realPress("ArrowUp");
692
+ UDExHeaderRow().should("be.focused");
693
+ cy.get("@selectionChange").should("not.have.been.called");
694
+ });
695
+
696
+ it("Home and End move between the first header and last enabled leaf without selecting", () => {
697
+ cy.realPress("End");
698
+ UDExLeafRow("keyboard-gamma").should("be.focused");
699
+ cy.realPress("Home");
700
+ UDExHeaderRow().should("be.focused");
701
+ cy.get("@selectionChange").should("not.have.been.called");
702
+ });
703
+
704
+ it("keeps one roving row tabindex and lets Tab leave the list", () => {
705
+ UDExAssertRovingTabStop("keyboard-group-1");
706
+ cy.realPress("End");
707
+ UDExLeafRow("keyboard-gamma").should("be.focused");
708
+ UDExAssertRovingTabStop("keyboard-gamma");
709
+ cy.realPress("Tab");
710
+ cy.get("#after-list").should("be.focused");
711
+ });
712
+
713
+ (["Space", "Enter"] as const).forEach(UDExKey => {
714
+ it(`${UDExKey} toggles the focused leaf and bubbles its selection-change event`, () => {
715
+ cy.realPress("ArrowDown");
716
+ UDExLeafRow("keyboard-alpha").should("be.focused");
717
+ cy.realPress(UDExKey);
718
+ UDExLeafRow("keyboard-alpha").should("have.attr", "aria-selected", "true");
719
+ UDExLeafRow("keyboard-beta").should("have.attr", "aria-selected", "false");
720
+ UDExLeafRow("keyboard-disabled").should("have.attr", "aria-selected", "false");
721
+ cy.get("@selectionChange").should("have.been.calledOnce")
722
+ .its("firstCall.args.0.detail.item.id").should("equal", "keyboard-alpha");
723
+
724
+ cy.realPress(UDExKey);
725
+ UDExLeafRow("keyboard-alpha").should("have.attr", "aria-selected", "false");
726
+ UDExLeafRow("keyboard-alpha").should("be.focused");
727
+ cy.get("@selectionChange").should("have.been.calledTwice")
728
+ .its("lastCall.args.0.detail.item.id").should("equal", "keyboard-alpha");
729
+ });
730
+
731
+ it(`${UDExKey} toggles only the focused group's enabled children`, () => {
732
+ cy.realPress(UDExKey);
733
+ UDExLeafRow("keyboard-alpha").should("have.attr", "aria-selected", "true");
734
+ UDExLeafRow("keyboard-beta").should("have.attr", "aria-selected", "true");
735
+ UDExLeafRow("keyboard-disabled").should("have.attr", "aria-selected", "false");
736
+ UDExLeafRow("keyboard-gamma").should("have.attr", "aria-selected", "false");
737
+ UDExHeaderRow().should("be.focused");
738
+
739
+ cy.realPress(UDExKey);
740
+ UDExLeafRow("keyboard-alpha").should("have.attr", "aria-selected", "false");
741
+ UDExLeafRow("keyboard-beta").should("have.attr", "aria-selected", "false");
742
+ UDExHeaderRow().should("be.focused");
743
+ });
744
+ });
745
+ });
746
+
747
+ describe("List - SingleSelect collapsible header names and ownership", () => {
748
+ const UDExHeaderRow = (UDExIndex = 0) =>
749
+ cy.get("[udex-list-group-header]").eq(UDExIndex).shadow()
750
+ .find(".udex-list__group-content.udex-list__group-header-wrap");
751
+
752
+ [false, true].forEach(UDExRichLabel => {
753
+ ["", "Digital Builders"].forEach(UDExSupportingText => {
754
+ it(`names a ${UDExRichLabel ? "rich" : "plain"} heading ${UDExSupportingText ? "with" : "without"} supporting text, excluding leaves and the chevron`, () => {
755
+ cy.mount(
756
+ <UDExList mode="SingleSelect">
757
+ <UDExListGroupHeader
758
+ collapsible
759
+ label={UDExRichLabel ? "<b>Programming</b> Languages" : "Programming Languages"}
760
+ supporting-text={UDExSupportingText}>
761
+ <UDExListItem label="JavaScript" />
762
+ <UDExListItem label="Python" />
763
+ </UDExListGroupHeader>
764
+ </UDExList>
765
+ );
766
+
767
+ UDExHeaderRow().find("#group-heading").should("have.text", "Programming Languages");
768
+ if (UDExRichLabel) {
769
+ UDExHeaderRow().find("#group-heading b").should("have.text", "Programming");
770
+ }
771
+ if (UDExSupportingText) {
772
+ UDExHeaderRow().find("#group-supporting-text").should("have.text", UDExSupportingText);
773
+ } else {
774
+ UDExHeaderRow().find("#group-supporting-text").should("not.exist");
775
+ }
776
+ UDExHeaderRow().find("[udex-button].udex-list__group-heading__icon").should("exist");
777
+ UDExHeaderRow().should(UDExHeader => {
778
+ expect(UDExHeader).to.have.attr("role", "treeitem");
779
+ expect(UDExHeader).to.have.attr("tabindex", "0");
780
+ expect(UDExHeader).to.have.attr("aria-labelledby", UDExSupportingText
781
+ ? "group-heading group-supporting-text" : "group-heading");
782
+ // Resolve the name sources in this shadow root, not via document-global IDs.
783
+ const UDExShadow = UDExHeader[0].getRootNode() as ShadowRoot;
784
+ const UDExName = UDExHeader.attr("aria-labelledby")!.split(" ").map(UDExId => {
785
+ const UDExLabels = UDExShadow.querySelectorAll(`[id="${UDExId}"]`);
786
+ expect(UDExLabels).to.have.length(1);
787
+ expect(UDExHeader[0].contains(UDExLabels[0])).to.equal(true);
788
+ return UDExLabels[0].textContent!.trim();
789
+ }).join(" ");
790
+ expect(UDExName).to.equal(
791
+ ["Programming Languages", UDExSupportingText].filter(Boolean).join(" ")
792
+ );
793
+ expect(UDExHeader[0].querySelector(".udex-list__header")!.textContent!
794
+ .replace(/\s+/g, " ").trim()).to.equal(UDExName);
795
+ expect(UDExHeader[0].querySelector("slot, [role=group], [udex-list-item]")).to.equal(null);
796
+ expect(UDExHeader.text()).not.to.contain("JavaScript");
797
+ expect(UDExHeader.text()).not.to.contain("Python");
798
+ });
799
+ });
800
+ });
801
+ });
802
+
803
+ it("keeps unique same-shadow ownership and leaf focus through keyboard collapse and expansion", () => {
804
+ cy.mount(
805
+ <div>
806
+ <button id="before-owned-list" type="button">Before list</button>
807
+ <UDExList mode="SingleSelect" accessible-name="Grouped choices">
808
+ <UDExListGroupHeader collapsible label="First group">
809
+ <UDExListItem id="owned-leaf" label="Alpha" />
810
+ </UDExListGroupHeader>
811
+ <UDExListGroupHeader collapsible collapsed label="Second group">
812
+ <UDExListItem label="Beta" />
813
+ </UDExListGroupHeader>
814
+ </UDExList>
815
+ </div>
816
+ );
817
+
818
+ UDExHeaderRow().should("have.attr", "aria-expanded", "true");
819
+ UDExHeaderRow(1).should("have.attr", "aria-expanded", "false");
820
+ UDExHeaderRow().invoke("attr", "aria-owns").then(UDExFirstId => {
821
+ UDExHeaderRow(1).invoke("attr", "aria-owns").then(UDExSecondId => {
822
+ expect(UDExSecondId).to.match(/^\S+$/).and.not.equal(UDExFirstId);
823
+ const UDExAssertOwnership = () => {
824
+ cy.get("[udex-list-group-header]").each((UDExHost, UDExIndex) => {
825
+ cy.wrap(UDExHost).shadow().find(".udex-list__group-content").should(UDExHeader => {
826
+ const UDExRow = UDExHeader[0];
827
+ const UDExOwnedId = UDExRow.getAttribute("aria-owns");
828
+ expect(UDExOwnedId).to.equal([UDExFirstId, UDExSecondId][UDExIndex]);
829
+ expect(UDExRow.getAttribute("aria-controls")).to.equal(UDExOwnedId);
830
+ const UDExShadow = UDExHost[0].shadowRoot!;
831
+ expect(UDExRow.getRootNode()).to.equal(UDExShadow);
832
+ const UDExGroups = UDExShadow.querySelectorAll(`[id="${UDExOwnedId}"]`);
833
+ expect(UDExGroups).to.have.length(1);
834
+ expect(UDExGroups[0].matches(".udex-list__group-list__content[role=group]")).to.equal(true);
835
+ expect(UDExRow.nextElementSibling).to.equal(UDExGroups[0]);
836
+ expect(UDExRow.querySelector("slot, [role=group]")).to.equal(null);
837
+ expect(UDExGroups[0].querySelector("slot")!.assignedElements()).to.have.length(1);
838
+ });
839
+ });
840
+ };
841
+
842
+ UDExAssertOwnership();
843
+ cy.get("#before-owned-list").realClick().should("be.focused");
844
+ cy.realPress("Tab");
845
+ UDExHeaderRow().should("be.focused");
846
+ cy.realPress("ArrowLeft");
847
+ UDExHeaderRow().should("have.attr", "aria-expanded", "false");
848
+ UDExHeaderRow().should("be.focused");
849
+ cy.get("[udex-list-item]#owned-leaf").should("have.attr", "collapsed");
850
+ UDExAssertOwnership();
851
+
852
+ cy.realPress("ArrowDown");
853
+ UDExHeaderRow(1).should("be.focused");
854
+ cy.realPress("ArrowUp");
855
+ UDExHeaderRow().should("be.focused");
856
+ cy.realPress("ArrowRight");
857
+ UDExHeaderRow().should("have.attr", "aria-expanded", "true");
858
+ UDExHeaderRow().should("be.focused");
859
+ cy.get("[udex-list-item]#owned-leaf").should("not.have.attr", "collapsed");
860
+ UDExAssertOwnership();
861
+ cy.realPress("ArrowDown");
862
+ cy.get("[udex-list-item]#owned-leaf").shadow().find("[part=item]").should("be.focused");
863
+ cy.realPress("ArrowUp");
864
+ UDExHeaderRow().should("be.focused");
865
+ });
866
+ });
867
+ });
868
+
869
+ it("omits inner ownership and duplicate semantics in SelectBox-style host-ARIA mode", () => {
870
+ cy.mount(
871
+ <UDExList mode="SingleSelect">
872
+ <UDExListGroupHeader collapsible label="Host-owned group" _useHostAriaRole={true}>
873
+ <UDExListItem label="Alpha" />
874
+ </UDExListGroupHeader>
875
+ </UDExList>
876
+ );
877
+
878
+ UDExHeaderRow().should(UDExHeader => {
879
+ expect(UDExHeader).to.have.attr("role", "presentation");
880
+ ["aria-owns", "aria-controls", "aria-expanded", "aria-labelledby", "tabindex"].forEach(UDExAttribute => {
881
+ expect(UDExHeader).not.to.have.attr(UDExAttribute);
882
+ });
883
+ });
884
+ cy.get("[udex-list-group-header]").shadow()
885
+ .find(".udex-list__group-list > .udex-list__group-list__content[role=group] slot").should("exist");
886
+ });
887
+ });
888
+
889
+ describe("List - MultiSelect header host-ARIA Boolean binding regressions", () => {
890
+ const UDExLayouts = [
891
+ { name: "noncollapsible", collapsible: false, collapsed: false },
892
+ { name: "collapsible expanded", collapsible: true, collapsed: false },
893
+ { name: "collapsible collapsed", collapsible: true, collapsed: true },
894
+ ];
895
+
896
+ UDExLayouts.forEach(UDExLayout => {
897
+ [false, true].forEach(UDExHostAriaMode => {
898
+ // Updated: the nested host must remain transparent; only its actual row exposes tree state.
899
+ it(`${UDExLayout.name}: propagates _useHostAriaRole=${UDExHostAriaMode} to the nested header row`, () => {
900
+ cy.mount(
901
+ <div>
902
+ <button id="before-binding-list" type="button">Before list</button>
903
+ <UDExList mode="MultiSelect">
904
+ <UDExListGroupHeader
905
+ label="Binding group"
906
+ collapsible={UDExLayout.collapsible}
907
+ collapsed={UDExLayout.collapsed}
908
+ _useHostAriaRole={UDExHostAriaMode}>
909
+ <UDExListItem label="Child" />
910
+ </UDExListGroupHeader>
911
+ </UDExList>
912
+ </div>
913
+ );
914
+
915
+ cy.get("[udex-list-group-header]").should("have.prop", "_useHostAriaRole", UDExHostAriaMode);
916
+ const UDExNestedHeader = () => cy.get("[udex-list-group-header]").shadow().find("[udex-list-item]#group-heading");
917
+ UDExNestedHeader().should(UDExHeader => {
918
+ expect(UDExHeader).to.have.prop("_useHostAriaRole", UDExHostAriaMode);
919
+ expect(UDExHeader).to.have.attr("role", "none");
920
+ expect(UDExHeader[0].getAttributeNames().filter(UDExAttribute => UDExAttribute.startsWith("aria-")),
921
+ "presentational host omits all ARIA naming/state attributes").to.deep.equal([]);
922
+ // A false Boolean must be absent, never the present attribute string "false".
923
+ if (UDExHostAriaMode) {
924
+ expect(UDExHeader).to.have.attr("_use-host-aria-role");
925
+ } else {
926
+ expect(UDExHeader).not.to.have.attr("_use-host-aria-role");
927
+ }
928
+ });
929
+ UDExNestedHeader().shadow().find("[part=item]").should(UDExRow => {
930
+ if (UDExHostAriaMode) {
931
+ expect(UDExRow).to.have.attr("role", "presentation");
932
+ ["tabindex", "aria-selected", "aria-checked", "aria-expanded"].forEach(UDExAttribute => {
933
+ expect(UDExRow).not.to.have.attr(UDExAttribute);
934
+ });
935
+ } else {
936
+ expect(UDExRow).to.have.attr("role", UDExLayout.collapsible ? "treeitem" : "option");
937
+ expect(UDExRow).to.have.attr("tabindex", "0");
938
+ if (UDExLayout.collapsible) {
939
+ expect(UDExRow).to.have.attr("aria-expanded", String(!UDExLayout.collapsed));
940
+ expect(UDExRow).to.have.attr("aria-checked", "false");
941
+ expect(UDExRow).not.to.have.attr("aria-selected");
942
+ } else {
943
+ expect(UDExRow).not.to.have.attr("aria-expanded");
944
+ }
945
+ }
946
+ });
947
+
948
+ if (!UDExHostAriaMode) {
949
+ cy.get("#before-binding-list").realClick().should("be.focused");
950
+ cy.realPress("Tab");
951
+ UDExNestedHeader().shadow().find("[part=item]").should("be.focused");
952
+ }
953
+ });
954
+ });
955
+ });
956
+ });
957
+
958
+ describe("List - grouped SingleContent regressions", () => {
959
+ const UDExGroupedList = (
960
+ UDExMode: "SingleContent" | "SingleSelect" | "MultiSelect" = "SingleContent",
961
+ UDExMixed = false
962
+ ) => (
963
+ <div id="sc-grouped-fixture">
964
+ <button id="before-sc-grouped" type="button">Before list</button>
965
+ <UDExList id="sc-grouped-list" mode={UDExMode} accessible-name="Grouped information">
966
+ <UDExListGroupHeader id="sc-grouped-expanded" collapsible label="First group" supportingText="Details">
967
+ <UDExListItem id="sc-grouped-alpha" label="Alpha" />
968
+ </UDExListGroupHeader>
969
+ <UDExListGroupHeader id="sc-grouped-second" collapsible={!UDExMixed} collapsed={!UDExMixed} label="Second group">
970
+ <UDExListItem label="Beta" />
971
+ </UDExListGroupHeader>
972
+ {UDExMixed && <UDExListItem label="Ungrouped item" />}
973
+ </UDExList>
974
+ <button id="after-sc-grouped" type="button">After list</button>
975
+ </div>
976
+ );
977
+ const UDExInnerList = () => cy.get("#sc-grouped-list").shadow().find("#udex-list");
978
+ const UDExHeaderRow = () => cy.get("#sc-grouped-expanded").shadow().find(".udex-list__group-content");
979
+ const UDExSelectionSemantics = [
980
+ '[role="tree"]', '[role="treeitem"]', '[role="listbox"]', '[role="option"]',
981
+ "[aria-selected]", "[aria-checked]", "[aria-multiselectable]", "[aria-owns]",
982
+ ].join(", ");
983
+
984
+ const UDExAssertContentList = () => {
985
+ UDExInnerList().should("have.attr", "role", "list");
986
+ cy.get("#sc-grouped-list").shadow().find(UDExSelectionSemantics).should("not.exist");
987
+ cy.get("#sc-grouped-list").find("[udex-list-group-header]").each(UDExGroup => {
988
+ cy.wrap(UDExGroup).should(UDExHost => {
989
+ expect(UDExHost).to.have.prop("_treeMode", false);
990
+ expect(UDExHost).not.to.have.attr("_list-managed-tree-mode");
991
+ });
992
+ cy.wrap(UDExGroup).shadow().find(".udex-list__group").should("have.attr", "role", "listitem");
993
+ cy.wrap(UDExGroup).shadow().find('.udex-list__group[role="listitem"] [role="list"] > slot')
994
+ .should("have.length", 1).should(UDExSlot => {
995
+ expect((UDExSlot[0] as HTMLSlotElement).assignedElements())
996
+ .to.deep.equal(Array.from(UDExGroup[0].children));
997
+ });
998
+ cy.wrap(UDExGroup).shadow().find(UDExSelectionSemantics).should("not.exist");
999
+ });
1000
+ cy.get("#sc-grouped-list").find("[udex-list-item]").each(UDExItem => {
1001
+ cy.wrap(UDExItem).should(UDExHost => {
1002
+ expect(UDExHost).to.have.prop("mode", "SingleContent");
1003
+ expect(UDExHost).to.have.prop("accessibleRole", "");
1004
+ expect(UDExHost).not.to.have.attr("_list-managed-role");
1005
+ });
1006
+ cy.wrap(UDExItem).shadow().find("[part=item]").should("have.attr", "role", "listitem");
1007
+ cy.wrap(UDExItem).shadow().find(UDExSelectionSemantics).should("not.exist");
1008
+ });
1009
+ };
1010
+
1011
+ [false, true].forEach(UDExMixed => {
1012
+ it(`${UDExMixed ? "mixed groups and an ungrouped leaf" : "expanded and collapsed groups"} use nested lists without selection semantics`, () => {
1013
+ cy.mount(UDExGroupedList("SingleContent", UDExMixed));
1014
+ UDExAssertContentList();
1015
+ UDExInnerList().should("have.attr", "aria-label", "Grouped information");
1016
+ UDExHeaderRow().should(UDExHeader => {
1017
+ const UDExRow = UDExHeader[0];
1018
+ const UDExShadow = UDExRow.getRootNode() as ShadowRoot;
1019
+ expect(UDExHeader).to.have.attr("role", "button");
1020
+ expect(UDExHeader).to.have.attr("aria-expanded", "true");
1021
+ expect(UDExHeader).to.have.attr("aria-labelledby", "group-heading group-supporting-text");
1022
+ const UDExLabels = UDExRow.getAttribute("aria-labelledby")!.split(" ")
1023
+ .map(UDExId => UDExShadow.getElementById(UDExId)!.textContent!.trim());
1024
+ expect(UDExLabels.join(" ")).to.equal("First group Details");
1025
+ const UDExContent = UDExShadow.getElementById(UDExRow.getAttribute("aria-controls")!);
1026
+ expect(UDExContent).to.equal(UDExRow.nextElementSibling);
1027
+ expect(UDExContent!.getAttribute("role")).to.equal("list");
1028
+ expect(UDExRow.querySelector("slot, [role=list]")).to.equal(null);
1029
+ });
1030
+ if (!UDExMixed) {
1031
+ cy.get("#sc-grouped-second").shadow().find(".udex-list__group-content")
1032
+ .should("have.attr", "role", "button").and("have.attr", "aria-expanded", "false");
1033
+ }
1034
+ });
1035
+ });
1036
+
1037
+ it("Tab enters the collapse button; Enter, Space and Left/Right toggle it without selecting or losing focus", () => {
1038
+ cy.mount(UDExGroupedList());
1039
+ cy.get("#sc-grouped-fixture").then(UDExFixture => {
1040
+ UDExFixture[0].addEventListener("selection-change", cy.stub().as("scSelectionChange"));
1041
+ });
1042
+ UDExHeaderRow().should("have.attr", "role", "button");
1043
+ cy.get("#before-sc-grouped").realClick().should("be.focused");
1044
+ cy.realPress("Tab");
1045
+ UDExHeaderRow().should("be.focused");
1046
+
1047
+ (["Enter", "Space", "ArrowLeft"] as const).forEach(UDExKey => {
1048
+ cy.realPress(UDExKey);
1049
+ UDExHeaderRow().should("have.attr", "aria-expanded", "false");
1050
+ UDExHeaderRow().should("be.focused");
1051
+ cy.get("#sc-grouped-expanded").should("have.attr", "collapsed");
1052
+ cy.get("#sc-grouped-alpha").should("have.attr", "collapsed");
1053
+ cy.realPress(UDExKey === "ArrowLeft" ? "ArrowRight" : UDExKey);
1054
+ UDExHeaderRow().should("have.attr", "aria-expanded", "true");
1055
+ UDExHeaderRow().should("be.focused");
1056
+ cy.get("#sc-grouped-expanded").should("not.have.attr", "collapsed");
1057
+ cy.get("#sc-grouped-alpha").should("not.have.attr", "collapsed");
1058
+ });
1059
+ cy.realPress("Tab");
1060
+ cy.get("#sc-grouped-second").shadow().find(".udex-list__group-content").should("be.focused");
1061
+ cy.realPress("Tab");
1062
+ cy.get("#after-sc-grouped").should("be.focused");
1063
+ cy.get("@scSelectionChange").should("not.have.been.called");
1064
+ UDExAssertContentList();
1065
+ });
1066
+
1067
+ (["SingleSelect", "MultiSelect"] as const).forEach(UDExMode => {
1068
+ it(`${UDExMode} -> SingleContent -> ${UDExMode} clears List-owned tree semantics and rebuilds them`, () => {
1069
+ cy.mount(UDExGroupedList(UDExMode));
1070
+ const UDExAssertSelectionTree = () => {
1071
+ UDExInnerList().should("have.attr", "role", "tree");
1072
+ cy.get("#sc-grouped-list").find("[udex-list-group-header]").each(UDExGroup => {
1073
+ cy.wrap(UDExGroup).should(UDExHost => {
1074
+ expect(UDExHost).to.have.prop("_treeMode", true);
1075
+ expect(UDExHost).to.have.attr("_list-managed-tree-mode");
1076
+ });
1077
+ cy.wrap(UDExGroup).shadow().find(".udex-list__group").should("have.attr", "role", "none");
1078
+ cy.wrap(UDExGroup).shadow().find('[role="group"] > slot').should("have.length", 1);
1079
+ if (UDExMode === "MultiSelect") {
1080
+ cy.wrap(UDExGroup).shadow().find("[udex-list-item]#group-heading").shadow()
1081
+ .find("[part=item]").should("have.attr", "role", "treeitem");
1082
+ } else {
1083
+ cy.wrap(UDExGroup).shadow().find(".udex-list__group-content").should("have.attr", "role", "treeitem");
1084
+ }
1085
+ });
1086
+ cy.get("#sc-grouped-list").find("[udex-list-item]").each(UDExItem => {
1087
+ cy.wrap(UDExItem).should(UDExHost => {
1088
+ expect(UDExHost).to.have.prop("accessibleRole", "treeitem");
1089
+ expect(UDExHost).to.have.attr("_list-managed-role");
1090
+ });
1091
+ cy.wrap(UDExItem).shadow().find("[part=item]").should("have.attr", "role", "treeitem")
1092
+ .and("have.attr", UDExMode === "MultiSelect" ? "aria-checked" : "aria-selected", "false");
1093
+ });
1094
+ };
1095
+ UDExAssertSelectionTree();
1096
+ cy.get("#sc-grouped-list").invoke("prop", "mode", "SingleContent");
1097
+ UDExAssertContentList();
1098
+ UDExHeaderRow().should("have.attr", "role", "button");
1099
+ cy.get("#sc-grouped-list").invoke("prop", "mode", UDExMode);
1100
+ UDExAssertSelectionTree();
1101
+ });
1102
+ });
1103
+
1104
+ it("clears List-owned tree modes when the last collapsible group is removed", () => {
1105
+ cy.mount(UDExGroupedList("MultiSelect", true));
1106
+ UDExInnerList().should("have.attr", "role", "tree");
1107
+ cy.get("#sc-grouped-list").find("[udex-list-group-header]")
1108
+ .should("have.length", 2).each(UDExGroup => {
1109
+ cy.wrap(UDExGroup).should("have.attr", "_list-managed-tree-mode");
1110
+ });
1111
+ cy.get("#sc-grouped-expanded").invoke("prop", "collapsible", false).should("not.have.attr", "collapsible");
1112
+ // Re-render the List after changing its slotted group's configuration.
1113
+ cy.get("#sc-grouped-list").invoke("prop", "accessibleName", "Updated grouped information");
1114
+ UDExInnerList().should("have.attr", "role", "listbox");
1115
+ UDExInnerList().should("have.attr", "aria-multiselectable", "true");
1116
+ cy.get("#sc-grouped-list").find("[udex-list-group-header]").each(UDExGroup => {
1117
+ cy.wrap(UDExGroup).should(UDExHost => {
1118
+ expect(UDExHost).to.have.prop("_treeMode", false);
1119
+ expect(UDExHost).not.to.have.attr("_list-managed-tree-mode");
1120
+ });
1121
+ cy.wrap(UDExGroup).shadow().find("[udex-list-item]#group-heading").shadow()
1122
+ .find("[part=item]").should("have.attr", "role", "option");
1123
+ });
1124
+ cy.get("#sc-grouped-list").find("[udex-list-item]").each(UDExItem => {
1125
+ cy.wrap(UDExItem).should("not.have.attr", "_list-managed-role");
1126
+ cy.wrap(UDExItem).should("have.prop", "accessibleRole", "");
1127
+ cy.wrap(UDExItem).shadow().find("[part=item]").should("have.attr", "role", "option");
1128
+ });
1129
+ });
1130
+
1131
+ it("preserves externally supplied tree mode and host-owned roles in SingleContent", () => {
1132
+ cy.mount(
1133
+ <UDExList id="sc-host-tree" mode="SingleContent" role="tree" accessibleRole="">
1134
+ <UDExListGroupHeader collapsible label="Host-owned group" role="treeitem" aria-label="Host-owned group"
1135
+ aria-expanded="true" _treeMode={true} _useHostAriaRole={true}>
1136
+ <UDExListItem label="Alpha" role="treeitem" accessibleRole="treeitem" _useHostAriaRole={true} />
1137
+ </UDExListGroupHeader>
1138
+ </UDExList>
1139
+ );
1140
+ cy.get("#sc-host-tree").should("have.attr", "role", "tree");
1141
+ cy.get("#sc-host-tree").shadow().find("#udex-list").should("have.attr", "role", "none");
1142
+ cy.get("#sc-host-tree").find("[udex-list-group-header]").should(UDExGroup => {
1143
+ expect(UDExGroup).to.have.prop("_treeMode", true);
1144
+ expect(UDExGroup).to.have.prop("_useHostAriaRole", true);
1145
+ expect(UDExGroup).to.have.attr("role", "treeitem");
1146
+ expect(UDExGroup).to.have.attr("aria-expanded", "true");
1147
+ expect(UDExGroup).not.to.have.attr("_list-managed-tree-mode");
1148
+ }).shadow().find(".udex-list__group-content").should(UDExHeader => {
1149
+ expect(UDExHeader).to.have.attr("role", "presentation");
1150
+ ["tabindex", "aria-expanded", "aria-controls", "aria-labelledby", "aria-owns"].forEach(UDExAttribute => {
1151
+ expect(UDExHeader).not.to.have.attr(UDExAttribute);
1152
+ });
1153
+ });
1154
+ cy.get("#sc-host-tree").find("[udex-list-group-header]").shadow()
1155
+ .find('.udex-list__group[role="presentation"] [role="group"] > slot').should("have.length", 1);
1156
+ cy.get("#sc-host-tree").find("[udex-list-item]").should(UDExItem => {
1157
+ expect(UDExItem).to.have.attr("role", "treeitem");
1158
+ expect(UDExItem).to.have.prop("accessibleRole", "treeitem");
1159
+ expect(UDExItem).to.have.prop("_useHostAriaRole", true);
1160
+ expect(UDExItem).not.to.have.attr("_list-managed-role");
1161
+ }).shadow().find("[part=item]").should("have.attr", "role", "presentation");
1162
+ });
1163
+ });