@keenthemes/ktui 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/README.md +0 -27
  2. package/dist/ktui.js +6790 -14063
  3. package/dist/ktui.min.js +1 -1
  4. package/dist/ktui.min.js.map +1 -1
  5. package/dist/styles.css +1132 -2705
  6. package/lib/cjs/components/datatable/__tests__/pagination-reset.test.js +596 -0
  7. package/lib/cjs/components/datatable/__tests__/pagination-reset.test.js.map +1 -0
  8. package/lib/cjs/components/datatable/__tests__/race-conditions.test.js +548 -0
  9. package/lib/cjs/components/datatable/__tests__/race-conditions.test.js.map +1 -0
  10. package/lib/cjs/components/datatable/__tests__/setup.js +63 -0
  11. package/lib/cjs/components/datatable/__tests__/setup.js.map +1 -0
  12. package/lib/cjs/components/datatable/datatable.js +92 -30
  13. package/lib/cjs/components/datatable/datatable.js.map +1 -1
  14. package/lib/cjs/index.js +1 -10
  15. package/lib/cjs/index.js.map +1 -1
  16. package/lib/esm/components/datatable/__tests__/pagination-reset.test.js +594 -0
  17. package/lib/esm/components/datatable/__tests__/pagination-reset.test.js.map +1 -0
  18. package/lib/esm/components/datatable/__tests__/race-conditions.test.js +546 -0
  19. package/lib/esm/components/datatable/__tests__/race-conditions.test.js.map +1 -0
  20. package/lib/esm/components/datatable/__tests__/setup.js +58 -0
  21. package/lib/esm/components/datatable/__tests__/setup.js.map +1 -0
  22. package/lib/esm/components/datatable/datatable.js +92 -30
  23. package/lib/esm/components/datatable/datatable.js.map +1 -1
  24. package/lib/esm/index.js +0 -7
  25. package/lib/esm/index.js.map +1 -1
  26. package/package.json +7 -9
  27. package/src/components/alert/alert.css +188 -429
  28. package/src/components/datatable/__tests__/pagination-reset.test.ts +657 -0
  29. package/src/components/datatable/__tests__/race-conditions.test.ts +455 -0
  30. package/src/components/datatable/__tests__/setup.ts +67 -0
  31. package/src/components/datatable/datatable.ts +66 -11
  32. package/src/components/input/input.css +0 -1
  33. package/src/components/select/select.css +0 -1
  34. package/src/components/select/variants.css +4 -0
  35. package/src/components/textarea/textarea.css +0 -1
  36. package/src/index.ts +0 -10
  37. package/styles.css +0 -1
  38. package/lib/cjs/components/alert/alert.js +0 -1025
  39. package/lib/cjs/components/alert/alert.js.map +0 -1
  40. package/lib/cjs/components/alert/index.js +0 -20
  41. package/lib/cjs/components/alert/index.js.map +0 -1
  42. package/lib/cjs/components/alert/templates.js +0 -120
  43. package/lib/cjs/components/alert/templates.js.map +0 -1
  44. package/lib/cjs/components/alert/types.js +0 -7
  45. package/lib/cjs/components/alert/types.js.map +0 -1
  46. package/lib/cjs/components/datepicker/config/config.js +0 -42
  47. package/lib/cjs/components/datepicker/config/config.js.map +0 -1
  48. package/lib/cjs/components/datepicker/config/index.js +0 -24
  49. package/lib/cjs/components/datepicker/config/index.js.map +0 -1
  50. package/lib/cjs/components/datepicker/config/interfaces.js +0 -7
  51. package/lib/cjs/components/datepicker/config/interfaces.js.map +0 -1
  52. package/lib/cjs/components/datepicker/config/types.js +0 -7
  53. package/lib/cjs/components/datepicker/config/types.js.map +0 -1
  54. package/lib/cjs/components/datepicker/core/event-manager.js +0 -135
  55. package/lib/cjs/components/datepicker/core/event-manager.js.map +0 -1
  56. package/lib/cjs/components/datepicker/core/focus-manager.js +0 -167
  57. package/lib/cjs/components/datepicker/core/focus-manager.js.map +0 -1
  58. package/lib/cjs/components/datepicker/core/helpers.js +0 -219
  59. package/lib/cjs/components/datepicker/core/helpers.js.map +0 -1
  60. package/lib/cjs/components/datepicker/core/index.js +0 -25
  61. package/lib/cjs/components/datepicker/core/index.js.map +0 -1
  62. package/lib/cjs/components/datepicker/core/unified-state-manager.js +0 -394
  63. package/lib/cjs/components/datepicker/core/unified-state-manager.js.map +0 -1
  64. package/lib/cjs/components/datepicker/datepicker.js +0 -2252
  65. package/lib/cjs/components/datepicker/datepicker.js.map +0 -1
  66. package/lib/cjs/components/datepicker/index.js +0 -24
  67. package/lib/cjs/components/datepicker/index.js.map +0 -1
  68. package/lib/cjs/components/datepicker/ui/index.js +0 -23
  69. package/lib/cjs/components/datepicker/ui/index.js.map +0 -1
  70. package/lib/cjs/components/datepicker/ui/input/dropdown.js +0 -489
  71. package/lib/cjs/components/datepicker/ui/input/dropdown.js.map +0 -1
  72. package/lib/cjs/components/datepicker/ui/input/index.js +0 -23
  73. package/lib/cjs/components/datepicker/ui/input/index.js.map +0 -1
  74. package/lib/cjs/components/datepicker/ui/input/segmented-input.js +0 -640
  75. package/lib/cjs/components/datepicker/ui/input/segmented-input.js.map +0 -1
  76. package/lib/cjs/components/datepicker/ui/renderers/calendar.js +0 -446
  77. package/lib/cjs/components/datepicker/ui/renderers/calendar.js.map +0 -1
  78. package/lib/cjs/components/datepicker/ui/renderers/footer.js +0 -42
  79. package/lib/cjs/components/datepicker/ui/renderers/footer.js.map +0 -1
  80. package/lib/cjs/components/datepicker/ui/renderers/header.js +0 -32
  81. package/lib/cjs/components/datepicker/ui/renderers/header.js.map +0 -1
  82. package/lib/cjs/components/datepicker/ui/renderers/index.js +0 -25
  83. package/lib/cjs/components/datepicker/ui/renderers/index.js.map +0 -1
  84. package/lib/cjs/components/datepicker/ui/renderers/time-picker.js +0 -384
  85. package/lib/cjs/components/datepicker/ui/renderers/time-picker.js.map +0 -1
  86. package/lib/cjs/components/datepicker/ui/templates/index.js +0 -22
  87. package/lib/cjs/components/datepicker/ui/templates/index.js.map +0 -1
  88. package/lib/cjs/components/datepicker/ui/templates/templates.js +0 -253
  89. package/lib/cjs/components/datepicker/ui/templates/templates.js.map +0 -1
  90. package/lib/cjs/components/datepicker/utils/date-formatters.js +0 -88
  91. package/lib/cjs/components/datepicker/utils/date-formatters.js.map +0 -1
  92. package/lib/cjs/components/datepicker/utils/date-utils.js +0 -194
  93. package/lib/cjs/components/datepicker/utils/date-utils.js.map +0 -1
  94. package/lib/cjs/components/datepicker/utils/index.js +0 -24
  95. package/lib/cjs/components/datepicker/utils/index.js.map +0 -1
  96. package/lib/cjs/components/datepicker/utils/time-utils.js +0 -213
  97. package/lib/cjs/components/datepicker/utils/time-utils.js.map +0 -1
  98. package/lib/esm/components/alert/alert.js +0 -1022
  99. package/lib/esm/components/alert/alert.js.map +0 -1
  100. package/lib/esm/components/alert/index.js +0 -4
  101. package/lib/esm/components/alert/index.js.map +0 -1
  102. package/lib/esm/components/alert/templates.js +0 -112
  103. package/lib/esm/components/alert/templates.js.map +0 -1
  104. package/lib/esm/components/alert/types.js +0 -6
  105. package/lib/esm/components/alert/types.js.map +0 -1
  106. package/lib/esm/components/datepicker/config/config.js +0 -39
  107. package/lib/esm/components/datepicker/config/config.js.map +0 -1
  108. package/lib/esm/components/datepicker/config/index.js +0 -8
  109. package/lib/esm/components/datepicker/config/index.js.map +0 -1
  110. package/lib/esm/components/datepicker/config/interfaces.js +0 -6
  111. package/lib/esm/components/datepicker/config/interfaces.js.map +0 -1
  112. package/lib/esm/components/datepicker/config/types.js +0 -6
  113. package/lib/esm/components/datepicker/config/types.js.map +0 -1
  114. package/lib/esm/components/datepicker/core/event-manager.js +0 -133
  115. package/lib/esm/components/datepicker/core/event-manager.js.map +0 -1
  116. package/lib/esm/components/datepicker/core/focus-manager.js +0 -164
  117. package/lib/esm/components/datepicker/core/focus-manager.js.map +0 -1
  118. package/lib/esm/components/datepicker/core/helpers.js +0 -211
  119. package/lib/esm/components/datepicker/core/helpers.js.map +0 -1
  120. package/lib/esm/components/datepicker/core/index.js +0 -9
  121. package/lib/esm/components/datepicker/core/index.js.map +0 -1
  122. package/lib/esm/components/datepicker/core/unified-state-manager.js +0 -391
  123. package/lib/esm/components/datepicker/core/unified-state-manager.js.map +0 -1
  124. package/lib/esm/components/datepicker/datepicker.js +0 -2248
  125. package/lib/esm/components/datepicker/datepicker.js.map +0 -1
  126. package/lib/esm/components/datepicker/index.js +0 -7
  127. package/lib/esm/components/datepicker/index.js.map +0 -1
  128. package/lib/esm/components/datepicker/ui/index.js +0 -7
  129. package/lib/esm/components/datepicker/ui/index.js.map +0 -1
  130. package/lib/esm/components/datepicker/ui/input/dropdown.js +0 -486
  131. package/lib/esm/components/datepicker/ui/input/dropdown.js.map +0 -1
  132. package/lib/esm/components/datepicker/ui/input/index.js +0 -7
  133. package/lib/esm/components/datepicker/ui/input/index.js.map +0 -1
  134. package/lib/esm/components/datepicker/ui/input/segmented-input.js +0 -637
  135. package/lib/esm/components/datepicker/ui/input/segmented-input.js.map +0 -1
  136. package/lib/esm/components/datepicker/ui/renderers/calendar.js +0 -443
  137. package/lib/esm/components/datepicker/ui/renderers/calendar.js.map +0 -1
  138. package/lib/esm/components/datepicker/ui/renderers/footer.js +0 -39
  139. package/lib/esm/components/datepicker/ui/renderers/footer.js.map +0 -1
  140. package/lib/esm/components/datepicker/ui/renderers/header.js +0 -29
  141. package/lib/esm/components/datepicker/ui/renderers/header.js.map +0 -1
  142. package/lib/esm/components/datepicker/ui/renderers/index.js +0 -9
  143. package/lib/esm/components/datepicker/ui/renderers/index.js.map +0 -1
  144. package/lib/esm/components/datepicker/ui/renderers/time-picker.js +0 -381
  145. package/lib/esm/components/datepicker/ui/renderers/time-picker.js.map +0 -1
  146. package/lib/esm/components/datepicker/ui/templates/index.js +0 -6
  147. package/lib/esm/components/datepicker/ui/templates/index.js.map +0 -1
  148. package/lib/esm/components/datepicker/ui/templates/templates.js +0 -242
  149. package/lib/esm/components/datepicker/ui/templates/templates.js.map +0 -1
  150. package/lib/esm/components/datepicker/utils/date-formatters.js +0 -83
  151. package/lib/esm/components/datepicker/utils/date-formatters.js.map +0 -1
  152. package/lib/esm/components/datepicker/utils/date-utils.js +0 -184
  153. package/lib/esm/components/datepicker/utils/date-utils.js.map +0 -1
  154. package/lib/esm/components/datepicker/utils/index.js +0 -8
  155. package/lib/esm/components/datepicker/utils/index.js.map +0 -1
  156. package/lib/esm/components/datepicker/utils/time-utils.js +0 -201
  157. package/lib/esm/components/datepicker/utils/time-utils.js.map +0 -1
  158. package/src/components/alert/alert.ts +0 -990
  159. package/src/components/alert/index.ts +0 -4
  160. package/src/components/alert/templates.ts +0 -110
  161. package/src/components/alert/tests/accessibility/aria-roles.test.ts +0 -19
  162. package/src/components/alert/tests/accessibility/focus-management.test.ts +0 -19
  163. package/src/components/alert/tests/accessibility/keyboard-nav.test.ts +0 -22
  164. package/src/components/alert/tests/actions/confirm-cancel.test.ts +0 -122
  165. package/src/components/alert/tests/actions/input-field.test.ts +0 -180
  166. package/src/components/alert/tests/alert.basic.test.ts +0 -126
  167. package/src/components/alert/tests/alert.config.test.ts +0 -75
  168. package/src/components/alert/tests/alert.templates.test.ts +0 -17
  169. package/src/components/alert/tests/config/attribute-config.test.ts +0 -94
  170. package/src/components/alert/tests/config/json-config.test.ts +0 -119
  171. package/src/components/alert/tests/config/merging.test.ts +0 -89
  172. package/src/components/alert/tests/dismissal/auto-dismiss.test.ts +0 -96
  173. package/src/components/alert/tests/dismissal/escape-key-dismiss.test.ts +0 -105
  174. package/src/components/alert/tests/dismissal/manual-dismiss.test.ts +0 -90
  175. package/src/components/alert/tests/dismissal/outside-click-dismiss.test.ts +0 -91
  176. package/src/components/alert/tests/edge-cases/invalid-config.test.ts +0 -19
  177. package/src/components/alert/tests/edge-cases/multiple-alerts.test.ts +0 -19
  178. package/src/components/alert/tests/rendering/custom-content.test.ts +0 -81
  179. package/src/components/alert/tests/rendering/info-alert.test.ts +0 -84
  180. package/src/components/alert/tests/rendering/success-alert.test.ts +0 -100
  181. package/src/components/alert/tests/templates/default-templates.test.ts +0 -16
  182. package/src/components/alert/tests/templates/user-templates.test.ts +0 -16
  183. package/src/components/alert/types.ts +0 -145
  184. package/src/components/datepicker/__tests__/datepicker-events.test.ts +0 -356
  185. package/src/components/datepicker/__tests__/datepicker-init.test.ts +0 -343
  186. package/src/components/datepicker/__tests__/datepicker-integration.test.ts +0 -435
  187. package/src/components/datepicker/__tests__/datepicker-timezone.test.ts +0 -220
  188. package/src/components/datepicker/__tests__/segmented-input-focus.test.ts +0 -380
  189. package/src/components/datepicker/__tests__/selective-state-updates.test.ts +0 -400
  190. package/src/components/datepicker/__tests__/state-manager.test.ts +0 -421
  191. package/src/components/datepicker/__tests__/time-preservation.test.ts +0 -387
  192. package/src/components/datepicker/config/config.ts +0 -40
  193. package/src/components/datepicker/config/index.ts +0 -8
  194. package/src/components/datepicker/config/interfaces.ts +0 -82
  195. package/src/components/datepicker/config/types.ts +0 -188
  196. package/src/components/datepicker/core/event-manager.ts +0 -159
  197. package/src/components/datepicker/core/focus-manager.ts +0 -201
  198. package/src/components/datepicker/core/helpers.ts +0 -231
  199. package/src/components/datepicker/core/index.ts +0 -9
  200. package/src/components/datepicker/core/unified-state-manager.ts +0 -459
  201. package/src/components/datepicker/datepicker.css +0 -435
  202. package/src/components/datepicker/datepicker.ts +0 -2548
  203. package/src/components/datepicker/index.ts +0 -8
  204. package/src/components/datepicker/ui/index.ts +0 -7
  205. package/src/components/datepicker/ui/input/dropdown.ts +0 -552
  206. package/src/components/datepicker/ui/input/index.ts +0 -7
  207. package/src/components/datepicker/ui/input/segmented-input.ts +0 -638
  208. package/src/components/datepicker/ui/renderers/__tests__/calendar-optimizations.test.ts +0 -611
  209. package/src/components/datepicker/ui/renderers/calendar.ts +0 -530
  210. package/src/components/datepicker/ui/renderers/footer.ts +0 -43
  211. package/src/components/datepicker/ui/renderers/header.ts +0 -33
  212. package/src/components/datepicker/ui/renderers/index.ts +0 -9
  213. package/src/components/datepicker/ui/renderers/time-picker.ts +0 -438
  214. package/src/components/datepicker/ui/templates/index.ts +0 -6
  215. package/src/components/datepicker/ui/templates/templates.ts +0 -306
  216. package/src/components/datepicker/utils/__tests__/date-formatters.test.ts +0 -160
  217. package/src/components/datepicker/utils/__tests__/date-utils-keys.test.ts +0 -86
  218. package/src/components/datepicker/utils/__tests__/date-utils-timezone.test.ts +0 -215
  219. package/src/components/datepicker/utils/date-formatters.ts +0 -85
  220. package/src/components/datepicker/utils/date-utils.ts +0 -172
  221. package/src/components/datepicker/utils/index.ts +0 -8
  222. package/src/components/datepicker/utils/time-utils.ts +0 -221
