@sapudex/web-components 2.31.0 → 2.32.1

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 (134) hide show
  1. package/cypress/specs/Breadcrumbs.cy.tsx +26 -0
  2. package/cypress/specs/MediaPlayer.cy.tsx +209 -0
  3. package/cypress/specs/SecondaryNavigationDesktop.cy.tsx +74 -21
  4. package/cypress/specs/SecondaryNavigationMobile.cy.tsx +58 -1
  5. package/cypress/specs/SelectBox.cy.tsx +35 -0
  6. package/cypress/specs/StepInput.cy.tsx +507 -0
  7. package/cypress/specs/TabNavigation.cy.tsx +603 -39
  8. package/cypress/support/component-imports.instrumented.ts +4 -0
  9. package/cypress/support/component-imports.src.ts +7 -3
  10. package/dist/Breadcrumbs.d.ts +5 -0
  11. package/dist/Breadcrumbs.js +8 -1
  12. package/dist/Breadcrumbs.js.map +1 -1
  13. package/dist/HeroBanner.d.ts +0 -3
  14. package/dist/HeroBanner.js +0 -5
  15. package/dist/HeroBanner.js.map +1 -1
  16. package/dist/HeroBannerTemplate.d.ts +2 -0
  17. package/dist/HeroBannerTemplate.js +6 -1
  18. package/dist/HeroBannerTemplate.js.map +1 -1
  19. package/dist/MastheadItem.js +5 -4
  20. package/dist/MastheadItem.js.map +1 -1
  21. package/dist/MediaPlayer.d.ts +15 -0
  22. package/dist/MediaPlayer.js +28 -0
  23. package/dist/MediaPlayer.js.map +1 -1
  24. package/dist/SecondaryNavigation.d.ts +12 -2
  25. package/dist/SecondaryNavigation.js +59 -8
  26. package/dist/SecondaryNavigation.js.map +1 -1
  27. package/dist/SecondaryNavigationItem.d.ts +0 -1
  28. package/dist/SecondaryNavigationItem.js +0 -4
  29. package/dist/SecondaryNavigationItem.js.map +1 -1
  30. package/dist/SecondaryNavigationSubItem.js +1 -1
  31. package/dist/SecondaryNavigationSubItem.js.map +1 -1
  32. package/dist/SelectBase.js +2 -1
  33. package/dist/SelectBase.js.map +1 -1
  34. package/dist/StepInput.d.ts +3 -0
  35. package/dist/StepInput.js +17 -0
  36. package/dist/StepInput.js.map +1 -1
  37. package/dist/StepInputTemplate.js +1 -1
  38. package/dist/StepInputTemplate.js.map +1 -1
  39. package/dist/Tab.d.ts +1 -1
  40. package/dist/Tab.js +3 -3
  41. package/dist/Tab.js.map +1 -1
  42. package/dist/TabNavigation.d.ts +64 -0
  43. package/dist/TabNavigation.js +124 -1
  44. package/dist/TabNavigation.js.map +1 -1
  45. package/dist/assets/{InputSuggestions-i2kFpCfC.js → InputSuggestions-DeXhpK0F.js} +1 -1
  46. package/dist/assets/{ListItemStandardExpandableTextTemplate-C1ceDaxu.js → ListItemStandardExpandableTextTemplate-jMEdrC07.js} +1 -1
  47. package/dist/assets/{bundle.esm-DQ3cxywT.js → bundle.esm-CwJAt93x.js} +219 -219
  48. package/dist/assets/messagebundle_en-iNqRB1VZ.js +1 -0
  49. package/dist/css/themes/BreadcrumbsPopover.css +1 -1
  50. package/dist/css/themes/FilterMenu.css +1 -1
  51. package/dist/css/themes/HeroBanner.css +1 -1
  52. package/dist/css/themes/Modal.css +1 -1
  53. package/dist/css/themes/RatingIndicator.css +1 -1
  54. package/dist/css/themes/SecondaryNavigationItem.css +1 -1
  55. package/dist/css/themes/Slider.css +1 -1
  56. package/dist/css/themes/TabNavigation.css +1 -1
  57. package/dist/custom-elements-internal.json +221 -0
  58. package/dist/custom-elements.json +149 -0
  59. package/dist/generated/assets/i18n/messagebundle_en.json +1 -1
  60. package/dist/generated/i18n/i18n-defaults.js +1 -1
  61. package/dist/generated/i18n/i18n-defaults.js.map +1 -1
  62. package/dist/generated/templates/SecondaryNavigationTemplate.lit.js +1 -1
  63. package/dist/generated/templates/SecondaryNavigationTemplate.lit.js.map +1 -1
  64. package/dist/generated/templates/TabNavigationTemplate.lit.js +10 -8
  65. package/dist/generated/templates/TabNavigationTemplate.lit.js.map +1 -1
  66. package/dist/generated/themes/BreadcrumbsPopover.css.d.ts +1 -1
  67. package/dist/generated/themes/BreadcrumbsPopover.css.js +1 -1
  68. package/dist/generated/themes/BreadcrumbsPopover.css.js.map +1 -1
  69. package/dist/generated/themes/FilterMenu.css.d.ts +1 -1
  70. package/dist/generated/themes/FilterMenu.css.js +1 -1
  71. package/dist/generated/themes/FilterMenu.css.js.map +1 -1
  72. package/dist/generated/themes/HeroBanner.css.d.ts +1 -1
  73. package/dist/generated/themes/HeroBanner.css.js +1 -1
  74. package/dist/generated/themes/HeroBanner.css.js.map +1 -1
  75. package/dist/generated/themes/Modal.css.d.ts +1 -1
  76. package/dist/generated/themes/Modal.css.js +1 -1
  77. package/dist/generated/themes/Modal.css.js.map +1 -1
  78. package/dist/generated/themes/RatingIndicator.css.d.ts +1 -1
  79. package/dist/generated/themes/RatingIndicator.css.js +1 -1
  80. package/dist/generated/themes/RatingIndicator.css.js.map +1 -1
  81. package/dist/generated/themes/SecondaryNavigationItem.css.d.ts +1 -1
  82. package/dist/generated/themes/SecondaryNavigationItem.css.js +1 -1
  83. package/dist/generated/themes/SecondaryNavigationItem.css.js.map +1 -1
  84. package/dist/generated/themes/Slider.css.d.ts +1 -1
  85. package/dist/generated/themes/Slider.css.js +1 -1
  86. package/dist/generated/themes/Slider.css.js.map +1 -1
  87. package/dist/generated/themes/TabNavigation.css.d.ts +1 -1
  88. package/dist/generated/themes/TabNavigation.css.js +1 -1
  89. package/dist/generated/themes/TabNavigation.css.js.map +1 -1
  90. package/dist/messagebundle.properties +1 -1
  91. package/dist/messagebundle_en.properties +1 -1
  92. package/dist/vscode.html-custom-data.json +23 -3
  93. package/dist/web-types.json +64 -4
  94. package/package.json +1 -1
  95. package/src/Breadcrumbs.ts +9 -1
  96. package/src/HeroBanner.ts +0 -5
  97. package/src/HeroBannerTemplate.tsx +6 -1
  98. package/src/MastheadItem.ts +5 -4
  99. package/src/MediaPlayer.ts +35 -0
  100. package/src/SecondaryNavigation.hbs +1 -0
  101. package/src/SecondaryNavigation.ts +77 -9
  102. package/src/SecondaryNavigationItem.ts +0 -6
  103. package/src/SecondaryNavigationSubItem.ts +1 -1
  104. package/src/SelectBase.ts +2 -1
  105. package/src/StepInput.ts +20 -0
  106. package/src/StepInputTemplate.tsx +1 -1
  107. package/src/Tab.ts +4 -4
  108. package/src/TabNavigation.hbs +15 -0
  109. package/src/TabNavigation.ts +134 -1
  110. package/src/generated/i18n/i18n-defaults.ts +1 -1
  111. package/src/generated/templates/SecondaryNavigationTemplate.lit.ts +1 -1
  112. package/src/generated/templates/TabNavigationTemplate.lit.ts +10 -8
  113. package/src/generated/themes/BreadcrumbsPopover.css.ts +1 -1
  114. package/src/generated/themes/FilterMenu.css.ts +1 -1
  115. package/src/generated/themes/HeroBanner.css.ts +1 -1
  116. package/src/generated/themes/Modal.css.ts +1 -1
  117. package/src/generated/themes/RatingIndicator.css.ts +1 -1
  118. package/src/generated/themes/SecondaryNavigationItem.css.ts +1 -1
  119. package/src/generated/themes/Slider.css.ts +1 -1
  120. package/src/generated/themes/TabNavigation.css.ts +1 -1
  121. package/src/i18n/messagebundle.properties +1 -1
  122. package/src/i18n/messagebundle_en.properties +1 -1
  123. package/src/themes/BreadcrumbsPopover.css +2 -2
  124. package/src/themes/FilterMenu.css +5 -1
  125. package/src/themes/HeroBanner.css +93 -76
  126. package/src/themes/Modal.css +3 -2
  127. package/src/themes/SecondaryNavigationItem.css +4 -1
  128. package/src/themes/TabNavigation.css +27 -16
  129. package/dist/assets/messagebundle_en-DWAgV5VH.js +0 -1
  130. package/dist/generated/templates/HeroBannerTemplate.lit.d.ts +0 -3
  131. package/dist/generated/templates/HeroBannerTemplate.lit.js +0 -15
  132. package/dist/generated/templates/HeroBannerTemplate.lit.js.map +0 -1
  133. package/src/HeroBanner.hbs +0 -46
  134. package/src/generated/templates/HeroBannerTemplate.lit.ts +0 -19
