@sapudex/web-components 2.33.0 → 2.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/cypress/specs/FilterMenu.cy.tsx +53 -0
  2. package/cypress/specs/ListItem.cy.tsx +48 -0
  3. package/cypress/specs/MastheadMenu.cy.tsx +564 -0
  4. package/cypress/specs/MastheadMobileMenu.cy.tsx +573 -0
  5. package/cypress/specs/SelectBox.cy.tsx +63 -0
  6. package/dist/ListItem.d.ts +3 -0
  7. package/dist/ListItem.js +10 -1
  8. package/dist/ListItem.js.map +1 -1
  9. package/dist/ListItemBase.d.ts +29 -0
  10. package/dist/ListItemBase.js +22 -0
  11. package/dist/ListItemBase.js.map +1 -1
  12. package/dist/MastheadItem.d.ts +2 -1
  13. package/dist/MastheadItem.js.map +1 -1
  14. package/dist/MastheadMenu.d.ts +2 -1
  15. package/dist/MastheadMenu.js.map +1 -1
  16. package/dist/SearchableListBase.d.ts +3 -1
  17. package/dist/SearchableListBase.js +7 -1
  18. package/dist/SearchableListBase.js.map +1 -1
  19. package/dist/SelectBase.js +4 -2
  20. package/dist/SelectBase.js.map +1 -1
  21. package/dist/assets/{InputSuggestions-B_Dl2L6w.js → InputSuggestions-Bz7HZXUc.js} +1 -1
  22. package/dist/assets/{ListItemStandardExpandableTextTemplate-9Z-aZmJx.js → ListItemStandardExpandableTextTemplate-CjE7hAvu.js} +1 -1
  23. package/dist/assets/{bundle-DbTEs-Za.css → bundle-JYo3bV8v.css} +1 -1
  24. package/dist/assets/{bundle.esm-BlIxH9k1.js → bundle.esm-mAR7ytdB.js} +216 -216
  25. package/dist/css/themes/ListItem.css +1 -1
  26. package/dist/css/themes/MastheadMenu.css +1 -1
  27. package/dist/css/themes/MastheadMobileMenu.css +1 -1
  28. package/dist/css/themes/RatingIndicator.css +1 -1
  29. package/dist/css/themes/Slider.css +1 -1
  30. package/dist/custom-elements-internal.json +1113 -23
  31. package/dist/custom-elements.json +1114 -24
  32. package/dist/generated/templates/ListItemTemplate.lit.js +32 -28
  33. package/dist/generated/templates/ListItemTemplate.lit.js.map +1 -1
  34. package/dist/generated/templates/MastheadMenuTemplate.lit.js +8 -1
  35. package/dist/generated/templates/MastheadMenuTemplate.lit.js.map +1 -1
  36. package/dist/generated/templates/MastheadMobileMenuTemplate.lit.js +10 -4
  37. package/dist/generated/templates/MastheadMobileMenuTemplate.lit.js.map +1 -1
  38. package/dist/generated/templates/SearchableListBaseTemplate.lit.js +3 -3
  39. package/dist/generated/templates/SearchableListBaseTemplate.lit.js.map +1 -1
  40. package/dist/generated/templates/SelectBoxTemplate.lit.js +12 -10
  41. package/dist/generated/templates/SelectBoxTemplate.lit.js.map +1 -1
  42. package/dist/generated/themes/ListItem.css.d.ts +1 -1
  43. package/dist/generated/themes/ListItem.css.js +1 -1
  44. package/dist/generated/themes/ListItem.css.js.map +1 -1
  45. package/dist/generated/themes/MastheadMenu.css.d.ts +1 -1
  46. package/dist/generated/themes/MastheadMenu.css.js +1 -1
  47. package/dist/generated/themes/MastheadMenu.css.js.map +1 -1
  48. package/dist/generated/themes/MastheadMobileMenu.css.d.ts +1 -1
  49. package/dist/generated/themes/MastheadMobileMenu.css.js +1 -1
  50. package/dist/generated/themes/MastheadMobileMenu.css.js.map +1 -1
  51. package/dist/generated/themes/RatingIndicator.css.d.ts +1 -1
  52. package/dist/generated/themes/RatingIndicator.css.js +1 -1
  53. package/dist/generated/themes/RatingIndicator.css.js.map +1 -1
  54. package/dist/generated/themes/Slider.css.d.ts +1 -1
  55. package/dist/generated/themes/Slider.css.js +1 -1
  56. package/dist/generated/themes/Slider.css.js.map +1 -1
  57. package/dist/vscode.html-custom-data.json +220 -0
  58. package/dist/web-types.json +361 -1
  59. package/package.json +1 -1
  60. package/src/ListItem.hbs +26 -2
  61. package/src/ListItem.ts +12 -1
  62. package/src/ListItemBase.ts +40 -0
  63. package/src/MastheadItem.ts +2 -1
  64. package/src/MastheadMenu.hbs +35 -0
  65. package/src/MastheadMenu.ts +2 -1
  66. package/src/MastheadMobileMenu.hbs +40 -9
  67. package/src/SearchableListBase.hbs +12 -0
  68. package/src/SearchableListBase.ts +11 -2
  69. package/src/SelectBase.ts +4 -2
  70. package/src/SelectBox.hbs +13 -5
  71. package/src/generated/templates/ListItemTemplate.lit.ts +32 -28
  72. package/src/generated/templates/MastheadMenuTemplate.lit.ts +8 -1
  73. package/src/generated/templates/MastheadMobileMenuTemplate.lit.ts +10 -4
  74. package/src/generated/templates/SearchableListBaseTemplate.lit.ts +3 -3
  75. package/src/generated/templates/SelectBoxTemplate.lit.ts +12 -10
  76. package/src/generated/themes/ListItem.css.ts +1 -1
  77. package/src/generated/themes/MastheadMenu.css.ts +1 -1
  78. package/src/generated/themes/MastheadMobileMenu.css.ts +1 -1
  79. package/src/generated/themes/RatingIndicator.css.ts +1 -1
  80. package/src/generated/themes/Slider.css.ts +1 -1
  81. package/src/themes/ListItem.css +6 -4
  82. package/src/themes/MastheadMenu.css +45 -0
  83. package/src/themes/MastheadMobileMenu.css +45 -0
