@nuvia-ui/components 4.0.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 (230) hide show
  1. package/package.json +27 -0
  2. package/src/ds-accordion/ds-accordion-item.js +288 -0
  3. package/src/ds-accordion/ds-accordion-item.stories.js +82 -0
  4. package/src/ds-accordion/ds-accordion.a11y.test.js +92 -0
  5. package/src/ds-accordion/ds-accordion.js +68 -0
  6. package/src/ds-accordion/ds-accordion.stories.js +118 -0
  7. package/src/ds-accordion/ds-accordion.test.js +146 -0
  8. package/src/ds-accordion/index.js +2 -0
  9. package/src/ds-action-bar/ds-action-bar.js +116 -0
  10. package/src/ds-action-bar/ds-action-bar.stories.js +86 -0
  11. package/src/ds-action-bar/ds-action-bar.test.js +64 -0
  12. package/src/ds-action-bar/index.js +1 -0
  13. package/src/ds-alert/ds-alert.a11y.test.js +151 -0
  14. package/src/ds-alert/ds-alert.js +223 -0
  15. package/src/ds-alert/ds-alert.mdx +142 -0
  16. package/src/ds-alert/ds-alert.stories.js +166 -0
  17. package/src/ds-alert/ds-alert.test.js +256 -0
  18. package/src/ds-alert/index.js +1 -0
  19. package/src/ds-avatar/ds-avatar.a11y.test.js +45 -0
  20. package/src/ds-avatar/ds-avatar.js +216 -0
  21. package/src/ds-avatar/ds-avatar.stories.js +120 -0
  22. package/src/ds-avatar/ds-avatar.test.js +83 -0
  23. package/src/ds-avatar/index.js +1 -0
  24. package/src/ds-avatar-extended/ds-avatar-extended.a11y.test.js +29 -0
  25. package/src/ds-avatar-extended/ds-avatar-extended.js +108 -0
  26. package/src/ds-avatar-extended/ds-avatar-extended.stories.js +93 -0
  27. package/src/ds-avatar-extended/ds-avatar-extended.test.js +66 -0
  28. package/src/ds-avatar-extended/index.js +1 -0
  29. package/src/ds-banner/ds-banner.a11y.test.js +51 -0
  30. package/src/ds-banner/ds-banner.js +233 -0
  31. package/src/ds-banner/ds-banner.stories.js +185 -0
  32. package/src/ds-banner/ds-banner.test.js +116 -0
  33. package/src/ds-banner/index.js +1 -0
  34. package/src/ds-breadcrumb-item/ds-breadcrumb-item.js +135 -0
  35. package/src/ds-breadcrumb-item/ds-breadcrumb-item.stories.js +49 -0
  36. package/src/ds-breadcrumb-item/ds-breadcrumb-item.test.js +55 -0
  37. package/src/ds-breadcrumbs/ds-breadcrumbs.js +194 -0
  38. package/src/ds-breadcrumbs/ds-breadcrumbs.stories.js +54 -0
  39. package/src/ds-breadcrumbs/ds-breadcrumbs.test.js +33 -0
  40. package/src/ds-button/ds-button.a11y.test.js +49 -0
  41. package/src/ds-button/ds-button.js +205 -0
  42. package/src/ds-button/ds-button.mdx +141 -0
  43. package/src/ds-button/ds-button.stories.js +152 -0
  44. package/src/ds-button/ds-button.test.js +62 -0
  45. package/src/ds-button/index.js +1 -0
  46. package/src/ds-button-group/ds-button-group.js +82 -0
  47. package/src/ds-button-group/ds-button-group.mdx +39 -0
  48. package/src/ds-button-group/ds-button-group.stories.js +47 -0
  49. package/src/ds-button-group/ds-button-group.test.js +47 -0
  50. package/src/ds-button-group/index.js +1 -0
  51. package/src/ds-checkbox/ds-checkbox.a11y.test.js +79 -0
  52. package/src/ds-checkbox/ds-checkbox.js +271 -0
  53. package/src/ds-checkbox/ds-checkbox.stories.js +77 -0
  54. package/src/ds-checkbox/ds-checkbox.test.js +191 -0
  55. package/src/ds-checkbox/index.js +1 -0
  56. package/src/ds-checkbox-group/ds-checkbox-group.a11y.test.js +146 -0
  57. package/src/ds-checkbox-group/ds-checkbox-group.js +235 -0
  58. package/src/ds-checkbox-group/ds-checkbox-group.stories.js +210 -0
  59. package/src/ds-checkbox-group/ds-checkbox-group.test.js +150 -0
  60. package/src/ds-checkbox-group/index.js +1 -0
  61. package/src/ds-dialog/ds-dialog.js +466 -0
  62. package/src/ds-dialog/ds-dialog.stories.js +274 -0
  63. package/src/ds-dialog/ds-dialog.test.js +441 -0
  64. package/src/ds-dialog/index.js +1 -0
  65. package/src/ds-dropdown/ds-dropdown.a11y.test.js +80 -0
  66. package/src/ds-dropdown/ds-dropdown.js +891 -0
  67. package/src/ds-dropdown/ds-dropdown.stories.js +259 -0
  68. package/src/ds-dropdown/ds-dropdown.test.js +268 -0
  69. package/src/ds-dropdown/index.js +1 -0
  70. package/src/ds-dropdown-group/ds-dropdown-group.js +55 -0
  71. package/src/ds-dropdown-panel/ds-dropdown-panel.js +34 -0
  72. package/src/ds-file-uploaded/ds-file-uploaded.a11y.test.js +40 -0
  73. package/src/ds-file-uploaded/ds-file-uploaded.js +135 -0
  74. package/src/ds-file-uploaded/ds-file-uploaded.mdx +33 -0
  75. package/src/ds-file-uploaded/ds-file-uploaded.stories.js +81 -0
  76. package/src/ds-file-uploaded/ds-file-uploaded.test.js +85 -0
  77. package/src/ds-file-uploader/ds-file-uploader.a11y.test.js +61 -0
  78. package/src/ds-file-uploader/ds-file-uploader.js +442 -0
  79. package/src/ds-file-uploader/ds-file-uploader.mdx +44 -0
  80. package/src/ds-file-uploader/ds-file-uploader.stories.js +76 -0
  81. package/src/ds-file-uploader/ds-file-uploader.test.js +142 -0
  82. package/src/ds-header/ds-header.a11y.test.js +38 -0
  83. package/src/ds-header/ds-header.js +149 -0
  84. package/src/ds-header/ds-header.stories.js +63 -0
  85. package/src/ds-header/ds-header.test.js +52 -0
  86. package/src/ds-header/index.js +1 -0
  87. package/src/ds-header-nav/ds-header-nav.a11y.test.js +69 -0
  88. package/src/ds-header-nav/ds-header-nav.js +114 -0
  89. package/src/ds-header-nav/ds-header-nav.stories.js +17 -0
  90. package/src/ds-header-nav/ds-header-nav.test.js +93 -0
  91. package/src/ds-header-nav-item/ds-header-nav-item.a11y.test.js +71 -0
  92. package/src/ds-header-nav-item/ds-header-nav-item.js +124 -0
  93. package/src/ds-header-nav-item/ds-header-nav-item.stories.js +43 -0
  94. package/src/ds-header-nav-item/ds-header-nav-item.test.js +61 -0
  95. package/src/ds-icon/ds-icon.a11y.test.js +49 -0
  96. package/src/ds-icon/ds-icon.js +75 -0
  97. package/src/ds-icon/ds-icon.mdx +36 -0
  98. package/src/ds-icon/ds-icon.stories.js +88 -0
  99. package/src/ds-icon/ds-icon.test.js +97 -0
  100. package/src/ds-icon/index.js +1 -0
  101. package/src/ds-icon-button/ds-icon-button.a11y.test.js +55 -0
  102. package/src/ds-icon-button/ds-icon-button.js +224 -0
  103. package/src/ds-icon-button/ds-icon-button.mdx +131 -0
  104. package/src/ds-icon-button/ds-icon-button.stories.js +128 -0
  105. package/src/ds-icon-button/ds-icon-button.test.js +90 -0
  106. package/src/ds-icon-button/index.js +1 -0
  107. package/src/ds-input/ds-input.a11y.test.js +145 -0
  108. package/src/ds-input/ds-input.js +645 -0
  109. package/src/ds-input/ds-input.mdx +251 -0
  110. package/src/ds-input/ds-input.stories.js +298 -0
  111. package/src/ds-input/ds-input.test.js +792 -0
  112. package/src/ds-input/index.js +1 -0
  113. package/src/ds-link/ds-link.js +111 -0
  114. package/src/ds-link/ds-link.stories.js +56 -0
  115. package/src/ds-link/ds-link.test.js +74 -0
  116. package/src/ds-list-item/ds-list-item.a11y.test.js +39 -0
  117. package/src/ds-list-item/ds-list-item.js +292 -0
  118. package/src/ds-list-item/ds-list-item.stories.js +101 -0
  119. package/src/ds-list-item/ds-list-item.test.js +63 -0
  120. package/src/ds-menu/ds-menu.js +30 -0
  121. package/src/ds-menu/ds-menu.stories.js +120 -0
  122. package/src/ds-menu/ds-menu.test.js +123 -0
  123. package/src/ds-menu-group/ds-menu-group.js +101 -0
  124. package/src/ds-menu-group/ds-menu-group.stories.js +99 -0
  125. package/src/ds-nav-item/ds-nav-item.a11y.test.js +91 -0
  126. package/src/ds-nav-item/ds-nav-item.js +307 -0
  127. package/src/ds-nav-item/ds-nav-item.stories.js +99 -0
  128. package/src/ds-nav-item/ds-nav-item.test.js +169 -0
  129. package/src/ds-nav-item/index.js +1 -0
  130. package/src/ds-nav-vertical/ds-nav-vertical.a11y.test.js +69 -0
  131. package/src/ds-nav-vertical/ds-nav-vertical.js +173 -0
  132. package/src/ds-nav-vertical/ds-nav-vertical.stories.js +124 -0
  133. package/src/ds-nav-vertical/ds-nav-vertical.test.js +176 -0
  134. package/src/ds-nav-vertical/index.js +1 -0
  135. package/src/ds-pagination/ds-pagination.a11y.test.js +50 -0
  136. package/src/ds-pagination/ds-pagination.js +232 -0
  137. package/src/ds-pagination/ds-pagination.stories.js +63 -0
  138. package/src/ds-pagination/ds-pagination.test.js +141 -0
  139. package/src/ds-pagination/index.js +1 -0
  140. package/src/ds-progress-bar/ds-progress-bar.a11y.test.js +25 -0
  141. package/src/ds-progress-bar/ds-progress-bar.js +81 -0
  142. package/src/ds-progress-bar/ds-progress-bar.stories.js +69 -0
  143. package/src/ds-progress-bar/ds-progress-bar.test.js +60 -0
  144. package/src/ds-radio/ds-radio.a11y.test.js +69 -0
  145. package/src/ds-radio/ds-radio.js +240 -0
  146. package/src/ds-radio/ds-radio.stories.js +102 -0
  147. package/src/ds-radio/ds-radio.test.js +114 -0
  148. package/src/ds-radio/index.js +1 -0
  149. package/src/ds-radio-group/ds-radio-group.a11y.test.js +164 -0
  150. package/src/ds-radio-group/ds-radio-group.js +257 -0
  151. package/src/ds-radio-group/ds-radio-group.stories.js +247 -0
  152. package/src/ds-radio-group/ds-radio-group.test.js +194 -0
  153. package/src/ds-radio-group/index.js +1 -0
  154. package/src/ds-rich-list/ds-rich-list.js +246 -0
  155. package/src/ds-rich-list/ds-rich-list.stories.js +368 -0
  156. package/src/ds-rich-list/ds-rich-list.test.js +293 -0
  157. package/src/ds-rich-list-item/ds-rich-list-item.js +579 -0
  158. package/src/ds-rich-list-item/ds-rich-list-item.stories.js +197 -0
  159. package/src/ds-rich-list-item/ds-rich-list-item.test.js +434 -0
  160. package/src/ds-slider/ds-slider.js +399 -0
  161. package/src/ds-slider/ds-slider.stories.js +107 -0
  162. package/src/ds-slider/ds-slider.test.js +308 -0
  163. package/src/ds-spinner/ds-spinner.js +173 -0
  164. package/src/ds-spinner/ds-spinner.stories.js +52 -0
  165. package/src/ds-spinner/ds-spinner.test.js +50 -0
  166. package/src/ds-status-border/ds-status-border.js +88 -0
  167. package/src/ds-status-border/ds-status-border.stories.js +242 -0
  168. package/src/ds-status-border/ds-status-border.test.js +168 -0
  169. package/src/ds-stepper/ds-stepper.a11y.test.js +198 -0
  170. package/src/ds-stepper/ds-stepper.js +207 -0
  171. package/src/ds-stepper/ds-stepper.stories.js +530 -0
  172. package/src/ds-stepper/ds-stepper.test.js +311 -0
  173. package/src/ds-stepper-item/ds-stepper-item.js +485 -0
  174. package/src/ds-stepper-item/ds-stepper-item.stories.js +288 -0
  175. package/src/ds-switch/ds-switch.js +348 -0
  176. package/src/ds-switch/ds-switch.stories.js +145 -0
  177. package/src/ds-switch/ds-switch.test.js +226 -0
  178. package/src/ds-switch/index.js +1 -0
  179. package/src/ds-tab-item/ds-tab-item.js +341 -0
  180. package/src/ds-tab-item/ds-tab-item.stories.js +69 -0
  181. package/src/ds-tabs/ds-tab-panel.js +48 -0
  182. package/src/ds-tabs/ds-tabs.a11y.test.js +56 -0
  183. package/src/ds-tabs/ds-tabs.js +180 -0
  184. package/src/ds-tabs/ds-tabs.stories.js +152 -0
  185. package/src/ds-tabs/ds-tabs.test.js +306 -0
  186. package/src/ds-tabs/index.js +3 -0
  187. package/src/ds-tag-action/ds-tag-action.a11y.test.js +32 -0
  188. package/src/ds-tag-action/ds-tag-action.js +185 -0
  189. package/src/ds-tag-action/ds-tag-action.stories.js +55 -0
  190. package/src/ds-tag-action/ds-tag-action.test.js +44 -0
  191. package/src/ds-tag-removable/ds-tag-removable.a11y.test.js +24 -0
  192. package/src/ds-tag-removable/ds-tag-removable.js +146 -0
  193. package/src/ds-tag-removable/ds-tag-removable.stories.js +52 -0
  194. package/src/ds-tag-removable/ds-tag-removable.test.js +46 -0
  195. package/src/ds-tag-status/ds-tag-status.a11y.test.js +93 -0
  196. package/src/ds-tag-status/ds-tag-status.js +164 -0
  197. package/src/ds-tag-status/ds-tag-status.stories.js +200 -0
  198. package/src/ds-tag-status/ds-tag-status.test.js +140 -0
  199. package/src/ds-tag-status/index.js +1 -0
  200. package/src/ds-textarea/ds-textarea-clearable.test.js +89 -0
  201. package/src/ds-textarea/ds-textarea.a11y.test.js +66 -0
  202. package/src/ds-textarea/ds-textarea.js +505 -0
  203. package/src/ds-textarea/ds-textarea.stories.js +335 -0
  204. package/src/ds-textarea/ds-textarea.test.js +218 -0
  205. package/src/ds-textarea/index.js +1 -0
  206. package/src/ds-thumbnail/ds-thumbnail.js +207 -0
  207. package/src/ds-thumbnail/ds-thumbnail.stories.js +217 -0
  208. package/src/ds-thumbnail/ds-thumbnail.test.js +220 -0
  209. package/src/ds-toast/ds-toast-provider.js +110 -0
  210. package/src/ds-toast/ds-toast.a11y.test.js +34 -0
  211. package/src/ds-toast/ds-toast.js +243 -0
  212. package/src/ds-toast/ds-toast.stories.js +143 -0
  213. package/src/ds-toast/ds-toast.test.js +93 -0
  214. package/src/ds-toast/index.js +2 -0
  215. package/src/ds-tooltip/ds-tooltip.a11y.test.js +110 -0
  216. package/src/ds-tooltip/ds-tooltip.js +217 -0
  217. package/src/ds-tooltip/ds-tooltip.mdx +75 -0
  218. package/src/ds-tooltip/ds-tooltip.stories.js +72 -0
  219. package/src/ds-tooltip/ds-tooltip.test.js +191 -0
  220. package/src/ds-tooltip/index.js +1 -0
  221. package/src/ds-tooltip/positioner.js +117 -0
  222. package/src/index.js +50 -0
  223. package/src/mixins/field-label.mixin.js +113 -0
  224. package/src/mixins/field-message.mixin.js +66 -0
  225. package/src/token-provider/index.js +1 -0
  226. package/src/token-provider/token-provider.a11y.test.js +44 -0
  227. package/src/token-provider/token-provider.js +85 -0
  228. package/src/token-provider/token-provider.stories.js +105 -0
  229. package/src/token-provider/token-provider.test.js +134 -0
  230. package/src/utils/number-input.utils.js +42 -0
