@jmruthers/pace-core 0.5.86 → 0.5.88

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 (248) hide show
  1. package/dist/{AuthService-Df3IozMG.d.ts → AuthService-DcTI5Ov4.d.ts} +9 -0
  2. package/dist/{DataTable-DKGTBLWT.js → DataTable-PWBMKMOG.js} +8 -8
  3. package/dist/{PublicLoadingSpinner-CnUaz0vG.d.ts → PublicLoadingSpinner-BQXD1fbO.d.ts} +161 -131
  4. package/dist/{UnifiedAuthProvider-K2IZAY5F.js → UnifiedAuthProvider-5D3HEQND.js} +4 -4
  5. package/dist/{UnifiedAuthProvider-B391Aqum.d.ts → UnifiedAuthProvider-BVKmQd9u.d.ts} +4 -0
  6. package/dist/auth-DReDSLq9.d.ts +16 -0
  7. package/dist/{chunk-CBSD3BZ3.js → chunk-3RZBKQ5Y.js} +2 -6
  8. package/dist/{chunk-CBSD3BZ3.js.map → chunk-3RZBKQ5Y.js.map} +1 -1
  9. package/dist/{chunk-NTW3KGS4.js → chunk-6UHXQH7P.js} +5 -5
  10. package/dist/{chunk-YVUZWLQG.js → chunk-AQGF5OG7.js} +3 -3
  11. package/dist/{chunk-CVMVPYAL.js → chunk-BDZUMRBD.js} +3 -5
  12. package/dist/chunk-BDZUMRBD.js.map +1 -0
  13. package/dist/{chunk-QCCJ3P4W.js → chunk-BNXBJOGL.js} +5 -5
  14. package/dist/{chunk-IBMPGOCN.js → chunk-CJIZS3UE.js} +1430 -783
  15. package/dist/chunk-CJIZS3UE.js.map +1 -0
  16. package/dist/{chunk-S3JKDMD5.js → chunk-CXKMRKRF.js} +4 -4
  17. package/dist/{chunk-5BN3YGNK.js → chunk-DP5X5ORK.js} +217 -27
  18. package/dist/chunk-DP5X5ORK.js.map +1 -0
  19. package/dist/{chunk-KUYWZVR2.js → chunk-H3P2RGKZ.js} +353 -9
  20. package/dist/chunk-H3P2RGKZ.js.map +1 -0
  21. package/dist/{chunk-RIXPZJUB.js → chunk-KTPG5VCH.js} +2 -2
  22. package/dist/{chunk-YCKPEMJA.js → chunk-QPCAGLUS.js} +2 -3
  23. package/dist/chunk-QPCAGLUS.js.map +1 -0
  24. package/dist/{chunk-WUXCWRL6.js → chunk-XJ2HZOBU.js} +6 -1
  25. package/dist/chunk-XJ2HZOBU.js.map +1 -0
  26. package/dist/{chunk-V5SWX6KL.js → chunk-XXVM53P4.js} +4 -4
  27. package/dist/{chunk-I2VVV5PQ.js → chunk-YY4YYM3E.js} +2 -2
  28. package/dist/components.d.ts +6 -55
  29. package/dist/components.js +25 -206
  30. package/dist/components.js.map +1 -1
  31. package/dist/{file-reference-9xUOnwyt.d.ts → file-reference-C9isKNPn.d.ts} +67 -2
  32. package/dist/hooks.js +10 -9
  33. package/dist/hooks.js.map +1 -1
  34. package/dist/index.d.ts +152 -26
  35. package/dist/index.js +65 -195
  36. package/dist/index.js.map +1 -1
  37. package/dist/providers.d.ts +5 -3
  38. package/dist/providers.js +3 -3
  39. package/dist/rbac/index.js +8 -8
  40. package/dist/types.d.ts +2 -1
  41. package/dist/types.js +3 -3
  42. package/dist/utils.js +2 -2
  43. package/docs/DOCUMENTATION_AUDIT.md +6 -6
  44. package/docs/DOCUMENTATION_STANDARD.md +137 -0
  45. package/docs/README.md +1 -1
  46. package/docs/api/classes/ColumnFactory.md +1 -1
  47. package/docs/api/classes/ErrorBoundary.md +1 -1
  48. package/docs/api/classes/InvalidScopeError.md +1 -1
  49. package/docs/api/classes/MissingUserContextError.md +1 -1
  50. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  51. package/docs/api/classes/PermissionDeniedError.md +1 -1
  52. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  53. package/docs/api/classes/RBACAuditManager.md +1 -1
  54. package/docs/api/classes/RBACCache.md +1 -1
  55. package/docs/api/classes/RBACEngine.md +1 -1
  56. package/docs/api/classes/RBACError.md +1 -1
  57. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  58. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  59. package/docs/api/classes/StorageUtils.md +83 -40
  60. package/docs/api/enums/FileCategory.md +56 -1
  61. package/docs/api/interfaces/AggregateConfig.md +1 -1
  62. package/docs/api/interfaces/ButtonProps.md +1 -1
  63. package/docs/api/interfaces/CardProps.md +1 -1
  64. package/docs/api/interfaces/ColorPalette.md +1 -1
  65. package/docs/api/interfaces/ColorShade.md +1 -1
  66. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  67. package/docs/api/interfaces/DataRecord.md +1 -1
  68. package/docs/api/interfaces/DataTableAction.md +1 -1
  69. package/docs/api/interfaces/DataTableColumn.md +1 -1
  70. package/docs/api/interfaces/DataTableProps.md +1 -1
  71. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  72. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  73. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  74. package/docs/api/interfaces/EventLogoProps.md +11 -11
  75. package/docs/api/interfaces/FileDisplayProps.md +10 -10
  76. package/docs/api/interfaces/FileMetadata.md +1 -1
  77. package/docs/api/interfaces/FileReference.md +1 -1
  78. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  79. package/docs/api/interfaces/FileUploadOptions.md +8 -8
  80. package/docs/api/interfaces/FileUploadProps.md +137 -42
  81. package/docs/api/interfaces/FooterProps.md +1 -1
  82. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  83. package/docs/api/interfaces/InputProps.md +1 -1
  84. package/docs/api/interfaces/LabelProps.md +1 -1
  85. package/docs/api/interfaces/LoginFormProps.md +1 -1
  86. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  87. package/docs/api/interfaces/NavigationContextType.md +1 -1
  88. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  89. package/docs/api/interfaces/NavigationItem.md +1 -1
  90. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  91. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  92. package/docs/api/interfaces/Organisation.md +1 -1
  93. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  94. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  95. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  96. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  97. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  98. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  99. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  100. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  101. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  102. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  103. package/docs/api/interfaces/PaletteData.md +1 -1
  104. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  105. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  106. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  107. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  108. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  109. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  110. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  111. package/docs/api/interfaces/RBACConfig.md +1 -1
  112. package/docs/api/interfaces/RBACLogger.md +1 -1
  113. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  114. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  115. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  116. package/docs/api/interfaces/RouteConfig.md +1 -1
  117. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  118. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  119. package/docs/api/interfaces/StorageConfig.md +1 -1
  120. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  121. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  122. package/docs/api/interfaces/StorageListOptions.md +1 -1
  123. package/docs/api/interfaces/StorageListResult.md +1 -1
  124. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  125. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  126. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  127. package/docs/api/interfaces/StyleImport.md +1 -1
  128. package/docs/api/interfaces/SwitchProps.md +1 -1
  129. package/docs/api/interfaces/ToastActionElement.md +1 -1
  130. package/docs/api/interfaces/ToastProps.md +1 -1
  131. package/docs/api/interfaces/UnifiedAuthContextType.md +83 -50
  132. package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
  133. package/docs/api/interfaces/UseEventLogoOptions.md +74 -0
  134. package/docs/api/interfaces/UseEventLogoReturn.md +81 -0
  135. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  136. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  137. package/docs/api/interfaces/UsePublicEventLogoOptions.md +6 -6
  138. package/docs/api/interfaces/UsePublicEventLogoReturn.md +6 -6
  139. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  140. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  141. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  142. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  143. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  144. package/docs/api/interfaces/UserEventAccess.md +11 -11
  145. package/docs/api/interfaces/UserMenuProps.md +1 -1
  146. package/docs/api/interfaces/UserProfile.md +1 -1
  147. package/docs/api/modules.md +292 -97
  148. package/docs/api-reference/components.md +1 -18
  149. package/docs/api-reference/hooks.md +1 -4
  150. package/docs/best-practices/testing.md +2 -0
  151. package/docs/documentation-index.md +1 -1
  152. package/docs/getting-started/faq.md +1 -1
  153. package/docs/implementation-guides/file-reference-system.md +592 -58
  154. package/docs/implementation-guides/file-upload-storage.md +137 -73
  155. package/docs/rbac/super-admin-guide.md +18 -70
  156. package/docs/testing/README.md +2 -0
  157. package/package.json +1 -1
  158. package/src/__tests__/TEST_STANDARD.md +674 -0
  159. package/src/__tests__/helpers/test-utils.tsx +3 -2
  160. package/src/components/DataTable/__tests__/{DataTable.comprehensive.test.tsx.skip → DataTable.comprehensive.test.tsx} +17 -18
  161. package/src/components/DataTable/__tests__/{DataTable.test.tsx.skip → DataTable.test.tsx} +14 -22
  162. package/src/components/DataTable/__tests__/{ssr.strict-mode.test.tsx.skip → ssr.strict-mode.test.tsx} +42 -47
  163. package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +1 -1
  164. package/src/components/DataTable/examples/__tests__/PerformanceExample.test.tsx +13 -4
  165. package/src/components/DataTable/utils/__tests__/COVERAGE_NOTE.md +1 -1
  166. package/src/components/DataTable/utils/__tests__/performanceUtils.test.ts +10 -6
  167. package/src/components/DataTable/utils/performanceUtils.ts +12 -3
  168. package/src/components/FileDisplay/FileDisplay.test.tsx +257 -0
  169. package/src/components/{FileDisplay.tsx → FileDisplay/FileDisplay.tsx} +111 -10
  170. package/src/components/FileDisplay/index.tsx +4 -0
  171. package/src/components/FileUpload/FileUpload.test.tsx +171 -621
  172. package/src/components/FileUpload/FileUpload.tsx +512 -168
  173. package/src/components/FileUpload/index.tsx +4 -0
  174. package/src/components/Progress/Progress.test.tsx +38 -0
  175. package/src/components/PublicLayout/EventLogo.tsx +220 -39
  176. package/src/components/PublicLayout/PublicPageProvider.tsx +1 -1
  177. package/src/components/Select/Select.test.tsx +1 -1
  178. package/src/components/SessionRestorationLoader.tsx +48 -0
  179. package/src/components/Toast/Toast.tsx +13 -8
  180. package/src/components/index.ts +16 -16
  181. package/src/hooks/__tests__/ServiceHooks.test.tsx +615 -0
  182. package/src/hooks/public/usePublicEventLogo.ts +17 -7
  183. package/src/hooks/useDataTablePerformance.ts +4 -0
  184. package/src/hooks/useEventLogo.ts +316 -0
  185. package/src/hooks/useEvents.ts +0 -5
  186. package/src/hooks/useFileReference.test.ts +659 -0
  187. package/src/hooks/useFileReference.ts +207 -3
  188. package/src/hooks/useSessionRestoration.ts +64 -0
  189. package/src/index.ts +17 -5
  190. package/src/providers/{UnifiedAuthProvider.test.simple.tsx → UnifiedAuthProvider.smoke.test.tsx} +81 -60
  191. package/src/providers/services/AuthServiceProvider.tsx +27 -3
  192. package/src/providers/services/UnifiedAuthProvider.tsx +34 -5
  193. package/src/rbac/{engine.test.simple.ts → RBACEngine.smoke.test.ts} +17 -12
  194. package/src/services/AuthService.ts +142 -20
  195. package/src/services/EventService.ts +0 -4
  196. package/src/types/auth.ts +15 -0
  197. package/src/types/file-reference.ts +73 -1
  198. package/src/types/index.ts +1 -0
  199. package/src/utils/__tests__/organisationContext.unit.test.ts +2 -4
  200. package/src/utils/appNameResolver.simple.test.ts +99 -29
  201. package/src/utils/file-reference.test.ts +535 -0
  202. package/src/utils/file-reference.ts +200 -30
  203. package/src/utils/organisationContext.test.ts +5 -19
  204. package/src/utils/organisationContext.ts +3 -5
  205. package/src/utils/storage/README.md +269 -262
  206. package/src/utils/storage/config.ts +9 -0
  207. package/src/utils/storage/helpers.test.ts +631 -0
  208. package/src/utils/storage/helpers.ts +112 -14
  209. package/src/utils/storage/index.ts +3 -0
  210. package/src/validation/__tests__/sanitization.unit.test.ts +1 -1
  211. package/src/validation/__tests__/schemaUtils.unit.test.ts +1 -1
  212. package/src/validation/__tests__/user.unit.test.ts +1 -1
  213. package/dist/chunk-5BN3YGNK.js.map +0 -1
  214. package/dist/chunk-CVMVPYAL.js.map +0 -1
  215. package/dist/chunk-IBMPGOCN.js.map +0 -1
  216. package/dist/chunk-KUYWZVR2.js.map +0 -1
  217. package/dist/chunk-WUXCWRL6.js.map +0 -1
  218. package/dist/chunk-YCKPEMJA.js.map +0 -1
  219. package/docs/CONTENT_AUDIT_REPORT.md +0 -253
  220. package/docs/STYLE_GUIDE.md +0 -37
  221. package/examples/RBAC/__tests__/PermissionExample.test.tsx +0 -150
  222. package/examples/public-pages/__tests__/PublicPageUsageExample.test.tsx +0 -159
  223. package/src/__tests__/TEST_GUIDE_CURSOR.md +0 -1605
  224. package/src/__tests__/TEST_GUIDE_HUMAN.md +0 -103
  225. package/src/components/FileUpload/FileUpload.example.tsx +0 -218
  226. package/src/components/FileUpload/index.ts +0 -6
  227. package/src/components/FileUpload.tsx +0 -176
  228. package/src/components/Progress/index.ts +0 -3
  229. package/src/components/PublicLayout/__tests__/EventLogo.test.tsx +0 -666
  230. package/src/components/SuperAdminGuard.tsx +0 -116
  231. package/src/components/__tests__/FileDisplay.test.tsx +0 -575
  232. package/src/components/__tests__/FileUpload.test.tsx +0 -446
  233. package/src/components/__tests__/SuperAdminGuard.test.tsx +0 -627
  234. package/src/components/examples/PermissionExample.tsx +0 -173
  235. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +0 -583
  236. package/src/hooks/__tests__/usePublicEventLogo.unit.test.ts +0 -640
  237. package/src/types/__tests__/file-reference.test.ts +0 -447
  238. package/src/utils/__tests__/file-reference.test.ts +0 -383
  239. /package/dist/{DataTable-DKGTBLWT.js.map → DataTable-PWBMKMOG.js.map} +0 -0
  240. /package/dist/{UnifiedAuthProvider-K2IZAY5F.js.map → UnifiedAuthProvider-5D3HEQND.js.map} +0 -0
  241. /package/dist/{chunk-NTW3KGS4.js.map → chunk-6UHXQH7P.js.map} +0 -0
  242. /package/dist/{chunk-YVUZWLQG.js.map → chunk-AQGF5OG7.js.map} +0 -0
  243. /package/dist/{chunk-QCCJ3P4W.js.map → chunk-BNXBJOGL.js.map} +0 -0
  244. /package/dist/{chunk-S3JKDMD5.js.map → chunk-CXKMRKRF.js.map} +0 -0
  245. /package/dist/{chunk-RIXPZJUB.js.map → chunk-KTPG5VCH.js.map} +0 -0
  246. /package/dist/{chunk-V5SWX6KL.js.map → chunk-XXVM53P4.js.map} +0 -0
  247. /package/dist/{chunk-I2VVV5PQ.js.map → chunk-YY4YYM3E.js.map} +0 -0
  248. /package/src/providers/{OrganisationProvider.test.simple.tsx → OrganisationProvider.context.test.tsx} +0 -0
