@sapudex/web-components 2.51.0 → 2.53.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/Avatar.cy.tsx +103 -0
- package/cypress/specs/CountrySelector.cy.tsx +21 -0
- package/cypress/specs/List.cy.tsx +119 -10
- package/cypress/specs/SelectBox.cy.tsx +426 -70
- package/dist/Avatar.d.ts +10 -1
- package/dist/Avatar.js +43 -3
- package/dist/Avatar.js.map +1 -1
- package/dist/AvatarTemplate.js +1 -1
- package/dist/AvatarTemplate.js.map +1 -1
- package/dist/CountrySelector.js +1 -0
- package/dist/CountrySelector.js.map +1 -1
- package/dist/List.d.ts +10 -0
- package/dist/List.js +35 -0
- package/dist/List.js.map +1 -1
- package/dist/ListGroupHeaderBase.js +3 -0
- package/dist/ListGroupHeaderBase.js.map +1 -1
- package/dist/ListItem.d.ts +1 -0
- package/dist/ListItem.js +11 -3
- package/dist/ListItem.js.map +1 -1
- package/dist/ListItemBase.d.ts +9 -0
- package/dist/ListItemBase.js +12 -0
- package/dist/ListItemBase.js.map +1 -1
- package/dist/ListSelectAll.js +1 -1
- package/dist/ListSelectAll.js.map +1 -1
- package/dist/MediaPlayer.js +8 -0
- package/dist/MediaPlayer.js.map +1 -1
- package/dist/SearchableListBase.d.ts +10 -0
- package/dist/SearchableListBase.js +29 -6
- package/dist/SearchableListBase.js.map +1 -1
- package/dist/SearchableListBaseTemplate.js +32 -5
- package/dist/SearchableListBaseTemplate.js.map +1 -1
- package/dist/SelectBase.d.ts +65 -1
- package/dist/SelectBase.js +446 -59
- package/dist/SelectBase.js.map +1 -1
- package/dist/SelectBox.d.ts +5 -0
- package/dist/SelectBox.js +33 -2
- package/dist/SelectBox.js.map +1 -1
- package/dist/SelectBoxTemplate.js +2 -2
- package/dist/SelectBoxTemplate.js.map +1 -1
- package/dist/assets/{InputSuggestions-7eP5S95I.js → InputSuggestions-0yCPXAmq.js} +1 -1
- package/dist/assets/{ListItemStandardExpandableTextTemplate-CLcWBFcg.js → ListItemStandardExpandableTextTemplate-DjZEnCXN.js} +1 -1
- package/dist/assets/{bundle-CkxyEA8c.css → bundle-C7Yft7IG.css} +1 -1
- package/dist/assets/{bundle.esm-D4--IDh7.js → bundle.esm-DJuMR5ko.js} +241 -241
- package/dist/assets/messagebundle_lv-D06tlATH.js +1 -0
- package/dist/assets/messagebundle_nl-BKUpVV-D.js +1 -0
- package/dist/css/themes/AccordionItem.css +1 -1
- package/dist/css/themes/BreadcrumbsPopover.css +1 -1
- package/dist/css/themes/Contact.css +1 -1
- package/dist/css/themes/Divider.css +1 -1
- package/dist/css/themes/FilterMenu.css +1 -1
- package/dist/css/themes/Link.css +1 -1
- package/dist/css/themes/ListGroupHeader.css +1 -1
- package/dist/css/themes/ListItem.css +1 -1
- package/dist/css/themes/MessageStrip.css +1 -1
- package/dist/css/themes/Search.css +1 -1
- package/dist/css/themes/SelectBase.css +1 -1
- package/dist/css/themes/SelectBox.css +1 -1
- package/dist/css/themes/Slider.css +1 -1
- package/dist/css/themes/Tile.css +1 -1
- package/dist/css/themes/Toast.css +1 -1
- package/dist/css/themes/Tooltip.css +1 -1
- package/dist/custom-elements-internal.json +167 -156
- package/dist/custom-elements.json +152 -141
- package/dist/generated/assets/i18n/messagebundle_lv.json +1 -1
- package/dist/generated/assets/i18n/messagebundle_nl.json +1 -1
- package/dist/generated/templates/ListTemplate.lit.js +1 -1
- package/dist/generated/templates/ListTemplate.lit.js.map +1 -1
- package/dist/generated/themes/AccordionItem.css.d.ts +1 -1
- package/dist/generated/themes/AccordionItem.css.js +1 -1
- package/dist/generated/themes/AccordionItem.css.js.map +1 -1
- package/dist/generated/themes/BreadcrumbsPopover.css.d.ts +1 -1
- package/dist/generated/themes/BreadcrumbsPopover.css.js +1 -1
- package/dist/generated/themes/BreadcrumbsPopover.css.js.map +1 -1
- package/dist/generated/themes/Contact.css.d.ts +1 -1
- package/dist/generated/themes/Contact.css.js +1 -1
- package/dist/generated/themes/Contact.css.js.map +1 -1
- package/dist/generated/themes/Divider.css.d.ts +1 -1
- package/dist/generated/themes/Divider.css.js +1 -1
- package/dist/generated/themes/Divider.css.js.map +1 -1
- package/dist/generated/themes/FilterMenu.css.d.ts +1 -1
- package/dist/generated/themes/FilterMenu.css.js +1 -1
- package/dist/generated/themes/FilterMenu.css.js.map +1 -1
- package/dist/generated/themes/Link.css.d.ts +1 -1
- package/dist/generated/themes/Link.css.js +1 -1
- package/dist/generated/themes/Link.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/ListItem.css.d.ts +1 -1
- package/dist/generated/themes/ListItem.css.js +1 -1
- package/dist/generated/themes/ListItem.css.js.map +1 -1
- package/dist/generated/themes/MessageStrip.css.d.ts +1 -1
- package/dist/generated/themes/MessageStrip.css.js +1 -1
- package/dist/generated/themes/MessageStrip.css.js.map +1 -1
- package/dist/generated/themes/Search.css.d.ts +1 -1
- package/dist/generated/themes/Search.css.js +1 -1
- package/dist/generated/themes/Search.css.js.map +1 -1
- package/dist/generated/themes/SelectBase.css.d.ts +1 -1
- package/dist/generated/themes/SelectBase.css.js +1 -1
- package/dist/generated/themes/SelectBase.css.js.map +1 -1
- package/dist/generated/themes/SelectBox.css.d.ts +1 -1
- package/dist/generated/themes/SelectBox.css.js +1 -1
- package/dist/generated/themes/SelectBox.css.js.map +1 -1
- package/dist/generated/themes/Slider.css.d.ts +1 -1
- package/dist/generated/themes/Slider.css.js +1 -1
- package/dist/generated/themes/Slider.css.js.map +1 -1
- package/dist/generated/themes/Tile.css.d.ts +1 -1
- package/dist/generated/themes/Tile.css.js +1 -1
- package/dist/generated/themes/Tile.css.js.map +1 -1
- package/dist/generated/themes/Toast.css.d.ts +1 -1
- package/dist/generated/themes/Toast.css.js +1 -1
- package/dist/generated/themes/Toast.css.js.map +1 -1
- package/dist/generated/themes/Tooltip.css.d.ts +1 -1
- package/dist/generated/themes/Tooltip.css.js +1 -1
- package/dist/generated/themes/Tooltip.css.js.map +1 -1
- package/dist/messagebundle_lv.properties +126 -0
- package/dist/messagebundle_nl.properties +126 -0
- package/dist/vscode.html-custom-data.json +1 -1
- package/dist/web-types.json +3 -3
- package/package.json +1 -1
- package/src/Avatar.ts +52 -3
- package/src/AvatarTemplate.tsx +2 -2
- package/src/CountrySelector.ts +1 -0
- package/src/List.hbs +2 -1
- package/src/List.ts +36 -0
- package/src/ListGroupHeaderBase.ts +3 -0
- package/src/ListItem.ts +12 -3
- package/src/ListItemBase.ts +11 -0
- package/src/ListSelectAll.ts +1 -1
- package/src/MediaPlayer.ts +8 -0
- package/src/SearchableListBase.ts +31 -8
- package/src/SearchableListBaseTemplate.tsx +38 -6
- package/src/SelectBase.ts +466 -69
- package/src/SelectBox.ts +37 -2
- package/src/SelectBoxTemplate.tsx +4 -3
- package/src/generated/templates/ListTemplate.lit.ts +1 -1
- package/src/generated/themes/AccordionItem.css.ts +1 -1
- package/src/generated/themes/BreadcrumbsPopover.css.ts +1 -1
- package/src/generated/themes/Contact.css.ts +1 -1
- package/src/generated/themes/Divider.css.ts +1 -1
- package/src/generated/themes/FilterMenu.css.ts +1 -1
- package/src/generated/themes/Link.css.ts +1 -1
- package/src/generated/themes/ListGroupHeader.css.ts +1 -1
- package/src/generated/themes/ListItem.css.ts +1 -1
- package/src/generated/themes/MessageStrip.css.ts +1 -1
- package/src/generated/themes/Search.css.ts +1 -1
- package/src/generated/themes/SelectBase.css.ts +1 -1
- package/src/generated/themes/SelectBox.css.ts +1 -1
- package/src/generated/themes/Slider.css.ts +1 -1
- package/src/generated/themes/Tile.css.ts +1 -1
- package/src/generated/themes/Toast.css.ts +1 -1
- package/src/generated/themes/Tooltip.css.ts +1 -1
- package/src/i18n/messagebundle_lv.properties +126 -0
- package/src/i18n/messagebundle_nl.properties +126 -0
- package/src/themes/AccordionItem.css +8 -3
- package/src/themes/BreadcrumbsPopover.css +1 -1
- package/src/themes/Contact.css +3 -3
- package/src/themes/Divider.css +2 -0
- package/src/themes/FilterMenu.css +1 -1
- package/src/themes/Link.css +20 -2
- package/src/themes/ListGroupHeader.css +18 -1
- package/src/themes/ListItem.css +2 -1
- package/src/themes/MessageStrip.css +8 -5
- package/src/themes/Search.css +3 -3
- package/src/themes/SelectBase.css +2 -1
- package/src/themes/SelectBox.css +10 -0
- package/src/themes/Slider.css +1 -1
- package/src/themes/Tile.css +2 -2
- package/src/themes/Toast.css +1 -1
- package/src/themes/Tooltip.css +1 -1
- package/dist/assets/messagebundle_lv-BHId5zho.js +0 -1
- package/dist/assets/messagebundle_nl-etaldyfo.js +0 -1
|
@@ -69,3 +69,106 @@ describe("Avatar", () => {
|
|
|
69
69
|
cy.get("[udex-avatar]").shadow().find("ui5-avatar").should("not.be.disabled");
|
|
70
70
|
});
|
|
71
71
|
});
|
|
72
|
+
|
|
73
|
+
describe("Avatar - fallback icon on image error", () => {
|
|
74
|
+
// A valid 1x1 transparent PNG so the browser does not auto-fire `error`;
|
|
75
|
+
// the error/load events are dispatched manually to keep tests deterministic.
|
|
76
|
+
const VALID_IMAGE =
|
|
77
|
+
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";
|
|
78
|
+
|
|
79
|
+
it("shows the explicit fallback icon and hides the broken image when the slotted <img> errors", () => {
|
|
80
|
+
// Use a non-default icon ("camera") so the assertion proves the explicit
|
|
81
|
+
// fallbackIcon is honoured rather than the "employee" default.
|
|
82
|
+
cy.mount(
|
|
83
|
+
<UDExAvatar fallbackIcon="camera">
|
|
84
|
+
<img src={VALID_IMAGE} alt="Profile" />
|
|
85
|
+
</UDExAvatar>
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
cy.get("[udex-avatar]").shadow().find("ui5-avatar").should("have.class", "udex-avatar--with-image");
|
|
89
|
+
cy.get("[udex-avatar]").shadow().find("slot:not([name])").should("exist");
|
|
90
|
+
|
|
91
|
+
cy.get("[udex-avatar]").find("img").then($img => {
|
|
92
|
+
$img[0].dispatchEvent(new Event("error"));
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
cy.get("[udex-avatar]").shadow().find("slot:not([name])").should("not.exist");
|
|
96
|
+
cy.get("[udex-avatar]").shadow().find("ui5-avatar").should("not.have.class", "udex-avatar--with-image");
|
|
97
|
+
cy.get("[udex-avatar]").shadow().find("ui5-avatar").should("have.attr", "icon", "camera");
|
|
98
|
+
cy.get("[udex-avatar]").shadow().find("ui5-avatar").shadow().find("ui5-icon").should("exist");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("shows the default 'employee' fallback icon on error when no fallbackIcon is set", () => {
|
|
102
|
+
// fallbackIcon defaults to "employee"; a broken image with no explicit
|
|
103
|
+
// fallbackIcon should surface the default icon.
|
|
104
|
+
cy.mount(
|
|
105
|
+
<UDExAvatar>
|
|
106
|
+
<img src={VALID_IMAGE} alt="Profile" />
|
|
107
|
+
</UDExAvatar>
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
cy.get("[udex-avatar]").find("img").then($img => {
|
|
111
|
+
$img[0].dispatchEvent(new Event("error"));
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
cy.get("[udex-avatar]").shadow().find("slot:not([name])").should("not.exist");
|
|
115
|
+
cy.get("[udex-avatar]").shadow().find("ui5-avatar").should("not.have.class", "udex-avatar--with-image");
|
|
116
|
+
cy.get("[udex-avatar]").shadow().find("ui5-avatar").should("have.attr", "icon", "employee");
|
|
117
|
+
cy.get("[udex-avatar]").shadow().find("ui5-avatar").shadow().find("ui5-icon").should("exist");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("keeps the image and shows no fallback icon when the slotted <img> loads successfully", () => {
|
|
121
|
+
cy.mount(
|
|
122
|
+
<UDExAvatar fallbackIcon="camera">
|
|
123
|
+
<img src={VALID_IMAGE} alt="Profile" />
|
|
124
|
+
</UDExAvatar>
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
cy.get("[udex-avatar]").find("img").then($img => {
|
|
128
|
+
$img[0].dispatchEvent(new Event("load"));
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
cy.get("[udex-avatar]").shadow().find("slot:not([name])").should("exist");
|
|
132
|
+
cy.get("[udex-avatar]").shadow().find("ui5-avatar").should("have.class", "udex-avatar--with-image");
|
|
133
|
+
cy.get("[udex-avatar]").shadow().find("ui5-avatar").should("not.have.attr", "icon", "camera");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("recovers and shows the image again when a new valid image loads after an error", () => {
|
|
137
|
+
cy.mount(
|
|
138
|
+
<UDExAvatar fallbackIcon="camera">
|
|
139
|
+
<img src={VALID_IMAGE} alt="Profile" />
|
|
140
|
+
</UDExAvatar>
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
cy.get("[udex-avatar]").find("img").then($img => {
|
|
144
|
+
$img[0].dispatchEvent(new Event("error"));
|
|
145
|
+
});
|
|
146
|
+
cy.get("[udex-avatar]").shadow().find("slot:not([name])").should("not.exist");
|
|
147
|
+
cy.get("[udex-avatar]").find("img").then($img => {
|
|
148
|
+
$img[0].setAttribute("src", VALID_IMAGE);
|
|
149
|
+
$img[0].dispatchEvent(new Event("load"));
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
cy.get("[udex-avatar]").shadow().find("slot:not([name])").should("exist");
|
|
153
|
+
cy.get("[udex-avatar]").shadow().find("ui5-avatar").should("have.class", "udex-avatar--with-image");
|
|
154
|
+
cy.get("[udex-avatar]").shadow().find("ui5-avatar").should("not.have.attr", "icon", "camera");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("shows the fallback icon immediately when the slotted image is already errored on mount", () => {
|
|
158
|
+
cy.mount(<UDExAvatar fallbackIcon="camera"></UDExAvatar>);
|
|
159
|
+
|
|
160
|
+
cy.get("[udex-avatar]").then($host => {
|
|
161
|
+
const img = document.createElement("img");
|
|
162
|
+
Object.defineProperty(img, "complete", { value: true });
|
|
163
|
+
Object.defineProperty(img, "naturalWidth", { value: 0 });
|
|
164
|
+
img.setAttribute("src", "broken.png");
|
|
165
|
+
img.setAttribute("alt", "Profile");
|
|
166
|
+
$host[0].appendChild(img);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
cy.get("[udex-avatar]").shadow().find("slot:not([name])").should("not.exist");
|
|
170
|
+
cy.get("[udex-avatar]").shadow().find("ui5-avatar").should("not.have.class", "udex-avatar--with-image");
|
|
171
|
+
cy.get("[udex-avatar]").shadow().find("ui5-avatar").should("have.attr", "icon", "camera");
|
|
172
|
+
cy.get("[udex-avatar]").shadow().find("ui5-avatar").shadow().find("ui5-icon").should("exist");
|
|
173
|
+
});
|
|
174
|
+
});
|
|
@@ -235,4 +235,25 @@ describe("CountrySelector component", () => {
|
|
|
235
235
|
cy.get("[udex-country-selector]").shadow().find("[udex-list-item]").first().click();
|
|
236
236
|
cy.get("@selectionHandler").should("have.been.called");
|
|
237
237
|
});
|
|
238
|
+
|
|
239
|
+
// Added: covers aria-required wiring on the combobox input in SelectBoxTemplate
|
|
240
|
+
// (shared template). The `required` boolean (inherited from SelectBase) drives
|
|
241
|
+
// aria-required={this.required} on the inner input[role="combobox"].
|
|
242
|
+
describe("aria-required accessibility", () => {
|
|
243
|
+
it("does not set aria-required=\"true\" on the combobox input by default (required not set)", () => {
|
|
244
|
+
cy.mount(<UDExCountrySelector id="country-selector-required" label="Countries"></UDExCountrySelector>);
|
|
245
|
+
|
|
246
|
+
cy.get("#country-selector-required").shadow()
|
|
247
|
+
.find('input[role="combobox"]')
|
|
248
|
+
.should("not.have.attr", "aria-required", "true");
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("sets aria-required=\"true\" on the combobox input when required is set", () => {
|
|
252
|
+
cy.mount(<UDExCountrySelector id="country-selector-required" label="Countries" required></UDExCountrySelector>);
|
|
253
|
+
|
|
254
|
+
cy.get("#country-selector-required").shadow()
|
|
255
|
+
.find('input[role="combobox"]')
|
|
256
|
+
.should("have.attr", "aria-required", "true");
|
|
257
|
+
});
|
|
258
|
+
});
|
|
238
259
|
});
|
|
@@ -44,6 +44,16 @@ const collapsibleListRadioGroup = <UDExList id="list-group-header-collapsible" m
|
|
|
44
44
|
</UDExListGroupHeader>
|
|
45
45
|
</UDExList>;
|
|
46
46
|
|
|
47
|
+
// Regression fixture: grouped MultiSelect list WITHOUT any collapsible group header.
|
|
48
|
+
// This must stay on the listbox pattern (role="listbox", no aria-multiselectable, items
|
|
49
|
+
// role="option") because _hasCollapsibleGroups is false.
|
|
50
|
+
const nonCollapsibleList = <UDExList id="list-group-header-non-collapsible" mode="MultiSelect">
|
|
51
|
+
<UDExListGroupHeader id="group-header-non-collapsible" supporting-text="Supporting text" label="Group Header">
|
|
52
|
+
<UDExListItem>List Item1</UDExListItem>
|
|
53
|
+
<UDExListItem>List Item2</UDExListItem>
|
|
54
|
+
</UDExListGroupHeader>
|
|
55
|
+
</UDExList>;
|
|
56
|
+
|
|
47
57
|
describe("List component - collapsible group header", () => {
|
|
48
58
|
it("MultiSelect - collapse and expand the group when pressing arrow keys on grouping header", () => {
|
|
49
59
|
cy.mount(collapsibleList);
|
|
@@ -171,12 +181,14 @@ describe("List component - collapsible group header", () => {
|
|
|
171
181
|
});
|
|
172
182
|
|
|
173
183
|
// Added: covers the fix for a collapsible SingleSelect group header being incorrectly
|
|
174
|
-
// selectable via click/Enter.
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
//
|
|
184
|
+
// selectable via click/Enter. A collapsible SingleSelect header renders the plain
|
|
185
|
+
// `single-group-header-collapsible` <div> (NOT a udex-list-item). The <div> has no
|
|
186
|
+
// @click handler and only a Left/Right arrow @keydown handler — so it can never be
|
|
187
|
+
// selected, only collapsed via the arrows or the arrow <udex-button>.
|
|
188
|
+
// Updated: standalone UDExList now enters the WAI-ARIA tree pattern whenever it has a
|
|
189
|
+
// collapsible group header (via its own `_hasCollapsibleGroups` getter, which drives
|
|
190
|
+
// `_treeMode=true` on the group headers). So the header div's `singleCollapsibleHeaderRole`
|
|
191
|
+
// resolves to "treeitem" (was "button") — it is a non-selectable parent tree node.
|
|
180
192
|
describe("List - SingleSelect collapsible group header is never selectable", () => {
|
|
181
193
|
const headerDiv = () =>
|
|
182
194
|
cy.get("#group-header-collapsible").shadow().find(".udex-list__group-content");
|
|
@@ -188,10 +200,14 @@ describe("List - SingleSelect collapsible group header is never selectable", ()
|
|
|
188
200
|
cy.mount(collapsibleListSingleSelect);
|
|
189
201
|
});
|
|
190
202
|
|
|
191
|
-
|
|
203
|
+
// Updated: role is now "treeitem" (tree mode) instead of "button". A standalone
|
|
204
|
+
// collapsible list activates the tree pattern, so singleCollapsibleHeaderRole="treeitem".
|
|
205
|
+
// The header still renders as a plain div (no udex-list-item), so it is a non-selectable
|
|
206
|
+
// parent node with no aria-selected/aria-checked.
|
|
207
|
+
it("renders the header as a plain div with role=\"treeitem\" (tree mode)", () => {
|
|
192
208
|
headerDiv()
|
|
193
209
|
.should("exist")
|
|
194
|
-
.and("have.attr", "role", "
|
|
210
|
+
.and("have.attr", "role", "treeitem")
|
|
195
211
|
.and("have.attr", "tabindex", "0");
|
|
196
212
|
cy.get("#group-header-collapsible").shadow()
|
|
197
213
|
.find("udex-list-item").should("not.exist");
|
|
@@ -201,8 +217,9 @@ describe("List - SingleSelect collapsible group header is never selectable", ()
|
|
|
201
217
|
// freshly-queried `.should()` calls. The doubled `.and("not.have.attr", ...)` after a
|
|
202
218
|
// shadow `.find()` re-yielded the subject as undefined (chai-jQuery "attr on undefined").
|
|
203
219
|
// The standalone-List SingleSelect collapsible header renders the plain
|
|
204
|
-
// `single-group-header-collapsible` <div> (.udex-list__group-content) with role="
|
|
205
|
-
// (
|
|
220
|
+
// `single-group-header-collapsible` <div> (.udex-list__group-content) with role="treeitem"
|
|
221
|
+
// (tree mode), aria-expanded, and no selection-state attribute (parent tree nodes are not
|
|
222
|
+
// themselves selectable — only leaf items carry aria-selected).
|
|
206
223
|
it("exposes aria-expanded but no selection-state attribute", () => {
|
|
207
224
|
headerDiv().should("have.attr", "aria-expanded", "true");
|
|
208
225
|
headerDiv().should("not.have.attr", "aria-selected");
|
|
@@ -276,6 +293,98 @@ describe("List - SingleSelect collapsible group header is never selectable", ()
|
|
|
276
293
|
});
|
|
277
294
|
});
|
|
278
295
|
|
|
296
|
+
// Added: covers the WAI-ARIA tree pattern that the standalone <udex-list> activates
|
|
297
|
+
// automatically whenever it has at least one collapsible <udex-list-group-header>.
|
|
298
|
+
// The trigger is UDExList's own `_hasCollapsibleGroups` getter (independent of
|
|
299
|
+
// SearchableListBase.supportsTreeStructure), which sets `_treeMode=true` on the group
|
|
300
|
+
// headers and `accessibleRole="treeitem"` on the leaf items. In that mode the inner
|
|
301
|
+
// `#udex-list` div gets role="tree" (aria-multiselectable="true" only in MultiSelect).
|
|
302
|
+
describe("List - collapsible group header uses tree pattern", () => {
|
|
303
|
+
const innerList = () =>
|
|
304
|
+
cy.get("[udex-list]").shadow().find("#udex-list");
|
|
305
|
+
|
|
306
|
+
it("MultiSelect: inner #udex-list div has role=\"tree\"", () => {
|
|
307
|
+
cy.mount(collapsibleList);
|
|
308
|
+
innerList().should("have.attr", "role", "tree");
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("SingleSelect: inner #udex-list div has role=\"tree\"", () => {
|
|
312
|
+
cy.mount(collapsibleListSingleSelect);
|
|
313
|
+
innerList().should("have.attr", "role", "tree");
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it("MultiSelect collapsible: inner #udex-list has aria-multiselectable=\"true\"", () => {
|
|
317
|
+
cy.mount(collapsibleList);
|
|
318
|
+
innerList().should("have.attr", "aria-multiselectable", "true");
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("SingleSelect collapsible: inner #udex-list does NOT have aria-multiselectable", () => {
|
|
322
|
+
cy.mount(collapsibleListSingleSelect);
|
|
323
|
+
innerList().should("not.have.attr", "aria-multiselectable");
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it("group header host gets the _tree-mode attribute", () => {
|
|
327
|
+
cy.mount(collapsibleList);
|
|
328
|
+
cy.get("#group-header-collapsible").should("have.attr", "_tree-mode");
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("MultiSelect collapsible header: inner udex-list-item exposes accessible-role=\"treeitem\"", () => {
|
|
332
|
+
cy.mount(collapsibleList);
|
|
333
|
+
// MultiSelect => useListItemHeader=true => the header renders a <udex-list-item>.
|
|
334
|
+
cy.get("#group-header-collapsible").shadow()
|
|
335
|
+
.find("udex-list-item").should("have.attr", "accessible-role", "treeitem");
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it("MultiSelect collapsible header: shadow [part=item] has role=\"treeitem\" and aria-expanded", () => {
|
|
339
|
+
cy.mount(collapsibleList);
|
|
340
|
+
cy.get("#group-header-collapsible").shadow()
|
|
341
|
+
.find("udex-list-item").shadow()
|
|
342
|
+
.find("[part=item]")
|
|
343
|
+
.should("have.attr", "role", "treeitem")
|
|
344
|
+
.and("have.attr", "aria-expanded", "true");
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it("SingleSelect collapsible header: plain div gets role=\"treeitem\"", () => {
|
|
348
|
+
cy.mount(collapsibleListSingleSelect);
|
|
349
|
+
// SingleSelect => useListItemHeader=false => plain .udex-list__group-content <div>,
|
|
350
|
+
// whose role comes from singleCollapsibleHeaderRole => "treeitem" in tree mode.
|
|
351
|
+
cy.get("#group-header-collapsible").shadow()
|
|
352
|
+
.find(".udex-list__group-content").should("have.attr", "role", "treeitem");
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it("child items expose accessible-role=\"treeitem\" on the host", () => {
|
|
356
|
+
cy.mount(collapsibleList);
|
|
357
|
+
cy.get("#group-header-collapsible").find("udex-list-item")
|
|
358
|
+
.each($item => {
|
|
359
|
+
cy.wrap($item).should("have.attr", "accessible-role", "treeitem");
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("child items render role=\"treeitem\" on their shadow [part=item]", () => {
|
|
364
|
+
cy.mount(collapsibleList);
|
|
365
|
+
cy.get("#group-header-collapsible").find("udex-list-item").eq(0).shadow()
|
|
366
|
+
.find("[part=item]").should("have.attr", "role", "treeitem");
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// Regression: a grouped list WITHOUT any collapsible header must NOT enter tree mode.
|
|
370
|
+
it("non-collapsible grouped list keeps role=\"listbox\" (no tree pattern)", () => {
|
|
371
|
+
cy.mount(nonCollapsibleList);
|
|
372
|
+
innerList().should("have.attr", "role", "listbox");
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it("non-collapsible grouped list has no aria-multiselectable and no _tree-mode", () => {
|
|
376
|
+
cy.mount(nonCollapsibleList);
|
|
377
|
+
innerList().should("not.have.attr", "aria-multiselectable");
|
|
378
|
+
cy.get("#group-header-non-collapsible").should("not.have.attr", "_tree-mode");
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it("non-collapsible list: child items render role=\"option\" on shadow [part=item]", () => {
|
|
382
|
+
cy.mount(nonCollapsibleList);
|
|
383
|
+
cy.get("#group-header-non-collapsible").find("udex-list-item").eq(0).shadow()
|
|
384
|
+
.find("[part=item]").should("have.attr", "role", "option");
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
279
388
|
describe("List component - SingleContent accessible name", () => {
|
|
280
389
|
it("uses accessible-name as aria-label when only accessible-name is set", () => {
|
|
281
390
|
cy.mount(
|