@addev-be/ui 2.7.3 → 2.7.5

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 (238) hide show
  1. package/assets/icons/arrow-down-1-9.svg +1 -1
  2. package/assets/icons/arrow-down-a-z.svg +1 -1
  3. package/assets/icons/arrow-up-z-a.svg +1 -1
  4. package/assets/icons/check.svg +1 -1
  5. package/assets/icons/circle-check.svg +1 -1
  6. package/assets/icons/down.svg +1 -1
  7. package/assets/icons/filter-full.svg +1 -1
  8. package/assets/icons/filter.svg +1 -1
  9. package/assets/icons/hashtag.svg +1 -1
  10. package/assets/icons/image-slash.svg +1 -1
  11. package/assets/icons/left.svg +1 -1
  12. package/assets/icons/magnifier.svg +1 -1
  13. package/assets/icons/phone.svg +1 -1
  14. package/assets/icons/right.svg +1 -1
  15. package/assets/icons/sort-calendar-ascending.svg +5 -5
  16. package/assets/icons/spinner-third.svg +1 -1
  17. package/assets/icons/table-columns.svg +1 -1
  18. package/assets/icons/table-footer-slash.svg +4 -4
  19. package/assets/icons/table-footer.svg +3 -3
  20. package/assets/icons/up.svg +1 -1
  21. package/assets/icons/user-tie.svg +1 -1
  22. package/assets/icons/x-bar.svg +3 -3
  23. package/dist/components/data/AdvancedRequestDataGrid/helpers/advancedRequests.js +8 -9
  24. package/dist/components/data/AdvancedRequestDataGrid/helpers/columns.js +72 -21
  25. package/dist/components/data/AdvancedRequestDataGrid/index.js +2 -2
  26. package/dist/components/data/AdvancedRequestDataGrid/types.d.ts +1 -1
  27. package/dist/components/data/SqlRequestDataGrid/SqlRequestForeignListEditableCell.d.ts +1 -2
  28. package/dist/components/data/SqlRequestDataGrid/SqlRequestForeignListEditableCell.js +3 -2
  29. package/dist/services/index.js +7 -17
  30. package/eslint.config.js +3 -3
  31. package/package.json +2 -2
  32. package/src/Icons.tsx +138 -138
  33. package/src/components/auth/LoginForm.tsx +86 -86
  34. package/src/components/auth/LoginPage.tsx +32 -32
  35. package/src/components/auth/PasswordRecoveryForm.tsx +53 -53
  36. package/src/components/auth/PasswordResetForm.tsx +112 -112
  37. package/src/components/auth/styles.ts +14 -14
  38. package/src/components/data/DataGrid/DataGridCell.tsx +81 -81
  39. package/src/components/data/DataGrid/DataGridColumnsModal/helpers.ts +9 -9
  40. package/src/components/data/DataGrid/DataGridColumnsModal/hooks.tsx +59 -59
  41. package/src/components/data/DataGrid/DataGridColumnsModal/index.tsx +182 -182
  42. package/src/components/data/DataGrid/DataGridColumnsModal/styles.ts +104 -104
  43. package/src/components/data/DataGrid/DataGridEditableCell/CheckboxEditableCell.tsx +37 -37
  44. package/src/components/data/DataGrid/DataGridEditableCell/DateEditableCell.tsx +38 -38
  45. package/src/components/data/DataGrid/DataGridEditableCell/NumberEditableCell.tsx +71 -71
  46. package/src/components/data/DataGrid/DataGridEditableCell/TextEditableCell.tsx +37 -37
  47. package/src/components/data/DataGrid/DataGridEditableCell/index.tsx +106 -106
  48. package/src/components/data/DataGrid/DataGridEditableCell/styles.ts +35 -35
  49. package/src/components/data/DataGrid/DataGridEditableCell/types.ts +18 -18
  50. package/src/components/data/DataGrid/DataGridFilterMenu/FilterValuesScroller.tsx +129 -129
  51. package/src/components/data/DataGrid/DataGridFilterMenu/hooks.tsx +81 -81
  52. package/src/components/data/DataGrid/DataGridFilterMenu/index.tsx +370 -370
  53. package/src/components/data/DataGrid/DataGridFilterMenu/styles.ts +97 -97
  54. package/src/components/data/DataGrid/DataGridFooter.tsx +47 -47
  55. package/src/components/data/DataGrid/DataGridHeader.tsx +74 -74
  56. package/src/components/data/DataGrid/DataGridHeaderCell.tsx +112 -112
  57. package/src/components/data/DataGrid/DataGridRowTemplate.tsx +83 -83
  58. package/src/components/data/DataGrid/DataGridToolbar.tsx +134 -134
  59. package/src/components/data/DataGrid/FilterModalContent/index.tsx +137 -137
  60. package/src/components/data/DataGrid/FilterModalContent/styles.ts +22 -22
  61. package/src/components/data/DataGrid/constants.ts +6 -6
  62. package/src/components/data/DataGrid/helpers/columns.tsx +449 -449
  63. package/src/components/data/DataGrid/helpers/filters.ts +287 -287
  64. package/src/components/data/DataGrid/helpers/index.ts +2 -2
  65. package/src/components/data/DataGrid/hooks/index.ts +29 -29
  66. package/src/components/data/DataGrid/hooks/useDataGrid.tsx +383 -383
  67. package/src/components/data/DataGrid/hooks/useDataGridChangedRows.ts +97 -97
  68. package/src/components/data/DataGrid/hooks/useDataGridCopy.ts +174 -174
  69. package/src/components/data/DataGrid/hooks/useDataGridSettings.ts +48 -48
  70. package/src/components/data/DataGrid/hooks/useRefreshModal.tsx +48 -48
  71. package/src/components/data/DataGrid/index.tsx +111 -111
  72. package/src/components/data/DataGrid/styles.ts +434 -434
  73. package/src/components/data/DataGrid/types.ts +380 -380
  74. package/src/components/data/SqlRequestDataGrid/helpers/columns.tsx +526 -526
  75. package/src/components/data/SqlRequestDataGrid/helpers/index.ts +2 -2
  76. package/src/components/data/SqlRequestDataGrid/helpers/rows.ts +24 -24
  77. package/src/components/data/SqlRequestDataGrid/helpers/sqlRequests.ts +17 -17
  78. package/src/components/data/SqlRequestDataGrid/index.tsx +417 -417
  79. package/src/components/data/SqlRequestDataGrid/styles.ts +15 -15
  80. package/src/components/data/SqlRequestDataGrid/types.ts +74 -74
  81. package/src/components/data/SqlRequestForeignList/index.tsx +254 -254
  82. package/src/components/data/SqlRequestForeignList/styles.ts +43 -43
  83. package/src/components/data/SqlRequestForeignList/types.ts +32 -32
  84. package/src/components/data/SqlRequestGrid/filters/FiltersSidebar.tsx +108 -108
  85. package/src/components/data/SqlRequestGrid/filters/styles.ts +88 -88
  86. package/src/components/data/SqlRequestGrid/helpers/index.ts +1 -1
  87. package/src/components/data/SqlRequestGrid/helpers/sqlRequests.ts +16 -16
  88. package/src/components/data/SqlRequestGrid/index.tsx +304 -304
  89. package/src/components/data/SqlRequestGrid/styles.ts +20 -20
  90. package/src/components/data/SqlRequestGrid/types.ts +73 -73
  91. package/src/components/data/VirtualScroller/hooks.ts +71 -71
  92. package/src/components/data/VirtualScroller/index.tsx +89 -89
  93. package/src/components/data/VirtualScroller/styles.ts +57 -57
  94. package/src/components/data/VirtualScroller/types.ts +10 -10
  95. package/src/components/forms/AutoTextArea.tsx +48 -48
  96. package/src/components/forms/BillitIdentifier/index.tsx +78 -78
  97. package/src/components/forms/BillitIdentifier/styles.tsx +43 -43
  98. package/src/components/forms/Button.tsx +132 -132
  99. package/src/components/forms/Form/Checkbox.tsx +12 -12
  100. package/src/components/forms/Form/CustomSelect.tsx +86 -86
  101. package/src/components/forms/Form/FormGroup.tsx +33 -33
  102. package/src/components/forms/Form/Input.tsx +16 -16
  103. package/src/components/forms/Form/Row.tsx +28 -28
  104. package/src/components/forms/Form/Select.tsx +99 -99
  105. package/src/components/forms/Form/TextArea.tsx +17 -17
  106. package/src/components/forms/Form/index.tsx +48 -48
  107. package/src/components/forms/Form/styles.ts +148 -148
  108. package/src/components/forms/IconButton.tsx +61 -61
  109. package/src/components/forms/IndeterminateCheckbox.tsx +46 -46
  110. package/src/components/forms/NumberInput.tsx +53 -53
  111. package/src/components/forms/Select.tsx +34 -34
  112. package/src/components/forms/VerticalLabel.tsx +20 -20
  113. package/src/components/forms/styles.ts +42 -42
  114. package/src/components/layout/Columns.ts +28 -28
  115. package/src/components/layout/Dropdown/index.tsx +113 -113
  116. package/src/components/layout/Dropdown/styles.ts +53 -53
  117. package/src/components/layout/Flexbox.ts +21 -21
  118. package/src/components/layout/Grid/index.tsx +8 -8
  119. package/src/components/layout/Grid/styles.ts +34 -34
  120. package/src/components/layout/Loading/index.tsx +29 -29
  121. package/src/components/layout/Loading/styles.ts +29 -29
  122. package/src/components/layout/Masonry/index.tsx +29 -29
  123. package/src/components/layout/Masonry/styles.ts +20 -20
  124. package/src/components/layout/Modal/index.tsx +51 -51
  125. package/src/components/layout/Modal/styles.ts +125 -125
  126. package/src/components/search/HighlightedText.tsx +41 -41
  127. package/src/components/search/QuickSearchBar.tsx +102 -102
  128. package/src/components/search/QuickSearchResults.tsx +86 -86
  129. package/src/components/search/styles.ts +96 -96
  130. package/src/components/search/types.ts +29 -29
  131. package/src/components/ui/Avatar/index.tsx +54 -54
  132. package/src/components/ui/Card/index.tsx +16 -16
  133. package/src/components/ui/Card/styles.ts +41 -41
  134. package/src/components/ui/ContextMenu/index.tsx +79 -79
  135. package/src/components/ui/ContextMenu/styles.ts +119 -119
  136. package/src/components/ui/Ellipsis.tsx +33 -33
  137. package/src/components/ui/Label.tsx +93 -93
  138. package/src/components/ui/Message/index.tsx +57 -57
  139. package/src/components/ui/Message/styles.ts +44 -44
  140. package/src/components/ui/TabsView/TabsList.tsx +49 -49
  141. package/src/components/ui/TabsView/TabsView.tsx +42 -42
  142. package/src/components/ui/TabsView/styles.ts +84 -84
  143. package/src/components/ui/TabsView/types.ts +15 -15
  144. package/src/config/index.ts +10 -10
  145. package/src/helpers/components.ts +9 -9
  146. package/src/helpers/dates.ts +17 -17
  147. package/src/helpers/getScrollbarSize.ts +14 -14
  148. package/src/helpers/numbers.ts +63 -63
  149. package/src/helpers/responsive.ts +83 -83
  150. package/src/helpers/styled/space.ts +114 -114
  151. package/src/helpers/styled/typography.ts +25 -25
  152. package/src/helpers/text.ts +13 -13
  153. package/src/helpers/types.ts +9 -9
  154. package/src/hooks/useContainerMediaQuery.ts +7 -7
  155. package/src/hooks/useElementSize.ts +24 -24
  156. package/src/hooks/useMediaQuery.ts +9 -9
  157. package/src/hooks/useMediaQueryForWidth.ts +35 -35
  158. package/src/hooks/useMutableState.test.ts +410 -410
  159. package/src/hooks/useMutableState.ts +39 -39
  160. package/src/hooks/useShowArchived.ts +28 -28
  161. package/src/hooks/useWindowSize.ts +20 -20
  162. package/src/index.ts +103 -103
  163. package/src/providers/AuthenticationProvider/helpers.ts +3 -3
  164. package/src/providers/AuthenticationProvider/index.tsx +303 -303
  165. package/src/providers/LoadingProvider/index.tsx +47 -47
  166. package/src/providers/PortalsProvider/index.tsx +54 -54
  167. package/src/providers/PortalsProvider/styles.ts +31 -31
  168. package/src/providers/SettingsProvider/index.tsx +70 -70
  169. package/src/providers/ThemeProvider/defaultTheme.ts +471 -471
  170. package/src/providers/ThemeProvider/helpers.ts +84 -84
  171. package/src/providers/ThemeProvider/index.ts +73 -73
  172. package/src/providers/ThemeProvider/types.ts +134 -134
  173. package/src/providers/ToastProvider/index.tsx +93 -93
  174. package/src/providers/TrackingProvider/index.tsx +71 -71
  175. package/src/providers/UiProviders/index.tsx +76 -76
  176. package/src/providers/UiProviders/styles.ts +10 -10
  177. package/src/providers/hooks.ts +14 -14
  178. package/src/services/HttpService.ts +92 -92
  179. package/src/services/WebSocketService.ts +155 -155
  180. package/src/services/advancedRequests.ts +102 -102
  181. package/src/services/base.ts +23 -23
  182. package/src/services/globalSearch.ts +32 -32
  183. package/src/services/hooks.ts +92 -92
  184. package/src/services/requests/auth.ts +44 -44
  185. package/src/services/requests/generic.ts +62 -62
  186. package/src/services/requests/printing.ts +12 -12
  187. package/src/services/requests/tracking.ts +12 -12
  188. package/src/services/requests/userProfiles.ts +35 -35
  189. package/src/services/requests/users.ts +28 -28
  190. package/src/services/smartQueries.ts +122 -122
  191. package/src/services/sqlRequests.ts +119 -119
  192. package/src/services/types/auth.ts +98 -98
  193. package/src/services/types/base.ts +10 -10
  194. package/src/services/types/generic.ts +82 -82
  195. package/src/services/types/printing.ts +10 -10
  196. package/src/services/types/tracking.ts +29 -29
  197. package/src/services/types/userProfiles.ts +79 -79
  198. package/src/services/types/users.ts +74 -74
  199. package/src/services/updateSqlRequests.ts +32 -32
  200. package/src/styles/animations.scss +30 -30
  201. package/src/styles/index.scss +42 -42
  202. package/src/types.ts +8 -8
  203. package/src/typings.d.ts +2 -2
  204. package/tsconfig.json +18 -18
  205. package/dist/components/data/DataGrid/AdvancedRequestDataGrid.d.ts +0 -10
  206. package/dist/components/data/DataGrid/AdvancedRequestDataGrid.js +0 -173
  207. package/dist/components/data/DataGrid/FilterValuesScroller.d.ts +0 -13
  208. package/dist/components/data/DataGrid/FilterValuesScroller.js +0 -73
  209. package/dist/components/data/DataGrid/VirtualScroller.d.ts +0 -11
  210. package/dist/components/data/DataGrid/VirtualScroller.js +0 -41
  211. package/dist/components/data/DataGrid/helpers/advancedRequests.d.ts +0 -12
  212. package/dist/components/data/DataGrid/helpers/advancedRequests.js +0 -53
  213. package/dist/components/data/DataGrid/helpers.d.ts +0 -28
  214. package/dist/components/data/DataGrid/helpers.js +0 -436
  215. package/dist/components/data/SmartQueryDataGrid/helpers/columns.d.ts +0 -20
  216. package/dist/components/data/SmartQueryDataGrid/helpers/columns.js +0 -160
  217. package/dist/components/data/SmartQueryDataGrid/helpers/hooks.d.ts +0 -5
  218. package/dist/components/data/SmartQueryDataGrid/helpers/hooks.js +0 -41
  219. package/dist/components/data/SmartQueryDataGrid/helpers/index.d.ts +0 -2
  220. package/dist/components/data/SmartQueryDataGrid/helpers/index.js +0 -18
  221. package/dist/components/data/SmartQueryDataGrid/helpers/smartQueries.d.ts +0 -3
  222. package/dist/components/data/SmartQueryDataGrid/helpers/smartQueries.js +0 -15
  223. package/dist/components/data/SmartQueryDataGrid/hooks.d.ts +0 -9
  224. package/dist/components/data/SmartQueryDataGrid/hooks.js +0 -63
  225. package/dist/components/data/SmartQueryDataGrid/index.d.ts +0 -12
  226. package/dist/components/data/SmartQueryDataGrid/index.js +0 -242
  227. package/dist/components/data/SmartQueryDataGrid/types.d.ts +0 -22
  228. package/dist/components/data/SmartQueryDataGrid/types.js +0 -2
  229. package/dist/components/forms/Form/InputWithLabel.d.ts +0 -2
  230. package/dist/components/forms/Form/InputWithLabel.js +0 -10
  231. package/dist/components/forms/Form/types.d.ts +0 -7
  232. package/dist/components/forms/Form/types.js +0 -2
  233. package/dist/config/types.d.ts +0 -11
  234. package/dist/config/types.js +0 -2
  235. package/dist/helpers/react.d.ts +0 -2
  236. package/dist/helpers/react.js +0 -8
  237. package/dist/services/requests/userPermissions.d.ts +0 -4
  238. package/dist/services/requests/userPermissions.js +0 -20