@@ -1,116 +0,0 @@
1
- /**
2
- * Super Admin Guard Component
3
- * @package @jmruthers/pace-core
4
- * @module Components/SuperAdminGuard
5
- * @since 1.0.0
6
- *
7
- * A reusable component for protecting content that should only be visible to super admins.
8
- */
9
-
10
- import React from 'react';
11
- import { useRBAC } from '../rbac/hooks/useRBAC';
12
-
13
- export interface SuperAdminGuardProps {
14
- children: React.ReactNode;
15
- fallback?: React.ReactNode;
16
- showDebugInfo?: boolean;
17
- }
18
-
19
- /**
20
- * Super Admin Guard Component
21
- *
22
- * Renders children only if the current user is a super admin.
23
- * Provides a fallback for non-super admin users.
24
- *
25
- * @param children - Content to render for super admins
26
- * @param fallback - Content to render for non-super admins (optional)
27
- * @param showDebugInfo - Whether to show debug information (optional)
28
- */
29
- export function SuperAdminGuard({
30
- children,
31
- fallback = null,
32
- showDebugInfo = false
33
- }: SuperAdminGuardProps) {
34
- const { isSuperAdmin, hasGlobalPermission, isLoading } = useRBAC();
35
-
36
- // Show loading state
37
- if (isLoading) {
38
- return (
39
- <div className="super-admin-guard-loading">
40
- <div className="loading-spinner" />
41
- <span>Checking permissions...</span>
42
- </div>
43
- );
44
- }
45
-
46
- // Show debug info if enabled
47
- if (showDebugInfo) {
48
- console.log('[SuperAdminGuard] Debug Info:', {
49
- isSuperAdmin,
50
- isLoading
51
- });
52
- }
53
-
54
- // Render children for super admins
55
- if (isSuperAdmin) {
56
- return (
57
- <div className="super-admin-content">
58
- {children}
59
- </div>
60
- );
61
- }
62
-
63
- // Render fallback for non-super admins
64
- return (
65
- <div className="super-admin-fallback">
66
- {fallback}
67
- </div>
68
- );
69
- }
70
-
71
- /**
72
- * Super Admin Badge Component
73
- *
74
- * Shows a visual indicator when the current user is a super admin.
75
- */
76
- export function SuperAdminBadge() {
77
- const { isSuperAdmin } = useRBAC();
78
-
79
- if (!isSuperAdmin) {
80
- return null;
81
- }
82
-
83
- return (
84
- <div className="super-admin-badge">
85
- <span className="badge-text">SUPER ADMIN</span>
86
- </div>
87
- );
88
- }
89
-
90
- /**
91
- * Super Admin Debug Panel
92
- *
93
- * Shows debug information about the current user's permissions.
94
- * Only visible to super admins and in development mode.
95
- */
96
- export function SuperAdminDebugPanel() {
97
- const { isSuperAdmin, isLoading } = useRBAC();
98
-
99
- // Only show in development or for super admins
100
- if (import.meta.env.MODE !== 'development' && !isSuperAdmin) {
101
- return null;
102
- }
103
-
104
- return (
105
- <div className="super-admin-debug-panel">
106
- <h4>Super Admin Debug Info</h4>
107
- <div className="debug-info">
108
- <p><strong>Is Super Admin:</strong> {isSuperAdmin ? 'Yes' : 'No'}</p>
109
- <p><strong>Is Loading:</strong> {isLoading ? 'Yes' : 'No'}</p>
110
- <p><strong>Environment:</strong> {import.meta.env.MODE}</p>
111
- </div>
112
- </div>
113
- );
114
- }
115
-
116
- export default SuperAdminGuard;
@@ -1,575 +0,0 @@
1
- /**
2
- * @file FileDisplay Component Tests
3
- * @description Comprehensive test suite for FileDisplay component
4
- * @package @jmruthers/pace-core
5
- * @module Components/FileDisplay
6
- * @since 1.0.0
7
- */
8
-
9
- import React from 'react';
10
- import { screen, waitFor, fireEvent } from '@testing-library/react';
11
- import userEvent from '@testing-library/user-event';
12
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
13
- import { FileDisplay } from '../FileDisplay';
14
- import { renderWithProviders, createMockSupabaseClient } from '../../__tests__/helpers/test-utils';
15
- import { FileCategory } from '../../types/file-reference';
16
-
17
- // Mock the useFileReferenceForRecord hook
18
- const mockUseFileReferenceForRecord = vi.fn();
19
- vi.mock('../../hooks/useFileReference', () => ({
20
- useFileReferenceForRecord: () => mockUseFileReferenceForRecord(),
21
- }));
22
-
23
- // Mock window.confirm
24
- const mockConfirm = vi.fn();
25
- Object.defineProperty(window, 'confirm', {
26
- value: mockConfirm,
27
- writable: true,
28
- });
29
-
30
- describe('FileDisplay Component', () => {
31
- const defaultProps = {
32
- supabase: createMockSupabaseClient(),
33
- table_name: 'test_table',
34
- record_id: 'test_record_123',
35
- organisation_id: 'test_org_123',
36
- };
37
-
38
- const mockFileReference = {
39
- id: 'file_123',
40
- table_name: 'test_table',
41
- record_id: 'test_record_123',
42
- file_path: 'uploads/test-file.jpg',
43
- file_metadata: {
44
- fileName: 'test-file.jpg',
45
- fileType: 'image/jpeg',
46
- fileSize: 1024000,
47
- category: FileCategory.IMAGES,
48
- },
49
- organisation_id: 'test_org_123',
50
- app_id: 'test_app',
51
- is_public: true,
52
- created_at: '2023-01-01T00:00:00Z',
53
- updated_at: '2023-01-01T00:00:00Z',
54
- };
55
-
56
- const mockFileReferences = [
57
- mockFileReference,
58
- {
59
- ...mockFileReference,
60
- id: 'file_456',
61
- file_metadata: {
62
- ...mockFileReference.file_metadata,
63
- fileName: 'document.pdf',
64
- fileType: 'application/pdf',
65
- fileSize: 2048000,
66
- },
67
- },
68
- ];
69
-
70
- beforeEach(() => {
71
- vi.clearAllMocks();
72
- mockConfirm.mockReturnValue(true);
73
-
74
- // Default mock implementation
75
- mockUseFileReferenceForRecord.mockReturnValue({
76
- isLoading: false,
77
- error: null,
78
- fileUrl: 'https://example.com/test-file.jpg',
79
- fileReference: null,
80
- fileReferences: [],
81
- fileCount: 0,
82
- loadFileReference: vi.fn(),
83
- loadFileUrl: vi.fn(),
84
- loadFileReferences: vi.fn(),
85
- loadFileCount: vi.fn(),
86
- deleteFile: vi.fn().mockResolvedValue(true),
87
- clearError: vi.fn(),
88
- });
89
- });
90
-
91
- describe('Rendering', () => {
92
- it('renders with basic props', () => {
93
- renderWithProviders(<FileDisplay {...defaultProps} />);
94
- expect(screen.getByText('No files found')).toBeInTheDocument();
95
- });
96
-
97
- it('renders with custom className', () => {
98
- const { container } = renderWithProviders(
99
- <FileDisplay {...defaultProps} className="custom-class" />
100
- );
101
- expect(container.firstChild).toHaveClass('custom-class');
102
- });
103
-
104
- it('renders with children', () => {
105
- renderWithProviders(
106
- <FileDisplay {...defaultProps}>
107
- <div data-testid="custom-child">Custom content</div>
108
- </FileDisplay>
109
- );
110
- expect(screen.getByTestId('custom-child')).toBeInTheDocument();
111
- });
112
- });
113
-
114
- describe('Loading State', () => {
115
- it('shows loading spinner when isLoading is true', () => {
116
- mockUseFileReferenceForRecord.mockReturnValue({
117
- ...mockUseFileReferenceForRecord(),
118
- isLoading: true,
119
- });
120
-
121
- renderWithProviders(<FileDisplay {...defaultProps} />);
122
-
123
- const spinner = document.querySelector('.animate-spin');
124
- expect(spinner).toBeInTheDocument();
125
- expect(spinner).toHaveClass('animate-spin');
126
- });
127
- });
128
-
129
- describe('Error State', () => {
130
- it('displays error message when error occurs', () => {
131
- const errorMessage = 'Failed to load files';
132
- mockUseFileReferenceForRecord.mockReturnValue({
133
- ...mockUseFileReferenceForRecord(),
134
- error: errorMessage,
135
- });
136
-
137
- renderWithProviders(<FileDisplay {...defaultProps} />);
138
-
139
- expect(screen.getByText(`Error loading file: ${errorMessage}`)).toBeInTheDocument();
140
- });
141
-
142
- it('calls clearError when try again button is clicked', async () => {
143
- const clearError = vi.fn();
144
- mockUseFileReferenceForRecord.mockReturnValue({
145
- ...mockUseFileReferenceForRecord(),
146
- error: 'Test error',
147
- clearError,
148
- });
149
-
150
- const user = userEvent.setup();
151
- renderWithProviders(<FileDisplay {...defaultProps} />);
152
-
153
- await user.click(screen.getByText('Try again'));
154
- expect(clearError).toHaveBeenCalledTimes(1);
155
- });
156
- });
157
-
158
- describe('Empty State', () => {
159
- it('shows no files found when fileCount is 0', () => {
160
- mockUseFileReferenceForRecord.mockReturnValue({
161
- ...mockUseFileReferenceForRecord(),
162
- fileCount: 0,
163
- });
164
-
165
- renderWithProviders(<FileDisplay {...defaultProps} />);
166
- expect(screen.getByText('No files found')).toBeInTheDocument();
167
- });
168
- });
169
-
170
- describe('Single File Display', () => {
171
- beforeEach(() => {
172
- mockUseFileReferenceForRecord.mockReturnValue({
173
- ...mockUseFileReferenceForRecord(),
174
- fileReference: mockFileReference,
175
- fileCount: 1,
176
- });
177
- });
178
-
179
- it('displays image file with image element', () => {
180
- renderWithProviders(
181
- <FileDisplay {...defaultProps} category={FileCategory.IMAGES} />
182
- );
183
-
184
- const image = screen.getByRole('img');
185
- expect(image).toBeInTheDocument();
186
- expect(image).toHaveAttribute('src', 'https://example.com/test-file.jpg');
187
- expect(image).toHaveAttribute('alt', 'test-file.jpg');
188
- });
189
-
190
- it('displays non-image file with file icon and metadata', () => {
191
- const pdfFileReference = {
192
- ...mockFileReference,
193
- file_metadata: {
194
- ...mockFileReference.file_metadata,
195
- fileName: 'document.pdf',
196
- fileType: 'application/pdf',
197
- fileSize: 2048000,
198
- },
199
- };
200
-
201
- mockUseFileReferenceForRecord.mockReturnValue({
202
- ...mockUseFileReferenceForRecord(),
203
- fileReference: pdfFileReference,
204
- fileCount: 1,
205
- });
206
-
207
- renderWithProviders(
208
- <FileDisplay {...defaultProps} category={FileCategory.GENERAL_DOCUMENTS} />
209
- );
210
-
211
- expect(screen.getByText('document.pdf')).toBeInTheDocument();
212
- expect(screen.getAllByText((content, element) =>
213
- element?.textContent?.includes('1.95 MB') && element?.textContent?.includes('application/pdf') || false
214
- )[0]).toBeInTheDocument();
215
- expect(screen.getByText('📄')).toBeInTheDocument(); // PDF icon
216
- });
217
-
218
- it('handles image load error by showing file icon', async () => {
219
- const user = userEvent.setup();
220
- renderWithProviders(
221
- <FileDisplay {...defaultProps} category={FileCategory.IMAGES} />
222
- );
223
-
224
- const image = screen.getByRole('img');
225
- await user.click(image); // Trigger error by clicking
226
-
227
- // Simulate image error
228
- fireEvent.error(image);
229
-
230
- await waitFor(() => {
231
- expect(screen.queryByRole('img')).not.toBeInTheDocument();
232
- // Check if the file is still displayed (either as img or text)
233
- const imgElement = screen.queryByRole('img', { name: 'test-file.jpg' });
234
- const textElement = screen.queryByText('test-file.jpg');
235
- expect(imgElement || textElement).toBeInTheDocument();
236
- });
237
- });
238
-
239
- it('shows delete button when showDelete is true', () => {
240
- renderWithProviders(
241
- <FileDisplay {...defaultProps} category={FileCategory.IMAGES} showDelete />
242
- );
243
-
244
- const deleteButton = screen.getByTitle('Delete file');
245
- expect(deleteButton).toBeInTheDocument();
246
- });
247
-
248
- it('does not show delete button when showDelete is false', () => {
249
- renderWithProviders(
250
- <FileDisplay {...defaultProps} category={FileCategory.IMAGES} showDelete={false} />
251
- );
252
-
253
- expect(screen.queryByTitle('Delete file')).not.toBeInTheDocument();
254
- });
255
-
256
- it('calls deleteFile when delete button is clicked and confirmed', async () => {
257
- const deleteFile = vi.fn().mockResolvedValue(true);
258
- mockUseFileReferenceForRecord.mockReturnValue({
259
- ...mockUseFileReferenceForRecord(),
260
- fileReference: mockFileReference,
261
- fileCount: 1,
262
- deleteFile,
263
- });
264
-
265
- const user = userEvent.setup();
266
- renderWithProviders(
267
- <FileDisplay {...defaultProps} category={FileCategory.IMAGES} showDelete />
268
- );
269
-
270
- await user.click(screen.getByTitle('Delete file'));
271
-
272
- expect(mockConfirm).toHaveBeenCalledWith('Are you sure you want to delete this file?');
273
- expect(deleteFile).toHaveBeenCalledWith(true);
274
- });
275
-
276
- it('does not delete file when user cancels confirmation', async () => {
277
- mockConfirm.mockReturnValue(false);
278
- const deleteFile = vi.fn();
279
- mockUseFileReferenceForRecord.mockReturnValue({
280
- ...mockUseFileReferenceForRecord(),
281
- fileReference: mockFileReference,
282
- fileCount: 1,
283
- deleteFile,
284
- });
285
-
286
- const user = userEvent.setup();
287
- renderWithProviders(
288
- <FileDisplay {...defaultProps} category={FileCategory.IMAGES} showDelete />
289
- );
290
-
291
- await user.click(screen.getByTitle('Delete file'));
292
-
293
- expect(mockConfirm).toHaveBeenCalledWith('Are you sure you want to delete this file?');
294
- expect(deleteFile).not.toHaveBeenCalled();
295
- });
296
- });
297
-
298
- describe('Multiple Files Display', () => {
299
- beforeEach(() => {
300
- mockUseFileReferenceForRecord.mockReturnValue({
301
- ...mockUseFileReferenceForRecord(),
302
- fileReferences: mockFileReferences,
303
- fileCount: 2,
304
- });
305
- });
306
-
307
- it('displays multiple files in a list', () => {
308
- renderWithProviders(<FileDisplay {...defaultProps} />);
309
-
310
- expect(screen.getByText('test-file.jpg')).toBeInTheDocument();
311
- expect(screen.getByText('document.pdf')).toBeInTheDocument();
312
- expect(screen.getAllByText((content, element) =>
313
- element?.textContent?.includes('1000 KB') && element?.textContent?.includes('image/jpeg') || false
314
- )[0]).toBeInTheDocument();
315
- expect(screen.getAllByText((content, element) =>
316
- element?.textContent?.includes('1.95 MB') && element?.textContent?.includes('application/pdf') || false
317
- )[0]).toBeInTheDocument();
318
- });
319
-
320
- it('shows delete buttons for each file when showDelete is true', () => {
321
- renderWithProviders(<FileDisplay {...defaultProps} showDelete />);
322
-
323
- const deleteButtons = screen.getAllByTitle('Delete file');
324
- expect(deleteButtons).toHaveLength(2);
325
- });
326
-
327
- it('calls deleteFile for specific file when delete button is clicked', async () => {
328
- const deleteFile = vi.fn().mockResolvedValue(true);
329
- mockUseFileReferenceForRecord.mockReturnValue({
330
- ...mockUseFileReferenceForRecord(),
331
- fileReferences: mockFileReferences,
332
- fileCount: 2,
333
- deleteFile,
334
- });
335
-
336
- const user = userEvent.setup();
337
- renderWithProviders(<FileDisplay {...defaultProps} showDelete />);
338
-
339
- const deleteButtons = screen.getAllByTitle('Delete file');
340
- await user.click(deleteButtons[0]);
341
-
342
- expect(deleteFile).toHaveBeenCalledWith(true);
343
- });
344
- });
345
-
346
- describe('File Icons', () => {
347
- it('shows correct icon for different file types', () => {
348
- const testCases = [
349
- { fileType: 'image/jpeg', expectedIcon: '🖼️' },
350
- { fileType: 'video/mp4', expectedIcon: '🎥' },
351
- { fileType: 'audio/mp3', expectedIcon: '🎵' },
352
- { fileType: 'application/pdf', expectedIcon: '📄' },
353
- { fileType: 'application/msword', expectedIcon: '📝' },
354
- { fileType: 'application/vnd.ms-excel', expectedIcon: '📊' },
355
- { fileType: 'application/vnd.ms-powerpoint', expectedIcon: '📊' },
356
- { fileType: 'text/plain', expectedIcon: '📁' },
357
- ];
358
-
359
- testCases.forEach(({ fileType, expectedIcon }) => {
360
- const fileRef = {
361
- ...mockFileReference,
362
- file_metadata: {
363
- ...mockFileReference.file_metadata,
364
- fileType,
365
- },
366
- };
367
-
368
- mockUseFileReferenceForRecord.mockReturnValue({
369
- ...mockUseFileReferenceForRecord(),
370
- fileReference: fileRef,
371
- fileCount: 1,
372
- });
373
-
374
- const { unmount } = renderWithProviders(
375
- <FileDisplay {...defaultProps} category={FileCategory.GENERAL_DOCUMENTS} />
376
- );
377
-
378
- // For image files, check if it's an img element or icon
379
- if (fileType.startsWith('image/')) {
380
- const imgElement = screen.queryByRole('img');
381
- if (imgElement) {
382
- expect(imgElement).toBeInTheDocument();
383
- } else {
384
- expect(screen.getByText(expectedIcon)).toBeInTheDocument();
385
- }
386
- } else {
387
- expect(screen.getByText(expectedIcon)).toBeInTheDocument();
388
- }
389
- unmount();
390
- });
391
- });
392
- });
393
-
394
- describe('File Size Formatting', () => {
395
- it('formats file sizes correctly', () => {
396
- const testCases = [
397
- { size: 0, expected: '0 Bytes' },
398
- { size: 1024, expected: '1 KB' },
399
- { size: 1024 * 1024, expected: '1 MB' },
400
- { size: 1024 * 1024 * 1024, expected: '1 GB' },
401
- { size: 1536, expected: '1.5 KB' },
402
- ];
403
-
404
- testCases.forEach(({ size, expected }) => {
405
- const fileRef = {
406
- ...mockFileReference,
407
- file_metadata: {
408
- ...mockFileReference.file_metadata,
409
- fileSize: size,
410
- },
411
- };
412
-
413
- mockUseFileReferenceForRecord.mockReturnValue({
414
- ...mockUseFileReferenceForRecord(),
415
- fileReference: fileRef,
416
- fileCount: 1,
417
- });
418
-
419
- const { unmount } = renderWithProviders(
420
- <FileDisplay {...defaultProps} category={FileCategory.GENERAL_DOCUMENTS} />
421
- );
422
-
423
- // For image files, check if it's an img element or text
424
- if (fileRef.file_metadata.fileType.startsWith('image/')) {
425
- const imgElement = screen.queryByRole('img');
426
- if (imgElement) {
427
- expect(imgElement).toBeInTheDocument();
428
- } else {
429
- expect(screen.getByText(expected)).toBeInTheDocument();
430
- }
431
- } else {
432
- expect(screen.getByText(expected)).toBeInTheDocument();
433
- }
434
- unmount();
435
- });
436
- });
437
- });
438
-
439
- describe('Accessibility', () => {
440
- it('has proper alt text for images', () => {
441
- mockUseFileReferenceForRecord.mockReturnValue({
442
- ...mockUseFileReferenceForRecord(),
443
- fileReference: mockFileReference,
444
- fileCount: 1,
445
- });
446
-
447
- renderWithProviders(
448
- <FileDisplay {...defaultProps} category={FileCategory.IMAGES} />
449
- );
450
-
451
- const image = screen.getByRole('img');
452
- expect(image).toHaveAttribute('alt', 'test-file.jpg');
453
- });
454
-
455
- it('has proper title attributes for delete buttons', () => {
456
- mockUseFileReferenceForRecord.mockReturnValue({
457
- ...mockUseFileReferenceForRecord(),
458
- fileReference: mockFileReference,
459
- fileCount: 1,
460
- });
461
-
462
- renderWithProviders(
463
- <FileDisplay {...defaultProps} category={FileCategory.IMAGES} showDelete />
464
- );
465
-
466
- const deleteButton = screen.getByTitle('Delete file');
467
- expect(deleteButton).toBeInTheDocument();
468
- });
469
- });
470
-
471
- describe('Integration', () => {
472
- it('loads file data on mount', () => {
473
- const loadFileCount = vi.fn();
474
- const loadFileReference = vi.fn();
475
- const loadFileReferences = vi.fn();
476
-
477
- mockUseFileReferenceForRecord.mockReturnValue({
478
- ...mockUseFileReferenceForRecord(),
479
- loadFileCount,
480
- loadFileReference,
481
- loadFileReferences,
482
- });
483
-
484
- renderWithProviders(
485
- <FileDisplay {...defaultProps} category={FileCategory.IMAGES} />
486
- );
487
-
488
- expect(loadFileCount).toHaveBeenCalledTimes(1);
489
- expect(loadFileReference).toHaveBeenCalledTimes(1);
490
- expect(loadFileReferences).not.toHaveBeenCalled();
491
- });
492
-
493
- it('loads file references when no category is specified', () => {
494
- const loadFileCount = vi.fn();
495
- const loadFileReference = vi.fn();
496
- const loadFileReferences = vi.fn();
497
-
498
- mockUseFileReferenceForRecord.mockReturnValue({
499
- ...mockUseFileReferenceForRecord(),
500
- loadFileCount,
501
- loadFileReference,
502
- loadFileReferences,
503
- });
504
-
505
- renderWithProviders(<FileDisplay {...defaultProps} />);
506
-
507
- expect(loadFileCount).toHaveBeenCalledTimes(1);
508
- expect(loadFileReferences).toHaveBeenCalledTimes(1);
509
- expect(loadFileReference).not.toHaveBeenCalled();
510
- });
511
-
512
- it('loads file URL when file reference is available', () => {
513
- const loadFileUrl = vi.fn();
514
- mockUseFileReferenceForRecord.mockReturnValue({
515
- ...mockUseFileReferenceForRecord(),
516
- fileReference: mockFileReference,
517
- fileCount: 1,
518
- loadFileUrl,
519
- });
520
-
521
- renderWithProviders(
522
- <FileDisplay {...defaultProps} category={FileCategory.IMAGES} />
523
- );
524
-
525
- expect(loadFileUrl).toHaveBeenCalledTimes(1);
526
- });
527
- });
528
-
529
- describe('Error Handling', () => {
530
- it('handles missing file metadata gracefully', () => {
531
- const fileRefWithoutMetadata = {
532
- ...mockFileReference,
533
- file_metadata: {
534
- fileName: undefined,
535
- fileType: undefined,
536
- fileSize: undefined,
537
- category: FileCategory.IMAGES,
538
- },
539
- };
540
-
541
- mockUseFileReferenceForRecord.mockReturnValue({
542
- ...mockUseFileReferenceForRecord(),
543
- fileReference: fileRefWithoutMetadata,
544
- fileCount: 1,
545
- });
546
-
547
- renderWithProviders(
548
- <FileDisplay {...defaultProps} category={FileCategory.IMAGES} />
549
- );
550
-
551
- expect(screen.getByText('Unknown file')).toBeInTheDocument();
552
- });
553
-
554
- it('handles delete failure gracefully', async () => {
555
- const deleteFile = vi.fn().mockResolvedValue(false);
556
- mockUseFileReferenceForRecord.mockReturnValue({
557
- ...mockUseFileReferenceForRecord(),
558
- fileReference: mockFileReference,
559
- fileCount: 1,
560
- deleteFile,
561
- });
562
-
563
- const user = userEvent.setup();
564
- renderWithProviders(
565
- <FileDisplay {...defaultProps} category={FileCategory.IMAGES} showDelete />
566
- );
567
-
568
- await user.click(screen.getByTitle('Delete file'));
569
-
570
- expect(deleteFile).toHaveBeenCalledWith(true);
571
- // File should still be displayed since deletion failed
572
- expect(screen.getByRole('img', { name: 'test-file.jpg' })).toBeInTheDocument();
573
- });
574
- });
575
- });