@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,861 @@
1
+ import { Injector } from '@furystack/inject'
2
+ import { createComponent, initializeShadeRoot } from '@furystack/shades'
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
4
+ import { Suggest } from './index.js'
5
+ import type { SuggestionResult } from './suggestion-result.js'
6
+
7
+ type TestEntry = { id: number; name: string }
8
+
9
+ describe('Suggest', () => {
10
+ let originalAnimate: typeof Element.prototype.animate
11
+ let animateCalls: Array<{ keyframes: unknown; options: unknown }>
12
+
13
+ beforeEach(() => {
14
+ vi.useFakeTimers()
15
+ document.body.innerHTML = '<div id="root"></div>'
16
+ animateCalls = []
17
+ originalAnimate = Element.prototype.animate
18
+
19
+ Element.prototype.animate = vi.fn(
20
+ (keyframes: Keyframe[] | PropertyIndexedKeyframes | null, options?: number | KeyframeAnimationOptions) => {
21
+ animateCalls.push({ keyframes, options })
22
+ const mockAnimation = {
23
+ onfinish: null as ((event: AnimationPlaybackEvent) => void) | null,
24
+ oncancel: null as ((event: AnimationPlaybackEvent) => void) | null,
25
+ cancel: vi.fn(),
26
+ play: vi.fn(),
27
+ pause: vi.fn(),
28
+ finish: vi.fn(),
29
+ addEventListener: vi.fn(),
30
+ removeEventListener: vi.fn(),
31
+ }
32
+
33
+ return mockAnimation as unknown as Animation
34
+ },
35
+ ) as typeof Element.prototype.animate
36
+ })
37
+
38
+ afterEach(async () => {
39
+ await vi.runAllTimersAsync()
40
+ const suggest = document.querySelector('shade-suggest')
41
+ suggest?.remove()
42
+ document.body.innerHTML = ''
43
+ Element.prototype.animate = originalAnimate
44
+ vi.useRealTimers()
45
+ vi.restoreAllMocks()
46
+ })
47
+
48
+ const advanceTimers = async (ms: number) => {
49
+ await vi.advanceTimersByTimeAsync(ms)
50
+ }
51
+
52
+ const createTestEntries = (): TestEntry[] => [
53
+ { id: 1, name: 'First' },
54
+ { id: 2, name: 'Second' },
55
+ { id: 3, name: 'Third' },
56
+ ]
57
+
58
+ const getTestEntries = async (term: string): Promise<TestEntry[]> => {
59
+ const entries = createTestEntries()
60
+ if (!term) return entries
61
+ return entries.filter((e) => e.name.toLowerCase().includes(term.toLowerCase()))
62
+ }
63
+
64
+ const getSuggestionEntry = (entry: TestEntry): SuggestionResult => ({
65
+ element: <span data-testid={`suggestion-${entry.id}`}>{entry.name}</span>,
66
+ score: entry.id,
67
+ })
68
+
69
+ describe('rendering', () => {
70
+ it('should render with shadow DOM', async () => {
71
+ const injector = new Injector()
72
+ const rootElement = document.getElementById('root') as HTMLDivElement
73
+ const onSelectSuggestion = vi.fn()
74
+
75
+ initializeShadeRoot({
76
+ injector,
77
+ rootElement,
78
+ jsxElement: (
79
+ <Suggest<TestEntry>
80
+ defaultPrefix="🔍"
81
+ getEntries={getTestEntries}
82
+ getSuggestionEntry={getSuggestionEntry}
83
+ onSelectSuggestion={onSelectSuggestion}
84
+ />
85
+ ),
86
+ })
87
+
88
+ await advanceTimers(50)
89
+
90
+ const suggest = document.querySelector('shade-suggest')
91
+ expect(suggest).not.toBeNull()
92
+ })
93
+
94
+ it('should render the default prefix', async () => {
95
+ const injector = new Injector()
96
+ const rootElement = document.getElementById('root') as HTMLDivElement
97
+ const onSelectSuggestion = vi.fn()
98
+
99
+ initializeShadeRoot({
100
+ injector,
101
+ rootElement,
102
+ jsxElement: (
103
+ <Suggest<TestEntry>
104
+ defaultPrefix="Search:"
105
+ getEntries={getTestEntries}
106
+ getSuggestionEntry={getSuggestionEntry}
107
+ onSelectSuggestion={onSelectSuggestion}
108
+ />
109
+ ),
110
+ })
111
+
112
+ await advanceTimers(50)
113
+
114
+ const suggest = document.querySelector('shade-suggest') as HTMLElement
115
+ const termIcon = suggest?.querySelector('.term-icon')
116
+ expect(termIcon?.textContent).toBe('Search:')
117
+ })
118
+
119
+ it('should render the input container', async () => {
120
+ const injector = new Injector()
121
+ const rootElement = document.getElementById('root') as HTMLDivElement
122
+ const onSelectSuggestion = vi.fn()
123
+
124
+ initializeShadeRoot({
125
+ injector,
126
+ rootElement,
127
+ jsxElement: (
128
+ <Suggest<TestEntry>
129
+ defaultPrefix="🔍"
130
+ getEntries={getTestEntries}
131
+ getSuggestionEntry={getSuggestionEntry}
132
+ onSelectSuggestion={onSelectSuggestion}
133
+ />
134
+ ),
135
+ })
136
+
137
+ await advanceTimers(50)
138
+
139
+ const suggest = document.querySelector('shade-suggest') as HTMLElement
140
+ const inputContainer = suggest?.querySelector('.input-container')
141
+ expect(inputContainer).not.toBeNull()
142
+ })
143
+
144
+ it('should apply custom styles', async () => {
145
+ const injector = new Injector()
146
+ const rootElement = document.getElementById('root') as HTMLDivElement
147
+ const onSelectSuggestion = vi.fn()
148
+
149
+ initializeShadeRoot({
150
+ injector,
151
+ rootElement,
152
+ jsxElement: (
153
+ <Suggest<TestEntry>
154
+ defaultPrefix="🔍"
155
+ getEntries={getTestEntries}
156
+ getSuggestionEntry={getSuggestionEntry}
157
+ onSelectSuggestion={onSelectSuggestion}
158
+ style={{ backgroundColor: 'red' }}
159
+ />
160
+ ),
161
+ })
162
+
163
+ await advanceTimers(50)
164
+
165
+ const suggest = document.querySelector('shade-suggest') as HTMLElement
166
+ const inputContainer = suggest?.querySelector('.input-container') as HTMLElement
167
+ expect(inputContainer?.style.backgroundColor).toBe('red')
168
+ })
169
+ })
170
+
171
+ describe('keyboard navigation', () => {
172
+ it('should handle ArrowDown to move selection down', async () => {
173
+ const injector = new Injector()
174
+ const rootElement = document.getElementById('root') as HTMLDivElement
175
+ const onSelectSuggestion = vi.fn()
176
+
177
+ initializeShadeRoot({
178
+ injector,
179
+ rootElement,
180
+ jsxElement: (
181
+ <Suggest<TestEntry>
182
+ defaultPrefix="🔍"
183
+ getEntries={getTestEntries}
184
+ getSuggestionEntry={getSuggestionEntry}
185
+ onSelectSuggestion={onSelectSuggestion}
186
+ />
187
+ ),
188
+ })
189
+
190
+ await advanceTimers(50)
191
+
192
+ const suggest = document.querySelector('shade-suggest') as HTMLElement
193
+ const wrapper = suggest?.querySelector('.suggest-wrapper') as HTMLElement
194
+
195
+ const input = suggest?.querySelector('input') as HTMLInputElement
196
+ input.value = 'test'
197
+
198
+ const keyupEvent = new KeyboardEvent('keyup', { key: 'a', bubbles: true })
199
+ Object.defineProperty(keyupEvent, 'target', { value: input })
200
+ wrapper?.dispatchEvent(keyupEvent)
201
+
202
+ await advanceTimers(300)
203
+
204
+ const arrowDownEvent = new KeyboardEvent('keyup', { key: 'ArrowDown', bubbles: true })
205
+ Object.defineProperty(arrowDownEvent, 'target', { value: input })
206
+ wrapper?.dispatchEvent(arrowDownEvent)
207
+
208
+ await advanceTimers(50)
209
+
210
+ const selectedItems = suggest?.querySelectorAll('.suggestion-item.selected')
211
+ expect(selectedItems?.length).toBeGreaterThanOrEqual(0)
212
+ })
213
+
214
+ it('should handle ArrowUp to move selection up', async () => {
215
+ const injector = new Injector()
216
+ const rootElement = document.getElementById('root') as HTMLDivElement
217
+ const onSelectSuggestion = vi.fn()
218
+
219
+ initializeShadeRoot({
220
+ injector,
221
+ rootElement,
222
+ jsxElement: (
223
+ <Suggest<TestEntry>
224
+ defaultPrefix="🔍"
225
+ getEntries={getTestEntries}
226
+ getSuggestionEntry={getSuggestionEntry}
227
+ onSelectSuggestion={onSelectSuggestion}
228
+ />
229
+ ),
230
+ })
231
+
232
+ await advanceTimers(50)
233
+
234
+ const suggest = document.querySelector('shade-suggest') as HTMLElement
235
+ const wrapper = suggest?.querySelector('.suggest-wrapper') as HTMLElement
236
+
237
+ const input = suggest?.querySelector('input') as HTMLInputElement
238
+ input.value = 'test'
239
+
240
+ const keyupEvent = new KeyboardEvent('keyup', { key: 'a', bubbles: true })
241
+ Object.defineProperty(keyupEvent, 'target', { value: input })
242
+ wrapper?.dispatchEvent(keyupEvent)
243
+
244
+ await advanceTimers(300)
245
+
246
+ const arrowDownEvent = new KeyboardEvent('keyup', { key: 'ArrowDown', bubbles: true })
247
+ Object.defineProperty(arrowDownEvent, 'target', { value: input })
248
+ wrapper?.dispatchEvent(arrowDownEvent)
249
+ wrapper?.dispatchEvent(arrowDownEvent)
250
+
251
+ const arrowUpEvent = new KeyboardEvent('keyup', { key: 'ArrowUp', bubbles: true })
252
+ Object.defineProperty(arrowUpEvent, 'target', { value: input })
253
+ wrapper?.dispatchEvent(arrowUpEvent)
254
+
255
+ await advanceTimers(50)
256
+
257
+ expect(suggest).not.toBeNull()
258
+ })
259
+
260
+ it('should handle Enter to select current suggestion', async () => {
261
+ const injector = new Injector()
262
+ const rootElement = document.getElementById('root') as HTMLDivElement
263
+ const onSelectSuggestion = vi.fn()
264
+
265
+ initializeShadeRoot({
266
+ injector,
267
+ rootElement,
268
+ jsxElement: (
269
+ <Suggest<TestEntry>
270
+ defaultPrefix="🔍"
271
+ getEntries={getTestEntries}
272
+ getSuggestionEntry={getSuggestionEntry}
273
+ onSelectSuggestion={onSelectSuggestion}
274
+ />
275
+ ),
276
+ })
277
+
278
+ await advanceTimers(50)
279
+
280
+ const suggest = document.querySelector('shade-suggest') as HTMLElement
281
+ const wrapper = suggest?.querySelector('.suggest-wrapper') as HTMLElement
282
+
283
+ const input = suggest?.querySelector('input') as HTMLInputElement
284
+ input.value = 'First'
285
+
286
+ const keyupEvent = new KeyboardEvent('keyup', { key: 'a', bubbles: true })
287
+ Object.defineProperty(keyupEvent, 'target', { value: input })
288
+ wrapper?.dispatchEvent(keyupEvent)
289
+
290
+ await advanceTimers(300)
291
+
292
+ const enterEvent = new KeyboardEvent('keyup', { key: 'Enter', bubbles: true })
293
+ Object.defineProperty(enterEvent, 'target', { value: input })
294
+ wrapper?.dispatchEvent(enterEvent)
295
+
296
+ await advanceTimers(50)
297
+
298
+ expect(onSelectSuggestion).toHaveBeenCalled()
299
+ })
300
+
301
+ it('should prevent default on Enter key', async () => {
302
+ const injector = new Injector()
303
+ const rootElement = document.getElementById('root') as HTMLDivElement
304
+ const onSelectSuggestion = vi.fn()
305
+
306
+ initializeShadeRoot({
307
+ injector,
308
+ rootElement,
309
+ jsxElement: (
310
+ <Suggest<TestEntry>
311
+ defaultPrefix="🔍"
312
+ getEntries={getTestEntries}
313
+ getSuggestionEntry={getSuggestionEntry}
314
+ onSelectSuggestion={onSelectSuggestion}
315
+ />
316
+ ),
317
+ })
318
+
319
+ await advanceTimers(50)
320
+
321
+ const suggest = document.querySelector('shade-suggest') as HTMLElement
322
+ const wrapper = suggest?.querySelector('.suggest-wrapper') as HTMLElement
323
+
324
+ const input = suggest?.querySelector('input') as HTMLInputElement
325
+ input.value = 'First'
326
+
327
+ const keyupEvent = new KeyboardEvent('keyup', { key: 'a', bubbles: true })
328
+ Object.defineProperty(keyupEvent, 'target', { value: input })
329
+ wrapper?.dispatchEvent(keyupEvent)
330
+
331
+ await advanceTimers(300)
332
+
333
+ const enterEvent = new KeyboardEvent('keyup', { key: 'Enter', bubbles: true, cancelable: true })
334
+ Object.defineProperty(enterEvent, 'target', { value: input })
335
+
336
+ const preventDefaultSpy = vi.spyOn(enterEvent, 'preventDefault')
337
+ wrapper?.dispatchEvent(enterEvent)
338
+
339
+ expect(preventDefaultSpy).toHaveBeenCalled()
340
+ })
341
+
342
+ it('should prevent default on ArrowUp key', async () => {
343
+ const injector = new Injector()
344
+ const rootElement = document.getElementById('root') as HTMLDivElement
345
+ const onSelectSuggestion = vi.fn()
346
+
347
+ initializeShadeRoot({
348
+ injector,
349
+ rootElement,
350
+ jsxElement: (
351
+ <Suggest<TestEntry>
352
+ defaultPrefix="🔍"
353
+ getEntries={getTestEntries}
354
+ getSuggestionEntry={getSuggestionEntry}
355
+ onSelectSuggestion={onSelectSuggestion}
356
+ />
357
+ ),
358
+ })
359
+
360
+ await advanceTimers(50)
361
+
362
+ const suggest = document.querySelector('shade-suggest') as HTMLElement
363
+ const wrapper = suggest?.querySelector('.suggest-wrapper') as HTMLElement
364
+
365
+ const input = suggest?.querySelector('input') as HTMLInputElement
366
+
367
+ const arrowUpEvent = new KeyboardEvent('keyup', { key: 'ArrowUp', bubbles: true, cancelable: true })
368
+ Object.defineProperty(arrowUpEvent, 'target', { value: input })
369
+
370
+ const preventDefaultSpy = vi.spyOn(arrowUpEvent, 'preventDefault')
371
+ wrapper?.dispatchEvent(arrowUpEvent)
372
+
373
+ expect(preventDefaultSpy).toHaveBeenCalled()
374
+ })
375
+
376
+ it('should prevent default on ArrowDown key', async () => {
377
+ const injector = new Injector()
378
+ const rootElement = document.getElementById('root') as HTMLDivElement
379
+ const onSelectSuggestion = vi.fn()
380
+
381
+ initializeShadeRoot({
382
+ injector,
383
+ rootElement,
384
+ jsxElement: (
385
+ <Suggest<TestEntry>
386
+ defaultPrefix="🔍"
387
+ getEntries={getTestEntries}
388
+ getSuggestionEntry={getSuggestionEntry}
389
+ onSelectSuggestion={onSelectSuggestion}
390
+ />
391
+ ),
392
+ })
393
+
394
+ await advanceTimers(50)
395
+
396
+ const suggest = document.querySelector('shade-suggest') as HTMLElement
397
+ const wrapper = suggest?.querySelector('.suggest-wrapper') as HTMLElement
398
+
399
+ const input = suggest?.querySelector('input') as HTMLInputElement
400
+
401
+ const arrowDownEvent = new KeyboardEvent('keyup', { key: 'ArrowDown', bubbles: true, cancelable: true })
402
+ Object.defineProperty(arrowDownEvent, 'target', { value: input })
403
+
404
+ const preventDefaultSpy = vi.spyOn(arrowDownEvent, 'preventDefault')
405
+ wrapper?.dispatchEvent(arrowDownEvent)
406
+
407
+ expect(preventDefaultSpy).toHaveBeenCalled()
408
+ })
409
+
410
+ it('should not move selection below 0', async () => {
411
+ const injector = new Injector()
412
+ const rootElement = document.getElementById('root') as HTMLDivElement
413
+ const onSelectSuggestion = vi.fn()
414
+
415
+ initializeShadeRoot({
416
+ injector,
417
+ rootElement,
418
+ jsxElement: (
419
+ <Suggest<TestEntry>
420
+ defaultPrefix="🔍"
421
+ getEntries={getTestEntries}
422
+ getSuggestionEntry={getSuggestionEntry}
423
+ onSelectSuggestion={onSelectSuggestion}
424
+ />
425
+ ),
426
+ })
427
+
428
+ await advanceTimers(50)
429
+
430
+ const suggest = document.querySelector('shade-suggest') as HTMLElement
431
+ const wrapper = suggest?.querySelector('.suggest-wrapper') as HTMLElement
432
+
433
+ const input = suggest?.querySelector('input') as HTMLInputElement
434
+
435
+ for (let i = 0; i < 5; i++) {
436
+ const arrowUpEvent = new KeyboardEvent('keyup', { key: 'ArrowUp', bubbles: true })
437
+ Object.defineProperty(arrowUpEvent, 'target', { value: input })
438
+ wrapper?.dispatchEvent(arrowUpEvent)
439
+ }
440
+
441
+ await advanceTimers(50)
442
+
443
+ expect(suggest).not.toBeNull()
444
+ })
445
+ })
446
+
447
+ describe('open/close behavior', () => {
448
+ it('should open when clicking term icon', async () => {
449
+ const injector = new Injector()
450
+ const rootElement = document.getElementById('root') as HTMLDivElement
451
+ const onSelectSuggestion = vi.fn()
452
+
453
+ initializeShadeRoot({
454
+ injector,
455
+ rootElement,
456
+ jsxElement: (
457
+ <Suggest<TestEntry>
458
+ defaultPrefix="🔍"
459
+ getEntries={getTestEntries}
460
+ getSuggestionEntry={getSuggestionEntry}
461
+ onSelectSuggestion={onSelectSuggestion}
462
+ />
463
+ ),
464
+ })
465
+
466
+ await advanceTimers(50)
467
+
468
+ const suggest = document.querySelector('shade-suggest') as HTMLElement
469
+ const termIcon = suggest?.querySelector('.term-icon') as HTMLElement
470
+
471
+ termIcon?.click()
472
+
473
+ await advanceTimers(50)
474
+
475
+ expect(suggest?.classList.contains('opened')).toBe(true)
476
+ })
477
+
478
+ it('should close when clicking close button', async () => {
479
+ const injector = new Injector()
480
+ const rootElement = document.getElementById('root') as HTMLDivElement
481
+ const onSelectSuggestion = vi.fn()
482
+
483
+ initializeShadeRoot({
484
+ injector,
485
+ rootElement,
486
+ jsxElement: (
487
+ <Suggest<TestEntry>
488
+ defaultPrefix="🔍"
489
+ getEntries={getTestEntries}
490
+ getSuggestionEntry={getSuggestionEntry}
491
+ onSelectSuggestion={onSelectSuggestion}
492
+ />
493
+ ),
494
+ })
495
+
496
+ await advanceTimers(50)
497
+
498
+ const suggest = document.querySelector('shade-suggest') as HTMLElement
499
+ const termIcon = suggest?.querySelector('.term-icon') as HTMLElement
500
+ termIcon?.click()
501
+
502
+ await advanceTimers(50)
503
+
504
+ const closeButton = suggest?.querySelector('.close-suggestions') as HTMLElement
505
+ closeButton?.click()
506
+
507
+ await advanceTimers(50)
508
+
509
+ expect(suggest?.classList.contains('opened')).toBe(false)
510
+ })
511
+
512
+ it('should trigger animation when opening', async () => {
513
+ const injector = new Injector()
514
+ const rootElement = document.getElementById('root') as HTMLDivElement
515
+ const onSelectSuggestion = vi.fn()
516
+
517
+ initializeShadeRoot({
518
+ injector,
519
+ rootElement,
520
+ jsxElement: (
521
+ <Suggest<TestEntry>
522
+ defaultPrefix="🔍"
523
+ getEntries={getTestEntries}
524
+ getSuggestionEntry={getSuggestionEntry}
525
+ onSelectSuggestion={onSelectSuggestion}
526
+ />
527
+ ),
528
+ })
529
+
530
+ await advanceTimers(50)
531
+
532
+ const initialAnimationCount = animateCalls.length
533
+
534
+ const suggest = document.querySelector('shade-suggest') as HTMLElement
535
+ const termIcon = suggest?.querySelector('.term-icon') as HTMLElement
536
+ termIcon?.click()
537
+
538
+ await advanceTimers(50)
539
+
540
+ expect(animateCalls.length).toBeGreaterThan(initialAnimationCount)
541
+ })
542
+
543
+ it('should trigger animation when closing', async () => {
544
+ const injector = new Injector()
545
+ const rootElement = document.getElementById('root') as HTMLDivElement
546
+ const onSelectSuggestion = vi.fn()
547
+
548
+ initializeShadeRoot({
549
+ injector,
550
+ rootElement,
551
+ jsxElement: (
552
+ <Suggest<TestEntry>
553
+ defaultPrefix="🔍"
554
+ getEntries={getTestEntries}
555
+ getSuggestionEntry={getSuggestionEntry}
556
+ onSelectSuggestion={onSelectSuggestion}
557
+ />
558
+ ),
559
+ })
560
+
561
+ await advanceTimers(50)
562
+
563
+ const suggest = document.querySelector('shade-suggest') as HTMLElement
564
+ const termIcon = suggest?.querySelector('.term-icon') as HTMLElement
565
+ termIcon?.click()
566
+
567
+ await advanceTimers(50)
568
+
569
+ const animationCountAfterOpen = animateCalls.length
570
+
571
+ const closeButton = suggest?.querySelector('.close-suggestions') as HTMLElement
572
+ closeButton?.click()
573
+
574
+ await advanceTimers(50)
575
+
576
+ expect(animateCalls.length).toBeGreaterThan(animationCountAfterOpen)
577
+ })
578
+ })
579
+
580
+ describe('suggestions loading', () => {
581
+ it('should fetch suggestions when typing', async () => {
582
+ const injector = new Injector()
583
+ const rootElement = document.getElementById('root') as HTMLDivElement
584
+ const onSelectSuggestion = vi.fn()
585
+ const getEntriesSpy = vi.fn(getTestEntries)
586
+
587
+ initializeShadeRoot({
588
+ injector,
589
+ rootElement,
590
+ jsxElement: (
591
+ <Suggest<TestEntry>
592
+ defaultPrefix="🔍"
593
+ getEntries={getEntriesSpy}
594
+ getSuggestionEntry={getSuggestionEntry}
595
+ onSelectSuggestion={onSelectSuggestion}
596
+ />
597
+ ),
598
+ })
599
+
600
+ await advanceTimers(50)
601
+
602
+ const suggest = document.querySelector('shade-suggest') as HTMLElement
603
+ const wrapper = suggest?.querySelector('.suggest-wrapper') as HTMLElement
604
+
605
+ const input = suggest?.querySelector('input') as HTMLInputElement
606
+ input.value = 'First'
607
+
608
+ const keyupEvent = new KeyboardEvent('keyup', { key: 'a', bubbles: true })
609
+ Object.defineProperty(keyupEvent, 'target', { value: input })
610
+ wrapper?.dispatchEvent(keyupEvent)
611
+
612
+ await advanceTimers(300)
613
+
614
+ expect(getEntriesSpy).toHaveBeenCalledWith('First')
615
+ })
616
+
617
+ it('should show loader animation while loading', async () => {
618
+ const injector = new Injector()
619
+ const rootElement = document.getElementById('root') as HTMLDivElement
620
+ const onSelectSuggestion = vi.fn()
621
+
622
+ const resolveHolder: { resolve: ((entries: TestEntry[]) => void) | null } = { resolve: null }
623
+ const slowGetEntries = () =>
624
+ new Promise<TestEntry[]>((resolve) => {
625
+ resolveHolder.resolve = resolve
626
+ })
627
+
628
+ initializeShadeRoot({
629
+ injector,
630
+ rootElement,
631
+ jsxElement: (
632
+ <Suggest<TestEntry>
633
+ defaultPrefix="🔍"
634
+ getEntries={slowGetEntries}
635
+ getSuggestionEntry={getSuggestionEntry}
636
+ onSelectSuggestion={onSelectSuggestion}
637
+ />
638
+ ),
639
+ })
640
+
641
+ await advanceTimers(50)
642
+
643
+ const suggest = document.querySelector('shade-suggest') as HTMLElement
644
+ const wrapper = suggest?.querySelector('.suggest-wrapper') as HTMLElement
645
+
646
+ const input = suggest?.querySelector('input') as HTMLInputElement
647
+ input.value = 'test'
648
+
649
+ const keyupEvent = new KeyboardEvent('keyup', { key: 'a', bubbles: true })
650
+ Object.defineProperty(keyupEvent, 'target', { value: input })
651
+ wrapper?.dispatchEvent(keyupEvent)
652
+
653
+ await advanceTimers(300)
654
+
655
+ const loader = suggest?.querySelector('shade-loader')
656
+ expect(loader).not.toBeNull()
657
+
658
+ resolveHolder.resolve?.(createTestEntries())
659
+ await advanceTimers(50)
660
+ })
661
+
662
+ it('should render suggestions after loading', async () => {
663
+ const injector = new Injector()
664
+ const rootElement = document.getElementById('root') as HTMLDivElement
665
+ const onSelectSuggestion = vi.fn()
666
+
667
+ initializeShadeRoot({
668
+ injector,
669
+ rootElement,
670
+ jsxElement: (
671
+ <Suggest<TestEntry>
672
+ defaultPrefix="🔍"
673
+ getEntries={getTestEntries}
674
+ getSuggestionEntry={getSuggestionEntry}
675
+ onSelectSuggestion={onSelectSuggestion}
676
+ />
677
+ ),
678
+ })
679
+
680
+ await advanceTimers(50)
681
+
682
+ const suggest = document.querySelector('shade-suggest') as HTMLElement
683
+ const wrapper = suggest?.querySelector('.suggest-wrapper') as HTMLElement
684
+
685
+ const input = suggest?.querySelector('input') as HTMLInputElement
686
+ input.value = 'test'
687
+
688
+ const keyupEvent = new KeyboardEvent('keyup', { key: 'a', bubbles: true })
689
+ Object.defineProperty(keyupEvent, 'target', { value: input })
690
+ wrapper?.dispatchEvent(keyupEvent)
691
+
692
+ await advanceTimers(300)
693
+
694
+ const suggestionList = suggest?.querySelector('shade-suggest-suggestion-list')
695
+ expect(suggestionList).not.toBeNull()
696
+ })
697
+ })
698
+
699
+ describe('suggestion selection', () => {
700
+ it('should call onSelectSuggestion when selecting via Enter', async () => {
701
+ const injector = new Injector()
702
+ const rootElement = document.getElementById('root') as HTMLDivElement
703
+ const onSelectSuggestion = vi.fn()
704
+
705
+ initializeShadeRoot({
706
+ injector,
707
+ rootElement,
708
+ jsxElement: (
709
+ <Suggest<TestEntry>
710
+ defaultPrefix="🔍"
711
+ getEntries={getTestEntries}
712
+ getSuggestionEntry={getSuggestionEntry}
713
+ onSelectSuggestion={onSelectSuggestion}
714
+ />
715
+ ),
716
+ })
717
+
718
+ await advanceTimers(50)
719
+
720
+ const suggest = document.querySelector('shade-suggest') as HTMLElement
721
+ const wrapper = suggest?.querySelector('.suggest-wrapper') as HTMLElement
722
+
723
+ const input = suggest?.querySelector('input') as HTMLInputElement
724
+ input.value = 'First'
725
+
726
+ const keyupEvent = new KeyboardEvent('keyup', { key: 'a', bubbles: true })
727
+ Object.defineProperty(keyupEvent, 'target', { value: input })
728
+ wrapper?.dispatchEvent(keyupEvent)
729
+
730
+ await advanceTimers(300)
731
+
732
+ const enterEvent = new KeyboardEvent('keyup', { key: 'Enter', bubbles: true })
733
+ Object.defineProperty(enterEvent, 'target', { value: input })
734
+ wrapper?.dispatchEvent(enterEvent)
735
+
736
+ await advanceTimers(50)
737
+
738
+ expect(onSelectSuggestion).toHaveBeenCalledWith(expect.objectContaining({ name: 'First' }))
739
+ })
740
+
741
+ it('should close after selecting a suggestion', async () => {
742
+ const injector = new Injector()
743
+ const rootElement = document.getElementById('root') as HTMLDivElement
744
+ const onSelectSuggestion = vi.fn()
745
+
746
+ initializeShadeRoot({
747
+ injector,
748
+ rootElement,
749
+ jsxElement: (
750
+ <Suggest<TestEntry>
751
+ defaultPrefix="🔍"
752
+ getEntries={getTestEntries}
753
+ getSuggestionEntry={getSuggestionEntry}
754
+ onSelectSuggestion={onSelectSuggestion}
755
+ />
756
+ ),
757
+ })
758
+
759
+ await advanceTimers(50)
760
+
761
+ const suggest = document.querySelector('shade-suggest') as HTMLElement
762
+ const wrapper = suggest?.querySelector('.suggest-wrapper') as HTMLElement
763
+
764
+ const input = suggest?.querySelector('input') as HTMLInputElement
765
+ input.value = 'First'
766
+
767
+ const keyupEvent = new KeyboardEvent('keyup', { key: 'a', bubbles: true })
768
+ Object.defineProperty(keyupEvent, 'target', { value: input })
769
+ wrapper?.dispatchEvent(keyupEvent)
770
+
771
+ await advanceTimers(300)
772
+
773
+ expect(suggest?.classList.contains('opened')).toBe(true)
774
+
775
+ const enterEvent = new KeyboardEvent('keyup', { key: 'Enter', bubbles: true })
776
+ Object.defineProperty(enterEvent, 'target', { value: input })
777
+ wrapper?.dispatchEvent(enterEvent)
778
+
779
+ await advanceTimers(50)
780
+
781
+ expect(suggest?.classList.contains('opened')).toBe(false)
782
+ })
783
+ })
784
+
785
+ describe('sub-components', () => {
786
+ it('should render SuggestInput component', async () => {
787
+ const injector = new Injector()
788
+ const rootElement = document.getElementById('root') as HTMLDivElement
789
+ const onSelectSuggestion = vi.fn()
790
+
791
+ initializeShadeRoot({
792
+ injector,
793
+ rootElement,
794
+ jsxElement: (
795
+ <Suggest<TestEntry>
796
+ defaultPrefix="🔍"
797
+ getEntries={getTestEntries}
798
+ getSuggestionEntry={getSuggestionEntry}
799
+ onSelectSuggestion={onSelectSuggestion}
800
+ />
801
+ ),
802
+ })
803
+
804
+ await advanceTimers(50)
805
+
806
+ const suggest = document.querySelector('shade-suggest') as HTMLElement
807
+ const suggestInput = suggest?.querySelector('shades-suggest-input')
808
+ expect(suggestInput).not.toBeNull()
809
+ })
810
+
811
+ it('should render SuggestionList component', async () => {
812
+ const injector = new Injector()
813
+ const rootElement = document.getElementById('root') as HTMLDivElement
814
+ const onSelectSuggestion = vi.fn()
815
+
816
+ initializeShadeRoot({
817
+ injector,
818
+ rootElement,
819
+ jsxElement: (
820
+ <Suggest<TestEntry>
821
+ defaultPrefix="🔍"
822
+ getEntries={getTestEntries}
823
+ getSuggestionEntry={getSuggestionEntry}
824
+ onSelectSuggestion={onSelectSuggestion}
825
+ />
826
+ ),
827
+ })
828
+
829
+ await advanceTimers(50)
830
+
831
+ const suggest = document.querySelector('shade-suggest') as HTMLElement
832
+ const suggestionList = suggest?.querySelector('shade-suggest-suggestion-list')
833
+ expect(suggestionList).not.toBeNull()
834
+ })
835
+
836
+ it('should render Loader component', async () => {
837
+ const injector = new Injector()
838
+ const rootElement = document.getElementById('root') as HTMLDivElement
839
+ const onSelectSuggestion = vi.fn()
840
+
841
+ initializeShadeRoot({
842
+ injector,
843
+ rootElement,
844
+ jsxElement: (
845
+ <Suggest<TestEntry>
846
+ defaultPrefix="🔍"
847
+ getEntries={getTestEntries}
848
+ getSuggestionEntry={getSuggestionEntry}
849
+ onSelectSuggestion={onSelectSuggestion}
850
+ />
851
+ ),
852
+ })
853
+
854
+ await advanceTimers(50)
855
+
856
+ const suggest = document.querySelector('shade-suggest') as HTMLElement
857
+ const loader = suggest?.querySelector('shade-loader')
858
+ expect(loader).not.toBeNull()
859
+ })
860
+ })
861
+ })