@@ -1,6 +1,18 @@
1
1
  import {UDExFilterMenu, UDExFilterMenuItem, UDExFilterMenuGroupHeader, UDExFilterMenuSelectAll} from "../support/component-imports.js";
2
2
  import {UDExListMode} from "../../src/types/List.js";
3
3
 
4
+ const filterMenuWithRating = <UDExFilterMenu
5
+ id="filter-menu-rating"
6
+ filter-name="Rating"
7
+ show-search-input="true"
8
+ mode={UDExListMode.MultiSelect}>
9
+ <UDExFilterMenuItem rating="5" ratingCount="5.0" totalCount="23"></UDExFilterMenuItem>
10
+ <UDExFilterMenuItem rating="4" ratingCount="4.0" totalCount="17"></UDExFilterMenuItem>
11
+ <UDExFilterMenuItem rating="3" ratingCount="3.0" totalCount="34"></UDExFilterMenuItem>
12
+ <UDExFilterMenuItem rating="2" ratingCount="2.0" totalCount="40"></UDExFilterMenuItem>
13
+ <UDExFilterMenuItem rating="1" ratingCount="1.0" totalCount="73"></UDExFilterMenuItem>
14
+ </UDExFilterMenu>
15
+
4
16
  const filterMenu = <>
5
17
  <div id="resetClick"></div>
6
18
  <div id="resultClick"></div>
@@ -911,4 +923,45 @@ describe("Filter Menu component", () => {
911
923
  cy.get("#resultSelectAll").should("have.text", "ui5-select-all");
912
924
  });
913
925
  });
