@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.
- package/cypress/specs/AccordionSpotlight.cy.tsx +43 -2
- package/cypress/specs/Carousel.cy.tsx +88 -0
- package/cypress/specs/CountrySelector.cy.tsx +233 -6
- package/cypress/specs/FilterMenu.cy.tsx +39 -45
- package/cypress/specs/List.cy.tsx +624 -10
- package/cypress/specs/ListItem.cy.tsx +50 -0
- package/cypress/specs/SelectBox.cy.tsx +239 -131
- package/cypress/specs/SelectBoxAccessibility.cy.tsx +3109 -0
- package/cypress/specs/TagGroup.cy.tsx +168 -0
- package/dist/AccordionSpotlight.d.ts +7 -2
- package/dist/AccordionSpotlight.js +31 -6
- package/dist/AccordionSpotlight.js.map +1 -1
- package/dist/AccordionSpotlightItem.d.ts +3 -1
- package/dist/AccordionSpotlightItem.js +3 -1
- package/dist/AccordionSpotlightItem.js.map +1 -1
- package/dist/Carousel.d.ts +1 -0
- package/dist/Carousel.js +19 -3
- package/dist/Carousel.js.map +1 -1
- package/dist/CountrySelector.js +1 -3
- package/dist/CountrySelector.js.map +1 -1
- package/dist/List.d.ts +2 -0
- package/dist/List.js +22 -4
- package/dist/List.js.map +1 -1
- package/dist/ListGroupHeader.js +5 -1
- package/dist/ListGroupHeader.js.map +1 -1
- package/dist/ListGroupHeaderBase.d.ts +2 -0
- package/dist/ListGroupHeaderBase.js +15 -2
- package/dist/ListGroupHeaderBase.js.map +1 -1
- package/dist/ListGroupHeaderTemplate.js +14 -22
- package/dist/ListGroupHeaderTemplate.js.map +1 -1
- package/dist/ListItem.d.ts +1 -2
- package/dist/ListItem.js +14 -12
- package/dist/ListItem.js.map +1 -1
- package/dist/ListItemBase.d.ts +5 -0
- package/dist/ListItemBase.js +11 -1
- package/dist/ListItemBase.js.map +1 -1
- package/dist/ListItemTemplate.js +1 -1
- package/dist/ListItemTemplate.js.map +1 -1
- package/dist/ListSelectAllTemplate.js +1 -1
- package/dist/ListSelectAllTemplate.js.map +1 -1
- package/dist/ListTemplate.js +2 -1
- package/dist/ListTemplate.js.map +1 -1
- package/dist/Search.d.ts +13 -3
- package/dist/Search.js +36 -11
- package/dist/Search.js.map +1 -1
- package/dist/SearchableListBase.d.ts +2 -0
- package/dist/SearchableListBase.js +13 -3
- package/dist/SearchableListBase.js.map +1 -1
- package/dist/SearchableListBaseTemplate.js +42 -33
- package/dist/SearchableListBaseTemplate.js.map +1 -1
- package/dist/SelectBase.d.ts +18 -33
- package/dist/SelectBase.js +124 -139
- package/dist/SelectBase.js.map +1 -1
- package/dist/SelectBox.d.ts +3 -0
- package/dist/SelectBox.js +19 -7
- package/dist/SelectBox.js.map +1 -1
- package/dist/SelectBoxTemplate.js +3 -3
- package/dist/SelectBoxTemplate.js.map +1 -1
- package/dist/SideNavigationItem.js +2 -0
- package/dist/SideNavigationItem.js.map +1 -1
- package/dist/SideNavigationItemTemplate.d.ts +2 -0
- package/dist/SideNavigationItemTemplate.js +15 -0
- package/dist/SideNavigationItemTemplate.js.map +1 -0
- package/dist/SideNavigationMobile.js +1 -2
- package/dist/SideNavigationMobile.js.map +1 -1
- package/dist/SideNavigationSubItem.js +2 -1
- package/dist/SideNavigationSubItem.js.map +1 -1
- package/dist/SideNavigationSubItemTemplate.js +1 -2
- package/dist/SideNavigationSubItemTemplate.js.map +1 -1
- package/dist/TagGroup.d.ts +3 -0
- package/dist/TagGroup.js +31 -0
- package/dist/TagGroup.js.map +1 -1
- package/dist/TagGroupTemplate.js +1 -1
- package/dist/TagGroupTemplate.js.map +1 -1
- package/dist/assets/{InputSuggestions-1OnUn6pM.js → InputSuggestions-BPKrPqPb.js} +1 -1
- package/dist/assets/{ListItemStandardExpandableTextTemplate-CoBAMp_v.js → ListItemStandardExpandableTextTemplate-DkdOcCxC.js} +1 -1
- package/dist/assets/{bundle.esm-BqaVWFKf.js → bundle.esm-17Yt5gDr.js} +232 -233
- package/dist/css/themes/AccordionSpotlight.css +1 -1
- package/dist/css/themes/ListGroupHeader.css +1 -1
- package/dist/css/themes/SideNavigationItem.css +1 -1
- package/dist/css/themes/SideNavigationMobile.css +1 -1
- package/dist/css/themes/SideNavigationSubItem.css +1 -1
- package/dist/css/themes/Table.css +1 -1
- package/dist/custom-elements-internal.json +7 -7
- package/dist/custom-elements.json +7 -7
- package/dist/generated/i18n/i18n-defaults.d.ts +2 -1
- package/dist/generated/i18n/i18n-defaults.js +2 -1
- package/dist/generated/i18n/i18n-defaults.js.map +1 -1
- package/dist/generated/themes/AccordionSpotlight.css.d.ts +1 -1
- package/dist/generated/themes/AccordionSpotlight.css.js +1 -1
- package/dist/generated/themes/AccordionSpotlight.css.js.map +1 -1
- package/dist/generated/themes/ListGroupHeader.css.d.ts +1 -1
- package/dist/generated/themes/ListGroupHeader.css.js +1 -1
- package/dist/generated/themes/ListGroupHeader.css.js.map +1 -1
- package/dist/generated/themes/SideNavigationItem.css.d.ts +1 -1
- package/dist/generated/themes/SideNavigationItem.css.js +1 -1
- package/dist/generated/themes/SideNavigationItem.css.js.map +1 -1
- package/dist/generated/themes/SideNavigationMobile.css.d.ts +1 -1
- package/dist/generated/themes/SideNavigationMobile.css.js +1 -1
- package/dist/generated/themes/SideNavigationMobile.css.js.map +1 -1
- package/dist/generated/themes/SideNavigationSubItem.css.d.ts +1 -1
- package/dist/generated/themes/SideNavigationSubItem.css.js +1 -1
- package/dist/generated/themes/SideNavigationSubItem.css.js.map +1 -1
- package/dist/generated/themes/Table.css.d.ts +1 -1
- package/dist/generated/themes/Table.css.js +1 -1
- package/dist/generated/themes/Table.css.js.map +1 -1
- package/dist/messagebundle.properties +3 -0
- package/dist/vscode.html-custom-data.json +4 -4
- package/dist/web-types.json +9 -9
- package/package.json +1 -1
- package/src/AccordionSpotlight.ts +35 -6
- package/src/AccordionSpotlightItem.ts +3 -1
- package/src/Carousel.ts +22 -3
- package/src/CountrySelector.ts +1 -3
- package/src/List.ts +21 -4
- package/src/ListGroupHeader.ts +5 -1
- package/src/ListGroupHeaderBase.ts +16 -2
- package/src/ListGroupHeaderTemplate.tsx +29 -47
- package/src/ListItem.ts +14 -13
- package/src/ListItemBase.ts +14 -1
- package/src/ListItemTemplate.tsx +4 -4
- package/src/ListSelectAllTemplate.tsx +10 -8
- package/src/ListTemplate.tsx +14 -10
- package/src/Search.ts +48 -13
- package/src/SearchableListBase.ts +15 -3
- package/src/SearchableListBaseTemplate.tsx +51 -36
- package/src/SelectBase.ts +130 -144
- package/src/SelectBox.ts +21 -7
- package/src/SelectBoxTemplate.tsx +10 -9
- package/src/SideNavigationItem.ts +2 -0
- package/src/SideNavigationItemTemplate.tsx +97 -0
- package/src/SideNavigationMobile.ts +1 -2
- package/src/SideNavigationSubItem.ts +2 -1
- package/src/SideNavigationSubItemTemplate.tsx +7 -6
- package/src/TagGroup.ts +32 -0
- package/src/TagGroupTemplate.tsx +1 -1
- package/src/generated/i18n/i18n-defaults.ts +2 -2
- package/src/generated/themes/AccordionSpotlight.css.ts +1 -1
- package/src/generated/themes/ListGroupHeader.css.ts +1 -1
- package/src/generated/themes/SideNavigationItem.css.ts +1 -1
- package/src/generated/themes/SideNavigationMobile.css.ts +1 -1
- package/src/generated/themes/SideNavigationSubItem.css.ts +1 -1
- package/src/generated/themes/Table.css.ts +1 -1
- package/src/i18n/messagebundle.properties +3 -0
- package/src/themes/AccordionSpotlight.css +9 -9
- package/src/themes/ListGroupHeader.css +2 -43
- package/src/themes/SideNavigationItem.css +56 -6
- package/src/themes/SideNavigationMobile.css +61 -15
- package/src/themes/SideNavigationSubItem.css +14 -109
- package/src/themes/Table.css +6 -1
- package/dist/css/themes/SideNavigationMobileDropdown.css +0 -1
- package/dist/generated/themes/SideNavigationMobileDropdown.css.d.ts +0 -2
- package/dist/generated/themes/SideNavigationMobileDropdown.css.js +0 -8
- package/dist/generated/themes/SideNavigationMobileDropdown.css.js.map +0 -1
- package/src/generated/themes/SideNavigationMobileDropdown.css.ts +0 -9
- package/src/themes/SideNavigationMobileDropdown.css +0 -36
|
@@ -0,0 +1,3109 @@
|
|
|
1
|
+
import { UDExButton, UDExCountrySelector, UDExSelectBox, UDExSelectBoxGroupHeader, UDExSelectBoxItem, UDExSelectBoxSelectAll } from "../support/component-imports.js";
|
|
2
|
+
import { UDExListMode } from "../../src/types/List.js";
|
|
3
|
+
import type { UDExSearchableListBaseSelectionChangeEventDetail, UDExSelectBoxIconClickEventDetail } from "../../src/SearchableListBase.js";
|
|
4
|
+
import type { UDExSelectBoxRemoveEventDetail } from "../../src/SelectBox.js";
|
|
5
|
+
import type UDExSelectBoxElement from "../../src/SelectBox.js";
|
|
6
|
+
import type UDExSelectBoxItemElement from "../../src/SelectBoxItem.js";
|
|
7
|
+
import type UDExSelectBoxSelectAllElement from "../../src/SelectBoxSelectAll.js";
|
|
8
|
+
import { renderFinished } from "@ui5/webcomponents-base/dist/Render.js";
|
|
9
|
+
import { getTokensCountText } from "@ui5/webcomponents/dist/Tokenizer.js";
|
|
10
|
+
import type Tokenizer from "@ui5/webcomponents/dist/Tokenizer.js";
|
|
11
|
+
|
|
12
|
+
type UDExSelectBoxLayout = {
|
|
13
|
+
name: string;
|
|
14
|
+
grouped: boolean;
|
|
15
|
+
collapsible: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const UDExLayouts: UDExSelectBoxLayout[] = [
|
|
19
|
+
{ name: "flat", grouped: false, collapsible: false },
|
|
20
|
+
{ name: "grouped noncollapsible", grouped: true, collapsible: false },
|
|
21
|
+
{ name: "grouped collapsible", grouped: true, collapsible: true },
|
|
22
|
+
];
|
|
23
|
+
const UDExModes = [UDExListMode.SingleContent, UDExListMode.SingleSelect, UDExListMode.MultiSelect] as const;
|
|
24
|
+
type UDExSelectBoxMode = typeof UDExModes[number];
|
|
25
|
+
const UDExInnerStateAttributes = ["tabindex", "aria-selected", "aria-checked", "aria-expanded", "aria-disabled", "aria-description"];
|
|
26
|
+
|
|
27
|
+
const UDExCombobox = () => cy.get("[udex-select-box]").shadow().find('input[role="combobox"]');
|
|
28
|
+
const UDExListHost = () => cy.get("[udex-select-box]").shadow().find("[udex-list]");
|
|
29
|
+
const UDExHeaderHost = () => UDExListHost().find("[udex-list-group-header]");
|
|
30
|
+
const UDExNestedHeader = () => UDExHeaderHost().shadow().find("[udex-list-item]#group-heading");
|
|
31
|
+
const UDExLeafHost = (UDExLabel: string) => UDExListHost().find(`[udex-list-item][label="${UDExLabel}"]`);
|
|
32
|
+
const UDExSourceLeaf = (UDExLabel: string) => cy.get("[udex-select-box]").find(`[udex-select-box-item][label="${UDExLabel}"]`);
|
|
33
|
+
const UDExStatus = () => cy.get("[udex-select-box]").shadow().find('[aria-live="polite"]');
|
|
34
|
+
const UDExPopover = () => cy.get("[udex-select-box]").shadow().find("[ui5-popover]");
|
|
35
|
+
const UDExSelectAllHost = () => UDExListHost().find("[udex-list-select-all]");
|
|
36
|
+
const UDExSelectAllRow = () => UDExSelectAllHost().shadow().find('[udex-list-item][part="group-item"]');
|
|
37
|
+
|
|
38
|
+
function UDExMountClosedSelectBox(UDExMode: UDExSelectBoxMode, UDExLayout: UDExSelectBoxLayout, UDExWithFooter = false) {
|
|
39
|
+
const UDExItems = ["Alpha", "Beta", "Unavailable"].map(UDExLabel => (
|
|
40
|
+
<UDExSelectBoxItem label={UDExLabel} disabled={UDExLabel === "Unavailable"}></UDExSelectBoxItem>
|
|
41
|
+
));
|
|
42
|
+
|
|
43
|
+
cy.mount(
|
|
44
|
+
<div id="select-box-regression-fixture">
|
|
45
|
+
<UDExSelectBox label="Languages" mode={UDExMode}>
|
|
46
|
+
{UDExLayout.grouped ? (
|
|
47
|
+
<UDExSelectBoxGroupHeader label="Languages group" collapsible={UDExLayout.collapsible}>
|
|
48
|
+
{UDExItems}
|
|
49
|
+
</UDExSelectBoxGroupHeader>
|
|
50
|
+
) : UDExItems}
|
|
51
|
+
{UDExWithFooter && <UDExButton id="select-box-footer-action" slot="footer">Apply</UDExButton>}
|
|
52
|
+
</UDExSelectBox>
|
|
53
|
+
{UDExWithFooter && <button id="after-select-box">Next</button>}
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function UDExMountSelectBox(UDExMode: UDExSelectBoxMode, UDExLayout: UDExSelectBoxLayout, UDExWithFooter = false) {
|
|
59
|
+
UDExMountClosedSelectBox(UDExMode, UDExLayout, UDExWithFooter);
|
|
60
|
+
UDExOpenSelectBox(UDExLayout);
|
|
61
|
+
// Updated: this fixture starts on an expanded header only in the collapsible layout.
|
|
62
|
+
UDExAssertOpeningStatus(UDExLayout.collapsible ? "" : "expanded");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function UDExOpenSelectBox(UDExLayout: UDExSelectBoxLayout) {
|
|
66
|
+
// Listen outside the component to also check selection-change bubbling.
|
|
67
|
+
cy.get("#select-box-regression-fixture").then($fixture => {
|
|
68
|
+
$fixture[0].addEventListener("selection-change", cy.stub().as("selectionChange"));
|
|
69
|
+
});
|
|
70
|
+
cy.get("[udex-select-box]").shadow().find("[ui5-popover]").then($popover => {
|
|
71
|
+
$popover[0].addEventListener("ui5-open", cy.stub().as("popoverOpened"), { once: true });
|
|
72
|
+
});
|
|
73
|
+
UDExCombobox().realClick();
|
|
74
|
+
cy.get("@popoverOpened").should("have.been.calledOnce");
|
|
75
|
+
cy.get("[udex-select-box]").shadow().find("[ui5-popover]").should("have.attr", "open");
|
|
76
|
+
// realClick dispatches coordinates without Cypress's normal animation checks.
|
|
77
|
+
// Opening the popup and expanding a group can still move or clip those coordinates.
|
|
78
|
+
cy.get("[udex-select-box]").shadow().find("[ui5-popover]").should($popover => {
|
|
79
|
+
expect($popover).not.to.have.class("ui5-popup-opening");
|
|
80
|
+
expect($popover[0].shadowRoot!.getAnimations().filter(UDExAnimation =>
|
|
81
|
+
UDExAnimation.pending || UDExAnimation.playState === "running")).to.have.length(0);
|
|
82
|
+
});
|
|
83
|
+
if (UDExLayout.collapsible) {
|
|
84
|
+
// Updated: mixed grouping also contains noncollapsible headers without this content wrapper.
|
|
85
|
+
UDExHeaderHost().filter("[collapsible]").each($header => {
|
|
86
|
+
cy.wrap($header).shadow().find(".udex-list__group-list__content").should($content => {
|
|
87
|
+
expect($content[0].getAnimations().filter(UDExAnimation =>
|
|
88
|
+
UDExAnimation.pending || UDExAnimation.playState === "running")).to.have.length(0);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
UDExListHost().find("[_active-descendant]").should("have.length", 1);
|
|
93
|
+
UDExCombobox().should("be.focused");
|
|
94
|
+
|
|
95
|
+
// Post-click focus alone would miss a blur followed by a programmatic refocus.
|
|
96
|
+
UDExCombobox().then($input => {
|
|
97
|
+
$input[0].addEventListener("blur", cy.stub().as("comboboxBlur"));
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function UDExAssertPresentationItem(UDExHost: Cypress.Chainable<JQuery<HTMLElement>>) {
|
|
102
|
+
UDExHost.shadow().should($shadow => {
|
|
103
|
+
const $item = $shadow.find('[part="item"]');
|
|
104
|
+
expect($item).to.have.length(1);
|
|
105
|
+
expect($item).to.have.attr("role", "presentation");
|
|
106
|
+
UDExInnerStateAttributes.forEach(UDExAttribute => {
|
|
107
|
+
expect($item, `presentational row omits ${UDExAttribute}`).not.to.have.attr(UDExAttribute);
|
|
108
|
+
});
|
|
109
|
+
expect($shadow.find('[role="treeitem"], [role="option"], [role="listitem"]')).to.have.length(0);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function UDExAssertActiveDescendant(UDExHost: Cypress.Chainable<JQuery<HTMLElement>>) {
|
|
114
|
+
// Updated: consume the queued host first and verify IDREF identity, not just matching strings.
|
|
115
|
+
UDExHost.should($host => {
|
|
116
|
+
expect($host).to.have.length(1);
|
|
117
|
+
expect($host).to.have.attr("_active-descendant");
|
|
118
|
+
expect($host).to.have.class("_udex-active-descendant");
|
|
119
|
+
}).and("not.be.focused").then($host => {
|
|
120
|
+
UDExListHost().find("[_active-descendant]").should("have.length", 1).and($active => {
|
|
121
|
+
expect($active[0]).to.equal($host[0]);
|
|
122
|
+
});
|
|
123
|
+
UDExListHost().find("._udex-active-descendant").should("have.length", 1).and($highlight => {
|
|
124
|
+
expect($highlight[0]).to.equal($host[0]);
|
|
125
|
+
});
|
|
126
|
+
UDExCombobox().should($input => {
|
|
127
|
+
const UDExRoot = $input[0].getRootNode() as ShadowRoot;
|
|
128
|
+
const UDExId = $input.attr("aria-activedescendant");
|
|
129
|
+
expect(UDExId).to.equal($host[0].id).and.not.to.equal("");
|
|
130
|
+
expect(UDExRoot.getElementById(UDExId!), "IDREF resolves to the rendered dropdown host, not a selected tag")
|
|
131
|
+
.to.equal($host[0]);
|
|
132
|
+
const UDExMatches = Array.from(UDExRoot.querySelectorAll("[id]")).filter(UDExElement => UDExElement.id === UDExId);
|
|
133
|
+
expect(UDExMatches, "exactly one element in the input's own shadow root owns the active ID").to.deep.equal([$host[0]]);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
UDExCombobox().should("be.focused").should($input => {
|
|
137
|
+
const UDExRoot = $input[0].getRootNode() as ShadowRoot;
|
|
138
|
+
expect($input[0].ownerDocument.activeElement).to.equal(UDExRoot.host);
|
|
139
|
+
expect(UDExRoot.activeElement).to.equal($input[0]);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function UDExAssertSelectionEvent(UDExLabel: string, UDExSelectedLabels: string[], UDExCallCount = 1) {
|
|
144
|
+
cy.get("@selectionChange").should("have.callCount", UDExCallCount)
|
|
145
|
+
.its("lastCall.args.0.detail").should((UDExDetail: UDExSearchableListBaseSelectionChangeEventDetail) => {
|
|
146
|
+
expect(UDExDetail.item?.label).to.equal(UDExLabel);
|
|
147
|
+
expect(UDExDetail.selectedItems.filter(UDExItem => !UDExItem.isGroup).map(UDExItem => UDExItem.label))
|
|
148
|
+
.to.deep.equal(UDExSelectedLabels);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function UDExAssertSupplementalStatus(UDExExpected: string) {
|
|
153
|
+
UDExStatus().should($status => {
|
|
154
|
+
expect($status).to.have.length(1);
|
|
155
|
+
expect($status.text()).to.equal(UDExExpected);
|
|
156
|
+
// Navigation never repeats native names; explicit selection feedback is tested separately.
|
|
157
|
+
const UDExSelectBox = ($status[0].getRootNode() as ShadowRoot).host;
|
|
158
|
+
UDExSelectBox.querySelectorAll("[udex-select-box-item], [udex-select-box-group-header], [udex-select-box-select-all]").forEach(UDExItem => {
|
|
159
|
+
["label", "supporting-text", "additional-text"].forEach(UDExAttribute => {
|
|
160
|
+
const UDExName = UDExItem.getAttribute(UDExAttribute);
|
|
161
|
+
if (UDExName) {
|
|
162
|
+
expect($status.text(), `status excludes ${UDExAttribute}: ${UDExName}`).not.to.contain(UDExName);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function UDExAssertSelectionStatus(UDExName: string, UDExState: "Selected" | "Unselected" | "Indeterminate") {
|
|
170
|
+
UDExStatus().should("have.length", 1).and("have.attr", "aria-atomic", "true");
|
|
171
|
+
UDExStatus().should("have.text", `${UDExName}, ${UDExState}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Callers specify the opening status independently of rendered attributes.
|
|
175
|
+
// Expanded active headers supply native feedback; other hosts need the popup fallback.
|
|
176
|
+
function UDExAssertOpeningStatus(UDExExpected: "" | "expanded" = "expanded") {
|
|
177
|
+
// An empty expectation must not pass against the initial blank span before live text renders.
|
|
178
|
+
cy.then(() => renderFinished());
|
|
179
|
+
cy.get("[udex-select-box]").shadow().find("[aria-live]").should("have.length", 1).and("have.attr", "aria-live", "polite");
|
|
180
|
+
UDExAssertSupplementalStatus(UDExExpected);
|
|
181
|
+
UDExStatus().should("have.attr", "aria-atomic", "true");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function UDExAssertOpenWithActiveDescendant(UDExHost: Cypress.Chainable<JQuery<HTMLElement>>, UDExExpectedStatus: "" | "expanded" = "expanded") {
|
|
185
|
+
// Consume the queued host query before unrelated queries change Cypress's subject.
|
|
186
|
+
UDExAssertActiveDescendant(UDExHost);
|
|
187
|
+
UDExAssertOpeningStatus(UDExExpectedStatus);
|
|
188
|
+
UDExPopover().should("have.attr", "open");
|
|
189
|
+
UDExCombobox().should("have.attr", "aria-expanded", "true");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function UDExAssertDescription(UDExExpected: string, UDExTokenCount?: number) {
|
|
193
|
+
UDExCombobox().should($input => {
|
|
194
|
+
const UDExIds = $input.attr("aria-describedby")!.split(/\s+/);
|
|
195
|
+
const UDExRoot = $input[0].getRootNode() as ShadowRoot;
|
|
196
|
+
expect(UDExIds).to.have.length(1);
|
|
197
|
+
const UDExDescription = UDExRoot.getElementById(UDExIds[0]);
|
|
198
|
+
expect(UDExDescription, "description resolves in the input's own shadow root").not.to.equal(null);
|
|
199
|
+
const UDExText = UDExTokenCount === undefined ? UDExExpected
|
|
200
|
+
: [UDExExpected, getTokensCountText(UDExTokenCount)].filter(Boolean).join(" ");
|
|
201
|
+
expect(UDExDescription!.textContent).to.equal(UDExText);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function UDExAssertSupplementalActiveDescendant(UDExHost: Cypress.Chainable<JQuery<HTMLElement>>, UDExExpected: string) {
|
|
206
|
+
UDExAssertActiveDescendant(UDExHost);
|
|
207
|
+
UDExAssertSupplementalStatus(UDExExpected);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function UDExAssertHostPosition(UDExHost: Cypress.Chainable<JQuery<HTMLElement>>, UDExPosition: number, UDExSize: number, UDExLevel?: number) {
|
|
211
|
+
UDExHost.should($host => {
|
|
212
|
+
expect($host).to.have.length(1);
|
|
213
|
+
expect($host).to.have.attr("role", UDExLevel === undefined ? "option" : "treeitem");
|
|
214
|
+
expect($host).to.have.attr("aria-posinset", String(UDExPosition));
|
|
215
|
+
expect($host).to.have.attr("aria-setsize", String(UDExSize));
|
|
216
|
+
if (UDExLevel === undefined) {
|
|
217
|
+
expect($host).not.to.have.attr("aria-level");
|
|
218
|
+
} else {
|
|
219
|
+
expect($host).to.have.attr("aria-level", String(UDExLevel));
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function UDExAssertNativeActiveDescendant(UDExHost: Cypress.Chainable<JQuery<HTMLElement>>, UDExPosition: number, UDExSize: number, UDExLevel?: number) {
|
|
225
|
+
// Consume the host before enqueueing any other queries, preserving exact IDREF ownership checks.
|
|
226
|
+
UDExHost.then($host => {
|
|
227
|
+
UDExAssertActiveDescendant(cy.wrap($host));
|
|
228
|
+
UDExAssertHostPosition(cy.wrap($host), UDExPosition, UDExSize, UDExLevel);
|
|
229
|
+
});
|
|
230
|
+
UDExAssertSupplementalStatus("");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function UDExAssertHostSelection(UDExHost: Cypress.Chainable<JQuery<HTMLElement>>, UDExSelected: boolean,
|
|
234
|
+
UDExIndeterminate = false, UDExPartialDescription = "Indeterminate") {
|
|
235
|
+
UDExHost.should($host => {
|
|
236
|
+
expect($host).to.have.length(1);
|
|
237
|
+
expect($host).to.have.attr("aria-selected", String(UDExSelected && !UDExIndeterminate));
|
|
238
|
+
expect($host).not.to.have.attr("aria-checked");
|
|
239
|
+
if (UDExIndeterminate) {
|
|
240
|
+
expect($host).to.have.attr("aria-description", UDExPartialDescription);
|
|
241
|
+
} else {
|
|
242
|
+
expect($host).not.to.have.attr("aria-description");
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function UDExAssertNativeHostStates() {
|
|
248
|
+
UDExListHost().find("[udex-list-item], [udex-list-group-header], [udex-list-select-all]").should($hosts => {
|
|
249
|
+
expect($hosts.length).to.be.greaterThan(0);
|
|
250
|
+
$hosts.each((UDExIndex, UDExHost) => {
|
|
251
|
+
expect(UDExHost).not.to.have.attr("aria-checked");
|
|
252
|
+
// Default-valued UI5 properties need not serialize an attribute.
|
|
253
|
+
if ((UDExHost as HTMLElement & { mode: string }).mode === UDExListMode.SingleContent) {
|
|
254
|
+
expect(UDExHost).not.to.have.attr("aria-selected");
|
|
255
|
+
} else {
|
|
256
|
+
expect(UDExHost.getAttribute("aria-selected"), `host ${UDExIndex} has Boolean selection`).to.be.oneOf(["true", "false"]);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function UDExAssertLeafName(UDExHost: Cypress.Chainable<JQuery<HTMLElement>>, UDExLabel: string, UDExSupportingText = "", UDExAdditionalText = "") {
|
|
263
|
+
// Active-descendant hosts expose the complete name, including text outside the title.
|
|
264
|
+
UDExHost.should("have.attr", "aria-label", [UDExLabel, UDExSupportingText, UDExAdditionalText].filter(Boolean).join(" "));
|
|
265
|
+
// Keep the rendered title/name-source checks, including after search highlighting.
|
|
266
|
+
UDExHost.shadow().find(".udex-list__content-wrap").should($content => {
|
|
267
|
+
expect($content).to.have.attr("aria-label", UDExLabel);
|
|
268
|
+
const UDExLabelId = $content.attr("aria-labelledby")!;
|
|
269
|
+
const UDExRoot = $content[0].getRootNode() as ShadowRoot;
|
|
270
|
+
expect(UDExRoot.getElementById(UDExLabelId)?.textContent).to.equal(UDExLabel);
|
|
271
|
+
expect($content.find('[part="title"]').text()).to.equal(UDExLabel);
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const UDExCountryGroups = [
|
|
276
|
+
{ label: "Europe", supportingText: "Available countries", countries: ["Germany", "France", "Spain"] },
|
|
277
|
+
{ label: "Asia", supportingText: "More destinations", countries: ["Japan", "Jordan", "China"] },
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
function UDExMountCountrySelectBox() {
|
|
281
|
+
cy.mount(
|
|
282
|
+
<div id="select-box-regression-fixture">
|
|
283
|
+
<UDExSelectBox label="Country">
|
|
284
|
+
{UDExCountryGroups.map(UDExGroup => (
|
|
285
|
+
<UDExSelectBoxGroupHeader label={UDExGroup.label} supportingText={UDExGroup.supportingText} collapsible>
|
|
286
|
+
{UDExGroup.countries.map(UDExCountry => <UDExSelectBoxItem label={UDExCountry}></UDExSelectBoxItem>)}
|
|
287
|
+
</UDExSelectBoxGroupHeader>
|
|
288
|
+
))}
|
|
289
|
+
</UDExSelectBox>
|
|
290
|
+
</div>
|
|
291
|
+
);
|
|
292
|
+
UDExOpenSelectBox(UDExLayouts[2]);
|
|
293
|
+
// Updated: the first country group is already expanded and owns its native state.
|
|
294
|
+
UDExAssertOpeningStatus("");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function UDExMountSelectAll(UDExLabel?: string, UDExSupportingText?: string, UDExDisabledChild = false) {
|
|
298
|
+
cy.mount(
|
|
299
|
+
<div id="select-box-regression-fixture">
|
|
300
|
+
<UDExSelectBox accessibleName="Languages" supportingText="Choose languages" mode={UDExListMode.MultiSelect}>
|
|
301
|
+
<UDExSelectBoxItem label="Worldwide"></UDExSelectBoxItem>
|
|
302
|
+
<UDExSelectBoxSelectAll id="select-all-source" label={UDExLabel} supportingText={UDExSupportingText}>
|
|
303
|
+
<UDExSelectBoxItem label="Alpha" supportingText="First choice" additionalText="Code A"></UDExSelectBoxItem>
|
|
304
|
+
<UDExSelectBoxItem label="Beta" supportingText="Second choice" additionalText="Code B"></UDExSelectBoxItem>
|
|
305
|
+
{UDExDisabledChild && <UDExSelectBoxItem label="Unavailable" disabled></UDExSelectBoxItem>}
|
|
306
|
+
</UDExSelectBoxSelectAll>
|
|
307
|
+
<UDExSelectBoxItem label="Other languages"></UDExSelectBoxItem>
|
|
308
|
+
</UDExSelectBox>
|
|
309
|
+
</div>
|
|
310
|
+
);
|
|
311
|
+
UDExOpenSelectBox(UDExLayouts[1]);
|
|
312
|
+
UDExAssertOpenWithActiveDescendant(UDExLeafHost("Worldwide"));
|
|
313
|
+
UDExAssertHostPosition(UDExLeafHost("Worldwide"), 1, 3, 1);
|
|
314
|
+
cy.get("#select-box-regression-fixture").then($fixture => {
|
|
315
|
+
$fixture[0].addEventListener("select-all", cy.stub().as("selectAll"));
|
|
316
|
+
});
|
|
317
|
+
UDExSelectAllHost().then($host => {
|
|
318
|
+
$host[0].addEventListener("ui5-select-all", cy.stub().as("routedSelectAll"));
|
|
319
|
+
$host[0].addEventListener("ui5-header-press", cy.stub().as("wrongHeaderPress"));
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function UDExAssertSelectAllEvent(UDExCallCount: number) {
|
|
324
|
+
cy.get("@selectAll").should("have.callCount", UDExCallCount)
|
|
325
|
+
.its("lastCall.args.0").should((UDExEvent: Event) => {
|
|
326
|
+
expect(UDExEvent.type).to.equal("select-all");
|
|
327
|
+
expect(UDExEvent.bubbles).to.equal(true);
|
|
328
|
+
});
|
|
329
|
+
UDExSelectAllHost().then($host => {
|
|
330
|
+
cy.get("@routedSelectAll").should("have.callCount", UDExCallCount)
|
|
331
|
+
.its("lastCall.args.0.detail.item").should("equal", $host[0]);
|
|
332
|
+
});
|
|
333
|
+
cy.get("@wrongHeaderPress").should("not.have.been.called");
|
|
334
|
+
// Select All has its own public event; individual leaves retain selection-change payloads.
|
|
335
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
describe("SelectBase resolved popup names", () => {
|
|
339
|
+
const UDExNameCases: {
|
|
340
|
+
name: string;
|
|
341
|
+
label?: string;
|
|
342
|
+
accessibleName?: string;
|
|
343
|
+
accessibleNameRef?: string;
|
|
344
|
+
expected: string;
|
|
345
|
+
}[] = [
|
|
346
|
+
{ name: "accessibleName only", accessibleName: "Accessible languages", expected: "Accessible languages" },
|
|
347
|
+
{ name: "accessibleName before visible label", label: "Languages", accessibleName: "Accessible languages", expected: "Accessible languages" },
|
|
348
|
+
{ name: "external reference before accessibleName and label", label: "Languages", accessibleName: "Accessible languages", accessibleNameRef: "popup-name", expected: "Referenced languages" },
|
|
349
|
+
{ name: "multiple external references in IDREF order", label: "Languages", accessibleName: "Accessible languages", accessibleNameRef: "popup-region popup-name", expected: "Regional Referenced languages" },
|
|
350
|
+
{ name: "visible label fallback", label: "Languages", expected: "Languages" },
|
|
351
|
+
{ name: "existing translated default", expected: "Select country" },
|
|
352
|
+
];
|
|
353
|
+
|
|
354
|
+
[
|
|
355
|
+
{ component: UDExSelectBox, tag: "[udex-select-box]", name: "SelectBox" },
|
|
356
|
+
{ component: UDExCountrySelector, tag: "[udex-country-selector]", name: "CountrySelector" },
|
|
357
|
+
].forEach(({ component: UDExComponent, tag: UDExTag, name: UDExName }) => {
|
|
358
|
+
const UDExInput = () => cy.get(UDExTag).shadow().find('input[role="combobox"]');
|
|
359
|
+
const UDExPopupList = () => cy.get(UDExTag).shadow().find("[udex-list]");
|
|
360
|
+
|
|
361
|
+
function UDExMountNamedPopup(UDExLabel?: string, UDExAccessibleName?: string, UDExNameRef?: string) {
|
|
362
|
+
cy.mount(
|
|
363
|
+
<div>
|
|
364
|
+
<span id="popup-name">Referenced languages</span>
|
|
365
|
+
<span id="popup-region">Regional</span>
|
|
366
|
+
<UDExComponent label={UDExLabel} accessibleName={UDExAccessibleName} accessibleNameRef={UDExNameRef}>
|
|
367
|
+
{UDExTag === "[udex-select-box]" && <UDExSelectBoxItem label="Alpha"></UDExSelectBoxItem>}
|
|
368
|
+
</UDExComponent>
|
|
369
|
+
</div>
|
|
370
|
+
);
|
|
371
|
+
// Install before opening: an eventual refocus must not hide a transient blur.
|
|
372
|
+
UDExInput().then($input => {
|
|
373
|
+
$input[0].addEventListener("blur", cy.stub().as("namedPopupBlur"));
|
|
374
|
+
});
|
|
375
|
+
UDExInput().realClick();
|
|
376
|
+
cy.get(UDExTag).shadow().find("[ui5-popover]").should("have.attr", "open");
|
|
377
|
+
UDExPopupList().find("[_active-descendant]").should("have.length", 1).then($active => {
|
|
378
|
+
UDExInput().should("have.attr", "aria-activedescendant", $active[0].id);
|
|
379
|
+
});
|
|
380
|
+
UDExInput().should("be.focused").should($input => {
|
|
381
|
+
const UDExRoot = $input[0].getRootNode() as ShadowRoot;
|
|
382
|
+
expect($input[0].ownerDocument.activeElement).to.equal(UDExRoot.host);
|
|
383
|
+
expect(UDExRoot.activeElement).to.equal($input[0]);
|
|
384
|
+
});
|
|
385
|
+
cy.get("@namedPopupBlur").should("not.have.been.called");
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function UDExAssertPopupName(UDExExpected: string) {
|
|
389
|
+
UDExInput().should("have.attr", "aria-label", UDExExpected);
|
|
390
|
+
UDExInput().should("have.attr", "aria-haspopup", "listbox");
|
|
391
|
+
UDExPopupList().should($list => {
|
|
392
|
+
expect($list).to.have.attr("role", "listbox");
|
|
393
|
+
expect($list).to.have.attr("aria-label", UDExExpected);
|
|
394
|
+
expect($list).not.to.have.attr("aria-labelledby");
|
|
395
|
+
expect($list[0]).to.have.property("accessibleName", UDExExpected);
|
|
396
|
+
}).then($list => {
|
|
397
|
+
UDExInput().should("have.attr", "aria-controls", $list[0].id);
|
|
398
|
+
});
|
|
399
|
+
// CountrySelector's synthetic sources must remain safe in the shared tree-detection path.
|
|
400
|
+
UDExPopupList().find("[udex-list-item]").first().should("have.attr", "role", "option");
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
UDExNameCases.forEach(UDExCase => {
|
|
404
|
+
it(`${UDExName} uses the exact resolved name on the input and popup host for ${UDExCase.name}`, () => {
|
|
405
|
+
UDExMountNamedPopup(UDExCase.label, UDExCase.accessibleName, UDExCase.accessibleNameRef);
|
|
406
|
+
UDExAssertPopupName(UDExCase.expected);
|
|
407
|
+
cy.get("@namedPopupBlur").should("not.have.been.called");
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it(`${UDExName} updates both host and input names when the visible label changes or is cleared`, () => {
|
|
412
|
+
UDExMountNamedPopup("Languages");
|
|
413
|
+
UDExAssertPopupName("Languages");
|
|
414
|
+
cy.get(UDExTag).invoke("prop", "label", "Updated languages");
|
|
415
|
+
UDExAssertPopupName("Updated languages");
|
|
416
|
+
cy.get(UDExTag).shadow().find('label[for="search-input"]').should("have.text", "Updated languages");
|
|
417
|
+
cy.get(UDExTag).invoke("prop", "label", "");
|
|
418
|
+
UDExAssertPopupName("Select country");
|
|
419
|
+
UDExInput().should("be.focused");
|
|
420
|
+
cy.get("@namedPopupBlur").should("not.have.been.called");
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
describe("SelectBox MultiSelect first-ever opening announcements", () => {
|
|
426
|
+
UDExLayouts.forEach(UDExLayout => {
|
|
427
|
+
(["pointer", "keyboard"] as const).forEach(UDExActivation => {
|
|
428
|
+
// Updated: expanded groups use native state; flat and noncollapsible hosts retain the popup fallback.
|
|
429
|
+
it(`uses ${UDExLayout.collapsible ? "native expanded-group state with empty live status" : "the expanded popup fallback"} on the first ${UDExActivation} opening and reopening of ${UDExLayout.name} MultiSelect without changing input or active-host identity`, () => {
|
|
430
|
+
// Never warm up by opening and closing: the first interaction must exercise a fresh popup.
|
|
431
|
+
UDExMountClosedSelectBox(UDExListMode.MultiSelect, UDExLayout);
|
|
432
|
+
cy.get("#select-box-regression-fixture").then($fixture => {
|
|
433
|
+
$fixture[0].addEventListener("selection-change", cy.stub().as("selectionChange"));
|
|
434
|
+
});
|
|
435
|
+
UDExPopover().then($popover => {
|
|
436
|
+
$popover[0].addEventListener("ui5-open", cy.stub().as("firstMultiSelectOpened"));
|
|
437
|
+
});
|
|
438
|
+
cy.get("[udex-select-box]").then($host => {
|
|
439
|
+
$host[0].addEventListener("close", cy.stub().as("firstMultiSelectClosed"));
|
|
440
|
+
});
|
|
441
|
+
UDExCombobox().then($input => {
|
|
442
|
+
const UDExOriginalInput = $input[0];
|
|
443
|
+
UDExOriginalInput.addEventListener("blur", cy.stub().as("comboboxBlur"));
|
|
444
|
+
const UDExAssertUnchangedInput = () => {
|
|
445
|
+
UDExCombobox().should($current => {
|
|
446
|
+
expect($current[0], "the original native input survives opening and selection").to.equal(UDExOriginalInput);
|
|
447
|
+
});
|
|
448
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
449
|
+
};
|
|
450
|
+
const UDExNextLabel = UDExLayout.grouped ? "Alpha" : "Beta";
|
|
451
|
+
|
|
452
|
+
[1, 2].forEach(UDExCycle => {
|
|
453
|
+
UDExPopover().should("not.have.attr", "open");
|
|
454
|
+
UDExCombobox().should("have.attr", "aria-expanded", "false");
|
|
455
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
456
|
+
UDExListHost().find("[_active-descendant], ._udex-active-descendant").should("not.exist");
|
|
457
|
+
UDExAssertSupplementalStatus("");
|
|
458
|
+
|
|
459
|
+
if (UDExActivation === "pointer") {
|
|
460
|
+
UDExCombobox().realClick();
|
|
461
|
+
} else {
|
|
462
|
+
UDExCombobox().focus().realPress("ArrowDown");
|
|
463
|
+
}
|
|
464
|
+
cy.get("@firstMultiSelectOpened").should("have.callCount", UDExCycle);
|
|
465
|
+
UDExAssertOpenWithActiveDescendant(UDExLayout.grouped ? UDExHeaderHost() : UDExLeafHost("Alpha"),
|
|
466
|
+
UDExLayout.collapsible ? "" : "expanded");
|
|
467
|
+
UDExAssertHostPosition(UDExLayout.grouped ? UDExHeaderHost() : UDExLeafHost("Alpha"), 1,
|
|
468
|
+
UDExLayout.grouped ? 1 : 3, UDExLayout.grouped ? 1 : undefined);
|
|
469
|
+
if (UDExLayout.grouped) {
|
|
470
|
+
// Assert native state separately: it must not determine the expected live text.
|
|
471
|
+
UDExHeaderHost().should($header => {
|
|
472
|
+
if (UDExLayout.collapsible) {
|
|
473
|
+
expect($header).to.have.attr("aria-expanded", "true");
|
|
474
|
+
} else {
|
|
475
|
+
expect($header).not.to.have.attr("aria-expanded");
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
UDExAssertHostSelection(UDExHeaderHost(), false);
|
|
479
|
+
}
|
|
480
|
+
UDExAssertUnchangedInput();
|
|
481
|
+
cy.get("@selectionChange").should("have.callCount", (UDExCycle - 1) * 2);
|
|
482
|
+
|
|
483
|
+
// Opening already activated the first host: this must advance, not activate it again.
|
|
484
|
+
cy.realPress("ArrowDown");
|
|
485
|
+
UDExAssertNativeActiveDescendant(UDExLeafHost(UDExNextLabel), UDExLayout.grouped ? 1 : 2, 3,
|
|
486
|
+
UDExLayout.grouped ? 2 : undefined);
|
|
487
|
+
UDExAssertHostSelection(UDExLeafHost(UDExNextLabel), false);
|
|
488
|
+
UDExAssertUnchangedInput();
|
|
489
|
+
|
|
490
|
+
UDExLeafHost(UDExNextLabel).then($leaf => {
|
|
491
|
+
[true, false].forEach((UDExSelected, UDExIndex) => {
|
|
492
|
+
cy.realPress(UDExSelected ? "Enter" : "Space");
|
|
493
|
+
UDExSourceLeaf(UDExNextLabel).should(UDExSelected ? "have.attr" : "not.have.attr", "selected");
|
|
494
|
+
UDExAssertHostSelection(UDExLeafHost(UDExNextLabel), UDExSelected);
|
|
495
|
+
UDExAssertActiveDescendant(UDExLeafHost(UDExNextLabel));
|
|
496
|
+
UDExLeafHost(UDExNextLabel).should($current => {
|
|
497
|
+
expect($current[0], "selection preserves the rendered active host").to.equal($leaf[0]);
|
|
498
|
+
});
|
|
499
|
+
UDExAssertSelectionStatus(UDExNextLabel, UDExSelected ? "Selected" : "Unselected");
|
|
500
|
+
UDExAssertSelectionEvent(UDExNextLabel, UDExSelected ? [UDExNextLabel] : [], (UDExCycle - 1) * 2 + UDExIndex + 1);
|
|
501
|
+
UDExPopover().should("have.attr", "open");
|
|
502
|
+
UDExCombobox().should("have.attr", "aria-expanded", "true");
|
|
503
|
+
UDExAssertUnchangedInput();
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
cy.realPress("Escape");
|
|
508
|
+
cy.get("@firstMultiSelectClosed").should("have.callCount", UDExCycle);
|
|
509
|
+
UDExPopover().should("not.have.attr", "open");
|
|
510
|
+
UDExCombobox().should("be.focused").and("have.attr", "aria-expanded", "false");
|
|
511
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
512
|
+
UDExListHost().find("[_active-descendant], ._udex-active-descendant").should("not.exist");
|
|
513
|
+
UDExAssertSupplementalStatus("");
|
|
514
|
+
UDExAssertUnchangedInput();
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
describe("SelectBox rating option accessibility", () => {
|
|
523
|
+
// These are DOM/interaction regressions, not assertions about JAWS speech.
|
|
524
|
+
type UDExRatingOption = {
|
|
525
|
+
rating: string;
|
|
526
|
+
ratingCount: string;
|
|
527
|
+
totalCount: string;
|
|
528
|
+
name: string;
|
|
529
|
+
ratingMax?: number;
|
|
530
|
+
label?: string;
|
|
531
|
+
supportingText?: string;
|
|
532
|
+
additionalText?: string;
|
|
533
|
+
};
|
|
534
|
+
const UDExRatings: UDExRatingOption[] = [
|
|
535
|
+
{ rating: "5", ratingCount: "5.0", totalCount: "23", name: "Rating 5.0, Total 23, Rating Indicator 5 of 5" },
|
|
536
|
+
{ rating: "4", ratingCount: "4.0", totalCount: "17", name: "Rating 4.0, Total 17, Rating Indicator 4 of 5" },
|
|
537
|
+
{ rating: "3", ratingCount: "3.0", totalCount: "34", name: "Rating 3.0, Total 34, Rating Indicator 3 of 5" },
|
|
538
|
+
{ rating: "2", ratingCount: "2.0", totalCount: "40", name: "Rating 2.0, Total 40, Rating Indicator 2 of 5" },
|
|
539
|
+
{ rating: "1", ratingCount: "1.0", totalCount: "73", name: "Rating 1.0, Total 73, Rating Indicator 1 of 5" },
|
|
540
|
+
];
|
|
541
|
+
const UDExRatingHost = (UDExIndex: number) => UDExListHost().find("[udex-list-item]").eq(UDExIndex);
|
|
542
|
+
const UDExRatingSource = (UDExIndex: number) => cy.get("[udex-select-box]").find("[udex-select-box-item]").eq(UDExIndex);
|
|
543
|
+
const UDExRatingTags = () => cy.get("[udex-select-box]").shadow().find("[udex-tag-group]").shadow().find("[udex-tag]");
|
|
544
|
+
|
|
545
|
+
function UDExMountRatings(UDExMode: UDExSelectBoxMode = UDExListMode.MultiSelect,
|
|
546
|
+
UDExActivation: "pointer" | "keyboard" = "pointer", UDExOptions = UDExRatings) {
|
|
547
|
+
cy.mount(
|
|
548
|
+
<div id="select-box-regression-fixture">
|
|
549
|
+
<UDExSelectBox label="Rating" mode={UDExMode}>
|
|
550
|
+
{UDExOptions.map(UDExOption => (
|
|
551
|
+
<UDExSelectBoxItem label={UDExOption.label} supportingText={UDExOption.supportingText}
|
|
552
|
+
additionalText={UDExOption.additionalText} rating={UDExOption.rating}
|
|
553
|
+
ratingCount={UDExOption.ratingCount} totalCount={UDExOption.totalCount}
|
|
554
|
+
ratingMax={UDExOption.ratingMax ?? 5}></UDExSelectBoxItem>
|
|
555
|
+
))}
|
|
556
|
+
</UDExSelectBox>
|
|
557
|
+
</div>
|
|
558
|
+
);
|
|
559
|
+
// Install before the very first opening to catch even a transient blur/refocus.
|
|
560
|
+
UDExCombobox().then($input => {
|
|
561
|
+
cy.wrap($input).as("ratingOriginalInput", { type: "static" });
|
|
562
|
+
$input[0].addEventListener("blur", cy.stub().as("ratingInputBlur"));
|
|
563
|
+
});
|
|
564
|
+
UDExPopover().should("not.have.attr", "open");
|
|
565
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
566
|
+
if (UDExActivation === "pointer") {
|
|
567
|
+
UDExOpenSelectBox(UDExLayouts[0]);
|
|
568
|
+
} else {
|
|
569
|
+
cy.get("#select-box-regression-fixture").then($fixture => {
|
|
570
|
+
$fixture[0].addEventListener("selection-change", cy.stub().as("selectionChange"));
|
|
571
|
+
});
|
|
572
|
+
UDExCombobox().focus().realPress("ArrowDown");
|
|
573
|
+
}
|
|
574
|
+
UDExAssertOpenWithActiveDescendant(UDExRatingHost(0), "expanded");
|
|
575
|
+
UDExAssertRatingInput();
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function UDExAssertRatingInput() {
|
|
579
|
+
cy.get<JQuery<HTMLElement>>("@ratingOriginalInput").then($original => {
|
|
580
|
+
UDExCombobox().should("be.focused").should($current => {
|
|
581
|
+
expect($current[0], "the original native input is retained").to.equal($original[0]);
|
|
582
|
+
["aria-description", "aria-roledescription", "aria-keyshortcuts"].forEach(UDExAttribute => {
|
|
583
|
+
expect($current, "no added navigation metadata").not.to.have.attr(UDExAttribute);
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
|
+
cy.get("@ratingInputBlur").should("not.have.been.called");
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function UDExAssertRatingName(UDExIndex: number, UDExName: string) {
|
|
591
|
+
UDExRatingHost(UDExIndex).should($host => {
|
|
592
|
+
expect($host).to.have.attr("aria-label", UDExName);
|
|
593
|
+
expect($host.attr("aria-label")).not.to.match(/checkbox|selected|unselected/i);
|
|
594
|
+
["aria-checked", "aria-description", "aria-roledescription", "aria-keyshortcuts"].forEach(UDExAttribute => {
|
|
595
|
+
expect($host).not.to.have.attr(UDExAttribute);
|
|
596
|
+
});
|
|
597
|
+
});
|
|
598
|
+
UDExAssertPresentationItem(UDExRatingHost(UDExIndex));
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function UDExAssertRatingCheckbox(UDExIndex: number, UDExSelected: boolean) {
|
|
602
|
+
UDExRatingHost(UDExIndex).shadow().find("[udex-checkbox]").should($checkbox => {
|
|
603
|
+
expect($checkbox).to.have.length(1);
|
|
604
|
+
expect($checkbox).to.have.attr("aria-hidden", "true");
|
|
605
|
+
expect($checkbox).to.have.attr("tabindex", "-1");
|
|
606
|
+
expect($checkbox[0]).to.have.property("checked", UDExSelected);
|
|
607
|
+
// UI5 may reflect an omitted name as accessible-name=""; absence is not the contract.
|
|
608
|
+
expect($checkbox.prop("accessibleName") || "").not.to.match(/checkbox|selected|unselected/i);
|
|
609
|
+
expect($checkbox.attr("accessible-name") || "").not.to.match(/checkbox|selected|unselected/i);
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function UDExAssertRatingVisual(UDExIndex: number, UDExOption: UDExRatingOption) {
|
|
614
|
+
UDExRatingHost(UDExIndex).shadow().find("[udex-rating-indicator]").should($rating => {
|
|
615
|
+
expect($rating).to.have.length(1);
|
|
616
|
+
expect($rating[0]).to.have.property("value", Number(UDExOption.rating));
|
|
617
|
+
expect($rating[0]).to.have.property("max", UDExOption.ratingMax ?? 5);
|
|
618
|
+
expect($rating).to.have.attr("rating-count", UDExOption.ratingCount);
|
|
619
|
+
expect($rating).to.have.attr("total-count", UDExOption.totalCount);
|
|
620
|
+
expect($rating).to.have.attr("disabled");
|
|
621
|
+
expect($rating).to.have.attr("readonly");
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function UDExAssertRatingEvent(UDExSource: UDExSelectBoxItemElement, UDExSelected: boolean, UDExCallCount: number) {
|
|
626
|
+
cy.get("@selectionChange").should("have.callCount", UDExCallCount).its("lastCall.args.0")
|
|
627
|
+
.should((UDExEvent: CustomEvent<UDExSearchableListBaseSelectionChangeEventDetail>) => {
|
|
628
|
+
expect(UDExEvent.type).to.equal("selection-change");
|
|
629
|
+
expect(UDExEvent.bubbles).to.equal(true);
|
|
630
|
+
expect(UDExEvent.detail.item, "payload uses the original source, not a rendered copy").to.equal(UDExSource);
|
|
631
|
+
expect(UDExEvent.detail.selectedItems).to.have.length(UDExSelected ? 1 : 0);
|
|
632
|
+
if (UDExSelected) {
|
|
633
|
+
expect(UDExEvent.detail.selectedItems[0]).to.equal(UDExSource);
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function UDExToggleRating(UDExActivation: "pointer" | "Enter" | "Space", UDExSelected: boolean, UDExCallCount: number) {
|
|
639
|
+
UDExRatingSource(0).then($source => {
|
|
640
|
+
const UDExOriginalSource = $source[0] as UDExSelectBoxItemElement;
|
|
641
|
+
UDExRatingHost(0).then($host => {
|
|
642
|
+
if (UDExActivation === "pointer") {
|
|
643
|
+
UDExRatingHost(0).shadow().find('[part="item"]').realClick({ scrollBehavior: false });
|
|
644
|
+
} else {
|
|
645
|
+
cy.realPress(UDExActivation);
|
|
646
|
+
}
|
|
647
|
+
UDExRatingSource(0).should($current => {
|
|
648
|
+
expect($current[0]).to.equal(UDExOriginalSource);
|
|
649
|
+
expect($current[0]).to.have.property("selected", UDExSelected);
|
|
650
|
+
});
|
|
651
|
+
UDExRatingHost(0).should($current => expect($current[0]).to.equal($host[0]));
|
|
652
|
+
UDExAssertHostSelection(UDExRatingHost(0), UDExSelected);
|
|
653
|
+
UDExAssertRatingName(0, UDExRatings[0].name);
|
|
654
|
+
UDExAssertRatingCheckbox(0, UDExSelected);
|
|
655
|
+
UDExAssertRatingVisual(0, UDExRatings[0]);
|
|
656
|
+
UDExAssertActiveDescendant(UDExRatingHost(0));
|
|
657
|
+
UDExAssertSelectionStatus(UDExRatings[0].name, UDExSelected ? "Selected" : "Unselected");
|
|
658
|
+
UDExAssertRatingEvent(UDExOriginalSource, UDExSelected, UDExCallCount);
|
|
659
|
+
if (UDExSelected) {
|
|
660
|
+
UDExRatingTags().should("have.length", 1).should($tags => {
|
|
661
|
+
expect($tags).to.have.attr("text", "5 stars");
|
|
662
|
+
expect($tags).to.have.attr("data-id", UDExOriginalSource._id);
|
|
663
|
+
});
|
|
664
|
+
} else {
|
|
665
|
+
cy.get("[udex-select-box]").shadow().find("[udex-tag-group]").should("not.exist");
|
|
666
|
+
}
|
|
667
|
+
UDExPopover().should("have.attr", "open");
|
|
668
|
+
UDExCombobox().should("have.attr", "aria-expanded", "true");
|
|
669
|
+
UDExAssertRatingInput();
|
|
670
|
+
});
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
(["pointer", "keyboard"] as const).forEach(UDExActivation => {
|
|
675
|
+
it(`exposes all five state-free rating names on the first ${UDExActivation} opening and advances immediately with ArrowDown`, () => {
|
|
676
|
+
UDExMountRatings(UDExListMode.MultiSelect, UDExActivation);
|
|
677
|
+
UDExCombobox().should("have.attr", "aria-label", "Rating").and("have.attr", "aria-haspopup", "listbox");
|
|
678
|
+
UDExListHost().should("have.attr", "role", "listbox").and("have.attr", "aria-multiselectable", "true");
|
|
679
|
+
UDExListHost().find("[udex-list-item]").should("have.length", 5);
|
|
680
|
+
UDExRatings.forEach((UDExOption, UDExIndex) => {
|
|
681
|
+
UDExAssertRatingName(UDExIndex, UDExOption.name);
|
|
682
|
+
UDExAssertHostPosition(UDExRatingHost(UDExIndex), UDExIndex + 1, 5);
|
|
683
|
+
UDExAssertHostSelection(UDExRatingHost(UDExIndex), false);
|
|
684
|
+
UDExAssertRatingCheckbox(UDExIndex, false);
|
|
685
|
+
UDExAssertRatingVisual(UDExIndex, UDExOption);
|
|
686
|
+
});
|
|
687
|
+
UDExAssertActiveDescendant(UDExRatingHost(0));
|
|
688
|
+
cy.get("[udex-select-box]").shadow().find('[aria-live], [role="status"], [role="alert"]').should("have.length", 1);
|
|
689
|
+
cy.realPress("ArrowDown");
|
|
690
|
+
UDExAssertNativeActiveDescendant(UDExRatingHost(1), 2, 5);
|
|
691
|
+
UDExAssertRatingName(1, UDExRatings[1].name);
|
|
692
|
+
UDExAssertRatingInput();
|
|
693
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
(["Enter", "Space", "pointer"] as const).forEach(UDExActivation => {
|
|
698
|
+
it(`toggles a rating false to true to false with ${UDExActivation}, retaining one native state owner, source identity and full-name feedback`, () => {
|
|
699
|
+
UDExMountRatings();
|
|
700
|
+
UDExAssertHostSelection(UDExRatingHost(0), false);
|
|
701
|
+
UDExAssertRatingCheckbox(0, false);
|
|
702
|
+
UDExToggleRating(UDExActivation, true, 1);
|
|
703
|
+
UDExToggleRating(UDExActivation, false, 2);
|
|
704
|
+
cy.realPress("ArrowDown");
|
|
705
|
+
UDExAssertNativeActiveDescendant(UDExRatingHost(1), 2, 5);
|
|
706
|
+
UDExAssertRatingInput();
|
|
707
|
+
});
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
it("maps an unlabelled rating tag's delete icon back to the exact source without changing the other selected rating", () => {
|
|
711
|
+
UDExMountRatings();
|
|
712
|
+
UDExToggleRating("Enter", true, 1);
|
|
713
|
+
cy.realPress("ArrowDown");
|
|
714
|
+
UDExAssertNativeActiveDescendant(UDExRatingHost(1), 2, 5);
|
|
715
|
+
UDExRatingSource(0).then($first => {
|
|
716
|
+
UDExRatingSource(1).then($second => {
|
|
717
|
+
cy.realPress("Space");
|
|
718
|
+
cy.get("@selectionChange").should("have.callCount", 2).its("lastCall.args.0.detail")
|
|
719
|
+
.should((UDExDetail: UDExSearchableListBaseSelectionChangeEventDetail) => {
|
|
720
|
+
expect(UDExDetail.item).to.equal($second[0]);
|
|
721
|
+
expect(UDExDetail.selectedItems).to.have.length(2);
|
|
722
|
+
expect(UDExDetail.selectedItems[0]).to.equal($first[0]);
|
|
723
|
+
expect(UDExDetail.selectedItems[1]).to.equal($second[0]);
|
|
724
|
+
});
|
|
725
|
+
});
|
|
726
|
+
});
|
|
727
|
+
UDExAssertHostSelection(UDExRatingHost(1), true);
|
|
728
|
+
UDExAssertRatingCheckbox(1, true);
|
|
729
|
+
UDExAssertSelectionStatus(UDExRatings[1].name, "Selected");
|
|
730
|
+
UDExRatingTags().should("have.length", 2);
|
|
731
|
+
UDExAssertActiveDescendant(UDExRatingHost(1));
|
|
732
|
+
UDExPopover().should("have.attr", "open");
|
|
733
|
+
UDExAssertRatingInput();
|
|
734
|
+
cy.get("#select-box-regression-fixture").then($fixture => {
|
|
735
|
+
$fixture[0].addEventListener("remove", cy.stub().as("ratingTagRemoved"));
|
|
736
|
+
});
|
|
737
|
+
UDExRatingSource(0).then($source => {
|
|
738
|
+
const UDExOriginalSource = $source[0] as UDExSelectBoxItemElement;
|
|
739
|
+
UDExRatingSource(1).then($second => {
|
|
740
|
+
UDExRatingTags().filter('[text="5 stars"]').should("have.attr", "data-id", UDExOriginalSource._id);
|
|
741
|
+
UDExRatingTags().filter('[text="5 stars"]').shadow().find('[ui5-icon][name="decline"]').click();
|
|
742
|
+
cy.get("@ratingTagRemoved").should("have.been.calledOnce").its("lastCall.args.0")
|
|
743
|
+
.should((UDExEvent: CustomEvent<UDExSelectBoxRemoveEventDetail>) => {
|
|
744
|
+
expect(UDExEvent.bubbles).to.equal(true);
|
|
745
|
+
expect(UDExEvent.detail.item).to.equal(UDExOriginalSource);
|
|
746
|
+
expect(UDExEvent.detail.item).to.have.property("selected", false);
|
|
747
|
+
});
|
|
748
|
+
UDExRatingTags().should("have.length", 1).should($tag => {
|
|
749
|
+
expect($tag).to.have.attr("text", "4 stars");
|
|
750
|
+
expect($tag).to.have.attr("data-id", ($second[0] as UDExSelectBoxItemElement)._id);
|
|
751
|
+
});
|
|
752
|
+
UDExRatingSource(0).should($current => expect($current[0]).to.equal(UDExOriginalSource));
|
|
753
|
+
UDExRatingSource(1).should($current => {
|
|
754
|
+
expect($current[0]).to.equal($second[0]);
|
|
755
|
+
expect($current[0]).to.have.property("selected", true);
|
|
756
|
+
});
|
|
757
|
+
});
|
|
758
|
+
});
|
|
759
|
+
UDExAssertHostSelection(UDExRatingHost(0), false);
|
|
760
|
+
UDExAssertHostSelection(UDExRatingHost(1), true);
|
|
761
|
+
UDExAssertRatingCheckbox(0, false);
|
|
762
|
+
UDExAssertRatingCheckbox(1, true);
|
|
763
|
+
UDExAssertRatingName(0, UDExRatings[0].name);
|
|
764
|
+
UDExAssertRatingName(1, UDExRatings[1].name);
|
|
765
|
+
// Removal has its own public event; it does not add a selection-change event.
|
|
766
|
+
cy.get("@selectionChange").should("have.callCount", 2);
|
|
767
|
+
UDExCombobox().should("be.focused");
|
|
768
|
+
cy.get<JQuery<HTMLElement>>("@ratingOriginalInput").then($original => {
|
|
769
|
+
UDExCombobox().should($current => expect($current[0]).to.equal($original[0]));
|
|
770
|
+
});
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
it("preserves the complete source rating name in SingleSelect feedback after selection closes the popup", () => {
|
|
774
|
+
UDExMountRatings(UDExListMode.SingleSelect, "keyboard");
|
|
775
|
+
UDExRatingSource(0).then($source => {
|
|
776
|
+
const UDExOriginalSource = $source[0] as UDExSelectBoxItemElement;
|
|
777
|
+
// No rendered aria-label on the source: close feedback must use the shared base getter.
|
|
778
|
+
expect($source).not.to.have.attr("aria-label");
|
|
779
|
+
expect(UDExOriginalSource.ratingAriaLabel).to.equal(UDExRatings[0].name);
|
|
780
|
+
UDExAssertRatingName(0, UDExRatings[0].name);
|
|
781
|
+
UDExAssertHostSelection(UDExRatingHost(0), false);
|
|
782
|
+
cy.realPress("Enter");
|
|
783
|
+
UDExAssertRatingEvent(UDExOriginalSource, true, 1);
|
|
784
|
+
UDExRatingSource(0).should($current => {
|
|
785
|
+
expect($current[0]).to.equal(UDExOriginalSource);
|
|
786
|
+
expect($current[0]).to.have.property("selected", true);
|
|
787
|
+
});
|
|
788
|
+
});
|
|
789
|
+
UDExPopover().should("not.have.attr", "open");
|
|
790
|
+
UDExCombobox().should("have.attr", "aria-expanded", "false");
|
|
791
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
792
|
+
UDExListHost().find("[_active-descendant]").should("not.exist");
|
|
793
|
+
UDExAssertHostSelection(UDExRatingHost(0), true);
|
|
794
|
+
UDExAssertSelectionStatus(UDExRatings[0].name, "Selected");
|
|
795
|
+
UDExAssertRatingInput();
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
it("retains label, supporting text and additional text with zero rating/counts and a custom maximum", () => {
|
|
799
|
+
const UDExZeroRating: UDExRatingOption = {
|
|
800
|
+
rating: "0", ratingCount: "0", totalCount: "0", ratingMax: 7,
|
|
801
|
+
label: "Reviews", supportingText: "No reviews yet", additionalText: "Code R",
|
|
802
|
+
name: "Reviews No reviews yet Code R Rating 0, Total 0, Rating Indicator 0 of 7",
|
|
803
|
+
};
|
|
804
|
+
UDExMountRatings(UDExListMode.MultiSelect, "keyboard", [UDExZeroRating]);
|
|
805
|
+
UDExAssertRatingName(0, UDExZeroRating.name);
|
|
806
|
+
UDExAssertHostPosition(UDExRatingHost(0), 1, 1);
|
|
807
|
+
UDExAssertHostSelection(UDExRatingHost(0), false);
|
|
808
|
+
UDExAssertRatingCheckbox(0, false);
|
|
809
|
+
UDExAssertRatingVisual(0, UDExZeroRating);
|
|
810
|
+
cy.realPress("Enter");
|
|
811
|
+
UDExAssertRatingName(0, UDExZeroRating.name);
|
|
812
|
+
UDExAssertSelectionStatus(UDExZeroRating.name, "Selected");
|
|
813
|
+
UDExAssertHostSelection(UDExRatingHost(0), true);
|
|
814
|
+
UDExAssertRatingCheckbox(0, true);
|
|
815
|
+
UDExAssertActiveDescendant(UDExRatingHost(0));
|
|
816
|
+
UDExPopover().should("have.attr", "open");
|
|
817
|
+
UDExAssertRatingInput();
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
it("recalculates the active host's complete rating name after reactive source changes without stale state words", () => {
|
|
821
|
+
UDExMountRatings();
|
|
822
|
+
UDExToggleRating("Enter", true, 1);
|
|
823
|
+
// Clear explicit selection feedback through normal navigation before updating data.
|
|
824
|
+
cy.realPress("ArrowDown");
|
|
825
|
+
cy.realPress("ArrowUp");
|
|
826
|
+
UDExAssertNativeActiveDescendant(UDExRatingHost(0), 1, 5);
|
|
827
|
+
const UDExUpdated: UDExRatingOption = {
|
|
828
|
+
rating: "6", ratingCount: "6.0", totalCount: "99", ratingMax: 7,
|
|
829
|
+
name: "Rating 6.0, Total 99, Rating Indicator 6 of 7",
|
|
830
|
+
};
|
|
831
|
+
UDExRatingHost(0).then($host => {
|
|
832
|
+
UDExRatingSource(0).then($source => {
|
|
833
|
+
const UDExSource = $source[0] as UDExSelectBoxItemElement;
|
|
834
|
+
UDExSource.rating = UDExUpdated.rating;
|
|
835
|
+
UDExSource.ratingCount = UDExUpdated.ratingCount;
|
|
836
|
+
UDExSource.totalCount = UDExUpdated.totalCount;
|
|
837
|
+
UDExSource.ratingMax = UDExUpdated.ratingMax!;
|
|
838
|
+
});
|
|
839
|
+
UDExAssertRatingName(0, UDExUpdated.name);
|
|
840
|
+
UDExAssertRatingVisual(0, UDExUpdated);
|
|
841
|
+
UDExRatingHost(0).should($current => expect($current[0]).to.equal($host[0]));
|
|
842
|
+
});
|
|
843
|
+
UDExAssertHostSelection(UDExRatingHost(0), true);
|
|
844
|
+
UDExAssertRatingCheckbox(0, true);
|
|
845
|
+
UDExAssertNativeActiveDescendant(UDExRatingHost(0), 1, 5);
|
|
846
|
+
cy.get("@selectionChange").should("have.been.calledOnce");
|
|
847
|
+
UDExAssertRatingInput();
|
|
848
|
+
});
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
describe("SelectBox Country List first-ever opening announcements", () => {
|
|
852
|
+
const UDExStoryCountryGroups = [
|
|
853
|
+
{
|
|
854
|
+
label: "North America",
|
|
855
|
+
countries: [
|
|
856
|
+
{ label: "United States", supportingText: "USA, United States of America" },
|
|
857
|
+
{ label: "Canada", supportingText: "" },
|
|
858
|
+
{ label: "Mexico", supportingText: "" },
|
|
859
|
+
],
|
|
860
|
+
},
|
|
861
|
+
{
|
|
862
|
+
label: "Asia",
|
|
863
|
+
countries: ["India", "China", "Japan", "Singapore"].map(UDExLabel => ({ label: UDExLabel, supportingText: "" })),
|
|
864
|
+
},
|
|
865
|
+
];
|
|
866
|
+
|
|
867
|
+
(["pointer", "keyboard"] as const).forEach(UDExActivation => {
|
|
868
|
+
it(`keeps live status empty on the very first ${UDExActivation} Country List opening and Escape reopening without duplicating native group information or moving focus`, () => {
|
|
869
|
+
// Match the Country List story's SingleSelect mode and two expanded collapsible groups.
|
|
870
|
+
// Mount closed: a prior warm-up opening would miss the reported first-interaction regression.
|
|
871
|
+
cy.mount(
|
|
872
|
+
<div id="select-box-regression-fixture">
|
|
873
|
+
<UDExSelectBox label="Countries" mode={UDExListMode.SingleSelect}>
|
|
874
|
+
{UDExStoryCountryGroups.map(UDExGroup => (
|
|
875
|
+
<UDExSelectBoxGroupHeader label={UDExGroup.label} collapsible>
|
|
876
|
+
{UDExGroup.countries.map(UDExCountry => (
|
|
877
|
+
<UDExSelectBoxItem label={UDExCountry.label} supportingText={UDExCountry.supportingText}></UDExSelectBoxItem>
|
|
878
|
+
))}
|
|
879
|
+
</UDExSelectBoxGroupHeader>
|
|
880
|
+
))}
|
|
881
|
+
</UDExSelectBox>
|
|
882
|
+
</div>
|
|
883
|
+
);
|
|
884
|
+
cy.get("#select-box-regression-fixture").then($fixture => {
|
|
885
|
+
$fixture[0].addEventListener("selection-change", cy.stub().as("selectionChange"));
|
|
886
|
+
});
|
|
887
|
+
UDExPopover().then($popover => {
|
|
888
|
+
$popover[0].addEventListener("ui5-open", cy.stub().as("countryListOpened"));
|
|
889
|
+
});
|
|
890
|
+
cy.get("[udex-select-box]").then($host => {
|
|
891
|
+
$host[0].addEventListener("close", cy.stub().as("countryListClosed"));
|
|
892
|
+
});
|
|
893
|
+
UDExCombobox().then($input => {
|
|
894
|
+
const UDExOriginalInput = $input[0];
|
|
895
|
+
UDExOriginalInput.addEventListener("blur", cy.stub().as("countryListBlur"));
|
|
896
|
+
const UDExAssertUnchangedInput = (UDExExpectedDescription = "") => {
|
|
897
|
+
UDExCombobox().should("be.focused").should($current => {
|
|
898
|
+
expect($current[0], "the original native input survives opening, navigation and close").to.equal(UDExOriginalInput);
|
|
899
|
+
["aria-description", "aria-roledescription", "aria-keyshortcuts"].forEach(UDExAttribute => {
|
|
900
|
+
expect($current, "no custom navigation instructions or duplicate state on the input").not.to.have.attr(UDExAttribute);
|
|
901
|
+
});
|
|
902
|
+
const UDExRoot = $current[0].getRootNode() as ShadowRoot;
|
|
903
|
+
expect($current[0].ownerDocument.activeElement).to.equal(UDExRoot.host);
|
|
904
|
+
expect(UDExRoot.activeElement).to.equal(UDExOriginalInput);
|
|
905
|
+
});
|
|
906
|
+
UDExAssertDescription(UDExExpectedDescription);
|
|
907
|
+
cy.get("@countryListBlur").should("not.have.been.called");
|
|
908
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
909
|
+
cy.get("[udex-select-box]").find("[udex-select-box-item][selected]").should("not.exist");
|
|
910
|
+
};
|
|
911
|
+
|
|
912
|
+
[1, 2].forEach(UDExCycle => {
|
|
913
|
+
UDExPopover().should("not.have.attr", "open");
|
|
914
|
+
UDExCombobox().should("have.attr", "aria-expanded", "false");
|
|
915
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
916
|
+
UDExListHost().find("[_active-descendant], ._udex-active-descendant").should("not.exist");
|
|
917
|
+
UDExAssertSupplementalStatus("");
|
|
918
|
+
|
|
919
|
+
if (UDExActivation === "pointer") {
|
|
920
|
+
UDExCombobox().realClick();
|
|
921
|
+
} else {
|
|
922
|
+
UDExCombobox().focus().realPress("ArrowDown");
|
|
923
|
+
}
|
|
924
|
+
cy.get("@countryListOpened").should("have.callCount", UDExCycle);
|
|
925
|
+
UDExAssertOpenWithActiveDescendant(UDExHeaderHost().first(), "");
|
|
926
|
+
UDExCombobox().should("have.attr", "aria-label", "Countries");
|
|
927
|
+
UDExCombobox().should("have.attr", "aria-haspopup", "tree");
|
|
928
|
+
UDExListHost().should("have.attr", "role", "tree");
|
|
929
|
+
UDExListHost().should("have.attr", "aria-label", "Countries");
|
|
930
|
+
UDExListHost().then($list => {
|
|
931
|
+
UDExCombobox().should("have.attr", "aria-controls", $list[0].id);
|
|
932
|
+
});
|
|
933
|
+
cy.get("[udex-select-box]").shadow().find('[aria-live], [role="status"], [role="alert"]')
|
|
934
|
+
.should("have.length", 1);
|
|
935
|
+
UDExHeaderHost().should("have.length", 2).each(($header, UDExIndex) => {
|
|
936
|
+
const UDExGroup = UDExStoryCountryGroups[UDExIndex];
|
|
937
|
+
UDExAssertHostPosition(cy.wrap($header), UDExIndex + 1, 2, 1);
|
|
938
|
+
UDExAssertHostSelection(cy.wrap($header), false);
|
|
939
|
+
cy.wrap($header).should($host => {
|
|
940
|
+
expect($host).to.have.attr("aria-label", UDExGroup.label);
|
|
941
|
+
expect($host).to.have.attr("aria-expanded", "true");
|
|
942
|
+
["aria-description", "aria-describedby", "aria-roledescription", "aria-keyshortcuts"].forEach(UDExAttribute => {
|
|
943
|
+
expect($host, "native name, hierarchy and expansion need no custom instructions").not.to.have.attr(UDExAttribute);
|
|
944
|
+
});
|
|
945
|
+
});
|
|
946
|
+
cy.wrap($header).shadow().find('[part="group-item"]').should($row => {
|
|
947
|
+
expect($row).to.have.attr("role", "presentation");
|
|
948
|
+
[...UDExInnerStateAttributes, "aria-label", "aria-labelledby"].forEach(UDExAttribute => {
|
|
949
|
+
expect($row, "the inner group row does not duplicate host semantics").not.to.have.attr(UDExAttribute);
|
|
950
|
+
});
|
|
951
|
+
});
|
|
952
|
+
cy.wrap($header).shadow().find('[role="treeitem"], [role="option"], [aria-live]').should("not.exist");
|
|
953
|
+
cy.wrap($header).find("[udex-list-item]").should("have.length", UDExGroup.countries.length).each(($country, UDExCountryIndex) => {
|
|
954
|
+
const UDExCountry = UDExGroup.countries[UDExCountryIndex];
|
|
955
|
+
UDExAssertHostPosition(cy.wrap($country), UDExCountryIndex + 1, UDExGroup.countries.length, 2);
|
|
956
|
+
UDExAssertHostSelection(cy.wrap($country), false);
|
|
957
|
+
UDExAssertLeafName(cy.wrap($country), UDExCountry.label, UDExCountry.supportingText);
|
|
958
|
+
cy.wrap($country).should("not.have.attr", "aria-expanded");
|
|
959
|
+
UDExAssertPresentationItem(cy.wrap($country));
|
|
960
|
+
});
|
|
961
|
+
});
|
|
962
|
+
UDExAssertUnchangedInput();
|
|
963
|
+
|
|
964
|
+
UDExHeaderHost().first().then($group => {
|
|
965
|
+
UDExLeafHost("United States").then($country => {
|
|
966
|
+
// The opening already activates North America: the next key must advance immediately.
|
|
967
|
+
cy.realPress("ArrowDown");
|
|
968
|
+
UDExAssertNativeActiveDescendant(UDExLeafHost("United States"), 1, 3, 2);
|
|
969
|
+
UDExLeafHost("United States").should($current => expect($current[0]).to.equal($country[0]));
|
|
970
|
+
UDExAssertLeafName(UDExLeafHost("United States"), "United States", "USA, United States of America");
|
|
971
|
+
UDExAssertUnchangedInput();
|
|
972
|
+
|
|
973
|
+
cy.realPress("ArrowUp");
|
|
974
|
+
UDExAssertNativeActiveDescendant(UDExHeaderHost().first(), 1, 2, 1);
|
|
975
|
+
UDExHeaderHost().first().should($current => {
|
|
976
|
+
expect($current[0], "backward navigation returns to the original group host and ID owner").to.equal($group[0]);
|
|
977
|
+
expect($current).to.have.attr("aria-label", "North America");
|
|
978
|
+
expect($current).to.have.attr("aria-expanded", "true");
|
|
979
|
+
});
|
|
980
|
+
UDExAssertUnchangedInput();
|
|
981
|
+
});
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
cy.realPress("Escape");
|
|
985
|
+
cy.get("@countryListClosed").should("have.callCount", UDExCycle);
|
|
986
|
+
UDExPopover().should("not.have.attr", "open");
|
|
987
|
+
UDExCombobox().should("have.attr", "aria-expanded", "false");
|
|
988
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
989
|
+
UDExListHost().find("[_active-descendant], ._udex-active-descendant").should("not.exist");
|
|
990
|
+
UDExAssertSupplementalStatus("");
|
|
991
|
+
// SingleSelect retains its navigated value description once the active item is cleared.
|
|
992
|
+
UDExAssertUnchangedInput("North America");
|
|
993
|
+
});
|
|
994
|
+
});
|
|
995
|
+
});
|
|
996
|
+
});
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
describe("SelectBox close before pending opening activation", () => {
|
|
1000
|
+
let UDExObserver: MutationObserver | undefined;
|
|
1001
|
+
|
|
1002
|
+
afterEach(() => UDExObserver?.disconnect());
|
|
1003
|
+
|
|
1004
|
+
it("does not activate or announce expanded after a synchronous close in the first open event", () => {
|
|
1005
|
+
UDExMountClosedSelectBox(UDExListMode.MultiSelect, UDExLayouts[0]);
|
|
1006
|
+
UDExCombobox().then($input => {
|
|
1007
|
+
const UDExOriginalInput = $input[0];
|
|
1008
|
+
const UDExRecords: MutationRecord[] = [];
|
|
1009
|
+
UDExObserver = new MutationObserver(UDExChanges => UDExRecords.push(...UDExChanges));
|
|
1010
|
+
UDExObserver.observe(UDExOriginalInput, {
|
|
1011
|
+
attributes: true, attributeFilter: ["aria-activedescendant"], attributeOldValue: true,
|
|
1012
|
+
});
|
|
1013
|
+
UDExOriginalInput.addEventListener("blur", cy.stub().as("pendingOpeningBlur"));
|
|
1014
|
+
cy.get("[udex-select-box]").then($host => {
|
|
1015
|
+
const UDExSelectBox = $host[0] as UDExSelectBoxElement;
|
|
1016
|
+
UDExSelectBox.addEventListener("close", cy.stub().as("pendingOpeningClosed"));
|
|
1017
|
+
// The public open event fires synchronously after scheduling activation, before renderFinished settles.
|
|
1018
|
+
// Close here rather than racing Escape against rendering or introducing a time-based wait.
|
|
1019
|
+
UDExSelectBox.addEventListener("open", cy.stub().callsFake(() => {
|
|
1020
|
+
expect(UDExOriginalInput.hasAttribute("aria-activedescendant"), "opening activation is still pending").to.equal(false);
|
|
1021
|
+
UDExSelectBox.close();
|
|
1022
|
+
}).as("pendingOpeningOpened"), { once: true });
|
|
1023
|
+
});
|
|
1024
|
+
UDExCombobox().focus().realPress("ArrowDown");
|
|
1025
|
+
cy.get("@pendingOpeningOpened").should("have.been.calledOnce");
|
|
1026
|
+
cy.get("@pendingOpeningClosed").should("have.been.calledOnce");
|
|
1027
|
+
// Drain the same rendering stages as handleDropdownOpen, without arbitrary delays.
|
|
1028
|
+
cy.then(() => renderFinished()).then(() => renderFinished());
|
|
1029
|
+
UDExPopover().should("not.have.attr", "open");
|
|
1030
|
+
UDExCombobox().should("be.focused").and("have.attr", "aria-expanded", "false");
|
|
1031
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
1032
|
+
UDExListHost().find("[_active-descendant], ._udex-active-descendant").should("not.exist");
|
|
1033
|
+
UDExAssertSupplementalStatus("");
|
|
1034
|
+
UDExCombobox().should($current => expect($current[0]).to.equal(UDExOriginalInput));
|
|
1035
|
+
cy.get("@pendingOpeningBlur").should("not.have.been.called");
|
|
1036
|
+
cy.then(() => {
|
|
1037
|
+
UDExRecords.push(...UDExObserver!.takeRecords());
|
|
1038
|
+
UDExObserver!.disconnect();
|
|
1039
|
+
// Catch an activation that is subsequently cleared, not just a stale ID left after close.
|
|
1040
|
+
UDExRecords.forEach(UDExRecord => expect(UDExRecord.oldValue, "no transient active ID after close").to.equal(null));
|
|
1041
|
+
expect(UDExOriginalInput.hasAttribute("aria-activedescendant")).to.equal(false);
|
|
1042
|
+
});
|
|
1043
|
+
});
|
|
1044
|
+
});
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
describe("SelectBox host-owned accessibility regressions", () => {
|
|
1048
|
+
UDExModes.forEach(UDExMode => {
|
|
1049
|
+
UDExLayouts.forEach(UDExLayout => {
|
|
1050
|
+
describe(`${UDExMode} / ${UDExLayout.name}`, () => {
|
|
1051
|
+
const UDExOwnerRole = UDExLayout.grouped ? "tree" : UDExMode === UDExListMode.SingleContent ? "list" : "listbox";
|
|
1052
|
+
const UDExItemRole = UDExLayout.grouped ? "treeitem" : UDExMode === UDExListMode.SingleContent ? "listitem" : "option";
|
|
1053
|
+
|
|
1054
|
+
beforeEach(() => {
|
|
1055
|
+
UDExMountSelectBox(UDExMode, UDExLayout);
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
// Updated: reopening omits live expansion for expanded headers; other initial hosts keep the fallback.
|
|
1059
|
+
it("activates the first navigable item on each ArrowDown reopening, then advances without moving input focus", () => {
|
|
1060
|
+
UDExPopover().then($popover => {
|
|
1061
|
+
$popover[0].addEventListener("ui5-open", cy.stub().as("keyboardPopoverOpened"));
|
|
1062
|
+
});
|
|
1063
|
+
cy.get("[udex-select-box]").then($selectBox => {
|
|
1064
|
+
$selectBox[0].addEventListener("close", cy.stub().as("keyboardDropdownClosed"));
|
|
1065
|
+
});
|
|
1066
|
+
const UDExStartsOnHeader = UDExLayout.grouped && (UDExLayout.collapsible || UDExMode === UDExListMode.MultiSelect);
|
|
1067
|
+
|
|
1068
|
+
[1, 2].forEach(UDExCycle => {
|
|
1069
|
+
// The click-opened popup is closed first, so ArrowDown really exercises keyboard opening.
|
|
1070
|
+
cy.realPress("Escape");
|
|
1071
|
+
UDExPopover().should("not.have.attr", "open");
|
|
1072
|
+
UDExCombobox().should("have.attr", "aria-expanded", "false");
|
|
1073
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
1074
|
+
UDExListHost().find("[_active-descendant]").should("not.exist");
|
|
1075
|
+
UDExAssertSupplementalStatus("");
|
|
1076
|
+
UDExCombobox().should("be.focused");
|
|
1077
|
+
cy.get("@keyboardDropdownClosed").should("have.callCount", UDExCycle);
|
|
1078
|
+
|
|
1079
|
+
cy.realPress("ArrowDown");
|
|
1080
|
+
cy.get("@keyboardPopoverOpened").should("have.callCount", UDExCycle);
|
|
1081
|
+
UDExAssertOpenWithActiveDescendant(UDExStartsOnHeader ? UDExHeaderHost() : UDExLeafHost("Alpha"),
|
|
1082
|
+
UDExLayout.collapsible ? "" : "expanded");
|
|
1083
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
1084
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
1085
|
+
|
|
1086
|
+
UDExListHost().find("[_active-descendant]").should("have.length", 1).should($host => {
|
|
1087
|
+
expect($host).to.have.attr("role", UDExItemRole);
|
|
1088
|
+
expect($host).to.have.attr("aria-posinset", "1");
|
|
1089
|
+
expect($host).to.have.attr("aria-setsize", UDExStartsOnHeader ? "1" : "3");
|
|
1090
|
+
if (UDExLayout.grouped) {
|
|
1091
|
+
expect($host).to.have.attr("aria-level", UDExStartsOnHeader ? "1" : "2");
|
|
1092
|
+
} else {
|
|
1093
|
+
expect($host).not.to.have.attr("aria-level");
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
cy.realPress("ArrowDown");
|
|
1098
|
+
UDExAssertSupplementalActiveDescendant(UDExLeafHost(UDExStartsOnHeader ? "Alpha" : "Beta"), "");
|
|
1099
|
+
UDExLeafHost(UDExStartsOnHeader ? "Alpha" : "Beta").should($host => {
|
|
1100
|
+
expect($host).to.have.attr("aria-posinset", UDExStartsOnHeader ? "1" : "2");
|
|
1101
|
+
expect($host).to.have.attr("aria-setsize", "3");
|
|
1102
|
+
if (UDExMode === UDExListMode.SingleContent) {
|
|
1103
|
+
expect($host).not.to.have.attr("aria-selected");
|
|
1104
|
+
} else {
|
|
1105
|
+
expect($host).to.have.attr("aria-selected", "false");
|
|
1106
|
+
}
|
|
1107
|
+
if (UDExLayout.grouped) {
|
|
1108
|
+
expect($host).to.have.attr("aria-level", "2");
|
|
1109
|
+
}
|
|
1110
|
+
});
|
|
1111
|
+
|
|
1112
|
+
// Navigation clears live feedback; disabled leaves still contribute to native set size.
|
|
1113
|
+
cy.realPress("End");
|
|
1114
|
+
UDExAssertSupplementalActiveDescendant(UDExLeafHost("Beta"), "");
|
|
1115
|
+
UDExLeafHost("Beta").should($host => {
|
|
1116
|
+
expect($host).to.have.attr("aria-posinset", "2");
|
|
1117
|
+
expect($host).to.have.attr("aria-setsize", "3");
|
|
1118
|
+
if (UDExMode === UDExListMode.SingleContent) {
|
|
1119
|
+
expect($host).not.to.have.attr("aria-selected");
|
|
1120
|
+
} else {
|
|
1121
|
+
expect($host).to.have.attr("aria-selected", "false");
|
|
1122
|
+
}
|
|
1123
|
+
});
|
|
1124
|
+
UDExAssertNativeHostStates();
|
|
1125
|
+
UDExLeafHost("Unavailable").should("have.attr", "aria-disabled", "true");
|
|
1126
|
+
cy.get("[udex-select-box]").find("[udex-select-box-item][selected]").should("not.exist");
|
|
1127
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
1128
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
1129
|
+
});
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
// Updated: native hosts own names and Boolean selection, never checked state or duplicate inner widgets.
|
|
1133
|
+
it("keeps the owning role, labels and states on hosts without duplicate inner widgets or tab stops", () => {
|
|
1134
|
+
UDExCombobox().should("have.attr", "aria-label", "Languages");
|
|
1135
|
+
UDExListHost().should($host => {
|
|
1136
|
+
expect($host).to.have.attr("role", UDExOwnerRole);
|
|
1137
|
+
expect($host).to.have.attr("accessible-role", "");
|
|
1138
|
+
expect($host).to.have.attr("aria-label", "Languages");
|
|
1139
|
+
expect($host[0]).to.have.property("accessibleName", "Languages");
|
|
1140
|
+
if (UDExMode === UDExListMode.MultiSelect) {
|
|
1141
|
+
expect($host).to.have.attr("aria-multiselectable", "true");
|
|
1142
|
+
} else {
|
|
1143
|
+
expect($host).not.to.have.attr("aria-multiselectable");
|
|
1144
|
+
}
|
|
1145
|
+
});
|
|
1146
|
+
UDExListHost().then($host => {
|
|
1147
|
+
UDExCombobox().should("have.attr", "aria-controls", $host[0].id);
|
|
1148
|
+
});
|
|
1149
|
+
UDExListHost().shadow().find("#udex-list").should($inner => {
|
|
1150
|
+
expect($inner).to.have.attr("role", "none");
|
|
1151
|
+
["aria-label", "aria-labelledby", "aria-multiselectable", "aria-busy"].forEach(UDExAttribute => {
|
|
1152
|
+
expect($inner, `inner container omits ${UDExAttribute}`).not.to.have.attr(UDExAttribute);
|
|
1153
|
+
});
|
|
1154
|
+
});
|
|
1155
|
+
UDExListHost().shadow().find('[role="tree"], [role="listbox"], [role="list"], .udex-list__hidden-text')
|
|
1156
|
+
.should("not.exist");
|
|
1157
|
+
|
|
1158
|
+
UDExListHost().find("[udex-list-item]").should("have.length", 3).each($item => {
|
|
1159
|
+
cy.wrap($item).should($host => {
|
|
1160
|
+
expect($host).to.have.attr("role", UDExItemRole);
|
|
1161
|
+
expect($host).to.have.attr("_use-host-aria-role");
|
|
1162
|
+
expect($host).not.to.have.attr("aria-expanded");
|
|
1163
|
+
if (UDExMode === UDExListMode.SingleContent) {
|
|
1164
|
+
// Informational listitems (and grouped SingleContent treeitems) are not selectable.
|
|
1165
|
+
expect($host).not.to.have.attr("aria-selected");
|
|
1166
|
+
expect($host).not.to.have.attr("aria-checked");
|
|
1167
|
+
} else {
|
|
1168
|
+
expect($host).to.have.attr("aria-selected", "false");
|
|
1169
|
+
expect($host).not.to.have.attr("aria-checked");
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
UDExAssertLeafName(cy.wrap($item), $item.attr("label")!);
|
|
1173
|
+
UDExAssertPresentationItem(cy.wrap($item));
|
|
1174
|
+
});
|
|
1175
|
+
UDExLeafHost("Unavailable").should("have.attr", "aria-disabled", "true");
|
|
1176
|
+
if (UDExMode === UDExListMode.MultiSelect) {
|
|
1177
|
+
// Decorative controls keep their deliberate -1; only the row must omit tabindex.
|
|
1178
|
+
UDExLeafHost("Alpha").shadow().find("[udex-checkbox]").should($checkbox => {
|
|
1179
|
+
expect($checkbox).to.have.attr("tabindex", "-1");
|
|
1180
|
+
expect($checkbox).to.have.attr("aria-hidden", "true");
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
if (UDExLayout.grouped) {
|
|
1185
|
+
UDExHeaderHost().should($host => {
|
|
1186
|
+
expect($host).to.have.attr("role", "treeitem");
|
|
1187
|
+
expect($host).to.have.attr("aria-label", "Languages group");
|
|
1188
|
+
if (UDExLayout.collapsible) {
|
|
1189
|
+
expect($host).to.have.attr("aria-expanded", "true");
|
|
1190
|
+
} else {
|
|
1191
|
+
expect($host).not.to.have.attr("aria-expanded");
|
|
1192
|
+
}
|
|
1193
|
+
expect($host).not.to.have.attr("aria-description");
|
|
1194
|
+
if (UDExMode !== UDExListMode.SingleContent) {
|
|
1195
|
+
expect($host).to.have.attr("aria-selected", "false");
|
|
1196
|
+
expect($host).not.to.have.attr("aria-checked");
|
|
1197
|
+
} else {
|
|
1198
|
+
expect($host).not.to.have.attr("aria-selected");
|
|
1199
|
+
expect($host).not.to.have.attr("aria-checked");
|
|
1200
|
+
}
|
|
1201
|
+
});
|
|
1202
|
+
UDExHeaderHost().shadow().find('[part="item"]').should($wrapper => {
|
|
1203
|
+
expect($wrapper).to.have.attr("role", "presentation");
|
|
1204
|
+
expect($wrapper).not.to.have.attr("aria-labelledby");
|
|
1205
|
+
});
|
|
1206
|
+
UDExHeaderHost().shadow().find('[role="tree"], [role="listbox"], [role="list"], [role="treeitem"]')
|
|
1207
|
+
.should("not.exist");
|
|
1208
|
+
|
|
1209
|
+
if (UDExMode === UDExListMode.MultiSelect) {
|
|
1210
|
+
UDExNestedHeader().should($host => {
|
|
1211
|
+
expect($host).to.have.attr("role", "none");
|
|
1212
|
+
expect($host).to.have.attr("_use-host-aria-role");
|
|
1213
|
+
UDExInnerStateAttributes.forEach(UDExAttribute => {
|
|
1214
|
+
expect($host).not.to.have.attr(UDExAttribute);
|
|
1215
|
+
});
|
|
1216
|
+
});
|
|
1217
|
+
UDExAssertPresentationItem(UDExNestedHeader());
|
|
1218
|
+
} else if (UDExLayout.collapsible) {
|
|
1219
|
+
UDExHeaderHost().shadow().find('[part="group-item"]').should($row => {
|
|
1220
|
+
expect($row).to.have.attr("role", "presentation");
|
|
1221
|
+
[...UDExInnerStateAttributes, "aria-controls", "aria-labelledby"].forEach(UDExAttribute => {
|
|
1222
|
+
expect($row).not.to.have.attr(UDExAttribute);
|
|
1223
|
+
});
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
});
|
|
1228
|
+
|
|
1229
|
+
if (UDExMode !== UDExListMode.SingleContent) {
|
|
1230
|
+
// Updated: pointer selection keeps Boolean native selection and complete-name live feedback.
|
|
1231
|
+
it("selects a leaf with a native mouse click without blurring the combobox", () => {
|
|
1232
|
+
UDExLeafHost("Beta").shadow().find('[part="item"]').should("be.visible").should($row => {
|
|
1233
|
+
const UDExRow = $row[0];
|
|
1234
|
+
const UDExRect = UDExRow.getBoundingClientRect();
|
|
1235
|
+
const UDExRoot = UDExRow.getRootNode() as ShadowRoot;
|
|
1236
|
+
const UDExHit = UDExRoot.elementFromPoint(
|
|
1237
|
+
UDExRect.left + UDExRect.width / 2, UDExRect.top + UDExRect.height / 2
|
|
1238
|
+
);
|
|
1239
|
+
expect(UDExHit === UDExRow || UDExRow.contains(UDExHit), "native click reaches the leaf row").to.equal(true);
|
|
1240
|
+
}).realClick({ scrollBehavior: false });
|
|
1241
|
+
|
|
1242
|
+
UDExSourceLeaf("Beta").should("have.attr", "selected");
|
|
1243
|
+
UDExAssertHostSelection(UDExLeafHost("Beta"), true);
|
|
1244
|
+
UDExAssertSelectionEvent("Beta", ["Beta"]);
|
|
1245
|
+
UDExCombobox().should("be.focused");
|
|
1246
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
1247
|
+
UDExAssertPresentationItem(UDExLeafHost("Beta"));
|
|
1248
|
+
UDExAssertSelectionStatus("Beta", "Selected");
|
|
1249
|
+
if (UDExMode === UDExListMode.MultiSelect) {
|
|
1250
|
+
UDExAssertActiveDescendant(UDExLeafHost("Beta"));
|
|
1251
|
+
UDExLeafHost("Beta").shadow().find('[part="item"]').realClick({ scrollBehavior: false });
|
|
1252
|
+
UDExSourceLeaf("Beta").should("not.have.attr", "selected");
|
|
1253
|
+
UDExAssertHostSelection(UDExLeafHost("Beta"), false);
|
|
1254
|
+
UDExAssertSelectionEvent("Beta", [], 2);
|
|
1255
|
+
UDExAssertActiveDescendant(UDExLeafHost("Beta"));
|
|
1256
|
+
UDExAssertSelectionStatus("Beta", "Unselected");
|
|
1257
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
1258
|
+
} else {
|
|
1259
|
+
UDExPopover().should("not.have.attr", "open");
|
|
1260
|
+
UDExCombobox().should("have.attr", "aria-expanded", "false");
|
|
1261
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
1262
|
+
UDExAssertSelectionStatus("Beta", "Selected");
|
|
1263
|
+
}
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
(["Enter", "Space"] as const).forEach(UDExKey => {
|
|
1267
|
+
// Updated: key activation uses native aria-selected and retains feedback after SingleSelect closes.
|
|
1268
|
+
it(`selects a host-owned leaf with ${UDExKey} while DOM focus stays on the combobox`, () => {
|
|
1269
|
+
// End reaches the last enabled leaf, not the disabled final item.
|
|
1270
|
+
cy.realPress("End");
|
|
1271
|
+
UDExAssertActiveDescendant(UDExLeafHost("Beta"));
|
|
1272
|
+
cy.realPress(UDExKey);
|
|
1273
|
+
|
|
1274
|
+
UDExSourceLeaf("Beta").should("have.attr", "selected");
|
|
1275
|
+
UDExAssertHostSelection(UDExLeafHost("Beta"), true);
|
|
1276
|
+
UDExAssertSelectionEvent("Beta", ["Beta"]);
|
|
1277
|
+
UDExAssertSelectionStatus("Beta", "Selected");
|
|
1278
|
+
if (UDExMode === UDExListMode.MultiSelect) {
|
|
1279
|
+
UDExAssertActiveDescendant(UDExLeafHost("Beta"));
|
|
1280
|
+
cy.realPress(UDExKey);
|
|
1281
|
+
UDExSourceLeaf("Beta").should("not.have.attr", "selected");
|
|
1282
|
+
UDExAssertHostSelection(UDExLeafHost("Beta"), false);
|
|
1283
|
+
UDExAssertSelectionEvent("Beta", [], 2);
|
|
1284
|
+
UDExAssertSelectionStatus("Beta", "Unselected");
|
|
1285
|
+
} else {
|
|
1286
|
+
UDExPopover().should("not.have.attr", "open");
|
|
1287
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
1288
|
+
UDExAssertSelectionStatus("Beta", "Selected");
|
|
1289
|
+
}
|
|
1290
|
+
UDExCombobox().should("be.focused");
|
|
1291
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
1292
|
+
});
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
if (UDExLayout.grouped && UDExMode === UDExListMode.MultiSelect) {
|
|
1297
|
+
// Updated: partial group selection is false with Indeterminate description, removed on deselection.
|
|
1298
|
+
it("selects and deselects enabled children by native clicks on the presentational group row", () => {
|
|
1299
|
+
UDExNestedHeader().shadow().find('[part="item"]').realClick();
|
|
1300
|
+
|
|
1301
|
+
UDExSourceLeaf("Alpha").should("have.attr", "selected");
|
|
1302
|
+
UDExSourceLeaf("Beta").should("have.attr", "selected");
|
|
1303
|
+
UDExSourceLeaf("Unavailable").should("not.have.attr", "selected");
|
|
1304
|
+
// The disabled, unselected child keeps the group partially selected, not aria-selected=mixed.
|
|
1305
|
+
UDExAssertHostSelection(UDExHeaderHost(), false, true);
|
|
1306
|
+
UDExAssertSelectionEvent("Languages group", ["Alpha", "Beta"]);
|
|
1307
|
+
UDExAssertActiveDescendant(UDExHeaderHost());
|
|
1308
|
+
UDExAssertSelectionStatus("Languages group", "Indeterminate");
|
|
1309
|
+
UDExAssertPresentationItem(UDExNestedHeader());
|
|
1310
|
+
|
|
1311
|
+
UDExNestedHeader().shadow().find('[part="item"]').realClick();
|
|
1312
|
+
|
|
1313
|
+
UDExSourceLeaf("Alpha").should("not.have.attr", "selected");
|
|
1314
|
+
UDExSourceLeaf("Beta").should("not.have.attr", "selected");
|
|
1315
|
+
UDExAssertHostSelection(UDExHeaderHost(), false);
|
|
1316
|
+
UDExAssertSelectionEvent("Languages group", [], 2);
|
|
1317
|
+
UDExAssertActiveDescendant(UDExHeaderHost());
|
|
1318
|
+
UDExAssertSelectionStatus("Languages group", "Unselected");
|
|
1319
|
+
UDExCombobox().should("be.focused");
|
|
1320
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1323
|
+
// Updated: keyboard partial selection describes Indeterminate and removes it when unselected.
|
|
1324
|
+
it("toggles group selection exactly once per Enter or Space press from the combobox", () => {
|
|
1325
|
+
UDExAssertActiveDescendant(UDExHeaderHost());
|
|
1326
|
+
cy.realPress("Enter");
|
|
1327
|
+
UDExAssertHostSelection(UDExHeaderHost(), false, true);
|
|
1328
|
+
UDExAssertSelectionEvent("Languages group", ["Alpha", "Beta"]);
|
|
1329
|
+
UDExAssertActiveDescendant(UDExHeaderHost());
|
|
1330
|
+
UDExAssertSelectionStatus("Languages group", "Indeterminate");
|
|
1331
|
+
|
|
1332
|
+
cy.realPress("Space");
|
|
1333
|
+
UDExAssertHostSelection(UDExHeaderHost(), false);
|
|
1334
|
+
UDExAssertSelectionEvent("Languages group", [], 2);
|
|
1335
|
+
UDExAssertActiveDescendant(UDExHeaderHost());
|
|
1336
|
+
UDExAssertSelectionStatus("Languages group", "Unselected");
|
|
1337
|
+
UDExAssertPresentationItem(UDExNestedHeader());
|
|
1338
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
if (UDExLayout.collapsible) {
|
|
1343
|
+
// Updated: accordion state is native only; no hierarchy or expansion summary is repeated live.
|
|
1344
|
+
it("toggles the chevron with a native click and still collapses and expands with arrow keys", () => {
|
|
1345
|
+
const UDExHeader = () => UDExMode === UDExListMode.MultiSelect ? UDExNestedHeader() : UDExHeaderHost();
|
|
1346
|
+
// Updated: expanded-header opening and subsequent accordion navigation both keep live status empty.
|
|
1347
|
+
cy.realPress("Home");
|
|
1348
|
+
UDExAssertNativeActiveDescendant(UDExHeaderHost(), 1, 1, 1);
|
|
1349
|
+
UDExHeader().shadow().find("[udex-button].udex-list__group-heading__icon").realClick();
|
|
1350
|
+
|
|
1351
|
+
UDExHeaderHost().should("have.attr", "aria-expanded", "false");
|
|
1352
|
+
cy.get("[udex-select-box]").find("[udex-select-box-group-header]").should("have.attr", "collapsed");
|
|
1353
|
+
UDExHeaderHost().find("[udex-list-item]").each($item => {
|
|
1354
|
+
cy.wrap($item).should("have.attr", "collapsed");
|
|
1355
|
+
});
|
|
1356
|
+
UDExAssertNativeActiveDescendant(UDExHeaderHost(), 1, 1, 1);
|
|
1357
|
+
|
|
1358
|
+
cy.realPress("ArrowRight");
|
|
1359
|
+
UDExHeaderHost().should("have.attr", "aria-expanded", "true");
|
|
1360
|
+
cy.get("[udex-select-box]").find("[udex-select-box-group-header]").should("not.have.attr", "collapsed");
|
|
1361
|
+
UDExHeaderHost().find("[udex-list-item]").each($item => {
|
|
1362
|
+
cy.wrap($item).should("not.have.attr", "collapsed");
|
|
1363
|
+
});
|
|
1364
|
+
UDExAssertNativeActiveDescendant(UDExHeaderHost(), 1, 1, 1);
|
|
1365
|
+
cy.realPress("ArrowLeft");
|
|
1366
|
+
UDExHeaderHost().should("have.attr", "aria-expanded", "false");
|
|
1367
|
+
cy.get("[udex-select-box]").find("[udex-select-box-group-header]").should("have.attr", "collapsed");
|
|
1368
|
+
UDExAssertNativeActiveDescendant(UDExHeaderHost(), 1, 1, 1);
|
|
1369
|
+
if (UDExMode !== UDExListMode.SingleContent) {
|
|
1370
|
+
UDExAssertHostSelection(UDExHeaderHost(), false);
|
|
1371
|
+
}
|
|
1372
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
1373
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
});
|
|
1377
|
+
});
|
|
1378
|
+
});
|
|
1379
|
+
});
|
|
1380
|
+
|
|
1381
|
+
describe("SelectBox MultiSelect selected-tag IDREF regressions", () => {
|
|
1382
|
+
const UDExTagGroup = () => cy.get("[udex-select-box]").shadow().find("[udex-tag-group]");
|
|
1383
|
+
const UDExRenderedTags = () => UDExTagGroup().shadow().find("[udex-tag]");
|
|
1384
|
+
|
|
1385
|
+
function UDExAssertTags(UDExLabels: string[]) {
|
|
1386
|
+
cy.get("[udex-select-box]").shadow().should($root => {
|
|
1387
|
+
const UDExIds = Array.from($root[0].querySelectorAll("[id]"), UDExElement => UDExElement.id);
|
|
1388
|
+
expect(new Set(UDExIds).size, "no duplicate IDs in the combobox shadow root").to.equal(UDExIds.length);
|
|
1389
|
+
const UDExSourceTags = Array.from($root[0].querySelectorAll("[udex-tag-group] > [udex-tag]"));
|
|
1390
|
+
expect(UDExSourceTags.map(UDExTag => UDExTag.getAttribute("text"))).to.deep.equal(UDExLabels);
|
|
1391
|
+
});
|
|
1392
|
+
if (!UDExLabels.length) {
|
|
1393
|
+
UDExTagGroup().should("not.exist");
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
UDExRenderedTags().should("have.length", UDExLabels.length).each(($tag, UDExIndex) => {
|
|
1397
|
+
cy.wrap($tag).should("have.attr", "text", UDExLabels[UDExIndex]);
|
|
1398
|
+
UDExSourceLeaf(UDExLabels[UDExIndex]).then($source => {
|
|
1399
|
+
cy.wrap($tag).should("have.attr", "data-id", ($source[0] as UDExSelectBoxItemElement)._id);
|
|
1400
|
+
});
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
UDExLayouts.forEach(UDExLayout => {
|
|
1405
|
+
describe(UDExLayout.name, () => {
|
|
1406
|
+
let UDExObserver: MutationObserver | undefined;
|
|
1407
|
+
|
|
1408
|
+
beforeEach(() => {
|
|
1409
|
+
UDExMountSelectBox(UDExListMode.MultiSelect, UDExLayout);
|
|
1410
|
+
UDExAssertTags([]);
|
|
1411
|
+
});
|
|
1412
|
+
|
|
1413
|
+
afterEach(() => UDExObserver?.disconnect());
|
|
1414
|
+
|
|
1415
|
+
function UDExToggleLeaf(UDExLabel: string, UDExSelectedLabels: string[], UDExCallCount: number,
|
|
1416
|
+
UDExActivation: "pointer" | "Enter" | "Space" = "pointer") {
|
|
1417
|
+
UDExAssertActiveDescendant(UDExLeafHost(UDExLabel));
|
|
1418
|
+
UDExLeafHost(UDExLabel).then($host => {
|
|
1419
|
+
UDExCombobox().then($input => {
|
|
1420
|
+
const UDExInput = $input[0];
|
|
1421
|
+
const UDExRecords: MutationRecord[] = [];
|
|
1422
|
+
UDExObserver = new MutationObserver(UDExChanges => UDExRecords.push(...UDExChanges));
|
|
1423
|
+
UDExObserver.observe(UDExInput, {
|
|
1424
|
+
attributes: true, attributeFilter: ["aria-activedescendant"], attributeOldValue: true,
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
if (UDExActivation === "pointer") {
|
|
1428
|
+
UDExLeafHost(UDExLabel).shadow().find('[part="item"]').realClick({ scrollBehavior: false });
|
|
1429
|
+
} else {
|
|
1430
|
+
cy.realPress(UDExActivation);
|
|
1431
|
+
}
|
|
1432
|
+
const UDExSelected = UDExSelectedLabels.includes(UDExLabel);
|
|
1433
|
+
UDExSourceLeaf(UDExLabel).should(UDExSelected ? "have.attr" : "not.have.attr", "selected");
|
|
1434
|
+
UDExAssertHostSelection(UDExLeafHost(UDExLabel), UDExSelected);
|
|
1435
|
+
UDExAssertActiveDescendant(UDExLeafHost(UDExLabel));
|
|
1436
|
+
UDExLeafHost(UDExLabel).should($current => expect($current[0], "same rendered leaf after toggling").to.equal($host[0]));
|
|
1437
|
+
UDExCombobox().should($current => expect($current[0], "same input after toggling").to.equal(UDExInput));
|
|
1438
|
+
UDExAssertSelectionStatus(UDExLabel, UDExSelected ? "Selected" : "Unselected");
|
|
1439
|
+
UDExAssertSelectionEvent(UDExLabel, UDExSelectedLabels, UDExCallCount);
|
|
1440
|
+
UDExAssertTags(UDExSelectedLabels);
|
|
1441
|
+
UDExPopover().should("have.attr", "open");
|
|
1442
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
1443
|
+
cy.then(() => {
|
|
1444
|
+
UDExRecords.push(...UDExObserver!.takeRecords());
|
|
1445
|
+
UDExObserver!.disconnect();
|
|
1446
|
+
// oldValue catches a temporary clear/rebind even if the final ID is unchanged.
|
|
1447
|
+
UDExRecords.forEach(UDExRecord => expect(UDExRecord.oldValue, "active ID never cleared or redirected during selection")
|
|
1448
|
+
.to.equal($host[0].id));
|
|
1449
|
+
expect(UDExInput.getAttribute("aria-activedescendant")).to.equal($host[0].id);
|
|
1450
|
+
});
|
|
1451
|
+
});
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
// Updated: reopening expanded headers is silent; other hosts retain the fallback and tag semantics are unchanged.
|
|
1456
|
+
it("keeps the active leaf's identity through first selection, additional selection, deselection and reopening with tags", () => {
|
|
1457
|
+
if (UDExLayout.grouped) {
|
|
1458
|
+
cy.realPress("ArrowDown");
|
|
1459
|
+
}
|
|
1460
|
+
UDExToggleLeaf("Alpha", ["Alpha"], 1);
|
|
1461
|
+
cy.realPress("End");
|
|
1462
|
+
UDExToggleLeaf("Beta", ["Alpha", "Beta"], 2, "Space");
|
|
1463
|
+
cy.realPress("ArrowUp");
|
|
1464
|
+
UDExToggleLeaf("Alpha", ["Beta"], 3);
|
|
1465
|
+
UDExToggleLeaf("Alpha", ["Alpha", "Beta"], 4, "Enter");
|
|
1466
|
+
|
|
1467
|
+
cy.realPress("Escape");
|
|
1468
|
+
UDExPopover().should("not.have.attr", "open");
|
|
1469
|
+
UDExCombobox().should("have.attr", "aria-expanded", "false");
|
|
1470
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
1471
|
+
UDExAssertSupplementalStatus("");
|
|
1472
|
+
UDExAssertTags(["Alpha", "Beta"]);
|
|
1473
|
+
UDExCombobox().should("be.focused");
|
|
1474
|
+
cy.realPress("ArrowDown");
|
|
1475
|
+
UDExAssertOpenWithActiveDescendant(UDExLayout.grouped ? UDExHeaderHost() : UDExLeafHost("Alpha"),
|
|
1476
|
+
UDExLayout.collapsible ? "" : "expanded");
|
|
1477
|
+
if (UDExLayout.grouped) {
|
|
1478
|
+
UDExAssertHostPosition(UDExHeaderHost(), 1, 1, 1);
|
|
1479
|
+
UDExAssertHostSelection(UDExHeaderHost(), false, true);
|
|
1480
|
+
}
|
|
1481
|
+
["Alpha", "Beta"].forEach(UDExLabel => UDExAssertHostSelection(UDExLeafHost(UDExLabel), true));
|
|
1482
|
+
cy.realPress("End");
|
|
1483
|
+
UDExAssertNativeActiveDescendant(UDExLeafHost("Beta"), 2, 3, UDExLayout.grouped ? 2 : undefined);
|
|
1484
|
+
UDExToggleLeaf("Beta", ["Alpha"], 5);
|
|
1485
|
+
UDExSourceLeaf("Unavailable").should("not.have.attr", "selected");
|
|
1486
|
+
});
|
|
1487
|
+
|
|
1488
|
+
// Updated: tag removal changes aria-selected without changing source identity or remove payloads.
|
|
1489
|
+
it("maps each rendered tag's delete icon to its original source item and bubbling remove payload", () => {
|
|
1490
|
+
if (UDExLayout.grouped) {
|
|
1491
|
+
cy.realPress("ArrowDown");
|
|
1492
|
+
}
|
|
1493
|
+
UDExToggleLeaf("Alpha", ["Alpha"], 1);
|
|
1494
|
+
cy.realPress("End");
|
|
1495
|
+
UDExToggleLeaf("Beta", ["Alpha", "Beta"], 2);
|
|
1496
|
+
cy.get("#select-box-regression-fixture").then($fixture => {
|
|
1497
|
+
$fixture[0].addEventListener("remove", cy.stub().as("tagRemoved"));
|
|
1498
|
+
});
|
|
1499
|
+
|
|
1500
|
+
["Alpha", "Beta"].forEach((UDExLabel, UDExIndex) => {
|
|
1501
|
+
UDExSourceLeaf(UDExLabel).then($source => {
|
|
1502
|
+
const UDExOriginal = $source[0] as UDExSelectBoxItemElement;
|
|
1503
|
+
UDExRenderedTags().filter(`[text="${UDExLabel}"]`).should("have.attr", "data-id", UDExOriginal._id);
|
|
1504
|
+
// Exercise UI5's icon event through Tag and TagGroup, not the removal callback directly.
|
|
1505
|
+
UDExRenderedTags().filter(`[text="${UDExLabel}"]`).shadow().find('[ui5-icon][name="decline"]')
|
|
1506
|
+
.click();
|
|
1507
|
+
cy.get("@tagRemoved").should("have.callCount", UDExIndex + 1).its("lastCall.args.0")
|
|
1508
|
+
.should((UDExEvent: CustomEvent<UDExSelectBoxRemoveEventDetail>) => {
|
|
1509
|
+
expect(UDExEvent.type).to.equal("remove");
|
|
1510
|
+
expect(UDExEvent.bubbles).to.equal(true);
|
|
1511
|
+
expect(UDExEvent.detail.item).to.equal(UDExOriginal);
|
|
1512
|
+
expect(UDExEvent.detail.item).to.have.property("selected", false);
|
|
1513
|
+
});
|
|
1514
|
+
UDExSourceLeaf(UDExLabel).should("not.have.attr", "selected");
|
|
1515
|
+
UDExAssertHostSelection(UDExLeafHost(UDExLabel), false);
|
|
1516
|
+
UDExAssertTags(UDExIndex === 0 ? ["Beta"] : []);
|
|
1517
|
+
if (UDExIndex === 0) {
|
|
1518
|
+
UDExSourceLeaf("Beta").should("have.attr", "selected");
|
|
1519
|
+
UDExAssertHostSelection(UDExLeafHost("Beta"), true);
|
|
1520
|
+
}
|
|
1521
|
+
// Tag removal retains its existing callback-driven input focus restoration.
|
|
1522
|
+
UDExCombobox().should("be.focused");
|
|
1523
|
+
});
|
|
1524
|
+
});
|
|
1525
|
+
cy.get("@selectionChange").should("have.callCount", 2);
|
|
1526
|
+
UDExSourceLeaf("Unavailable").should("not.have.attr", "selected");
|
|
1527
|
+
});
|
|
1528
|
+
});
|
|
1529
|
+
});
|
|
1530
|
+
});
|
|
1531
|
+
|
|
1532
|
+
describe("SelectBox mixed collapsible and noncollapsible grouping", () => {
|
|
1533
|
+
// Match the Grouping story's mixed layout, with no preselection to mask first-item activation.
|
|
1534
|
+
const UDExMixedGroups = [
|
|
1535
|
+
{ label: "Backend Languages", collapsible: true, items: ["Python", "Java", "Ruby", "Node.js"] },
|
|
1536
|
+
{ label: "Frontend Languages", collapsible: false, items: ["JavaScript", "Swift", "React"] },
|
|
1537
|
+
];
|
|
1538
|
+
|
|
1539
|
+
[UDExListMode.SingleSelect, UDExListMode.MultiSelect].forEach(UDExMode => {
|
|
1540
|
+
describe(UDExMode, () => {
|
|
1541
|
+
function UDExAssertMixedTree(UDExGroups = UDExMixedGroups) {
|
|
1542
|
+
UDExCombobox().should("have.attr", "aria-haspopup", "tree");
|
|
1543
|
+
UDExListHost().should("have.attr", "role", "tree");
|
|
1544
|
+
UDExListHost().should("have.attr", "aria-label", "Languages");
|
|
1545
|
+
UDExListHost().find('[role="option"], [role="listbox"]').should("not.exist");
|
|
1546
|
+
UDExListHost().shadow().find('[role="tree"], [role="listbox"]').should("not.exist");
|
|
1547
|
+
UDExHeaderHost().should("have.length", UDExGroups.length).each(($header, UDExIndex) => {
|
|
1548
|
+
const UDExGroup = UDExGroups[UDExIndex];
|
|
1549
|
+
UDExAssertHostPosition(cy.wrap($header), UDExIndex + 1, UDExGroups.length, 1);
|
|
1550
|
+
cy.wrap($header).should($host => {
|
|
1551
|
+
expect($host).to.have.attr("aria-label", UDExGroup.label);
|
|
1552
|
+
expect($host[0]).to.have.property("mode", UDExMode);
|
|
1553
|
+
expect($host[0]).to.have.property("_treeMode", true);
|
|
1554
|
+
expect($host[0]).to.have.property("_groupAsListbox", false);
|
|
1555
|
+
expect($host).not.to.have.attr("aria-checked");
|
|
1556
|
+
expect($host.attr("aria-selected")).to.be.oneOf(["true", "false"]);
|
|
1557
|
+
if (!UDExGroup.collapsible) {
|
|
1558
|
+
expect($host).not.to.have.attr("aria-expanded");
|
|
1559
|
+
}
|
|
1560
|
+
});
|
|
1561
|
+
cy.wrap($header).shadow().find('[part="item"]').should("have.attr", "role", "presentation");
|
|
1562
|
+
cy.wrap($header).shadow().find('[role="listbox"], [role="treeitem"], [role="option"]').should("not.exist");
|
|
1563
|
+
cy.wrap($header).shadow().find('[role="group"] > slot').should("have.length", 1).then($slot => {
|
|
1564
|
+
// Verify composed ownership: the host treeitem owns a group containing its slotted leaves.
|
|
1565
|
+
expect(($slot[0] as HTMLSlotElement).assignedElements()).to.deep.equal(Array.from($header[0].children));
|
|
1566
|
+
});
|
|
1567
|
+
if (UDExMode === UDExListMode.MultiSelect) {
|
|
1568
|
+
const UDExRow = () => cy.wrap($header).shadow().find('[udex-list-item]#group-heading');
|
|
1569
|
+
UDExRow().should("have.attr", "role", "none");
|
|
1570
|
+
UDExRow().should($row => {
|
|
1571
|
+
UDExInnerStateAttributes.forEach(UDExAttribute => expect($row).not.to.have.attr(UDExAttribute));
|
|
1572
|
+
});
|
|
1573
|
+
UDExAssertPresentationItem(UDExRow());
|
|
1574
|
+
UDExRow().shadow().find('[role="listbox"]').should("not.exist");
|
|
1575
|
+
}
|
|
1576
|
+
cy.wrap($header).find("[udex-list-item]").should("have.length", UDExGroup.items.length).each(($leaf, UDExChildIndex) => {
|
|
1577
|
+
UDExAssertHostPosition(cy.wrap($leaf), UDExChildIndex + 1, UDExGroup.items.length, 2);
|
|
1578
|
+
UDExAssertLeafName(cy.wrap($leaf), UDExGroup.items[UDExChildIndex]);
|
|
1579
|
+
cy.wrap($leaf).should("not.have.attr", "aria-expanded");
|
|
1580
|
+
cy.wrap($leaf).should("not.have.attr", "aria-checked");
|
|
1581
|
+
cy.wrap($leaf).should($host => expect($host.attr("aria-selected")).to.be.oneOf(["true", "false"]));
|
|
1582
|
+
UDExAssertPresentationItem(cy.wrap($leaf));
|
|
1583
|
+
cy.wrap($leaf).shadow().find('[role="listbox"]').should("not.exist");
|
|
1584
|
+
});
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
beforeEach(() => {
|
|
1589
|
+
cy.mount(
|
|
1590
|
+
<div id="select-box-regression-fixture">
|
|
1591
|
+
<UDExSelectBox label="Languages" mode={UDExMode}>
|
|
1592
|
+
{UDExMixedGroups.map(UDExGroup => (
|
|
1593
|
+
<UDExSelectBoxGroupHeader label={UDExGroup.label} collapsible={UDExGroup.collapsible}>
|
|
1594
|
+
{UDExGroup.items.map(UDExLabel => <UDExSelectBoxItem label={UDExLabel} disabled={UDExLabel === "Java"}></UDExSelectBoxItem>)}
|
|
1595
|
+
</UDExSelectBoxGroupHeader>
|
|
1596
|
+
))}
|
|
1597
|
+
</UDExSelectBox>
|
|
1598
|
+
</div>
|
|
1599
|
+
);
|
|
1600
|
+
UDExCombobox().then($input => {
|
|
1601
|
+
$input[0].addEventListener("blur", cy.stub().as("mixedOpeningBlur"));
|
|
1602
|
+
});
|
|
1603
|
+
UDExOpenSelectBox(UDExLayouts[2]);
|
|
1604
|
+
// Updated: the expanded first header supplies native expansion without live duplication.
|
|
1605
|
+
UDExAssertOpenWithActiveDescendant(UDExHeaderHost().first(), "");
|
|
1606
|
+
UDExAssertHostPosition(UDExHeaderHost().first(), 1, 2, 1);
|
|
1607
|
+
cy.get("@mixedOpeningBlur").should("not.have.been.called");
|
|
1608
|
+
});
|
|
1609
|
+
|
|
1610
|
+
// Updated: both group types and their leaves expose Boolean aria-selected, never aria-checked.
|
|
1611
|
+
it("owns both group types as treeitems with slotted groups, host selection states and no nested listboxes", () => {
|
|
1612
|
+
UDExAssertMixedTree();
|
|
1613
|
+
UDExHeaderHost().each($header => UDExAssertHostSelection(cy.wrap($header), false));
|
|
1614
|
+
UDExListHost().find("[udex-list-item]").each($leaf => UDExAssertHostSelection(cy.wrap($leaf), false));
|
|
1615
|
+
UDExLeafHost("Java").should("have.attr", "aria-disabled", "true");
|
|
1616
|
+
UDExHeaderHost().first().should("have.attr", "aria-expanded", "true");
|
|
1617
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
1618
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
1619
|
+
});
|
|
1620
|
+
|
|
1621
|
+
it("keeps native levels, positions and host identity stable in both directions across collapsible and noncollapsible groups", () => {
|
|
1622
|
+
UDExAssertMixedTree();
|
|
1623
|
+
const UDExPath = [
|
|
1624
|
+
{ host: () => UDExHeaderHost().first(), position: 1, size: 2, level: 1 },
|
|
1625
|
+
{ host: () => UDExLeafHost("Python"), position: 1, size: 4, level: 2 },
|
|
1626
|
+
// Disabled Java is skipped by navigation but remains the second sibling in the set.
|
|
1627
|
+
{ host: () => UDExLeafHost("Ruby"), position: 3, size: 4, level: 2 },
|
|
1628
|
+
{ host: () => UDExLeafHost("Node.js"), position: 4, size: 4, level: 2 },
|
|
1629
|
+
...(UDExMode === UDExListMode.MultiSelect
|
|
1630
|
+
? [{ host: () => UDExHeaderHost().last(), position: 2, size: 2, level: 1 }] : []),
|
|
1631
|
+
{ host: () => UDExLeafHost("JavaScript"), position: 1, size: 3, level: 2 },
|
|
1632
|
+
{ host: () => UDExLeafHost("Swift"), position: 2, size: 3, level: 2 },
|
|
1633
|
+
{ host: () => UDExLeafHost("React"), position: 3, size: 3, level: 2 },
|
|
1634
|
+
];
|
|
1635
|
+
UDExCombobox().then($input => {
|
|
1636
|
+
const UDExOriginalInput = $input[0];
|
|
1637
|
+
UDExListHost().find("[udex-list-group-header], [udex-list-item]").then($hosts => {
|
|
1638
|
+
const UDExOriginalHosts = Array.from($hosts);
|
|
1639
|
+
const UDExAssertStep = (UDExStep: typeof UDExPath[number]) => {
|
|
1640
|
+
UDExAssertNativeActiveDescendant(UDExStep.host(), UDExStep.position, UDExStep.size, UDExStep.level);
|
|
1641
|
+
UDExAssertHostSelection(UDExStep.host(), false);
|
|
1642
|
+
UDExListHost().find("[udex-list-group-header], [udex-list-item]").should($current => {
|
|
1643
|
+
expect(Array.from($current), "navigation preserves each rendered host and its ID owner").to.deep.equal(UDExOriginalHosts);
|
|
1644
|
+
});
|
|
1645
|
+
UDExCombobox().should($current => expect($current[0]).to.equal(UDExOriginalInput));
|
|
1646
|
+
UDExHeaderHost().first().should("have.attr", "aria-expanded", "true");
|
|
1647
|
+
UDExHeaderHost().last().should("not.have.attr", "aria-expanded");
|
|
1648
|
+
UDExAssertNativeHostStates();
|
|
1649
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
1650
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
1651
|
+
cy.get("@mixedOpeningBlur").should("not.have.been.called");
|
|
1652
|
+
};
|
|
1653
|
+
|
|
1654
|
+
UDExPath.slice(1).forEach(UDExStep => {
|
|
1655
|
+
cy.realPress("ArrowDown");
|
|
1656
|
+
UDExAssertStep(UDExStep);
|
|
1657
|
+
});
|
|
1658
|
+
UDExAssertMixedTree();
|
|
1659
|
+
UDExPath.slice(0, -1).reverse().forEach(UDExStep => {
|
|
1660
|
+
cy.realPress("ArrowUp");
|
|
1661
|
+
UDExAssertStep(UDExStep);
|
|
1662
|
+
});
|
|
1663
|
+
// Wrapping must not change the hierarchy or silently select the active leaf.
|
|
1664
|
+
cy.realPress("ArrowUp");
|
|
1665
|
+
UDExAssertStep(UDExPath[UDExPath.length - 1]);
|
|
1666
|
+
cy.realPress("ArrowDown");
|
|
1667
|
+
UDExAssertStep(UDExPath[0]);
|
|
1668
|
+
UDExAssertMixedTree();
|
|
1669
|
+
cy.get("[udex-select-box]").find("[udex-select-box-item][selected]").should("not.exist");
|
|
1670
|
+
});
|
|
1671
|
+
});
|
|
1672
|
+
});
|
|
1673
|
+
|
|
1674
|
+
it("toggles only the collapsible group by pointer and arrows without changing tree ownership or input focus", () => {
|
|
1675
|
+
const UDExChevronHost = UDExMode === UDExListMode.MultiSelect
|
|
1676
|
+
? UDExHeaderHost().first().shadow().find('[udex-list-item]#group-heading') : UDExHeaderHost().first();
|
|
1677
|
+
UDExChevronHost.shadow().find("[udex-button].udex-list__group-heading__icon").realClick();
|
|
1678
|
+
UDExHeaderHost().first().should("have.attr", "aria-expanded", "false");
|
|
1679
|
+
cy.get("[udex-select-box]").find('[udex-select-box-group-header][label="Backend Languages"]').should("have.attr", "collapsed");
|
|
1680
|
+
UDExHeaderHost().first().find("[udex-list-item]").each($leaf => cy.wrap($leaf).should("have.attr", "collapsed"));
|
|
1681
|
+
UDExAssertActiveDescendant(UDExHeaderHost().first());
|
|
1682
|
+
cy.realPress("ArrowRight");
|
|
1683
|
+
UDExHeaderHost().first().should("have.attr", "aria-expanded", "true");
|
|
1684
|
+
UDExHeaderHost().first().find("[udex-list-item]").each($leaf => cy.wrap($leaf).should("not.have.attr", "collapsed"));
|
|
1685
|
+
cy.realPress("ArrowLeft");
|
|
1686
|
+
UDExHeaderHost().first().should("have.attr", "aria-expanded", "false");
|
|
1687
|
+
cy.realPress("ArrowDown");
|
|
1688
|
+
UDExAssertActiveDescendant(UDExMode === UDExListMode.MultiSelect ? UDExHeaderHost().last() : UDExLeafHost("JavaScript"));
|
|
1689
|
+
cy.realPress("ArrowLeft");
|
|
1690
|
+
UDExHeaderHost().last().should("not.have.attr", "aria-expanded");
|
|
1691
|
+
UDExHeaderHost().last().should("not.have.attr", "collapsed");
|
|
1692
|
+
cy.realPress("Home");
|
|
1693
|
+
cy.realPress("ArrowRight");
|
|
1694
|
+
UDExHeaderHost().first().should("have.attr", "aria-expanded", "true");
|
|
1695
|
+
cy.get("[udex-select-box]").find('[udex-select-box-group-header][label="Backend Languages"]').should("not.have.attr", "collapsed");
|
|
1696
|
+
UDExAssertMixedTree();
|
|
1697
|
+
UDExAssertActiveDescendant(UDExHeaderHost().first());
|
|
1698
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
1699
|
+
cy.get("@mixedOpeningBlur").should("not.have.been.called");
|
|
1700
|
+
});
|
|
1701
|
+
|
|
1702
|
+
// Updated: partial MultiSelect groups are false with an Indeterminate description, not mixed selection.
|
|
1703
|
+
it("updates the leaf's host selection state without duplicating inner selection semantics", () => {
|
|
1704
|
+
cy.realPress("ArrowDown");
|
|
1705
|
+
UDExAssertActiveDescendant(UDExLeafHost("Python"));
|
|
1706
|
+
cy.realPress("Enter");
|
|
1707
|
+
UDExSourceLeaf("Python").should("have.attr", "selected");
|
|
1708
|
+
UDExAssertHostSelection(UDExLeafHost("Python"), true);
|
|
1709
|
+
UDExAssertHostSelection(UDExHeaderHost().first(), false, UDExMode === UDExListMode.MultiSelect);
|
|
1710
|
+
UDExAssertHostSelection(UDExHeaderHost().last(), false);
|
|
1711
|
+
UDExAssertSelectionEvent("Python", ["Python"]);
|
|
1712
|
+
UDExAssertSelectionStatus("Python", "Selected");
|
|
1713
|
+
UDExAssertMixedTree();
|
|
1714
|
+
UDExSourceLeaf("Java").should("not.have.attr", "selected");
|
|
1715
|
+
UDExPopover().should(UDExMode === UDExListMode.MultiSelect ? "have.attr" : "not.have.attr", "open");
|
|
1716
|
+
UDExCombobox().should("be.focused");
|
|
1717
|
+
cy.get("@mixedOpeningBlur").should("not.have.been.called");
|
|
1718
|
+
});
|
|
1719
|
+
|
|
1720
|
+
// Updated: navigation clears filtered result counts; native positions retain the filtered hierarchy.
|
|
1721
|
+
it("keeps explicit group roles and recalculates positions when filtering retains both groups, then only the noncollapsible group", () => {
|
|
1722
|
+
cy.realType("Java");
|
|
1723
|
+
UDExStatus().should("have.text", "3 results returned.");
|
|
1724
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
1725
|
+
UDExListHost().find("[_active-descendant]").should("not.exist");
|
|
1726
|
+
UDExAssertMixedTree([
|
|
1727
|
+
{ ...UDExMixedGroups[0], items: ["Java"] },
|
|
1728
|
+
{ ...UDExMixedGroups[1], items: ["JavaScript"] },
|
|
1729
|
+
]);
|
|
1730
|
+
UDExCombobox().clear();
|
|
1731
|
+
cy.realType("Swift");
|
|
1732
|
+
UDExStatus().should("have.text", "2 results returned.");
|
|
1733
|
+
UDExAssertMixedTree([{ ...UDExMixedGroups[1], items: ["Swift"] }]);
|
|
1734
|
+
UDExListHost().find("[_active-descendant]").should("not.exist");
|
|
1735
|
+
cy.realPress("ArrowDown");
|
|
1736
|
+
UDExAssertNativeActiveDescendant(UDExMode === UDExListMode.MultiSelect ? UDExHeaderHost() : UDExHeaderHost().find("[udex-list-item]"),
|
|
1737
|
+
1, 1, UDExMode === UDExListMode.MultiSelect ? 1 : 2);
|
|
1738
|
+
UDExAssertHostSelection(UDExMode === UDExListMode.MultiSelect ? UDExHeaderHost() : UDExHeaderHost().find("[udex-list-item]"), false);
|
|
1739
|
+
UDExCombobox().clear();
|
|
1740
|
+
UDExAssertMixedTree();
|
|
1741
|
+
cy.realPress("ArrowDown");
|
|
1742
|
+
UDExAssertActiveDescendant(UDExHeaderHost().first());
|
|
1743
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
1744
|
+
cy.get("@mixedOpeningBlur").should("not.have.been.called");
|
|
1745
|
+
});
|
|
1746
|
+
});
|
|
1747
|
+
});
|
|
1748
|
+
});
|
|
1749
|
+
|
|
1750
|
+
describe("SelectBox Grouping with all selected tags overflowing", () => {
|
|
1751
|
+
it("omits the empty tokenizer listbox while preserving More and input-focused tree highlighting", () => {
|
|
1752
|
+
const UDExFrontendLabels = [
|
|
1753
|
+
"JavaScript is a lightweight interpreted programming language", "Swift",
|
|
1754
|
+
"HyperText Markup", "Cascading Style Sheets", "React", "Angular", "Django",
|
|
1755
|
+
];
|
|
1756
|
+
const UDExTagGroup = () => cy.get("[udex-select-box]").shadow().find("[udex-tag-group]");
|
|
1757
|
+
const UDExTokenizer = () => UDExTagGroup().shadow().find(".udex-tag-group__tokenizer");
|
|
1758
|
+
const UDExMoreButton = () => UDExTokenizer().shadow().find(".ui5-tokenizer-more-text");
|
|
1759
|
+
|
|
1760
|
+
function UDExAssertTokenizerList(UDExVisibleCount: number) {
|
|
1761
|
+
UDExTokenizer().find("[udex-tag]").should("have.length", 5);
|
|
1762
|
+
UDExTokenizer().shadow().find(".ui5-tokenizer--list").should($list => {
|
|
1763
|
+
const UDExTags = Array.from($list[0].querySelectorAll("slot"))
|
|
1764
|
+
.flatMap(UDExSlot => UDExSlot.assignedElements());
|
|
1765
|
+
const UDExVisibleTags = UDExTags.filter(UDExTokenElement => !UDExTokenElement.hasAttribute("overflows"));
|
|
1766
|
+
expect(UDExTags).to.have.length(5);
|
|
1767
|
+
expect(UDExVisibleTags).to.have.length(UDExVisibleCount);
|
|
1768
|
+
expect($list).to.have.attr("role", UDExVisibleCount ? "listbox" : "presentation");
|
|
1769
|
+
if (UDExVisibleCount) {
|
|
1770
|
+
const UDExTokenizerHost = ($list[0].getRootNode() as ShadowRoot).host as Tokenizer;
|
|
1771
|
+
expect($list).to.have.attr("aria-label", UDExTokenizerHost.tokenizerLabel);
|
|
1772
|
+
UDExVisibleTags.forEach(UDExTokenElement => {
|
|
1773
|
+
expect(UDExTokenElement.shadowRoot?.querySelector('[role="option"]'), "restored listbox owns an option").to.exist;
|
|
1774
|
+
});
|
|
1775
|
+
} else {
|
|
1776
|
+
["aria-label", "aria-description", "aria-disabled", "aria-readonly"].forEach(UDExAttribute => {
|
|
1777
|
+
expect($list).not.to.have.attr(UDExAttribute);
|
|
1778
|
+
});
|
|
1779
|
+
expect($list.parent().find('[role="listbox"]')).to.have.length(0);
|
|
1780
|
+
}
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
// Match the Grouping story's 320px width, mixed groups and five selected frontend leaves.
|
|
1785
|
+
cy.mount(
|
|
1786
|
+
<div id="select-box-regression-fixture" style="width: 320px;">
|
|
1787
|
+
<UDExSelectBox label="Programming languages" mode={UDExListMode.MultiSelect} height="400px">
|
|
1788
|
+
<UDExSelectBoxGroupHeader label="Backend Languages" collapsible>
|
|
1789
|
+
{["Python", "Java", "Ruby", "Node.js"].map(UDExLabel => (
|
|
1790
|
+
<UDExSelectBoxItem label={UDExLabel} disabled={UDExLabel === "Java"}></UDExSelectBoxItem>
|
|
1791
|
+
))}
|
|
1792
|
+
</UDExSelectBoxGroupHeader>
|
|
1793
|
+
<UDExSelectBoxGroupHeader label="Frontend Languages" indeterminate selected>
|
|
1794
|
+
{UDExFrontendLabels.map((UDExLabel, UDExIndex) => (
|
|
1795
|
+
<UDExSelectBoxItem label={UDExLabel} selected={UDExIndex >= 2}></UDExSelectBoxItem>
|
|
1796
|
+
))}
|
|
1797
|
+
</UDExSelectBoxGroupHeader>
|
|
1798
|
+
</UDExSelectBox>
|
|
1799
|
+
</div>
|
|
1800
|
+
);
|
|
1801
|
+
UDExSourceLeaf("HyperText Markup").should("have.attr", "selected");
|
|
1802
|
+
UDExTokenizer().find("[udex-tag][overflows]").should("have.length", 5);
|
|
1803
|
+
UDExAssertTokenizerList(0);
|
|
1804
|
+
UDExMoreButton().should("be.visible").and("have.attr", "role", "button");
|
|
1805
|
+
UDExMoreButton().should($more => {
|
|
1806
|
+
expect($more.text()).to.contain("5");
|
|
1807
|
+
// SelectBox deliberately skips More in Tab order, but it must remain exposed and clickable.
|
|
1808
|
+
expect($more).to.have.attr("tabindex", "-1");
|
|
1809
|
+
let UDExNode: HTMLElement | null = $more[0];
|
|
1810
|
+
while (UDExNode) {
|
|
1811
|
+
expect(UDExNode.getAttribute("aria-hidden")).not.to.equal("true");
|
|
1812
|
+
expect(UDExNode.hasAttribute("hidden") || UDExNode.hasAttribute("inert")).to.equal(false);
|
|
1813
|
+
UDExNode = UDExNode.assignedSlot || UDExNode.parentElement
|
|
1814
|
+
|| ((UDExNode.getRootNode() as ShadowRoot).host as HTMLElement | undefined) || null;
|
|
1815
|
+
}
|
|
1816
|
+
});
|
|
1817
|
+
cy.get("[udex-select-box]").then($selectBox => {
|
|
1818
|
+
$selectBox[0].addEventListener("show-more", cy.stub().as("overflowShowMore"));
|
|
1819
|
+
});
|
|
1820
|
+
cy.get("#select-box-regression-fixture").then($fixture => {
|
|
1821
|
+
$fixture[0].addEventListener("selection-change", cy.stub().as("selectionChange"));
|
|
1822
|
+
});
|
|
1823
|
+
UDExPopover().then($popover => {
|
|
1824
|
+
$popover[0].addEventListener("ui5-open", cy.stub().as("groupingPopoverOpened"), { once: true });
|
|
1825
|
+
});
|
|
1826
|
+
UDExCombobox().then($input => {
|
|
1827
|
+
$input[0].addEventListener("blur", cy.stub().as("groupingOpeningBlur"));
|
|
1828
|
+
});
|
|
1829
|
+
|
|
1830
|
+
// More opens SelectBox's popup, not TagGroup's tooltip, and focus expands the tokenizer.
|
|
1831
|
+
UDExMoreButton().click();
|
|
1832
|
+
cy.get("@overflowShowMore").should("have.been.calledOnce");
|
|
1833
|
+
cy.get("@groupingPopoverOpened").should("have.been.calledOnce");
|
|
1834
|
+
UDExPopover().should("have.attr", "open");
|
|
1835
|
+
UDExCombobox().should("have.attr", "aria-expanded", "true");
|
|
1836
|
+
UDExCombobox().should("have.attr", "aria-haspopup", "tree");
|
|
1837
|
+
UDExListHost().should("have.attr", "role", "tree");
|
|
1838
|
+
UDExListHost().should("have.attr", "aria-label", "Programming languages");
|
|
1839
|
+
UDExListHost().then($list => {
|
|
1840
|
+
UDExCombobox().should("have.attr", "aria-controls", $list[0].id);
|
|
1841
|
+
});
|
|
1842
|
+
UDExAssertActiveDescendant(UDExHeaderHost().first());
|
|
1843
|
+
UDExAssertTokenizerList(5);
|
|
1844
|
+
cy.realPress("ArrowDown");
|
|
1845
|
+
UDExAssertActiveDescendant(UDExLeafHost("Python"));
|
|
1846
|
+
cy.get("@groupingOpeningBlur").should("not.have.been.called");
|
|
1847
|
+
UDExAssertTokenizerList(5);
|
|
1848
|
+
UDExTagGroup().shadow().find("[udex-tooltip][open]").should("not.exist");
|
|
1849
|
+
cy.get("[udex-select-box]").find("[udex-select-box-item][selected]").should("have.length", 5);
|
|
1850
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
1851
|
+
});
|
|
1852
|
+
});
|
|
1853
|
+
|
|
1854
|
+
describe("SelectBox actionable flat popup accessibility", () => {
|
|
1855
|
+
const UDExActionLayouts = [
|
|
1856
|
+
{ name: "first icon only", slots: ["firstIcon"] },
|
|
1857
|
+
{ name: "second icon only", slots: ["secondIcon"] },
|
|
1858
|
+
{ name: "both icons", slots: ["firstIcon", "secondIcon"] },
|
|
1859
|
+
];
|
|
1860
|
+
|
|
1861
|
+
UDExModes.forEach(UDExMode => {
|
|
1862
|
+
UDExActionLayouts.forEach(UDExLayout => {
|
|
1863
|
+
describe(`${UDExMode} / ${UDExLayout.name}`, () => {
|
|
1864
|
+
const UDExAction = (UDExSlot: string) => UDExLeafHost("Alpha").find(`[udex-button][slot="${UDExSlot}"]`);
|
|
1865
|
+
const UDExActionName = (UDExSlot: string) => UDExSlot === "firstIcon" ? "First action" : "Second action";
|
|
1866
|
+
const UDExEventName = (UDExSlot: string) => UDExSlot === "firstIcon" ? "first-icon-click" : "second-icon-click";
|
|
1867
|
+
|
|
1868
|
+
function UDExAssertActionEvent(UDExSlot: string, UDExCount: number) {
|
|
1869
|
+
UDExSourceLeaf("Alpha").then($source => {
|
|
1870
|
+
UDExAction(UDExSlot).then($button => {
|
|
1871
|
+
cy.get(`@${UDExSlot}`).should("have.callCount", UDExCount).its("lastCall.args.0")
|
|
1872
|
+
.should((UDExEvent: CustomEvent<UDExSelectBoxIconClickEventDetail>) => {
|
|
1873
|
+
expect(UDExEvent.type).to.equal(UDExEventName(UDExSlot));
|
|
1874
|
+
expect(UDExEvent.bubbles).to.equal(true);
|
|
1875
|
+
expect(UDExEvent.detail.item).to.equal($source[0]);
|
|
1876
|
+
expect(UDExEvent.detail.item?.label).to.equal("Alpha");
|
|
1877
|
+
expect(UDExEvent.detail.item?.supportingText).to.equal("First choice");
|
|
1878
|
+
expect(UDExEvent.detail.item?.getAttribute("additional-text")).to.equal("Code A");
|
|
1879
|
+
expect(UDExEvent.detail.originalEvent.type).to.equal("click");
|
|
1880
|
+
}).then((UDExEvent: CustomEvent<UDExSelectBoxIconClickEventDetail>) => {
|
|
1881
|
+
// A shadow-crossing native event can clear its target after dispatch.
|
|
1882
|
+
cy.get(`@${UDExSlot}ClickSnapshot`).should("have.callCount", UDExCount)
|
|
1883
|
+
.its("lastCall.args.0").should(UDExSnapshot => {
|
|
1884
|
+
expect(UDExSnapshot.event).to.equal(UDExEvent.detail.originalEvent);
|
|
1885
|
+
expect(UDExSnapshot.target).to.equal($button[0]);
|
|
1886
|
+
});
|
|
1887
|
+
});
|
|
1888
|
+
});
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
beforeEach(() => {
|
|
1893
|
+
cy.mount(
|
|
1894
|
+
<div id="select-box-regression-fixture">
|
|
1895
|
+
<UDExSelectBox label="Languages" mode={UDExMode}>
|
|
1896
|
+
<UDExSelectBoxItem label="Alpha" supportingText="First choice" additionalText="Code A">
|
|
1897
|
+
{UDExLayout.slots.map(UDExSlot => (
|
|
1898
|
+
<UDExButton slot={UDExSlot} accessibleName={UDExActionName(UDExSlot)} design="Tertiary"
|
|
1899
|
+
icon={UDExSlot === "firstIcon" ? "action" : "email"} size="Small"></UDExButton>
|
|
1900
|
+
))}
|
|
1901
|
+
</UDExSelectBoxItem>
|
|
1902
|
+
<UDExSelectBoxItem label="Beta"></UDExSelectBoxItem>
|
|
1903
|
+
<UDExSelectBoxItem label="Unavailable" disabled></UDExSelectBoxItem>
|
|
1904
|
+
</UDExSelectBox>
|
|
1905
|
+
</div>
|
|
1906
|
+
);
|
|
1907
|
+
cy.get("#select-box-regression-fixture").then($fixture => {
|
|
1908
|
+
UDExLayout.slots.forEach(UDExSlot => {
|
|
1909
|
+
const UDExClickSnapshot = cy.stub().as(`${UDExSlot}ClickSnapshot`);
|
|
1910
|
+
// Preserve the existing consumer contract: actions stop their original click, not row handlers.
|
|
1911
|
+
const UDExHandler = cy.stub().callsFake((UDExEvent: CustomEvent<UDExSelectBoxIconClickEventDetail>) => {
|
|
1912
|
+
UDExClickSnapshot({
|
|
1913
|
+
event: UDExEvent.detail.originalEvent,
|
|
1914
|
+
target: UDExEvent.detail.originalEvent.target,
|
|
1915
|
+
});
|
|
1916
|
+
UDExEvent.detail.originalEvent.stopPropagation();
|
|
1917
|
+
}).as(UDExSlot);
|
|
1918
|
+
$fixture[0].addEventListener(UDExEventName(UDExSlot), UDExHandler);
|
|
1919
|
+
});
|
|
1920
|
+
});
|
|
1921
|
+
UDExCombobox().then($input => {
|
|
1922
|
+
$input[0].addEventListener("blur", cy.stub().as("actionOpeningBlur"));
|
|
1923
|
+
});
|
|
1924
|
+
UDExOpenSelectBox(UDExLayouts[0]);
|
|
1925
|
+
UDExAssertOpenWithActiveDescendant(UDExLeafHost("Alpha"));
|
|
1926
|
+
UDExAssertHostPosition(UDExLeafHost("Alpha"), 1, 3, 1);
|
|
1927
|
+
cy.get("@actionOpeningBlur").should("not.have.been.called");
|
|
1928
|
+
});
|
|
1929
|
+
|
|
1930
|
+
// Updated: actionable native treeitems use Boolean selection without a parallel checked state.
|
|
1931
|
+
it("exposes every leaf as a named level-1 treeitem and keeps focusable actions outside option semantics", () => {
|
|
1932
|
+
UDExCombobox().should("have.attr", "aria-haspopup", "tree");
|
|
1933
|
+
UDExListHost().should($host => {
|
|
1934
|
+
expect($host).to.have.attr("role", "tree");
|
|
1935
|
+
expect($host).to.have.attr("aria-label", "Languages");
|
|
1936
|
+
if (UDExMode === UDExListMode.MultiSelect) {
|
|
1937
|
+
expect($host).to.have.attr("aria-multiselectable", "true");
|
|
1938
|
+
} else {
|
|
1939
|
+
expect($host).not.to.have.attr("aria-multiselectable");
|
|
1940
|
+
}
|
|
1941
|
+
});
|
|
1942
|
+
UDExListHost().shadow().find('[role="tree"], [role="listbox"]').should("not.exist");
|
|
1943
|
+
UDExListHost().find('[role="option"], [udex-list-group-header]').should("not.exist");
|
|
1944
|
+
UDExListHost().find("[udex-list-item]").should("have.length", 3).each(($leaf, UDExIndex) => {
|
|
1945
|
+
UDExAssertHostPosition(cy.wrap($leaf), UDExIndex + 1, 3, 1);
|
|
1946
|
+
cy.wrap($leaf).should("not.have.attr", "aria-expanded");
|
|
1947
|
+
cy.wrap($leaf).should($host => {
|
|
1948
|
+
if (UDExMode === UDExListMode.SingleContent) {
|
|
1949
|
+
expect($host).not.to.have.attr("aria-selected");
|
|
1950
|
+
expect($host).not.to.have.attr("aria-checked");
|
|
1951
|
+
} else {
|
|
1952
|
+
expect($host).to.have.attr("aria-selected", "false");
|
|
1953
|
+
expect($host).not.to.have.attr("aria-checked");
|
|
1954
|
+
}
|
|
1955
|
+
});
|
|
1956
|
+
UDExAssertPresentationItem(cy.wrap($leaf));
|
|
1957
|
+
});
|
|
1958
|
+
UDExAssertLeafName(UDExLeafHost("Alpha"), "Alpha", "First choice", "Code A");
|
|
1959
|
+
UDExAssertLeafName(UDExLeafHost("Beta"), "Beta");
|
|
1960
|
+
UDExAssertLeafName(UDExLeafHost("Unavailable"), "Unavailable");
|
|
1961
|
+
UDExLeafHost("Unavailable").should("have.attr", "aria-disabled", "true");
|
|
1962
|
+
UDExLayout.slots.forEach(UDExSlot => {
|
|
1963
|
+
UDExAction(UDExSlot).should($button => {
|
|
1964
|
+
expect($button).to.have.length(1);
|
|
1965
|
+
expect($button).not.to.have.attr("disabled");
|
|
1966
|
+
// Walk slots and shadow boundaries too: closest() alone misses presentational option children.
|
|
1967
|
+
let UDExNode: HTMLElement | null = $button[0];
|
|
1968
|
+
while (UDExNode) {
|
|
1969
|
+
expect(UDExNode.getAttribute("role"), "no composed option ancestor").not.to.equal("option");
|
|
1970
|
+
expect(UDExNode.getAttribute("aria-hidden"), "action is not hidden").not.to.equal("true");
|
|
1971
|
+
expect(UDExNode.hasAttribute("inert"), "action is not inert").to.equal(false);
|
|
1972
|
+
UDExNode = UDExNode.assignedSlot || UDExNode.parentElement
|
|
1973
|
+
|| ((UDExNode.getRootNode() as ShadowRoot).host as HTMLElement | undefined) || null;
|
|
1974
|
+
}
|
|
1975
|
+
});
|
|
1976
|
+
UDExAction(UDExSlot).shadow().find("button").should("not.be.disabled").should($button => {
|
|
1977
|
+
expect($button).to.have.attr("aria-label", UDExActionName(UDExSlot));
|
|
1978
|
+
expect($button).not.to.have.attr("aria-hidden", "true");
|
|
1979
|
+
expect($button).not.to.have.attr("aria-disabled", "true");
|
|
1980
|
+
expect(($button[0] as HTMLButtonElement).tabIndex).to.be.at.least(0);
|
|
1981
|
+
});
|
|
1982
|
+
});
|
|
1983
|
+
UDExAssertActiveDescendant(UDExLeafHost("Alpha"));
|
|
1984
|
+
cy.get("@actionOpeningBlur").should("not.have.been.called");
|
|
1985
|
+
});
|
|
1986
|
+
|
|
1987
|
+
it("preserves pointer action events with the original source item and click payload", () => {
|
|
1988
|
+
UDExLayout.slots.forEach(UDExSlot => {
|
|
1989
|
+
UDExAction(UDExSlot).realClick({ scrollBehavior: false });
|
|
1990
|
+
UDExAssertActionEvent(UDExSlot, 1);
|
|
1991
|
+
});
|
|
1992
|
+
UDExLayout.slots.forEach(UDExSlot => cy.get(`@${UDExSlot}`).should("have.been.calledOnce"));
|
|
1993
|
+
UDExPopover().should("have.attr", "open");
|
|
1994
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
1995
|
+
UDExSourceLeaf("Alpha").should("not.have.attr", "selected");
|
|
1996
|
+
UDExAssertPresentationItem(UDExLeafHost("Alpha"));
|
|
1997
|
+
});
|
|
1998
|
+
|
|
1999
|
+
// Updated: returning from an action restores native positions with an empty live status.
|
|
2000
|
+
it("preserves Enter and Space action activation and the existing real-icon focus path", () => {
|
|
2001
|
+
if (UDExLayout.slots[0] === "firstIcon") {
|
|
2002
|
+
cy.realPress("ArrowRight");
|
|
2003
|
+
} else {
|
|
2004
|
+
// Second-only ArrowRight entry is unchanged and reviewed separately; test the focusable action itself.
|
|
2005
|
+
UDExAction("secondIcon").then($button => $button[0].focus());
|
|
2006
|
+
}
|
|
2007
|
+
UDExLayout.slots.forEach((UDExSlot, UDExIndex) => {
|
|
2008
|
+
if (UDExIndex > 0) {
|
|
2009
|
+
cy.realPress("ArrowRight");
|
|
2010
|
+
}
|
|
2011
|
+
UDExAction(UDExSlot).should("be.focused");
|
|
2012
|
+
UDExCombobox().should("not.be.focused");
|
|
2013
|
+
if (UDExLayout.slots[0] === "firstIcon") {
|
|
2014
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
2015
|
+
UDExListHost().find("[_active-descendant], ._udex-active-descendant").should("not.exist");
|
|
2016
|
+
}
|
|
2017
|
+
(["Enter", "Space"] as const).forEach((UDExKey, UDExKeyIndex) => {
|
|
2018
|
+
cy.realPress(UDExKey);
|
|
2019
|
+
UDExAssertActionEvent(UDExSlot, UDExKeyIndex + 1);
|
|
2020
|
+
UDExAction(UDExSlot).should("be.focused");
|
|
2021
|
+
UDExPopover().should("have.attr", "open");
|
|
2022
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
2023
|
+
});
|
|
2024
|
+
});
|
|
2025
|
+
UDExLayout.slots.forEach(UDExSlot => cy.get(`@${UDExSlot}`).should("have.callCount", 2));
|
|
2026
|
+
if (UDExLayout.slots[0] === "firstIcon") {
|
|
2027
|
+
if (UDExLayout.slots.length === 2) {
|
|
2028
|
+
cy.realPress("ArrowLeft");
|
|
2029
|
+
UDExAction("firstIcon").should("be.focused");
|
|
2030
|
+
}
|
|
2031
|
+
cy.realPress("ArrowLeft");
|
|
2032
|
+
UDExAssertNativeActiveDescendant(UDExLeafHost("Alpha"), 1, 3, 1);
|
|
2033
|
+
if (UDExMode === UDExListMode.SingleContent) {
|
|
2034
|
+
UDExLeafHost("Alpha").should("not.have.attr", "aria-selected");
|
|
2035
|
+
} else {
|
|
2036
|
+
UDExAssertHostSelection(UDExLeafHost("Alpha"), false);
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
cy.realPress("Escape");
|
|
2040
|
+
UDExPopover().should("not.have.attr", "open");
|
|
2041
|
+
UDExCombobox().should("be.focused");
|
|
2042
|
+
UDExSourceLeaf("Alpha").should("not.have.attr", "selected");
|
|
2043
|
+
});
|
|
2044
|
+
});
|
|
2045
|
+
});
|
|
2046
|
+
});
|
|
2047
|
+
});
|
|
2048
|
+
|
|
2049
|
+
describe("SelectBox first keyboard opening and outside-background dismissal", () => {
|
|
2050
|
+
UDExLayouts.forEach(UDExLayout => {
|
|
2051
|
+
// Updated: expanded headers open silently; other hosts retain the fallback on first opening and native-Tab reopening.
|
|
2052
|
+
it(`activates the first navigable ${UDExLayout.name} item on keyboard opening, including after an outside click and native Tab return`, () => {
|
|
2053
|
+
const UDExItems = [
|
|
2054
|
+
<UDExSelectBoxItem label="Alpha"></UDExSelectBoxItem>,
|
|
2055
|
+
<UDExSelectBoxItem label="Beta"></UDExSelectBoxItem>,
|
|
2056
|
+
<UDExSelectBoxItem label="Unavailable" disabled></UDExSelectBoxItem>,
|
|
2057
|
+
];
|
|
2058
|
+
cy.mount(
|
|
2059
|
+
<div id="select-box-regression-fixture">
|
|
2060
|
+
<button id="before-select-box">Previous</button>
|
|
2061
|
+
<div id="select-box-outside-background">Outside dropdown</div>
|
|
2062
|
+
<UDExSelectBox label="Languages" mode={UDExListMode.SingleSelect}>
|
|
2063
|
+
{UDExLayout.grouped ? (
|
|
2064
|
+
<UDExSelectBoxGroupHeader label="Languages group" collapsible={UDExLayout.collapsible}>
|
|
2065
|
+
{UDExItems}
|
|
2066
|
+
</UDExSelectBoxGroupHeader>
|
|
2067
|
+
) : UDExItems}
|
|
2068
|
+
</UDExSelectBox>
|
|
2069
|
+
</div>
|
|
2070
|
+
);
|
|
2071
|
+
cy.get("#select-box-regression-fixture").then($fixture => {
|
|
2072
|
+
$fixture[0].addEventListener("selection-change", cy.stub().as("selectionChange"));
|
|
2073
|
+
});
|
|
2074
|
+
cy.get("[udex-select-box]").then($host => {
|
|
2075
|
+
$host[0].addEventListener("close", cy.stub().as("outsideDropdownClosed"));
|
|
2076
|
+
});
|
|
2077
|
+
UDExPopover().then($popover => {
|
|
2078
|
+
$popover[0].addEventListener("ui5-open", cy.stub().as("firstKeyboardPopoverOpened"));
|
|
2079
|
+
});
|
|
2080
|
+
UDExCombobox().then($input => {
|
|
2081
|
+
$input[0].addEventListener("blur", cy.stub().as("comboboxBlur"));
|
|
2082
|
+
});
|
|
2083
|
+
const UDExAssertNativeInputFocus = () => UDExCombobox().should("be.focused").should($input => {
|
|
2084
|
+
const UDExRoot = $input[0].getRootNode() as ShadowRoot;
|
|
2085
|
+
expect($input[0].ownerDocument.activeElement).to.equal(UDExRoot.host);
|
|
2086
|
+
expect(UDExRoot.activeElement).to.equal($input[0]);
|
|
2087
|
+
});
|
|
2088
|
+
cy.get("#before-select-box").realClick();
|
|
2089
|
+
cy.realPress("Tab");
|
|
2090
|
+
|
|
2091
|
+
[1, 2].forEach(UDExCycle => {
|
|
2092
|
+
UDExAssertNativeInputFocus();
|
|
2093
|
+
UDExPopover().should("not.have.attr", "open");
|
|
2094
|
+
UDExCombobox().should("have.attr", "aria-expanded", "false");
|
|
2095
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
2096
|
+
UDExListHost().find("[_active-descendant]").should("not.exist");
|
|
2097
|
+
UDExAssertSupplementalStatus("");
|
|
2098
|
+
|
|
2099
|
+
cy.realPress("ArrowDown");
|
|
2100
|
+
cy.get("@firstKeyboardPopoverOpened").should("have.callCount", UDExCycle);
|
|
2101
|
+
UDExAssertOpenWithActiveDescendant(UDExLayout.collapsible ? UDExHeaderHost() : UDExLeafHost("Alpha"),
|
|
2102
|
+
UDExLayout.collapsible ? "" : "expanded");
|
|
2103
|
+
UDExAssertHostPosition(UDExLayout.collapsible ? UDExHeaderHost() : UDExLeafHost("Alpha"), 1,
|
|
2104
|
+
UDExLayout.collapsible ? 1 : 3, UDExLayout.grouped ? UDExLayout.collapsible ? 1 : 2 : undefined);
|
|
2105
|
+
UDExAssertNativeInputFocus();
|
|
2106
|
+
cy.get("@comboboxBlur").should("have.callCount", UDExCycle - 1);
|
|
2107
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
2108
|
+
|
|
2109
|
+
cy.realPress("ArrowDown");
|
|
2110
|
+
UDExAssertNativeActiveDescendant(UDExLeafHost(UDExLayout.collapsible ? "Alpha" : "Beta"),
|
|
2111
|
+
UDExLayout.collapsible ? 1 : 2, 3, UDExLayout.grouped ? 2 : undefined);
|
|
2112
|
+
UDExAssertHostSelection(UDExLeafHost(UDExLayout.collapsible ? "Alpha" : "Beta"), false);
|
|
2113
|
+
UDExListHost().find("[_active-descendant]").should("have.length", 1);
|
|
2114
|
+
UDExAssertNativeInputFocus();
|
|
2115
|
+
cy.realPress("End");
|
|
2116
|
+
UDExAssertNativeActiveDescendant(UDExLeafHost("Beta"), 2, 3, UDExLayout.grouped ? 2 : undefined);
|
|
2117
|
+
UDExAssertHostSelection(UDExLeafHost("Beta"), false);
|
|
2118
|
+
UDExAssertNativeInputFocus();
|
|
2119
|
+
cy.get("@comboboxBlur").should("have.callCount", UDExCycle - 1);
|
|
2120
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
2121
|
+
cy.get("[udex-select-box]").find("[udex-select-box-item][selected]").should("not.exist");
|
|
2122
|
+
|
|
2123
|
+
if (UDExCycle === 1) {
|
|
2124
|
+
// This nonfocusable background sits before the input in sequential focus order.
|
|
2125
|
+
cy.get("#select-box-outside-background").realClick();
|
|
2126
|
+
cy.get("@outsideDropdownClosed").should("have.been.calledOnce");
|
|
2127
|
+
UDExPopover().should("not.have.attr", "open");
|
|
2128
|
+
UDExCombobox().should("have.attr", "aria-expanded", "false");
|
|
2129
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
2130
|
+
UDExListHost().find("[_active-descendant]").should("not.exist");
|
|
2131
|
+
UDExAssertSupplementalStatus("");
|
|
2132
|
+
UDExCombobox().should("not.be.focused").should($input => {
|
|
2133
|
+
expect($input[0].ownerDocument.activeElement).to.equal($input[0].ownerDocument.body);
|
|
2134
|
+
expect(($input[0].getRootNode() as ShadowRoot).activeElement).to.equal(null);
|
|
2135
|
+
});
|
|
2136
|
+
cy.get("@comboboxBlur").should("have.been.calledOnce");
|
|
2137
|
+
// Do not synthesize focus or click the combobox: Tab must restore native keyboard entry.
|
|
2138
|
+
cy.realPress("Tab");
|
|
2139
|
+
}
|
|
2140
|
+
});
|
|
2141
|
+
});
|
|
2142
|
+
});
|
|
2143
|
+
});
|
|
2144
|
+
|
|
2145
|
+
describe("SelectBox native navigation without redundant live metadata", () => {
|
|
2146
|
+
UDExLayouts.forEach(UDExLayout => {
|
|
2147
|
+
// Updated: a selected child keeps the expanded popup fallback even when its parent is auto-expanded.
|
|
2148
|
+
it(`restores the selected SingleSelect ${UDExLayout.name} child immediately on each keyboard opening${UDExLayout.collapsible ? " after expanding its collapsed group" : ""}`, () => {
|
|
2149
|
+
const UDExItems = [
|
|
2150
|
+
<UDExSelectBoxItem label="Alpha" supportingText="First choice"></UDExSelectBoxItem>,
|
|
2151
|
+
<UDExSelectBoxItem label="Beta" supportingText="Second choice" selected></UDExSelectBoxItem>,
|
|
2152
|
+
<UDExSelectBoxItem label="Unavailable" disabled></UDExSelectBoxItem>,
|
|
2153
|
+
];
|
|
2154
|
+
cy.mount(
|
|
2155
|
+
<div id="select-box-regression-fixture">
|
|
2156
|
+
<button id="before-select-box">Previous</button>
|
|
2157
|
+
<UDExSelectBox label="Languages" mode={UDExListMode.SingleSelect}>
|
|
2158
|
+
{UDExLayout.grouped ? (
|
|
2159
|
+
<UDExSelectBoxGroupHeader label="Languages group" supportingText="Choose a language"
|
|
2160
|
+
collapsible={UDExLayout.collapsible} collapsed={UDExLayout.collapsible}>
|
|
2161
|
+
{UDExItems}
|
|
2162
|
+
</UDExSelectBoxGroupHeader>
|
|
2163
|
+
) : UDExItems}
|
|
2164
|
+
</UDExSelectBox>
|
|
2165
|
+
</div>
|
|
2166
|
+
);
|
|
2167
|
+
cy.get("#select-box-regression-fixture").then($fixture => {
|
|
2168
|
+
$fixture[0].addEventListener("selection-change", cy.stub().as("selectionChange"));
|
|
2169
|
+
});
|
|
2170
|
+
UDExPopover().then($popover => {
|
|
2171
|
+
$popover[0].addEventListener("ui5-open", cy.stub().as("selectedPopoverOpened"));
|
|
2172
|
+
});
|
|
2173
|
+
cy.get("[udex-select-box]").then($selectBox => {
|
|
2174
|
+
$selectBox[0].addEventListener("close", cy.stub().as("selectedDropdownClosed"));
|
|
2175
|
+
});
|
|
2176
|
+
UDExCombobox().then($input => {
|
|
2177
|
+
$input[0].addEventListener("blur", cy.stub().as("comboboxBlur"));
|
|
2178
|
+
});
|
|
2179
|
+
cy.get("#before-select-box").realClick();
|
|
2180
|
+
cy.realPress("Tab");
|
|
2181
|
+
UDExCombobox().should("be.focused");
|
|
2182
|
+
|
|
2183
|
+
[1, 2].forEach(UDExCycle => {
|
|
2184
|
+
UDExPopover().should("not.have.attr", "open");
|
|
2185
|
+
UDExCombobox().should("have.attr", "aria-expanded", "false");
|
|
2186
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
2187
|
+
UDExAssertSupplementalStatus("");
|
|
2188
|
+
if (UDExLayout.collapsible) {
|
|
2189
|
+
cy.get("[udex-select-box]").find("[udex-select-box-group-header]").should("have.attr", "collapsed");
|
|
2190
|
+
UDExHeaderHost().should("have.attr", "aria-expanded", "false");
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
cy.realPress("ArrowDown");
|
|
2194
|
+
cy.get("@selectedPopoverOpened").should("have.callCount", UDExCycle);
|
|
2195
|
+
UDExAssertOpenWithActiveDescendant(UDExLeafHost("Beta"), "expanded");
|
|
2196
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
2197
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
2198
|
+
if (UDExLayout.collapsible) {
|
|
2199
|
+
UDExHeaderHost().should("have.attr", "aria-expanded", "true");
|
|
2200
|
+
cy.get("[udex-select-box]").find("[udex-select-box-group-header]").should("not.have.attr", "collapsed");
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
UDExAssertHostPosition(UDExLeafHost("Beta"), 2, 3, UDExLayout.grouped ? 2 : undefined);
|
|
2204
|
+
UDExListHost().find("[_active-descendant]").should("have.length", 1);
|
|
2205
|
+
UDExLeafHost("Alpha").should("not.have.attr", "_active-descendant");
|
|
2206
|
+
UDExLeafHost("Beta").should("have.attr", "aria-selected", "true");
|
|
2207
|
+
UDExSourceLeaf("Beta").should("have.attr", "selected");
|
|
2208
|
+
UDExSourceLeaf("Alpha").should("not.have.attr", "selected");
|
|
2209
|
+
UDExSourceLeaf("Unavailable").should("not.have.attr", "selected");
|
|
2210
|
+
UDExAssertLeafName(UDExLeafHost("Beta"), "Beta", "Second choice");
|
|
2211
|
+
UDExAssertPresentationItem(UDExLeafHost("Beta"));
|
|
2212
|
+
|
|
2213
|
+
// Skip the disabled final leaf and wrap to the next navigable host, not Beta again.
|
|
2214
|
+
cy.realPress("ArrowDown");
|
|
2215
|
+
UDExAssertNativeActiveDescendant(UDExLayout.collapsible ? UDExHeaderHost() : UDExLeafHost("Alpha"), 1,
|
|
2216
|
+
UDExLayout.collapsible ? 1 : 3, UDExLayout.grouped ? UDExLayout.collapsible ? 1 : 2 : undefined);
|
|
2217
|
+
UDExAssertHostSelection(UDExLayout.collapsible ? UDExHeaderHost() : UDExLeafHost("Alpha"), false);
|
|
2218
|
+
|
|
2219
|
+
if (UDExLayout.collapsible) {
|
|
2220
|
+
// Initial activation must wait until the selected child's group has rendered expanded.
|
|
2221
|
+
UDExHeaderHost().should("have.attr", "aria-expanded", "true");
|
|
2222
|
+
cy.get("[udex-select-box]").find("[udex-select-box-group-header]").should("not.have.attr", "collapsed");
|
|
2223
|
+
UDExHeaderHost().find("[udex-list-item]").each($item => {
|
|
2224
|
+
cy.wrap($item).should("not.have.attr", "collapsed");
|
|
2225
|
+
});
|
|
2226
|
+
cy.realPress("Home");
|
|
2227
|
+
UDExAssertNativeActiveDescendant(UDExHeaderHost(), 1, 1, 1);
|
|
2228
|
+
cy.realPress("ArrowLeft");
|
|
2229
|
+
UDExAssertNativeActiveDescendant(UDExHeaderHost(), 1, 1, 1);
|
|
2230
|
+
UDExHeaderHost().should("have.attr", "aria-expanded", "false");
|
|
2231
|
+
cy.get("[udex-select-box]").find("[udex-select-box-group-header]").should("have.attr", "collapsed");
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
cy.realPress("Escape");
|
|
2235
|
+
cy.get("@selectedDropdownClosed").should("have.callCount", UDExCycle);
|
|
2236
|
+
UDExPopover().should("not.have.attr", "open");
|
|
2237
|
+
UDExCombobox().should("have.attr", "aria-expanded", "false");
|
|
2238
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
2239
|
+
UDExListHost().find("[_active-descendant]").should("not.exist");
|
|
2240
|
+
UDExAssertSupplementalStatus("");
|
|
2241
|
+
UDExCombobox().should("be.focused");
|
|
2242
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
2243
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
2244
|
+
});
|
|
2245
|
+
});
|
|
2246
|
+
});
|
|
2247
|
+
|
|
2248
|
+
// Updated: native hosts alone expose per-level positions and states; live navigation is empty.
|
|
2249
|
+
it("navigates two country groups with native names and exact per-level positions without live metadata", () => {
|
|
2250
|
+
UDExMountCountrySelectBox();
|
|
2251
|
+
UDExCombobox().should("have.attr", "aria-label", "Country");
|
|
2252
|
+
UDExCombobox().should("have.attr", "aria-haspopup", "tree");
|
|
2253
|
+
UDExListHost().should("have.attr", "role", "tree");
|
|
2254
|
+
UDExListHost().then($list => {
|
|
2255
|
+
UDExCombobox().should("have.attr", "aria-controls", $list[0].id);
|
|
2256
|
+
});
|
|
2257
|
+
UDExHeaderHost().should("have.length", 2);
|
|
2258
|
+
UDExListHost().find("[udex-list-item]").should("have.length", 6);
|
|
2259
|
+
|
|
2260
|
+
UDExCountryGroups.forEach((UDExGroup, UDExGroupIndex) => {
|
|
2261
|
+
if (UDExGroupIndex > 0) {
|
|
2262
|
+
cy.realPress("ArrowDown");
|
|
2263
|
+
UDExAssertNativeActiveDescendant(UDExHeaderHost().eq(UDExGroupIndex), UDExGroupIndex + 1, 2, 1);
|
|
2264
|
+
} else {
|
|
2265
|
+
// Updated: the expanded first header is active from opening with no live expansion duplicate.
|
|
2266
|
+
UDExAssertOpenWithActiveDescendant(UDExHeaderHost().eq(UDExGroupIndex), "");
|
|
2267
|
+
}
|
|
2268
|
+
UDExAssertHostPosition(UDExHeaderHost().eq(UDExGroupIndex), UDExGroupIndex + 1, 2, 1);
|
|
2269
|
+
UDExHeaderHost().eq(UDExGroupIndex).should("have.attr", "aria-label", `${UDExGroup.label} ${UDExGroup.supportingText}`);
|
|
2270
|
+
UDExHeaderHost().eq(UDExGroupIndex).should("have.attr", "aria-expanded", "true");
|
|
2271
|
+
UDExHeaderHost().eq(UDExGroupIndex).should("have.attr", "aria-selected", "false");
|
|
2272
|
+
|
|
2273
|
+
UDExGroup.countries.forEach((UDExCountry, UDExChildIndex) => {
|
|
2274
|
+
cy.realPress("ArrowDown");
|
|
2275
|
+
UDExAssertNativeActiveDescendant(UDExLeafHost(UDExCountry), UDExChildIndex + 1, 3, 2);
|
|
2276
|
+
UDExAssertLeafName(UDExLeafHost(UDExCountry), UDExCountry);
|
|
2277
|
+
UDExLeafHost(UDExCountry).should("have.attr", "aria-selected", "false");
|
|
2278
|
+
UDExAssertPresentationItem(UDExLeafHost(UDExCountry));
|
|
2279
|
+
});
|
|
2280
|
+
});
|
|
2281
|
+
|
|
2282
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
2283
|
+
cy.get("[udex-select-box]").find("[udex-select-box-item][selected]").should("not.exist");
|
|
2284
|
+
cy.realPress("Enter");
|
|
2285
|
+
UDExSourceLeaf("China").should("have.attr", "selected");
|
|
2286
|
+
UDExLeafHost("China").should("have.attr", "aria-selected", "true");
|
|
2287
|
+
UDExAssertSelectionEvent("China", ["China"]);
|
|
2288
|
+
UDExPopover().should("not.have.attr", "open");
|
|
2289
|
+
UDExCombobox().should("be.focused");
|
|
2290
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
2291
|
+
UDExAssertSelectionStatus("China", "Selected");
|
|
2292
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
2293
|
+
});
|
|
2294
|
+
|
|
2295
|
+
// Updated: root and child positions are exposed only by one-based native host attributes.
|
|
2296
|
+
it("counts top-level leaves and a group in native root positions while keeping children at level 2 and navigation silent", () => {
|
|
2297
|
+
cy.mount(
|
|
2298
|
+
<div id="select-box-regression-fixture">
|
|
2299
|
+
<UDExSelectBox label="Country">
|
|
2300
|
+
<UDExSelectBoxItem label="Worldwide"></UDExSelectBoxItem>
|
|
2301
|
+
<UDExSelectBoxGroupHeader label="Europe" collapsible>
|
|
2302
|
+
<UDExSelectBoxItem label="Germany"></UDExSelectBoxItem>
|
|
2303
|
+
<UDExSelectBoxItem label="France"></UDExSelectBoxItem>
|
|
2304
|
+
</UDExSelectBoxGroupHeader>
|
|
2305
|
+
<UDExSelectBoxItem label="Other countries"></UDExSelectBoxItem>
|
|
2306
|
+
</UDExSelectBox>
|
|
2307
|
+
</div>
|
|
2308
|
+
);
|
|
2309
|
+
UDExOpenSelectBox(UDExLayouts[2]);
|
|
2310
|
+
UDExAssertOpenWithActiveDescendant(UDExLeafHost("Worldwide"));
|
|
2311
|
+
UDExAssertHostPosition(UDExLeafHost("Worldwide"), 1, 3, 1);
|
|
2312
|
+
UDExAssertLeafName(UDExLeafHost("Worldwide"), "Worldwide");
|
|
2313
|
+
UDExAssertHostSelection(UDExLeafHost("Worldwide"), false);
|
|
2314
|
+
cy.realPress("ArrowDown");
|
|
2315
|
+
UDExAssertNativeActiveDescendant(UDExHeaderHost(), 2, 3, 1);
|
|
2316
|
+
UDExHeaderHost().should("have.attr", "aria-label", "Europe");
|
|
2317
|
+
UDExHeaderHost().should("have.attr", "aria-expanded", "true");
|
|
2318
|
+
cy.realPress("ArrowDown");
|
|
2319
|
+
UDExAssertNativeActiveDescendant(UDExLeafHost("Germany"), 1, 2, 2);
|
|
2320
|
+
UDExAssertHostSelection(UDExLeafHost("Germany"), false);
|
|
2321
|
+
cy.realPress("End");
|
|
2322
|
+
UDExAssertNativeActiveDescendant(UDExLeafHost("Other countries"), 3, 3, 1);
|
|
2323
|
+
UDExAssertLeafName(UDExLeafHost("Other countries"), "Other countries");
|
|
2324
|
+
UDExAssertHostSelection(UDExLeafHost("Other countries"), false);
|
|
2325
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
2326
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
2327
|
+
});
|
|
2328
|
+
|
|
2329
|
+
// Updated: navigation clears search counts while retaining the complete native highlighted group name.
|
|
2330
|
+
it("preserves the space-joined highlighted group name with native positions and silent navigation", () => {
|
|
2331
|
+
UDExMountCountrySelectBox();
|
|
2332
|
+
UDExHeaderHost().first().should("have.attr", "aria-label", "Europe Available countries");
|
|
2333
|
+
cy.realType("Europe");
|
|
2334
|
+
UDExCombobox().should("have.value", "Europe");
|
|
2335
|
+
UDExHeaderHost().should("have.length", 1);
|
|
2336
|
+
UDExHeaderHost().should("have.attr", "aria-label", "Europe Available countries");
|
|
2337
|
+
UDExHeaderHost().should("have.attr", "supporting-text", "Available countries");
|
|
2338
|
+
UDExHeaderHost().find("[udex-list-item]").should("have.length", 3);
|
|
2339
|
+
UDExAssertHostPosition(UDExHeaderHost(), 1, 1, 1);
|
|
2340
|
+
UDExStatus().should("have.length", 1).and("have.text", "4 results returned.");
|
|
2341
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
2342
|
+
UDExListHost().find("[_active-descendant]").should("not.exist");
|
|
2343
|
+
|
|
2344
|
+
cy.realPress("ArrowDown");
|
|
2345
|
+
UDExAssertNativeActiveDescendant(UDExHeaderHost(), 1, 1, 1);
|
|
2346
|
+
UDExHeaderHost().should("have.attr", "aria-expanded", "true");
|
|
2347
|
+
UDExAssertHostSelection(UDExHeaderHost(), false);
|
|
2348
|
+
UDExHeaderHost().should("have.attr", "aria-label", "Europe Available countries");
|
|
2349
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
2350
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
2351
|
+
});
|
|
2352
|
+
|
|
2353
|
+
// Updated: navigation clears result counts; filtered per-level positions and selection remain native.
|
|
2354
|
+
it("retains filtering counts and recomputes native group and child positions with silent navigation", () => {
|
|
2355
|
+
UDExMountCountrySelectBox();
|
|
2356
|
+
cy.realType("an");
|
|
2357
|
+
UDExCombobox().should("have.value", "an");
|
|
2358
|
+
// Existing search counts include each matching group header as well as its enabled children.
|
|
2359
|
+
UDExStatus().should("have.length", 1).and("have.text", "6 results returned.");
|
|
2360
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
2361
|
+
UDExListHost().find("[_active-descendant]").should("not.exist");
|
|
2362
|
+
UDExHeaderHost().should("have.length", 2).each(($header, UDExGroupIndex) => {
|
|
2363
|
+
UDExAssertHostPosition(cy.wrap($header), UDExGroupIndex + 1, 2, 1);
|
|
2364
|
+
cy.wrap($header).find("[udex-list-item]").should("have.length", 2).each(($child, UDExChildIndex) => {
|
|
2365
|
+
UDExAssertHostPosition(cy.wrap($child), UDExChildIndex + 1, 2, 2);
|
|
2366
|
+
const UDExLabel = UDExGroupIndex === 0 ? ["Germany", "France"][UDExChildIndex] : ["Japan", "Jordan"][UDExChildIndex];
|
|
2367
|
+
UDExAssertLeafName(cy.wrap($child), UDExLabel);
|
|
2368
|
+
cy.wrap($child).should("have.attr", "aria-selected", "false");
|
|
2369
|
+
});
|
|
2370
|
+
});
|
|
2371
|
+
cy.realPress("ArrowDown");
|
|
2372
|
+
UDExAssertNativeActiveDescendant(UDExHeaderHost().first(), 1, 2, 1);
|
|
2373
|
+
UDExHeaderHost().first().should("have.attr", "aria-expanded", "true");
|
|
2374
|
+
UDExAssertHostSelection(UDExHeaderHost().first(), false);
|
|
2375
|
+
cy.realPress("ArrowDown");
|
|
2376
|
+
UDExAssertNativeActiveDescendant(UDExHeaderHost().first().find("[udex-list-item]").first(), 1, 2, 2);
|
|
2377
|
+
|
|
2378
|
+
UDExCombobox().clear();
|
|
2379
|
+
cy.realType("Japan");
|
|
2380
|
+
UDExStatus().should("have.text", "2 results returned.");
|
|
2381
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
2382
|
+
UDExListHost().find("[_active-descendant]").should("not.exist");
|
|
2383
|
+
UDExHeaderHost().should("have.length", 1);
|
|
2384
|
+
UDExHeaderHost().should("have.attr", "aria-label", "Asia More destinations");
|
|
2385
|
+
UDExAssertHostPosition(UDExHeaderHost(), 1, 1, 1);
|
|
2386
|
+
UDExHeaderHost().find("[udex-list-item]").should("have.length", 1);
|
|
2387
|
+
UDExAssertHostPosition(UDExHeaderHost().find("[udex-list-item]"), 1, 1, 2);
|
|
2388
|
+
UDExAssertLeafName(UDExHeaderHost().find("[udex-list-item]"), "Japan");
|
|
2389
|
+
cy.realPress("End");
|
|
2390
|
+
UDExAssertNativeActiveDescendant(UDExHeaderHost().find("[udex-list-item]"), 1, 1, 2);
|
|
2391
|
+
UDExAssertHostSelection(UDExHeaderHost().find("[udex-list-item]"), false);
|
|
2392
|
+
UDExCombobox().should("have.value", "Japan");
|
|
2393
|
+
cy.get("[udex-select-box]").find("[udex-select-box-item][selected]").should("not.exist");
|
|
2394
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
2395
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
2396
|
+
});
|
|
2397
|
+
|
|
2398
|
+
// Updated: filtered opening preserves exact counts; navigation clears them and retains native group metadata.
|
|
2399
|
+
it("announces only search results when typing opens a closed dropdown, then updates counts on further input", () => {
|
|
2400
|
+
UDExMountCountrySelectBox();
|
|
2401
|
+
cy.realPress("Escape");
|
|
2402
|
+
UDExPopover().should("not.have.attr", "open");
|
|
2403
|
+
// The negative attribute assertion yields no attribute value; requery the element.
|
|
2404
|
+
UDExPopover().then($popover => {
|
|
2405
|
+
$popover[0].addEventListener("ui5-open", cy.stub().as("filteredPopoverOpened"), { once: true });
|
|
2406
|
+
});
|
|
2407
|
+
cy.realType("J");
|
|
2408
|
+
cy.get("@filteredPopoverOpened").should("have.been.calledOnce");
|
|
2409
|
+
UDExPopover().should("have.attr", "open");
|
|
2410
|
+
UDExAssertSupplementalStatus("3 results returned.");
|
|
2411
|
+
UDExCombobox().should("have.value", "J").and("be.focused");
|
|
2412
|
+
UDExCombobox().should("have.attr", "aria-expanded", "true");
|
|
2413
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
2414
|
+
UDExListHost().find("[_active-descendant]").should("not.exist");
|
|
2415
|
+
cy.realType("apan");
|
|
2416
|
+
UDExStatus().should("have.text", "2 results returned.");
|
|
2417
|
+
UDExCombobox().should("have.value", "Japan").and("be.focused");
|
|
2418
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
2419
|
+
UDExListHost().find("[_active-descendant]").should("not.exist");
|
|
2420
|
+
cy.realPress("ArrowDown");
|
|
2421
|
+
UDExAssertNativeActiveDescendant(UDExHeaderHost(), 1, 1, 1);
|
|
2422
|
+
UDExHeaderHost().should("have.attr", "aria-expanded", "true");
|
|
2423
|
+
UDExAssertHostSelection(UDExHeaderHost(), false);
|
|
2424
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
2425
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
2426
|
+
});
|
|
2427
|
+
|
|
2428
|
+
// Updated: silent grouped End navigation is followed by exact no-matches status and cleared active identity.
|
|
2429
|
+
it("reports no matches after silent grouped navigation without a stale active descendant or selection", () => {
|
|
2430
|
+
UDExMountCountrySelectBox();
|
|
2431
|
+
cy.realPress("End");
|
|
2432
|
+
UDExAssertNativeActiveDescendant(UDExLeafHost("China"), 3, 3, 2);
|
|
2433
|
+
UDExAssertHostSelection(UDExLeafHost("China"), false);
|
|
2434
|
+
cy.realType("zzznomatch");
|
|
2435
|
+
UDExCombobox().should("have.value", "zzznomatch");
|
|
2436
|
+
UDExStatus().should("have.length", 1).and("have.text", "No matches found.");
|
|
2437
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
2438
|
+
cy.get("[udex-select-box]").shadow().find("[_active-descendant], [udex-list-group-header], [udex-list-item]").should("not.exist");
|
|
2439
|
+
cy.get("[udex-select-box]").shadow().find(".udex-no-result").should("contain.text", "No matches found.");
|
|
2440
|
+
cy.realPress("ArrowDown");
|
|
2441
|
+
UDExStatus().should("have.text", "No matches found.");
|
|
2442
|
+
UDExCombobox().should("be.focused");
|
|
2443
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
2444
|
+
UDExPopover().should("have.attr", "open");
|
|
2445
|
+
cy.get("[udex-select-box]").find("[udex-select-box-item][selected]").should("not.exist");
|
|
2446
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
2447
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
2448
|
+
});
|
|
2449
|
+
|
|
2450
|
+
(["Escape", "Tab"] as const).forEach(UDExExitKey => {
|
|
2451
|
+
// Updated: flat pointer opening announces expanded; subsequent footer navigation remains silent.
|
|
2452
|
+
it(`keeps footer return navigation on the combobox and native option hosts without echo before ${UDExExitKey} exits`, () => {
|
|
2453
|
+
UDExMountSelectBox(UDExListMode.SingleSelect, UDExLayouts[0], true);
|
|
2454
|
+
// Unlike selection-change, close does not bubble to the surrounding fixture.
|
|
2455
|
+
cy.get("[udex-select-box]").then($selectBox => {
|
|
2456
|
+
$selectBox[0].addEventListener("close", cy.stub().as("dropdownClosed"));
|
|
2457
|
+
});
|
|
2458
|
+
cy.realPress("End");
|
|
2459
|
+
UDExAssertSupplementalActiveDescendant(UDExLeafHost("Beta"), "");
|
|
2460
|
+
UDExAssertHostPosition(UDExLeafHost("Beta"), 2, 3);
|
|
2461
|
+
UDExAssertLeafName(UDExLeafHost("Beta"), "Beta");
|
|
2462
|
+
UDExLeafHost("Unavailable").should("have.attr", "aria-disabled", "true");
|
|
2463
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
2464
|
+
|
|
2465
|
+
cy.realPress("Tab");
|
|
2466
|
+
cy.get("#select-box-footer-action").should("be.focused");
|
|
2467
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
2468
|
+
UDExListHost().find("[_active-descendant]").should("not.exist");
|
|
2469
|
+
UDExStatus().should("have.text", "");
|
|
2470
|
+
UDExPopover().should("have.attr", "open");
|
|
2471
|
+
|
|
2472
|
+
cy.realPress(["Shift", "Tab"]);
|
|
2473
|
+
UDExAssertSupplementalActiveDescendant(UDExLeafHost("Beta"), "");
|
|
2474
|
+
UDExAssertHostPosition(UDExLeafHost("Beta"), 2, 3);
|
|
2475
|
+
UDExAssertPresentationItem(UDExLeafHost("Beta"));
|
|
2476
|
+
UDExLeafHost("Beta").should("not.be.focused");
|
|
2477
|
+
UDExLeafHost("Beta").should("have.attr", "aria-selected", "false");
|
|
2478
|
+
cy.realPress("ArrowUp");
|
|
2479
|
+
UDExAssertSupplementalActiveDescendant(UDExLeafHost("Alpha"), "");
|
|
2480
|
+
UDExAssertHostPosition(UDExLeafHost("Alpha"), 1, 3);
|
|
2481
|
+
UDExAssertLeafName(UDExLeafHost("Alpha"), "Alpha");
|
|
2482
|
+
cy.get("@comboboxBlur").should("have.been.calledOnce");
|
|
2483
|
+
|
|
2484
|
+
cy.realPress("Tab");
|
|
2485
|
+
cy.get("#select-box-footer-action").should("be.focused");
|
|
2486
|
+
cy.realPress(UDExExitKey);
|
|
2487
|
+
UDExPopover().should("not.have.attr", "open");
|
|
2488
|
+
UDExCombobox().should("have.attr", "aria-expanded", "false");
|
|
2489
|
+
UDExCombobox().should("have.attr", "aria-label", "Languages");
|
|
2490
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
2491
|
+
UDExListHost().find("[_active-descendant]").should("not.exist");
|
|
2492
|
+
UDExStatus().should("have.text", "");
|
|
2493
|
+
if (UDExExitKey === "Escape") {
|
|
2494
|
+
UDExCombobox().should("be.focused");
|
|
2495
|
+
} else {
|
|
2496
|
+
cy.get("#after-select-box").should("be.focused");
|
|
2497
|
+
}
|
|
2498
|
+
cy.get("@dropdownClosed").should("have.been.calledOnce");
|
|
2499
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
2500
|
+
cy.get("[udex-select-box]").find("[udex-select-box-item][selected]").should("not.exist");
|
|
2501
|
+
});
|
|
2502
|
+
});
|
|
2503
|
+
|
|
2504
|
+
// Updated: selection retains full-name feedback; native Boolean state and partial descriptions replace live metadata.
|
|
2505
|
+
it("separates MultiSelect selection feedback from silent navigation, including selection while collapsed", () => {
|
|
2506
|
+
UDExMountSelectBox(UDExListMode.MultiSelect, UDExLayouts[2]);
|
|
2507
|
+
UDExAssertOpenWithActiveDescendant(UDExHeaderHost(), "");
|
|
2508
|
+
UDExAssertHostPosition(UDExHeaderHost(), 1, 1, 1);
|
|
2509
|
+
UDExAssertHostSelection(UDExHeaderHost(), false);
|
|
2510
|
+
cy.realPress("ArrowDown");
|
|
2511
|
+
UDExAssertNativeActiveDescendant(UDExLeafHost("Alpha"), 1, 3, 2);
|
|
2512
|
+
UDExAssertHostSelection(UDExLeafHost("Alpha"), false);
|
|
2513
|
+
cy.realPress("Enter");
|
|
2514
|
+
UDExSourceLeaf("Alpha").should("have.attr", "selected");
|
|
2515
|
+
UDExSourceLeaf("Beta").should("not.have.attr", "selected");
|
|
2516
|
+
UDExAssertHostSelection(UDExLeafHost("Alpha"), true);
|
|
2517
|
+
UDExAssertHostSelection(UDExHeaderHost(), false, true);
|
|
2518
|
+
UDExAssertSelectionEvent("Alpha", ["Alpha"]);
|
|
2519
|
+
UDExAssertActiveDescendant(UDExLeafHost("Alpha"));
|
|
2520
|
+
UDExAssertSelectionStatus("Alpha", "Selected");
|
|
2521
|
+
|
|
2522
|
+
cy.realPress("Space");
|
|
2523
|
+
UDExSourceLeaf("Alpha").should("not.have.attr", "selected");
|
|
2524
|
+
UDExAssertHostSelection(UDExLeafHost("Alpha"), false);
|
|
2525
|
+
UDExAssertHostSelection(UDExHeaderHost(), false);
|
|
2526
|
+
UDExAssertSelectionEvent("Alpha", [], 2);
|
|
2527
|
+
UDExAssertActiveDescendant(UDExLeafHost("Alpha"));
|
|
2528
|
+
UDExAssertSelectionStatus("Alpha", "Unselected");
|
|
2529
|
+
cy.realPress("Home");
|
|
2530
|
+
UDExAssertNativeActiveDescendant(UDExHeaderHost(), 1, 1, 1);
|
|
2531
|
+
|
|
2532
|
+
cy.realPress("ArrowLeft");
|
|
2533
|
+
UDExHeaderHost().should("have.attr", "aria-expanded", "false");
|
|
2534
|
+
cy.get("[udex-select-box]").find("[udex-select-box-group-header]").should("have.attr", "collapsed");
|
|
2535
|
+
UDExHeaderHost().find("[udex-list-item]").each($item => {
|
|
2536
|
+
cy.wrap($item).should("have.attr", "collapsed");
|
|
2537
|
+
});
|
|
2538
|
+
UDExAssertNativeActiveDescendant(UDExHeaderHost(), 1, 1, 1);
|
|
2539
|
+
cy.get("@selectionChange").should("have.callCount", 2);
|
|
2540
|
+
|
|
2541
|
+
cy.realPress("Enter");
|
|
2542
|
+
UDExSourceLeaf("Alpha").should("have.attr", "selected");
|
|
2543
|
+
UDExSourceLeaf("Beta").should("have.attr", "selected");
|
|
2544
|
+
UDExSourceLeaf("Unavailable").should("not.have.attr", "selected");
|
|
2545
|
+
UDExAssertHostSelection(UDExHeaderHost(), false, true);
|
|
2546
|
+
UDExHeaderHost().should("have.attr", "aria-expanded", "false");
|
|
2547
|
+
UDExAssertHostSelection(UDExLeafHost("Alpha"), true);
|
|
2548
|
+
UDExAssertHostSelection(UDExLeafHost("Beta"), true);
|
|
2549
|
+
UDExAssertHostSelection(UDExLeafHost("Unavailable"), false);
|
|
2550
|
+
UDExLeafHost("Unavailable").should("have.attr", "aria-disabled", "true");
|
|
2551
|
+
UDExAssertSelectionEvent("Languages group", ["Alpha", "Beta"], 3);
|
|
2552
|
+
UDExAssertActiveDescendant(UDExHeaderHost());
|
|
2553
|
+
UDExAssertSelectionStatus("Languages group", "Indeterminate");
|
|
2554
|
+
UDExAssertHostPosition(UDExHeaderHost(), 1, 1, 1);
|
|
2555
|
+
|
|
2556
|
+
cy.realPress("Space");
|
|
2557
|
+
UDExSourceLeaf("Alpha").should("not.have.attr", "selected");
|
|
2558
|
+
UDExSourceLeaf("Beta").should("not.have.attr", "selected");
|
|
2559
|
+
UDExAssertHostSelection(UDExHeaderHost(), false);
|
|
2560
|
+
UDExHeaderHost().should("have.attr", "aria-expanded", "false");
|
|
2561
|
+
UDExAssertHostSelection(UDExLeafHost("Alpha"), false);
|
|
2562
|
+
UDExAssertHostSelection(UDExLeafHost("Beta"), false);
|
|
2563
|
+
UDExAssertSelectionEvent("Languages group", [], 4);
|
|
2564
|
+
UDExAssertActiveDescendant(UDExHeaderHost());
|
|
2565
|
+
UDExAssertSelectionStatus("Languages group", "Unselected");
|
|
2566
|
+
|
|
2567
|
+
cy.realPress("ArrowRight");
|
|
2568
|
+
UDExHeaderHost().should("have.attr", "aria-expanded", "true");
|
|
2569
|
+
cy.get("[udex-select-box]").find("[udex-select-box-group-header]").should("not.have.attr", "collapsed");
|
|
2570
|
+
UDExHeaderHost().find("[udex-list-item]").each($item => {
|
|
2571
|
+
cy.wrap($item).should("not.have.attr", "collapsed");
|
|
2572
|
+
});
|
|
2573
|
+
UDExAssertNativeActiveDescendant(UDExHeaderHost(), 1, 1, 1);
|
|
2574
|
+
UDExAssertPresentationItem(UDExNestedHeader());
|
|
2575
|
+
UDExAssertHostSelection(UDExHeaderHost(), false);
|
|
2576
|
+
UDExAssertHostSelection(UDExLeafHost("Alpha"), false);
|
|
2577
|
+
UDExAssertNativeHostStates();
|
|
2578
|
+
UDExSourceLeaf("Unavailable").should("not.have.attr", "selected");
|
|
2579
|
+
cy.get("@selectionChange").should("have.callCount", 4);
|
|
2580
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
2581
|
+
UDExPopover().should("have.attr", "open");
|
|
2582
|
+
});
|
|
2583
|
+
});
|
|
2584
|
+
|
|
2585
|
+
describe("SelectBox complete leaf names and explicit selection feedback", () => {
|
|
2586
|
+
[UDExListMode.SingleSelect, UDExListMode.MultiSelect].forEach(UDExMode => {
|
|
2587
|
+
UDExLayouts.forEach(UDExLayout => {
|
|
2588
|
+
// Updated: full-name selection feedback persists; selected-child reopening keeps the popup fallback.
|
|
2589
|
+
it(`announces the complete ${UDExMode} / ${UDExLayout.name} leaf name and new state without level or position`, () => {
|
|
2590
|
+
const UDExItems = [
|
|
2591
|
+
<UDExSelectBoxItem label="Alpha" supportingText="First choice" additionalText="Code A"></UDExSelectBoxItem>,
|
|
2592
|
+
<UDExSelectBoxItem label="Beta" supportingText="Second choice" additionalText="Code B"></UDExSelectBoxItem>,
|
|
2593
|
+
];
|
|
2594
|
+
cy.mount(
|
|
2595
|
+
<div id="select-box-regression-fixture">
|
|
2596
|
+
<UDExSelectBox label="Languages" supportingText="Choose a language" mode={UDExMode}>
|
|
2597
|
+
{UDExLayout.grouped ? (
|
|
2598
|
+
<UDExSelectBoxGroupHeader label="Languages group" supportingText="Available choices" collapsible={UDExLayout.collapsible}>
|
|
2599
|
+
{UDExItems}
|
|
2600
|
+
</UDExSelectBoxGroupHeader>
|
|
2601
|
+
) : UDExItems}
|
|
2602
|
+
</UDExSelectBox>
|
|
2603
|
+
</div>
|
|
2604
|
+
);
|
|
2605
|
+
UDExOpenSelectBox(UDExLayout);
|
|
2606
|
+
UDExAssertLeafName(UDExLeafHost("Alpha"), "Alpha", "First choice", "Code A");
|
|
2607
|
+
UDExAssertLeafName(UDExLeafHost("Beta"), "Beta", "Second choice", "Code B");
|
|
2608
|
+
cy.get("[udex-select-box]").then($host => {
|
|
2609
|
+
$host[0].addEventListener("close", cy.stub().as("selectionClosed"));
|
|
2610
|
+
});
|
|
2611
|
+
cy.realPress("End");
|
|
2612
|
+
UDExAssertNativeActiveDescendant(UDExLeafHost("Beta"), 2, 2, UDExLayout.grouped ? 2 : undefined);
|
|
2613
|
+
UDExAssertHostSelection(UDExLeafHost("Beta"), false);
|
|
2614
|
+
UDExAssertDescription("Choose a language");
|
|
2615
|
+
cy.realPress("Enter");
|
|
2616
|
+
UDExSourceLeaf("Beta").should("have.attr", "selected");
|
|
2617
|
+
UDExAssertSelectionEvent("Beta", ["Beta"]);
|
|
2618
|
+
UDExAssertSelectionStatus("Beta Second choice Code B", "Selected");
|
|
2619
|
+
|
|
2620
|
+
if (UDExMode === UDExListMode.SingleSelect) {
|
|
2621
|
+
cy.get("@selectionClosed").should("have.been.calledOnce");
|
|
2622
|
+
UDExPopover().should("not.have.attr", "open");
|
|
2623
|
+
UDExCombobox().should("have.attr", "aria-expanded", "false");
|
|
2624
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
2625
|
+
UDExListHost().find("[_active-descendant]").should("not.exist");
|
|
2626
|
+
UDExAssertDescription("Choose a language Beta");
|
|
2627
|
+
// Feedback survives the complete close lifecycle, not merely the initial selection handler.
|
|
2628
|
+
UDExAssertSelectionStatus("Beta Second choice Code B", "Selected");
|
|
2629
|
+
cy.realPress("ArrowDown");
|
|
2630
|
+
UDExAssertOpenWithActiveDescendant(UDExLeafHost("Beta"), "expanded");
|
|
2631
|
+
UDExAssertHostPosition(UDExLeafHost("Beta"), 2, 2, UDExLayout.grouped ? 2 : undefined);
|
|
2632
|
+
UDExAssertHostSelection(UDExLeafHost("Beta"), true);
|
|
2633
|
+
UDExAssertDescription("Choose a language");
|
|
2634
|
+
UDExAssertSelectionEvent("Beta", ["Beta"]);
|
|
2635
|
+
cy.realPress("ArrowDown");
|
|
2636
|
+
UDExAssertNativeActiveDescendant(UDExLayout.collapsible ? UDExHeaderHost() : UDExLeafHost("Alpha"), 1,
|
|
2637
|
+
UDExLayout.collapsible ? 1 : 2, UDExLayout.grouped ? UDExLayout.collapsible ? 1 : 2 : undefined);
|
|
2638
|
+
if (UDExLayout.collapsible) {
|
|
2639
|
+
UDExHeaderHost().should("have.attr", "aria-expanded", "true");
|
|
2640
|
+
}
|
|
2641
|
+
UDExAssertHostSelection(UDExLayout.collapsible ? UDExHeaderHost() : UDExLeafHost("Alpha"), false);
|
|
2642
|
+
UDExAssertDescription("Choose a language");
|
|
2643
|
+
UDExAssertSelectionEvent("Beta", ["Beta"]);
|
|
2644
|
+
} else {
|
|
2645
|
+
UDExAssertActiveDescendant(UDExLeafHost("Beta"));
|
|
2646
|
+
UDExAssertDescription("Choose a language");
|
|
2647
|
+
cy.realPress("Space");
|
|
2648
|
+
UDExSourceLeaf("Beta").should("not.have.attr", "selected");
|
|
2649
|
+
UDExAssertSelectionEvent("Beta", [], 2);
|
|
2650
|
+
UDExAssertSelectionStatus("Beta Second choice Code B", "Unselected");
|
|
2651
|
+
UDExAssertDescription("Choose a language");
|
|
2652
|
+
cy.realPress("ArrowUp");
|
|
2653
|
+
UDExAssertNativeActiveDescendant(UDExLeafHost("Alpha"), 1, 2, UDExLayout.grouped ? 2 : undefined);
|
|
2654
|
+
UDExAssertHostSelection(UDExLeafHost("Alpha"), false);
|
|
2655
|
+
cy.get("@selectionClosed").should("not.have.been.called");
|
|
2656
|
+
}
|
|
2657
|
+
UDExCombobox().should("be.focused");
|
|
2658
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
2659
|
+
});
|
|
2660
|
+
});
|
|
2661
|
+
});
|
|
2662
|
+
|
|
2663
|
+
[
|
|
2664
|
+
{ name: "supporting text", supportingText: "Unnamed choice", additionalText: "" },
|
|
2665
|
+
{ name: "additional text", supportingText: "", additionalText: "Choice code" },
|
|
2666
|
+
].forEach(UDExCase => {
|
|
2667
|
+
// Updated: unnamed leaf opening supplements only expanded, not native item metadata.
|
|
2668
|
+
it(`exposes ${UDExCase.name} as the host name when a leaf has no label`, () => {
|
|
2669
|
+
cy.mount(
|
|
2670
|
+
<div id="select-box-regression-fixture">
|
|
2671
|
+
<UDExSelectBox accessibleName="Languages">
|
|
2672
|
+
<UDExSelectBoxItem supportingText={UDExCase.supportingText} additionalText={UDExCase.additionalText}></UDExSelectBoxItem>
|
|
2673
|
+
</UDExSelectBox>
|
|
2674
|
+
</div>
|
|
2675
|
+
);
|
|
2676
|
+
UDExOpenSelectBox(UDExLayouts[0]);
|
|
2677
|
+
const UDExName = UDExCase.supportingText || UDExCase.additionalText;
|
|
2678
|
+
UDExListHost().find("[udex-list-item]").should("have.attr", "aria-label", UDExName);
|
|
2679
|
+
UDExAssertActiveDescendant(UDExListHost().find("[udex-list-item]"));
|
|
2680
|
+
UDExAssertOpeningStatus();
|
|
2681
|
+
cy.realPress("Enter");
|
|
2682
|
+
cy.get("[udex-select-box-item]").should("have.attr", "selected");
|
|
2683
|
+
cy.get("[udex-select-box-item]").then($source => {
|
|
2684
|
+
cy.get("@selectionChange").should("have.been.calledOnce")
|
|
2685
|
+
.its("lastCall.args.0.detail").should((UDExDetail: UDExSearchableListBaseSelectionChangeEventDetail) => {
|
|
2686
|
+
expect(UDExDetail.item).to.equal($source[0]);
|
|
2687
|
+
expect(UDExDetail.selectedItems).to.deep.equal([$source[0]]);
|
|
2688
|
+
});
|
|
2689
|
+
});
|
|
2690
|
+
UDExPopover().should("not.have.attr", "open");
|
|
2691
|
+
UDExAssertSelectionStatus(UDExName, "Selected");
|
|
2692
|
+
UDExCombobox().should("be.focused");
|
|
2693
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
2694
|
+
});
|
|
2695
|
+
});
|
|
2696
|
+
});
|
|
2697
|
+
|
|
2698
|
+
describe("SelectBox same-shadow supporting descriptions", () => {
|
|
2699
|
+
[UDExListMode.SingleSelect, UDExListMode.MultiSelect].forEach(UDExMode => {
|
|
2700
|
+
[true, false].forEach(UDExVisibleLabel => {
|
|
2701
|
+
it(`retains supporting text for ${UDExMode} with ${UDExVisibleLabel ? "a visible label" : "accessibleName only"} while suppressing navigation rereads`, () => {
|
|
2702
|
+
cy.mount(
|
|
2703
|
+
<div id="select-box-regression-fixture">
|
|
2704
|
+
<UDExSelectBox label={UDExVisibleLabel ? "Languages" : undefined} accessibleName="Languages"
|
|
2705
|
+
supportingText="Choose languages" mode={UDExMode}>
|
|
2706
|
+
<UDExSelectBoxItem label="Alpha" selected></UDExSelectBoxItem>
|
|
2707
|
+
<UDExSelectBoxItem label="Beta"></UDExSelectBoxItem>
|
|
2708
|
+
</UDExSelectBox>
|
|
2709
|
+
</div>
|
|
2710
|
+
);
|
|
2711
|
+
UDExCombobox().should("have.attr", "aria-label", "Languages");
|
|
2712
|
+
cy.get("[udex-select-box]").shadow().find('label[for="search-input"]').should(UDExVisibleLabel ? "exist" : "not.exist");
|
|
2713
|
+
if (UDExMode === UDExListMode.SingleSelect) {
|
|
2714
|
+
UDExAssertDescription("Choose languages Alpha");
|
|
2715
|
+
} else {
|
|
2716
|
+
UDExAssertDescription("Choose languages", 1);
|
|
2717
|
+
}
|
|
2718
|
+
UDExOpenSelectBox(UDExLayouts[0]);
|
|
2719
|
+
UDExAssertDescription("Choose languages");
|
|
2720
|
+
cy.realPress("End");
|
|
2721
|
+
UDExAssertActiveDescendant(UDExLeafHost("Beta"));
|
|
2722
|
+
UDExAssertDescription("Choose languages");
|
|
2723
|
+
cy.realPress("Space");
|
|
2724
|
+
UDExAssertSelectionEvent("Beta", UDExMode === UDExListMode.SingleSelect ? ["Beta"] : ["Alpha", "Beta"]);
|
|
2725
|
+
UDExAssertSelectionStatus("Beta", "Selected");
|
|
2726
|
+
if (UDExMode === UDExListMode.MultiSelect) {
|
|
2727
|
+
// Token count changed, but the input description must not change during active navigation.
|
|
2728
|
+
UDExAssertActiveDescendant(UDExLeafHost("Beta"));
|
|
2729
|
+
UDExAssertDescription("Choose languages");
|
|
2730
|
+
cy.realPress("Escape");
|
|
2731
|
+
UDExPopover().should("not.have.attr", "open");
|
|
2732
|
+
UDExAssertDescription("Choose languages", 2);
|
|
2733
|
+
UDExAssertSupplementalStatus("");
|
|
2734
|
+
} else {
|
|
2735
|
+
UDExPopover().should("not.have.attr", "open");
|
|
2736
|
+
UDExAssertDescription("Choose languages Beta");
|
|
2737
|
+
UDExAssertSelectionStatus("Beta", "Selected");
|
|
2738
|
+
}
|
|
2739
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
2740
|
+
UDExCombobox().should("be.focused");
|
|
2741
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
2742
|
+
});
|
|
2743
|
+
});
|
|
2744
|
+
});
|
|
2745
|
+
});
|
|
2746
|
+
|
|
2747
|
+
describe("SelectBox partial-selection description lifecycle", () => {
|
|
2748
|
+
const UDExWindow = window as Window & { "udex-config"?: { translations?: Record<string, string> } };
|
|
2749
|
+
let UDExOriginalConfig: typeof UDExWindow["udex-config"];
|
|
2750
|
+
|
|
2751
|
+
beforeEach(() => {
|
|
2752
|
+
UDExOriginalConfig = UDExWindow["udex-config"];
|
|
2753
|
+
});
|
|
2754
|
+
|
|
2755
|
+
afterEach(() => {
|
|
2756
|
+
if (UDExOriginalConfig === undefined) {
|
|
2757
|
+
delete UDExWindow["udex-config"];
|
|
2758
|
+
} else {
|
|
2759
|
+
UDExWindow["udex-config"] = UDExOriginalConfig;
|
|
2760
|
+
}
|
|
2761
|
+
});
|
|
2762
|
+
|
|
2763
|
+
[
|
|
2764
|
+
{ name: "Select All", selectAll: true, collapsible: false, description: "Indeterminate", localized: false },
|
|
2765
|
+
{ name: "localized Select All", selectAll: true, collapsible: false, description: "Teilweise ausgewählt", localized: true },
|
|
2766
|
+
{ name: "localized noncollapsible group", selectAll: false, collapsible: false, description: "Teilweise ausgewählt", localized: true },
|
|
2767
|
+
{ name: "localized collapsible group", selectAll: false, collapsible: true, description: "Teilweise ausgewählt", localized: true },
|
|
2768
|
+
].forEach(UDExCase => {
|
|
2769
|
+
it(`exposes ${UDExCase.name} as partial, selected, then unselected with Boolean selection and exact description cleanup`, () => {
|
|
2770
|
+
if (UDExCase.localized) {
|
|
2771
|
+
// Use the supported translation override without changing global UI5 language or loading bundles.
|
|
2772
|
+
UDExWindow["udex-config"] = {
|
|
2773
|
+
...UDExOriginalConfig,
|
|
2774
|
+
translations: { ...UDExOriginalConfig?.translations, INDETERMINATE: UDExCase.description },
|
|
2775
|
+
};
|
|
2776
|
+
}
|
|
2777
|
+
const UDExGroupName = "All languages Available choices";
|
|
2778
|
+
const UDExHost = () => UDExCase.selectAll ? UDExSelectAllHost() : UDExHeaderHost();
|
|
2779
|
+
const UDExSource = () => cy.get(UDExCase.selectAll ? "#select-all-source" : "#partial-group-source");
|
|
2780
|
+
const UDExHeaderPosition = UDExCase.selectAll ? 2 : 1;
|
|
2781
|
+
const UDExRootSize = UDExCase.selectAll ? 3 : 1;
|
|
2782
|
+
|
|
2783
|
+
if (UDExCase.selectAll) {
|
|
2784
|
+
UDExMountSelectAll("All languages", "Available choices");
|
|
2785
|
+
cy.realPress("ArrowDown");
|
|
2786
|
+
} else {
|
|
2787
|
+
cy.mount(
|
|
2788
|
+
<div id="select-box-regression-fixture">
|
|
2789
|
+
<UDExSelectBox accessibleName="Languages" supportingText="Choose languages" mode={UDExListMode.MultiSelect}>
|
|
2790
|
+
<UDExSelectBoxGroupHeader id="partial-group-source" label="All languages" supportingText="Available choices"
|
|
2791
|
+
collapsible={UDExCase.collapsible}>
|
|
2792
|
+
<UDExSelectBoxItem label="Alpha" supportingText="First choice" additionalText="Code A"></UDExSelectBoxItem>
|
|
2793
|
+
<UDExSelectBoxItem label="Beta" supportingText="Second choice" additionalText="Code B"></UDExSelectBoxItem>
|
|
2794
|
+
</UDExSelectBoxGroupHeader>
|
|
2795
|
+
</UDExSelectBox>
|
|
2796
|
+
</div>
|
|
2797
|
+
);
|
|
2798
|
+
UDExOpenSelectBox(UDExLayouts[UDExCase.collapsible ? 2 : 1]);
|
|
2799
|
+
// Updated: only the expanded collapsible header suppresses the opening fallback.
|
|
2800
|
+
UDExAssertOpenWithActiveDescendant(UDExHost(), UDExCase.collapsible ? "" : "expanded");
|
|
2801
|
+
// Start navigation to clear opening feedback before testing the partial state.
|
|
2802
|
+
cy.realPress("Home");
|
|
2803
|
+
}
|
|
2804
|
+
UDExAssertNativeActiveDescendant(UDExHost(), UDExHeaderPosition, UDExRootSize, 1);
|
|
2805
|
+
UDExAssertHostSelection(UDExHost(), false);
|
|
2806
|
+
UDExHost().then($originalHeader => {
|
|
2807
|
+
UDExSource().then($source => {
|
|
2808
|
+
UDExCombobox().then($input => {
|
|
2809
|
+
const UDExOriginalInput = $input[0];
|
|
2810
|
+
const UDExAssertHeaderIdentity = () => {
|
|
2811
|
+
UDExHost().should($current => {
|
|
2812
|
+
expect($current[0], "partial selection does not replace the header").to.equal($originalHeader[0]);
|
|
2813
|
+
expect($current).to.have.attr("aria-label", UDExGroupName);
|
|
2814
|
+
if (UDExCase.collapsible) {
|
|
2815
|
+
expect($current).to.have.attr("aria-expanded", "true");
|
|
2816
|
+
} else {
|
|
2817
|
+
expect($current).not.to.have.attr("aria-expanded");
|
|
2818
|
+
}
|
|
2819
|
+
});
|
|
2820
|
+
UDExSource().should($current => expect($current[0]).to.equal($source[0]));
|
|
2821
|
+
UDExCombobox().should($current => expect($current[0]).to.equal(UDExOriginalInput));
|
|
2822
|
+
UDExAssertHostPosition(UDExHost(), UDExHeaderPosition, UDExRootSize, 1);
|
|
2823
|
+
UDExAssertNativeHostStates();
|
|
2824
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
2825
|
+
};
|
|
2826
|
+
|
|
2827
|
+
cy.realPress("ArrowDown");
|
|
2828
|
+
UDExAssertNativeActiveDescendant(UDExLeafHost("Alpha"), 1, 2, 2);
|
|
2829
|
+
cy.realPress("Enter");
|
|
2830
|
+
UDExAssertSelectionEvent("Alpha", ["Alpha"]);
|
|
2831
|
+
UDExSourceLeaf("Alpha").then($leaf => {
|
|
2832
|
+
cy.get("@selectionChange").its("lastCall.args.0.detail.item").should("equal", $leaf[0]);
|
|
2833
|
+
});
|
|
2834
|
+
UDExAssertSelectionStatus("Alpha First choice Code A", "Selected");
|
|
2835
|
+
UDExAssertHostSelection(UDExLeafHost("Alpha"), true);
|
|
2836
|
+
UDExAssertHostSelection(UDExLeafHost("Beta"), false);
|
|
2837
|
+
UDExAssertHostSelection(UDExHost(), false, true, UDExCase.description);
|
|
2838
|
+
UDExSource().should("have.prop", "selected", true);
|
|
2839
|
+
UDExSource().should("have.prop", "indeterminate", true);
|
|
2840
|
+
UDExAssertHeaderIdentity();
|
|
2841
|
+
|
|
2842
|
+
cy.realPress("ArrowUp");
|
|
2843
|
+
UDExAssertNativeActiveDescendant(UDExHost(), UDExHeaderPosition, UDExRootSize, 1);
|
|
2844
|
+
// Partial description remains native on re-entry; neither it nor Selected is repeated live.
|
|
2845
|
+
UDExAssertHostSelection(UDExHost(), false, true, UDExCase.description);
|
|
2846
|
+
[true, false].forEach((UDExSelected, UDExIndex) => {
|
|
2847
|
+
cy.realPress(UDExSelected ? "Enter" : "Space");
|
|
2848
|
+
UDExAssertHostSelection(UDExHost(), UDExSelected);
|
|
2849
|
+
UDExSource().should("have.prop", "selected", UDExSelected);
|
|
2850
|
+
UDExSource().should("have.prop", "indeterminate", false);
|
|
2851
|
+
["Alpha", "Beta"].forEach(UDExLabel => {
|
|
2852
|
+
UDExSourceLeaf(UDExLabel).should(UDExSelected ? "have.attr" : "not.have.attr", "selected");
|
|
2853
|
+
UDExAssertHostSelection(UDExLeafHost(UDExLabel), UDExSelected);
|
|
2854
|
+
});
|
|
2855
|
+
UDExAssertActiveDescendant(UDExHost());
|
|
2856
|
+
UDExAssertSelectionStatus(UDExGroupName, UDExSelected ? "Selected" : "Unselected");
|
|
2857
|
+
if (UDExCase.selectAll) {
|
|
2858
|
+
cy.get("@selectAll").should("have.callCount", UDExIndex + 1).its("lastCall.args.0").should((UDExEvent: Event) => {
|
|
2859
|
+
expect(UDExEvent.type).to.equal("select-all");
|
|
2860
|
+
expect(UDExEvent.bubbles).to.equal(true);
|
|
2861
|
+
});
|
|
2862
|
+
cy.get("@routedSelectAll").should("have.callCount", UDExIndex + 1)
|
|
2863
|
+
.its("lastCall.args.0.detail.item").should("equal", $originalHeader[0]);
|
|
2864
|
+
cy.get("@wrongHeaderPress").should("not.have.been.called");
|
|
2865
|
+
UDExAssertSelectionEvent("Alpha", ["Alpha"]);
|
|
2866
|
+
UDExSourceLeaf("Worldwide").should("not.have.attr", "selected");
|
|
2867
|
+
UDExSourceLeaf("Other languages").should("not.have.attr", "selected");
|
|
2868
|
+
} else {
|
|
2869
|
+
UDExAssertSelectionEvent("All languages", UDExSelected ? ["Alpha", "Beta"] : [], UDExIndex + 2);
|
|
2870
|
+
cy.get("@selectionChange").its("lastCall.args.0.detail.item").should("equal", $source[0]);
|
|
2871
|
+
}
|
|
2872
|
+
UDExAssertHeaderIdentity();
|
|
2873
|
+
UDExAssertDescription("Choose languages");
|
|
2874
|
+
UDExPopover().should("have.attr", "open");
|
|
2875
|
+
});
|
|
2876
|
+
cy.realPress("ArrowDown");
|
|
2877
|
+
UDExAssertNativeActiveDescendant(UDExLeafHost("Alpha"), 1, 2, 2);
|
|
2878
|
+
UDExAssertHostSelection(UDExHost(), false);
|
|
2879
|
+
});
|
|
2880
|
+
});
|
|
2881
|
+
});
|
|
2882
|
+
});
|
|
2883
|
+
});
|
|
2884
|
+
|
|
2885
|
+
it("removes a partial group's description outside MultiSelect and omits selection entirely in SingleContent", () => {
|
|
2886
|
+
UDExMountSelectBox(UDExListMode.MultiSelect, UDExLayouts[2]);
|
|
2887
|
+
cy.realPress("ArrowDown");
|
|
2888
|
+
UDExAssertNativeActiveDescendant(UDExLeafHost("Alpha"), 1, 3, 2);
|
|
2889
|
+
cy.realPress("Enter");
|
|
2890
|
+
UDExAssertSelectionEvent("Alpha", ["Alpha"]);
|
|
2891
|
+
UDExAssertHostSelection(UDExHeaderHost(), false, true);
|
|
2892
|
+
cy.get("[udex-select-box]").invoke("prop", "mode", UDExListMode.SingleSelect);
|
|
2893
|
+
UDExAssertHostSelection(UDExHeaderHost(), false);
|
|
2894
|
+
UDExAssertHostSelection(UDExLeafHost("Alpha"), true);
|
|
2895
|
+
UDExAssertHostPosition(UDExHeaderHost(), 1, 1, 1);
|
|
2896
|
+
cy.get("[udex-select-box]").invoke("prop", "mode", UDExListMode.SingleContent);
|
|
2897
|
+
UDExListHost().find("[udex-list-group-header], [udex-list-item]").should($hosts => {
|
|
2898
|
+
expect($hosts).to.have.length(4);
|
|
2899
|
+
$hosts.each((UDExIndex, UDExHost) => {
|
|
2900
|
+
expect(UDExHost, `informational host ${UDExIndex}`).not.to.have.attr("aria-selected");
|
|
2901
|
+
expect(UDExHost).not.to.have.attr("aria-checked");
|
|
2902
|
+
expect(UDExHost).not.to.have.attr("aria-description");
|
|
2903
|
+
});
|
|
2904
|
+
});
|
|
2905
|
+
UDExSourceLeaf("Alpha").should("have.attr", "selected");
|
|
2906
|
+
cy.get("@selectionChange").should("have.been.calledOnce");
|
|
2907
|
+
UDExCombobox().should("be.focused");
|
|
2908
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
2909
|
+
});
|
|
2910
|
+
});
|
|
2911
|
+
|
|
2912
|
+
describe("SelectBox unified Select All host architecture", () => {
|
|
2913
|
+
[
|
|
2914
|
+
{ name: "translated default", label: undefined, supportingText: undefined, accessibleName: "Select All" },
|
|
2915
|
+
{ name: "custom label and support", label: "All languages", supportingText: "Available choices", accessibleName: "All languages Available choices" },
|
|
2916
|
+
].forEach(UDExCase => {
|
|
2917
|
+
describe(UDExCase.name, () => {
|
|
2918
|
+
beforeEach(() => {
|
|
2919
|
+
UDExMountSelectAll(UDExCase.label, UDExCase.supportingText);
|
|
2920
|
+
});
|
|
2921
|
+
|
|
2922
|
+
// Updated: Select All owns Boolean selection and native positions; opening is expanded-only and navigation silent.
|
|
2923
|
+
it("owns one stable named level-1 treeitem and presents its wrappers and inner row without duplicate widgets", () => {
|
|
2924
|
+
UDExCombobox().should("have.attr", "aria-haspopup", "tree");
|
|
2925
|
+
UDExListHost().should("have.attr", "role", "tree");
|
|
2926
|
+
cy.get("#select-all-source").then($source => {
|
|
2927
|
+
const UDExId = ($source[0] as UDExSelectBoxSelectAllElement)._id;
|
|
2928
|
+
UDExSelectAllHost().should("have.attr", "id", UDExId).and("have.attr", "data-token-id", UDExId);
|
|
2929
|
+
});
|
|
2930
|
+
UDExAssertHostPosition(UDExSelectAllHost(), 2, 3, 1);
|
|
2931
|
+
UDExSelectAllHost().should($host => {
|
|
2932
|
+
expect($host).to.have.attr("aria-label", UDExCase.accessibleName);
|
|
2933
|
+
expect($host).to.have.attr("aria-selected", "false");
|
|
2934
|
+
expect($host).to.have.attr("_use-host-aria-role");
|
|
2935
|
+
expect($host).to.have.attr("_tree-mode");
|
|
2936
|
+
expect($host).not.to.have.attr("aria-checked");
|
|
2937
|
+
expect($host).not.to.have.attr("aria-description");
|
|
2938
|
+
expect($host).not.to.have.attr("aria-expanded");
|
|
2939
|
+
expect($host).not.to.have.attr("tabindex");
|
|
2940
|
+
});
|
|
2941
|
+
UDExSelectAllHost().shadow().find('[part="item"], .udex-list__group-list').should("have.length", 2).each($wrapper => {
|
|
2942
|
+
cy.wrap($wrapper).should($element => {
|
|
2943
|
+
expect($element).to.have.attr("role", "presentation");
|
|
2944
|
+
["aria-label", "aria-labelledby", ...UDExInnerStateAttributes].forEach(UDExAttribute => {
|
|
2945
|
+
expect($element).not.to.have.attr(UDExAttribute);
|
|
2946
|
+
});
|
|
2947
|
+
});
|
|
2948
|
+
});
|
|
2949
|
+
UDExSelectAllHost().shadow().find('[role="treeitem"], [role="option"], [role="listbox"]').should("not.exist");
|
|
2950
|
+
UDExSelectAllHost().shadow().find('div[role="group"] > slot').should("have.length", 1);
|
|
2951
|
+
UDExSelectAllRow().should($row => {
|
|
2952
|
+
expect($row).to.have.attr("role", "none");
|
|
2953
|
+
expect($row).to.have.attr("_use-host-aria-role");
|
|
2954
|
+
[...UDExInnerStateAttributes, "aria-current"].forEach(UDExAttribute => {
|
|
2955
|
+
expect($row).not.to.have.attr(UDExAttribute);
|
|
2956
|
+
});
|
|
2957
|
+
});
|
|
2958
|
+
UDExAssertPresentationItem(UDExSelectAllRow());
|
|
2959
|
+
UDExSelectAllRow().shadow().find('[part="title"]').should("have.text", UDExCase.label || "Select All");
|
|
2960
|
+
if (UDExCase.supportingText) {
|
|
2961
|
+
UDExSelectAllRow().shadow().find('[part="supporting"]').should("have.text", UDExCase.supportingText);
|
|
2962
|
+
}
|
|
2963
|
+
UDExAssertHostPosition(UDExLeafHost("Alpha"), 1, 2, 2);
|
|
2964
|
+
UDExAssertHostPosition(UDExLeafHost("Beta"), 2, 2, 2);
|
|
2965
|
+
UDExAssertLeafName(UDExLeafHost("Alpha"), "Alpha", "First choice", "Code A");
|
|
2966
|
+
UDExAssertLeafName(UDExLeafHost("Beta"), "Beta", "Second choice", "Code B");
|
|
2967
|
+
["Alpha", "Beta"].forEach(UDExLabel => {
|
|
2968
|
+
UDExAssertHostSelection(UDExLeafHost(UDExLabel), false);
|
|
2969
|
+
UDExLeafHost(UDExLabel).should("not.have.attr", "aria-expanded");
|
|
2970
|
+
UDExAssertPresentationItem(UDExLeafHost(UDExLabel));
|
|
2971
|
+
});
|
|
2972
|
+
cy.realPress("ArrowDown");
|
|
2973
|
+
UDExAssertNativeActiveDescendant(UDExSelectAllHost(), 2, 3, 1);
|
|
2974
|
+
cy.realPress("Escape");
|
|
2975
|
+
UDExPopover().should("not.have.attr", "open");
|
|
2976
|
+
UDExCombobox().should("have.attr", "aria-expanded", "false");
|
|
2977
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
2978
|
+
UDExListHost().find("[_active-descendant]").should("not.exist");
|
|
2979
|
+
UDExAssertSupplementalStatus("");
|
|
2980
|
+
UDExCombobox().should("be.focused");
|
|
2981
|
+
cy.realPress("ArrowDown");
|
|
2982
|
+
UDExAssertOpenWithActiveDescendant(UDExLeafHost("Worldwide"));
|
|
2983
|
+
UDExAssertHostPosition(UDExLeafHost("Worldwide"), 1, 3, 1);
|
|
2984
|
+
cy.realPress("ArrowDown");
|
|
2985
|
+
UDExAssertNativeActiveDescendant(UDExSelectAllHost(), 2, 3, 1);
|
|
2986
|
+
cy.get("#select-all-source").then($source => {
|
|
2987
|
+
UDExSelectAllHost().should("have.attr", "id", ($source[0] as UDExSelectBoxSelectAllElement)._id);
|
|
2988
|
+
});
|
|
2989
|
+
cy.get("@selectAll").should("not.have.been.called");
|
|
2990
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
2991
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
2992
|
+
});
|
|
2993
|
+
|
|
2994
|
+
(["Enter", "Space", "pointer"] as const).forEach(UDExActivation => {
|
|
2995
|
+
// Updated: Select All toggles native Boolean selection and removes any partial description.
|
|
2996
|
+
it(`routes ${UDExActivation} through select-all exactly once per toggle and announces the new header state without blurring`, () => {
|
|
2997
|
+
cy.realPress("ArrowDown");
|
|
2998
|
+
UDExAssertNativeActiveDescendant(UDExSelectAllHost(), 2, 3, 1);
|
|
2999
|
+
[true, false].forEach((UDExSelected, UDExIndex) => {
|
|
3000
|
+
if (UDExActivation === "pointer") {
|
|
3001
|
+
UDExSelectAllRow().shadow().find('[part="item"]').realClick({ scrollBehavior: false });
|
|
3002
|
+
} else {
|
|
3003
|
+
cy.realPress(UDExActivation);
|
|
3004
|
+
}
|
|
3005
|
+
["Alpha", "Beta"].forEach(UDExLabel => {
|
|
3006
|
+
UDExSourceLeaf(UDExLabel).should(UDExSelected ? "have.attr" : "not.have.attr", "selected");
|
|
3007
|
+
UDExAssertHostSelection(UDExLeafHost(UDExLabel), UDExSelected);
|
|
3008
|
+
});
|
|
3009
|
+
UDExAssertHostSelection(UDExSelectAllHost(), UDExSelected);
|
|
3010
|
+
cy.get("#select-all-source").should("have.prop", "selected", UDExSelected);
|
|
3011
|
+
cy.get("#select-all-source").should("have.prop", "indeterminate", false);
|
|
3012
|
+
UDExSelectAllHost().should("not.have.attr", "aria-expanded");
|
|
3013
|
+
UDExAssertActiveDescendant(UDExSelectAllHost());
|
|
3014
|
+
UDExAssertSelectionStatus(UDExCase.accessibleName, UDExSelected ? "Selected" : "Unselected");
|
|
3015
|
+
UDExAssertDescription("Choose languages");
|
|
3016
|
+
UDExAssertSelectAllEvent(UDExIndex + 1);
|
|
3017
|
+
UDExSourceLeaf("Worldwide").should("not.have.attr", "selected");
|
|
3018
|
+
UDExSourceLeaf("Other languages").should("not.have.attr", "selected");
|
|
3019
|
+
UDExAssertPresentationItem(UDExSelectAllRow());
|
|
3020
|
+
UDExCombobox().should("be.focused");
|
|
3021
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
3022
|
+
});
|
|
3023
|
+
cy.realPress("ArrowDown");
|
|
3024
|
+
UDExAssertNativeActiveDescendant(UDExLeafHost("Alpha"), 1, 2, 2);
|
|
3025
|
+
UDExAssertSelectAllEvent(2);
|
|
3026
|
+
});
|
|
3027
|
+
});
|
|
3028
|
+
});
|
|
3029
|
+
});
|
|
3030
|
+
|
|
3031
|
+
// Updated: partial Select All is false with Indeterminate description, which is removed after deselection.
|
|
3032
|
+
it("describes partial Select All when a disabled child remains unselected, then removes the description when unselected", () => {
|
|
3033
|
+
UDExMountSelectAll("All languages", "Available choices", true);
|
|
3034
|
+
cy.realPress("ArrowDown");
|
|
3035
|
+
UDExAssertNativeActiveDescendant(UDExSelectAllHost(), 2, 3, 1);
|
|
3036
|
+
cy.realPress("Space");
|
|
3037
|
+
UDExAssertSelectAllEvent(1);
|
|
3038
|
+
UDExAssertHostSelection(UDExSelectAllHost(), false, true);
|
|
3039
|
+
cy.get("#select-all-source").should("have.prop", "indeterminate", true);
|
|
3040
|
+
UDExAssertActiveDescendant(UDExSelectAllHost());
|
|
3041
|
+
UDExAssertSelectionStatus("All languages Available choices", "Indeterminate");
|
|
3042
|
+
["Alpha", "Beta"].forEach(UDExLabel => UDExSourceLeaf(UDExLabel).should("have.attr", "selected"));
|
|
3043
|
+
UDExSourceLeaf("Unavailable").should("not.have.attr", "selected");
|
|
3044
|
+
UDExAssertHostSelection(UDExLeafHost("Unavailable"), false);
|
|
3045
|
+
UDExLeafHost("Unavailable").should("have.attr", "aria-disabled", "true");
|
|
3046
|
+
UDExSelectAllRow().shadow().find('[part="item"]').realClick({ scrollBehavior: false });
|
|
3047
|
+
UDExAssertSelectAllEvent(2);
|
|
3048
|
+
UDExAssertHostSelection(UDExSelectAllHost(), false);
|
|
3049
|
+
UDExAssertActiveDescendant(UDExSelectAllHost());
|
|
3050
|
+
cy.get("#select-all-source").should("have.prop", "indeterminate", false);
|
|
3051
|
+
UDExAssertSelectionStatus("All languages Available choices", "Unselected");
|
|
3052
|
+
["Alpha", "Beta", "Unavailable"].forEach(UDExLabel => UDExSourceLeaf(UDExLabel).should("not.have.attr", "selected"));
|
|
3053
|
+
UDExAssertDescription("Choose languages");
|
|
3054
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
3055
|
+
});
|
|
3056
|
+
|
|
3057
|
+
(["pointer", "keyboard"] as const).forEach(UDExActivation => {
|
|
3058
|
+
// Updated: leaf selection remains Boolean; partial Select All description is added and cleaned up.
|
|
3059
|
+
it(`keeps complete child names and selection-change payloads for ${UDExActivation} leaf toggles inside Select All`, () => {
|
|
3060
|
+
UDExMountSelectAll();
|
|
3061
|
+
cy.realPress("ArrowDown");
|
|
3062
|
+
cy.realPress("ArrowDown");
|
|
3063
|
+
UDExAssertNativeActiveDescendant(UDExLeafHost("Alpha"), 1, 2, 2);
|
|
3064
|
+
[true, false].forEach((UDExSelected, UDExIndex) => {
|
|
3065
|
+
if (UDExActivation === "pointer") {
|
|
3066
|
+
UDExLeafHost("Alpha").shadow().find('[part="item"]').realClick({ scrollBehavior: false });
|
|
3067
|
+
} else {
|
|
3068
|
+
cy.realPress(UDExSelected ? "Space" : "Enter");
|
|
3069
|
+
}
|
|
3070
|
+
UDExAssertSelectionEvent("Alpha", UDExSelected ? ["Alpha"] : [], UDExIndex + 1);
|
|
3071
|
+
UDExSourceLeaf("Alpha").should(UDExSelected ? "have.attr" : "not.have.attr", "selected");
|
|
3072
|
+
UDExAssertHostSelection(UDExLeafHost("Alpha"), UDExSelected);
|
|
3073
|
+
UDExAssertHostSelection(UDExSelectAllHost(), false, UDExSelected);
|
|
3074
|
+
UDExAssertLeafName(UDExLeafHost("Alpha"), "Alpha", "First choice", "Code A");
|
|
3075
|
+
UDExAssertActiveDescendant(UDExLeafHost("Alpha"));
|
|
3076
|
+
UDExAssertSelectionStatus("Alpha First choice Code A", UDExSelected ? "Selected" : "Unselected");
|
|
3077
|
+
UDExAssertDescription("Choose languages");
|
|
3078
|
+
UDExAssertPresentationItem(UDExLeafHost("Alpha"));
|
|
3079
|
+
cy.get("@selectAll").should("not.have.been.called");
|
|
3080
|
+
cy.get("@routedSelectAll").should("not.have.been.called");
|
|
3081
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
3082
|
+
});
|
|
3083
|
+
cy.realPress("ArrowUp");
|
|
3084
|
+
UDExAssertNativeActiveDescendant(UDExSelectAllHost(), 2, 3, 1);
|
|
3085
|
+
UDExSelectAllHost().should("not.have.attr", "aria-expanded");
|
|
3086
|
+
});
|
|
3087
|
+
});
|
|
3088
|
+
|
|
3089
|
+
// Updated: highlighted native positions remain exact while navigation clears the live status.
|
|
3090
|
+
it("keeps highlighted Select All and leaf host names clean while recomputing root and child positions", () => {
|
|
3091
|
+
UDExMountSelectAll("All languages", "Available choices");
|
|
3092
|
+
cy.realType("All languages");
|
|
3093
|
+
UDExCombobox().should("not.have.attr", "aria-activedescendant");
|
|
3094
|
+
UDExSelectAllHost().should("have.attr", "aria-label", "All languages Available choices");
|
|
3095
|
+
UDExAssertHostPosition(UDExSelectAllHost(), 1, 1, 1);
|
|
3096
|
+
UDExSelectAllRow().shadow().find('[part="title"] b').should("have.text", "All languages");
|
|
3097
|
+
UDExAssertLeafName(UDExLeafHost("Alpha"), "Alpha", "First choice", "Code A");
|
|
3098
|
+
cy.realPress("ArrowDown");
|
|
3099
|
+
UDExAssertNativeActiveDescendant(UDExSelectAllHost(), 1, 1, 1);
|
|
3100
|
+
UDExAssertHostSelection(UDExSelectAllHost(), false);
|
|
3101
|
+
UDExSelectAllHost().should("not.have.attr", "aria-expanded");
|
|
3102
|
+
cy.realPress("ArrowDown");
|
|
3103
|
+
UDExAssertNativeActiveDescendant(UDExLeafHost("Alpha"), 1, 2, 2);
|
|
3104
|
+
UDExAssertHostSelection(UDExLeafHost("Alpha"), false);
|
|
3105
|
+
cy.get("@selectionChange").should("not.have.been.called");
|
|
3106
|
+
cy.get("@selectAll").should("not.have.been.called");
|
|
3107
|
+
cy.get("@comboboxBlur").should("not.have.been.called");
|
|
3108
|
+
});
|
|
3109
|
+
});
|