@furystack/shades-common-components 10.0.35 → 11.0.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 (295) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/esm/components/animations.spec.d.ts +2 -0
  3. package/esm/components/animations.spec.d.ts.map +1 -0
  4. package/esm/components/animations.spec.js +201 -0
  5. package/esm/components/animations.spec.js.map +1 -0
  6. package/esm/components/app-bar-link.js +21 -20
  7. package/esm/components/app-bar-link.js.map +1 -1
  8. package/esm/components/app-bar-link.spec.d.ts +2 -0
  9. package/esm/components/app-bar-link.spec.d.ts.map +1 -0
  10. package/esm/components/app-bar-link.spec.js +252 -0
  11. package/esm/components/app-bar-link.spec.js.map +1 -0
  12. package/esm/components/app-bar.js +21 -21
  13. package/esm/components/app-bar.js.map +1 -1
  14. package/esm/components/app-bar.spec.d.ts +2 -0
  15. package/esm/components/app-bar.spec.d.ts.map +1 -0
  16. package/esm/components/app-bar.spec.js +117 -0
  17. package/esm/components/app-bar.spec.js.map +1 -0
  18. package/esm/components/avatar.d.ts.map +1 -1
  19. package/esm/components/avatar.js +15 -19
  20. package/esm/components/avatar.js.map +1 -1
  21. package/esm/components/avatar.spec.d.ts +2 -0
  22. package/esm/components/avatar.spec.d.ts.map +1 -0
  23. package/esm/components/avatar.spec.js +114 -0
  24. package/esm/components/avatar.spec.js.map +1 -0
  25. package/esm/components/button.d.ts.map +1 -1
  26. package/esm/components/button.js +145 -156
  27. package/esm/components/button.js.map +1 -1
  28. package/esm/components/button.spec.d.ts +2 -0
  29. package/esm/components/button.spec.d.ts.map +1 -0
  30. package/esm/components/button.spec.js +155 -0
  31. package/esm/components/button.spec.js.map +1 -0
  32. package/esm/components/command-palette/command-palette-input.d.ts.map +1 -1
  33. package/esm/components/command-palette/command-palette-input.js +18 -16
  34. package/esm/components/command-palette/command-palette-input.js.map +1 -1
  35. package/esm/components/command-palette/command-palette-input.spec.d.ts +2 -0
  36. package/esm/components/command-palette/command-palette-input.spec.d.ts.map +1 -0
  37. package/esm/components/command-palette/command-palette-input.spec.js +233 -0
  38. package/esm/components/command-palette/command-palette-input.spec.js.map +1 -0
  39. package/esm/components/command-palette/command-palette-manager.spec.d.ts +2 -0
  40. package/esm/components/command-palette/command-palette-manager.spec.d.ts.map +1 -0
  41. package/esm/components/command-palette/command-palette-manager.spec.js +362 -0
  42. package/esm/components/command-palette/command-palette-manager.spec.js.map +1 -0
  43. package/esm/components/command-palette/command-palette-suggestion-list.d.ts.map +1 -1
  44. package/esm/components/command-palette/command-palette-suggestion-list.js +42 -46
  45. package/esm/components/command-palette/command-palette-suggestion-list.js.map +1 -1
  46. package/esm/components/command-palette/command-palette-suggestion-list.spec.d.ts +2 -0
  47. package/esm/components/command-palette/command-palette-suggestion-list.spec.d.ts.map +1 -0
  48. package/esm/components/command-palette/command-palette-suggestion-list.spec.js +376 -0
  49. package/esm/components/command-palette/command-palette-suggestion-list.spec.js.map +1 -0
  50. package/esm/components/command-palette/index.d.ts.map +1 -1
  51. package/esm/components/command-palette/index.js +100 -110
  52. package/esm/components/command-palette/index.js.map +1 -1
  53. package/esm/components/command-palette/index.spec.d.ts +2 -0
  54. package/esm/components/command-palette/index.spec.d.ts.map +1 -0
  55. package/esm/components/command-palette/index.spec.js +509 -0
  56. package/esm/components/command-palette/index.spec.js.map +1 -0
  57. package/esm/components/data-grid/body.js +1 -1
  58. package/esm/components/data-grid/body.js.map +1 -1
  59. package/esm/components/data-grid/body.spec.d.ts +2 -0
  60. package/esm/components/data-grid/body.spec.d.ts.map +1 -0
  61. package/esm/components/data-grid/body.spec.js +228 -0
  62. package/esm/components/data-grid/body.spec.js.map +1 -0
  63. package/esm/components/data-grid/data-grid-row.d.ts.map +1 -1
  64. package/esm/components/data-grid/data-grid-row.js +49 -73
  65. package/esm/components/data-grid/data-grid-row.js.map +1 -1
  66. package/esm/components/data-grid/data-grid-row.spec.d.ts +2 -0
  67. package/esm/components/data-grid/data-grid-row.spec.d.ts.map +1 -0
  68. package/esm/components/data-grid/data-grid-row.spec.js +296 -0
  69. package/esm/components/data-grid/data-grid-row.spec.js.map +1 -0
  70. package/esm/components/data-grid/data-grid.d.ts.map +1 -1
  71. package/esm/components/data-grid/data-grid.js +35 -28
  72. package/esm/components/data-grid/data-grid.js.map +1 -1
  73. package/esm/components/data-grid/data-grid.spec.d.ts +2 -0
  74. package/esm/components/data-grid/data-grid.spec.d.ts.map +1 -0
  75. package/esm/components/data-grid/data-grid.spec.js +544 -0
  76. package/esm/components/data-grid/data-grid.spec.js.map +1 -0
  77. package/esm/components/data-grid/footer.js +21 -15
  78. package/esm/components/data-grid/footer.js.map +1 -1
  79. package/esm/components/data-grid/footer.spec.d.ts +2 -0
  80. package/esm/components/data-grid/footer.spec.d.ts.map +1 -0
  81. package/esm/components/data-grid/footer.spec.js +264 -0
  82. package/esm/components/data-grid/footer.spec.js.map +1 -0
  83. package/esm/components/data-grid/header.d.ts.map +1 -1
  84. package/esm/components/data-grid/header.js +55 -33
  85. package/esm/components/data-grid/header.js.map +1 -1
  86. package/esm/components/data-grid/header.spec.d.ts +2 -0
  87. package/esm/components/data-grid/header.spec.d.ts.map +1 -0
  88. package/esm/components/data-grid/header.spec.js +421 -0
  89. package/esm/components/data-grid/header.spec.js.map +1 -0
  90. package/esm/components/data-grid/selection-cell.d.ts.map +1 -1
  91. package/esm/components/data-grid/selection-cell.js +13 -6
  92. package/esm/components/data-grid/selection-cell.js.map +1 -1
  93. package/esm/components/data-grid/selection-cell.spec.d.ts +2 -0
  94. package/esm/components/data-grid/selection-cell.spec.d.ts.map +1 -0
  95. package/esm/components/data-grid/selection-cell.spec.js +118 -0
  96. package/esm/components/data-grid/selection-cell.spec.js.map +1 -0
  97. package/esm/components/fab.d.ts.map +1 -1
  98. package/esm/components/fab.js +10 -1
  99. package/esm/components/fab.js.map +1 -1
  100. package/esm/components/fab.spec.d.ts +2 -0
  101. package/esm/components/fab.spec.d.ts.map +1 -0
  102. package/esm/components/fab.spec.js +95 -0
  103. package/esm/components/fab.spec.js.map +1 -0
  104. package/esm/components/form.spec.d.ts +2 -0
  105. package/esm/components/form.spec.d.ts.map +1 -0
  106. package/esm/components/form.spec.js +314 -0
  107. package/esm/components/form.spec.js.map +1 -0
  108. package/esm/components/grid.d.ts.map +1 -1
  109. package/esm/components/grid.js +40 -37
  110. package/esm/components/grid.js.map +1 -1
  111. package/esm/components/grid.spec.d.ts +2 -0
  112. package/esm/components/grid.spec.d.ts.map +1 -0
  113. package/esm/components/grid.spec.js +316 -0
  114. package/esm/components/grid.spec.js.map +1 -0
  115. package/esm/components/inputs/autocomplete.spec.d.ts +2 -0
  116. package/esm/components/inputs/autocomplete.spec.d.ts.map +1 -0
  117. package/esm/components/inputs/autocomplete.spec.js +194 -0
  118. package/esm/components/inputs/autocomplete.spec.js.map +1 -0
  119. package/esm/components/inputs/input.d.ts.map +1 -1
  120. package/esm/components/inputs/input.js +141 -109
  121. package/esm/components/inputs/input.js.map +1 -1
  122. package/esm/components/inputs/input.spec.d.ts +2 -0
  123. package/esm/components/inputs/input.spec.d.ts.map +1 -0
  124. package/esm/components/inputs/input.spec.js +577 -0
  125. package/esm/components/inputs/input.spec.js.map +1 -0
  126. package/esm/components/inputs/text-area.d.ts.map +1 -1
  127. package/esm/components/inputs/text-area.js +54 -58
  128. package/esm/components/inputs/text-area.js.map +1 -1
  129. package/esm/components/inputs/text-area.spec.d.ts +2 -0
  130. package/esm/components/inputs/text-area.spec.d.ts.map +1 -0
  131. package/esm/components/inputs/text-area.spec.js +214 -0
  132. package/esm/components/inputs/text-area.spec.js.map +1 -0
  133. package/esm/components/loader.js +1 -1
  134. package/esm/components/loader.js.map +1 -1
  135. package/esm/components/loader.spec.d.ts +2 -0
  136. package/esm/components/loader.spec.d.ts.map +1 -0
  137. package/esm/components/loader.spec.js +251 -0
  138. package/esm/components/loader.spec.js.map +1 -0
  139. package/esm/components/modal.d.ts.map +1 -1
  140. package/esm/components/modal.js +11 -9
  141. package/esm/components/modal.js.map +1 -1
  142. package/esm/components/modal.spec.d.ts +2 -0
  143. package/esm/components/modal.spec.d.ts.map +1 -0
  144. package/esm/components/modal.spec.js +227 -0
  145. package/esm/components/modal.spec.js.map +1 -0
  146. package/esm/components/noty-list.d.ts.map +1 -1
  147. package/esm/components/noty-list.js +39 -40
  148. package/esm/components/noty-list.js.map +1 -1
  149. package/esm/components/noty-list.spec.d.ts +2 -0
  150. package/esm/components/noty-list.spec.d.ts.map +1 -0
  151. package/esm/components/noty-list.spec.js +486 -0
  152. package/esm/components/noty-list.spec.js.map +1 -0
  153. package/esm/components/paper.d.ts.map +1 -1
  154. package/esm/components/paper.js +15 -12
  155. package/esm/components/paper.js.map +1 -1
  156. package/esm/components/paper.spec.d.ts +2 -0
  157. package/esm/components/paper.spec.d.ts.map +1 -0
  158. package/esm/components/paper.spec.js +63 -0
  159. package/esm/components/paper.spec.js.map +1 -0
  160. package/esm/components/skeleton.js +1 -1
  161. package/esm/components/skeleton.js.map +1 -1
  162. package/esm/components/skeleton.spec.d.ts +2 -0
  163. package/esm/components/skeleton.spec.d.ts.map +1 -0
  164. package/esm/components/skeleton.spec.js +159 -0
  165. package/esm/components/skeleton.spec.js.map +1 -0
  166. package/esm/components/styles.spec.d.ts +2 -0
  167. package/esm/components/styles.spec.d.ts.map +1 -0
  168. package/esm/components/styles.spec.js +56 -0
  169. package/esm/components/styles.spec.js.map +1 -0
  170. package/esm/components/suggest/index.d.ts.map +1 -1
  171. package/esm/components/suggest/index.js +74 -83
  172. package/esm/components/suggest/index.js.map +1 -1
  173. package/esm/components/suggest/index.spec.d.ts +2 -0
  174. package/esm/components/suggest/index.spec.d.ts.map +1 -0
  175. package/esm/components/suggest/index.spec.js +515 -0
  176. package/esm/components/suggest/index.spec.js.map +1 -0
  177. package/esm/components/suggest/suggest-input.d.ts.map +1 -1
  178. package/esm/components/suggest/suggest-input.js +16 -17
  179. package/esm/components/suggest/suggest-input.js.map +1 -1
  180. package/esm/components/suggest/suggest-input.spec.d.ts +2 -0
  181. package/esm/components/suggest/suggest-input.spec.d.ts.map +1 -0
  182. package/esm/components/suggest/suggest-input.spec.js +138 -0
  183. package/esm/components/suggest/suggest-input.spec.js.map +1 -0
  184. package/esm/components/suggest/suggest-manager.spec.d.ts +2 -0
  185. package/esm/components/suggest/suggest-manager.spec.d.ts.map +1 -0
  186. package/esm/components/suggest/suggest-manager.spec.js +308 -0
  187. package/esm/components/suggest/suggest-manager.spec.js.map +1 -0
  188. package/esm/components/suggest/suggestion-list.d.ts.map +1 -1
  189. package/esm/components/suggest/suggestion-list.js +43 -48
  190. package/esm/components/suggest/suggestion-list.js.map +1 -1
  191. package/esm/components/suggest/suggestion-list.spec.d.ts +2 -0
  192. package/esm/components/suggest/suggestion-list.spec.d.ts.map +1 -0
  193. package/esm/components/suggest/suggestion-list.spec.js +252 -0
  194. package/esm/components/suggest/suggestion-list.spec.js.map +1 -0
  195. package/esm/components/tabs.d.ts.map +1 -1
  196. package/esm/components/tabs.js +32 -18
  197. package/esm/components/tabs.js.map +1 -1
  198. package/esm/components/tabs.spec.d.ts +2 -0
  199. package/esm/components/tabs.spec.d.ts.map +1 -0
  200. package/esm/components/tabs.spec.js +187 -0
  201. package/esm/components/tabs.spec.js.map +1 -0
  202. package/esm/components/wizard/index.d.ts.map +1 -1
  203. package/esm/components/wizard/index.js +10 -7
  204. package/esm/components/wizard/index.js.map +1 -1
  205. package/esm/components/wizard/index.spec.d.ts +2 -0
  206. package/esm/components/wizard/index.spec.d.ts.map +1 -0
  207. package/esm/components/wizard/index.spec.js +171 -0
  208. package/esm/components/wizard/index.spec.js.map +1 -0
  209. package/esm/services/collection-service.spec.js +391 -2
  210. package/esm/services/collection-service.spec.js.map +1 -1
  211. package/esm/services/css-variable-theme.d.ts.map +1 -1
  212. package/esm/services/css-variable-theme.js +21 -1
  213. package/esm/services/css-variable-theme.js.map +1 -1
  214. package/esm/services/css-variable-theme.spec.d.ts +2 -0
  215. package/esm/services/css-variable-theme.spec.d.ts.map +1 -0
  216. package/esm/services/css-variable-theme.spec.js +169 -0
  217. package/esm/services/css-variable-theme.spec.js.map +1 -0
  218. package/esm/services/default-palette.d.ts +4 -0
  219. package/esm/services/default-palette.d.ts.map +1 -1
  220. package/esm/services/default-palette.js +22 -0
  221. package/esm/services/default-palette.js.map +1 -1
  222. package/esm/services/theme-provider-service.d.ts +59 -1
  223. package/esm/services/theme-provider-service.d.ts.map +1 -1
  224. package/esm/services/theme-provider-service.js.map +1 -1
  225. package/esm/services/theme-provider-service.spec.d.ts +2 -0
  226. package/esm/services/theme-provider-service.spec.d.ts.map +1 -0
  227. package/esm/services/theme-provider-service.spec.js +166 -0
  228. package/esm/services/theme-provider-service.spec.js.map +1 -0
  229. package/package.json +2 -2
  230. package/src/components/animations.spec.ts +299 -0
  231. package/src/components/app-bar-link.spec.tsx +341 -0
  232. package/src/components/app-bar-link.tsx +21 -21
  233. package/src/components/app-bar.spec.tsx +142 -0
  234. package/src/components/app-bar.tsx +22 -22
  235. package/src/components/avatar.spec.tsx +146 -0
  236. package/src/components/avatar.tsx +17 -20
  237. package/src/components/button.spec.tsx +193 -0
  238. package/src/components/button.tsx +162 -197
  239. package/src/components/command-palette/command-palette-input.spec.tsx +320 -0
  240. package/src/components/command-palette/command-palette-input.tsx +19 -22
  241. package/src/components/command-palette/command-palette-manager.spec.ts +470 -0
  242. package/src/components/command-palette/command-palette-suggestion-list.spec.tsx +499 -0
  243. package/src/components/command-palette/command-palette-suggestion-list.tsx +42 -46
  244. package/src/components/command-palette/index.spec.tsx +684 -0
  245. package/src/components/command-palette/index.tsx +107 -136
  246. package/src/components/data-grid/body.spec.tsx +340 -0
  247. package/src/components/data-grid/body.tsx +1 -1
  248. package/src/components/data-grid/data-grid-row.spec.tsx +382 -0
  249. package/src/components/data-grid/data-grid-row.tsx +50 -82
  250. package/src/components/data-grid/data-grid.spec.tsx +939 -0
  251. package/src/components/data-grid/data-grid.tsx +38 -35
  252. package/src/components/data-grid/footer.spec.tsx +344 -0
  253. package/src/components/data-grid/footer.tsx +19 -19
  254. package/src/components/data-grid/header.spec.tsx +563 -0
  255. package/src/components/data-grid/header.tsx +53 -44
  256. package/src/components/data-grid/selection-cell.spec.tsx +150 -0
  257. package/src/components/data-grid/selection-cell.tsx +12 -6
  258. package/src/components/fab.spec.tsx +108 -0
  259. package/src/components/fab.tsx +10 -1
  260. package/src/components/form.spec.tsx +481 -0
  261. package/src/components/grid.spec.tsx +334 -0
  262. package/src/components/grid.tsx +57 -63
  263. package/src/components/inputs/autocomplete.spec.tsx +258 -0
  264. package/src/components/inputs/input.spec.tsx +808 -0
  265. package/src/components/inputs/input.tsx +153 -139
  266. package/src/components/inputs/text-area.spec.tsx +285 -0
  267. package/src/components/inputs/text-area.tsx +53 -79
  268. package/src/components/loader.spec.tsx +346 -0
  269. package/src/components/loader.tsx +1 -1
  270. package/src/components/modal.spec.tsx +304 -0
  271. package/src/components/modal.tsx +11 -9
  272. package/src/components/noty-list.spec.tsx +631 -0
  273. package/src/components/noty-list.tsx +39 -50
  274. package/src/components/paper.spec.tsx +72 -0
  275. package/src/components/paper.tsx +15 -13
  276. package/src/components/skeleton.spec.tsx +219 -0
  277. package/src/components/skeleton.tsx +1 -1
  278. package/src/components/styles.spec.ts +70 -0
  279. package/src/components/suggest/index.spec.tsx +861 -0
  280. package/src/components/suggest/index.tsx +74 -101
  281. package/src/components/suggest/suggest-input.spec.tsx +181 -0
  282. package/src/components/suggest/suggest-input.tsx +16 -24
  283. package/src/components/suggest/suggest-manager.spec.ts +409 -0
  284. package/src/components/suggest/suggestion-list.spec.tsx +334 -0
  285. package/src/components/suggest/suggestion-list.tsx +43 -48
  286. package/src/components/tabs.spec.tsx +236 -0
  287. package/src/components/tabs.tsx +33 -21
  288. package/src/components/wizard/index.spec.tsx +224 -0
  289. package/src/components/wizard/index.tsx +10 -9
  290. package/src/services/collection-service.spec.ts +492 -3
  291. package/src/services/css-variable-theme.spec.ts +204 -0
  292. package/src/services/css-variable-theme.ts +21 -1
  293. package/src/services/default-palette.ts +22 -0
  294. package/src/services/theme-provider-service.spec.ts +195 -0
  295. package/src/services/theme-provider-service.ts +60 -2
