@sapudex/web-components 2.25.1 → 2.27.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 (132) hide show
  1. package/cypress/fixtures/profile-menu.ts +2 -1
  2. package/cypress/specs/MastheadProfileMenu.cy.tsx +21 -6
  3. package/cypress/specs/TabNavigation.cy.tsx +1024 -0
  4. package/cypress/support/component-imports.src.ts +4 -0
  5. package/dist/List.d.ts +9 -0
  6. package/dist/List.js +33 -2
  7. package/dist/List.js.map +1 -1
  8. package/dist/MastheadCountrySelector.d.ts +11 -0
  9. package/dist/MastheadCountrySelector.js +71 -14
  10. package/dist/MastheadCountrySelector.js.map +1 -1
  11. package/dist/MastheadItem.d.ts +1 -0
  12. package/dist/MastheadItem.js +9 -1
  13. package/dist/MastheadItem.js.map +1 -1
  14. package/dist/MastheadMenu.d.ts +11 -0
  15. package/dist/MastheadMenu.js +43 -13
  16. package/dist/MastheadMenu.js.map +1 -1
  17. package/dist/MastheadProfileMenu.d.ts +33 -3
  18. package/dist/MastheadProfileMenu.js +93 -5
  19. package/dist/MastheadProfileMenu.js.map +1 -1
  20. package/dist/Pagination.d.ts +2 -0
  21. package/dist/Pagination.js +31 -6
  22. package/dist/Pagination.js.map +1 -1
  23. package/dist/PaginationTemplate.js +6 -5
  24. package/dist/PaginationTemplate.js.map +1 -1
  25. package/dist/Tab.d.ts +57 -0
  26. package/dist/Tab.js +67 -0
  27. package/dist/Tab.js.map +1 -1
  28. package/dist/TabNavigation.d.ts +25 -3
  29. package/dist/TabNavigation.js +96 -18
  30. package/dist/TabNavigation.js.map +1 -1
  31. package/dist/Tooltip.js +3 -5
  32. package/dist/Tooltip.js.map +1 -1
  33. package/dist/TooltipTemplate.d.ts +2 -0
  34. package/dist/TooltipTemplate.js +7 -0
  35. package/dist/TooltipTemplate.js.map +1 -0
  36. package/dist/assets/{InputSuggestions-CTFZ6hH-.js → InputSuggestions-Bm_0fOBQ.js} +1 -1
  37. package/dist/assets/{ListItemStandardExpandableTextTemplate-wXdCijez.js → ListItemStandardExpandableTextTemplate-CHXlUP-E.js} +1 -1
  38. package/dist/assets/{bundle.esm-BXCZ9BP3.js → bundle.esm-D_SXCGNu.js} +219 -219
  39. package/dist/css/themes/MastheadCountrySelector.css +1 -1
  40. package/dist/css/themes/MastheadItem.css +1 -1
  41. package/dist/css/themes/MastheadMenu.css +1 -1
  42. package/dist/css/themes/MastheadMenuItem.css +1 -1
  43. package/dist/css/themes/MastheadProfileMenu.css +1 -1
  44. package/dist/css/themes/Quote.css +1 -1
  45. package/dist/css/themes/RatingIndicator.css +1 -1
  46. package/dist/css/themes/Slider.css +1 -1
  47. package/dist/css/themes/TabNavigation.css +1 -1
  48. package/dist/custom-elements-internal.json +60 -1
  49. package/dist/custom-elements.json +52 -1
  50. package/dist/generated/templates/MastheadCountrySelectorTemplate.lit.js +1 -1
  51. package/dist/generated/templates/MastheadCountrySelectorTemplate.lit.js.map +1 -1
  52. package/dist/generated/templates/MastheadProfileMenuTemplate.lit.js +7 -5
  53. package/dist/generated/templates/MastheadProfileMenuTemplate.lit.js.map +1 -1
  54. package/dist/generated/templates/TabHeaderTemplate.lit.js +30 -11
  55. package/dist/generated/templates/TabHeaderTemplate.lit.js.map +1 -1
  56. package/dist/generated/templates/TabNavigationTemplate.lit.js +8 -6
  57. package/dist/generated/templates/TabNavigationTemplate.lit.js.map +1 -1
  58. package/dist/generated/themes/MastheadCountrySelector.css.d.ts +1 -1
  59. package/dist/generated/themes/MastheadCountrySelector.css.js +1 -1
  60. package/dist/generated/themes/MastheadCountrySelector.css.js.map +1 -1
  61. package/dist/generated/themes/MastheadItem.css.d.ts +1 -1
  62. package/dist/generated/themes/MastheadItem.css.js +1 -1
  63. package/dist/generated/themes/MastheadItem.css.js.map +1 -1
  64. package/dist/generated/themes/MastheadMenu.css.d.ts +1 -1
  65. package/dist/generated/themes/MastheadMenu.css.js +1 -1
  66. package/dist/generated/themes/MastheadMenu.css.js.map +1 -1
  67. package/dist/generated/themes/MastheadMenuItem.css.d.ts +1 -1
  68. package/dist/generated/themes/MastheadMenuItem.css.js +1 -1
  69. package/dist/generated/themes/MastheadMenuItem.css.js.map +1 -1
  70. package/dist/generated/themes/MastheadProfileMenu.css.d.ts +1 -1
  71. package/dist/generated/themes/MastheadProfileMenu.css.js +1 -1
  72. package/dist/generated/themes/MastheadProfileMenu.css.js.map +1 -1
  73. package/dist/generated/themes/Quote.css.d.ts +1 -1
  74. package/dist/generated/themes/Quote.css.js +1 -1
  75. package/dist/generated/themes/Quote.css.js.map +1 -1
  76. package/dist/generated/themes/RatingIndicator.css.d.ts +1 -1
  77. package/dist/generated/themes/RatingIndicator.css.js +1 -1
  78. package/dist/generated/themes/RatingIndicator.css.js.map +1 -1
  79. package/dist/generated/themes/Slider.css.d.ts +1 -1
  80. package/dist/generated/themes/Slider.css.js +1 -1
  81. package/dist/generated/themes/Slider.css.js.map +1 -1
  82. package/dist/generated/themes/TabNavigation.css.d.ts +1 -1
  83. package/dist/generated/themes/TabNavigation.css.js +1 -1
  84. package/dist/generated/themes/TabNavigation.css.js.map +1 -1
  85. package/dist/types/List.d.ts +6 -0
  86. package/dist/types/List.js +7 -0
  87. package/dist/types/List.js.map +1 -1
  88. package/dist/vscode.html-custom-data.json +12 -2
  89. package/dist/web-types.json +32 -7
  90. package/package.json +1 -1
  91. package/src/List.ts +41 -2
  92. package/src/MastheadCountrySelector.hbs +20 -16
  93. package/src/MastheadCountrySelector.ts +83 -16
  94. package/src/MastheadItem.ts +10 -1
  95. package/src/MastheadMenu.ts +56 -13
  96. package/src/MastheadProfileMenu.hbs +43 -26
  97. package/src/MastheadProfileMenu.ts +124 -7
  98. package/src/Pagination.ts +32 -6
  99. package/src/PaginationTemplate.tsx +19 -16
  100. package/src/Tab.ts +98 -0
  101. package/src/TabHeader.hbs +85 -33
  102. package/src/TabNavigation.hbs +16 -0
  103. package/src/TabNavigation.ts +109 -21
  104. package/src/Tooltip.ts +7 -6
  105. package/src/TooltipTemplate.tsx +29 -0
  106. package/src/generated/templates/MastheadCountrySelectorTemplate.lit.ts +1 -1
  107. package/src/generated/templates/MastheadProfileMenuTemplate.lit.ts +7 -5
  108. package/src/generated/templates/TabHeaderTemplate.lit.ts +29 -10
  109. package/src/generated/templates/TabNavigationTemplate.lit.ts +8 -6
  110. package/src/generated/themes/MastheadCountrySelector.css.ts +1 -1
  111. package/src/generated/themes/MastheadItem.css.ts +1 -1
  112. package/src/generated/themes/MastheadMenu.css.ts +1 -1
  113. package/src/generated/themes/MastheadMenuItem.css.ts +1 -1
  114. package/src/generated/themes/MastheadProfileMenu.css.ts +1 -1
  115. package/src/generated/themes/Quote.css.ts +1 -1
  116. package/src/generated/themes/RatingIndicator.css.ts +1 -1
  117. package/src/generated/themes/Slider.css.ts +1 -1
  118. package/src/generated/themes/TabNavigation.css.ts +1 -1
  119. package/src/themes/MastheadCountrySelector.css +70 -10
  120. package/src/themes/MastheadItem.css +0 -1
  121. package/src/themes/MastheadMenu.css +1 -2
  122. package/src/themes/MastheadMenuItem.css +1 -0
  123. package/src/themes/MastheadProfileMenu.css +113 -17
  124. package/src/themes/Quote.css +1 -1
  125. package/src/themes/TabNavigation.css +203 -1
  126. package/src/types/List.ts +7 -0
  127. package/cypress/downloads/downloads.html +0 -0
  128. package/dist/generated/templates/TooltipTemplate.lit.d.ts +0 -3
  129. package/dist/generated/templates/TooltipTemplate.lit.js +0 -8
  130. package/dist/generated/templates/TooltipTemplate.lit.js.map +0 -1
  131. package/src/Tooltip.hbs +0 -22
  132. package/src/generated/templates/TooltipTemplate.lit.ts +0 -12