@@ -1,410 +1,410 @@
1
- /**
2
- * @vitest-environment jsdom
3
- */
4
-
5
- import { act, renderHook } from '@testing-library/react';
6
- import { describe, expect, it } from 'vitest';
7
- import { isDispatchFunction, useMutableState } from './useMutableState';
8
-
9
- describe('isDispatchFunction', () => {
10
- it('should return true for functions', () => {
11
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
- const fn = (state: any) => state;
13
- expect(isDispatchFunction(fn)).toBe(true);
14
- });
15
-
16
- it('should return false for objects', () => {
17
- expect(isDispatchFunction({ key: 'value' })).toBe(false);
18
- });
19
-
20
- it('should return false for primitives', () => {
21
- expect(isDispatchFunction('string')).toBe(false);
22
- expect(isDispatchFunction(123)).toBe(false);
23
- expect(isDispatchFunction(null)).toBe(false);
24
- expect(isDispatchFunction(undefined)).toBe(false);
25
- });
26
- });
27
-
28
- describe('useMutableState', () => {
29
- describe('initialization', () => {
30
- it('should initialize with default value', () => {
31
- const { result } = renderHook(() =>
32
- useMutableState({ name: 'John', age: 30 })
33
- );
34
- const [state] = result.current;
35
-
36
- expect(state).toEqual({ name: 'John', age: 30 });
37
- });
38
-
39
- it('should initialize with empty object when no value is provided', () => {
40
- const { result } = renderHook(() => useMutableState());
41
- const [state] = result.current;
42
-
43
- expect(state).toEqual({});
44
- });
45
-
46
- it('should initialize with function result', () => {
47
- const { result } = renderHook(() =>
48
- useMutableState(() => ({ count: 0, items: [] }))
49
- );
50
- const [state] = result.current;
51
-
52
- expect(state).toEqual({ count: 0, items: [] });
53
- });
54
-
55
- it('should return tuple with three elements', () => {
56
- const { result } = renderHook(() => useMutableState({ value: 1 }));
57
-
58
- expect(result.current).toHaveLength(3);
59
- expect(typeof result.current[0]).toBe('object');
60
- expect(typeof result.current[1]).toBe('function');
61
- expect(typeof result.current[2]).toBe('function');
62
- });
63
- });
64
-
65
- describe('setValue (full state update)', () => {
66
- it('should update entire state with new value', () => {
67
- const { result } = renderHook(() =>
68
- useMutableState({ name: 'John', age: 30 })
69
- );
70
-
71
- act(() => {
72
- const [, setValue] = result.current;
73
- setValue({ name: 'Jane', age: 25 });
74
- });
75
-
76
- expect(result.current[0]).toEqual({ name: 'Jane', age: 25 });
77
- });
78
-
79
- it('should update state using function', () => {
80
- const { result } = renderHook(() => useMutableState({ count: 0 }));
81
-
82
- act(() => {
83
- const [, setValue] = result.current;
84
- setValue((prev) => ({ count: prev.count + 1 }));
85
- });
86
-
87
- expect(result.current[0]).toEqual({ count: 1 });
88
- });
89
-
90
- it('should replace entire state object', () => {
91
- const { result } = renderHook(() =>
92
- useMutableState({ name: 'John', age: 30, city: 'Paris' })
93
- );
94
-
95
- act(() => {
96
- const [, setValue] = result.current;
97
- setValue({ name: 'Jane', age: 22, city: 'London' });
98
- });
99
-
100
- expect(result.current[0]).toEqual({
101
- name: 'Jane',
102
- age: 22,
103
- city: 'London',
104
- });
105
- });
106
- });
107
-
108
- describe('setPartialValue (partial state update)', () => {
109
- it('should merge partial object with existing state', () => {
110
- const { result } = renderHook(() =>
111
- useMutableState({ name: 'John', age: 30, city: 'Paris' })
112
- );
113
-
114
- act(() => {
115
- const [, , setPartialValue] = result.current;
116
- setPartialValue({ age: 31 });
117
- });
118
-
119
- expect(result.current[0]).toEqual({
120
- name: 'John',
121
- age: 31,
122
- city: 'Paris',
123
- });
124
- });
125
-
126
- it('should merge partial object using function', () => {
127
- const { result } = renderHook(() =>
128
- useMutableState({ count: 0, total: 100 })
129
- );
130
-
131
- act(() => {
132
- const [, , setPartialValue] = result.current;
133
- setPartialValue((state) => ({ count: state.count + 5 }));
134
- });
135
-
136
- expect(result.current[0]).toEqual({ count: 5, total: 100 });
137
- });
138
-
139
- it('should handle deep nested objects', () => {
140
- const { result } = renderHook(() =>
141
- useMutableState({
142
- user: {
143
- name: 'John',
144
- address: {
145
- city: 'Paris',
146
- country: 'France',
147
- },
148
- },
149
- settings: {
150
- theme: 'dark',
151
- },
152
- })
153
- );
154
-
155
- act(() => {
156
- const [, , setPartialValue] = result.current;
157
- setPartialValue({
158
- user: {
159
- address: {
160
- city: 'Lyon',
161
- },
162
- },
163
- });
164
- });
165
-
166
- expect(result.current[0]).toEqual({
167
- user: {
168
- name: 'John',
169
- address: {
170
- city: 'Lyon',
171
- country: 'France',
172
- },
173
- },
174
- settings: {
175
- theme: 'dark',
176
- },
177
- });
178
- });
179
-
180
- it('should add new properties to state', () => {
181
- const { result } = renderHook(() =>
182
- useMutableState<{ name: string; age?: number }>({ name: 'John' })
183
- );
184
-
185
- act(() => {
186
- const [, , setPartialValue] = result.current;
187
- setPartialValue({ age: 30 });
188
- });
189
-
190
- expect(result.current[0]).toEqual({ name: 'John', age: 30 });
191
- });
192
-
193
- it('should handle multiple consecutive updates', () => {
194
- const { result } = renderHook(() =>
195
- useMutableState({ count: 0, value: 10 })
196
- );
197
-
198
- act(() => {
199
- const [, , setPartialValue] = result.current;
200
- setPartialValue({ count: 1 });
201
- setPartialValue({ value: 20 });
202
- setPartialValue({ count: 2 });
203
- });
204
-
205
- expect(result.current[0]).toEqual({ count: 2, value: 20 });
206
- });
207
-
208
- it('should handle empty partial object', () => {
209
- const { result } = renderHook(() =>
210
- useMutableState({ name: 'John', age: 30 })
211
- );
212
-
213
- act(() => {
214
- const [, , setPartialValue] = result.current;
215
- setPartialValue({});
216
- });
217
-
218
- expect(result.current[0]).toEqual({ name: 'John', age: 30 });
219
- });
220
-
221
- it('should preserve arrays correctly', () => {
222
- const { result } = renderHook(() =>
223
- useMutableState({ items: [1, 2, 3], count: 0 })
224
- );
225
-
226
- act(() => {
227
- const [, , setPartialValue] = result.current;
228
- setPartialValue({ count: 5 });
229
- });
230
-
231
- expect(result.current[0]).toEqual({ items: [1, 2, 3], count: 5 });
232
- });
233
-
234
- it('should update arrays when provided', () => {
235
- const { result } = renderHook(() =>
236
- useMutableState({ items: [1, 2, 3], count: 0 })
237
- );
238
-
239
- act(() => {
240
- const [, , setPartialValue] = result.current;
241
- setPartialValue({ items: [4, 5, 6] });
242
- });
243
-
244
- expect(result.current[0]).toEqual({ items: [4, 5, 6], count: 0 });
245
- });
246
- });
247
-
248
- describe('referential stability', () => {
249
- it('should maintain stable setPartialValue reference', () => {
250
- const { result, rerender } = renderHook(() =>
251
- useMutableState({ count: 0 })
252
- );
253
-
254
- const [, , firstSetPartial] = result.current;
255
-
256
- act(() => {
257
- const [, setValue] = result.current;
258
- setValue({ count: 1 });
259
- });
260
-
261
- rerender();
262
-
263
- const [, , secondSetPartial] = result.current;
264
-
265
- expect(firstSetPartial).toBe(secondSetPartial);
266
- });
267
-
268
- it('should trigger new reference when state changes', () => {
269
- const { result, rerender } = renderHook(() =>
270
- useMutableState({ count: 0 })
271
- );
272
-
273
- const [firstState] = result.current;
274
-
275
- act(() => {
276
- const [, , setPartialValue] = result.current;
277
- setPartialValue({ count: 1 });
278
- });
279
-
280
- rerender();
281
-
282
- const [secondState] = result.current;
283
-
284
- expect(firstState).not.toBe(secondState);
285
- });
286
- });
287
-
288
- describe('complex types', () => {
289
- interface ComplexState {
290
- id: number;
291
- user: {
292
- name: string;
293
- email: string;
294
- preferences: {
295
- notifications: boolean;
296
- theme: 'light' | 'dark';
297
- };
298
- };
299
- items: string[];
300
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
301
- metadata?: Record<string, any>;
302
- }
303
-
304
- it('should handle complex nested types', () => {
305
- const initialState: ComplexState = {
306
- id: 1,
307
- user: {
308
- name: 'John',
309
- email: 'john@example.com',
310
- preferences: {
311
- notifications: true,
312
- theme: 'dark',
313
- },
314
- },
315
- items: ['item1', 'item2'],
316
- };
317
-
318
- const { result } = renderHook(() => useMutableState(initialState));
319
-
320
- act(() => {
321
- const [, , setPartialValue] = result.current;
322
- setPartialValue({
323
- user: {
324
- preferences: {
325
- theme: 'light',
326
- },
327
- },
328
- metadata: { lastUpdated: Date.now() },
329
- });
330
- });
331
-
332
- const [state] = result.current;
333
-
334
- expect(state.user.name).toBe('John');
335
- expect(state.user.email).toBe('john@example.com');
336
- expect(state.user.preferences.notifications).toBe(true);
337
- expect(state.user.preferences.theme).toBe('light');
338
- expect(state.metadata).toBeDefined();
339
- expect(state.items).toEqual(['item1', 'item2']);
340
- });
341
- });
342
-
343
- describe('edge cases', () => {
344
- it('should handle null values', () => {
345
- const { result } = renderHook(() =>
346
- useMutableState<{ value: string | null }>({ value: 'test' })
347
- );
348
-
349
- act(() => {
350
- const [, , setPartialValue] = result.current;
351
- setPartialValue({ value: null });
352
- });
353
-
354
- expect(result.current[0]).toEqual({ value: null });
355
- });
356
-
357
- it('should handle undefined values', () => {
358
- const { result } = renderHook(() =>
359
- useMutableState<{ value?: string }>({ value: 'test' })
360
- );
361
-
362
- act(() => {
363
- const [, , setPartialValue] = result.current;
364
- setPartialValue({ value: undefined });
365
- });
366
-
367
- // defaultsDeep doesn't override with undefined, so original value remains
368
- expect(result.current[0].value).toBe('test');
369
- });
370
-
371
- it('should handle state with boolean false', () => {
372
- const { result } = renderHook(() =>
373
- useMutableState({ isActive: true, isEnabled: true })
374
- );
375
-
376
- act(() => {
377
- const [, , setPartialValue] = result.current;
378
- setPartialValue({ isActive: false });
379
- });
380
-
381
- expect(result.current[0]).toEqual({ isActive: false, isEnabled: true });
382
- });
383
-
384
- it('should handle state with number zero', () => {
385
- const { result } = renderHook(() =>
386
- useMutableState({ count: 10, total: 100 })
387
- );
388
-
389
- act(() => {
390
- const [, , setPartialValue] = result.current;
391
- setPartialValue({ count: 0 });
392
- });
393
-
394
- expect(result.current[0]).toEqual({ count: 0, total: 100 });
395
- });
396
-
397
- it('should handle state with empty string', () => {
398
- const { result } = renderHook(() =>
399
- useMutableState({ name: 'John', description: 'Test' })
400
- );
401
-
402
- act(() => {
403
- const [, , setPartialValue] = result.current;
404
- setPartialValue({ name: '' });
405
- });
406
-
407
- expect(result.current[0]).toEqual({ name: '', description: 'Test' });
408
- });
409
- });
410
- });
1
+ /**
2
+ * @vitest-environment jsdom
3
+ */
4
+
5
+ import { act, renderHook } from '@testing-library/react';
6
+ import { describe, expect, it } from 'vitest';
7
+ import { isDispatchFunction, useMutableState } from './useMutableState';
8
+
9
+ describe('isDispatchFunction', () => {
10
+ it('should return true for functions', () => {
11
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
+ const fn = (state: any) => state;
13
+ expect(isDispatchFunction(fn)).toBe(true);
14
+ });
15
+
16
+ it('should return false for objects', () => {
17
+ expect(isDispatchFunction({ key: 'value' })).toBe(false);
18
+ });
19
+
20
+ it('should return false for primitives', () => {
21
+ expect(isDispatchFunction('string')).toBe(false);
22
+ expect(isDispatchFunction(123)).toBe(false);
23
+ expect(isDispatchFunction(null)).toBe(false);
24
+ expect(isDispatchFunction(undefined)).toBe(false);
25
+ });
26
+ });
27
+
28
+ describe('useMutableState', () => {
29
+ describe('initialization', () => {
30
+ it('should initialize with default value', () => {
31
+ const { result } = renderHook(() =>
32
+ useMutableState({ name: 'John', age: 30 })
33
+ );
34
+ const [state] = result.current;
35
+
36
+ expect(state).toEqual({ name: 'John', age: 30 });
37
+ });
38
+
39
+ it('should initialize with empty object when no value is provided', () => {
40
+ const { result } = renderHook(() => useMutableState());
41
+ const [state] = result.current;
42
+
43
+ expect(state).toEqual({});
44
+ });
45
+
46
+ it('should initialize with function result', () => {
47
+ const { result } = renderHook(() =>
48
+ useMutableState(() => ({ count: 0, items: [] }))
49
+ );
50
+ const [state] = result.current;
51
+
52
+ expect(state).toEqual({ count: 0, items: [] });
53
+ });
54
+
55
+ it('should return tuple with three elements', () => {
56
+ const { result } = renderHook(() => useMutableState({ value: 1 }));
57
+
58
+ expect(result.current).toHaveLength(3);
59
+ expect(typeof result.current[0]).toBe('object');
60
+ expect(typeof result.current[1]).toBe('function');
61
+ expect(typeof result.current[2]).toBe('function');
62
+ });
63
+ });
64
+
65
+ describe('setValue (full state update)', () => {
66
+ it('should update entire state with new value', () => {
67
+ const { result } = renderHook(() =>
68
+ useMutableState({ name: 'John', age: 30 })
69
+ );
70
+
71
+ act(() => {
72
+ const [, setValue] = result.current;
73
+ setValue({ name: 'Jane', age: 25 });
74
+ });
75
+
76
+ expect(result.current[0]).toEqual({ name: 'Jane', age: 25 });
77
+ });
78
+
79
+ it('should update state using function', () => {
80
+ const { result } = renderHook(() => useMutableState({ count: 0 }));
81
+
82
+ act(() => {
83
+ const [, setValue] = result.current;
84
+ setValue((prev) => ({ count: prev.count + 1 }));
85
+ });
86
+
87
+ expect(result.current[0]).toEqual({ count: 1 });
88
+ });
89
+
90
+ it('should replace entire state object', () => {
91
+ const { result } = renderHook(() =>
92
+ useMutableState({ name: 'John', age: 30, city: 'Paris' })
93
+ );
94
+
95
+ act(() => {
96
+ const [, setValue] = result.current;
97
+ setValue({ name: 'Jane', age: 22, city: 'London' });
98
+ });
99
+
100
+ expect(result.current[0]).toEqual({
101
+ name: 'Jane',
102
+ age: 22,
103
+ city: 'London',
104
+ });
105
+ });
106
+ });
107
+
108
+ describe('setPartialValue (partial state update)', () => {
109
+ it('should merge partial object with existing state', () => {
110
+ const { result } = renderHook(() =>
111
+ useMutableState({ name: 'John', age: 30, city: 'Paris' })
112
+ );
113
+
114
+ act(() => {
115
+ const [, , setPartialValue] = result.current;
116
+ setPartialValue({ age: 31 });
117
+ });
118
+
119
+ expect(result.current[0]).toEqual({
120
+ name: 'John',
121
+ age: 31,
122
+ city: 'Paris',
123
+ });
124
+ });
125
+
126
+ it('should merge partial object using function', () => {
127
+ const { result } = renderHook(() =>
128
+ useMutableState({ count: 0, total: 100 })
129
+ );
130
+
131
+ act(() => {
132
+ const [, , setPartialValue] = result.current;
133
+ setPartialValue((state) => ({ count: state.count + 5 }));
134
+ });
135
+
136
+ expect(result.current[0]).toEqual({ count: 5, total: 100 });
137
+ });
138
+
139
+ it('should handle deep nested objects', () => {
140
+ const { result } = renderHook(() =>
141
+ useMutableState({
142
+ user: {
143
+ name: 'John',
144
+ address: {
145
+ city: 'Paris',
146
+ country: 'France',
147
+ },
148
+ },
149
+ settings: {
150
+ theme: 'dark',
151
+ },
152
+ })
153
+ );
154
+
155
+ act(() => {
156
+ const [, , setPartialValue] = result.current;
157
+ setPartialValue({
158
+ user: {
159
+ address: {
160
+ city: 'Lyon',
161
+ },
162
+ },
163
+ });
164
+ });
165
+
166
+ expect(result.current[0]).toEqual({
167
+ user: {
168
+ name: 'John',
169
+ address: {
170
+ city: 'Lyon',
171
+ country: 'France',
172
+ },
173
+ },
174
+ settings: {
175
+ theme: 'dark',
176
+ },
177
+ });
178
+ });
179
+
180
+ it('should add new properties to state', () => {
181
+ const { result } = renderHook(() =>
182
+ useMutableState<{ name: string; age?: number }>({ name: 'John' })
183
+ );
184
+
185
+ act(() => {
186
+ const [, , setPartialValue] = result.current;
187
+ setPartialValue({ age: 30 });
188
+ });
189
+
190
+ expect(result.current[0]).toEqual({ name: 'John', age: 30 });
191
+ });
192
+
193
+ it('should handle multiple consecutive updates', () => {
194
+ const { result } = renderHook(() =>
195
+ useMutableState({ count: 0, value: 10 })
196
+ );
197
+
198
+ act(() => {
199
+ const [, , setPartialValue] = result.current;
200
+ setPartialValue({ count: 1 });
201
+ setPartialValue({ value: 20 });
202
+ setPartialValue({ count: 2 });
203
+ });
204
+
205
+ expect(result.current[0]).toEqual({ count: 2, value: 20 });
206
+ });
207
+
208
+ it('should handle empty partial object', () => {
209
+ const { result } = renderHook(() =>
210
+ useMutableState({ name: 'John', age: 30 })
211
+ );
212
+
213
+ act(() => {
214
+ const [, , setPartialValue] = result.current;
215
+ setPartialValue({});
216
+ });
217
+
218
+ expect(result.current[0]).toEqual({ name: 'John', age: 30 });
219
+ });
220
+
221
+ it('should preserve arrays correctly', () => {
222
+ const { result } = renderHook(() =>
223
+ useMutableState({ items: [1, 2, 3], count: 0 })
224
+ );
225
+
226
+ act(() => {
227
+ const [, , setPartialValue] = result.current;
228
+ setPartialValue({ count: 5 });
229
+ });
230
+
231
+ expect(result.current[0]).toEqual({ items: [1, 2, 3], count: 5 });
232
+ });
233
+
234
+ it('should update arrays when provided', () => {
235
+ const { result } = renderHook(() =>
236
+ useMutableState({ items: [1, 2, 3], count: 0 })
237
+ );
238
+
239
+ act(() => {
240
+ const [, , setPartialValue] = result.current;
241
+ setPartialValue({ items: [4, 5, 6] });
242
+ });
243
+
244
+ expect(result.current[0]).toEqual({ items: [4, 5, 6], count: 0 });
245
+ });
246
+ });
247
+
248
+ describe('referential stability', () => {
249
+ it('should maintain stable setPartialValue reference', () => {
250
+ const { result, rerender } = renderHook(() =>
251
+ useMutableState({ count: 0 })
252
+ );
253
+
254
+ const [, , firstSetPartial] = result.current;
255
+
256
+ act(() => {
257
+ const [, setValue] = result.current;
258
+ setValue({ count: 1 });
259
+ });
260
+
261
+ rerender();
262
+
263
+ const [, , secondSetPartial] = result.current;
264
+
265
+ expect(firstSetPartial).toBe(secondSetPartial);
266
+ });
267
+
268
+ it('should trigger new reference when state changes', () => {
269
+ const { result, rerender } = renderHook(() =>
270
+ useMutableState({ count: 0 })
271
+ );
272
+
273
+ const [firstState] = result.current;
274
+
275
+ act(() => {
276
+ const [, , setPartialValue] = result.current;
277
+ setPartialValue({ count: 1 });
278
+ });
279
+
280
+ rerender();
281
+
282
+ const [secondState] = result.current;
283
+
284
+ expect(firstState).not.toBe(secondState);
285
+ });
286
+ });
287
+
288
+ describe('complex types', () => {
289
+ interface ComplexState {
290
+ id: number;
291
+ user: {
292
+ name: string;
293
+ email: string;
294
+ preferences: {
295
+ notifications: boolean;
296
+ theme: 'light' | 'dark';
297
+ };
298
+ };
299
+ items: string[];
300
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
301
+ metadata?: Record<string, any>;
302
+ }
303
+
304
+ it('should handle complex nested types', () => {
305
+ const initialState: ComplexState = {
306
+ id: 1,
307
+ user: {
308
+ name: 'John',
309
+ email: 'john@example.com',
310
+ preferences: {
311
+ notifications: true,
312
+ theme: 'dark',
313
+ },
314
+ },
315
+ items: ['item1', 'item2'],
316
+ };
317
+
318
+ const { result } = renderHook(() => useMutableState(initialState));
319
+
320
+ act(() => {
321
+ const [, , setPartialValue] = result.current;
322
+ setPartialValue({
323
+ user: {
324
+ preferences: {
325
+ theme: 'light',
326
+ },
327
+ },
328
+ metadata: { lastUpdated: Date.now() },
329
+ });
330
+ });
331
+
332
+ const [state] = result.current;
333
+
334
+ expect(state.user.name).toBe('John');
335
+ expect(state.user.email).toBe('john@example.com');
336
+ expect(state.user.preferences.notifications).toBe(true);
337
+ expect(state.user.preferences.theme).toBe('light');
338
+ expect(state.metadata).toBeDefined();
339
+ expect(state.items).toEqual(['item1', 'item2']);
340
+ });
341
+ });
342
+
343
+ describe('edge cases', () => {
344
+ it('should handle null values', () => {
345
+ const { result } = renderHook(() =>
346
+ useMutableState<{ value: string | null }>({ value: 'test' })
347
+ );
348
+
349
+ act(() => {
350
+ const [, , setPartialValue] = result.current;
351
+ setPartialValue({ value: null });
352
+ });
353
+
354
+ expect(result.current[0]).toEqual({ value: null });
355
+ });
356
+
357
+ it('should handle undefined values', () => {
358
+ const { result } = renderHook(() =>
359
+ useMutableState<{ value?: string }>({ value: 'test' })
360
+ );
361
+
362
+ act(() => {
363
+ const [, , setPartialValue] = result.current;
364
+ setPartialValue({ value: undefined });
365
+ });
366
+
367
+ // defaultsDeep doesn't override with undefined, so original value remains
368
+ expect(result.current[0].value).toBe('test');
369
+ });
370
+
371
+ it('should handle state with boolean false', () => {
372
+ const { result } = renderHook(() =>
373
+ useMutableState({ isActive: true, isEnabled: true })
374
+ );
375
+
376
+ act(() => {
377
+ const [, , setPartialValue] = result.current;
378
+ setPartialValue({ isActive: false });
379
+ });
380
+
381
+ expect(result.current[0]).toEqual({ isActive: false, isEnabled: true });
382
+ });
383
+
384
+ it('should handle state with number zero', () => {
385
+ const { result } = renderHook(() =>
386
+ useMutableState({ count: 10, total: 100 })
387
+ );
388
+
389
+ act(() => {
390
+ const [, , setPartialValue] = result.current;
391
+ setPartialValue({ count: 0 });
392
+ });
393
+
394
+ expect(result.current[0]).toEqual({ count: 0, total: 100 });
395
+ });
396
+
397
+ it('should handle state with empty string', () => {
398
+ const { result } = renderHook(() =>
399
+ useMutableState({ name: 'John', description: 'Test' })
400
+ );
401
+
402
+ act(() => {
403
+ const [, , setPartialValue] = result.current;
404
+ setPartialValue({ name: '' });
405
+ });
406
+
407
+ expect(result.current[0]).toEqual({ name: '', description: 'Test' });
408
+ });
409
+ });
410
+ });