@jmruthers/pace-core 0.5.87 → 0.5.89

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 (243) hide show
  1. package/dist/{AuthService-Df3IozMG.d.ts → AuthService-DcTI5Ov4.d.ts} +9 -0
  2. package/dist/{DataTable-FA6EUX5M.js → DataTable-PWBMKMOG.js} +7 -7
  3. package/dist/{PublicLoadingSpinner-DecuJBX0.d.ts → PublicLoadingSpinner-BQXD1fbO.d.ts} +160 -130
  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-ZFLOV3OM.js → chunk-7VJDS5QD.js} +401 -16
  11. package/dist/chunk-7VJDS5QD.js.map +1 -0
  12. package/dist/{chunk-YVUZWLQG.js → chunk-AQGF5OG7.js} +3 -3
  13. package/dist/{chunk-CVMVPYAL.js → chunk-BDZUMRBD.js} +3 -5
  14. package/dist/chunk-BDZUMRBD.js.map +1 -0
  15. package/dist/{chunk-KAY3K5TP.js → chunk-BNXBJOGL.js} +4 -4
  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-RIXPZJUB.js → chunk-KTPG5VCH.js} +2 -2
  20. package/dist/{chunk-2FQEQUJT.js → chunk-KWICIQVK.js} +4 -4
  21. package/dist/{chunk-WUXCWRL6.js → chunk-XJ2HZOBU.js} +6 -1
  22. package/dist/chunk-XJ2HZOBU.js.map +1 -0
  23. package/dist/{chunk-I7O3RSMN.js → chunk-YWAFPVJA.js} +1298 -769
  24. package/dist/chunk-YWAFPVJA.js.map +1 -0
  25. package/dist/{chunk-I2VVV5PQ.js → chunk-YY4YYM3E.js} +2 -2
  26. package/dist/components.d.ts +6 -55
  27. package/dist/components.js +24 -205
  28. package/dist/components.js.map +1 -1
  29. package/dist/{file-reference-9xUOnwyt.d.ts → file-reference-C9isKNPn.d.ts} +67 -2
  30. package/dist/hooks.js +9 -8
  31. package/dist/hooks.js.map +1 -1
  32. package/dist/index.d.ts +152 -26
  33. package/dist/index.js +64 -194
  34. package/dist/index.js.map +1 -1
  35. package/dist/providers.d.ts +5 -3
  36. package/dist/providers.js +3 -3
  37. package/dist/rbac/index.js +8 -8
  38. package/dist/types.d.ts +2 -1
  39. package/dist/types.js +3 -3
  40. package/dist/utils.js +2 -2
  41. package/docs/DOCUMENTATION_AUDIT.md +6 -6
  42. package/docs/DOCUMENTATION_STANDARD.md +137 -0
  43. package/docs/README.md +1 -1
  44. package/docs/api/classes/ColumnFactory.md +1 -1
  45. package/docs/api/classes/ErrorBoundary.md +1 -1
  46. package/docs/api/classes/InvalidScopeError.md +1 -1
  47. package/docs/api/classes/MissingUserContextError.md +1 -1
  48. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  49. package/docs/api/classes/PermissionDeniedError.md +1 -1
  50. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  51. package/docs/api/classes/RBACAuditManager.md +1 -1
  52. package/docs/api/classes/RBACCache.md +1 -1
  53. package/docs/api/classes/RBACEngine.md +1 -1
  54. package/docs/api/classes/RBACError.md +1 -1
  55. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  56. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  57. package/docs/api/classes/StorageUtils.md +83 -40
  58. package/docs/api/enums/FileCategory.md +56 -1
  59. package/docs/api/interfaces/AggregateConfig.md +1 -1
  60. package/docs/api/interfaces/ButtonProps.md +1 -1
  61. package/docs/api/interfaces/CardProps.md +1 -1
  62. package/docs/api/interfaces/ColorPalette.md +1 -1
  63. package/docs/api/interfaces/ColorShade.md +1 -1
  64. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  65. package/docs/api/interfaces/DataRecord.md +1 -1
  66. package/docs/api/interfaces/DataTableAction.md +1 -1
  67. package/docs/api/interfaces/DataTableColumn.md +1 -1
  68. package/docs/api/interfaces/DataTableProps.md +1 -1
  69. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  70. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  71. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  72. package/docs/api/interfaces/EventLogoProps.md +11 -11
  73. package/docs/api/interfaces/FileDisplayProps.md +10 -10
  74. package/docs/api/interfaces/FileMetadata.md +1 -1
  75. package/docs/api/interfaces/FileReference.md +1 -1
  76. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  77. package/docs/api/interfaces/FileUploadOptions.md +8 -8
  78. package/docs/api/interfaces/FileUploadProps.md +137 -42
  79. package/docs/api/interfaces/FooterProps.md +1 -1
  80. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  81. package/docs/api/interfaces/InputProps.md +1 -1
  82. package/docs/api/interfaces/LabelProps.md +1 -1
  83. package/docs/api/interfaces/LoginFormProps.md +1 -1
  84. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  85. package/docs/api/interfaces/NavigationContextType.md +1 -1
  86. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  87. package/docs/api/interfaces/NavigationItem.md +1 -1
  88. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  89. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  90. package/docs/api/interfaces/Organisation.md +1 -1
  91. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  92. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  93. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  94. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  95. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  96. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  97. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  98. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  99. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  100. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  101. package/docs/api/interfaces/PaletteData.md +1 -1
  102. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  103. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  104. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  105. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  106. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  107. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  108. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  109. package/docs/api/interfaces/RBACConfig.md +1 -1
  110. package/docs/api/interfaces/RBACLogger.md +1 -1
  111. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  112. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  113. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  114. package/docs/api/interfaces/RouteConfig.md +1 -1
  115. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  116. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  117. package/docs/api/interfaces/StorageConfig.md +1 -1
  118. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  119. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  120. package/docs/api/interfaces/StorageListOptions.md +1 -1
  121. package/docs/api/interfaces/StorageListResult.md +1 -1
  122. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  123. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  124. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  125. package/docs/api/interfaces/StyleImport.md +1 -1
  126. package/docs/api/interfaces/SwitchProps.md +1 -1
  127. package/docs/api/interfaces/ToastActionElement.md +1 -1
  128. package/docs/api/interfaces/ToastProps.md +1 -1
  129. package/docs/api/interfaces/UnifiedAuthContextType.md +83 -50
  130. package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
  131. package/docs/api/interfaces/UseEventLogoOptions.md +74 -0
  132. package/docs/api/interfaces/UseEventLogoReturn.md +81 -0
  133. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  134. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  135. package/docs/api/interfaces/UsePublicEventLogoOptions.md +6 -6
  136. package/docs/api/interfaces/UsePublicEventLogoReturn.md +6 -6
  137. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  138. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  139. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  140. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  141. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  142. package/docs/api/interfaces/UserEventAccess.md +11 -11
  143. package/docs/api/interfaces/UserMenuProps.md +1 -1
  144. package/docs/api/interfaces/UserProfile.md +1 -1
  145. package/docs/api/modules.md +290 -95
  146. package/docs/api-reference/components.md +1 -18
  147. package/docs/api-reference/hooks.md +1 -4
  148. package/docs/best-practices/testing.md +2 -0
  149. package/docs/documentation-index.md +1 -1
  150. package/docs/getting-started/faq.md +1 -1
  151. package/docs/implementation-guides/file-reference-system.md +592 -58
  152. package/docs/implementation-guides/file-upload-storage.md +137 -73
  153. package/docs/implementation-guides/public-pages-advanced.md +10 -0
  154. package/docs/rbac/super-admin-guide.md +18 -70
  155. package/docs/testing/README.md +2 -0
  156. package/package.json +1 -1
  157. package/src/__tests__/TEST_STANDARD.md +674 -0
  158. package/src/__tests__/helpers/test-utils.tsx +3 -2
  159. package/src/components/DataTable/__tests__/{DataTable.comprehensive.test.tsx.skip → DataTable.comprehensive.test.tsx} +17 -18
  160. package/src/components/DataTable/__tests__/{DataTable.test.tsx.skip → DataTable.test.tsx} +14 -22
  161. package/src/components/DataTable/__tests__/{ssr.strict-mode.test.tsx.skip → ssr.strict-mode.test.tsx} +42 -47
  162. package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +1 -1
  163. package/src/components/DataTable/examples/__tests__/PerformanceExample.test.tsx +13 -4
  164. package/src/components/DataTable/utils/__tests__/COVERAGE_NOTE.md +1 -1
  165. package/src/components/DataTable/utils/__tests__/performanceUtils.test.ts +10 -6
  166. package/src/components/FileDisplay/FileDisplay.test.tsx +257 -0
  167. package/src/components/{FileDisplay.tsx → FileDisplay/FileDisplay.tsx} +111 -10
  168. package/src/components/FileDisplay/index.tsx +4 -0
  169. package/src/components/FileUpload/FileUpload.test.tsx +171 -621
  170. package/src/components/FileUpload/FileUpload.tsx +512 -168
  171. package/src/components/FileUpload/index.tsx +4 -0
  172. package/src/components/Progress/Progress.test.tsx +38 -0
  173. package/src/components/PublicLayout/EventLogo.tsx +6 -4
  174. package/src/components/Select/Select.test.tsx +1 -1
  175. package/src/components/SessionRestorationLoader.tsx +48 -0
  176. package/src/components/Toast/Toast.tsx +13 -8
  177. package/src/components/index.ts +16 -16
  178. package/src/hooks/__tests__/ServiceHooks.test.tsx +615 -0
  179. package/src/hooks/public/usePublicEventLogo.ts +16 -20
  180. package/src/hooks/useEventLogo.ts +316 -0
  181. package/src/hooks/useEvents.ts +0 -5
  182. package/src/hooks/useFileReference.test.ts +659 -0
  183. package/src/hooks/useFileReference.ts +207 -3
  184. package/src/hooks/useSessionRestoration.ts +64 -0
  185. package/src/index.ts +17 -5
  186. package/src/providers/{UnifiedAuthProvider.test.simple.tsx → UnifiedAuthProvider.smoke.test.tsx} +81 -60
  187. package/src/providers/services/AuthServiceProvider.tsx +27 -3
  188. package/src/providers/services/UnifiedAuthProvider.tsx +34 -5
  189. package/src/rbac/{engine.test.simple.ts → RBACEngine.smoke.test.ts} +17 -12
  190. package/src/services/AuthService.ts +142 -20
  191. package/src/services/EventService.ts +0 -4
  192. package/src/types/auth.ts +15 -0
  193. package/src/types/file-reference.ts +73 -1
  194. package/src/types/index.ts +1 -0
  195. package/src/utils/__tests__/organisationContext.unit.test.ts +2 -4
  196. package/src/utils/appNameResolver.simple.test.ts +99 -29
  197. package/src/utils/file-reference.test.ts +535 -0
  198. package/src/utils/file-reference.ts +200 -30
  199. package/src/utils/organisationContext.test.ts +5 -19
  200. package/src/utils/organisationContext.ts +3 -5
  201. package/src/utils/storage/README.md +269 -262
  202. package/src/utils/storage/config.ts +9 -0
  203. package/src/utils/storage/helpers.test.ts +735 -0
  204. package/src/utils/storage/helpers.ts +189 -16
  205. package/src/utils/storage/index.ts +3 -0
  206. package/src/validation/__tests__/sanitization.unit.test.ts +1 -1
  207. package/src/validation/__tests__/schemaUtils.unit.test.ts +1 -1
  208. package/src/validation/__tests__/user.unit.test.ts +1 -1
  209. package/dist/chunk-5BN3YGNK.js.map +0 -1
  210. package/dist/chunk-CVMVPYAL.js.map +0 -1
  211. package/dist/chunk-I7O3RSMN.js.map +0 -1
  212. package/dist/chunk-WUXCWRL6.js.map +0 -1
  213. package/dist/chunk-ZFLOV3OM.js.map +0 -1
  214. package/docs/CONTENT_AUDIT_REPORT.md +0 -253
  215. package/docs/STYLE_GUIDE.md +0 -37
  216. package/examples/RBAC/__tests__/PermissionExample.test.tsx +0 -150
  217. package/examples/public-pages/__tests__/PublicPageUsageExample.test.tsx +0 -159
  218. package/src/__tests__/TEST_GUIDE_CURSOR.md +0 -1605
  219. package/src/__tests__/TEST_GUIDE_HUMAN.md +0 -103
  220. package/src/components/FileUpload/FileUpload.example.tsx +0 -218
  221. package/src/components/FileUpload/index.ts +0 -6
  222. package/src/components/FileUpload.tsx +0 -176
  223. package/src/components/Progress/index.ts +0 -3
  224. package/src/components/PublicLayout/__tests__/EventLogo.test.tsx +0 -666
  225. package/src/components/SuperAdminGuard.tsx +0 -116
  226. package/src/components/__tests__/FileDisplay.test.tsx +0 -575
  227. package/src/components/__tests__/FileUpload.test.tsx +0 -446
  228. package/src/components/__tests__/SuperAdminGuard.test.tsx +0 -627
  229. package/src/components/examples/PermissionExample.tsx +0 -173
  230. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +0 -583
  231. package/src/hooks/__tests__/usePublicEventLogo.unit.test.ts +0 -640
  232. package/src/types/__tests__/file-reference.test.ts +0 -447
  233. package/src/utils/__tests__/file-reference.test.ts +0 -383
  234. /package/dist/{DataTable-FA6EUX5M.js.map → DataTable-PWBMKMOG.js.map} +0 -0
  235. /package/dist/{UnifiedAuthProvider-K2IZAY5F.js.map → UnifiedAuthProvider-5D3HEQND.js.map} +0 -0
  236. /package/dist/{chunk-NTW3KGS4.js.map → chunk-6UHXQH7P.js.map} +0 -0
  237. /package/dist/{chunk-YVUZWLQG.js.map → chunk-AQGF5OG7.js.map} +0 -0
  238. /package/dist/{chunk-KAY3K5TP.js.map → chunk-BNXBJOGL.js.map} +0 -0
  239. /package/dist/{chunk-S3JKDMD5.js.map → chunk-CXKMRKRF.js.map} +0 -0
  240. /package/dist/{chunk-RIXPZJUB.js.map → chunk-KTPG5VCH.js.map} +0 -0
  241. /package/dist/{chunk-2FQEQUJT.js.map → chunk-KWICIQVK.js.map} +0 -0
  242. /package/dist/{chunk-I2VVV5PQ.js.map → chunk-YY4YYM3E.js.map} +0 -0
  243. /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
- });