@@ -1,421 +0,0 @@
1
- /**
2
- * state-manager.test.ts - Test suite for KTDatepickerUnifiedStateManager
3
- * Tests the immediate vs batched update functionality and observer patterns
4
- */
5
-
6
- import { describe, it, expect, beforeEach, vi } from 'vitest';
7
- import { KTDatepickerUnifiedStateManager } from '../core/unified-state-manager';
8
- import { KTDatepickerState } from '../config/types';
9
-
10
- describe('KTDatepickerUnifiedStateManager', () => {
11
- let stateManager: KTDatepickerUnifiedStateManager;
12
- let mockObserver: any;
13
-
14
- beforeEach(() => {
15
- // Create a new state manager for each test
16
- stateManager = new KTDatepickerUnifiedStateManager({
17
- enableValidation: true,
18
- enableDebugging: false,
19
- enableUpdateBatching: true,
20
- batchDelay: 16
21
- });
22
-
23
- // Create a mock observer
24
- mockObserver = {
25
- onStateChange: vi.fn(),
26
- getUpdatePriority: vi.fn().mockReturnValue(1)
27
- };
28
- });
29
-
30
- describe('Immediate Updates', () => {
31
- it('should apply immediate updates without batching delay', async () => {
32
- const observer = stateManager.subscribe(mockObserver);
33
-
34
- const testDate = new Date(2024, 0, 15);
35
- const success = stateManager.updateState({ selectedDate: testDate }, 'test', true); // immediate = true
36
-
37
- expect(success).toBe(true);
38
- expect(mockObserver.onStateChange).toHaveBeenCalledTimes(1);
39
-
40
- const [newState, oldState] = mockObserver.onStateChange.mock.calls[0];
41
- expect(newState.selectedDate).toEqual(testDate);
42
-
43
- observer(); // unsubscribe
44
- });
45
-
46
- it('should apply batched updates with delay when immediate is false', async () => {
47
- const observer = stateManager.subscribe(mockObserver);
48
-
49
- const testDate = new Date(2024, 0, 15);
50
- const success = stateManager.updateState({ selectedDate: testDate }, 'test', false); // immediate = false
51
-
52
- expect(success).toBe(true);
53
-
54
- // Should not have been called immediately
55
- expect(mockObserver.onStateChange).not.toHaveBeenCalled();
56
-
57
- // Wait for batch timeout (using a longer timeout for test reliability)
58
- await new Promise(resolve => setTimeout(resolve, 20));
59
-
60
- expect(mockObserver.onStateChange).toHaveBeenCalledTimes(1);
61
-
62
- observer(); // unsubscribe
63
- });
64
-
65
- it('should default to batched updates when immediate is not specified', async () => {
66
- const observer = stateManager.subscribe(mockObserver);
67
-
68
- const testDate = new Date(2024, 0, 15);
69
- const success = stateManager.updateState({ selectedDate: testDate }, 'test'); // no immediate parameter
70
-
71
- expect(success).toBe(true);
72
- expect(mockObserver.onStateChange).not.toHaveBeenCalled();
73
-
74
- // Wait for batch timeout
75
- await new Promise(resolve => setTimeout(resolve, 20));
76
-
77
- expect(mockObserver.onStateChange).toHaveBeenCalledTimes(1);
78
-
79
- observer(); // unsubscribe
80
- });
81
- });
82
-
83
- describe('Convenience Methods', () => {
84
- it('should use immediate updates for critical user interaction methods', async () => {
85
- const observer = stateManager.subscribe(mockObserver);
86
-
87
- // Test setSelectedDate (should be immediate)
88
- const testDate = new Date(2024, 0, 15);
89
- stateManager.setSelectedDate(testDate, 'test');
90
-
91
- // Should be called immediately, not batched
92
- expect(mockObserver.onStateChange).toHaveBeenCalledTimes(1);
93
-
94
- const [newState] = mockObserver.onStateChange.mock.calls[0];
95
- expect(newState.selectedDate).toEqual(testDate);
96
-
97
- observer(); // unsubscribe
98
- });
99
-
100
- it('should use immediate updates for setDropdownOpen', async () => {
101
- const observer = stateManager.subscribe(mockObserver);
102
-
103
- // Test setDropdownOpen (should be immediate)
104
- stateManager.setDropdownOpen(true, 'test');
105
-
106
- // Should be called immediately
107
- expect(mockObserver.onStateChange).toHaveBeenCalledTimes(1);
108
-
109
- const [newState] = mockObserver.onStateChange.mock.calls[0];
110
- expect(newState.dropdownState.isOpen).toBe(true);
111
- expect(newState.isOpen).toBe(true); // Also updates legacy field
112
-
113
- observer(); // unsubscribe
114
- });
115
-
116
- it('should use batched updates for non-critical methods', async () => {
117
- const observer = stateManager.subscribe(mockObserver);
118
-
119
- // Test setCurrentDate (should be batched)
120
- const testDate = new Date(2024, 0, 15);
121
- stateManager.setCurrentDate(testDate, 'test');
122
-
123
- // Should not be called immediately
124
- expect(mockObserver.onStateChange).not.toHaveBeenCalled();
125
-
126
- // Wait for batch timeout
127
- await new Promise(resolve => setTimeout(resolve, 20));
128
-
129
- expect(mockObserver.onStateChange).toHaveBeenCalledTimes(1);
130
-
131
- observer(); // unsubscribe
132
- });
133
- });
134
-
135
- describe('Observer Pattern', () => {
136
- it('should notify all subscribed observers', async () => {
137
- const observer2 = {
138
- onStateChange: vi.fn(),
139
- getUpdatePriority: vi.fn().mockReturnValue(1)
140
- };
141
-
142
- const unsub1 = stateManager.subscribe(mockObserver);
143
- const unsub2 = stateManager.subscribe(observer2);
144
-
145
- const testDate = new Date(2024, 0, 15);
146
- stateManager.updateState({ selectedDate: testDate }, 'test', true); // immediate
147
-
148
- expect(mockObserver.onStateChange).toHaveBeenCalledTimes(1);
149
- expect(observer2.onStateChange).toHaveBeenCalledTimes(1);
150
-
151
- unsub1();
152
- unsub2();
153
- });
154
-
155
- it('should respect observer priority ordering', async () => {
156
- const highPriorityObserver = {
157
- onStateChange: vi.fn(),
158
- getUpdatePriority: vi.fn().mockReturnValue(0) // Higher priority (lower number)
159
- };
160
-
161
- const lowPriorityObserver = {
162
- onStateChange: vi.fn(),
163
- getUpdatePriority: vi.fn().mockReturnValue(2) // Lower priority (higher number)
164
- };
165
-
166
- const unsub1 = stateManager.subscribe(highPriorityObserver);
167
- const unsub2 = stateManager.subscribe(lowPriorityObserver);
168
-
169
- const testDate = new Date(2024, 0, 15);
170
- stateManager.updateState({ selectedDate: testDate }, 'test', true); // immediate
171
-
172
- // Both should be called
173
- expect(highPriorityObserver.onStateChange).toHaveBeenCalledTimes(1);
174
- expect(lowPriorityObserver.onStateChange).toHaveBeenCalledTimes(1);
175
-
176
- unsub1();
177
- unsub2();
178
- });
179
-
180
- it('should handle observer errors gracefully', async () => {
181
- const errorObserver = {
182
- onStateChange: vi.fn().mockImplementation(() => {
183
- throw new Error('Observer error');
184
- }),
185
- getUpdatePriority: vi.fn().mockReturnValue(1)
186
- };
187
-
188
- const normalObserver = {
189
- onStateChange: vi.fn(),
190
- getUpdatePriority: vi.fn().mockReturnValue(1)
191
- };
192
-
193
- const unsub1 = stateManager.subscribe(errorObserver);
194
- const unsub2 = stateManager.subscribe(normalObserver);
195
-
196
- const testDate = new Date(2024, 0, 15);
197
- stateManager.updateState({ selectedDate: testDate }, 'test', true); // immediate
198
-
199
- // Normal observer should still be called despite error in first observer
200
- expect(errorObserver.onStateChange).toHaveBeenCalledTimes(1);
201
- expect(normalObserver.onStateChange).toHaveBeenCalledTimes(1);
202
-
203
- unsub1();
204
- unsub2();
205
- });
206
-
207
- it('should allow unsubscribing from updates', async () => {
208
- const unsub = stateManager.subscribe(mockObserver);
209
-
210
- const testDate = new Date(2024, 0, 15);
211
- stateManager.updateState({ selectedDate: testDate }, 'test', true); // immediate
212
-
213
- expect(mockObserver.onStateChange).toHaveBeenCalledTimes(1);
214
-
215
- // Unsubscribe
216
- unsub();
217
-
218
- // This update should not notify the unsubscribed observer
219
- stateManager.updateState({ selectedDate: new Date(2024, 1, 20) }, 'test2', true);
220
-
221
- // Should still be 1 call (not 2)
222
- expect(mockObserver.onStateChange).toHaveBeenCalledTimes(1);
223
- });
224
- });
225
-
226
- describe('State Validation', () => {
227
- it('should validate state before applying updates', async () => {
228
- const observer = stateManager.subscribe(mockObserver);
229
-
230
- // Try to set an invalid date (NaN)
231
- const invalidDate = new Date(NaN);
232
- const success = stateManager.updateState({ selectedDate: invalidDate }, 'test', true);
233
-
234
- expect(success).toBe(false);
235
- expect(mockObserver.onStateChange).not.toHaveBeenCalled();
236
-
237
- observer(); // unsubscribe
238
- });
239
-
240
- it('should validate range constraints', async () => {
241
- const observer = stateManager.subscribe(mockObserver);
242
-
243
- // Set start date after end date (invalid range)
244
- const startDate = new Date(2024, 1, 15);
245
- const endDate = new Date(2024, 0, 15);
246
- const success = stateManager.updateState({
247
- selectedRange: { start: startDate, end: endDate }
248
- }, 'test', true);
249
-
250
- expect(success).toBe(false);
251
- expect(mockObserver.onStateChange).not.toHaveBeenCalled();
252
-
253
- observer(); // unsubscribe
254
- });
255
-
256
- it('should allow valid state updates', async () => {
257
- const observer = stateManager.subscribe(mockObserver);
258
-
259
- const validDate = new Date(2024, 0, 15);
260
- const success = stateManager.updateState({ selectedDate: validDate }, 'test', true);
261
-
262
- expect(success).toBe(true);
263
- expect(mockObserver.onStateChange).toHaveBeenCalledTimes(1);
264
-
265
- observer(); // unsubscribe
266
- });
267
- });
268
-
269
- describe('Batching Behavior', () => {
270
- it('should batch multiple rapid updates', async () => {
271
- const observer = stateManager.subscribe(mockObserver);
272
-
273
- // Multiple rapid updates
274
- stateManager.updateState({ selectedDate: new Date(2024, 0, 15) }, 'test1', false);
275
- stateManager.updateState({ currentDate: new Date(2024, 1, 20) }, 'test2', false);
276
- stateManager.updateState({ viewMode: 'months' }, 'test3', false);
277
-
278
- // Should not have been called yet
279
- expect(mockObserver.onStateChange).not.toHaveBeenCalled();
280
-
281
- // Wait for batch timeout
282
- await new Promise(resolve => setTimeout(resolve, 20));
283
-
284
- // Should be called once with merged updates
285
- expect(mockObserver.onStateChange).toHaveBeenCalledTimes(1);
286
-
287
- const [newState] = mockObserver.onStateChange.mock.calls[0];
288
- expect(newState.selectedDate).toEqual(new Date(2024, 0, 15));
289
- expect(newState.currentDate).toEqual(new Date(2024, 1, 20));
290
- expect(newState.viewMode).toBe('months');
291
-
292
- observer(); // unsubscribe
293
- });
294
-
295
- it('should reset batch timeout on new batched updates', async () => {
296
- const observer = stateManager.subscribe(mockObserver);
297
-
298
- // First update
299
- stateManager.updateState({ selectedDate: new Date(2024, 0, 15) }, 'test1', false);
300
-
301
- // Wait partial time
302
- await new Promise(resolve => setTimeout(resolve, 5));
303
-
304
- // Second update (should reset timeout)
305
- stateManager.updateState({ currentDate: new Date(2024, 1, 20) }, 'test2', false);
306
-
307
- // Wait another partial time
308
- await new Promise(resolve => setTimeout(resolve, 5));
309
-
310
- // Should still not have been called
311
- expect(mockObserver.onStateChange).not.toHaveBeenCalled();
312
-
313
- // Wait for full timeout from second update
314
- await new Promise(resolve => setTimeout(resolve, 12));
315
-
316
- expect(mockObserver.onStateChange).toHaveBeenCalledTimes(1);
317
-
318
- observer(); // unsubscribe
319
- });
320
- });
321
-
322
- describe('Change Tracking', () => {
323
- it('should provide access to changed properties after state update', () => {
324
- const observer = stateManager.subscribe(mockObserver);
325
-
326
- stateManager.updateState({ isOpen: true }, 'test', true);
327
-
328
- const changedProperties = stateManager.getLastChangedProperties();
329
- expect(changedProperties).toBeInstanceOf(Set);
330
- expect(changedProperties.has('isOpen')).toBe(true);
331
-
332
- observer(); // unsubscribe
333
- });
334
-
335
- it('should track multiple changed properties', () => {
336
- const observer = stateManager.subscribe(mockObserver);
337
-
338
- stateManager.updateState({
339
- selectedDate: new Date(2024, 0, 15),
340
- isOpen: true,
341
- currentDate: new Date(2024, 1, 1)
342
- }, 'test', true);
343
-
344
- const changedProperties = stateManager.getLastChangedProperties();
345
- expect(changedProperties.has('selectedDate')).toBe(true);
346
- expect(changedProperties.has('isOpen')).toBe(true);
347
- expect(changedProperties.has('currentDate')).toBe(true);
348
-
349
- observer(); // unsubscribe
350
- });
351
-
352
- it('should return empty set when no changes occurred', () => {
353
- // Initially, no changes
354
- const changedProperties = stateManager.getLastChangedProperties();
355
- expect(changedProperties.size).toBe(0);
356
- });
357
-
358
- it('should update changed properties for each state update', () => {
359
- const observer = stateManager.subscribe(mockObserver);
360
-
361
- // First update
362
- stateManager.updateState({ isOpen: true }, 'test1', true);
363
- let changedProperties = stateManager.getLastChangedProperties();
364
- expect(changedProperties.has('isOpen')).toBe(true);
365
- expect(changedProperties.has('selectedDate')).toBe(false);
366
-
367
- // Second update
368
- stateManager.updateState({ selectedDate: new Date(2024, 0, 15) }, 'test2', true);
369
- changedProperties = stateManager.getLastChangedProperties();
370
- expect(changedProperties.has('isOpen')).toBe(false); // Previous update
371
- expect(changedProperties.has('selectedDate')).toBe(true); // Current update
372
-
373
- observer(); // unsubscribe
374
- });
375
-
376
- it('should track nested properties in selectedRange', () => {
377
- const observer = stateManager.subscribe(mockObserver);
378
-
379
- // First, set an initial range
380
- const initialRange = {
381
- start: new Date(2024, 0, 15),
382
- end: new Date(2024, 0, 20)
383
- };
384
- stateManager.updateState({ selectedRange: initialRange }, 'test', true);
385
- mockObserver.onStateChange.mockClear();
386
-
387
- // Then update the range (now both old and new are objects, so nested tracking applies)
388
- const updatedRange = {
389
- start: new Date(2024, 0, 16), // Changed
390
- end: new Date(2024, 0, 20) // Same
391
- };
392
- stateManager.updateState({ selectedRange: updatedRange }, 'test2', true);
393
-
394
- const changedProperties = stateManager.getLastChangedProperties();
395
- expect(changedProperties.has('selectedRange')).toBe(true);
396
- // Nested properties should be tracked when both old and new are objects
397
- expect(changedProperties.has('selectedRange.start')).toBe(true); // Changed
398
- expect(changedProperties.has('selectedRange.end')).toBe(false); // Not changed
399
-
400
- observer(); // unsubscribe
401
- });
402
-
403
- it('should maintain backward compatibility with existing observer pattern', () => {
404
- const observer = stateManager.subscribe(mockObserver);
405
-
406
- const testDate = new Date(2024, 0, 15);
407
- stateManager.updateState({ selectedDate: testDate }, 'test', true);
408
-
409
- // Existing behavior should still work
410
- expect(mockObserver.onStateChange).toHaveBeenCalledTimes(1);
411
- const [newState, oldState] = mockObserver.onStateChange.mock.calls[0];
412
- expect(newState.selectedDate).toEqual(testDate);
413
-
414
- // New functionality should also work
415
- const changedProperties = stateManager.getLastChangedProperties();
416
- expect(changedProperties.has('selectedDate')).toBe(true);
417
-
418
- observer(); // unsubscribe
419
- });
420
- });
421
- });