@@ -0,0 +1,792 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import './ds-input.js';
3
+ import '../ds-icon/ds-icon.js';
4
+ import '../ds-icon-button/ds-icon-button.js';
5
+
6
+ describe('ds-input', () => {
7
+ let container;
8
+
9
+ beforeEach(() => {
10
+ container = document.createElement('div');
11
+ document.body.appendChild(container);
12
+ });
13
+
14
+ afterEach(() => {
15
+ container.remove();
16
+ });
17
+
18
+ it('should render with default props', async () => {
19
+ container.innerHTML = '<ds-input></ds-input>';
20
+ const element = container.querySelector('ds-input');
21
+ await new Promise(resolve => setTimeout(resolve, 100));
22
+
23
+ expect(element).toBeTruthy();
24
+ expect(element.tagName.toLowerCase()).toBe('ds-input');
25
+ });
26
+
27
+ it('should display label when provided', async () => {
28
+ container.innerHTML = '<ds-input label="Email"></ds-input>';
29
+ const element = container.querySelector('ds-input');
30
+ await new Promise(resolve => setTimeout(resolve, 100));
31
+
32
+ const label = element.shadowRoot.querySelector('label');
33
+ expect(label).toBeTruthy();
34
+ expect(label.textContent).toBe('Email');
35
+ });
36
+
37
+ it('should display info button when info prop is set', async () => {
38
+ container.innerHTML = '<ds-input label="Email" info="Info text"></ds-input>';
39
+ const element = container.querySelector('ds-input');
40
+ await new Promise(resolve => setTimeout(resolve, 100));
41
+
42
+ const infoButton = element.shadowRoot.querySelector('ds-icon-button');
43
+ expect(infoButton).toBeTruthy();
44
+ });
45
+
46
+ it('should not display info button when info prop is empty', async () => {
47
+ container.innerHTML = '<ds-input label="Email"></ds-input>';
48
+ const element = container.querySelector('ds-input');
49
+ await new Promise(resolve => setTimeout(resolve, 100));
50
+
51
+ const infoButton = element.shadowRoot.querySelector('ds-icon-button');
52
+ expect(infoButton).toBeFalsy();
53
+ });
54
+
55
+ it('should wrap info button with ds-tooltip when info prop is set', async () => {
56
+ container.innerHTML = '<ds-input label="Email" info="Info text"></ds-input>';
57
+ const element = container.querySelector('ds-input');
58
+ await new Promise(resolve => setTimeout(resolve, 100));
59
+
60
+ const tooltip = element.shadowRoot.querySelector('ds-tooltip');
61
+ expect(tooltip).toBeTruthy();
62
+
63
+ // Verify tooltip wraps the info button
64
+ const infoButton = tooltip.querySelector('ds-icon-button');
65
+ expect(infoButton).toBeTruthy();
66
+ });
67
+
68
+ it('should set tooltip content to info text', async () => {
69
+ const infoText = 'This is helpful information';
70
+ container.innerHTML = `<ds-input label="Email" info="${infoText}"></ds-input>`;
71
+ const element = container.querySelector('ds-input');
72
+ await new Promise(resolve => setTimeout(resolve, 100));
73
+
74
+ const tooltip = element.shadowRoot.querySelector('ds-tooltip');
75
+ expect(tooltip).toBeTruthy();
76
+ expect(tooltip.getAttribute('content')).toBe(infoText);
77
+ });
78
+
79
+ it('should set tooltip placement to top for info button', async () => {
80
+ container.innerHTML = '<ds-input label="Email" info="Info text"></ds-input>';
81
+ const element = container.querySelector('ds-input');
82
+ await new Promise(resolve => setTimeout(resolve, 100));
83
+
84
+ const tooltip = element.shadowRoot.querySelector('ds-tooltip');
85
+ expect(tooltip).toBeTruthy();
86
+ expect(tooltip.getAttribute('placement')).toBe('top');
87
+ });
88
+
89
+ it('should not render ds-tooltip when info prop is empty', async () => {
90
+ container.innerHTML = '<ds-input label="Email"></ds-input>';
91
+ const element = container.querySelector('ds-input');
92
+ await new Promise(resolve => setTimeout(resolve, 100));
93
+
94
+ const tooltip = element.shadowRoot.querySelector('ds-tooltip');
95
+ expect(tooltip).toBeFalsy();
96
+ });
97
+
98
+ it('should display placeholder', async () => {
99
+ container.innerHTML = '<ds-input placeholder="Enter email"></ds-input>';
100
+ const element = container.querySelector('ds-input');
101
+ await new Promise(resolve => setTimeout(resolve, 100));
102
+
103
+ const input = element.shadowRoot.querySelector('input');
104
+ expect(input.placeholder).toBe('Enter email');
105
+ });
106
+
107
+ it('should display value', async () => {
108
+ container.innerHTML = '<ds-input value="test@example.com"></ds-input>';
109
+ const element = container.querySelector('ds-input');
110
+ await new Promise(resolve => setTimeout(resolve, 100));
111
+
112
+ const input = element.shadowRoot.querySelector('input');
113
+ expect(input.value).toBe('test@example.com');
114
+ });
115
+
116
+ it('should update value on input', async () => {
117
+ container.innerHTML = '<ds-input></ds-input>';
118
+ const element = container.querySelector('ds-input');
119
+ await new Promise(resolve => setTimeout(resolve, 100));
120
+
121
+ const input = element.shadowRoot.querySelector('input');
122
+ input.value = 'new value';
123
+ input.dispatchEvent(new Event('input', { bubbles: true }));
124
+
125
+ expect(element.value).toBe('new value');
126
+ });
127
+
128
+ it('should display helper text', async () => {
129
+ container.innerHTML = '<ds-input helper="This is help text"></ds-input>';
130
+ const element = container.querySelector('ds-input');
131
+ await new Promise(resolve => setTimeout(resolve, 100));
132
+
133
+ const message = element.shadowRoot.querySelector('.field-message');
134
+ expect(message).toBeTruthy();
135
+ expect(message.textContent).toContain('This is help text');
136
+ });
137
+
138
+ it('should display error text with icon', async () => {
139
+ container.innerHTML = '<ds-input validation-status="error" validation-message="This is an error"></ds-input>';
140
+ const element = container.querySelector('ds-input');
141
+ await new Promise(resolve => setTimeout(resolve, 100));
142
+
143
+ const message = element.shadowRoot.querySelector('.field-message--error');
144
+ const icon = message?.querySelector('ds-icon');
145
+
146
+ expect(message).toBeTruthy();
147
+ expect(message.textContent).toContain('This is an error');
148
+ expect(icon).toBeTruthy();
149
+ expect(icon.getAttribute('name')).toBe('warning');
150
+ });
151
+
152
+ it('should prioritize error over helper text', async () => {
153
+ container.innerHTML = '<ds-input helper="Helper text" validation-status="error" validation-message="Error text"></ds-input>';
154
+ const element = container.querySelector('ds-input');
155
+ await new Promise(resolve => setTimeout(resolve, 100));
156
+
157
+ const message = element.shadowRoot.querySelector('.field-message');
158
+ expect(message.textContent).toContain('Error text');
159
+ expect(message.textContent).not.toContain('Helper text');
160
+ });
161
+
162
+ it('should respect disabled state', async () => {
163
+ container.innerHTML = '<ds-input disabled></ds-input>';
164
+ const element = container.querySelector('ds-input');
165
+ await new Promise(resolve => setTimeout(resolve, 100));
166
+
167
+ const input = element.shadowRoot.querySelector('input');
168
+ expect(input.disabled).toBe(true);
169
+ expect(element.hasAttribute('disabled')).toBe(true);
170
+ });
171
+
172
+ it('should respect readonly state', async () => {
173
+ container.innerHTML = '<ds-input readonly></ds-input>';
174
+ const element = container.querySelector('ds-input');
175
+ await new Promise(resolve => setTimeout(resolve, 100));
176
+
177
+ const input = element.shadowRoot.querySelector('input');
178
+ expect(input.readOnly).toBe(true);
179
+ expect(element.hasAttribute('readonly')).toBe(true);
180
+ });
181
+
182
+ it('should respect required state', async () => {
183
+ container.innerHTML = '<ds-input required></ds-input>';
184
+ const element = container.querySelector('ds-input');
185
+ await new Promise(resolve => setTimeout(resolve, 100));
186
+
187
+ const input = element.shadowRoot.querySelector('input');
188
+ expect(input.required).toBe(true);
189
+ expect(element.hasAttribute('required')).toBe(true);
190
+ });
191
+
192
+ it('should set input type', async () => {
193
+ container.innerHTML = '<ds-input type="email"></ds-input>';
194
+ const element = container.querySelector('ds-input');
195
+ await new Promise(resolve => setTimeout(resolve, 100));
196
+
197
+ const input = element.shadowRoot.querySelector('input');
198
+ expect(input.type).toBe('email');
199
+ });
200
+
201
+ it('should render prefix slot content', async () => {
202
+ container.innerHTML = `
203
+ <ds-input>
204
+ <span slot="prefix">$</span>
205
+ </ds-input>
206
+ `;
207
+ const element = container.querySelector('ds-input');
208
+ await new Promise(resolve => setTimeout(resolve, 100));
209
+
210
+ const slottedContent = element.querySelector('[slot="prefix"]');
211
+ expect(slottedContent).toBeTruthy();
212
+ expect(slottedContent.textContent).toBe('$');
213
+ });
214
+
215
+ it('should render suffix slot content', async () => {
216
+ container.innerHTML = `
217
+ <ds-input>
218
+ <span slot="suffix">USD</span>
219
+ </ds-input>
220
+ `;
221
+ const element = container.querySelector('ds-input');
222
+ await new Promise(resolve => setTimeout(resolve, 100));
223
+
224
+ const slottedContent = element.querySelector('[slot="suffix"]');
225
+ expect(slottedContent).toBeTruthy();
226
+ expect(slottedContent.textContent).toBe('USD');
227
+ });
228
+
229
+ it('should render action slot content', async () => {
230
+ container.innerHTML = `
231
+ <ds-input>
232
+ <ds-icon-button slot="action" icon="search"></ds-icon-button>
233
+ </ds-input>
234
+ `;
235
+ const element = container.querySelector('ds-input');
236
+ await new Promise(resolve => setTimeout(resolve, 100));
237
+
238
+ const slottedAction = element.querySelector('[slot="action"]');
239
+ expect(slottedAction).toBeTruthy();
240
+ });
241
+
242
+ it('should emit input event on value change', async () => {
243
+ container.innerHTML = '<ds-input></ds-input>';
244
+ const element = container.querySelector('ds-input');
245
+ await new Promise(resolve => setTimeout(resolve, 100));
246
+
247
+ let eventFired = false;
248
+ let eventDetail = null;
249
+
250
+ element.addEventListener('input', (e) => {
251
+ eventFired = true;
252
+ eventDetail = e.detail;
253
+ });
254
+
255
+ const input = element.shadowRoot.querySelector('input');
256
+ input.value = 'test';
257
+ input.dispatchEvent(new Event('input', { bubbles: true }));
258
+
259
+ expect(eventFired).toBe(true);
260
+ expect(eventDetail.value).toBe('test');
261
+ });
262
+
263
+ it('should emit change event on blur', async () => {
264
+ container.innerHTML = '<ds-input></ds-input>';
265
+ const element = container.querySelector('ds-input');
266
+ await new Promise(resolve => setTimeout(resolve, 100));
267
+
268
+ let eventFired = false;
269
+
270
+ element.addEventListener('change', () => {
271
+ eventFired = true;
272
+ });
273
+
274
+ const input = element.shadowRoot.querySelector('input');
275
+ input.dispatchEvent(new Event('change', { bubbles: true }));
276
+
277
+ expect(eventFired).toBe(true);
278
+ });
279
+
280
+ it('should set aria-invalid when error is present', async () => {
281
+ container.innerHTML = '<ds-input validation-status="error" validation-message="Error message"></ds-input>';
282
+ const element = container.querySelector('ds-input');
283
+ await new Promise(resolve => setTimeout(resolve, 100));
284
+
285
+ const input = element.shadowRoot.querySelector('input');
286
+ expect(input.getAttribute('aria-invalid')).toBe('true');
287
+ });
288
+
289
+ it('should set aria-required when required', async () => {
290
+ container.innerHTML = '<ds-input required></ds-input>';
291
+ const element = container.querySelector('ds-input');
292
+ await new Promise(resolve => setTimeout(resolve, 100));
293
+
294
+ const input = element.shadowRoot.querySelector('input');
295
+ expect(input.getAttribute('aria-required')).toBe('true');
296
+ });
297
+
298
+ it('should show password toggle button when showPasswordToggle is true', async () => {
299
+ container.innerHTML = '<ds-input type="password" show-password-toggle></ds-input>';
300
+ const element = container.querySelector('ds-input');
301
+ await new Promise(resolve => setTimeout(resolve, 100));
302
+
303
+ const toggleButton = element.shadowRoot.querySelectorAll('ds-icon-button');
304
+ // Filter to find the password toggle (not info button)
305
+ const passwordToggle = Array.from(toggleButton).find(btn =>
306
+ btn.getAttribute('icon') === 'visibility' || btn.getAttribute('icon') === 'visibility-off'
307
+ );
308
+ expect(passwordToggle).toBeTruthy();
309
+ });
310
+
311
+ it('should not show password toggle when showPasswordToggle is false', async () => {
312
+ container.innerHTML = '<ds-input type="password"></ds-input>';
313
+ const element = container.querySelector('ds-input');
314
+ await new Promise(resolve => setTimeout(resolve, 100));
315
+
316
+ const toggleButton = element.shadowRoot.querySelectorAll('ds-icon-button');
317
+ const passwordToggle = Array.from(toggleButton).find(btn =>
318
+ btn.getAttribute('icon') === 'visibility' || btn.getAttribute('icon') === 'visibility-off'
319
+ );
320
+ expect(passwordToggle).toBeFalsy();
321
+ });
322
+
323
+ it('should toggle password visibility when toggle button is clicked', async () => {
324
+ container.innerHTML = '<ds-input type="password" show-password-toggle></ds-input>';
325
+ const element = container.querySelector('ds-input');
326
+ await new Promise(resolve => setTimeout(resolve, 100));
327
+
328
+ const input = element.shadowRoot.querySelector('input');
329
+ expect(input.type).toBe('password');
330
+
331
+ const toggleButton = element.shadowRoot.querySelectorAll('ds-icon-button');
332
+ const passwordToggle = Array.from(toggleButton).find(btn =>
333
+ btn.getAttribute('icon') === 'visibility' || btn.getAttribute('icon') === 'visibility-off'
334
+ );
335
+
336
+ passwordToggle.click();
337
+ await new Promise(resolve => setTimeout(resolve, 100));
338
+
339
+ expect(input.type).toBe('text');
340
+ });
341
+
342
+ it('should apply validation-status attribute', async () => {
343
+ container.innerHTML = '<ds-input validation-status="error"></ds-input>';
344
+ const element = container.querySelector('ds-input');
345
+ await new Promise(resolve => setTimeout(resolve, 100));
346
+
347
+ expect(element.getAttribute('validation-status')).toBe('error');
348
+ });
349
+
350
+ it('should show number steppers for number type', async () => {
351
+ container.innerHTML = '<ds-input type="number"></ds-input>';
352
+ const element = container.querySelector('ds-input');
353
+ await new Promise(resolve => setTimeout(resolve, 100));
354
+
355
+ const buttons = element.shadowRoot.querySelectorAll('ds-icon-button');
356
+ const decrementBtn = Array.from(buttons).find(btn => btn.getAttribute('icon') === 'remove');
357
+ const incrementBtn = Array.from(buttons).find(btn => btn.getAttribute('icon') === 'add');
358
+
359
+ expect(decrementBtn).toBeTruthy();
360
+ expect(incrementBtn).toBeTruthy();
361
+ });
362
+
363
+ it('should increment number value when + button is clicked', async () => {
364
+ container.innerHTML = '<ds-input type="number" value="5" step="1"></ds-input>';
365
+ const element = container.querySelector('ds-input');
366
+ await new Promise(resolve => setTimeout(resolve, 100));
367
+
368
+ const buttons = element.shadowRoot.querySelectorAll('ds-icon-button');
369
+ const incrementBtn = Array.from(buttons).find(btn => btn.getAttribute('icon') === 'add');
370
+
371
+ incrementBtn.click();
372
+ await new Promise(resolve => setTimeout(resolve, 100));
373
+
374
+ expect(element.value).toBe('6');
375
+ });
376
+
377
+ it('should decrement number value when - button is clicked', async () => {
378
+ container.innerHTML = '<ds-input type="number" value="5" step="1"></ds-input>';
379
+ const element = container.querySelector('ds-input');
380
+ await new Promise(resolve => setTimeout(resolve, 100));
381
+
382
+ const buttons = element.shadowRoot.querySelectorAll('ds-icon-button');
383
+ const decrementBtn = Array.from(buttons).find(btn => btn.getAttribute('icon') === 'remove');
384
+
385
+ decrementBtn.click();
386
+ await new Promise(resolve => setTimeout(resolve, 100));
387
+
388
+ expect(element.value).toBe('4');
389
+ });
390
+
391
+ it('should respect custom step value', async () => {
392
+ container.innerHTML = '<ds-input type="number" value="10" step="0.5"></ds-input>';
393
+ const element = container.querySelector('ds-input');
394
+ await new Promise(resolve => setTimeout(resolve, 100));
395
+
396
+ const buttons = element.shadowRoot.querySelectorAll('ds-icon-button');
397
+ const incrementBtn = Array.from(buttons).find(btn => btn.getAttribute('icon') === 'add');
398
+
399
+ incrementBtn.click();
400
+ await new Promise(resolve => setTimeout(resolve, 100));
401
+
402
+ expect(element.value).toBe('10.5');
403
+ });
404
+
405
+ it('should not show password toggle for non-password types even if prop is true', async () => {
406
+ container.innerHTML = '<ds-input type="text" show-password-toggle></ds-input>';
407
+ const element = container.querySelector('ds-input');
408
+ await new Promise(resolve => setTimeout(resolve, 100));
409
+
410
+ const toggleButton = element.shadowRoot.querySelectorAll('ds-icon-button');
411
+ const passwordToggle = Array.from(toggleButton).find(btn =>
412
+ btn.getAttribute('icon') === 'visibility' || btn.getAttribute('icon') === 'visibility-off'
413
+ );
414
+ expect(passwordToggle).toBeFalsy();
415
+ });
416
+
417
+ // Clearable tests
418
+ it('should not show clear button when clearable is false', async () => {
419
+ container.innerHTML = '<ds-input value="test" clearable="false"></ds-input>';
420
+ const element = container.querySelector('ds-input');
421
+ await new Promise(resolve => setTimeout(resolve, 100));
422
+
423
+ const buttons = element.shadowRoot.querySelectorAll('ds-icon-button');
424
+ const clearBtn = Array.from(buttons).find(btn => btn.getAttribute('icon') === 'close');
425
+ expect(clearBtn).toBeFalsy();
426
+ });
427
+
428
+ it('should not show clear button when value is empty', async () => {
429
+ container.innerHTML = '<ds-input clearable></ds-input>';
430
+ const element = container.querySelector('ds-input');
431
+ await new Promise(resolve => setTimeout(resolve, 100));
432
+
433
+ const buttons = element.shadowRoot.querySelectorAll('ds-icon-button');
434
+ const clearBtn = Array.from(buttons).find(btn => btn.getAttribute('icon') === 'close');
435
+ expect(clearBtn).toBeFalsy();
436
+ });
437
+
438
+ it('should not show clear button when not focused', async () => {
439
+ container.innerHTML = '<ds-input value="test" clearable></ds-input>';
440
+ const element = container.querySelector('ds-input');
441
+ await new Promise(resolve => setTimeout(resolve, 100));
442
+
443
+ const buttons = element.shadowRoot.querySelectorAll('ds-icon-button');
444
+ const clearBtn = Array.from(buttons).find(btn => btn.getAttribute('icon') === 'close');
445
+ expect(clearBtn).toBeFalsy();
446
+ });
447
+
448
+ it('should show clear button when clearable, has value, and is focused', async () => {
449
+ container.innerHTML = '<ds-input value="test" clearable></ds-input>';
450
+ const element = container.querySelector('ds-input');
451
+ await new Promise(resolve => setTimeout(resolve, 100));
452
+
453
+ const input = element.shadowRoot.querySelector('input');
454
+ input.focus();
455
+ await new Promise(resolve => setTimeout(resolve, 100));
456
+
457
+ const buttons = element.shadowRoot.querySelectorAll('ds-icon-button');
458
+ const clearBtn = Array.from(buttons).find(btn => btn.getAttribute('icon') === 'close');
459
+ expect(clearBtn).toBeTruthy();
460
+ });
461
+
462
+ it('should hide clear button when losing focus', async () => {
463
+ container.innerHTML = '<ds-input value="test" clearable></ds-input>';
464
+ const element = container.querySelector('ds-input');
465
+ await new Promise(resolve => setTimeout(resolve, 100));
466
+
467
+ const input = element.shadowRoot.querySelector('input');
468
+ input.focus();
469
+ await new Promise(resolve => setTimeout(resolve, 100));
470
+
471
+ let buttons = element.shadowRoot.querySelectorAll('ds-icon-button');
472
+ let clearBtn = Array.from(buttons).find(btn => btn.getAttribute('icon') === 'close');
473
+ expect(clearBtn).toBeTruthy();
474
+
475
+ input.blur();
476
+ await new Promise(resolve => setTimeout(resolve, 100));
477
+
478
+ buttons = element.shadowRoot.querySelectorAll('ds-icon-button');
479
+ clearBtn = Array.from(buttons).find(btn => btn.getAttribute('icon') === 'close');
480
+ expect(clearBtn).toBeFalsy();
481
+ });
482
+
483
+ it('should clear value when clear button is clicked', async () => {
484
+ container.innerHTML = '<ds-input value="test value" clearable></ds-input>';
485
+ const element = container.querySelector('ds-input');
486
+ await new Promise(resolve => setTimeout(resolve, 100));
487
+
488
+ const input = element.shadowRoot.querySelector('input');
489
+ input.focus();
490
+ await new Promise(resolve => setTimeout(resolve, 100));
491
+
492
+ const buttons = element.shadowRoot.querySelectorAll('ds-icon-button');
493
+ const clearBtn = Array.from(buttons).find(btn => btn.getAttribute('icon') === 'close');
494
+
495
+ clearBtn.click();
496
+ await new Promise(resolve => setTimeout(resolve, 100));
497
+
498
+ expect(element.value).toBe('');
499
+ expect(input.value).toBe('');
500
+ });
501
+
502
+ it('should refocus input after clearing', async () => {
503
+ container.innerHTML = '<ds-input value="test" clearable></ds-input>';
504
+ const element = container.querySelector('ds-input');
505
+ await new Promise(resolve => setTimeout(resolve, 100));
506
+
507
+ const input = element.shadowRoot.querySelector('input');
508
+ input.focus();
509
+ await new Promise(resolve => setTimeout(resolve, 100));
510
+
511
+ const buttons = element.shadowRoot.querySelectorAll('ds-icon-button');
512
+ const clearBtn = Array.from(buttons).find(btn => btn.getAttribute('icon') === 'close');
513
+
514
+ clearBtn.click();
515
+ await new Promise(resolve => setTimeout(resolve, 100));
516
+
517
+ expect(element.shadowRoot.activeElement).toBe(input);
518
+ });
519
+
520
+ it('should clear value when ESC key is pressed', async () => {
521
+ container.innerHTML = '<ds-input value="test value" clearable></ds-input>';
522
+ const element = container.querySelector('ds-input');
523
+ await new Promise(resolve => setTimeout(resolve, 100));
524
+
525
+ const input = element.shadowRoot.querySelector('input');
526
+ input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
527
+ await new Promise(resolve => setTimeout(resolve, 100));
528
+
529
+ expect(element.value).toBe('');
530
+ });
531
+
532
+ it('should not clear value on ESC if clearable is false', async () => {
533
+ container.innerHTML = '<ds-input value="test value"></ds-input>';
534
+ const element = container.querySelector('ds-input');
535
+ await new Promise(resolve => setTimeout(resolve, 100));
536
+
537
+ const input = element.shadowRoot.querySelector('input');
538
+ input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
539
+ await new Promise(resolve => setTimeout(resolve, 100));
540
+
541
+ expect(element.value).toBe('test value');
542
+ });
543
+
544
+ it('should select all text on focus when input has value', async () => {
545
+ container.innerHTML = '<ds-input value="test value"></ds-input>';
546
+ const element = container.querySelector('ds-input');
547
+ await new Promise(resolve => setTimeout(resolve, 100));
548
+
549
+ const input = element.shadowRoot.querySelector('input');
550
+ let selectionCalled = false;
551
+ input.select = () => { selectionCalled = true; };
552
+
553
+ input.dispatchEvent(new Event('focus', { bubbles: true }));
554
+ await new Promise(resolve => setTimeout(resolve, 100));
555
+
556
+ expect(selectionCalled).toBe(true);
557
+ });
558
+
559
+ it('should position clear button before other action buttons', async () => {
560
+ container.innerHTML = '<ds-input type="password" show-password-toggle value="test" clearable></ds-input>';
561
+ const element = container.querySelector('ds-input');
562
+ await new Promise(resolve => setTimeout(resolve, 100));
563
+
564
+ const input = element.shadowRoot.querySelector('input');
565
+ input.focus();
566
+ await new Promise(resolve => setTimeout(resolve, 100));
567
+
568
+ const buttons = element.shadowRoot.querySelectorAll('ds-icon-button');
569
+ const buttonIcons = Array.from(buttons).map(btn => btn.getAttribute('icon'));
570
+
571
+ const clearIndex = buttonIcons.indexOf('close');
572
+ const visibilityIndex = Math.max(
573
+ buttonIcons.indexOf('visibility'),
574
+ buttonIcons.indexOf('visibility-off')
575
+ );
576
+
577
+ expect(clearIndex).toBeLessThan(visibilityIndex);
578
+ });
579
+
580
+ // New API tests: selectAll, forceValue, autocomplete, width, textAlign, min, max, clamp
581
+
582
+ it('should expose selectAll() method', async () => {
583
+ container.innerHTML = '<ds-input value="test value"></ds-input>';
584
+ const element = container.querySelector('ds-input');
585
+ await new Promise(resolve => setTimeout(resolve, 100));
586
+
587
+ const input = element.shadowRoot.querySelector('input');
588
+ let selectCalled = false;
589
+ input.select = () => { selectCalled = true; };
590
+
591
+ element.selectAll();
592
+
593
+ expect(selectCalled).toBe(true);
594
+ });
595
+
596
+ it('should expose forceValue() method', async () => {
597
+ container.innerHTML = '<ds-input value="original"></ds-input>';
598
+ const element = container.querySelector('ds-input');
599
+ await new Promise(resolve => setTimeout(resolve, 100));
600
+
601
+ const input = element.shadowRoot.querySelector('input');
602
+
603
+ element.forceValue('forced');
604
+ await new Promise(resolve => setTimeout(resolve, 0));
605
+
606
+ expect(element.value).toBe('forced');
607
+ expect(input.value).toBe('forced');
608
+ });
609
+
610
+ it('should pass autocomplete attribute to native input', async () => {
611
+ container.innerHTML = '<ds-input autocomplete="new-password"></ds-input>';
612
+ const element = container.querySelector('ds-input');
613
+ await new Promise(resolve => setTimeout(resolve, 100));
614
+
615
+ const input = element.shadowRoot.querySelector('input');
616
+ expect(input.getAttribute('autocomplete')).toBe('new-password');
617
+ });
618
+
619
+ it('should default autocomplete to off', async () => {
620
+ container.innerHTML = '<ds-input></ds-input>';
621
+ const element = container.querySelector('ds-input');
622
+ await new Promise(resolve => setTimeout(resolve, 100));
623
+
624
+ const input = element.shadowRoot.querySelector('input');
625
+ expect(input.getAttribute('autocomplete')).toBe('off');
626
+ });
627
+
628
+ it('should apply width prop as CSS variable', async () => {
629
+ container.innerHTML = '<ds-input width="200px"></ds-input>';
630
+ const element = container.querySelector('ds-input');
631
+ await new Promise(resolve => setTimeout(resolve, 100));
632
+
633
+ const computedWidth = element.style.getPropertyValue('--ds-input-computed-width');
634
+ expect(computedWidth).toBe('200px');
635
+ });
636
+
637
+ it('should apply textAlign prop to native input', async () => {
638
+ container.innerHTML = '<ds-input text-align="center"></ds-input>';
639
+ const element = container.querySelector('ds-input');
640
+ await new Promise(resolve => setTimeout(resolve, 100));
641
+
642
+ const input = element.shadowRoot.querySelector('input');
643
+ expect(input.style.textAlign).toBe('center');
644
+ });
645
+
646
+ it('should clamp value on blur when clamp prop is set', async () => {
647
+ container.innerHTML = '<ds-input type="number" min="1" max="10" clamp value="15"></ds-input>';
648
+ const element = container.querySelector('ds-input');
649
+ await new Promise(resolve => setTimeout(resolve, 100));
650
+
651
+ const input = element.shadowRoot.querySelector('input');
652
+ input.dispatchEvent(new Event('blur'));
653
+ await new Promise(resolve => setTimeout(resolve, 0));
654
+
655
+ expect(element.value).toBe('10');
656
+ });
657
+
658
+ it('should clamp to min when value is below minimum', async () => {
659
+ container.innerHTML = '<ds-input type="number" min="5" max="10" clamp value="-3"></ds-input>';
660
+ const element = container.querySelector('ds-input');
661
+ await new Promise(resolve => setTimeout(resolve, 100));
662
+
663
+ const input = element.shadowRoot.querySelector('input');
664
+ input.dispatchEvent(new Event('blur'));
665
+ await new Promise(resolve => setTimeout(resolve, 0));
666
+
667
+ expect(element.value).toBe('5');
668
+ });
669
+
670
+ it('should clamp on Enter key for number inputs', async () => {
671
+ container.innerHTML = '<ds-input type="number" min="1" max="10" clamp value="50"></ds-input>';
672
+ const element = container.querySelector('ds-input');
673
+ await new Promise(resolve => setTimeout(resolve, 100));
674
+
675
+ const input = element.shadowRoot.querySelector('input');
676
+ input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
677
+ await new Promise(resolve => setTimeout(resolve, 0));
678
+
679
+ expect(element.value).toBe('10');
680
+ });
681
+
682
+ it('should not clamp when clamp prop is false', async () => {
683
+ container.innerHTML = '<ds-input type="number" min="1" max="10" value="15"></ds-input>';
684
+ const element = container.querySelector('ds-input');
685
+ await new Promise(resolve => setTimeout(resolve, 100));
686
+
687
+ const input = element.shadowRoot.querySelector('input');
688
+ input.dispatchEvent(new Event('blur'));
689
+ await new Promise(resolve => setTimeout(resolve, 0));
690
+
691
+ expect(element.value).toBe('15');
692
+ });
693
+
694
+ it('should reset to min on NaN input when clamp is enabled', async () => {
695
+ container.innerHTML = '<ds-input type="number" min="1" max="10" clamp value="abc"></ds-input>';
696
+ const element = container.querySelector('ds-input');
697
+ await new Promise(resolve => setTimeout(resolve, 100));
698
+
699
+ const input = element.shadowRoot.querySelector('input');
700
+ input.dispatchEvent(new Event('blur'));
701
+ await new Promise(resolve => setTimeout(resolve, 0));
702
+
703
+ expect(element.value).toBe('1');
704
+ });
705
+
706
+ // Stepper and Arrow Key min/max constraint tests
707
+
708
+ it('should not increment beyond max with stepper button', async () => {
709
+ container.innerHTML = '<ds-input type="number" min="1" max="5" value="5"></ds-input>';
710
+ const element = container.querySelector('ds-input');
711
+ await new Promise(resolve => setTimeout(resolve, 100));
712
+
713
+ const buttons = element.shadowRoot.querySelectorAll('ds-icon-button');
714
+ const incrementBtn = Array.from(buttons).find(btn => btn.getAttribute('icon') === 'add');
715
+
716
+ // Button should be disabled at max
717
+ expect(incrementBtn.hasAttribute('disabled')).toBe(true);
718
+
719
+ // Try to increment anyway
720
+ incrementBtn.click();
721
+ await new Promise(resolve => setTimeout(resolve, 0));
722
+
723
+ expect(element.value).toBe('5');
724
+ });
725
+
726
+ it('should not decrement below min with stepper button', async () => {
727
+ container.innerHTML = '<ds-input type="number" min="1" max="10" value="1"></ds-input>';
728
+ const element = container.querySelector('ds-input');
729
+ await new Promise(resolve => setTimeout(resolve, 100));
730
+
731
+ const buttons = element.shadowRoot.querySelectorAll('ds-icon-button');
732
+ const decrementBtn = Array.from(buttons).find(btn => btn.getAttribute('icon') === 'remove');
733
+
734
+ // Button should be disabled at min
735
+ expect(decrementBtn.hasAttribute('disabled')).toBe(true);
736
+
737
+ // Try to decrement anyway
738
+ decrementBtn.click();
739
+ await new Promise(resolve => setTimeout(resolve, 0));
740
+
741
+ expect(element.value).toBe('1');
742
+ });
743
+
744
+ it('should not increment beyond max with Arrow Up', async () => {
745
+ container.innerHTML = '<ds-input type="number" min="1" max="5" value="5"></ds-input>';
746
+ const element = container.querySelector('ds-input');
747
+ await new Promise(resolve => setTimeout(resolve, 100));
748
+
749
+ const input = element.shadowRoot.querySelector('input');
750
+ input.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true }));
751
+ await new Promise(resolve => setTimeout(resolve, 0));
752
+
753
+ expect(element.value).toBe('5');
754
+ });
755
+
756
+ it('should not decrement below min with Arrow Down', async () => {
757
+ container.innerHTML = '<ds-input type="number" min="1" max="10" value="1"></ds-input>';
758
+ const element = container.querySelector('ds-input');
759
+ await new Promise(resolve => setTimeout(resolve, 100));
760
+
761
+ const input = element.shadowRoot.querySelector('input');
762
+ input.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
763
+ await new Promise(resolve => setTimeout(resolve, 0));
764
+
765
+ expect(element.value).toBe('1');
766
+ });
767
+
768
+ it('should increment with Arrow Up within bounds', async () => {
769
+ container.innerHTML = '<ds-input type="number" min="1" max="10" value="5"></ds-input>';
770
+ const element = container.querySelector('ds-input');
771
+ await new Promise(resolve => setTimeout(resolve, 100));
772
+
773
+ const input = element.shadowRoot.querySelector('input');
774
+ input.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true }));
775
+ await new Promise(resolve => setTimeout(resolve, 0));
776
+
777
+ expect(element.value).toBe('6');
778
+ });
779
+
780
+ it('should decrement with Arrow Down within bounds', async () => {
781
+ container.innerHTML = '<ds-input type="number" min="1" max="10" value="5"></ds-input>';
782
+ const element = container.querySelector('ds-input');
783
+ await new Promise(resolve => setTimeout(resolve, 100));
784
+
785
+ const input = element.shadowRoot.querySelector('input');
786
+ input.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
787
+ await new Promise(resolve => setTimeout(resolve, 0));
788
+
789
+ expect(element.value).toBe('4');
790
+ });
791
+ });
792
+