@jmruthers/pace-core 0.5.75 → 0.5.76

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 (226) hide show
  1. package/dist/{DataTable-HWZQGASI.js → DataTable-4GAVPIEG.js} +48 -30
  2. package/dist/{PublicLoadingSpinner-BKNBT6b6.d.ts → PublicLoadingSpinner-BiNER8F5.d.ts} +28 -17
  3. package/dist/{chunk-33PHABLB.js → chunk-AFGTSUAD.js} +10 -127
  4. package/dist/chunk-AFGTSUAD.js.map +1 -0
  5. package/dist/{chunk-2DFZ432F.js → chunk-K34IM5CT.js} +3 -5
  6. package/dist/{chunk-2DFZ432F.js.map → chunk-K34IM5CT.js.map} +1 -1
  7. package/dist/{chunk-2CHATWBF.js → chunk-KHJS6VIA.js} +199 -35
  8. package/dist/chunk-KHJS6VIA.js.map +1 -0
  9. package/dist/{chunk-ZTT2AXMX.js → chunk-KK73ZB4E.js} +2 -2
  10. package/dist/{chunk-CY3AHGO4.js → chunk-M5IWZRBT.js} +1750 -2815
  11. package/dist/chunk-M5IWZRBT.js.map +1 -0
  12. package/dist/{chunk-DAXLNIDY.js → chunk-Y6TXWPJO.js} +6 -4
  13. package/dist/{chunk-DAXLNIDY.js.map → chunk-Y6TXWPJO.js.map} +1 -1
  14. package/dist/{chunk-YNUBMSMV.js → chunk-YCKPEMJA.js} +186 -263
  15. package/dist/chunk-YCKPEMJA.js.map +1 -0
  16. package/dist/components.d.ts +1 -1
  17. package/dist/components.js +7 -6
  18. package/dist/components.js.map +1 -1
  19. package/dist/hooks.d.ts +17 -40
  20. package/dist/hooks.js +6 -6
  21. package/dist/index.d.ts +3 -3
  22. package/dist/index.js +12 -10
  23. package/dist/index.js.map +1 -1
  24. package/dist/rbac/index.d.ts +54 -1
  25. package/dist/rbac/index.js +5 -4
  26. package/dist/utils.js +1 -1
  27. package/docs/TERMINOLOGY.md +231 -0
  28. package/docs/api/classes/ColumnFactory.md +1 -1
  29. package/docs/api/classes/ErrorBoundary.md +1 -1
  30. package/docs/api/classes/InvalidScopeError.md +1 -1
  31. package/docs/api/classes/MissingUserContextError.md +1 -1
  32. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  33. package/docs/api/classes/PermissionDeniedError.md +1 -1
  34. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  35. package/docs/api/classes/RBACAuditManager.md +1 -1
  36. package/docs/api/classes/RBACCache.md +1 -1
  37. package/docs/api/classes/RBACEngine.md +1 -1
  38. package/docs/api/classes/RBACError.md +1 -1
  39. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  40. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  41. package/docs/api/classes/StorageUtils.md +1 -1
  42. package/docs/api/enums/FileCategory.md +1 -1
  43. package/docs/api/interfaces/AggregateConfig.md +1 -1
  44. package/docs/api/interfaces/ButtonProps.md +1 -1
  45. package/docs/api/interfaces/CardProps.md +1 -1
  46. package/docs/api/interfaces/ColorPalette.md +1 -1
  47. package/docs/api/interfaces/ColorShade.md +1 -1
  48. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  49. package/docs/api/interfaces/DataTableAction.md +1 -1
  50. package/docs/api/interfaces/DataTableColumn.md +1 -1
  51. package/docs/api/interfaces/DataTableProps.md +1 -1
  52. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  53. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  54. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  55. package/docs/api/interfaces/EventLogoProps.md +1 -1
  56. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  57. package/docs/api/interfaces/FileMetadata.md +1 -1
  58. package/docs/api/interfaces/FileReference.md +1 -1
  59. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  60. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  61. package/docs/api/interfaces/FileUploadProps.md +1 -1
  62. package/docs/api/interfaces/FooterProps.md +1 -1
  63. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  64. package/docs/api/interfaces/InputProps.md +1 -1
  65. package/docs/api/interfaces/LabelProps.md +1 -1
  66. package/docs/api/interfaces/LoginFormProps.md +1 -1
  67. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  68. package/docs/api/interfaces/NavigationContextType.md +1 -1
  69. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  70. package/docs/api/interfaces/NavigationItem.md +1 -1
  71. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  72. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  73. package/docs/api/interfaces/Organisation.md +1 -1
  74. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  75. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  76. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  77. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  78. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  79. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  80. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  81. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  82. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  83. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  84. package/docs/api/interfaces/PaletteData.md +1 -1
  85. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  86. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  87. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  88. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  89. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  90. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  91. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  92. package/docs/api/interfaces/RBACConfig.md +1 -1
  93. package/docs/api/interfaces/RBACContextType.md +1 -1
  94. package/docs/api/interfaces/RBACLogger.md +1 -1
  95. package/docs/api/interfaces/RBACProviderProps.md +1 -1
  96. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  97. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  98. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  99. package/docs/api/interfaces/RouteConfig.md +1 -1
  100. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  101. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  102. package/docs/api/interfaces/StorageConfig.md +1 -1
  103. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  104. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  105. package/docs/api/interfaces/StorageListOptions.md +1 -1
  106. package/docs/api/interfaces/StorageListResult.md +1 -1
  107. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  108. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  109. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  110. package/docs/api/interfaces/StyleImport.md +1 -1
  111. package/docs/api/interfaces/SwitchProps.md +1 -1
  112. package/docs/api/interfaces/ToastActionElement.md +1 -1
  113. package/docs/api/interfaces/ToastProps.md +1 -1
  114. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  115. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  116. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  117. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  118. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  119. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  120. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  121. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  122. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  123. package/docs/api/interfaces/UseResolvedScopeOptions.md +47 -0
  124. package/docs/api/interfaces/UseResolvedScopeReturn.md +47 -0
  125. package/docs/api/interfaces/UserEventAccess.md +1 -1
  126. package/docs/api/interfaces/UserMenuProps.md +1 -1
  127. package/docs/api/interfaces/UserProfile.md +1 -1
  128. package/docs/api/modules.md +57 -11
  129. package/docs/api-reference/providers.md +26 -7
  130. package/docs/best-practices/README.md +20 -0
  131. package/docs/best-practices/accessibility.md +566 -0
  132. package/docs/best-practices/performance-expansion.md +473 -0
  133. package/docs/core-concepts/authentication.md +15 -7
  134. package/docs/documentation-index.md +1 -1
  135. package/docs/documentation-templates.md +539 -0
  136. package/docs/getting-started/quick-start.md +16 -66
  137. package/docs/implementation-guides/component-styling.md +410 -0
  138. package/docs/implementation-guides/data-tables.md +1 -1
  139. package/docs/style-guide.md +39 -0
  140. package/package.json +1 -1
  141. package/src/__tests__/TEST_GUIDE_CURSOR.md +290 -0
  142. package/src/__tests__/helpers/supabaseMock.ts +48 -2
  143. package/src/components/DataTable/__tests__/DataTable.default-state.test.tsx +17 -6
  144. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +73 -9
  145. package/src/components/DataTable/components/DataTableCore.tsx +280 -475
  146. package/src/components/DataTable/components/UnifiedTableBody.tsx +120 -153
  147. package/src/components/DataTable/components/index.ts +1 -2
  148. package/src/components/DataTable/context/__tests__/DataTableContext.test.tsx +208 -275
  149. package/src/components/DataTable/core/index.ts +1 -8
  150. package/src/components/DataTable/hooks/__tests__/useColumnOrderPersistence.test.ts +525 -0
  151. package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +570 -0
  152. package/src/components/DataTable/hooks/__tests__/useHierarchicalState.test.ts +214 -0
  153. package/src/components/DataTable/hooks/__tests__/useTableColumns.test.ts +224 -0
  154. package/src/components/DataTable/hooks/index.ts +6 -0
  155. package/src/components/DataTable/hooks/useColumnReordering.ts +1 -0
  156. package/src/components/DataTable/hooks/useDataTablePermissions.ts +149 -0
  157. package/src/components/DataTable/hooks/useDataTableState.ts +12 -6
  158. package/src/components/DataTable/hooks/useHierarchicalState.ts +26 -8
  159. package/src/components/DataTable/hooks/useTableColumns.ts +153 -0
  160. package/src/components/DataTable/index.ts +1 -9
  161. package/src/components/DataTable/utils/__tests__/COVERAGE_NOTE.md +89 -0
  162. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +3 -6
  163. package/src/components/DataTable/utils/__tests__/flexibleImport.test.ts +462 -0
  164. package/src/components/DataTable/utils/__tests__/hierarchicalSorting.test.ts +247 -0
  165. package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +8 -6
  166. package/src/components/DataTable/utils/__tests__/performanceUtils.test.ts +466 -0
  167. package/src/components/DataTable/utils/__tests__/rowUtils.test.ts +265 -0
  168. package/src/components/DataTable/utils/errorHandling.ts +52 -460
  169. package/src/components/DataTable/utils/exportUtils.ts +46 -15
  170. package/src/components/DataTable/utils/hierarchicalSorting.ts +50 -3
  171. package/src/components/DataTable/utils/hierarchicalUtils.ts +167 -34
  172. package/src/components/DataTable/utils/index.ts +5 -0
  173. package/src/components/DataTable/utils/rowUtils.ts +68 -0
  174. package/src/components/EventSelector/EventSelector.test.tsx +672 -0
  175. package/src/components/Label/__tests__/Label.test.tsx +434 -0
  176. package/src/components/PublicLayout/__tests__/PublicPageContextChecker.test.tsx +190 -0
  177. package/src/components/PublicLayout/__tests__/PublicPageDebugger.test.tsx +185 -0
  178. package/src/components/PublicLayout/__tests__/PublicPageProvider.test.tsx +313 -0
  179. package/src/components/Select/Select.test.tsx +143 -120
  180. package/src/components/Select/Select.tsx +47 -212
  181. package/src/components/Select/hooks.ts +36 -1
  182. package/src/components/Select/index.ts +2 -1
  183. package/src/hooks/services/__tests__/useServiceHooks.test.tsx +137 -0
  184. package/src/hooks/useSecureDataAccess.test.ts +32 -29
  185. package/src/providers/__tests__/ProviderLifecycle.test.tsx +341 -0
  186. package/src/rbac/hooks/__tests__/usePermissions.integration.test.ts +437 -0
  187. package/src/rbac/hooks/index.ts +2 -0
  188. package/src/rbac/hooks/useResolvedScope.ts +232 -0
  189. package/src/services/__tests__/InactivityService.lifecycle.test.ts +411 -0
  190. package/src/services/__tests__/OrganisationService.pagination.test.ts +375 -0
  191. package/src/types/__tests__/README.md +114 -0
  192. package/src/types/__tests__/validation.test.ts +731 -0
  193. package/src/utils/__tests__/file-reference.test.ts +383 -0
  194. package/src/utils/__tests__/performanceBenchmark.test.ts +175 -0
  195. package/src/utils/appNameResolver.test.ts +54 -0
  196. package/src/validation/__tests__/csrf.unit.test.ts +63 -0
  197. package/src/validation/__tests__/passwordSchema.unit.test.ts +105 -0
  198. package/dist/chunk-2CHATWBF.js.map +0 -1
  199. package/dist/chunk-33PHABLB.js.map +0 -1
  200. package/dist/chunk-CY3AHGO4.js.map +0 -1
  201. package/dist/chunk-TYHR5X4W.js +0 -33
  202. package/dist/chunk-TYHR5X4W.js.map +0 -1
  203. package/dist/chunk-YNUBMSMV.js.map +0 -1
  204. package/dist/eventContext-BBA42P6G.js +0 -14
  205. package/dist/eventContext-BBA42P6G.js.map +0 -1
  206. package/docs/documentation-style-checklist.md +0 -294
  207. package/src/components/DataTable/components/DataTableBody.tsx +0 -488
  208. package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -144
  209. package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -515
  210. package/src/components/DataTable/core/ActionManager.ts +0 -235
  211. package/src/components/DataTable/core/ColumnManager.ts +0 -215
  212. package/src/components/DataTable/core/DataManager.ts +0 -188
  213. package/src/components/DataTable/core/DataTableContext.tsx +0 -181
  214. package/src/components/DataTable/core/LocalDataAdapter.ts +0 -264
  215. package/src/components/DataTable/core/PluginRegistry.ts +0 -229
  216. package/src/components/DataTable/core/StateManager.ts +0 -311
  217. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +0 -634
  218. package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +0 -193
  219. package/src/components/DataTable/core/__tests__/DataManager.test.ts +0 -519
  220. package/src/components/DataTable/core/__tests__/StateManager.test.ts +0 -714
  221. package/src/components/DataTable/core/interfaces.ts +0 -338
  222. package/src/components/DataTable/utils/debugTools.ts +0 -583
  223. package/src/components/Select/Select.bug-test.tsx +0 -69
  224. package/src/components/Select/Select.refactored.tsx +0 -497
  225. /package/dist/{DataTable-HWZQGASI.js.map → DataTable-4GAVPIEG.js.map} +0 -0
  226. /package/dist/{chunk-ZTT2AXMX.js.map → chunk-KK73ZB4E.js.map} +0 -0