@@ -0,0 +1,1024 @@
1
+ import UDExTabNavigation from "../../src/TabNavigation.js";
2
+ import UDExTab from "../../src/Tab.js";
3
+
4
+ // Test fixtures
5
+ const verticalTabNavigation = (
6
+ <UDExTabNavigation orientation="Vertical" id="verticalTabNav">
7
+ <UDExTab text="General Settings" selected>
8
+ <div>
9
+ <h3>General Settings</h3>
10
+ <p>Configure your general application preferences and system settings.</p>
11
+ </div>
12
+ </UDExTab>
13
+ <UDExTab text="Account & Privacy">
14
+ <div>
15
+ <h3>Account & Privacy</h3>
16
+ <p>Manage your account information, password, and privacy settings.</p>
17
+ </div>
18
+ </UDExTab>
19
+ <UDExTab text="Notifications">
20
+ <div>
21
+ <h3>Notifications</h3>
22
+ <p>Control how and when you receive notifications from the application.</p>
23
+ </div>
24
+ </UDExTab>
25
+ <UDExTab text="Billing & Plans">
26
+ <div>
27
+ <h3>Billing & Plans</h3>
28
+ <p>View your current plan, billing information, and upgrade options.</p>
29
+ </div>
30
+ </UDExTab>
31
+ <UDExTab text="Help & Support">
32
+ <div>
33
+ <h3>Help & Support</h3>
34
+ <p>Access documentation, FAQs, and contact support team.</p>
35
+ </div>
36
+ </UDExTab>
37
+ </UDExTabNavigation>
38
+ );
39
+
40
+ const horizontalTabNavigation = (
41
+ <UDExTabNavigation orientation="Horizontal" id="horizontalTabNav">
42
+ <UDExTab text="Tab 1" selected>
43
+ <div><p>Content 1</p></div>
44
+ </UDExTab>
45
+ <UDExTab text="Tab 2">
46
+ <div><p>Content 2</p></div>
47
+ </UDExTab>
48
+ <UDExTab text="Tab 3">
49
+ <div><p>Content 3</p></div>
50
+ </UDExTab>
51
+ </UDExTabNavigation>
52
+ );
53
+
54
+ // Standard horizontal tabs with 5 tabs and badge (matching WebDriver tests)
55
+ const standardTabNavigation = (
56
+ <UDExTabNavigation orientation="Horizontal" id="standard-tab-navigation">
57
+ <UDExTab text="Tab Text 1" numberBadge="72" selected slot="default-1">
58
+ <div>Tab Content 1</div>
59
+ </UDExTab>
60
+ <UDExTab text="Tab Text 2" slot="default-2">
61
+ <div>Tab Content 2</div>
62
+ </UDExTab>
63
+ <UDExTab text="Tab Text 3" slot="default-3">
64
+ <div>Tab Content 3</div>
65
+ </UDExTab>
66
+ <UDExTab text="Tab Text 4" slot="default-4">
67
+ <div>Tab Content 4</div>
68
+ </UDExTab>
69
+ <UDExTab text="Tab Text 5" slot="default-5">
70
+ <div>Tab Content 5</div>
71
+ </UDExTab>
72
+ </UDExTabNavigation>
73
+ );
74
+
75
+ describe("Tab Navigation component", () => {
76
+ describe("Standard Horizontal Tabs - Rendering", () => {
77
+ beforeEach(() => {
78
+ cy.mount(standardTabNavigation);
79
+ });
80
+
81
+ it("should correctly render", () => {
82
+ cy.get("#standard-tab-navigation")
83
+ .shadow()
84
+ .find(".udex-tab-navigation__root")
85
+ .should("exist");
86
+ });
87
+
88
+ it("the component has 5 tabs", () => {
89
+ cy.get("#standard-tab-navigation")
90
+ .shadow()
91
+ .find(".udex-tab-navigation__button-tab")
92
+ .should("have.length", 5);
93
+ });
94
+
95
+ it("the component has contents of tabs", () => {
96
+ cy.get("#standard-tab-navigation")
97
+ .shadow()
98
+ .find("slot")
99
+ .should("have.length", 5);
100
+ });
101
+
102
+ it("the component has badge", () => {
103
+ cy.get("#standard-tab-navigation")
104
+ .shadow()
105
+ .find("udex-badge")
106
+ .should("exist");
107
+ });
108
+
109
+ it("the first tab should be selected", () => {
110
+ cy.get("#standard-tab-navigation")
111
+ .shadow()
112
+ .find(".udex-tab-navigation__button-tab--active")
113
+ .invoke("text")
114
+ .should("match", /Tab Text 1[\s\n]*72/);
115
+ });
116
+
117
+ it("the content of the first tab should be displayed", () => {
118
+ cy.get("#standard-tab-navigation")
119
+ .find("udex-tab[selected][slot='default-1']")
120
+ .should("contain.text", "Tab Content 1");
121
+ });
122
+ });
123
+
124
+ describe("Standard Horizontal Tabs - Tab Selection", () => {
125
+ beforeEach(() => {
126
+ cy.mount(standardTabNavigation);
127
+ });
128
+
129
+ it("should change the selected tab", () => {
130
+ cy.get("#standard-tab-navigation")
131
+ .shadow()
132
+ .find(".udex-tab-navigation__button-tab--active")
133
+ .invoke("text")
134
+ .should("match", /Tab Text 1[\s\n]*72/);
135
+
136
+ cy.get("#standard-tab-navigation")
137
+ .shadow()
138
+ .find(".udex-tab-navigation__button-tab:nth-child(2)")
139
+ .click();
140
+
141
+ cy.get("#standard-tab-navigation")
142
+ .shadow()
143
+ .find(".udex-tab-navigation__button-tab--active")
144
+ .should("contain.text", "Tab Text 2");
145
+ });
146
+
147
+ it("should display the content of the third tab", () => {
148
+ cy.get("#standard-tab-navigation")
149
+ .shadow()
150
+ .find(".udex-tab-navigation__button-tab:nth-child(3)")
151
+ .click();
152
+
153
+ cy.get("#standard-tab-navigation")
154
+ .shadow()
155
+ .find(".udex-tab-navigation__button-tab--active")
156
+ .should("contain.text", "Tab Text 3");
157
+
158
+ cy.get("#standard-tab-navigation")
159
+ .find("udex-tab[selected][slot='default-3']")
160
+ .should("contain.text", "Tab Content 3");
161
+ });
162
+ });
163
+
164
+ /**
165
+ * NOTE: Overflow behavior tests are skipped in Cypress component testing
166
+ * because the TabNavigation overflow calculation depends on accurate DOM measurements
167
+ * that don't work reliably in the component test environment. The overflow logic
168
+ * triggers when tab widths exceed the container width, but in isolated component tests
169
+ * the dimensions aren't calculated the same way as in a full page context.
170
+ *
171
+ * The original WebDriver.io tests (TabNavigation.spec.js) that run in full page context
172
+ * successfully test the overflow behavior including:
173
+ * - More button visibility
174
+ * - Dropdown opening/closing
175
+ * - Keyboard navigation in dropdown (Arrow keys, Home, End, Enter, Escape, Tab, Shift+Tab)
176
+ *
177
+ * These tests remain in the WebDriver.io suite and provide coverage for overflow functionality.
178
+ */
179
+ describe.skip("Horizontal Tabs - Overflow Behavior (tested in WebDriver.io)", () => {
180
+ // Tests skipped - see note above
181
+ });
182
+
183
+ describe.skip("Overflow Tabs - Accessibility Keyboard Navigation (tested in WebDriver.io)", () => {
184
+ // Tests skipped - see note above
185
+ });
186
+
187
+ describe("TabNavigation - Vertical Tabs", () => {
188
+ beforeEach(() => {
189
+ cy.mount(verticalTabNavigation);
190
+ });
191
+
192
+ describe("Rendering", () => {
193
+ it("renders with correct orientation and structure", () => {
194
+ cy.get("[udex-tab-navigation]")
195
+ .should("have.attr", "orientation", "Vertical")
196
+ .shadow()
197
+ .find(".udex-tab-navigation__root")
198
+ .should("be.visible");
199
+
200
+ // All tabs should be visible without overflow
201
+ cy.get("[udex-tab-navigation]")
202
+ .find("udex-tab")
203
+ .should("have.length", 5);
204
+
205
+ cy.get("[udex-tab-navigation]")
206
+ .shadow()
207
+ .find("button[role='tab']")
208
+ .should("have.length", 5)
209
+ .each($tab => {
210
+ cy.wrap($tab).should("be.visible");
211
+ });
212
+ });
213
+
214
+ it("does not show More button in vertical mode", () => {
215
+ cy.get("[udex-tab-navigation]")
216
+ .shadow()
217
+ .find(".udex-tab-navigation__button-more")
218
+ .should("not.be.visible");
219
+ });
220
+
221
+ it("displays selected tab and content correctly", () => {
222
+ cy.get("[udex-tab-navigation]")
223
+ .shadow()
224
+ .find("button[role='tab'][aria-selected='true']")
225
+ .should("contain.text", "General Settings");
226
+
227
+ cy.get("[udex-tab-navigation]")
228
+ .find("udex-tab[selected]")
229
+ .find("h3")
230
+ .should("contain.text", "General Settings");
231
+ });
232
+ });
233
+
234
+ describe("Tab Selection", () => {
235
+ it("switches tabs and updates content on click", () => {
236
+ cy.get("[udex-tab-navigation]")
237
+ .shadow()
238
+ .find("button[role='tab']")
239
+ .eq(2)
240
+ .click();
241
+
242
+ cy.get("[udex-tab-navigation]")
243
+ .shadow()
244
+ .find("button[role='tab'][aria-selected='true']")
245
+ .should("contain.text", "Notifications");
246
+
247
+ cy.get("[udex-tab-navigation]")
248
+ .find("udex-tab[selected]")
249
+ .find("h3")
250
+ .should("contain.text", "Notifications");
251
+ });
252
+
253
+ it("emits tab-select event on tab change", () => {
254
+ const tabSelectHandler = cy.stub().as("tabSelect");
255
+
256
+ cy.mount(
257
+ <UDExTabNavigation orientation="Vertical" onTabSelect={tabSelectHandler}>
258
+ <UDExTab text="Tab 1" selected>
259
+ <div>Content 1</div>
260
+ </UDExTab>
261
+ <UDExTab text="Tab 2">
262
+ <div>Content 2</div>
263
+ </UDExTab>
264
+ </UDExTabNavigation>
265
+ );
266
+
267
+ cy.get("[udex-tab-navigation]")
268
+ .shadow()
269
+ .find("button[role='tab']")
270
+ .eq(1)
271
+ .click();
272
+
273
+ cy.get("@tabSelect").should("have.been.called");
274
+ });
275
+ });
276
+
277
+ describe("Vertical Keyboard Navigation", () => {
278
+ it("navigates to next tab with ArrowDown key", () => {
279
+ cy.get("[udex-tab-navigation]")
280
+ .shadow()
281
+ .find("button[role='tab']")
282
+ .eq(0)
283
+ .focus()
284
+ .realPress("ArrowDown");
285
+
286
+ cy.get("[udex-tab-navigation]")
287
+ .shadow()
288
+ .find("button[role='tab']")
289
+ .eq(1)
290
+ .should("be.focused");
291
+ });
292
+
293
+ it("navigates to previous tab with ArrowUp key", () => {
294
+ cy.get("[udex-tab-navigation]")
295
+ .shadow()
296
+ .find("button[role='tab']")
297
+ .eq(2)
298
+ .focus()
299
+ .realPress("ArrowUp");
300
+
301
+ cy.get("[udex-tab-navigation]")
302
+ .shadow()
303
+ .find("button[role='tab']")
304
+ .eq(1)
305
+ .should("be.focused");
306
+ });
307
+
308
+ it("wraps to first tab when pressing ArrowDown on last tab", () => {
309
+ cy.get("[udex-tab-navigation]")
310
+ .shadow()
311
+ .find("button[role='tab']")
312
+ .eq(4)
313
+ .focus()
314
+ .realPress("ArrowDown");
315
+
316
+ cy.get("[udex-tab-navigation]")
317
+ .shadow()
318
+ .find("button[role='tab']")
319
+ .eq(0)
320
+ .should("be.focused");
321
+ });
322
+
323
+ it("wraps to last tab when pressing ArrowUp on first tab", () => {
324
+ cy.get("[udex-tab-navigation]")
325
+ .shadow()
326
+ .find("button[role='tab']")
327
+ .eq(0)
328
+ .focus()
329
+ .realPress("ArrowUp");
330
+
331
+ cy.get("[udex-tab-navigation]")
332
+ .shadow()
333
+ .find("button[role='tab']")
334
+ .eq(4)
335
+ .should("be.focused");
336
+ });
337
+
338
+ it("does not respond to ArrowLeft key in vertical mode", () => {
339
+ cy.get("[udex-tab-navigation]")
340
+ .shadow()
341
+ .find("button[role='tab']")
342
+ .eq(1)
343
+ .focus()
344
+ .realPress("ArrowLeft");
345
+
346
+ // Focus should remain on the same tab
347
+ cy.get("[udex-tab-navigation]")
348
+ .shadow()
349
+ .find("button[role='tab']")
350
+ .eq(1)
351
+ .should("be.focused");
352
+ });
353
+
354
+ it("does not respond to ArrowRight key in vertical mode", () => {
355
+ cy.get("[udex-tab-navigation]")
356
+ .shadow()
357
+ .find("button[role='tab']")
358
+ .eq(1)
359
+ .focus()
360
+ .realPress("ArrowRight");
361
+
362
+ // Focus should remain on the same tab
363
+ cy.get("[udex-tab-navigation]")
364
+ .shadow()
365
+ .find("button[role='tab']")
366
+ .eq(1)
367
+ .should("be.focused");
368
+ });
369
+
370
+ it("focuses first tab with Home key", () => {
371
+ cy.get("[udex-tab-navigation]")
372
+ .shadow()
373
+ .find("button[role='tab']")
374
+ .eq(3)
375
+ .focus()
376
+ .realPress("Home");
377
+
378
+ cy.get("[udex-tab-navigation]")
379
+ .shadow()
380
+ .find("button[role='tab']")
381
+ .eq(0)
382
+ .should("be.focused");
383
+ });
384
+
385
+ it("focuses last tab with End key", () => {
386
+ cy.get("[udex-tab-navigation]")
387
+ .shadow()
388
+ .find("button[role='tab']")
389
+ .eq(0)
390
+ .focus()
391
+ .realPress("End");
392
+
393
+ cy.get("[udex-tab-navigation]")
394
+ .shadow()
395
+ .find("button[role='tab']")
396
+ .eq(4)
397
+ .should("be.focused");
398
+ });
399
+
400
+ it("activates tab with Enter key", () => {
401
+ cy.get("[udex-tab-navigation]")
402
+ .shadow()
403
+ .find("button[role='tab']")
404
+ .eq(2)
405
+ .focus()
406
+ .realPress("Enter");
407
+
408
+ cy.get("[udex-tab-navigation]")
409
+ .shadow()
410
+ .find("button[role='tab']")
411
+ .eq(2)
412
+ .should("have.attr", "aria-selected", "true");
413
+ });
414
+
415
+ it("activates tab with Space key", () => {
416
+ cy.get("[udex-tab-navigation]")
417
+ .shadow()
418
+ .find("button[role='tab']")
419
+ .eq(3)
420
+ .focus()
421
+ .realPress("Space");
422
+
423
+ cy.get("[udex-tab-navigation]")
424
+ .shadow()
425
+ .find("button[role='tab']")
426
+ .eq(3)
427
+ .should("have.attr", "aria-selected", "true");
428
+ });
429
+ });
430
+ });
431
+
432
+ describe("TabNavigation - Horizontal Tabs", () => {
433
+ beforeEach(() => {
434
+ cy.mount(horizontalTabNavigation);
435
+ });
436
+
437
+ describe("Rendering", () => {
438
+ it("renders with correct orientation and structure", () => {
439
+ cy.get("[udex-tab-navigation]")
440
+ .should("have.attr", "orientation", "Horizontal")
441
+ .shadow()
442
+ .find(".udex-tab-navigation__root")
443
+ .should("be.visible");
444
+
445
+ cy.get("[udex-tab-navigation]")
446
+ .find("udex-tab")
447
+ .should("have.length", 3);
448
+
449
+ cy.get("[udex-tab-navigation]")
450
+ .shadow()
451
+ .find("button[role='tab']")
452
+ .should("have.length", 3);
453
+ });
454
+
455
+ it("displays selected tab and content correctly", () => {
456
+ cy.get("[udex-tab-navigation]")
457
+ .shadow()
458
+ .find("button[role='tab'][aria-selected='true']")
459
+ .should("contain.text", "Tab 1");
460
+
461
+ cy.get("[udex-tab-navigation]")
462
+ .find("udex-tab[selected]")
463
+ .find("p")
464
+ .should("contain.text", "Content 1");
465
+ });
466
+ });
467
+
468
+ describe("Tab Selection", () => {
469
+ it("switches tabs and updates content on click", () => {
470
+ cy.get("[udex-tab-navigation]")
471
+ .shadow()
472
+ .find("button[role='tab']")
473
+ .eq(2)
474
+ .click();
475
+
476
+ cy.get("[udex-tab-navigation]")
477
+ .shadow()
478
+ .find("button[role='tab'][aria-selected='true']")
479
+ .should("contain.text", "Tab 3");
480
+
481
+ cy.get("[udex-tab-navigation]")
482
+ .find("udex-tab[selected]")
483
+ .find("p")
484
+ .should("contain.text", "Content 3");
485
+ });
486
+ });
487
+
488
+ describe("Horizontal Keyboard Navigation", () => {
489
+ it("navigates to next tab with ArrowRight key", () => {
490
+ cy.get("[udex-tab-navigation]")
491
+ .shadow()
492
+ .find("button[role='tab']")
493
+ .eq(0)
494
+ .focus()
495
+ .realPress("ArrowRight");
496
+
497
+ cy.get("[udex-tab-navigation]")
498
+ .shadow()
499
+ .find("button[role='tab']")
500
+ .eq(1)
501
+ .should("be.focused");
502
+ });
503
+
504
+ it("navigates to previous tab with ArrowLeft key", () => {
505
+ cy.get("[udex-tab-navigation]")
506
+ .shadow()
507
+ .find("button[role='tab']")
508
+ .eq(2)
509
+ .focus()
510
+ .realPress("ArrowLeft");
511
+
512
+ cy.get("[udex-tab-navigation]")
513
+ .shadow()
514
+ .find("button[role='tab']")
515
+ .eq(1)
516
+ .should("be.focused");
517
+ });
518
+
519
+ it("wraps to first tab when pressing ArrowRight on last tab", () => {
520
+ cy.get("[udex-tab-navigation]")
521
+ .shadow()
522
+ .find("button[role='tab']")
523
+ .eq(2)
524
+ .focus()
525
+ .realPress("ArrowRight");
526
+
527
+ cy.get("[udex-tab-navigation]")
528
+ .shadow()
529
+ .find("button[role='tab']")
530
+ .eq(0)
531
+ .should("be.focused");
532
+ });
533
+
534
+ it("wraps to last tab when pressing ArrowLeft on first tab", () => {
535
+ cy.get("[udex-tab-navigation]")
536
+ .shadow()
537
+ .find("button[role='tab']")
538
+ .eq(0)
539
+ .focus()
540
+ .realPress("ArrowLeft");
541
+
542
+ cy.get("[udex-tab-navigation]")
543
+ .shadow()
544
+ .find("button[role='tab']")
545
+ .eq(2)
546
+ .should("be.focused");
547
+ });
548
+
549
+ it("does not respond to ArrowUp key in horizontal mode", () => {
550
+ cy.get("[udex-tab-navigation]")
551
+ .shadow()
552
+ .find("button[role='tab']")
553
+ .eq(1)
554
+ .focus()
555
+ .realPress("ArrowUp");
556
+
557
+ // Focus should remain on the same tab
558
+ cy.get("[udex-tab-navigation]")
559
+ .shadow()
560
+ .find("button[role='tab']")
561
+ .eq(1)
562
+ .should("be.focused");
563
+ });
564
+
565
+ it("does not respond to ArrowDown key in horizontal mode", () => {
566
+ cy.get("[udex-tab-navigation]")
567
+ .shadow()
568
+ .find("button[role='tab']")
569
+ .eq(1)
570
+ .focus()
571
+ .realPress("ArrowDown");
572
+
573
+ // Focus should remain on the same tab
574
+ cy.get("[udex-tab-navigation]")
575
+ .shadow()
576
+ .find("button[role='tab']")
577
+ .eq(1)
578
+ .should("be.focused");
579
+ });
580
+
581
+ it("focuses first tab with Home key", () => {
582
+ cy.get("[udex-tab-navigation]")
583
+ .shadow()
584
+ .find("button[role='tab']")
585
+ .eq(2)
586
+ .focus()
587
+ .realPress("Home");
588
+
589
+ cy.get("[udex-tab-navigation]")
590
+ .shadow()
591
+ .find("button[role='tab']")
592
+ .eq(0)
593
+ .should("be.focused");
594
+ });
595
+
596
+ it("focuses last tab with End key", () => {
597
+ cy.get("[udex-tab-navigation]")
598
+ .shadow()
599
+ .find("button[role='tab']")
600
+ .eq(0)
601
+ .focus()
602
+ .realPress("End");
603
+
604
+ cy.get("[udex-tab-navigation]")
605
+ .shadow()
606
+ .find("button[role='tab']")
607
+ .eq(2)
608
+ .should("be.focused");
609
+ });
610
+
611
+ it("activates tab with Enter key", () => {
612
+ cy.get("[udex-tab-navigation]")
613
+ .shadow()
614
+ .find("button[role='tab']")
615
+ .eq(1)
616
+ .focus()
617
+ .realPress("Enter");
618
+
619
+ cy.get("[udex-tab-navigation]")
620
+ .shadow()
621
+ .find("button[role='tab']")
622
+ .eq(1)
623
+ .should("have.attr", "aria-selected", "true");
624
+ });
625
+
626
+ it("activates tab with Space key", () => {
627
+ cy.get("[udex-tab-navigation]")
628
+ .shadow()
629
+ .find("button[role='tab']")
630
+ .eq(2)
631
+ .focus()
632
+ .realPress("Space");
633
+
634
+ cy.get("[udex-tab-navigation]")
635
+ .shadow()
636
+ .find("button[role='tab']")
637
+ .eq(2)
638
+ .should("have.attr", "aria-selected", "true");
639
+ });
640
+ });
641
+
642
+ describe("Accessibility", () => {
643
+ it("has correct ARIA role on tabs", () => {
644
+ cy.get("[udex-tab-navigation]")
645
+ .shadow()
646
+ .find("button[role='tab']")
647
+ .should("have.length.at.least", 1);
648
+ });
649
+
650
+ it("has correct ARIA role on tab list", () => {
651
+ cy.get("[udex-tab-navigation]")
652
+ .shadow()
653
+ .find("[role='tablist']")
654
+ .should("exist");
655
+ });
656
+
657
+ it("has correct tabindex on selected tab", () => {
658
+ cy.get("[udex-tab-navigation]")
659
+ .shadow()
660
+ .find("button[role='tab'][aria-selected='true']")
661
+ .should("have.attr", "tabindex", "0");
662
+ });
663
+
664
+ it("has correct tabindex on non-selected tabs", () => {
665
+ cy.get("[udex-tab-navigation]")
666
+ .shadow()
667
+ .find("button[role='tab'][aria-selected='false']")
668
+ .should("have.attr", "tabindex", "-1");
669
+ });
670
+
671
+ it("has aria-controls attribute pointing to tab panel", () => {
672
+ cy.get("[udex-tab-navigation]")
673
+ .shadow()
674
+ .find("button[role='tab']")
675
+ .first()
676
+ .should("have.attr", "aria-controls");
677
+ });
678
+
679
+ it("updates aria-selected when tabs change", () => {
680
+ cy.get("[udex-tab-navigation]")
681
+ .shadow()
682
+ .find("button[role='tab']")
683
+ .eq(1)
684
+ .click();
685
+
686
+ cy.get("[udex-tab-navigation]")
687
+ .shadow()
688
+ .find("button[role='tab']")
689
+ .eq(1)
690
+ .should("have.attr", "aria-selected", "true");
691
+
692
+ cy.get("[udex-tab-navigation]")
693
+ .shadow()
694
+ .find("button[role='tab']")
695
+ .eq(0)
696
+ .should("have.attr", "aria-selected", "false");
697
+ });
698
+ });
699
+ });
700
+
701
+ describe("TabNavigation - Body Text and Link Slot Features", () => {
702
+ describe("Horizontal tabs with bodyText", () => {
703
+ it("does NOT show body text in header for horizontal tabs", () => {
704
+ cy.mount(
705
+ <UDExTabNavigation orientation="Horizontal">
706
+ <UDExTab text="Settings" bodyText="Configure your preferences" selected>
707
+ <div>Settings content</div>
708
+ </UDExTab>
709
+ <UDExTab text="Account" bodyText="Manage your account">
710
+ <div>Account content</div>
711
+ </UDExTab>
712
+ </UDExTabNavigation>
713
+ );
714
+
715
+ // Body text should NOT be visible in the header for horizontal tabs
716
+ cy.get("[udex-tab-navigation]")
717
+ .shadow()
718
+ .find(".udex-tab-navigation__button-body-text")
719
+ .should("not.exist");
720
+
721
+ // Wrapper should NOT be used for horizontal tabs
722
+ cy.get("[udex-tab-navigation]")
723
+ .shadow()
724
+ .find(".udex-tab-navigation__tab-wrapper")
725
+ .should("not.exist");
726
+ });
727
+
728
+ it("does NOT show link slot in header for horizontal tabs", () => {
729
+ // Note: The Tab component uses a `link` slot for adding links
730
+ // In horizontal mode, even with bodyText, the wrapper and links should not be shown
731
+ cy.mount(
732
+ <UDExTabNavigation orientation="Horizontal">
733
+ <UDExTab text="Settings" bodyText="Configure your preferences" selected>
734
+ <div>Settings content</div>
735
+ </UDExTab>
736
+ <UDExTab text="Account" bodyText="Manage your account">
737
+ <div>Account content</div>
738
+ </UDExTab>
739
+ </UDExTabNavigation>
740
+ );
741
+
742
+ // Link container should NOT be visible in the header for horizontal tabs
743
+ cy.get("[udex-tab-navigation]")
744
+ .shadow()
745
+ .find(".udex-tab-navigation__button-links")
746
+ .should("not.exist");
747
+
748
+ // Wrapper should NOT be used for horizontal tabs
749
+ cy.get("[udex-tab-navigation]")
750
+ .shadow()
751
+ .find(".udex-tab-navigation__tab-wrapper")
752
+ .should("not.exist");
753
+ });
754
+
755
+ it("renders all tabs visible in horizontal mode with bodyText", () => {
756
+ cy.mount(
757
+ <UDExTabNavigation orientation="Horizontal">
758
+ <UDExTab text="Tab 1" bodyText="Description 1" selected>
759
+ <div>Content 1</div>
760
+ </UDExTab>
761
+ <UDExTab text="Tab 2" bodyText="Description 2">
762
+ <div>Content 2</div>
763
+ </UDExTab>
764
+ <UDExTab text="Tab 3" bodyText="Description 3">
765
+ <div>Content 3</div>
766
+ </UDExTab>
767
+ </UDExTabNavigation>
768
+ );
769
+
770
+ // All tabs should be visible
771
+ cy.get("[udex-tab-navigation]")
772
+ .shadow()
773
+ .find("button[role='tab']")
774
+ .should("have.length", 3)
775
+ .each($tab => {
776
+ cy.wrap($tab).should("be.visible");
777
+ });
778
+ });
779
+ });
780
+
781
+ describe("Vertical tabs with bodyText and link slot", () => {
782
+ it("shows body text in header for selected vertical tab", () => {
783
+ cy.mount(
784
+ <UDExTabNavigation orientation="Vertical">
785
+ <UDExTab text="Settings" bodyText="Configure your preferences" selected>
786
+ <div>Settings content</div>
787
+ </UDExTab>
788
+ <UDExTab text="Account" bodyText="Manage your account">
789
+ <div>Account content</div>
790
+ </UDExTab>
791
+ </UDExTabNavigation>
792
+ );
793
+
794
+ // Wrapper should be used for vertical tabs with body text
795
+ cy.get("[udex-tab-navigation]")
796
+ .shadow()
797
+ .find(".udex-tab-navigation__tab-wrapper")
798
+ .should("have.length", 2);
799
+
800
+ // Body text element should exist
801
+ cy.get("[udex-tab-navigation]")
802
+ .shadow()
803
+ .find(".udex-tab-navigation__button-body-text")
804
+ .should("have.length", 2);
805
+ });
806
+
807
+ it("shows link slot in header for vertical tabs with link elements", () => {
808
+ // The Tab component uses a `link` slot for adding links in vertical mode
809
+ // When link slot elements are provided, they should be rendered in the tab header
810
+ cy.mount(
811
+ <UDExTabNavigation orientation="Vertical">
812
+ <UDExTab text="Settings" selected>
813
+ <a slot="link" href="https://example.com">Learn more</a>
814
+ <div>Settings content</div>
815
+ </UDExTab>
816
+ <UDExTab text="Account">
817
+ <a slot="link" href="https://example.com">View details</a>
818
+ <div>Account content</div>
819
+ </UDExTab>
820
+ </UDExTabNavigation>
821
+ );
822
+
823
+ // Wrapper should be used for vertical tabs with link slot
824
+ cy.get("[udex-tab-navigation]")
825
+ .shadow()
826
+ .find(".udex-tab-navigation__tab-wrapper")
827
+ .should("have.length", 2);
828
+
829
+ // Link container should exist for tabs with link slot
830
+ cy.get("[udex-tab-navigation]")
831
+ .shadow()
832
+ .find(".udex-tab-navigation__button-links")
833
+ .should("have.length", 2);
834
+ });
835
+
836
+ it("displays body text content correctly for each vertical tab", () => {
837
+ cy.mount(
838
+ <UDExTabNavigation orientation="Vertical">
839
+ <UDExTab
840
+ text="Settings"
841
+ bodyText="Configure your preferences"
842
+ selected
843
+ >
844
+ <div>Settings content</div>
845
+ </UDExTab>
846
+ <UDExTab
847
+ text="Account"
848
+ bodyText="Manage your account"
849
+ >
850
+ <div>Account content</div>
851
+ </UDExTab>
852
+ </UDExTabNavigation>
853
+ );
854
+
855
+ // Body text elements should exist and contain correct text
856
+ cy.get("[udex-tab-navigation]")
857
+ .shadow()
858
+ .find(".udex-tab-navigation__button-body-text")
859
+ .should("have.length", 2);
860
+
861
+ cy.get("[udex-tab-navigation]")
862
+ .shadow()
863
+ .find(".udex-tab-navigation__button-body-text")
864
+ .eq(0)
865
+ .should("contain.text", "Configure your preferences");
866
+
867
+ cy.get("[udex-tab-navigation]")
868
+ .shadow()
869
+ .find(".udex-tab-navigation__button-body-text")
870
+ .eq(1)
871
+ .should("contain.text", "Manage your account");
872
+ });
873
+
874
+ it("does NOT use wrapper for vertical tabs without bodyText or link", () => {
875
+ cy.mount(
876
+ <UDExTabNavigation orientation="Vertical">
877
+ <UDExTab text="Settings" selected>
878
+ <div>Settings content</div>
879
+ </UDExTab>
880
+ <UDExTab text="Account">
881
+ <div>Account content</div>
882
+ </UDExTab>
883
+ </UDExTabNavigation>
884
+ );
885
+
886
+ // Wrapper should NOT be used when there's no body text or link
887
+ cy.get("[udex-tab-navigation]")
888
+ .shadow()
889
+ .find(".udex-tab-navigation__tab-wrapper")
890
+ .should("not.exist");
891
+
892
+ // Tabs should be rendered directly as buttons
893
+ cy.get("[udex-tab-navigation]")
894
+ .shadow()
895
+ .find("button[role='tab']")
896
+ .should("have.length", 2);
897
+ });
898
+ });
899
+ });
900
+
901
+ describe("TabNavigation - Additional Features", () => {
902
+ it("applies tabWidth property to vertical tabs", () => {
903
+ cy.mount(
904
+ <UDExTabNavigation orientation="Vertical" tabWidth="300px">
905
+ <UDExTab text="Settings" selected>
906
+ <div>Settings content</div>
907
+ </UDExTab>
908
+ <UDExTab text="Account">
909
+ <div>Account content</div>
910
+ </UDExTab>
911
+ </UDExTabNavigation>
912
+ );
913
+
914
+ cy.get("[udex-tab-navigation]")
915
+ .should("have.css", "--udex-tab-header-width", "300px");
916
+ });
917
+
918
+ it("applies tabWidth property with different units to vertical tabs", () => {
919
+ cy.mount(
920
+ <UDExTabNavigation orientation="Vertical" tabWidth="20rem">
921
+ <UDExTab text="Settings" selected>
922
+ <div>Settings content</div>
923
+ </UDExTab>
924
+ <UDExTab text="Account">
925
+ <div>Account content</div>
926
+ </UDExTab>
927
+ </UDExTabNavigation>
928
+ );
929
+
930
+ cy.get("[udex-tab-navigation]")
931
+ .should("have.css", "--udex-tab-header-width", "20rem");
932
+ });
933
+
934
+ it("uses default width when tabWidth is auto for vertical tabs", () => {
935
+ cy.mount(
936
+ <UDExTabNavigation orientation="Vertical" tabWidth="auto">
937
+ <UDExTab text="Settings" selected>
938
+ <div>Settings content</div>
939
+ </UDExTab>
940
+ <UDExTab text="Account">
941
+ <div>Account content</div>
942
+ </UDExTab>
943
+ </UDExTabNavigation>
944
+ );
945
+
946
+ // When tabWidth is "auto", the CSS variable should not be set
947
+ // The style attribute may be undefined or not contain the CSS variable
948
+ cy.get("[udex-tab-navigation]")
949
+ .then($el => {
950
+ const style = $el.attr("style") || "";
951
+ expect(style).to.not.contain("--udex-tab-header-width");
952
+ });
953
+ });
954
+
955
+ it("renders tabs with body text in vertical mode", () => {
956
+ cy.mount(
957
+ <UDExTabNavigation orientation="Vertical">
958
+ <UDExTab text="Settings" bodyText="Configure your preferences" selected>
959
+ <div>Settings content</div>
960
+ </UDExTab>
961
+ <UDExTab text="Account" bodyText="Manage your account">
962
+ <div>Account content</div>
963
+ </UDExTab>
964
+ </UDExTabNavigation>
965
+ );
966
+
967
+ cy.get("[udex-tab-navigation]")
968
+ .shadow()
969
+ .find("button[role='tab']")
970
+ .first()
971
+ .should("be.visible");
972
+ });
973
+
974
+ it("renders tabs with number badges in vertical mode", () => {
975
+ cy.mount(
976
+ <UDExTabNavigation orientation="Vertical">
977
+ <UDExTab text="Messages" numberBadge="5" selected>
978
+ <div>Messages content</div>
979
+ </UDExTab>
980
+ <UDExTab text="Notifications" numberBadge="12">
981
+ <div>Notifications content</div>
982
+ </UDExTab>
983
+ </UDExTabNavigation>
984
+ );
985
+
986
+ cy.get("[udex-tab-navigation]")
987
+ .shadow()
988
+ .find("button[role='tab']")
989
+ .should("have.length", 2);
990
+ });
991
+
992
+ it("handles tab selection in vertical mode with many tabs", () => {
993
+ const manyTabs = Array.from({ length: 10 }, (_, i) => (
994
+ <UDExTab text={`Tab ${i + 1}`} selected={i === 0}>
995
+ <div>Content {i + 1}</div>
996
+ </UDExTab>
997
+ ));
998
+
999
+ cy.mount(
1000
+ <UDExTabNavigation orientation="Vertical">
1001
+ {manyTabs}
1002
+ </UDExTabNavigation>
1003
+ );
1004
+
1005
+ // All tabs should be visible (no overflow)
1006
+ cy.get("[udex-tab-navigation]")
1007
+ .shadow()
1008
+ .find("button[role='tab']")
1009
+ .should("have.length", 10);
1010
+
1011
+ // Click on last tab
1012
+ cy.get("[udex-tab-navigation]")
1013
+ .shadow()
1014
+ .find("button[role='tab']")
1015
+ .eq(9)
1016
+ .click();
1017
+
1018
+ cy.get("[udex-tab-navigation]")
1019
+ .shadow()
1020
+ .find("button[role='tab'][aria-selected='true']")
1021
+ .should("contain.text", "Tab 10");
1022
+ });
1023
+ });
1024
+ });