@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
@@ -1,13 +1,36 @@
1
1
  import { using } from '@furystack/utils'
2
- import { describe, expect, it } from 'vitest'
2
+ import { describe, expect, it, vi } from 'vitest'
3
3
  import { CollectionService } from './collection-service.js'
4
4
 
5
- const testEntries = [{ foo: 1 }, { foo: 2 }, { foo: 3 }]
5
+ type TestEntry = { foo: number; name?: string }
6
+
7
+ const createTestEntries = (): TestEntry[] => [
8
+ { foo: 1, name: 'alpha' },
9
+ { foo: 2, name: 'beta' },
10
+ { foo: 3, name: 'gamma' },
11
+ ]
12
+
13
+ const createKeyboardEvent = (key: string, options: Partial<KeyboardEvent> = {}): KeyboardEvent => {
14
+ return {
15
+ key,
16
+ preventDefault: vi.fn(),
17
+ ...options,
18
+ } as unknown as KeyboardEvent
19
+ }
20
+
21
+ const createMouseEvent = (options: Partial<MouseEvent> = {}): MouseEvent => {
22
+ return {
23
+ ctrlKey: false,
24
+ shiftKey: false,
25
+ ...options,
26
+ } as unknown as MouseEvent
27
+ }
6
28
 
7
29
  describe('CollectionService', () => {
8
30
  describe('Selection', () => {
9
31
  it('Should add and remove selection', () => {
10
- using(new CollectionService({}), (collectionService) => {
32
+ const testEntries = createTestEntries()
33
+ using(new CollectionService<TestEntry>({}), (collectionService) => {
11
34
  collectionService.data.setValue({ count: 3, entries: testEntries })
12
35
  testEntries.forEach((entry) => {
13
36
  expect(collectionService.isSelected(entry)).toBe(false)
@@ -28,4 +51,470 @@ describe('CollectionService', () => {
28
51
  })
29
52
  })
30
53
  })
54
+
55
+ describe('Disposal', () => {
56
+ it('Should dispose all observables', () => {
57
+ const service = new CollectionService<TestEntry>({})
58
+ const dataSpy = vi.spyOn(service.data, Symbol.dispose)
59
+ const selectionSpy = vi.spyOn(service.selection, Symbol.dispose)
60
+ const searchTermSpy = vi.spyOn(service.searchTerm, Symbol.dispose)
61
+ const hasFocusSpy = vi.spyOn(service.hasFocus, Symbol.dispose)
62
+ const focusedEntrySpy = vi.spyOn(service.focusedEntry, Symbol.dispose)
63
+
64
+ service[Symbol.dispose]()
65
+
66
+ expect(dataSpy).toHaveBeenCalled()
67
+ expect(selectionSpy).toHaveBeenCalled()
68
+ expect(searchTermSpy).toHaveBeenCalled()
69
+ expect(hasFocusSpy).toHaveBeenCalled()
70
+ expect(focusedEntrySpy).toHaveBeenCalled()
71
+ })
72
+ })
73
+
74
+ describe('handleKeyDown', () => {
75
+ it('Should do nothing when hasFocus is false', () => {
76
+ const testEntries = createTestEntries()
77
+ using(new CollectionService<TestEntry>({}), (service) => {
78
+ service.data.setValue({ count: 3, entries: testEntries })
79
+ service.hasFocus.setValue(false)
80
+ service.focusedEntry.setValue(testEntries[0])
81
+
82
+ service.handleKeyDown(createKeyboardEvent(' '))
83
+
84
+ expect(service.selection.getValue()).toEqual([])
85
+ })
86
+ })
87
+
88
+ describe('Space key', () => {
89
+ it('Should toggle selection on focused entry', () => {
90
+ const testEntries = createTestEntries()
91
+ using(new CollectionService<TestEntry>({}), (service) => {
92
+ service.data.setValue({ count: 3, entries: testEntries })
93
+ service.hasFocus.setValue(true)
94
+ service.focusedEntry.setValue(testEntries[1])
95
+
96
+ const ev = createKeyboardEvent(' ')
97
+ service.handleKeyDown(ev)
98
+
99
+ expect(ev.preventDefault).toHaveBeenCalled()
100
+ expect(service.selection.getValue()).toEqual([testEntries[1]])
101
+
102
+ service.handleKeyDown(createKeyboardEvent(' '))
103
+ expect(service.selection.getValue()).toEqual([])
104
+ })
105
+ })
106
+
107
+ it('Should do nothing when no entry is focused', () => {
108
+ const testEntries = createTestEntries()
109
+ using(new CollectionService<TestEntry>({}), (service) => {
110
+ service.data.setValue({ count: 3, entries: testEntries })
111
+ service.hasFocus.setValue(true)
112
+ service.focusedEntry.setValue(undefined)
113
+
114
+ service.handleKeyDown(createKeyboardEvent(' '))
115
+
116
+ expect(service.selection.getValue()).toEqual([])
117
+ })
118
+ })
119
+ })
120
+
121
+ describe('* key (invert selection)', () => {
122
+ it('Should invert selection', () => {
123
+ const testEntries = createTestEntries()
124
+ using(new CollectionService<TestEntry>({}), (service) => {
125
+ service.data.setValue({ count: 3, entries: testEntries })
126
+ service.hasFocus.setValue(true)
127
+ service.selection.setValue([testEntries[0], testEntries[2]])
128
+
129
+ service.handleKeyDown(createKeyboardEvent('*'))
130
+
131
+ expect(service.selection.getValue()).toEqual([testEntries[1]])
132
+ })
133
+ })
134
+ })
135
+
136
+ describe('+ key (select all)', () => {
137
+ it('Should select all entries', () => {
138
+ const testEntries = createTestEntries()
139
+ using(new CollectionService<TestEntry>({}), (service) => {
140
+ service.data.setValue({ count: 3, entries: testEntries })
141
+ service.hasFocus.setValue(true)
142
+
143
+ service.handleKeyDown(createKeyboardEvent('+'))
144
+
145
+ expect(service.selection.getValue()).toEqual(testEntries)
146
+ })
147
+ })
148
+ })
149
+
150
+ describe('- key (deselect all)', () => {
151
+ it('Should deselect all entries', () => {
152
+ const testEntries = createTestEntries()
153
+ using(new CollectionService<TestEntry>({}), (service) => {
154
+ service.data.setValue({ count: 3, entries: testEntries })
155
+ service.hasFocus.setValue(true)
156
+ service.selection.setValue([testEntries[0], testEntries[1]])
157
+
158
+ service.handleKeyDown(createKeyboardEvent('-'))
159
+
160
+ expect(service.selection.getValue()).toEqual([])
161
+ })
162
+ })
163
+ })
164
+
165
+ describe('Insert key', () => {
166
+ it('Should toggle selection and move focus to next entry', () => {
167
+ const testEntries = createTestEntries()
168
+ using(new CollectionService<TestEntry>({}), (service) => {
169
+ service.data.setValue({ count: 3, entries: testEntries })
170
+ service.hasFocus.setValue(true)
171
+ service.focusedEntry.setValue(testEntries[0])
172
+
173
+ service.handleKeyDown(createKeyboardEvent('Insert'))
174
+
175
+ expect(service.selection.getValue()).toEqual([testEntries[0]])
176
+ expect(service.focusedEntry.getValue()).toBe(testEntries[1])
177
+ })
178
+ })
179
+
180
+ it('Should deselect if already selected and move focus', () => {
181
+ const testEntries = createTestEntries()
182
+ using(new CollectionService<TestEntry>({}), (service) => {
183
+ service.data.setValue({ count: 3, entries: testEntries })
184
+ service.hasFocus.setValue(true)
185
+ service.focusedEntry.setValue(testEntries[1])
186
+ service.selection.setValue([testEntries[1]])
187
+
188
+ service.handleKeyDown(createKeyboardEvent('Insert'))
189
+
190
+ expect(service.selection.getValue()).toEqual([])
191
+ expect(service.focusedEntry.getValue()).toBe(testEntries[2])
192
+ })
193
+ })
194
+
195
+ it('Should do nothing when no entry is focused', () => {
196
+ const testEntries = createTestEntries()
197
+ using(new CollectionService<TestEntry>({}), (service) => {
198
+ service.data.setValue({ count: 3, entries: testEntries })
199
+ service.hasFocus.setValue(true)
200
+ service.focusedEntry.setValue(undefined)
201
+
202
+ service.handleKeyDown(createKeyboardEvent('Insert'))
203
+
204
+ expect(service.selection.getValue()).toEqual([])
205
+ })
206
+ })
207
+ })
208
+
209
+ describe('ArrowUp key', () => {
210
+ it('Should move focus to previous entry', () => {
211
+ const testEntries = createTestEntries()
212
+ using(new CollectionService<TestEntry>({}), (service) => {
213
+ service.data.setValue({ count: 3, entries: testEntries })
214
+ service.hasFocus.setValue(true)
215
+ service.focusedEntry.setValue(testEntries[2])
216
+
217
+ const ev = createKeyboardEvent('ArrowUp')
218
+ service.handleKeyDown(ev)
219
+
220
+ expect(ev.preventDefault).toHaveBeenCalled()
221
+ expect(service.focusedEntry.getValue()).toBe(testEntries[1])
222
+ })
223
+ })
224
+
225
+ it('Should not go below index 0', () => {
226
+ const testEntries = createTestEntries()
227
+ using(new CollectionService<TestEntry>({}), (service) => {
228
+ service.data.setValue({ count: 3, entries: testEntries })
229
+ service.hasFocus.setValue(true)
230
+ service.focusedEntry.setValue(testEntries[0])
231
+
232
+ service.handleKeyDown(createKeyboardEvent('ArrowUp'))
233
+
234
+ expect(service.focusedEntry.getValue()).toBe(testEntries[0])
235
+ })
236
+ })
237
+ })
238
+
239
+ describe('ArrowDown key', () => {
240
+ it('Should move focus to next entry', () => {
241
+ const testEntries = createTestEntries()
242
+ using(new CollectionService<TestEntry>({}), (service) => {
243
+ service.data.setValue({ count: 3, entries: testEntries })
244
+ service.hasFocus.setValue(true)
245
+ service.focusedEntry.setValue(testEntries[0])
246
+
247
+ const ev = createKeyboardEvent('ArrowDown')
248
+ service.handleKeyDown(ev)
249
+
250
+ expect(ev.preventDefault).toHaveBeenCalled()
251
+ expect(service.focusedEntry.getValue()).toBe(testEntries[1])
252
+ })
253
+ })
254
+
255
+ it('Should not exceed the last entry', () => {
256
+ const testEntries = createTestEntries()
257
+ using(new CollectionService<TestEntry>({}), (service) => {
258
+ service.data.setValue({ count: 3, entries: testEntries })
259
+ service.hasFocus.setValue(true)
260
+ service.focusedEntry.setValue(testEntries[2])
261
+
262
+ service.handleKeyDown(createKeyboardEvent('ArrowDown'))
263
+
264
+ expect(service.focusedEntry.getValue()).toBe(testEntries[2])
265
+ })
266
+ })
267
+ })
268
+
269
+ describe('Home key', () => {
270
+ it('Should focus the first entry', () => {
271
+ const testEntries = createTestEntries()
272
+ using(new CollectionService<TestEntry>({}), (service) => {
273
+ service.data.setValue({ count: 3, entries: testEntries })
274
+ service.hasFocus.setValue(true)
275
+ service.focusedEntry.setValue(testEntries[2])
276
+
277
+ service.handleKeyDown(createKeyboardEvent('Home'))
278
+
279
+ expect(service.focusedEntry.getValue()).toBe(testEntries[0])
280
+ })
281
+ })
282
+ })
283
+
284
+ describe('End key', () => {
285
+ it('Should focus the last entry', () => {
286
+ const testEntries = createTestEntries()
287
+ using(new CollectionService<TestEntry>({}), (service) => {
288
+ service.data.setValue({ count: 3, entries: testEntries })
289
+ service.hasFocus.setValue(true)
290
+ service.focusedEntry.setValue(testEntries[0])
291
+
292
+ service.handleKeyDown(createKeyboardEvent('End'))
293
+
294
+ expect(service.focusedEntry.getValue()).toBe(testEntries[2])
295
+ })
296
+ })
297
+ })
298
+
299
+ describe('Tab key', () => {
300
+ it('Should toggle hasFocus', () => {
301
+ const testEntries = createTestEntries()
302
+ using(new CollectionService<TestEntry>({}), (service) => {
303
+ service.data.setValue({ count: 3, entries: testEntries })
304
+ service.hasFocus.setValue(true)
305
+
306
+ service.handleKeyDown(createKeyboardEvent('Tab'))
307
+
308
+ expect(service.hasFocus.getValue()).toBe(false)
309
+ })
310
+ })
311
+ })
312
+
313
+ describe('Escape key', () => {
314
+ it('Should clear search term and selection', () => {
315
+ const testEntries = createTestEntries()
316
+ using(new CollectionService<TestEntry>({}), (service) => {
317
+ service.data.setValue({ count: 3, entries: testEntries })
318
+ service.hasFocus.setValue(true)
319
+ service.selection.setValue([testEntries[0], testEntries[1]])
320
+ service.searchTerm.setValue('test')
321
+
322
+ service.handleKeyDown(createKeyboardEvent('Escape'))
323
+
324
+ expect(service.searchTerm.getValue()).toBe('')
325
+ expect(service.selection.getValue()).toEqual([])
326
+ })
327
+ })
328
+ })
329
+
330
+ describe('Character search', () => {
331
+ it('Should search by character when searchField is configured', () => {
332
+ const testEntries = createTestEntries()
333
+ using(new CollectionService<TestEntry>({ searchField: 'name' }), (service) => {
334
+ service.data.setValue({ count: 3, entries: testEntries })
335
+ service.hasFocus.setValue(true)
336
+
337
+ service.handleKeyDown(createKeyboardEvent('b'))
338
+
339
+ expect(service.searchTerm.getValue()).toBe('b')
340
+ expect(service.focusedEntry.getValue()).toBe(testEntries[1]) // 'beta'
341
+ })
342
+ })
343
+
344
+ it('Should accumulate search characters', () => {
345
+ const testEntries = createTestEntries()
346
+ using(new CollectionService<TestEntry>({ searchField: 'name' }), (service) => {
347
+ service.data.setValue({ count: 3, entries: testEntries })
348
+ service.hasFocus.setValue(true)
349
+
350
+ service.handleKeyDown(createKeyboardEvent('a'))
351
+ expect(service.focusedEntry.getValue()).toBe(testEntries[0]) // 'alpha'
352
+
353
+ service.handleKeyDown(createKeyboardEvent('l'))
354
+ expect(service.searchTerm.getValue()).toBe('al')
355
+ expect(service.focusedEntry.getValue()).toBe(testEntries[0]) // still 'alpha'
356
+ })
357
+ })
358
+
359
+ it('Should not search when searchField is not configured', () => {
360
+ const testEntries = createTestEntries()
361
+ using(new CollectionService<TestEntry>({}), (service) => {
362
+ service.data.setValue({ count: 3, entries: testEntries })
363
+ service.hasFocus.setValue(true)
364
+
365
+ service.handleKeyDown(createKeyboardEvent('b'))
366
+
367
+ expect(service.searchTerm.getValue()).toBe('')
368
+ expect(service.focusedEntry.getValue()).toBeUndefined()
369
+ })
370
+ })
371
+
372
+ it('Should set focusedEntry to undefined when no match found', () => {
373
+ const testEntries = createTestEntries()
374
+ using(new CollectionService<TestEntry>({ searchField: 'name' }), (service) => {
375
+ service.data.setValue({ count: 3, entries: testEntries })
376
+ service.hasFocus.setValue(true)
377
+ service.focusedEntry.setValue(testEntries[0])
378
+
379
+ service.handleKeyDown(createKeyboardEvent('z'))
380
+
381
+ expect(service.searchTerm.getValue()).toBe('z')
382
+ expect(service.focusedEntry.getValue()).toBeUndefined()
383
+ })
384
+ })
385
+
386
+ it('Should ignore multi-character keys', () => {
387
+ const testEntries = createTestEntries()
388
+ using(new CollectionService<TestEntry>({ searchField: 'name' }), (service) => {
389
+ service.data.setValue({ count: 3, entries: testEntries })
390
+ service.hasFocus.setValue(true)
391
+
392
+ service.handleKeyDown(createKeyboardEvent('Shift'))
393
+
394
+ expect(service.searchTerm.getValue()).toBe('')
395
+ })
396
+ })
397
+ })
398
+ })
399
+
400
+ describe('handleRowClick', () => {
401
+ it('Should call onRowClick callback', () => {
402
+ const testEntries = createTestEntries()
403
+ const onRowClick = vi.fn()
404
+ using(new CollectionService<TestEntry>({ onRowClick }), (service) => {
405
+ service.data.setValue({ count: 3, entries: testEntries })
406
+
407
+ service.handleRowClick(testEntries[0], createMouseEvent())
408
+
409
+ expect(onRowClick).toHaveBeenCalledWith(testEntries[0])
410
+ })
411
+ })
412
+
413
+ it('Should update focusedEntry on click', () => {
414
+ const testEntries = createTestEntries()
415
+ using(new CollectionService<TestEntry>({}), (service) => {
416
+ service.data.setValue({ count: 3, entries: testEntries })
417
+
418
+ service.handleRowClick(testEntries[1], createMouseEvent())
419
+
420
+ expect(service.focusedEntry.getValue()).toBe(testEntries[1])
421
+ })
422
+ })
423
+
424
+ describe('Ctrl+click', () => {
425
+ it('Should add entry to selection with Ctrl+click', () => {
426
+ const testEntries = createTestEntries()
427
+ using(new CollectionService<TestEntry>({}), (service) => {
428
+ service.data.setValue({ count: 3, entries: testEntries })
429
+ service.selection.setValue([testEntries[0]])
430
+
431
+ service.handleRowClick(testEntries[1], createMouseEvent({ ctrlKey: true }))
432
+
433
+ expect(service.selection.getValue()).toEqual([testEntries[0], testEntries[1]])
434
+ })
435
+ })
436
+
437
+ it('Should remove entry from selection with Ctrl+click if already selected', () => {
438
+ const testEntries = createTestEntries()
439
+ using(new CollectionService<TestEntry>({}), (service) => {
440
+ service.data.setValue({ count: 3, entries: testEntries })
441
+ service.selection.setValue([testEntries[0], testEntries[1]])
442
+
443
+ service.handleRowClick(testEntries[0], createMouseEvent({ ctrlKey: true }))
444
+
445
+ expect(service.selection.getValue()).toEqual([testEntries[1]])
446
+ })
447
+ })
448
+ })
449
+
450
+ describe('Shift+click', () => {
451
+ it('Should select range from last focused to clicked entry (forward)', () => {
452
+ const testEntries = createTestEntries()
453
+ using(new CollectionService<TestEntry>({}), (service) => {
454
+ service.data.setValue({ count: 3, entries: testEntries })
455
+ service.focusedEntry.setValue(testEntries[0])
456
+
457
+ service.handleRowClick(testEntries[2], createMouseEvent({ shiftKey: true }))
458
+
459
+ expect(service.selection.getValue()).toContain(testEntries[0])
460
+ expect(service.selection.getValue()).toContain(testEntries[1])
461
+ expect(service.selection.getValue()).toContain(testEntries[2])
462
+ })
463
+ })
464
+
465
+ it('Should select range from last focused to clicked entry (backward)', () => {
466
+ const testEntries = createTestEntries()
467
+ using(new CollectionService<TestEntry>({}), (service) => {
468
+ service.data.setValue({ count: 3, entries: testEntries })
469
+ service.focusedEntry.setValue(testEntries[2])
470
+
471
+ service.handleRowClick(testEntries[0], createMouseEvent({ shiftKey: true }))
472
+
473
+ expect(service.selection.getValue()).toContain(testEntries[0])
474
+ expect(service.selection.getValue()).toContain(testEntries[1])
475
+ expect(service.selection.getValue()).toContain(testEntries[2])
476
+ })
477
+ })
478
+
479
+ it('Should append to existing selection with Shift+click', () => {
480
+ const testEntries = createTestEntries()
481
+ const extraEntry = { foo: 4, name: 'delta' }
482
+ const allEntries = [...testEntries, extraEntry]
483
+ using(new CollectionService<TestEntry>({}), (service) => {
484
+ service.data.setValue({ count: 4, entries: allEntries })
485
+ service.selection.setValue([extraEntry])
486
+ service.focusedEntry.setValue(testEntries[0])
487
+
488
+ service.handleRowClick(testEntries[1], createMouseEvent({ shiftKey: true }))
489
+
490
+ expect(service.selection.getValue()).toContain(extraEntry)
491
+ expect(service.selection.getValue()).toContain(testEntries[0])
492
+ expect(service.selection.getValue()).toContain(testEntries[1])
493
+ })
494
+ })
495
+ })
496
+ })
497
+
498
+ describe('handleRowDoubleClick', () => {
499
+ it('Should call onRowDoubleClick callback', () => {
500
+ const testEntries = createTestEntries()
501
+ const onRowDoubleClick = vi.fn()
502
+ using(new CollectionService<TestEntry>({ onRowDoubleClick }), (service) => {
503
+ service.data.setValue({ count: 3, entries: testEntries })
504
+
505
+ service.handleRowDoubleClick(testEntries[0])
506
+
507
+ expect(onRowDoubleClick).toHaveBeenCalledWith(testEntries[0])
508
+ })
509
+ })
510
+
511
+ it('Should not throw when onRowDoubleClick is not configured', () => {
512
+ const testEntries = createTestEntries()
513
+ using(new CollectionService<TestEntry>({}), (service) => {
514
+ service.data.setValue({ count: 3, entries: testEntries })
515
+
516
+ expect(() => service.handleRowDoubleClick(testEntries[0])).not.toThrow()
517
+ })
518
+ })
519
+ })
31
520
  })