926
+
927
+ describe("Filter Menu Rating Indicator", () => {
928
+ it("renders rating indicator in dropdown items when rating is set", () => {
929
+ cy.mount(filterMenuWithRating);
930
+ cy.get("#filter-menu-rating").shadow().find(".udex-filter-menu__trigger").click();
931
+
932
+ cy.get("#filter-menu-rating").shadow().find("udex-list-item").eq(0).shadow()
933
+ .find("[udex-rating-indicator]")
934
+ .should("exist");
935
+ });
936
+
937
+ it("passes correct rating attributes to dropdown list items", () => {
938
+ cy.mount(filterMenuWithRating);
939
+ cy.get("#filter-menu-rating").shadow().find(".udex-filter-menu__trigger").click();
940
+
941
+ cy.get("#filter-menu-rating").shadow().find("udex-list-item").eq(0)
942
+ .should("have.attr", "rating", "5")
943
+ .and("have.attr", "rating-count", "5.0")
944
+ .and("have.attr", "total-count", "23");
945
+ });
946
+
947
+ it("renders correct number of rating items", () => {
948
+ cy.mount(filterMenuWithRating);
949
+ cy.get("#filter-menu-rating").shadow().find(".udex-filter-menu__trigger").click();
950
+
951
+ cy.get("#filter-menu-rating").shadow().find("udex-list-item")
952
+ .should("have.length", 5);
953
+ });
954
+
955
+ it("selects a rating item on click", () => {
956
+ cy.mount(filterMenuWithRating);
957
+ cy.get("#filter-menu-rating").shadow().find(".udex-filter-menu__trigger").click();
958
+
959
+ cy.get("#filter-menu-rating").shadow().find("udex-list-item").eq(0).shadow()
960
+ .find(".udex-list__item").click();
961
+
962
+ cy.get("#filter-menu-rating").shadow().find("udex-list-item").eq(0).shadow()
963
+ .find(".udex-list__item")
964
+ .should("have.attr", "data-selected", "true");
965
+ });
966
+ });
914
967
  });
@@ -4,6 +4,7 @@ import { UDExAvatarShape } from "../../dist/Avatar.js";
4
4
  import { UDExAvatarSize } from "../../src/Avatar.js";
5
5
  import { UDExAdditionalTextState, UDExListStyleType } from "../../src/types/List.js";
6
6
  import UDExStatusBadgeValueState from "../../dist/types/StatusBadgeValueState.js";
7
+ import { UDExDisplayType } from "../../src/RatingIndicator.js";
7
8
 
8
9
  const defaultListItem = <UDExListItem
9
10
  id="listItem"
@@ -73,6 +74,13 @@ const listItemWithIcons = <UDExListItem
73
74
  <UDExButton accessible-name='Action' slot='secondIcon' design='Tertiary' icon='decline' size='Small'></UDExButton>
74
75
  </UDExListItem>
75
76
 
