@sapudex/web-components 2.33.0 → 2.35.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 (175) hide show
  1. package/cypress/specs/FilterMenu.cy.tsx +53 -0
  2. package/cypress/specs/ListItem.cy.tsx +73 -21
  3. package/cypress/specs/MastheadMenu.cy.tsx +645 -0
  4. package/cypress/specs/MastheadMobileMenu.cy.tsx +573 -0
  5. package/cypress/specs/MastheadShell.cy.tsx +79 -0
  6. package/cypress/specs/SecondaryNavigationDesktop.cy.tsx +14 -0
  7. package/cypress/specs/SelectBox.cy.tsx +229 -1
  8. package/cypress/support/component-imports.instrumented.ts +2 -0
  9. package/cypress/support/component-imports.src.ts +2 -0
  10. package/dist/CountrySelector.d.ts +1 -0
  11. package/dist/CountrySelector.js +3 -0
  12. package/dist/CountrySelector.js.map +1 -1
  13. package/dist/FilterMenu.d.ts +1 -0
  14. package/dist/FilterMenu.js +3 -0
  15. package/dist/FilterMenu.js.map +1 -1
  16. package/dist/ListItem.d.ts +3 -0
  17. package/dist/ListItem.js +10 -1
  18. package/dist/ListItem.js.map +1 -1
  19. package/dist/ListItemBase.d.ts +31 -0
  20. package/dist/ListItemBase.js +28 -0
  21. package/dist/ListItemBase.js.map +1 -1
  22. package/dist/MastheadItem.d.ts +12 -1
  23. package/dist/MastheadItem.js +33 -1
  24. package/dist/MastheadItem.js.map +1 -1
  25. package/dist/MastheadMenu.d.ts +5 -1
  26. package/dist/MastheadMenu.js +16 -6
  27. package/dist/MastheadMenu.js.map +1 -1
  28. package/dist/SearchableListBase.d.ts +3 -1
  29. package/dist/SearchableListBase.js +7 -1
  30. package/dist/SearchableListBase.js.map +1 -1
  31. package/dist/SecondaryNavigationItem.d.ts +5 -1
  32. package/dist/SecondaryNavigationItem.js +35 -5
  33. package/dist/SecondaryNavigationItem.js.map +1 -1
  34. package/dist/SelectBase.js +14 -3
  35. package/dist/SelectBase.js.map +1 -1
  36. package/dist/SelectBox.d.ts +1 -0
  37. package/dist/SelectBox.js +3 -0
  38. package/dist/SelectBox.js.map +1 -1
  39. package/dist/assets/{InputSuggestions-B_Dl2L6w.js → InputSuggestions-CrSNIz-l.js} +1 -1
  40. package/dist/assets/{ListItemStandardExpandableTextTemplate-9Z-aZmJx.js → ListItemStandardExpandableTextTemplate-BUgxFxi7.js} +1 -1
  41. package/dist/assets/{bundle-DbTEs-Za.css → bundle-JYo3bV8v.css} +1 -1
  42. package/dist/assets/{bundle.esm-BlIxH9k1.js → bundle.esm-BZJm6rNI.js} +633 -219
  43. package/dist/css/themes/Breadcrumbs.css +1 -1
  44. package/dist/css/themes/BreadcrumbsPopover.css +1 -1
  45. package/dist/css/themes/FilterMenu.css +1 -1
  46. package/dist/css/themes/ListGroupHeader.css +1 -1
  47. package/dist/css/themes/ListItem.css +1 -1
  48. package/dist/css/themes/MastheadItem.css +1 -1
  49. package/dist/css/themes/MastheadMenu.css +1 -1
  50. package/dist/css/themes/MastheadMobileMenu.css +1 -1
  51. package/dist/css/themes/RatingIndicator.css +1 -1
  52. package/dist/css/themes/SearchableList.css +1 -1
  53. package/dist/css/themes/SecondaryNavigation.css +1 -1
  54. package/dist/css/themes/SecondaryNavigationItem.css +1 -1
  55. package/dist/css/themes/SelectBase.css +1 -1
  56. package/dist/css/themes/Slider.css +1 -1
  57. package/dist/custom-elements-internal.json +1125 -35
  58. package/dist/custom-elements.json +1126 -36
  59. package/dist/generated/templates/ListGroupHeaderTemplate.lit.js +16 -12
  60. package/dist/generated/templates/ListGroupHeaderTemplate.lit.js.map +1 -1
  61. package/dist/generated/templates/ListItemTemplate.lit.js +40 -32
  62. package/dist/generated/templates/ListItemTemplate.lit.js.map +1 -1
  63. package/dist/generated/templates/MastheadItemTemplate.lit.js +2 -2
  64. package/dist/generated/templates/MastheadItemTemplate.lit.js.map +1 -1
  65. package/dist/generated/templates/MastheadMenuTemplate.lit.js +8 -1
  66. package/dist/generated/templates/MastheadMenuTemplate.lit.js.map +1 -1
  67. package/dist/generated/templates/MastheadMobileMenuTemplate.lit.js +10 -4
  68. package/dist/generated/templates/MastheadMobileMenuTemplate.lit.js.map +1 -1
  69. package/dist/generated/templates/MastheadShellTemplate.lit.js +3 -3
  70. package/dist/generated/templates/MastheadShellTemplate.lit.js.map +1 -1
  71. package/dist/generated/templates/SearchableListBaseTemplate.lit.js +4 -4
  72. package/dist/generated/templates/SearchableListBaseTemplate.lit.js.map +1 -1
  73. package/dist/generated/templates/SelectBoxTemplate.lit.js +12 -10
  74. package/dist/generated/templates/SelectBoxTemplate.lit.js.map +1 -1
  75. package/dist/generated/themes/Breadcrumbs.css.d.ts +1 -1
  76. package/dist/generated/themes/Breadcrumbs.css.js +1 -1
  77. package/dist/generated/themes/Breadcrumbs.css.js.map +1 -1
  78. package/dist/generated/themes/BreadcrumbsPopover.css.d.ts +1 -1
  79. package/dist/generated/themes/BreadcrumbsPopover.css.js +1 -1
  80. package/dist/generated/themes/BreadcrumbsPopover.css.js.map +1 -1
  81. package/dist/generated/themes/FilterMenu.css.d.ts +1 -1
  82. package/dist/generated/themes/FilterMenu.css.js +1 -1
  83. package/dist/generated/themes/FilterMenu.css.js.map +1 -1
  84. package/dist/generated/themes/ListGroupHeader.css.d.ts +1 -1
  85. package/dist/generated/themes/ListGroupHeader.css.js +1 -1
  86. package/dist/generated/themes/ListGroupHeader.css.js.map +1 -1
  87. package/dist/generated/themes/ListItem.css.d.ts +1 -1
  88. package/dist/generated/themes/ListItem.css.js +1 -1
  89. package/dist/generated/themes/ListItem.css.js.map +1 -1
  90. package/dist/generated/themes/MastheadItem.css.d.ts +1 -1
  91. package/dist/generated/themes/MastheadItem.css.js +1 -1
  92. package/dist/generated/themes/MastheadItem.css.js.map +1 -1
  93. package/dist/generated/themes/MastheadMenu.css.d.ts +1 -1
  94. package/dist/generated/themes/MastheadMenu.css.js +1 -1
  95. package/dist/generated/themes/MastheadMenu.css.js.map +1 -1
  96. package/dist/generated/themes/MastheadMobileMenu.css.d.ts +1 -1
  97. package/dist/generated/themes/MastheadMobileMenu.css.js +1 -1
  98. package/dist/generated/themes/MastheadMobileMenu.css.js.map +1 -1
  99. package/dist/generated/themes/RatingIndicator.css.d.ts +1 -1
  100. package/dist/generated/themes/RatingIndicator.css.js +1 -1
  101. package/dist/generated/themes/RatingIndicator.css.js.map +1 -1
  102. package/dist/generated/themes/SearchableList.css.d.ts +1 -1
  103. package/dist/generated/themes/SearchableList.css.js +1 -1
  104. package/dist/generated/themes/SearchableList.css.js.map +1 -1
  105. package/dist/generated/themes/SecondaryNavigation.css.d.ts +1 -1
  106. package/dist/generated/themes/SecondaryNavigation.css.js +1 -1
  107. package/dist/generated/themes/SecondaryNavigation.css.js.map +1 -1
  108. package/dist/generated/themes/SecondaryNavigationItem.css.d.ts +1 -1
  109. package/dist/generated/themes/SecondaryNavigationItem.css.js +1 -1
  110. package/dist/generated/themes/SecondaryNavigationItem.css.js.map +1 -1
  111. package/dist/generated/themes/SelectBase.css.d.ts +1 -1
  112. package/dist/generated/themes/SelectBase.css.js +1 -1
  113. package/dist/generated/themes/SelectBase.css.js.map +1 -1
  114. package/dist/generated/themes/Slider.css.d.ts +1 -1
  115. package/dist/generated/themes/Slider.css.js +1 -1
  116. package/dist/generated/themes/Slider.css.js.map +1 -1
  117. package/dist/types/List.d.ts +2 -0
  118. package/dist/types/List.js +2 -0
  119. package/dist/types/List.js.map +1 -1
  120. package/dist/vscode.html-custom-data.json +292 -12
  121. package/dist/web-types.json +431 -21
  122. package/package.json +1 -1
  123. package/src/CountrySelector.ts +3 -0
  124. package/src/FilterMenu.ts +3 -0
  125. package/src/ListGroupHeader.hbs +11 -7
  126. package/src/ListItem.hbs +28 -2
  127. package/src/ListItem.ts +12 -1
  128. package/src/ListItemBase.ts +48 -0
  129. package/src/MastheadItem.hbs +2 -0
  130. package/src/MastheadItem.ts +37 -2
  131. package/src/MastheadMenu.hbs +35 -0
  132. package/src/MastheadMenu.ts +21 -7
  133. package/src/MastheadMobileMenu.hbs +40 -9
  134. package/src/MastheadShell.hbs +3 -0
  135. package/src/SearchableListBase.hbs +21 -1
  136. package/src/SearchableListBase.ts +11 -2
  137. package/src/SecondaryNavigationItem.ts +37 -5
  138. package/src/SelectBase.ts +15 -3
  139. package/src/SelectBox.hbs +13 -5
  140. package/src/SelectBox.ts +3 -0
  141. package/src/generated/templates/ListGroupHeaderTemplate.lit.ts +16 -12
  142. package/src/generated/templates/ListItemTemplate.lit.ts +40 -32
  143. package/src/generated/templates/MastheadItemTemplate.lit.ts +2 -2
  144. package/src/generated/templates/MastheadMenuTemplate.lit.ts +8 -1
  145. package/src/generated/templates/MastheadMobileMenuTemplate.lit.ts +10 -4
  146. package/src/generated/templates/MastheadShellTemplate.lit.ts +3 -3
  147. package/src/generated/templates/SearchableListBaseTemplate.lit.ts +4 -4
  148. package/src/generated/templates/SelectBoxTemplate.lit.ts +12 -10
  149. package/src/generated/themes/Breadcrumbs.css.ts +1 -1
  150. package/src/generated/themes/BreadcrumbsPopover.css.ts +1 -1
  151. package/src/generated/themes/FilterMenu.css.ts +1 -1
  152. package/src/generated/themes/ListGroupHeader.css.ts +1 -1
  153. package/src/generated/themes/ListItem.css.ts +1 -1
  154. package/src/generated/themes/MastheadItem.css.ts +1 -1
  155. package/src/generated/themes/MastheadMenu.css.ts +1 -1
  156. package/src/generated/themes/MastheadMobileMenu.css.ts +1 -1
  157. package/src/generated/themes/RatingIndicator.css.ts +1 -1
  158. package/src/generated/themes/SearchableList.css.ts +1 -1
  159. package/src/generated/themes/SecondaryNavigation.css.ts +1 -1
  160. package/src/generated/themes/SecondaryNavigationItem.css.ts +1 -1
  161. package/src/generated/themes/SelectBase.css.ts +1 -1
  162. package/src/generated/themes/Slider.css.ts +1 -1
  163. package/src/themes/Breadcrumbs.css +16 -2
  164. package/src/themes/BreadcrumbsPopover.css +18 -3
  165. package/src/themes/FilterMenu.css +4 -9
  166. package/src/themes/ListGroupHeader.css +11 -0
  167. package/src/themes/ListItem.css +6 -4
  168. package/src/themes/MastheadItem.css +41 -9
  169. package/src/themes/MastheadMenu.css +45 -0
  170. package/src/themes/MastheadMobileMenu.css +45 -0
  171. package/src/themes/SearchableList.css +18 -3
  172. package/src/themes/SecondaryNavigation.css +1 -1
  173. package/src/themes/SecondaryNavigationItem.css +5 -2
  174. package/src/themes/SelectBase.css +0 -9
  175. package/src/types/List.ts +2 -0