@@ -0,0 +1,507 @@
1
+ import { UDExStepInput } from "../support/component-imports.js";
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Helper: asserts focus has moved to the native <input> inside udex-text-field.
5
+ // _incValue()/_decValue() call this.input.focus() when a boundary is hit.
6
+ //
7
+ // With nested shadow DOMs, document.activeElement returns the outermost shadow
8
+ // host visible to the document (<udex-step-input>). To confirm focus is
9
+ // specifically on <udex-text-field>, we traverse one level deeper using
10
+ // shadowRoot.activeElement on the step-input's shadow root.
11
+ // ---------------------------------------------------------------------------
12
+ function assertFocusIsInput() {
13
+ cy.get("[udex-step-input]").then(($host) => {
14
+ cy.document().then((doc) => {
15
+ const stepInput = $host[0];
16
+ const textField = stepInput.shadowRoot?.querySelector("[udex-text-field]");
17
+ expect(
18
+ doc.activeElement === stepInput && stepInput.shadowRoot?.activeElement === textField,
19
+ "focus is inside the text field after boundary was reached",
20
+ ).to.be.true;
21
+ });
22
+ });
23
+ }
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Cypress component tests — StepInput focus management on button disable
27
+ // ---------------------------------------------------------------------------
28
+ describe("UDExStepInput — focus management on button disable", () => {
29
+ // -------------------------------------------------------------------------
30
+ // Scenario 1: increment to max via click
31
+ // Setup: value=4, min=0, max=5, step=1
32
+ // Action: click the + button (value becomes 5, _incIconDisabled becomes true)
33
+ // Expect: focus moves to the inner text input, NOT lost to document.body
34
+ // -------------------------------------------------------------------------
35
+ describe("incrementing to max value — click", () => {
36
+ beforeEach(() => {
37
+ cy.mount(
38
+ <UDExStepInput
39
+ id="si-inc-to-max"
40
+ value={4}
41
+ min={0}
42
+ max={5}
43
+ step={1}
44
+ />,
45
+ );
46
+ });
47
+
48
+ it("disables the increment button after reaching max", () => {
49
+ cy.get("[udex-step-input]")
50
+ .shadow()
51
+ .find(".udex-step-input__button_inc")
52
+ .click();
53
+ cy.get("[udex-step-input]")
54
+ .shadow()
55
+ .find(".udex-step-input__button_inc")
56
+ .should("have.attr", "disabled");
57
+ });
58
+
59
+ // Updated: source sends focus to this.input (inner text input), not the opposite button
60
+ it("moves focus to the text input when increment button becomes disabled on click", () => {
61
+ cy.get("[udex-step-input]")
62
+ .shadow()
63
+ .find(".udex-step-input__button_inc")
64
+ .click();
65
+ assertFocusIsInput();
66
+ });
67
+
68
+ it("keeps the decrement button enabled after incrementing to max", () => {
69
+ cy.get("[udex-step-input]")
70
+ .shadow()
71
+ .find(".udex-step-input__button_inc")
72
+ .click();
73
+ cy.get("[udex-step-input]")
74
+ .shadow()
75
+ .find(".udex-step-input__button_dec")
76
+ .should("not.have.attr", "disabled");
77
+ });
78
+ });
79
+
80
+ // -------------------------------------------------------------------------
81
+ // Scenario 2: increment to max via keyboard (Enter on the + button)
82
+ // Setup: value=4, min=0, max=5, step=1
83
+ // Action: focus the + button, press Enter (triggers onClick → _incValue)
84
+ // Expect: focus moves to the inner text input
85
+ // -------------------------------------------------------------------------
86
+ describe("incrementing to max value — Enter key", () => {
87
+ beforeEach(() => {
88
+ cy.mount(
89
+ <UDExStepInput
90
+ id="si-inc-to-max-kbd"
91
+ value={4}
92
+ min={0}
93
+ max={5}
94
+ step={1}
95
+ />,
96
+ );
97
+ });
98
+
99
+ it("moves focus to the text input when increment button becomes disabled via Enter key", () => {
100
+ cy.get("[udex-step-input]")
101
+ .shadow()
102
+ .find(".udex-step-input__button_inc")
103
+ .shadow()
104
+ .find(".ui5-button-root")
105
+ .focus()
106
+ .realPress("Enter");
107
+ assertFocusIsInput();
108
+ });
109
+
110
+ it("disables the increment button after reaching max via Enter key", () => {
111
+ cy.get("[udex-step-input]")
112
+ .shadow()
113
+ .find(".udex-step-input__button_inc")
114
+ .shadow()
115
+ .find(".ui5-button-root")
116
+ .focus()
117
+ .realPress("Enter");
118
+ cy.get("[udex-step-input]")
119
+ .shadow()
120
+ .find(".udex-step-input__button_inc")
121
+ .should("have.attr", "disabled");
122
+ });
123
+ });
124
+
125
+ // -------------------------------------------------------------------------
126
+ // Scenario 3: decrement to min via click
127
+ // Setup: value=1, min=0, max=5, step=1
128
+ // Action: click the - button (value becomes 0, _decIconDisabled becomes true)
129
+ // Expect: focus moves to the inner text input
130
+ // -------------------------------------------------------------------------
131
+ describe("decrementing to min value — click", () => {
132
+ beforeEach(() => {
133
+ cy.mount(
134
+ <UDExStepInput
135
+ id="si-dec-to-min"
136
+ value={1}
137
+ min={0}
138
+ max={5}
139
+ step={1}
140
+ />,
141
+ );
142
+ });
143
+
144
+ it("disables the decrement button after reaching min", () => {
145
+ cy.get("[udex-step-input]")
146
+ .shadow()
147
+ .find(".udex-step-input__button_dec")
148
+ .click();
149
+ cy.get("[udex-step-input]")
150
+ .shadow()
151
+ .find(".udex-step-input__button_dec")
152
+ .should("have.attr", "disabled");
153
+ });
154
+
155
+ // Updated: source sends focus to this.input (inner text input), not the opposite button
156
+ it("moves focus to the text input when decrement button becomes disabled on click", () => {
157
+ cy.get("[udex-step-input]")
158
+ .shadow()
159
+ .find(".udex-step-input__button_dec")
160
+ .click();
161
+ assertFocusIsInput();
162
+ });
163
+
164
+ it("keeps the increment button enabled after decrementing to min", () => {
165
+ cy.get("[udex-step-input]")
166
+ .shadow()
167
+ .find(".udex-step-input__button_dec")
168
+ .click();
169
+ cy.get("[udex-step-input]")
170
+ .shadow()
171
+ .find(".udex-step-input__button_inc")
172
+ .should("not.have.attr", "disabled");
173
+ });
174
+ });
175
+
176
+ // -------------------------------------------------------------------------
177
+ // Scenario 4: decrement to min via keyboard (Enter on the - button)
178
+ // Setup: value=1, min=0, max=5, step=1
179
+ // Action: focus the - button, press Enter (triggers onClick → _decValue)
180
+ // Expect: focus moves to the inner text input
181
+ // -------------------------------------------------------------------------
182
+ describe("decrementing to min value — Enter key", () => {
183
+ beforeEach(() => {
184
+ cy.mount(
185
+ <UDExStepInput
186
+ id="si-dec-to-min-kbd"
187
+ value={1}
188
+ min={0}
189
+ max={5}
190
+ step={1}
191
+ />,
192
+ );
193
+ });
194
+
195
+ it("moves focus to the text input when decrement button becomes disabled via Enter key", () => {
196
+ cy.get("[udex-step-input]")
197
+ .shadow()
198
+ .find(".udex-step-input__button_dec")
199
+ .shadow()
200
+ .find(".ui5-button-root")
201
+ .focus()
202
+ .realPress("Enter");
203
+ assertFocusIsInput();
204
+ });
205
+
206
+ it("disables the decrement button after reaching min via Enter key", () => {
207
+ cy.get("[udex-step-input]")
208
+ .shadow()
209
+ .find(".udex-step-input__button_dec")
210
+ .shadow()
211
+ .find(".ui5-button-root")
212
+ .focus()
213
+ .realPress("Enter");
214
+ cy.get("[udex-step-input]")
215
+ .shadow()
216
+ .find(".udex-step-input__button_dec")
217
+ .should("have.attr", "disabled");
218
+ });
219
+ });
220
+
221
+ // -------------------------------------------------------------------------
222
+ // Scenario 5: min equals max — both buttons disabled on render
223
+ // Both buttons are disabled from the start; clicking them (forced) must
224
+ // not change the value because _incIconInactive / _decIconInactive is true.
225
+ // -------------------------------------------------------------------------
226
+ describe("min equals max — both buttons disabled on render", () => {
227
+ beforeEach(() => {
228
+ cy.mount(
229
+ <UDExStepInput
230
+ id="si-min-eq-max"
231
+ value={3}
232
+ min={3}
233
+ max={3}
234
+ step={1}
235
+ />,
236
+ );
237
+ });
238
+
239
+ it("disables the increment button when min equals max", () => {
240
+ cy.get("[udex-step-input]")
241
+ .shadow()
242
+ .find(".udex-step-input__button_inc")
243
+ .should("have.attr", "disabled");
244
+ });
245
+
246
+ it("disables the decrement button when min equals max", () => {
247
+ cy.get("[udex-step-input]")
248
+ .shadow()
249
+ .find(".udex-step-input__button_dec")
250
+ .should("have.attr", "disabled");
251
+ });
252
+
253
+ it("does not change value when increment button is clicked while both are disabled", () => {
254
+ // The click guard (_incIconInactive) prevents any value change.
255
+ cy.get("[udex-step-input]")
256
+ .shadow()
257
+ .find(".udex-step-input__button_inc")
258
+ .click({ force: true }); // force because the element is disabled
259
+ cy.get("[udex-step-input]").should("have.attr", "value", "3");
260
+ });
261
+
262
+ it("does not change value when decrement button is clicked while both are disabled", () => {
263
+ cy.get("[udex-step-input]")
264
+ .shadow()
265
+ .find(".udex-step-input__button_dec")
266
+ .click({ force: true });
267
+ cy.get("[udex-step-input]").should("have.attr", "value", "3");
268
+ });
269
+ });
270
+
271
+ // -------------------------------------------------------------------------
272
+ // Scenario 6: focus stays inside the component boundary
273
+ // After hitting a boundary the active element must still be a descendant
274
+ // of udex-step-input — focus must never escape to document.body.
275
+ // This validates the core WCAG requirement for focus management.
276
+ // -------------------------------------------------------------------------
277
+ describe("focus stays inside component when a button becomes disabled", () => {
278
+ it("focus remains within step-input after incrementing to max", () => {
279
+ cy.mount(
280
+ <UDExStepInput value={4} min={0} max={5} step={1} />,
281
+ );
282
+ cy.get("[udex-step-input]")
283
+ .shadow()
284
+ .find(".udex-step-input__button_inc")
285
+ .click();
286
+
287
+ cy.document().then((doc) => {
288
+ cy.get("[udex-step-input]").then(($host) => {
289
+ const activeEl = doc.activeElement;
290
+ expect(
291
+ activeEl === $host[0] || $host[0].contains(activeEl),
292
+ "focus stays within udex-step-input after incrementing to max",
293
+ ).to.be.true;
294
+ });
295
+ });
296
+ });
297
+
298
+ it("focus remains within step-input after decrementing to min", () => {
299
+ cy.mount(
300
+ <UDExStepInput value={1} min={0} max={5} step={1} />,
301
+ );
302
+ cy.get("[udex-step-input]")
303
+ .shadow()
304
+ .find(".udex-step-input__button_dec")
305
+ .click();
306
+
307
+ cy.document().then((doc) => {
308
+ cy.get("[udex-step-input]").then(($host) => {
309
+ const activeEl = doc.activeElement;
310
+ expect(
311
+ activeEl === $host[0] || $host[0].contains(activeEl),
312
+ "focus stays within udex-step-input after decrementing to min",
313
+ ).to.be.true;
314
+ });
315
+ });
316
+ });
317
+ });
318
+
319
+ // -------------------------------------------------------------------------
320
+ // Scenario 7: focus is NOT redirected when boundary is not reached
321
+ // _incValue / _decValue must only redirect focus when the boundary is hit.
322
+ // Increments and decrements that land in the middle of the range must leave
323
+ // focus on the clicked button (default browser behaviour).
324
+ // -------------------------------------------------------------------------
325
+ describe("focus is not redirected when boundary is not reached", () => {
326
+ it("does not redirect focus to text input after a non-boundary increment", () => {
327
+ cy.mount(
328
+ <UDExStepInput value={2} min={0} max={5} step={1} />,
329
+ );
330
+ cy.get("[udex-step-input]")
331
+ .shadow()
332
+ .find(".udex-step-input__button_inc")
333
+ .click();
334
+
335
+ // Button must still be enabled (value 3, below max 5)
336
+ cy.get("[udex-step-input]")
337
+ .shadow()
338
+ .find(".udex-step-input__button_inc")
339
+ .should("not.have.attr", "disabled");
340
+
341
+ // Focus must NOT have been redirected to the text field
342
+ cy.get("[udex-step-input]")
343
+ .shadow()
344
+ .find("[udex-text-field]")
345
+ .then(($textField) => {
346
+ cy.document().then((doc) => {
347
+ const activeEl = doc.activeElement;
348
+ expect(
349
+ activeEl === $textField[0] || $textField[0].contains(activeEl),
350
+ "focus was NOT incorrectly redirected to text input after non-boundary increment",
351
+ ).to.be.false;
352
+ });
353
+ });
354
+ });
355
+
356
+ it("does not redirect focus to text input after a non-boundary decrement", () => {
357
+ cy.mount(
358
+ <UDExStepInput value={3} min={0} max={5} step={1} />,
359
+ );
360
+ cy.get("[udex-step-input]")
361
+ .shadow()
362
+ .find(".udex-step-input__button_dec")
363
+ .click();
364
+
365
+ // Button must still be enabled (value 2, above min 0)
366
+ cy.get("[udex-step-input]")
367
+ .shadow()
368
+ .find(".udex-step-input__button_dec")
369
+ .should("not.have.attr", "disabled");
370
+
371
+ // Focus must NOT have been redirected to the text field
372
+ cy.get("[udex-step-input]")
373
+ .shadow()
374
+ .find("[udex-text-field]")
375
+ .then(($textField) => {
376
+ cy.document().then((doc) => {
377
+ const activeEl = doc.activeElement;
378
+ expect(
379
+ activeEl === $textField[0] || $textField[0].contains(activeEl),
380
+ "focus was NOT incorrectly redirected to text input after non-boundary decrement",
381
+ ).to.be.false;
382
+ });
383
+ });
384
+ });
385
+ });
386
+
387
+ // -------------------------------------------------------------------------
388
+ // Scenario 8: aria-live region suppressed on boundary hit
389
+ // When _incValue / _decValue hits a boundary and redirects focus to the
390
+ // input, the aria-live region must be empty so the screen reader does not
391
+ // double-announce the value (once from the live region and once from the
392
+ // focused input).
393
+ // -------------------------------------------------------------------------
394
+ describe("aria-live region suppressed on boundary hit", () => {
395
+ it("clears the aria-live region when incrementing to max", () => {
396
+ cy.mount(
397
+ <UDExStepInput value={4} min={0} max={5} step={1} />,
398
+ );
399
+ cy.get("[udex-step-input]")
400
+ .shadow()
401
+ .find(".udex-step-input__button_inc")
402
+ .click();
403
+
404
+ // The increment button must be disabled (boundary reached)
405
+ cy.get("[udex-step-input]")
406
+ .shadow()
407
+ .find(".udex-step-input__button_inc")
408
+ .should("have.attr", "disabled");
409
+
410
+ // The aria-live div must be empty to prevent SR double-announcement
411
+ cy.get("[udex-step-input]")
412
+ .shadow()
413
+ .find("[aria-live]")
414
+ .should("have.text", "");
415
+ });
416
+
417
+ it("clears the aria-live region when decrementing to min", () => {
418
+ cy.mount(
419
+ <UDExStepInput value={1} min={0} max={5} step={1} />,
420
+ );
421
+ cy.get("[udex-step-input]")
422
+ .shadow()
423
+ .find(".udex-step-input__button_dec")
424
+ .click();
425
+
426
+ // The decrement button must be disabled (boundary reached)
427
+ cy.get("[udex-step-input]")
428
+ .shadow()
429
+ .find(".udex-step-input__button_dec")
430
+ .should("have.attr", "disabled");
431
+
432
+ // The aria-live div must be empty to prevent SR double-announcement
433
+ cy.get("[udex-step-input]")
434
+ .shadow()
435
+ .find("[aria-live]")
436
+ .should("have.text", "");
437
+ });
438
+ });
439
+
440
+ // -------------------------------------------------------------------------
441
+ // Scenario 9: aria-live region still announces for mid-range changes
442
+ // When the value changes but does NOT hit a boundary, the aria-live
443
+ // region must contain the updated value so the screen reader announces
444
+ // it normally.
445
+ // -------------------------------------------------------------------------
446
+ describe("aria-live region announces value for mid-range changes", () => {
447
+ it("shows the updated value in the aria-live region after a non-boundary increment", () => {
448
+ cy.mount(
449
+ <UDExStepInput value={2} min={0} max={5} step={1} />,
450
+ );
451
+ cy.get("[udex-step-input]")
452
+ .shadow()
453
+ .find(".udex-step-input__button_inc")
454
+ .click();
455
+
456
+ // The increment button must still be enabled (value 3, below max 5)
457
+ cy.get("[udex-step-input]")
458
+ .shadow()
459
+ .find(".udex-step-input__button_inc")
460
+ .should("not.have.attr", "disabled");
461
+
462
+ // The aria-live div must contain the new value for SR announcement
463
+ cy.get("[udex-step-input]")
464
+ .shadow()
465
+ .find("[aria-live]")
466
+ .should("have.text", "3");
467
+ });
468
+
469
+ it("shows the updated value in the aria-live region after a non-boundary decrement", () => {
470
+ cy.mount(
471
+ <UDExStepInput value={3} min={0} max={5} step={1} />,
472
+ );
473
+ cy.get("[udex-step-input]")
474
+ .shadow()
475
+ .find(".udex-step-input__button_dec")
476
+ .click();
477
+
478
+ // The decrement button must still be enabled (value 2, above min 0)
479
+ cy.get("[udex-step-input]")
480
+ .shadow()
481
+ .find(".udex-step-input__button_dec")
482
+ .should("not.have.attr", "disabled");
483
+
484
+ // The aria-live div must contain the new value for SR announcement
485
+ cy.get("[udex-step-input]")
486
+ .shadow()
487
+ .find("[aria-live]")
488
+ .should("have.text", "2");
489
+ });
490
+
491
+ it("shows the precision-formatted value in the aria-live region", () => {
492
+ cy.mount(
493
+ <UDExStepInput value={2} min={0} max={5} step={0.1} valuePrecision={1} />,
494
+ );
495
+ cy.get("[udex-step-input]")
496
+ .shadow()
497
+ .find(".udex-step-input__button_inc")
498
+ .click();
499
+
500
+ // The aria-live div must contain the precision-formatted value
501
+ cy.get("[udex-step-input]")
502
+ .shadow()
503
+ .find("[aria-live]")
504
+ .should("have.text", "2.1");
505
+ });
506
+ });
507
+ });