77
+ const listItemWithRating = <UDExListItem
78
+ id="listItemRating"
79
+ rating="4"
80
+ ratingCount="4.0"
81
+ totalCount="20">
82
+ </UDExListItem>
83
+
76
84
  describe("ListItem", () => {
77
85
  describe("Default rendering", () => {
78
86
  it("should render the component", () => {
@@ -169,4 +177,44 @@ describe("ListItem", () => {
169
177
  cy.get("#listItemWarning").shadow().find(".udex-list__add-text").should("have.css", "color", "rgb(141, 42, 0)");
170
178
  });
171
179
  });
180
+
181
+ describe("RatingIndicator", () => {
182
+ beforeEach(() => {
183
+ cy.mount(listItemWithRating);
184
+ cy.get("#listItemRating").shadow().find("[udex-rating-indicator]").as("rating");
185
+ });
186
+
187
+ it("renders rating indicator when rating is set", () => {
188
+ cy.get("@rating").should("exist");
189
+ });
190
+
191
+ it("passes correct value to rating indicator", () => {
192
+ cy.get("@rating").should("have.attr", "value", "4");
193
+ });
194
+
195
+ it("passes correct rating-count to rating indicator", () => {
196
+ cy.get("@rating").should("have.attr", "rating-count", "4.0");
197
+ });
198
+
199
+ it("passes correct total-count to rating indicator", () => {
200
+ cy.get("@rating").should("have.attr", "total-count", "20");
201
+ });
202
+
203
+ it("sets max to 5 by default", () => {
204
+ cy.get("@rating").should("have.attr", "max", "5");
205
+ });
206
+
207
+ it("sets display to Desktop by default", () => {
208
+ cy.get("@rating").should("have.attr", "display", UDExDisplayType.Desktop);
209
+ });
210
+
211
+ it("sets disabled and readonly states by default", () => {
212
+ cy.get("@rating").should("have.attr", "disabled");
213
+ cy.get("@rating").should("have.attr", "readonly");
214
+ });
215
+
216
+ it("sets correct aria-label on rating indicator", () => {
217
+ cy.get("@rating").should("have.attr", "aria-label", "Rating 4.0, Total 20, Rating Indicator 4 of 5");
218
+ });
219
+ });
172
220
  });
@@ -0,0 +1,564 @@
1
+ import { UDExMastheadMenu } from "../support/component-imports.js";
2
+
3
+ // Fixture: items with 3rd-level sub-items (no url on parent items — sub-category renders as <span>)
4
+ const itemsWithSubItems = [
5
+ {
6
+ subMenuLinkItems: [
7
+ {
8
+ category: "Products",
9
+ url: "https://example.com/products",
10
+ items: [
11
+ {
12
+ title: "Cloud Solutions",
13
+ subItems: [
14
+ {
15
+ title: "SAP S/4HANA Cloud",
16
+ url: "https://example.com/s4hana",
17
+ externalLink: false,
18
+ },
19
+ {
20
+ title: "SAP BTP",
21
+ url: "https://example.com/btp",
22
+ externalLink: false,
23
+ },
24
+ ],
25
+ },
26
+ {
27
+ title: "On-Premise Solutions",
28
+ subItems: [
29
+ {
30
+ title: "SAP ERP",
31
+ url: "https://example.com/erp",
32
+ externalLink: false,
33
+ },
34
+ ],
35
+ },
36
+ ],
37
+ },
38
+ ],
39
+ },
40
+ ];
41
+
42
+ // Fixture: items with 3rd-level sub-items AND url on the parent item — sub-category renders as <udex-masthead-menu-item> link
43
+ const itemsWithLinkedSubCategory = [
44
+ {
45
+ subMenuLinkItems: [
46
+ {
47
+ category: "AI Platform",
48
+ items: [
49
+ {
50
+ title: "Build",
51
+ url: "https://example.com/build",
52
+ externalLink: false,
53
+ subItems: [
54
+ {
55
+ title: "Develop",
56
+ url: "https://example.com/develop",
57
+ externalLink: false,
58
+ },
59
+ {
60
+ title: "Deploy",
61
+ url: "https://example.com/deploy",
62
+ externalLink: false,
63
+ },
64
+ ],
65
+ },
66
+ {
67
+ title: "Run",
68
+ url: "https://example.com/run",
69
+ externalLink: false,
70
+ subItems: [
71
+ {
72
+ title: "Monitor",
73
+ url: "https://example.com/monitor",
74
+ externalLink: false,
75
+ },
76
+ ],
77
+ },
78
+ ],
79
+ },
80
+ ],
81
+ },
82
+ ];
83
+
84
+ // Fixture: mixed linked and non-linked sub-categories within the same list
85
+ const mixedLinkedSubCategories = [
86
+ {
87
+ subMenuLinkItems: [
88
+ {
89
+ category: "Platform",
90
+ items: [
91
+ {
92
+ title: "Build",
93
+ url: "https://example.com/build",
94
+ externalLink: false,
95
+ subItems: [
96
+ {
97
+ title: "Develop",
98
+ url: "https://example.com/develop",
99
+ externalLink: false,
100
+ },
101
+ ],
102
+ },
103
+ {
104
+ title: "Design",
105
+ // no url — renders as <span>
106
+ subItems: [
107
+ {
108
+ title: "Figma",
109
+ url: "https://example.com/figma",
110
+ externalLink: false,
111
+ },
112
+ ],
113
+ },
114
+ ],
115
+ },
116
+ ],
117
+ },
118
+ ];
119
+
120
+ // Fixture: items without sub-items (plain links only)
121
+ const itemsWithoutSubItems = [
122
+ {
123
+ subMenuLinkItems: [
124
+ {
125
+ category: "Services",
126
+ items: [
127
+ {
128
+ title: "Consulting",
129
+ url: "https://example.com/consulting",
130
+ externalLink: false,
131
+ },
132
+ {
133
+ title: "Training",
134
+ url: "https://example.com/training",
135
+ externalLink: false,
136
+ },
137
+ ],
138
+ },
139
+ ],
140
+ },
141
+ ];
142
+
143
+ // Fixture: mixed — one category with sub-items, another without
144
+ const mixedItems = [
145
+ {
146
+ subMenuLinkItems: [
147
+ {
148
+ category: "Solutions",
149
+ items: [
150
+ {
151
+ title: "Cloud Solutions",
152
+ subItems: [
153
+ {
154
+ title: "SAP S/4HANA Cloud",
155
+ url: "https://example.com/s4hana",
156
+ externalLink: false,
157
+ },
158
+ ],
159
+ },
160
+ {
161
+ title: "Direct Link Item",
162
+ url: "https://example.com/direct",
163
+ externalLink: false,
164
+ },
165
+ ],
166
+ },
167
+ ],
168
+ },
169
+ ];
170
+
171
+ describe("UDExMastheadMenu — 3rd-level navigation (sub-items)", () => {
172
+ describe("Rendering sub-categories", () => {
173
+ beforeEach(() => {
174
+ cy.mount(
175
+ <UDExMastheadMenu
176
+ id="menu-sub-items"
177
+ subItems={JSON.stringify(itemsWithSubItems)}
178
+ />
179
+ );
180
+ cy.get("[udex-masthead-menu]").shadow().as("shadow");
181
+ });
182
+
183
+ it("renders a .udex-masthead-menu__sub-category for each item with subItems", () => {
184
+ cy.get("@shadow")
185
+ .find(".udex-masthead-menu__sub-category")
186
+ .should("have.length", 2);
187
+ });
188
+
189
+ // Updated: clarified that this covers the no-url case (item.url absent)
190
+ it("renders sub-category as a <span> when item has no url", () => {
191
+ cy.get("@shadow")
192
+ .find(".udex-masthead-menu__sub-category")
193
+ .first()
194
+ .should("have.prop", "tagName", "SPAN");
195
+ });
196
+
197
+ it("displays the correct title text in each sub-category", () => {
198
+ cy.get("@shadow")
199
+ .find(".udex-masthead-menu__sub-category")
200
+ .eq(0)
201
+ .should("have.text", "Cloud Solutions");
202
+ cy.get("@shadow")
203
+ .find(".udex-masthead-menu__sub-category")
204
+ .eq(1)
205
+ .should("have.text", "On-Premise Solutions");
206
+ });
207
+ });
208
+
209
+ describe("Rendering sub-list and sub-items", () => {
210
+ beforeEach(() => {
211
+ cy.mount(
212
+ <UDExMastheadMenu
213
+ id="menu-sub-items"
214
+ subItems={JSON.stringify(itemsWithSubItems)}
215
+ />
216
+ );
217
+ cy.get("[udex-masthead-menu]").shadow().as("shadow");
218
+ });
219
+
220
+ it("renders a .udex-masthead-menu__sub-list for each item with subItems", () => {
221
+ cy.get("@shadow")
222
+ .find(".udex-masthead-menu__sub-list")
223
+ .should("have.length", 2);
224
+ });
225
+
226
+ it("renders udex-masthead-menu-item elements inside each sub-list", () => {
227
+ // First sub-list: "Cloud Solutions" has 2 sub-items
228
+ cy.get("@shadow")
229
+ .find(".udex-masthead-menu__sub-list")
230
+ .eq(0)
231
+ .find("udex-masthead-menu-item")
232
+ .should("have.length", 2);
233
+
234
+ // Second sub-list: "On-Premise Solutions" has 1 sub-item
235
+ cy.get("@shadow")
236
+ .find(".udex-masthead-menu__sub-list")
237
+ .eq(1)
238
+ .find("udex-masthead-menu-item")
239
+ .should("have.length", 1);
240
+ });
241
+
242
+ it("renders the correct label for each sub-item menu-item", () => {
243
+ cy.get("@shadow")
244
+ .find(".udex-masthead-menu__sub-list")
245
+ .eq(0)
246
+ .find("udex-masthead-menu-item")
247
+ .eq(0)
248
+ .should("have.attr", "label", "SAP S/4HANA Cloud");
249
+
250
+ cy.get("@shadow")
251
+ .find(".udex-masthead-menu__sub-list")
252
+ .eq(0)
253
+ .find("udex-masthead-menu-item")
254
+ .eq(1)
255
+ .should("have.attr", "label", "SAP BTP");
256
+ });
257
+
258
+ it("sets the correct url attribute on sub-item menu-items", () => {
259
+ cy.get("@shadow")
260
+ .find(".udex-masthead-menu__sub-list")
261
+ .eq(0)
262
+ .find("udex-masthead-menu-item")
263
+ .eq(0)
264
+ .should("have.attr", "url", "https://example.com/s4hana");
265
+ });
266
+ });
267
+
268
+ describe("Sub-list accessibility", () => {
269
+ beforeEach(() => {
270
+ cy.mount(
271
+ <UDExMastheadMenu
272
+ id="menu-sub-items"
273
+ subItems={JSON.stringify(itemsWithSubItems)}
274
+ />
275
+ );
276
+ cy.get("[udex-masthead-menu]").shadow().as("shadow");
277
+ });
278
+
279
+ it("sets aria-label on the sub-list matching the sub-category title", () => {
280
+ cy.get("@shadow")
281
+ .find(".udex-masthead-menu__sub-list")
282
+ .eq(0)
283
+ .should("have.attr", "aria-label", "Cloud Solutions");
284
+
285
+ cy.get("@shadow")
286
+ .find(".udex-masthead-menu__sub-list")
287
+ .eq(1)
288
+ .should("have.attr", "aria-label", "On-Premise Solutions");
289
+ });
290
+ });
291
+
292
+ describe("Regression: items without subItems still render normally", () => {
293
+ beforeEach(() => {
294
+ cy.mount(
295
+ <UDExMastheadMenu
296
+ id="menu-no-sub-items"
297
+ subItems={JSON.stringify(itemsWithoutSubItems)}
298
+ />
299
+ );
300
+ cy.get("[udex-masthead-menu]").shadow().as("shadow");
301
+ });
302
+
303
+ it("does not render any .udex-masthead-menu__sub-category when no items have subItems", () => {
304
+ cy.get("@shadow")
305
+ .find(".udex-masthead-menu__sub-category")
306
+ .should("not.exist");
307
+ });
308
+
309
+ it("does not render any .udex-masthead-menu__sub-list when no items have subItems", () => {
310
+ cy.get("@shadow")
311
+ .find(".udex-masthead-menu__sub-list")
312
+ .should("not.exist");
313
+ });
314
+
315
+ it("renders udex-masthead-menu-item elements directly in the main list", () => {
316
+ cy.get("@shadow")
317
+ .find(".udex-masthead-menu__list")
318
+ .find("udex-masthead-menu-item")
319
+ .should("have.length", 2);
320
+ });
321
+
322
+ it("renders the correct labels for plain items", () => {
323
+ cy.get("@shadow")
324
+ .find(".udex-masthead-menu__list udex-masthead-menu-item")
325
+ .eq(0)
326
+ .should("have.attr", "label", "Consulting");
327
+
328
+ cy.get("@shadow")
329
+ .find(".udex-masthead-menu__list udex-masthead-menu-item")
330
+ .eq(1)
331
+ .should("have.attr", "label", "Training");
332
+ });
333
+ });
334
+
335
+ describe("Mixed content: items with and without subItems in the same list", () => {
336
+ beforeEach(() => {
337
+ cy.mount(
338
+ <UDExMastheadMenu
339
+ id="menu-mixed"
340
+ subItems={JSON.stringify(mixedItems)}
341
+ />
342
+ );
343
+ cy.get("[udex-masthead-menu]").shadow().as("shadow");
344
+ });
345
+
346
+ it("renders a sub-category for the item that has subItems", () => {
347
+ cy.get("@shadow")
348
+ .find(".udex-masthead-menu__sub-category")
349
+ .should("have.length", 1)
350
+ .and("have.text", "Cloud Solutions");
351
+ });
352
+
353
+ it("renders a sub-list with the sub-item for the grouped item", () => {
354
+ cy.get("@shadow")
355
+ .find(".udex-masthead-menu__sub-list")
356
+ .should("have.length", 1)
357
+ .find("udex-masthead-menu-item")
358
+ .should("have.length", 1)
359
+ .and("have.attr", "label", "SAP S/4HANA Cloud");
360
+ });
361
+
362
+ it("also renders the plain item as a direct udex-masthead-menu-item in the main list", () => {
363
+ // The main .udex-masthead-menu__list should contain both the sub-category group
364
+ // and the direct link item. The direct link item sits at the top level of the list.
365
+ cy.get("@shadow")
366
+ .find(".udex-masthead-menu__list > li > udex-masthead-menu-item")
367
+ .should("have.length", 1)
368
+ .and("have.attr", "label", "Direct Link Item");
369
+ });
370
+ });
371
+
372
+ // CSS variable remapping for linked sub-categories is handled by the
373
+ // `udex-masthead-menu-item.udex-masthead-menu__sub-category` CSS rule, which sets
374
+ // --udex-masthead-menu-item-color, --udex-masthead-menu-item-font-size,
375
+ // --udex-masthead-menu-item-line-height, --udex-masthead-menu-item-font-weight,
376
+ // and text-transform from the --udex-masthead-menu-sub-category-* variables.
377
+ describe("Linked sub-category: item with subItems AND url renders as udex-masthead-menu-item", () => {
378
+ beforeEach(() => {
379
+ cy.mount(
380
+ <UDExMastheadMenu
381
+ id="menu-linked-sub"
382
+ subItems={JSON.stringify(itemsWithLinkedSubCategory)}
383
+ />
384
+ );
385
+ cy.get("[udex-masthead-menu]").shadow().as("shadow");
386
+ });
387
+
388
+ describe("Rendering", () => {
389
+ it("renders a .udex-masthead-menu__sub-category for each item with subItems", () => {
390
+ cy.get("@shadow")
391
+ .find(".udex-masthead-menu__sub-category")
392
+ .should("have.length", 2);
393
+ });
394
+
395
+ it("applies the udex-masthead-menu__sub-category class on the linked udex-masthead-menu-item", () => {
396
+ cy.get("@shadow")
397
+ .find("udex-masthead-menu-item.udex-masthead-menu__sub-category")
398
+ .should("have.length", 2)
399
+ .each(($el) => {
400
+ cy.wrap($el).should("have.class", "udex-masthead-menu__sub-category");
401
+ });
402
+ });
403
+
404
+ it("renders sub-category as a <udex-masthead-menu-item>, not a <span>", () => {
405
+ cy.get("@shadow")
406
+ .find(".udex-masthead-menu__sub-category")
407
+ .first()
408
+ .should("have.prop", "tagName")
409
+ .then((tagName: string) => {
410
+ expect(tagName.toLowerCase()).to.equal("udex-masthead-menu-item");
411
+ });
412
+ });
413
+
414
+ it("displays the correct label attribute on each linked sub-category", () => {
415
+ cy.get("@shadow")
416
+ .find(".udex-masthead-menu__sub-category")
417
+ .eq(0)
418
+ .should("have.attr", "label", "Build");
419
+
420
+ cy.get("@shadow")
421
+ .find(".udex-masthead-menu__sub-category")
422
+ .eq(1)
423
+ .should("have.attr", "label", "Run");
424
+ });
425
+
426
+ it("sets the correct url attribute on each linked sub-category", () => {
427
+ cy.get("@shadow")
428
+ .find(".udex-masthead-menu__sub-category")
429
+ .eq(0)
430
+ .should("have.attr", "url", "https://example.com/build");
431
+
432
+ cy.get("@shadow")
433
+ .find(".udex-masthead-menu__sub-category")
434
+ .eq(1)
435
+ .should("have.attr", "url", "https://example.com/run");
436
+ });
437
+ });
438
+
439
+ describe("Accessibility", () => {
440
+ it("sets accessible-role='link' on the linked sub-category", () => {
441
+ cy.get("@shadow")
442
+ .find(".udex-masthead-menu__sub-category")
443
+ .each(($el) => {
444
+ cy.wrap($el).should("have.attr", "accessible-role", "link");
445
+ });
446
+ });
447
+
448
+ it("still renders the sub-list with aria-label matching the sub-category title", () => {
449
+ cy.get("@shadow")
450
+ .find(".udex-masthead-menu__sub-list")
451
+ .eq(0)
452
+ .should("have.attr", "aria-label", "Build");
453
+
454
+ cy.get("@shadow")
455
+ .find(".udex-masthead-menu__sub-list")
456
+ .eq(1)
457
+ .should("have.attr", "aria-label", "Run");
458
+ });
459
+ });
460
+
461
+ describe("Sub-items under linked sub-category", () => {
462
+ it("renders the correct number of sub-items in each sub-list", () => {
463
+ // "Build" has 2 sub-items: Develop, Deploy
464
+ cy.get("@shadow")
465
+ .find(".udex-masthead-menu__sub-list")
466
+ .eq(0)
467
+ .find("udex-masthead-menu-item")
468
+ .should("have.length", 2);
469
+
470
+ // "Run" has 1 sub-item: Monitor
471
+ cy.get("@shadow")
472
+ .find(".udex-masthead-menu__sub-list")
473
+ .eq(1)
474
+ .find("udex-masthead-menu-item")
475
+ .should("have.length", 1);
476
+ });
477
+
478
+ it("renders the correct labels and urls on sub-items", () => {
479
+ cy.get("@shadow")
480
+ .find(".udex-masthead-menu__sub-list")
481
+ .eq(0)
482
+ .find("udex-masthead-menu-item")
483
+ .eq(0)
484
+ .should("have.attr", "label", "Develop")
485
+ .and("have.attr", "url", "https://example.com/develop");
486
+
487
+ cy.get("@shadow")
488
+ .find(".udex-masthead-menu__sub-list")
489
+ .eq(0)
490
+ .find("udex-masthead-menu-item")
491
+ .eq(1)
492
+ .should("have.attr", "label", "Deploy")
493
+ .and("have.attr", "url", "https://example.com/deploy");
494
+ });
495
+ });
496
+ });
497
+
498
+ describe("Mixed linked and non-linked sub-categories in the same list", () => {
499
+ beforeEach(() => {
500
+ cy.mount(
501
+ <UDExMastheadMenu
502
+ id="menu-mixed-linked"
503
+ subItems={JSON.stringify(mixedLinkedSubCategories)}
504
+ />
505
+ );
506
+ cy.get("[udex-masthead-menu]").shadow().as("shadow");
507
+ });
508
+
509
+ it("renders two .udex-masthead-menu__sub-category elements", () => {
510
+ cy.get("@shadow")
511
+ .find(".udex-masthead-menu__sub-category")
512
+ .should("have.length", 2);
513
+ });
514
+
515
+ it("renders the linked sub-category (Build) as a udex-masthead-menu-item", () => {
516
+ cy.get("@shadow")
517
+ .find(".udex-masthead-menu__sub-category")
518
+ .eq(0)
519
+ .should("have.prop", "tagName")
520
+ .then((tagName: string) => {
521
+ expect(tagName.toLowerCase()).to.equal("udex-masthead-menu-item");
522
+ });
523
+
524
+ cy.get("@shadow")
525
+ .find(".udex-masthead-menu__sub-category")
526
+ .eq(0)
527
+ .should("have.attr", "label", "Build")
528
+ .and("have.attr", "url", "https://example.com/build")
529
+ .and("have.attr", "accessible-role", "link");
530
+ });
531
+
532
+ it("renders the non-linked sub-category (Design) as a <span>", () => {
533
+ cy.get("@shadow")
534
+ .find(".udex-masthead-menu__sub-category")
535
+ .eq(1)
536
+ .should("have.prop", "tagName", "SPAN")
537
+ .and("have.text", "Design");
538
+ });
539
+
540
+ it("renders sub-lists under both sub-categories", () => {
541
+ cy.get("@shadow")
542
+ .find(".udex-masthead-menu__sub-list")
543
+ .should("have.length", 2);
544
+ });
545
+
546
+ it("renders sub-items correctly under the linked sub-category", () => {
547
+ cy.get("@shadow")
548
+ .find(".udex-masthead-menu__sub-list")
549
+ .eq(0)
550
+ .find("udex-masthead-menu-item")
551
+ .should("have.length", 1)
552
+ .and("have.attr", "label", "Develop");
553
+ });
554
+
555
+ it("renders sub-items correctly under the non-linked sub-category", () => {
556
+ cy.get("@shadow")
557
+ .find(".udex-masthead-menu__sub-list")
558
+ .eq(1)
559
+ .find("udex-masthead-menu-item")
560
+ .should("have.length", 1)
561
+ .and("have.attr", "label", "Figma");
562
+ });
563
+ });
564
+ });