@jmruthers/pace-core 0.5.114 → 0.5.116

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 (236) hide show
  1. package/dist/{AuthService-CVgsgtaZ.d.ts → AuthService-D4646R4b.d.ts} +9 -4
  2. package/dist/{DataTable-3JRLZXER.js → DataTable-ZOAKQ3SU.js} +10 -9
  3. package/dist/{UnifiedAuthProvider-KZZUO27W.js → UnifiedAuthProvider-YFN7YGVN.js} +4 -3
  4. package/dist/{api-PKU4PUBO.js → api-TNIBJWLM.js} +3 -3
  5. package/dist/{audit-H4YJJF7R.js → audit-T36HM7IM.js} +2 -2
  6. package/dist/{chunk-4OX5PXHX.js → chunk-2GJ5GL77.js} +4 -5
  7. package/dist/chunk-2GJ5GL77.js.map +1 -0
  8. package/dist/{chunk-5YIZFEUQ.js → chunk-2LM4QQGH.js} +31 -35
  9. package/dist/chunk-2LM4QQGH.js.map +1 -0
  10. package/dist/{chunk-3OGQLOJM.js → chunk-3DBFLLLU.js} +30 -1
  11. package/dist/chunk-3DBFLLLU.js.map +1 -0
  12. package/dist/{chunk-KTHLNIMA.js → chunk-ECOVPXYS.js} +13 -62
  13. package/dist/chunk-ECOVPXYS.js.map +1 -0
  14. package/dist/{chunk-OO3V7W4H.js → chunk-KA3PSVNV.js} +87 -40
  15. package/dist/chunk-KA3PSVNV.js.map +1 -0
  16. package/dist/{chunk-HKWQN44G.js → chunk-KMPWND3F.js} +15 -15
  17. package/dist/{chunk-L36JW4KV.js → chunk-LFS45U62.js} +2 -2
  18. package/dist/{chunk-NEONKMTU.js → chunk-LZYHAL7Y.js} +9 -4
  19. package/dist/{chunk-NEONKMTU.js.map → chunk-LZYHAL7Y.js.map} +1 -1
  20. package/dist/{chunk-BUN7NMV7.js → chunk-O3FTRYEU.js} +2 -2
  21. package/dist/{chunk-F6QB26OS.js → chunk-P3PUOL6B.js} +80 -8
  22. package/dist/chunk-P3PUOL6B.js.map +1 -0
  23. package/dist/{chunk-ZPXWJA4H.js → chunk-PHDAXDHB.js} +131 -5
  24. package/dist/chunk-PHDAXDHB.js.map +1 -0
  25. package/dist/chunk-UJI6WSMD.js +201 -0
  26. package/dist/{chunk-5CDJCTOO.js.map → chunk-UJI6WSMD.js.map} +1 -1
  27. package/dist/{chunk-JHWQNJP3.js → chunk-UKZWNQMB.js} +65 -19
  28. package/dist/{chunk-JHWQNJP3.js.map → chunk-UKZWNQMB.js.map} +1 -1
  29. package/dist/{chunk-7H75SHXZ.js → chunk-VN3OOE35.js} +2 -2
  30. package/dist/{chunk-QKIVSZ2O.js → chunk-WP5I5GLN.js} +2 -2
  31. package/dist/components.d.ts +1 -1
  32. package/dist/components.js +12 -11
  33. package/dist/components.js.map +1 -1
  34. package/dist/hooks.d.ts +1 -1
  35. package/dist/hooks.js +10 -9
  36. package/dist/hooks.js.map +1 -1
  37. package/dist/index.d.ts +4 -4
  38. package/dist/index.js +19 -16
  39. package/dist/index.js.map +1 -1
  40. package/dist/providers.d.ts +2 -2
  41. package/dist/providers.js +3 -2
  42. package/dist/rbac/index.d.ts +82 -1
  43. package/dist/rbac/index.js +13 -10
  44. package/dist/{useToast-DRah6K-g.d.ts → useToast-Cs_g32bg.d.ts} +8 -6
  45. package/dist/utils.js +6 -4
  46. package/dist/utils.js.map +1 -1
  47. package/dist/validation.js +3 -1
  48. package/dist/validation.js.map +1 -1
  49. package/docs/README.md +4 -0
  50. package/docs/api/classes/ColumnFactory.md +1 -1
  51. package/docs/api/classes/ErrorBoundary.md +1 -1
  52. package/docs/api/classes/InvalidScopeError.md +1 -1
  53. package/docs/api/classes/MissingUserContextError.md +1 -1
  54. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  55. package/docs/api/classes/PermissionDeniedError.md +1 -1
  56. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  57. package/docs/api/classes/RBACAuditManager.md +35 -12
  58. package/docs/api/classes/RBACCache.md +1 -1
  59. package/docs/api/classes/RBACEngine.md +1 -1
  60. package/docs/api/classes/RBACError.md +1 -1
  61. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  62. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  63. package/docs/api/classes/StorageUtils.md +1 -1
  64. package/docs/api/enums/FileCategory.md +1 -1
  65. package/docs/api/interfaces/AggregateConfig.md +1 -1
  66. package/docs/api/interfaces/ButtonProps.md +1 -1
  67. package/docs/api/interfaces/CardProps.md +1 -1
  68. package/docs/api/interfaces/ColorPalette.md +1 -1
  69. package/docs/api/interfaces/ColorShade.md +1 -1
  70. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  71. package/docs/api/interfaces/DataRecord.md +1 -1
  72. package/docs/api/interfaces/DataTableAction.md +1 -1
  73. package/docs/api/interfaces/DataTableColumn.md +1 -1
  74. package/docs/api/interfaces/DataTableProps.md +1 -1
  75. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  76. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  77. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  78. package/docs/api/interfaces/EventAppRoleData.md +71 -0
  79. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  80. package/docs/api/interfaces/FileMetadata.md +1 -1
  81. package/docs/api/interfaces/FileReference.md +1 -1
  82. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  83. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  84. package/docs/api/interfaces/FileUploadProps.md +1 -1
  85. package/docs/api/interfaces/FooterProps.md +1 -1
  86. package/docs/api/interfaces/GrantEventAppRoleParams.md +122 -0
  87. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  88. package/docs/api/interfaces/InputProps.md +1 -1
  89. package/docs/api/interfaces/LabelProps.md +1 -1
  90. package/docs/api/interfaces/LoginFormProps.md +1 -1
  91. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  92. package/docs/api/interfaces/NavigationContextType.md +1 -1
  93. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  94. package/docs/api/interfaces/NavigationItem.md +1 -1
  95. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  96. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  97. package/docs/api/interfaces/Organisation.md +1 -1
  98. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  99. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  100. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  101. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  102. package/docs/api/interfaces/PaceAppLayoutProps.md +27 -27
  103. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  104. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  105. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  106. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  107. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  108. package/docs/api/interfaces/PaletteData.md +1 -1
  109. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  110. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  111. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  112. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  113. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  114. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  115. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  116. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  117. package/docs/api/interfaces/RBACConfig.md +1 -1
  118. package/docs/api/interfaces/RBACLogger.md +1 -1
  119. package/docs/api/interfaces/RevokeEventAppRoleParams.md +100 -0
  120. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  121. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  122. package/docs/api/interfaces/RoleManagementResult.md +52 -0
  123. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  124. package/docs/api/interfaces/RouteConfig.md +1 -1
  125. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  126. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  127. package/docs/api/interfaces/StorageConfig.md +1 -1
  128. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  129. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  130. package/docs/api/interfaces/StorageListOptions.md +1 -1
  131. package/docs/api/interfaces/StorageListResult.md +1 -1
  132. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  133. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  134. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  135. package/docs/api/interfaces/StyleImport.md +1 -1
  136. package/docs/api/interfaces/SwitchProps.md +1 -1
  137. package/docs/api/interfaces/ToastActionElement.md +1 -1
  138. package/docs/api/interfaces/ToastProps.md +1 -1
  139. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  140. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  141. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  142. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  143. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  144. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  145. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  146. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  147. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  148. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  149. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  150. package/docs/api/interfaces/UserEventAccess.md +1 -1
  151. package/docs/api/interfaces/UserMenuProps.md +1 -1
  152. package/docs/api/interfaces/UserProfile.md +1 -1
  153. package/docs/api/modules.md +43 -16
  154. package/docs/architecture/rpc-function-standards.md +193 -0
  155. package/package.json +1 -1
  156. package/src/__tests__/TEST_STANDARD.md +244 -2
  157. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +46 -16
  158. package/src/components/DataTable/__tests__/keyboard.test.tsx +276 -217
  159. package/src/components/DataTable/components/DataTableCore.tsx +32 -17
  160. package/src/components/DataTable/components/DataTableToolbar.tsx +3 -2
  161. package/src/components/DataTable/components/EditableRow.tsx +18 -1
  162. package/src/components/DataTable/components/ImportModal.tsx +25 -2
  163. package/src/components/DataTable/components/ViewRowModal.tsx +1 -1
  164. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +735 -0
  165. package/src/components/DataTable/components/__tests__/BulkOperationsDropdown.test.tsx +572 -0
  166. package/src/components/DataTable/components/__tests__/ColumnVisibilityDropdown.test.tsx +708 -0
  167. package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +451 -0
  168. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +456 -0
  169. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +454 -0
  170. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +462 -0
  171. package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +423 -0
  172. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +393 -0
  173. package/src/components/DataTable/components/__tests__/GroupingDropdown.test.tsx +617 -0
  174. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +734 -0
  175. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +412 -0
  176. package/src/components/DataTable/hooks/useTableHandlers.ts +4 -0
  177. package/src/components/EventSelector/EventSelector.tsx +5 -25
  178. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +12 -7
  179. package/src/components/PaceAppLayout/PaceAppLayout.tsx +4 -0
  180. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.accessibility.test.tsx +7 -2
  181. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +13 -8
  182. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +109 -100
  183. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +18 -13
  184. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.unit.test.tsx +17 -12
  185. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +2 -0
  186. package/src/components/PaceLoginPage/PaceLoginPage.tsx +11 -1
  187. package/src/components/PasswordReset/PasswordChangeForm.test.tsx +2 -2
  188. package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +648 -0
  189. package/src/components/ProtectedRoute/ProtectedRoute.tsx +10 -7
  190. package/src/components/PublicLayout/__tests__/PublicErrorBoundary.test.tsx +4 -12
  191. package/src/components/Select/Select.tsx +8 -0
  192. package/src/components/Toast/Toast.test.tsx +8 -7
  193. package/src/components/Toast/Toast.tsx +4 -4
  194. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +367 -3
  195. package/src/hooks/__tests__/usePublicFileDisplay.test.ts +916 -0
  196. package/src/hooks/useEventTheme.ts +49 -18
  197. package/src/hooks/usePermissionCache.ts +5 -3
  198. package/src/hooks/useSecureDataAccess.ts +11 -1
  199. package/src/hooks/useToast.ts +11 -12
  200. package/src/providers/services/EventServiceProvider.tsx +15 -8
  201. package/src/rbac/__tests__/cache-invalidation.test.ts +385 -0
  202. package/src/rbac/audit.test.ts +206 -0
  203. package/src/rbac/audit.ts +37 -2
  204. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +26 -23
  205. package/src/rbac/errors.test.ts +340 -0
  206. package/src/rbac/hooks/index.ts +9 -0
  207. package/src/rbac/hooks/useResolvedScope.test.ts +1063 -0
  208. package/src/rbac/hooks/useRoleManagement.test.ts +908 -0
  209. package/src/rbac/hooks/useRoleManagement.ts +255 -0
  210. package/src/services/AuthService.ts +10 -0
  211. package/src/services/EventService.ts +111 -50
  212. package/src/services/__tests__/AuthService.test.ts +1 -1
  213. package/src/services/__tests__/EventService.test.ts +60 -45
  214. package/src/services/interfaces/IEventService.ts +1 -1
  215. package/src/utils/__tests__/deviceFingerprint.unit.test.ts +320 -0
  216. package/src/utils/__tests__/logger.unit.test.ts +398 -0
  217. package/src/utils/__tests__/validation.unit.test.ts +225 -1
  218. package/src/utils/file-reference.test.ts +214 -0
  219. package/dist/chunk-3OGQLOJM.js.map +0 -1
  220. package/dist/chunk-4OX5PXHX.js.map +0 -1
  221. package/dist/chunk-5CDJCTOO.js +0 -190
  222. package/dist/chunk-5YIZFEUQ.js.map +0 -1
  223. package/dist/chunk-F6QB26OS.js.map +0 -1
  224. package/dist/chunk-KTHLNIMA.js.map +0 -1
  225. package/dist/chunk-OO3V7W4H.js.map +0 -1
  226. package/dist/chunk-ZPXWJA4H.js.map +0 -1
  227. package/src/rbac/audit-enhanced.ts +0 -351
  228. /package/dist/{DataTable-3JRLZXER.js.map → DataTable-ZOAKQ3SU.js.map} +0 -0
  229. /package/dist/{UnifiedAuthProvider-KZZUO27W.js.map → UnifiedAuthProvider-YFN7YGVN.js.map} +0 -0
  230. /package/dist/{api-PKU4PUBO.js.map → api-TNIBJWLM.js.map} +0 -0
  231. /package/dist/{audit-H4YJJF7R.js.map → audit-T36HM7IM.js.map} +0 -0
  232. /package/dist/{chunk-HKWQN44G.js.map → chunk-KMPWND3F.js.map} +0 -0
  233. /package/dist/{chunk-L36JW4KV.js.map → chunk-LFS45U62.js.map} +0 -0
  234. /package/dist/{chunk-BUN7NMV7.js.map → chunk-O3FTRYEU.js.map} +0 -0
  235. /package/dist/{chunk-7H75SHXZ.js.map → chunk-VN3OOE35.js.map} +0 -0
  236. /package/dist/{chunk-QKIVSZ2O.js.map → chunk-WP5I5GLN.js.map} +0 -0