@@ -1,714 +0,0 @@
1
- /**
2
- * @file StateManager Unit Tests
3
- * @package @jmruthers/pace-core
4
- * @module Components/DataTable/Core/StateManager
5
- * @since 0.3.0
6
- */
7
-
8
- import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
9
- import { StateManagerImpl } from '../StateManager';
10
- import type { DataTableObserver, DataTableState, UIState } from '../interfaces';
11
- import type { DataRecord } from '../../types';
12
-
13
- // Test data types
14
- interface TestDataRecord extends DataRecord {
15
- id: string;
16
- name: string;
17
- email: string;
18
- role: string;
19
- isActive: boolean;
20
- }
21
-
22
- // Test data fixtures
23
- const createTestData = (overrides: Partial<TestDataRecord> = {}): TestDataRecord => ({
24
- id: 'test-id-1',
25
- name: 'Test User',
26
- email: 'test@example.com',
27
- role: 'user',
28
- isActive: true,
29
- ...overrides,
30
- });
31
-
32
- const createTestDataArray = (count: number): TestDataRecord[] =>
33
- Array.from({ length: count }, (_, i) => createTestData({
34
- id: `test-id-${i + 1}`,
35
- name: `Test User ${i + 1}`,
36
- email: `test${i + 1}@example.com`,
37
- }));
38
-
39
- const createTestObserver = (id: string = 'test-observer'): DataTableObserver<TestDataRecord> => ({
40
- id,
41
- onDataChange: vi.fn(),
42
- onColumnChange: vi.fn(),
43
- onStateChange: vi.fn(),
44
- onError: vi.fn(),
45
- });
46
-
47
- describe('[unit] StateManager', () => {
48
- let stateManager: StateManagerImpl<TestDataRecord>;
49
- let testObserver: DataTableObserver<TestDataRecord>;
50
-
51
- beforeEach(() => {
52
- stateManager = new StateManagerImpl();
53
- testObserver = createTestObserver();
54
- });
55
-
56
- afterEach(() => {
57
- vi.clearAllMocks();
58
- });
59
-
60
- describe('Initialization', () => {
61
- it('should initialize with default state', () => {
62
- const state = stateManager.getState();
63
-
64
- expect(state.data).toEqual([]);
65
- expect(state.isLoading).toBe(false);
66
- expect(state.error).toBe(null);
67
- expect(state.columns).toEqual([]);
68
- expect(state.actions).toEqual([]);
69
- expect(state.features).toBeInstanceOf(Map);
70
- expect(state.plugins).toBeInstanceOf(Map);
71
- expect(state.ui).toMatchObject({
72
- globalFilter: '',
73
- columnFilters: [],
74
- sorting: [],
75
- grouping: [],
76
- expanded: {},
77
- pagination: { pageIndex: 0, pageSize: 10 },
78
- rowSelection: {},
79
- editing: {
80
- rowId: null,
81
- data: {},
82
- isCreating: false,
83
- creationData: {},
84
- },
85
- modals: {
86
- import: false,
87
- export: false,
88
- view: false,
89
- viewData: null,
90
- },
91
- });
92
- });
93
-
94
- it('should initialize with custom initial state', () => {
95
- const customState = {
96
- data: createTestDataArray(2),
97
- isLoading: true,
98
- error: new Error('Test error'),
99
- };
100
-
101
- const manager = new StateManagerImpl(customState);
102
- const state = manager.getState();
103
-
104
- expect(state.data).toEqual(customState.data);
105
- expect(state.isLoading).toBe(true);
106
- expect(state.error).toBe(customState.error);
107
- });
108
-
109
- it('should return copy of state', () => {
110
- const state1 = stateManager.getState();
111
- const state2 = stateManager.getState();
112
-
113
- expect(state1).not.toBe(state2); // Different objects
114
- expect(state1).toEqual(state2); // Same content
115
- });
116
- });
117
-
118
- describe('State Updates', () => {
119
- it('should update state with updater function', () => {
120
- const newData = createTestDataArray(3);
121
-
122
- stateManager.setState(state => ({
123
- ...state,
124
- data: newData,
125
- isLoading: true,
126
- }));
127
-
128
- const state = stateManager.getState();
129
- expect(state.data).toEqual(newData);
130
- expect(state.isLoading).toBe(true);
131
- });
132
-
133
- it('should preserve other state properties when updating', () => {
134
- const originalState = stateManager.getState();
135
-
136
- stateManager.setState(state => ({
137
- ...state,
138
- data: createTestDataArray(2),
139
- }));
140
-
141
- const newState = stateManager.getState();
142
- expect(newState.data).toEqual(createTestDataArray(2));
143
- expect(newState.isLoading).toBe(originalState.isLoading);
144
- expect(newState.error).toBe(originalState.error);
145
- expect(newState.columns).toEqual(originalState.columns);
146
- });
147
-
148
- it('should handle complex state updates', () => {
149
- const newData = createTestDataArray(5);
150
- const newColumns = [{ id: 'name', header: 'Name' }];
151
- const newActions = [{ label: 'Edit', onClick: vi.fn() }];
152
-
153
- stateManager.setState(state => ({
154
- ...state,
155
- data: newData,
156
- columns: newColumns,
157
- actions: newActions,
158
- ui: {
159
- ...state.ui,
160
- globalFilter: 'test filter',
161
- pagination: { pageIndex: 2, pageSize: 20 },
162
- },
163
- }));
164
-
165
- const state = stateManager.getState();
166
- expect(state.data).toEqual(newData);
167
- expect(state.columns).toEqual(newColumns);
168
- expect(state.actions).toEqual(newActions);
169
- expect(state.ui.globalFilter).toBe('test filter');
170
- expect(state.ui.pagination).toEqual({ pageIndex: 2, pageSize: 20 });
171
- });
172
- });
173
-
174
- describe('Observer Pattern', () => {
175
- it('should subscribe function observer', () => {
176
- const observerFn = vi.fn();
177
- const unsubscribe = stateManager.subscribe(observerFn);
178
-
179
- expect(typeof unsubscribe).toBe('function');
180
- expect(stateManager.getObserverCount()).toBe(1);
181
- });
182
-
183
- it('should subscribe object observer', () => {
184
- const unsubscribe = stateManager.subscribe(testObserver);
185
-
186
- expect(typeof unsubscribe).toBe('function');
187
- expect(stateManager.getObserverCount()).toBe(1);
188
- });
189
-
190
- it('should subscribe to specific observer', () => {
191
- const unsubscribe = stateManager.subscribeToObserver(testObserver);
192
-
193
- expect(typeof unsubscribe).toBe('function');
194
- expect(stateManager.getObserverCount()).toBe(1);
195
- });
196
-
197
- it('should unsubscribe observer', () => {
198
- const unsubscribe = stateManager.subscribe(testObserver);
199
- expect(stateManager.getObserverCount()).toBe(1);
200
-
201
- unsubscribe();
202
- expect(stateManager.getObserverCount()).toBe(0);
203
- });
204
-
205
- it('should handle multiple observers', () => {
206
- const observer1 = createTestObserver('observer-1');
207
- const observer2 = createTestObserver('observer-2');
208
- const observer3 = createTestObserver('observer-3');
209
-
210
- stateManager.subscribe(observer1);
211
- stateManager.subscribe(observer2);
212
- stateManager.subscribe(observer3);
213
-
214
- expect(stateManager.getObserverCount()).toBe(3);
215
- });
216
-
217
- it('should notify observers on state change', () => {
218
- const observerFn = vi.fn();
219
- stateManager.subscribe(observerFn);
220
-
221
- stateManager.setState(state => ({
222
- ...state,
223
- data: createTestDataArray(2),
224
- }));
225
-
226
- expect(observerFn).toHaveBeenCalledWith(expect.objectContaining({
227
- data: createTestDataArray(2),
228
- }));
229
- });
230
-
231
- it('should notify object observers on state change', () => {
232
- stateManager.subscribe(testObserver);
233
-
234
- stateManager.setState(state => ({
235
- ...state,
236
- data: createTestDataArray(2),
237
- }));
238
-
239
- expect(testObserver.onStateChange).toHaveBeenCalledWith(expect.objectContaining({
240
- data: createTestDataArray(2),
241
- }));
242
- });
243
- });
244
-
245
- describe('Data Updates', () => {
246
- it('should update data and notify observers', () => {
247
- const newData = createTestDataArray(3);
248
- stateManager.subscribe(testObserver);
249
-
250
- stateManager.updateData(newData);
251
-
252
- const state = stateManager.getState();
253
- expect(state.data).toEqual(newData);
254
- expect(testObserver.onDataChange).toHaveBeenCalledWith(newData);
255
- });
256
-
257
- it('should not notify if data is the same reference', () => {
258
- const data = createTestDataArray(2);
259
- stateManager.subscribe(testObserver);
260
-
261
- stateManager.updateData(data);
262
- vi.clearAllMocks();
263
-
264
- stateManager.updateData(data); // Same reference
265
-
266
- expect(testObserver.onDataChange).not.toHaveBeenCalled();
267
- });
268
-
269
- it('should notify when data reference changes', () => {
270
- const data1 = createTestDataArray(2);
271
- const data2 = createTestDataArray(3);
272
- stateManager.subscribe(testObserver);
273
-
274
- stateManager.updateData(data1);
275
- vi.clearAllMocks();
276
-
277
- stateManager.updateData(data2);
278
-
279
- expect(testObserver.onDataChange).toHaveBeenCalledWith(data2);
280
- });
281
- });
282
-
283
- describe('Column Updates', () => {
284
- it('should update columns and notify observers', () => {
285
- const newColumns = [
286
- { id: 'name', header: 'Name' },
287
- { id: 'email', header: 'Email' },
288
- ];
289
- stateManager.subscribe(testObserver);
290
-
291
- stateManager.updateColumns(newColumns);
292
-
293
- const state = stateManager.getState();
294
- expect(state.columns).toEqual(newColumns);
295
- expect(testObserver.onColumnChange).toHaveBeenCalledWith(newColumns);
296
- });
297
-
298
- it('should not notify if columns are the same reference', () => {
299
- const columns = [{ id: 'name', header: 'Name' }];
300
- stateManager.subscribe(testObserver);
301
-
302
- stateManager.updateColumns(columns);
303
- vi.clearAllMocks();
304
-
305
- stateManager.updateColumns(columns); // Same reference
306
-
307
- expect(testObserver.onColumnChange).not.toHaveBeenCalled();
308
- });
309
- });
310
-
311
- describe('Action Updates', () => {
312
- it('should update actions', () => {
313
- const newActions = [
314
- { label: 'Edit', onClick: vi.fn() },
315
- { label: 'Delete', onClick: vi.fn() },
316
- ];
317
-
318
- stateManager.updateActions(newActions);
319
-
320
- const state = stateManager.getState();
321
- expect(state.actions).toEqual(newActions);
322
- });
323
- });
324
-
325
- describe('UI State Updates', () => {
326
- it('should update global filter', () => {
327
- stateManager.updateGlobalFilter('test search');
328
-
329
- const state = stateManager.getState();
330
- expect(state.ui.globalFilter).toBe('test search');
331
- });
332
-
333
- it('should update column filters', () => {
334
- const filters = [
335
- { id: 'name', value: 'John' },
336
- { id: 'role', value: 'admin' },
337
- ];
338
-
339
- stateManager.updateColumnFilters(filters);
340
-
341
- const state = stateManager.getState();
342
- expect(state.ui.columnFilters).toEqual(filters);
343
- });
344
-
345
- it('should update sorting', () => {
346
- const sorting = [
347
- { id: 'name', desc: false },
348
- { id: 'created_at', desc: true },
349
- ];
350
-
351
- stateManager.updateSorting(sorting);
352
-
353
- const state = stateManager.getState();
354
- expect(state.ui.sorting).toEqual(sorting);
355
- });
356
-
357
- it('should update grouping', () => {
358
- const grouping = ['role', 'department'];
359
-
360
- stateManager.updateGrouping(grouping);
361
-
362
- const state = stateManager.getState();
363
- expect(state.ui.grouping).toEqual(grouping);
364
- });
365
-
366
- it('should update pagination', () => {
367
- const pagination = { pageIndex: 2, pageSize: 25 };
368
-
369
- stateManager.updatePagination(pagination);
370
-
371
- const state = stateManager.getState();
372
- expect(state.ui.pagination).toEqual(pagination);
373
- });
374
-
375
- it('should update row selection', () => {
376
- const selection = { 'row-1': true, 'row-2': false, 'row-3': true };
377
-
378
- stateManager.updateRowSelection(selection);
379
-
380
- const state = stateManager.getState();
381
- expect(state.ui.rowSelection).toEqual(selection);
382
- });
383
-
384
- it('should update editing state', () => {
385
- const editing = {
386
- rowId: 'row-1',
387
- data: { name: 'Updated Name' },
388
- isCreating: false,
389
- creationData: {},
390
- };
391
-
392
- stateManager.updateEditing(editing);
393
-
394
- const state = stateManager.getState();
395
- expect(state.ui.editing).toEqual(editing);
396
- });
397
-
398
- it('should update modal state', () => {
399
- const modals = {
400
- import: true,
401
- export: false,
402
- view: true,
403
- viewData: { id: 'test' },
404
- };
405
-
406
- stateManager.updateModals(modals);
407
-
408
- const state = stateManager.getState();
409
- expect(state.ui.modals).toEqual(modals);
410
- });
411
-
412
- it('should update multiple UI properties at once', () => {
413
- const uiUpdates = {
414
- globalFilter: 'search term',
415
- pagination: { pageIndex: 1, pageSize: 20 },
416
- sorting: [{ id: 'name', desc: false }],
417
- };
418
-
419
- stateManager.updateUI(uiUpdates);
420
-
421
- const state = stateManager.getState();
422
- expect(state.ui.globalFilter).toBe('search term');
423
- expect(state.ui.pagination).toEqual({ pageIndex: 1, pageSize: 20 });
424
- expect(state.ui.sorting).toEqual([{ id: 'name', desc: false }]);
425
- });
426
- });
427
-
428
- describe('Feature and Plugin State', () => {
429
- it('should update feature state', () => {
430
- const featureState = { enabled: true, options: { theme: 'dark' } };
431
-
432
- stateManager.updateFeatureState('theme', featureState);
433
-
434
- const state = stateManager.getState();
435
- expect(state.features.get('theme')).toEqual(featureState);
436
- });
437
-
438
- it('should update plugin state', () => {
439
- const pluginState = { loaded: true, version: '1.0.0' };
440
-
441
- stateManager.updatePluginState('analytics', pluginState);
442
-
443
- const state = stateManager.getState();
444
- expect(state.plugins.get('analytics')).toEqual(pluginState);
445
- });
446
-
447
- it('should handle multiple feature states', () => {
448
- stateManager.updateFeatureState('theme', { enabled: true });
449
- stateManager.updateFeatureState('notifications', { enabled: false });
450
-
451
- const state = stateManager.getState();
452
- expect(state.features.get('theme')).toEqual({ enabled: true });
453
- expect(state.features.get('notifications')).toEqual({ enabled: false });
454
- });
455
-
456
- it('should handle multiple plugin states', () => {
457
- stateManager.updatePluginState('analytics', { loaded: true });
458
- stateManager.updatePluginState('logging', { loaded: false });
459
-
460
- const state = stateManager.getState();
461
- expect(state.plugins.get('analytics')).toEqual({ loaded: true });
462
- expect(state.plugins.get('logging')).toEqual({ loaded: false });
463
- });
464
- });
465
-
466
- describe('Loading and Error States', () => {
467
- it('should set loading state', () => {
468
- stateManager.setLoading(true);
469
-
470
- const state = stateManager.getState();
471
- expect(state.isLoading).toBe(true);
472
- });
473
-
474
- it('should set error state', () => {
475
- const error = new Error('Test error');
476
- stateManager.subscribe(testObserver);
477
-
478
- stateManager.setError(error);
479
-
480
- const state = stateManager.getState();
481
- expect(state.error).toBe(error);
482
- expect(testObserver.onError).toHaveBeenCalledWith(error);
483
- });
484
-
485
- it('should clear error state', () => {
486
- stateManager.setError(new Error('Test error'));
487
- stateManager.setError(null);
488
-
489
- const state = stateManager.getState();
490
- expect(state.error).toBe(null);
491
- });
492
-
493
- it('should not notify on error when error is null', () => {
494
- stateManager.subscribe(testObserver);
495
- stateManager.setError(null);
496
-
497
- expect(testObserver.onError).not.toHaveBeenCalled();
498
- });
499
- });
500
-
501
- describe('State Reset', () => {
502
- it('should reset state to initial values', () => {
503
- // Modify state
504
- stateManager.setState(state => ({
505
- ...state,
506
- data: createTestDataArray(2),
507
- isLoading: true,
508
- error: new Error('Test error'),
509
- ui: {
510
- ...state.ui,
511
- globalFilter: 'test',
512
- pagination: { pageIndex: 2, pageSize: 20 },
513
- },
514
- }));
515
-
516
- stateManager.subscribe(testObserver);
517
- stateManager.reset();
518
-
519
- const state = stateManager.getState();
520
- expect(state.data).toEqual([]);
521
- expect(state.isLoading).toBe(false);
522
- expect(state.error).toBe(null);
523
- expect(state.ui.globalFilter).toBe('');
524
- expect(state.ui.pagination).toEqual({ pageIndex: 0, pageSize: 10 });
525
- expect(testObserver.onStateChange).toHaveBeenCalledWith(state);
526
- });
527
- });
528
-
529
- describe('Event Notifications', () => {
530
- it('should notify data change event', () => {
531
- stateManager.subscribe(testObserver);
532
- const data = createTestDataArray(2);
533
-
534
- stateManager.notify('dataChange', data);
535
-
536
- expect(testObserver.onDataChange).toHaveBeenCalledWith(data);
537
- });
538
-
539
- it('should notify column change event', () => {
540
- stateManager.subscribe(testObserver);
541
- const columns = [{ id: 'name', header: 'Name' }];
542
-
543
- stateManager.notify('columnChange', columns);
544
-
545
- expect(testObserver.onColumnChange).toHaveBeenCalledWith(columns);
546
- });
547
-
548
- it('should notify state change event', () => {
549
- stateManager.subscribe(testObserver);
550
- const state = { data: createTestDataArray(1) };
551
-
552
- stateManager.notify('stateChange', state);
553
-
554
- expect(testObserver.onStateChange).toHaveBeenCalledWith(state);
555
- });
556
-
557
- it('should notify error event', () => {
558
- stateManager.subscribe(testObserver);
559
- const error = new Error('Test error');
560
-
561
- stateManager.notify('error', error);
562
-
563
- expect(testObserver.onError).toHaveBeenCalledWith(error);
564
- });
565
-
566
- it('should handle observers without all callback methods', () => {
567
- const partialObserver = {
568
- id: 'partial-observer',
569
- onDataChange: vi.fn(),
570
- // Missing other callbacks
571
- } as DataTableObserver<TestDataRecord>;
572
-
573
- stateManager.subscribe(partialObserver);
574
-
575
- // Should not throw when calling missing callbacks
576
- expect(() => {
577
- stateManager.notify('columnChange', []);
578
- stateManager.notify('stateChange', {});
579
- stateManager.notify('error', new Error('Test'));
580
- }).not.toThrow();
581
-
582
- expect(partialObserver.onDataChange).not.toHaveBeenCalled();
583
- });
584
- });
585
-
586
- describe('Observer Management', () => {
587
- it('should handle observer unsubscription during notification', () => {
588
- const observer1 = createTestObserver('observer-1');
589
- const observer2 = createTestObserver('observer-2');
590
-
591
- // Observer 1 unsubscribes itself when notified
592
- observer1.onStateChange = vi.fn().mockImplementation(() => {
593
- stateManager.unsubscribe('observer-1');
594
- });
595
-
596
- stateManager.subscribe(observer1);
597
- stateManager.subscribe(observer2);
598
-
599
- expect(stateManager.getObserverCount()).toBe(2);
600
-
601
- stateManager.setState(state => ({ ...state, data: createTestDataArray(1) }));
602
-
603
- expect(observer1.onStateChange).toHaveBeenCalled();
604
- expect(observer2.onStateChange).toHaveBeenCalled();
605
- expect(stateManager.getObserverCount()).toBe(1);
606
- });
607
-
608
- it('should handle multiple unsubscriptions', () => {
609
- const unsubscribe1 = stateManager.subscribe(createTestObserver('obs-1'));
610
- const unsubscribe2 = stateManager.subscribe(createTestObserver('obs-2'));
611
- const unsubscribe3 = stateManager.subscribe(createTestObserver('obs-3'));
612
-
613
- expect(stateManager.getObserverCount()).toBe(3);
614
-
615
- unsubscribe1();
616
- unsubscribe2();
617
- unsubscribe3();
618
-
619
- expect(stateManager.getObserverCount()).toBe(0);
620
- });
621
-
622
- it('should handle unsubscription of non-existent observer', () => {
623
- expect(() => {
624
- stateManager.unsubscribe('non-existent');
625
- }).not.toThrow();
626
- });
627
- });
628
-
629
- describe('Performance and Memory', () => {
630
- it('should handle large datasets efficiently', () => {
631
- const largeDataset = createTestDataArray(1000);
632
- stateManager.updateData(largeDataset);
633
-
634
- const state = stateManager.getState();
635
- expect(state.data).toHaveLength(1000);
636
- });
637
-
638
- it('should handle many observers efficiently', () => {
639
- const observers = Array.from({ length: 100 }, (_, i) =>
640
- createTestObserver(`observer-${i}`)
641
- );
642
-
643
- observers.forEach(observer => stateManager.subscribe(observer));
644
- expect(stateManager.getObserverCount()).toBe(100);
645
-
646
- // Notify all observers
647
- stateManager.setState(state => ({ ...state, data: createTestDataArray(1) }));
648
-
649
- observers.forEach(observer => {
650
- expect(observer.onStateChange).toHaveBeenCalled();
651
- });
652
- });
653
-
654
- it('should not leak memory with repeated operations', () => {
655
- // Perform many state updates
656
- for (let i = 0; i < 100; i++) {
657
- stateManager.setState(state => ({
658
- ...state,
659
- data: createTestDataArray(i % 10),
660
- }));
661
- }
662
-
663
- const state = stateManager.getState();
664
- expect(state.data).toHaveLength(9); // Last iteration
665
- });
666
- });
667
-
668
- describe('Edge Cases', () => {
669
- it('should handle null/undefined state updates', () => {
670
- expect(() => {
671
- stateManager.setState(() => ({} as any));
672
- }).not.toThrow();
673
- });
674
-
675
- it('should handle empty observer arrays', () => {
676
- stateManager.notify('dataChange', []);
677
- expect(stateManager.getObserverCount()).toBe(0);
678
- });
679
-
680
- it('should handle observers with null callbacks', () => {
681
- const observer = {
682
- id: 'null-callbacks',
683
- onDataChange: null,
684
- onColumnChange: null,
685
- onStateChange: null,
686
- onError: null,
687
- } as any;
688
-
689
- stateManager.subscribe(observer);
690
-
691
- expect(() => {
692
- stateManager.notify('dataChange', []);
693
- stateManager.notify('columnChange', []);
694
- stateManager.notify('stateChange', {});
695
- stateManager.notify('error', new Error('Test'));
696
- }).not.toThrow();
697
- });
698
-
699
- it('should handle rapid state updates', () => {
700
- const observer = createTestObserver();
701
- stateManager.subscribe(observer);
702
-
703
- // Rapid updates
704
- for (let i = 0; i < 10; i++) {
705
- stateManager.setState(state => ({
706
- ...state,
707
- data: createTestDataArray(i + 1),
708
- }));
709
- }
710
-
711
- expect(observer.onStateChange).toHaveBeenCalledTimes(10);
712
- });
713
- });
714
- });