@@ -0,0 +1,645 @@
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
+ // Added: verifies focusInnerElement is used for Home/End/Tab navigation within the menu
499
+ describe("Keyboard navigation (Home/End/Tab focus inner element)", () => {
500
+ beforeEach(() => {
501
+ cy.mount(
502
+ <UDExMastheadMenu
503
+ id="menu-keyboard"
504
+ subItems={JSON.stringify(itemsWithSubItems)}
505
+ />
506
+ );
507
+ cy.get("[udex-masthead-menu]").shadow().as("shadow");
508
+ });
509
+
510
+ it("Home key focuses the inner element of the first menu item", () => {
511
+ // Focus the last link, then press Home — should move focus to the inner element of the first link
512
+ cy.get("@shadow")
513
+ .find("[udex-masthead-menu-item][url]")
514
+ .last()
515
+ .shadow()
516
+ .find(".udex-masthead-menu-item")
517
+ .focus();
518
+ cy.realPress("Home");
519
+ cy.get("@shadow")
520
+ .find("[udex-masthead-menu-item][url]")
521
+ .first()
522
+ .shadow()
523
+ .find(".udex-masthead-menu-item")
524
+ .should("be.focused");
525
+ });
526
+
527
+ it("End key focuses the inner element of the last menu item", () => {
528
+ // Focus the first link, then press End — should move focus to the inner element of the last link
529
+ cy.get("@shadow")
530
+ .find("[udex-masthead-menu-item][url]")
531
+ .first()
532
+ .shadow()
533
+ .find(".udex-masthead-menu-item")
534
+ .focus();
535
+ cy.realPress("End");
536
+ cy.get("@shadow")
537
+ .find("[udex-masthead-menu-item][url]")
538
+ .last()
539
+ .shadow()
540
+ .find(".udex-masthead-menu-item")
541
+ .should("be.focused");
542
+ });
543
+
544
+ it("Shift+Tab focuses the inner element of the previous menu item", () => {
545
+ // Focus the second link, then press Shift+Tab — should focus the inner element of the first link
546
+ cy.get("@shadow")
547
+ .find("[udex-masthead-menu-item][url]")
548
+ .eq(1)
549
+ .shadow()
550
+ .find(".udex-masthead-menu-item")
551
+ .focus();
552
+ cy.realPress(["Shift", "Tab"]);
553
+ cy.get("@shadow")
554
+ .find("[udex-masthead-menu-item][url]")
555
+ .first()
556
+ .shadow()
557
+ .find(".udex-masthead-menu-item")
558
+ .should("be.focused");
559
+ });
560
+
561
+ it("Tab focuses the inner element of the next menu item", () => {
562
+ // Focus the first link, then press Tab — should focus the inner element of the second link
563
+ cy.get("@shadow")
564
+ .find("[udex-masthead-menu-item][url]")
565
+ .first()
566
+ .shadow()
567
+ .find(".udex-masthead-menu-item")
568
+ .focus();
569
+ cy.realPress("Tab");
570
+ cy.get("@shadow")
571
+ .find("[udex-masthead-menu-item][url]")
572
+ .eq(1)
573
+ .shadow()
574
+ .find(".udex-masthead-menu-item")
575
+ .should("be.focused");
576
+ });
577
+ });
578
+
579
+ describe("Mixed linked and non-linked sub-categories in the same list", () => {
580
+ beforeEach(() => {
581
+ cy.mount(
582
+ <UDExMastheadMenu
583
+ id="menu-mixed-linked"
584
+ subItems={JSON.stringify(mixedLinkedSubCategories)}
585
+ />
586
+ );
587
+ cy.get("[udex-masthead-menu]").shadow().as("shadow");
588
+ });
589
+
590
+ it("renders two .udex-masthead-menu__sub-category elements", () => {
591
+ cy.get("@shadow")
592
+ .find(".udex-masthead-menu__sub-category")
593
+ .should("have.length", 2);
594
+ });
595
+
596
+ it("renders the linked sub-category (Build) as a udex-masthead-menu-item", () => {
597
+ cy.get("@shadow")
598
+ .find(".udex-masthead-menu__sub-category")
599
+ .eq(0)
600
+ .should("have.prop", "tagName")
601
+ .then((tagName: string) => {
602
+ expect(tagName.toLowerCase()).to.equal("udex-masthead-menu-item");
603
+ });
604
+
605
+ cy.get("@shadow")
606
+ .find(".udex-masthead-menu__sub-category")
607
+ .eq(0)
608
+ .should("have.attr", "label", "Build")
609
+ .and("have.attr", "url", "https://example.com/build")
610
+ .and("have.attr", "accessible-role", "link");
611
+ });
612
+
613
+ it("renders the non-linked sub-category (Design) as a <span>", () => {
614
+ cy.get("@shadow")
615
+ .find(".udex-masthead-menu__sub-category")
616
+ .eq(1)
617
+ .should("have.prop", "tagName", "SPAN")
618
+ .and("have.text", "Design");
619
+ });
620
+
621
+ it("renders sub-lists under both sub-categories", () => {
622
+ cy.get("@shadow")
623
+ .find(".udex-masthead-menu__sub-list")
624
+ .should("have.length", 2);
625
+ });
626
+
627
+ it("renders sub-items correctly under the linked sub-category", () => {
628
+ cy.get("@shadow")
629
+ .find(".udex-masthead-menu__sub-list")
630
+ .eq(0)
631
+ .find("udex-masthead-menu-item")
632
+ .should("have.length", 1)
633
+ .and("have.attr", "label", "Develop");
634
+ });
635
+
636
+ it("renders sub-items correctly under the non-linked sub-category", () => {
637
+ cy.get("@shadow")
638
+ .find(".udex-masthead-menu__sub-list")
639
+ .eq(1)
640
+ .find("udex-masthead-menu-item")
641
+ .should("have.length", 1)
642
+ .and("have.attr", "label", "Figma");
643
+ });
644
+ });
645
+ });