@@ -0,0 +1,808 @@
1
+ import { Injector } from '@furystack/inject'
2
+ import { createComponent, initializeShadeRoot } from '@furystack/shades'
3
+ import { sleepAsync } from '@furystack/utils'
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
5
+ import { ThemeProviderService } from '../../services/theme-provider-service.js'
6
+ import { Form, FormService } from '../form.js'
7
+ import { Input } from './input.js'
8
+
9
+ describe('Input', () => {
10
+ beforeEach(() => {
11
+ document.body.innerHTML = '<div id="root"></div>'
12
+ })
13
+
14
+ afterEach(() => {
15
+ document.body.innerHTML = ''
16
+ vi.restoreAllMocks()
17
+ })
18
+
19
+ it('should render with shadow DOM', async () => {
20
+ const injector = new Injector()
21
+ const rootElement = document.getElementById('root') as HTMLDivElement
22
+
23
+ initializeShadeRoot({
24
+ injector,
25
+ rootElement,
26
+ jsxElement: <Input />,
27
+ })
28
+
29
+ await sleepAsync(50)
30
+
31
+ const input = document.querySelector('shade-input')
32
+ expect(input).not.toBeNull()
33
+ })
34
+
35
+ it('should render the inner input element', async () => {
36
+ const injector = new Injector()
37
+ const rootElement = document.getElementById('root') as HTMLDivElement
38
+
39
+ initializeShadeRoot({
40
+ injector,
41
+ rootElement,
42
+ jsxElement: <Input name="testField" />,
43
+ })
44
+
45
+ await sleepAsync(50)
46
+
47
+ const input = document.querySelector('shade-input input') as HTMLInputElement
48
+ expect(input).not.toBeNull()
49
+ expect(input.name).toBe('testField')
50
+ })
51
+
52
+ it('should render the label title', async () => {
53
+ const injector = new Injector()
54
+ const rootElement = document.getElementById('root') as HTMLDivElement
55
+
56
+ initializeShadeRoot({
57
+ injector,
58
+ rootElement,
59
+ jsxElement: <Input labelTitle="Test Label" />,
60
+ })
61
+
62
+ await sleepAsync(50)
63
+
64
+ const label = document.querySelector('shade-input label') as HTMLLabelElement
65
+ expect(label).not.toBeNull()
66
+ expect(label.textContent).toContain('Test Label')
67
+ })
68
+
69
+ describe('variants', () => {
70
+ it('should set data-variant attribute for outlined variant', async () => {
71
+ const injector = new Injector()
72
+ const rootElement = document.getElementById('root') as HTMLDivElement
73
+
74
+ initializeShadeRoot({
75
+ injector,
76
+ rootElement,
77
+ jsxElement: <Input variant="outlined" />,
78
+ })
79
+
80
+ await sleepAsync(50)
81
+
82
+ const input = document.querySelector('shade-input') as HTMLElement
83
+ expect(input).not.toBeNull()
84
+ expect(input.getAttribute('data-variant')).toBe('outlined')
85
+ })
86
+
87
+ it('should set data-variant attribute for contained variant', async () => {
88
+ const injector = new Injector()
89
+ const rootElement = document.getElementById('root') as HTMLDivElement
90
+
91
+ initializeShadeRoot({
92
+ injector,
93
+ rootElement,
94
+ jsxElement: <Input variant="contained" />,
95
+ })
96
+
97
+ await sleepAsync(50)
98
+
99
+ const input = document.querySelector('shade-input') as HTMLElement
100
+ expect(input).not.toBeNull()
101
+ expect(input.getAttribute('data-variant')).toBe('contained')
102
+ })
103
+
104
+ it('should not have data-variant when variant is not specified', async () => {
105
+ const injector = new Injector()
106
+ const rootElement = document.getElementById('root') as HTMLDivElement
107
+
108
+ initializeShadeRoot({
109
+ injector,
110
+ rootElement,
111
+ jsxElement: <Input />,
112
+ })
113
+
114
+ await sleepAsync(50)
115
+
116
+ const input = document.querySelector('shade-input') as HTMLElement
117
+ expect(input).not.toBeNull()
118
+ expect(input.hasAttribute('data-variant')).toBe(false)
119
+ })
120
+ })
121
+
122
+ describe('disabled state', () => {
123
+ it('should set data-disabled attribute when disabled', async () => {
124
+ const injector = new Injector()
125
+ const rootElement = document.getElementById('root') as HTMLDivElement
126
+
127
+ initializeShadeRoot({
128
+ injector,
129
+ rootElement,
130
+ jsxElement: <Input disabled />,
131
+ })
132
+
133
+ await sleepAsync(50)
134
+
135
+ const input = document.querySelector('shade-input') as HTMLElement
136
+ expect(input).not.toBeNull()
137
+ expect(input.hasAttribute('data-disabled')).toBe(true)
138
+ })
139
+
140
+ it('should not have data-disabled attribute when not disabled', async () => {
141
+ const injector = new Injector()
142
+ const rootElement = document.getElementById('root') as HTMLDivElement
143
+
144
+ initializeShadeRoot({
145
+ injector,
146
+ rootElement,
147
+ jsxElement: <Input disabled={false} />,
148
+ })
149
+
150
+ await sleepAsync(50)
151
+
152
+ const input = document.querySelector('shade-input') as HTMLElement
153
+ expect(input).not.toBeNull()
154
+ expect(input.hasAttribute('data-disabled')).toBe(false)
155
+ })
156
+ })
157
+
158
+ describe('validation', () => {
159
+ it('should call custom validation callback', async () => {
160
+ const injector = new Injector()
161
+ const rootElement = document.getElementById('root') as HTMLDivElement
162
+ const getValidationResult = vi.fn().mockReturnValue({ isValid: true })
163
+
164
+ initializeShadeRoot({
165
+ injector,
166
+ rootElement,
167
+ jsxElement: <Input name="email" getValidationResult={getValidationResult} />,
168
+ })
169
+
170
+ await sleepAsync(50)
171
+
172
+ const input = document.querySelector('shade-input input') as HTMLInputElement
173
+ input.value = 'test@example.com'
174
+ input.dispatchEvent(new Event('change', { bubbles: true }))
175
+
176
+ await sleepAsync(50)
177
+
178
+ expect(getValidationResult).toHaveBeenCalled()
179
+ })
180
+
181
+ it('should set data-invalid when validation fails', async () => {
182
+ const injector = new Injector()
183
+ const rootElement = document.getElementById('root') as HTMLDivElement
184
+
185
+ initializeShadeRoot({
186
+ injector,
187
+ rootElement,
188
+ jsxElement: (
189
+ <Input
190
+ name="email"
191
+ getValidationResult={() => ({ isValid: false, message: 'Invalid email' })}
192
+ value="invalid"
193
+ />
194
+ ),
195
+ })
196
+
197
+ await sleepAsync(50)
198
+
199
+ const inputWrapper = document.querySelector('shade-input') as HTMLElement
200
+ const input = inputWrapper.querySelector('input') as HTMLInputElement
201
+ input.value = 'invalid'
202
+ input.dispatchEvent(new Event('change', { bubbles: true }))
203
+
204
+ await sleepAsync(50)
205
+
206
+ expect(inputWrapper.hasAttribute('data-invalid')).toBe(true)
207
+ })
208
+
209
+ it('should not have data-invalid when validation passes', async () => {
210
+ const injector = new Injector()
211
+ const rootElement = document.getElementById('root') as HTMLDivElement
212
+
213
+ initializeShadeRoot({
214
+ injector,
215
+ rootElement,
216
+ jsxElement: <Input name="email" getValidationResult={() => ({ isValid: true })} value="valid@email.com" />,
217
+ })
218
+
219
+ await sleepAsync(50)
220
+
221
+ const inputWrapper = document.querySelector('shade-input') as HTMLElement
222
+ const input = inputWrapper.querySelector('input') as HTMLInputElement
223
+ input.value = 'valid@email.com'
224
+ input.dispatchEvent(new Event('change', { bubbles: true }))
225
+
226
+ await sleepAsync(50)
227
+
228
+ expect(inputWrapper.hasAttribute('data-invalid')).toBe(false)
229
+ })
230
+
231
+ it('should display validation message in helper text when validation fails', async () => {
232
+ const injector = new Injector()
233
+ const rootElement = document.getElementById('root') as HTMLDivElement
234
+
235
+ initializeShadeRoot({
236
+ injector,
237
+ rootElement,
238
+ jsxElement: (
239
+ <Input name="email" getValidationResult={() => ({ isValid: false, message: 'Email is required' })} />
240
+ ),
241
+ })
242
+
243
+ await sleepAsync(50)
244
+
245
+ const inputWrapper = document.querySelector('shade-input') as HTMLElement
246
+ const input = inputWrapper.querySelector('input') as HTMLInputElement
247
+ input.value = ''
248
+ input.dispatchEvent(new Event('change', { bubbles: true }))
249
+
250
+ await sleepAsync(50)
251
+
252
+ const helperText = inputWrapper.querySelector('.helperText') as HTMLElement
253
+ expect(helperText.textContent).toBe('Email is required')
254
+ })
255
+
256
+ it('should show default validation message for required field', async () => {
257
+ const injector = new Injector()
258
+ const rootElement = document.getElementById('root') as HTMLDivElement
259
+
260
+ initializeShadeRoot({
261
+ injector,
262
+ rootElement,
263
+ jsxElement: <Input name="field" required />,
264
+ })
265
+
266
+ await sleepAsync(50)
267
+
268
+ const inputWrapper = document.querySelector('shade-input') as HTMLElement
269
+ const input = inputWrapper.querySelector('input') as HTMLInputElement
270
+
271
+ const invalidEvent = new Event('invalid', { bubbles: true, cancelable: true })
272
+ input.dispatchEvent(invalidEvent)
273
+
274
+ await sleepAsync(50)
275
+
276
+ const helperText = inputWrapper.querySelector('.helperText') as HTMLElement
277
+ expect(helperText.textContent).toBe('Value is required')
278
+ })
279
+ })
280
+
281
+ describe('helper text', () => {
282
+ it('should render custom helper text', async () => {
283
+ const injector = new Injector()
284
+ const rootElement = document.getElementById('root') as HTMLDivElement
285
+
286
+ initializeShadeRoot({
287
+ injector,
288
+ rootElement,
289
+ jsxElement: <Input name="email" getHelperText={() => 'Enter your email address'} />,
290
+ })
291
+
292
+ await sleepAsync(50)
293
+
294
+ const inputWrapper = document.querySelector('shade-input') as HTMLElement
295
+ const helperText = inputWrapper.querySelector('.helperText') as HTMLElement
296
+
297
+ expect(helperText.textContent).toBe('Enter your email address')
298
+ })
299
+
300
+ it('should call getHelperText with state and validation result when validation passes', async () => {
301
+ const injector = new Injector()
302
+ const rootElement = document.getElementById('root') as HTMLDivElement
303
+ const getHelperText = vi.fn().mockReturnValue('Helper text')
304
+
305
+ initializeShadeRoot({
306
+ injector,
307
+ rootElement,
308
+ jsxElement: (
309
+ <Input name="email" getHelperText={getHelperText} getValidationResult={() => ({ isValid: true })} />
310
+ ),
311
+ })
312
+
313
+ await sleepAsync(50)
314
+
315
+ const input = document.querySelector('shade-input input') as HTMLInputElement
316
+ input.value = 'test'
317
+ input.dispatchEvent(new Event('change', { bubbles: true }))
318
+
319
+ await sleepAsync(50)
320
+
321
+ expect(getHelperText).toHaveBeenCalled()
322
+
323
+ const { calls } = getHelperText.mock
324
+ const callWithValidation = calls.find(
325
+ (call: Array<{ validationResult?: unknown }>) => call[0].validationResult !== undefined,
326
+ )
327
+ expect(callWithValidation).toBeDefined()
328
+ expect((callWithValidation as [{ validationResult: unknown; state: unknown }])[0].validationResult).toEqual({
329
+ isValid: true,
330
+ })
331
+ expect((callWithValidation as [{ validationResult: unknown; state: unknown }])[0].state).toBeDefined()
332
+ })
333
+
334
+ it('should use validation message instead of getHelperText when validation fails with message', async () => {
335
+ const injector = new Injector()
336
+ const rootElement = document.getElementById('root') as HTMLDivElement
337
+ const getHelperText = vi.fn().mockReturnValue('Fallback helper')
338
+
339
+ initializeShadeRoot({
340
+ injector,
341
+ rootElement,
342
+ jsxElement: (
343
+ <Input
344
+ name="email"
345
+ getHelperText={getHelperText}
346
+ getValidationResult={() => ({ isValid: false, message: 'Validation error message' })}
347
+ />
348
+ ),
349
+ })
350
+
351
+ await sleepAsync(50)
352
+
353
+ const input = document.querySelector('shade-input input') as HTMLInputElement
354
+ input.value = 'test'
355
+ input.dispatchEvent(new Event('change', { bubbles: true }))
356
+
357
+ await sleepAsync(50)
358
+
359
+ const inputWrapper = document.querySelector('shade-input') as HTMLElement
360
+ const helperText = inputWrapper.querySelector('.helperText') as HTMLElement
361
+
362
+ expect(helperText.textContent).toBe('Validation error message')
363
+ })
364
+ })
365
+
366
+ describe('icons', () => {
367
+ it('should render start icon when getStartIcon is provided', async () => {
368
+ const injector = new Injector()
369
+ const rootElement = document.getElementById('root') as HTMLDivElement
370
+
371
+ initializeShadeRoot({
372
+ injector,
373
+ rootElement,
374
+ jsxElement: <Input name="search" getStartIcon={() => '🔍'} />,
375
+ })
376
+
377
+ await sleepAsync(50)
378
+
379
+ const inputWrapper = document.querySelector('shade-input') as HTMLElement
380
+ const startIcon = inputWrapper.querySelector('.startIcon') as HTMLElement
381
+
382
+ expect(startIcon).not.toBeNull()
383
+ expect(startIcon.textContent).toBe('🔍')
384
+ })
385
+
386
+ it('should render end icon when getEndIcon is provided', async () => {
387
+ const injector = new Injector()
388
+ const rootElement = document.getElementById('root') as HTMLDivElement
389
+
390
+ initializeShadeRoot({
391
+ injector,
392
+ rootElement,
393
+ jsxElement: <Input name="password" getEndIcon={() => '👁️'} />,
394
+ })
395
+
396
+ await sleepAsync(50)
397
+
398
+ const inputWrapper = document.querySelector('shade-input') as HTMLElement
399
+ const endIcon = inputWrapper.querySelector('.endIcon') as HTMLElement
400
+
401
+ expect(endIcon).not.toBeNull()
402
+ expect(endIcon.textContent).toBe('👁️')
403
+ })
404
+
405
+ it('should not render icon container when getStartIcon is not provided', async () => {
406
+ const injector = new Injector()
407
+ const rootElement = document.getElementById('root') as HTMLDivElement
408
+
409
+ initializeShadeRoot({
410
+ injector,
411
+ rootElement,
412
+ jsxElement: <Input name="field" />,
413
+ })
414
+
415
+ await sleepAsync(50)
416
+
417
+ const inputWrapper = document.querySelector('shade-input') as HTMLElement
418
+ const startIcon = inputWrapper.querySelector('.startIcon')
419
+
420
+ expect(startIcon).toBeNull()
421
+ })
422
+
423
+ it('should update icons on state change', async () => {
424
+ const injector = new Injector()
425
+ const rootElement = document.getElementById('root') as HTMLDivElement
426
+
427
+ initializeShadeRoot({
428
+ injector,
429
+ rootElement,
430
+ jsxElement: <Input name="field" getEndIcon={({ state }) => (state.focused ? '✓' : '○')} />,
431
+ })
432
+
433
+ await sleepAsync(50)
434
+
435
+ const inputWrapper = document.querySelector('shade-input') as HTMLElement
436
+ const input = inputWrapper.querySelector('input') as HTMLInputElement
437
+ const endIcon = inputWrapper.querySelector('.endIcon') as HTMLElement
438
+
439
+ expect(endIcon.textContent).toBe('○')
440
+
441
+ input.dispatchEvent(new FocusEvent('focus'))
442
+ await sleepAsync(50)
443
+
444
+ expect(endIcon.textContent).toBe('✓')
445
+ })
446
+ })
447
+
448
+ describe('theme integration', () => {
449
+ it('should set CSS color variables from theme', async () => {
450
+ const injector = new Injector()
451
+ const rootElement = document.getElementById('root') as HTMLDivElement
452
+
453
+ initializeShadeRoot({
454
+ injector,
455
+ rootElement,
456
+ jsxElement: <Input name="field" />,
457
+ })
458
+
459
+ await sleepAsync(50)
460
+
461
+ const inputWrapper = document.querySelector('shade-input') as HTMLElement
462
+
463
+ const themeService = injector.getInstance(ThemeProviderService)
464
+ expect(inputWrapper.style.getPropertyValue('--input-primary-color')).toBe(themeService.theme.palette.primary.main)
465
+ expect(inputWrapper.style.getPropertyValue('--input-error-color')).toBe(themeService.theme.palette.error.main)
466
+ })
467
+
468
+ it('should use custom color from defaultColor prop', async () => {
469
+ const injector = new Injector()
470
+ const rootElement = document.getElementById('root') as HTMLDivElement
471
+
472
+ initializeShadeRoot({
473
+ injector,
474
+ rootElement,
475
+ jsxElement: <Input name="field" defaultColor="secondary" />,
476
+ })
477
+
478
+ await sleepAsync(50)
479
+
480
+ const inputWrapper = document.querySelector('shade-input') as HTMLElement
481
+
482
+ const themeService = injector.getInstance(ThemeProviderService)
483
+ expect(inputWrapper.style.getPropertyValue('--input-primary-color')).toBe(
484
+ themeService.theme.palette.secondary.main,
485
+ )
486
+ })
487
+ })
488
+
489
+ describe('callbacks', () => {
490
+ it('should call onTextChange when input value changes', async () => {
491
+ const injector = new Injector()
492
+ const rootElement = document.getElementById('root') as HTMLDivElement
493
+ const onTextChange = vi.fn()
494
+
495
+ initializeShadeRoot({
496
+ injector,
497
+ rootElement,
498
+ jsxElement: <Input name="field" onTextChange={onTextChange} />,
499
+ })
500
+
501
+ await sleepAsync(50)
502
+
503
+ const input = document.querySelector('shade-input input') as HTMLInputElement
504
+ input.value = 'new value'
505
+ input.dispatchEvent(new Event('change', { bubbles: true }))
506
+
507
+ await sleepAsync(50)
508
+
509
+ expect(onTextChange).toHaveBeenCalledWith('new value')
510
+ })
511
+
512
+ it('should call onchange when input value changes', async () => {
513
+ const injector = new Injector()
514
+ const rootElement = document.getElementById('root') as HTMLDivElement
515
+ const onchange = vi.fn()
516
+
517
+ initializeShadeRoot({
518
+ injector,
519
+ rootElement,
520
+ jsxElement: <Input name="field" onchange={onchange} />,
521
+ })
522
+
523
+ await sleepAsync(50)
524
+
525
+ const input = document.querySelector('shade-input input') as HTMLInputElement
526
+ input.value = 'test'
527
+ input.dispatchEvent(new Event('change', { bubbles: true }))
528
+
529
+ await sleepAsync(50)
530
+
531
+ expect(onchange).toHaveBeenCalled()
532
+ })
533
+ })
534
+
535
+ describe('focus and blur', () => {
536
+ it('should update state on focus', async () => {
537
+ const injector = new Injector()
538
+ const rootElement = document.getElementById('root') as HTMLDivElement
539
+ const getEndIcon = vi.fn().mockReturnValue('icon')
540
+
541
+ initializeShadeRoot({
542
+ injector,
543
+ rootElement,
544
+ jsxElement: <Input name="field" getEndIcon={getEndIcon} />,
545
+ })
546
+
547
+ await sleepAsync(50)
548
+
549
+ const input = document.querySelector('shade-input input') as HTMLInputElement
550
+ input.dispatchEvent(new FocusEvent('focus'))
551
+
552
+ await sleepAsync(50)
553
+
554
+ expect(getEndIcon).toHaveBeenLastCalledWith(
555
+ expect.objectContaining({
556
+ state: expect.objectContaining({
557
+ focused: true,
558
+ }) as unknown,
559
+ }),
560
+ )
561
+ })
562
+
563
+ it('should update state on blur', async () => {
564
+ const injector = new Injector()
565
+ const rootElement = document.getElementById('root') as HTMLDivElement
566
+ const getEndIcon = vi.fn().mockReturnValue('icon')
567
+
568
+ initializeShadeRoot({
569
+ injector,
570
+ rootElement,
571
+ jsxElement: <Input name="field" getEndIcon={getEndIcon} />,
572
+ })
573
+
574
+ await sleepAsync(50)
575
+
576
+ const input = document.querySelector('shade-input input') as HTMLInputElement
577
+
578
+ input.dispatchEvent(new FocusEvent('focus'))
579
+ await sleepAsync(50)
580
+
581
+ input.dispatchEvent(new FocusEvent('blur'))
582
+ await sleepAsync(50)
583
+
584
+ expect(getEndIcon).toHaveBeenLastCalledWith(
585
+ expect.objectContaining({
586
+ state: expect.objectContaining({
587
+ focused: false,
588
+ }) as unknown,
589
+ }),
590
+ )
591
+ })
592
+ })
593
+
594
+ describe('autofocus', () => {
595
+ it('should set initial focused state based on autofocus prop', async () => {
596
+ const injector = new Injector()
597
+ const rootElement = document.getElementById('root') as HTMLDivElement
598
+ const getEndIcon = vi.fn().mockReturnValue('icon')
599
+
600
+ initializeShadeRoot({
601
+ injector,
602
+ rootElement,
603
+ jsxElement: <Input name="field" autofocus getEndIcon={getEndIcon} />,
604
+ })
605
+
606
+ await sleepAsync(50)
607
+
608
+ expect(getEndIcon).toHaveBeenCalledWith(
609
+ expect.objectContaining({
610
+ state: expect.objectContaining({
611
+ focused: true,
612
+ }) as unknown,
613
+ }),
614
+ )
615
+ })
616
+ })
617
+
618
+ describe('FormService integration', () => {
619
+ it('should register input with FormService when inside a Form', async () => {
620
+ const injector = new Injector()
621
+ const rootElement = document.getElementById('root') as HTMLDivElement
622
+
623
+ type TestFormData = { email: string }
624
+
625
+ initializeShadeRoot({
626
+ injector,
627
+ rootElement,
628
+ jsxElement: (
629
+ <Form<TestFormData> onSubmit={() => {}} validate={(_data): _data is TestFormData => true}>
630
+ <Input name="email" labelTitle="Email" />
631
+ </Form>
632
+ ),
633
+ })
634
+
635
+ await sleepAsync(50)
636
+
637
+ const form = document.querySelector('form[is="shade-form"]') as HTMLFormElement
638
+ const formInjector = (form as unknown as { injector: Injector }).injector
639
+ const formService = formInjector.getInstance(FormService)
640
+
641
+ expect(formService.inputs.size).toBe(1)
642
+ })
643
+
644
+ it('should update FormService field state on validation', async () => {
645
+ const injector = new Injector()
646
+ const rootElement = document.getElementById('root') as HTMLDivElement
647
+
648
+ type TestFormData = { email: string }
649
+
650
+ initializeShadeRoot({
651
+ injector,
652
+ rootElement,
653
+ jsxElement: (
654
+ <Form<TestFormData> onSubmit={() => {}} validate={(_data): _data is TestFormData => true}>
655
+ <Input
656
+ name="email"
657
+ labelTitle="Email"
658
+ getValidationResult={({ state }) => {
659
+ if (state.value.includes('@')) {
660
+ return { isValid: true }
661
+ }
662
+ return { isValid: false, message: 'Invalid email format' }
663
+ }}
664
+ />
665
+ </Form>
666
+ ),
667
+ })
668
+
669
+ await sleepAsync(50)
670
+
671
+ const form = document.querySelector('form[is="shade-form"]') as HTMLFormElement
672
+ const inputWrapper = form.querySelector('shade-input') as HTMLElement
673
+ const input = inputWrapper.querySelector('input') as HTMLInputElement
674
+
675
+ input.value = 'invalid'
676
+ input.dispatchEvent(new Event('change', { bubbles: true }))
677
+
678
+ await sleepAsync(50)
679
+
680
+ const formInjector = (form as unknown as { injector: Injector }).injector
681
+ const formService = formInjector.getInstance(FormService)
682
+ const fieldErrors = formService.fieldErrors.getValue()
683
+
684
+ expect(fieldErrors.email).toBeDefined()
685
+ expect(fieldErrors.email?.validationResult).toEqual({
686
+ isValid: false,
687
+ message: 'Invalid email format',
688
+ })
689
+ })
690
+
691
+ it('should unregister input from FormService on cleanup', async () => {
692
+ const injector = new Injector()
693
+ const rootElement = document.getElementById('root') as HTMLDivElement
694
+
695
+ type TestFormData = { email: string }
696
+
697
+ initializeShadeRoot({
698
+ injector,
699
+ rootElement,
700
+ jsxElement: (
701
+ <Form<TestFormData> onSubmit={() => {}} validate={(_data): _data is TestFormData => true}>
702
+ <Input name="email" labelTitle="Email" />
703
+ </Form>
704
+ ),
705
+ })
706
+
707
+ await sleepAsync(50)
708
+
709
+ const form = document.querySelector('form[is="shade-form"]') as HTMLFormElement
710
+ const formInjector = (form as unknown as { injector: Injector }).injector
711
+ const formService = formInjector.getInstance(FormService)
712
+
713
+ expect(formService.inputs.size).toBe(1)
714
+
715
+ rootElement.innerHTML = ''
716
+
717
+ await sleepAsync(50)
718
+ })
719
+ })
720
+
721
+ describe('default validation messages', () => {
722
+ it('should show message for typeMismatch', async () => {
723
+ const injector = new Injector()
724
+ const rootElement = document.getElementById('root') as HTMLDivElement
725
+
726
+ initializeShadeRoot({
727
+ injector,
728
+ rootElement,
729
+ jsxElement: <Input name="email" type="email" />,
730
+ })
731
+
732
+ await sleepAsync(50)
733
+
734
+ const inputWrapper = document.querySelector('shade-input') as HTMLElement
735
+ const input = inputWrapper.querySelector('input') as HTMLInputElement
736
+
737
+ input.value = 'not-an-email'
738
+ const invalidEvent = new Event('invalid', { bubbles: true, cancelable: true })
739
+ input.dispatchEvent(invalidEvent)
740
+
741
+ await sleepAsync(50)
742
+
743
+ const helperText = inputWrapper.querySelector('.helperText') as HTMLElement
744
+ expect(helperText.textContent).toBe('Value is not valid')
745
+ })
746
+
747
+ it('should handle pattern mismatch validation', async () => {
748
+ const injector = new Injector()
749
+ const rootElement = document.getElementById('root') as HTMLDivElement
750
+
751
+ initializeShadeRoot({
752
+ injector,
753
+ rootElement,
754
+ jsxElement: <Input name="code" pattern="[A-Z]{3}" />,
755
+ })
756
+
757
+ await sleepAsync(50)
758
+
759
+ const inputWrapper = document.querySelector('shade-input') as HTMLElement
760
+ const input = inputWrapper.querySelector('input') as HTMLInputElement
761
+
762
+ input.value = '123'
763
+ const invalidEvent = new Event('invalid', { bubbles: true, cancelable: true })
764
+ input.dispatchEvent(invalidEvent)
765
+
766
+ await sleepAsync(50)
767
+
768
+ const helperText = inputWrapper.querySelector('.helperText') as HTMLElement
769
+ expect(helperText.textContent).toBe('Value does not match the pattern')
770
+ })
771
+ })
772
+
773
+ describe('value handling', () => {
774
+ it('should use initial value prop', async () => {
775
+ const injector = new Injector()
776
+ const rootElement = document.getElementById('root') as HTMLDivElement
777
+
778
+ initializeShadeRoot({
779
+ injector,
780
+ rootElement,
781
+ jsxElement: <Input name="field" value="initial value" />,
782
+ })
783
+
784
+ await sleepAsync(50)
785
+
786
+ const input = document.querySelector('shade-input input') as HTMLInputElement
787
+ expect(input.value).toBe('initial value')
788
+ })
789
+ })
790
+
791
+ describe('labelProps', () => {
792
+ it('should pass labelProps to the label element', async () => {
793
+ const injector = new Injector()
794
+ const rootElement = document.getElementById('root') as HTMLDivElement
795
+
796
+ initializeShadeRoot({
797
+ injector,
798
+ rootElement,
799
+ jsxElement: <Input name="field" labelProps={{ className: 'custom-label' }} />,
800
+ })
801
+
802
+ await sleepAsync(50)
803
+
804
+ const label = document.querySelector('shade-input label') as HTMLLabelElement
805
+ expect(label.className).toContain('custom-label')
806
+ })
807
+ })
808
+ })