@@ -0,0 +1,456 @@
1
+ /**
2
+ * @file DataTable Modals Component Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module Components/DataTable/Components/__tests__
5
+ * @since 0.4.0
6
+ *
7
+ * Comprehensive test suite for DataTableModals component following testing guidelines.
8
+ * Tests cover all major functionality, edge cases, and user interactions.
9
+ */
10
+
11
+ import React from 'react';
12
+ import { render, screen, waitFor } from '@testing-library/react';
13
+ import userEvent from '@testing-library/user-event';
14
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
15
+ import { DataTableModals } from '../DataTableModals';
16
+
17
+ // Mock ImportModal
18
+ vi.mock('../ImportModal', () => ({
19
+ ImportModal: ({ isOpen, onClose, onImport, config }: any) => (
20
+ isOpen ? (
21
+ <div data-testid="import-modal">
22
+ <div data-testid="import-modal-title">{config?.title || 'Import Data'}</div>
23
+ <button onClick={onClose} data-testid="import-modal-close">Close</button>
24
+ <button
25
+ onClick={async () => {
26
+ try {
27
+ const mockData = [{ name: 'Test', email: 'test@example.com' }];
28
+ const result = onImport(mockData);
29
+ if (result && typeof result.then === 'function') {
30
+ await result;
31
+ }
32
+ } catch (error) {
33
+ // Errors are expected in error handling tests
34
+ // The component should handle them
35
+ }
36
+ }}
37
+ data-testid="import-modal-import"
38
+ >
39
+ Import
40
+ </button>
41
+ </div>
42
+ ) : null
43
+ ),
44
+ }));
45
+
46
+ interface TestData extends Record<string, unknown> {
47
+ name: string;
48
+ email: string;
49
+ }
50
+
51
+ describe('[component] DataTableModals', () => {
52
+ const defaultProps = {
53
+ showImportModal: false,
54
+ onCloseImportModal: vi.fn(),
55
+ onImport: vi.fn(),
56
+ };
57
+
58
+ beforeEach(() => {
59
+ vi.clearAllMocks();
60
+ });
61
+
62
+ afterEach(() => {
63
+ vi.clearAllMocks();
64
+ });
65
+
66
+ describe('Rendering', () => {
67
+ it('renders nothing when no modals are open', () => {
68
+ const { container } = render(<DataTableModals {...defaultProps} />);
69
+ expect(container.firstChild).toBeNull();
70
+ });
71
+
72
+ it('renders ImportModal when showImportModal is true', () => {
73
+ render(
74
+ <DataTableModals
75
+ {...defaultProps}
76
+ showImportModal={true}
77
+ />
78
+ );
79
+
80
+ expect(screen.getByTestId('import-modal')).toBeInTheDocument();
81
+ });
82
+
83
+ it('does not render ImportModal when showImportModal is false', () => {
84
+ render(<DataTableModals {...defaultProps} />);
85
+
86
+ expect(screen.queryByTestId('import-modal')).not.toBeInTheDocument();
87
+ });
88
+ });
89
+
90
+ describe('Import Modal Integration', () => {
91
+ it('passes isOpen prop to ImportModal', () => {
92
+ render(
93
+ <DataTableModals
94
+ {...defaultProps}
95
+ showImportModal={true}
96
+ />
97
+ );
98
+
99
+ expect(screen.getByTestId('import-modal')).toBeInTheDocument();
100
+ });
101
+
102
+ it('passes onClose prop to ImportModal', async () => {
103
+ const user = userEvent.setup();
104
+ const onCloseImportModal = vi.fn();
105
+
106
+ render(
107
+ <DataTableModals
108
+ {...defaultProps}
109
+ showImportModal={true}
110
+ onCloseImportModal={onCloseImportModal}
111
+ />
112
+ );
113
+
114
+ const closeButton = screen.getByTestId('import-modal-close');
115
+ await user.click(closeButton);
116
+
117
+ expect(onCloseImportModal).toHaveBeenCalledTimes(1);
118
+ });
119
+
120
+ it('passes config to ImportModal', () => {
121
+ const config = {
122
+ title: 'Custom Import Title',
123
+ description: 'Custom description',
124
+ };
125
+
126
+ render(
127
+ <DataTableModals
128
+ {...defaultProps}
129
+ showImportModal={true}
130
+ importModalConfig={config}
131
+ />
132
+ );
133
+
134
+ expect(screen.getByTestId('import-modal-title')).toHaveTextContent('Custom Import Title');
135
+ });
136
+ });
137
+
138
+ describe('CSV Column Mapping', () => {
139
+ it('maps CSV columns to table columns using column definitions', async () => {
140
+ const user = userEvent.setup();
141
+ const onImport = vi.fn();
142
+ const columns = [
143
+ { id: 'name', header: 'Name', accessorKey: 'name' },
144
+ { id: 'email', header: 'Email', accessorKey: 'email' },
145
+ ];
146
+
147
+ render(
148
+ <DataTableModals
149
+ {...defaultProps}
150
+ showImportModal={true}
151
+ onImport={onImport}
152
+ columns={columns}
153
+ />
154
+ );
155
+
156
+ const importButton = screen.getByTestId('import-modal-import');
157
+ await user.click(importButton);
158
+
159
+ await waitFor(() => {
160
+ expect(onImport).toHaveBeenCalled();
161
+ });
162
+ });
163
+
164
+ it('uses editAccessorKey when available for column mapping', async () => {
165
+ const user = userEvent.setup();
166
+ const onImport = vi.fn();
167
+ const columns = [
168
+ {
169
+ id: 'type',
170
+ header: 'Type',
171
+ accessorKey: 'type',
172
+ editAccessorKey: 'typeId',
173
+ },
174
+ ];
175
+
176
+ render(
177
+ <DataTableModals
178
+ {...defaultProps}
179
+ showImportModal={true}
180
+ onImport={onImport}
181
+ columns={columns}
182
+ />
183
+ );
184
+
185
+ const importButton = screen.getByTestId('import-modal-import');
186
+ await user.click(importButton);
187
+
188
+ await waitFor(() => {
189
+ expect(onImport).toHaveBeenCalled();
190
+ });
191
+ });
192
+
193
+ it('uses raw data when no columns provided', async () => {
194
+ const user = userEvent.setup();
195
+ const onImport = vi.fn();
196
+
197
+ render(
198
+ <DataTableModals
199
+ {...defaultProps}
200
+ showImportModal={true}
201
+ onImport={onImport}
202
+ />
203
+ );
204
+
205
+ const importButton = screen.getByTestId('import-modal-import');
206
+ await user.click(importButton);
207
+
208
+ await waitFor(() => {
209
+ expect(onImport).toHaveBeenCalled();
210
+ });
211
+ });
212
+ });
213
+
214
+ describe('Focus Management', () => {
215
+ it('calls onStoreFocus when import modal opens', () => {
216
+ const onStoreFocus = vi.fn();
217
+
218
+ const { rerender } = render(
219
+ <DataTableModals
220
+ {...defaultProps}
221
+ showImportModal={false}
222
+ onStoreFocus={onStoreFocus}
223
+ />
224
+ );
225
+
226
+ rerender(
227
+ <DataTableModals
228
+ {...defaultProps}
229
+ showImportModal={true}
230
+ onStoreFocus={onStoreFocus}
231
+ />
232
+ );
233
+
234
+ expect(onStoreFocus).toHaveBeenCalledTimes(1);
235
+ });
236
+
237
+ it('calls onRestoreFocus when import modal closes', async () => {
238
+ const onRestoreFocus = vi.fn();
239
+
240
+ const { rerender } = render(
241
+ <DataTableModals
242
+ {...defaultProps}
243
+ showImportModal={true}
244
+ onRestoreFocus={onRestoreFocus}
245
+ />
246
+ );
247
+
248
+ rerender(
249
+ <DataTableModals
250
+ {...defaultProps}
251
+ showImportModal={false}
252
+ onRestoreFocus={onRestoreFocus}
253
+ />
254
+ );
255
+
256
+ await waitFor(() => {
257
+ expect(onRestoreFocus).toHaveBeenCalled();
258
+ }, { timeout: 200 });
259
+ });
260
+
261
+ it('does not call focus handlers when not provided', () => {
262
+ const { rerender } = render(
263
+ <DataTableModals
264
+ {...defaultProps}
265
+ showImportModal={false}
266
+ />
267
+ );
268
+
269
+ expect(() => {
270
+ rerender(
271
+ <DataTableModals
272
+ {...defaultProps}
273
+ showImportModal={true}
274
+ />
275
+ );
276
+ }).not.toThrow();
277
+ });
278
+ });
279
+
280
+ describe('Error Handling', () => {
281
+ it('handles import errors gracefully', async () => {
282
+ const user = userEvent.setup();
283
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
284
+ const onImport = vi.fn(() => {
285
+ throw new Error('Import failed');
286
+ });
287
+
288
+ render(
289
+ <DataTableModals
290
+ {...defaultProps}
291
+ showImportModal={true}
292
+ onImport={onImport}
293
+ />
294
+ );
295
+
296
+ const importButton = screen.getByTestId('import-modal-import');
297
+
298
+ // DataTableModals re-throws errors to ImportModal, so we expect the error to propagate
299
+ // The error should be logged before being re-thrown
300
+ await user.click(importButton);
301
+
302
+ await waitFor(() => {
303
+ expect(consoleErrorSpy).toHaveBeenCalled();
304
+ expect(onImport).toHaveBeenCalled();
305
+ }, { timeout: 500 });
306
+
307
+ consoleErrorSpy.mockRestore();
308
+ });
309
+
310
+ it('handles async import errors', async () => {
311
+ const user = userEvent.setup();
312
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
313
+ const onImport = vi.fn(() => Promise.reject(new Error('Async import failed')));
314
+
315
+ render(
316
+ <DataTableModals
317
+ {...defaultProps}
318
+ showImportModal={true}
319
+ onImport={onImport}
320
+ />
321
+ );
322
+
323
+ const importButton = screen.getByTestId('import-modal-import');
324
+
325
+ // DataTableModals re-throws errors to ImportModal, so we expect the error to propagate
326
+ // The error should be logged before being re-thrown
327
+ await user.click(importButton);
328
+
329
+ await waitFor(() => {
330
+ expect(consoleErrorSpy).toHaveBeenCalled();
331
+ expect(onImport).toHaveBeenCalled();
332
+ }, { timeout: 500 });
333
+
334
+ consoleErrorSpy.mockRestore();
335
+ });
336
+ });
337
+
338
+ describe('Edge Cases', () => {
339
+ it('handles empty columns array', async () => {
340
+ const user = userEvent.setup();
341
+ const onImport = vi.fn();
342
+
343
+ render(
344
+ <DataTableModals
345
+ {...defaultProps}
346
+ showImportModal={true}
347
+ onImport={onImport}
348
+ columns={[]}
349
+ />
350
+ );
351
+
352
+ const importButton = screen.getByTestId('import-modal-import');
353
+ await user.click(importButton);
354
+
355
+ await waitFor(() => {
356
+ expect(onImport).toHaveBeenCalled();
357
+ });
358
+ });
359
+
360
+ it('handles columns with missing headers', async () => {
361
+ const user = userEvent.setup();
362
+ const onImport = vi.fn();
363
+ const columns = [
364
+ { id: 'name', accessorKey: 'name' },
365
+ { id: 'email', accessorKey: 'email' },
366
+ ];
367
+
368
+ render(
369
+ <DataTableModals
370
+ {...defaultProps}
371
+ showImportModal={true}
372
+ onImport={onImport}
373
+ columns={columns}
374
+ />
375
+ );
376
+
377
+ const importButton = screen.getByTestId('import-modal-import');
378
+ await user.click(importButton);
379
+
380
+ await waitFor(() => {
381
+ expect(onImport).toHaveBeenCalled();
382
+ });
383
+ });
384
+
385
+ it('handles missing onImport callback', async () => {
386
+ const user = userEvent.setup();
387
+
388
+ render(
389
+ <DataTableModals
390
+ {...defaultProps}
391
+ showImportModal={true}
392
+ onImport={undefined as any}
393
+ />
394
+ );
395
+
396
+ const importButton = screen.getByTestId('import-modal-import');
397
+
398
+ // Should handle gracefully
399
+ expect(() => {
400
+ user.click(importButton);
401
+ }).not.toThrow();
402
+ });
403
+ });
404
+
405
+ describe('Data Transformation', () => {
406
+ it('transforms CSV data to match table column structure', async () => {
407
+ const user = userEvent.setup();
408
+ const onImport = vi.fn();
409
+ const columns = [
410
+ { id: 'name', header: 'Name', accessorKey: 'name' },
411
+ { id: 'email', header: 'Email Address', accessorKey: 'email' },
412
+ ];
413
+
414
+ render(
415
+ <DataTableModals
416
+ {...defaultProps}
417
+ showImportModal={true}
418
+ onImport={onImport}
419
+ columns={columns}
420
+ />
421
+ );
422
+
423
+ const importButton = screen.getByTestId('import-modal-import');
424
+ await user.click(importButton);
425
+
426
+ await waitFor(() => {
427
+ expect(onImport).toHaveBeenCalled();
428
+ });
429
+ });
430
+
431
+ it('handles case-insensitive column header matching', async () => {
432
+ const user = userEvent.setup();
433
+ const onImport = vi.fn();
434
+ const columns = [
435
+ { id: 'name', header: 'Name', accessorKey: 'name' },
436
+ ];
437
+
438
+ render(
439
+ <DataTableModals
440
+ {...defaultProps}
441
+ showImportModal={true}
442
+ onImport={onImport}
443
+ columns={columns}
444
+ />
445
+ );
446
+
447
+ const importButton = screen.getByTestId('import-modal-import');
448
+ await user.click(importButton);
449
+
450
+ await waitFor(() => {
451
+ expect(onImport).toHaveBeenCalled();
452
+ });
453
+ });
454
+ });
455
+ });
456
+