@jmruthers/pace-core 0.6.9 → 0.6.11

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 (1182) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/audit-tool/00-dependencies.cjs +46 -13
  3. package/audit-tool/audits/01-pace-core-compliance.cjs +96 -21
  4. package/audit-tool/audits/02-project-structure.cjs +74 -2
  5. package/audit-tool/audits/03-architecture.cjs +220 -20
  6. package/audit-tool/audits/04-code-quality.cjs +95 -3
  7. package/audit-tool/audits/05-styling.cjs +19 -7
  8. package/audit-tool/audits/06-security-rbac.cjs +214 -25
  9. package/audit-tool/audits/07-api-tech-stack.cjs +31 -15
  10. package/audit-tool/audits/08-testing-documentation.cjs +11 -3
  11. package/audit-tool/audits/09-operations.cjs +19 -7
  12. package/audit-tool/index.cjs +22 -11
  13. package/audit-tool/utils/report-utils.cjs +4 -0
  14. package/cursor-rules/01-pace-core-compliance.mdc +1 -0
  15. package/cursor-rules/02-project-structure.mdc +3 -26
  16. package/cursor-rules/03-architecture.mdc +3 -1
  17. package/cursor-rules/04-code-quality.mdc +1 -0
  18. package/cursor-rules/05-styling.mdc +120 -8
  19. package/cursor-rules/06-security-rbac.mdc +126 -2
  20. package/cursor-rules/07-api-tech-stack.mdc +1 -0
  21. package/cursor-rules/08-testing-documentation.mdc +1 -0
  22. package/cursor-rules/09-operations.mdc +1 -0
  23. package/dist/DataTable-EFYP2QLE.js +16 -0
  24. package/dist/InactivityServiceProvider-BbxwwDz1.d.ts +308 -0
  25. package/dist/UnifiedAuthProvider-Bkt_tzdS.d.ts +183 -0
  26. package/dist/api-BZR2CYXL.js +5 -0
  27. package/dist/api-result-USV1Czr-.d.ts +51 -0
  28. package/dist/assets/app-icons/admin_favicon.svg +462 -0
  29. package/dist/assets/app-icons/base_favicon.svg +85 -0
  30. package/dist/assets/app-icons/cake_favicon.svg +68 -0
  31. package/dist/assets/app-icons/core_favicon.svg +256 -0
  32. package/dist/assets/app-icons/gear_favicon.svg +91 -0
  33. package/dist/assets/app-icons/medi_favicon.svg +92 -0
  34. package/dist/assets/app-icons/mint_favicon.svg +83 -0
  35. package/dist/assets/app-icons/pace_favicon.svg +49 -0
  36. package/dist/assets/app-icons/pump_favicon.svg +68 -0
  37. package/dist/assets/app-icons/seed_favicon.svg +91 -0
  38. package/dist/assets/app-icons/team_favicon.svg +67 -0
  39. package/dist/assets/app-icons/trac_favicon.svg +112 -0
  40. package/dist/assets/app-icons/trip_favicon.svg +102 -0
  41. package/dist/audit-HI2DHUVU.js +4 -0
  42. package/dist/auth-JvdRVaud.d.ts +49 -0
  43. package/dist/chunk-2DL2WSOE.js +327 -0
  44. package/dist/chunk-2OEVOGGR.js +9598 -0
  45. package/dist/chunk-44CNXN4P.js +15 -0
  46. package/dist/chunk-4R3T5ENU.js +2943 -0
  47. package/dist/chunk-7A6IMHH2.js +2321 -0
  48. package/dist/chunk-BTHN5MKC.js +121 -0
  49. package/dist/chunk-CU2BU2MQ.js +2 -0
  50. package/dist/chunk-D6BMFMQZ.js +200 -0
  51. package/dist/chunk-DDMPHZ3D.js +58 -0
  52. package/dist/chunk-ENLXB7GP.js +721 -0
  53. package/dist/chunk-J2KQK6DG.js +2159 -0
  54. package/dist/chunk-KJXRL3XE.js +6434 -0
  55. package/dist/chunk-L5LFKKLJ.js +61 -0
  56. package/dist/chunk-PCSHBLPB.js +811 -0
  57. package/dist/chunk-QRYSEPHB.js +429 -0
  58. package/dist/chunk-RMLY6KB5.js +187 -0
  59. package/dist/chunk-SACF5YSM.js +31 -0
  60. package/dist/chunk-UZNAFKGW.js +125 -0
  61. package/dist/chunk-V7FTM2LU.js +1080 -0
  62. package/dist/chunk-WY6Y7KC3.js +264 -0
  63. package/dist/chunk-XOJME5T7.js +407 -0
  64. package/dist/chunk-XPFVT3GN.js +492 -0
  65. package/dist/chunk-YFTFFJIV.js +529 -0
  66. package/dist/chunk-YYTWKVHO.js +1334 -0
  67. package/dist/components.d.ts +12 -89
  68. package/dist/components.js +23 -55
  69. package/dist/database.generated-qkdoiVrJ.d.ts +9441 -0
  70. package/dist/eslint-rules/index.cjs +3 -0
  71. package/dist/eslint-rules/rules/03-architecture.cjs +74 -0
  72. package/dist/eslint-rules/rules/05-styling.cjs +507 -0
  73. package/dist/eslint-rules/rules/06-security-rbac.cjs +84 -0
  74. package/dist/event-BfCox3N2.d.ts +265 -0
  75. package/dist/file-reference-DU1hcawx.d.ts +164 -0
  76. package/dist/functions-DH45k8ec.d.ts +208 -0
  77. package/dist/hooks.d.ts +28 -14
  78. package/dist/hooks.js +90 -56
  79. package/dist/icons/index.d.ts +1 -0
  80. package/dist/icons/index.js +1 -0
  81. package/dist/index.d.ts +392 -155
  82. package/dist/index.js +337 -347
  83. package/dist/pagination-BW1mqywp.d.ts +201 -0
  84. package/dist/papaparseLoader-WG2UXQ22.js +7 -0
  85. package/dist/providers.d.ts +29 -14
  86. package/dist/providers.js +7 -5
  87. package/dist/rbac/eslint-rules.js +2 -2
  88. package/dist/rbac/index.d.ts +180 -351
  89. package/dist/rbac/index.js +13 -11
  90. package/dist/theming/runtime.d.ts +28 -5
  91. package/dist/theming/runtime.js +2 -2
  92. package/dist/timezone-BTWWXKVY.d.ts +696 -0
  93. package/dist/types-BE2sEHKd.d.ts +55 -0
  94. package/dist/types-CvOPXWWZ.d.ts +111 -0
  95. package/dist/types-Dr8sNhER.d.ts +50 -0
  96. package/dist/types.d.ts +20 -13
  97. package/dist/types.js +1 -0
  98. package/dist/usePublicPageContext-B91dGYW1.d.ts +4367 -0
  99. package/dist/usePublicRouteParams-BgV6VhMi.d.ts +946 -0
  100. package/dist/utils.d.ts +338 -156
  101. package/dist/utils.js +78 -60
  102. package/dist/validation-g5n0hDkh.d.ts +177 -0
  103. package/docs/api/modules.md +1226 -1094
  104. package/docs/api-reference/components.md +5 -5
  105. package/docs/api-reference/rpc-functions.md +12 -3
  106. package/docs/core-concepts/rbac-system.md +8 -0
  107. package/docs/getting-started/cursor-rules.md +17 -20
  108. package/docs/getting-started/dependencies.md +1 -1
  109. package/docs/getting-started/setup.md +235 -0
  110. package/docs/implementation-guides/authentication.md +27 -0
  111. package/docs/implementation-guides/data-tables.md +365 -10
  112. package/docs/migration/ApiResult-migration.md +25 -0
  113. package/docs/rbac/RBAC_CONTRACT.md +0 -12
  114. package/docs/rbac/api-reference.md +33 -31
  115. package/docs/standards/0-standards-overview.md +50 -15
  116. package/docs/standards/1-pace-core-compliance-standards.md +62 -57
  117. package/docs/standards/2-project-structure-standards.md +45 -90
  118. package/docs/standards/3-architecture-standards.md +41 -1
  119. package/docs/standards/4-code-quality-standards.md +26 -6
  120. package/docs/standards/5-styling-standards.md +35 -1
  121. package/docs/standards/6-security-rbac-standards.md +288 -7
  122. package/docs/standards/7-api-tech-stack-standards.md +116 -17
  123. package/docs/standards/8-testing-documentation-standards.md +31 -0
  124. package/docs/standards/9-operations-standards.md +19 -0
  125. package/docs/standards/README.md +20 -201
  126. package/docs/testing/README.md +10 -0
  127. package/docs/testing/test-setup-for-consumers.md +916 -0
  128. package/docs/troubleshooting/common-issues.md +17 -1
  129. package/docs/troubleshooting/organisation-context-setup.md +8 -0
  130. package/docs/troubleshooting/print-event-name-css-variable-analysis.md +217 -0
  131. package/eslint-config-pace-core.cjs +24 -0
  132. package/package.json +14 -20
  133. package/scripts/build-docs.js +180 -0
  134. package/scripts/setup.cjs +536 -0
  135. package/scripts/validate.cjs +480 -0
  136. package/src/__mocks__/lucide-react.ts +0 -2
  137. package/src/__tests__/helpers/component-test-utils.test.tsx +260 -0
  138. package/src/__tests__/helpers/optimized-test-setup.test.ts +224 -0
  139. package/src/__tests__/helpers/supabaseMock.test.ts +273 -0
  140. package/src/__tests__/helpers/test-providers.test.tsx +99 -0
  141. package/src/__tests__/helpers/test-providers.tsx +37 -39
  142. package/src/__tests__/helpers/test-utils.test.tsx +447 -0
  143. package/src/__tests__/helpers/timer-utils.test.ts +371 -0
  144. package/src/assets/app-icons/admin_favicon.svg +462 -0
  145. package/src/assets/app-icons/base_favicon.svg +85 -0
  146. package/src/assets/app-icons/cake_favicon.svg +68 -0
  147. package/src/assets/app-icons/core_favicon.svg +256 -0
  148. package/src/assets/app-icons/gear_favicon.svg +91 -0
  149. package/src/assets/app-icons/index.test.ts +304 -0
  150. package/src/assets/app-icons/index.ts +83 -0
  151. package/src/assets/app-icons/medi_favicon.svg +92 -0
  152. package/src/assets/app-icons/mint_favicon.svg +83 -0
  153. package/src/assets/app-icons/pace_favicon.svg +49 -0
  154. package/src/assets/app-icons/pump_favicon.svg +68 -0
  155. package/src/assets/app-icons/seed_favicon.svg +91 -0
  156. package/src/assets/app-icons/team_favicon.svg +67 -0
  157. package/src/assets/app-icons/trac_favicon.svg +112 -0
  158. package/src/assets/app-icons/trip_favicon.svg +102 -0
  159. package/src/components/AddressField/AddressField.test.tsx +379 -4
  160. package/src/components/AddressField/AddressField.tsx +239 -213
  161. package/src/components/AddressField/types.ts +2 -2
  162. package/src/components/Alert/Alert.test.tsx +35 -25
  163. package/src/components/Alert/Alert.tsx +8 -8
  164. package/src/components/AppSwitcher/AppSwitcher.test.tsx +1250 -0
  165. package/src/components/AppSwitcher/AppSwitcher.tsx +315 -0
  166. package/src/components/Avatar/Avatar.test.tsx +11 -1
  167. package/src/components/Avatar/Avatar.tsx +3 -2
  168. package/src/components/Badge/Badge.test.tsx +11 -1
  169. package/src/components/Button/Button.test.tsx +13 -3
  170. package/src/components/Button/Button.tsx +1 -1
  171. package/src/components/Calendar/Calendar.test.tsx +523 -131
  172. package/src/components/Calendar/Calendar.tsx +107 -488
  173. package/src/components/Card/Card.test.tsx +384 -258
  174. package/src/components/Card/Card.tsx +19 -10
  175. package/src/components/Checkbox/Checkbox.test.tsx +58 -174
  176. package/src/components/ContextSelector/ContextSelector.internals.tsx +204 -0
  177. package/src/components/ContextSelector/ContextSelector.test.tsx +360 -0
  178. package/src/components/ContextSelector/ContextSelector.tsx +66 -280
  179. package/src/components/ContextSelector/ContextSelector.types.ts +35 -0
  180. package/src/components/ContextSelector/useContextSelectorState.tsx +195 -0
  181. package/src/components/DataTable/AUDIT_REPORT.md +59 -44
  182. package/src/components/DataTable/DataTable.comprehensive.test.tsx +759 -0
  183. package/src/components/DataTable/DataTable.default-state.test.tsx +524 -0
  184. package/src/components/DataTable/DataTable.export.test.tsx +705 -0
  185. package/src/components/DataTable/DataTable.grouping-aggregation.test.tsx +658 -0
  186. package/src/components/DataTable/DataTable.hooks.test.tsx +192 -0
  187. package/src/components/DataTable/DataTable.select-label-display.test.tsx +485 -0
  188. package/src/components/DataTable/DataTable.test.tsx +787 -416
  189. package/src/components/DataTable/DataTable.tsx +14 -14
  190. package/src/components/DataTable/DataTableCore.integration.test.tsx +458 -0
  191. package/src/components/DataTable/DataTableCore.test-setup.ts +221 -0
  192. package/src/components/DataTable/DataTableCore.test.tsx +970 -0
  193. package/src/components/DataTable/README.md +155 -0
  194. package/src/components/DataTable/TESTING.md +101 -0
  195. package/src/components/DataTable/a11y.basic.test.tsx +788 -0
  196. package/src/components/DataTable/components/DataTableCore.tsx +126 -894
  197. package/src/components/DataTable/components/GroupingDropdown.test.tsx +621 -0
  198. package/src/components/DataTable/components/GroupingDropdown.tsx +2 -3
  199. package/src/components/DataTable/components/ImportModal.tsx +82 -408
  200. package/src/components/DataTable/components/ImportModalFileSection.tsx +148 -0
  201. package/src/components/DataTable/context/DataTableContext.test.tsx +328 -0
  202. package/src/components/DataTable/context/DataTableContext.tsx +13 -13
  203. package/src/components/DataTable/core/ColumnFactory.test.ts +403 -0
  204. package/src/components/DataTable/core/ColumnFactory.ts +3 -3
  205. package/src/components/DataTable/hooks/useColumnOrderPersistence.test.ts +516 -0
  206. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +12 -9
  207. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.test.ts +256 -0
  208. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +12 -9
  209. package/src/components/DataTable/hooks/useDataTableConfiguration.test.ts +297 -0
  210. package/src/components/DataTable/hooks/useDataTableConfiguration.ts +15 -3
  211. package/src/components/DataTable/hooks/useDataTableDataPipeline.test.ts +270 -0
  212. package/src/components/DataTable/hooks/useDataTableDeletionBatching.test.ts +127 -0
  213. package/src/components/DataTable/hooks/useDataTableDeletionBatching.ts +106 -0
  214. package/src/components/DataTable/hooks/useDataTableEffectiveActions.test.ts +461 -0
  215. package/src/components/DataTable/hooks/useDataTableEffectiveActions.ts +238 -0
  216. package/src/components/DataTable/hooks/useDataTableLayoutHandlers.test.ts +296 -0
  217. package/src/components/DataTable/hooks/useDataTableLayoutHandlers.ts +175 -0
  218. package/src/components/DataTable/hooks/useDataTablePaginationSync.test.ts +203 -0
  219. package/src/components/DataTable/hooks/useDataTablePaginationSync.ts +109 -0
  220. package/src/components/DataTable/hooks/useDataTablePermissions.test.ts +280 -0
  221. package/src/components/DataTable/hooks/useDataTablePermissions.ts +81 -260
  222. package/src/components/DataTable/hooks/useDataTablePipeline.test.tsx +219 -0
  223. package/src/components/DataTable/hooks/useDataTablePipeline.tsx +239 -0
  224. package/src/components/DataTable/hooks/useDataTableRenderGuard.test.tsx +316 -0
  225. package/src/components/DataTable/hooks/useDataTableRenderGuard.tsx +195 -0
  226. package/src/components/DataTable/hooks/useDataTableScope.test.ts +110 -0
  227. package/src/components/DataTable/hooks/useDataTableScope.ts +123 -0
  228. package/src/components/DataTable/hooks/useDataTableState.test.ts +733 -0
  229. package/src/components/DataTable/hooks/useDataTableState.ts +161 -114
  230. package/src/components/DataTable/hooks/useDataTableStateAndPersistence.test.ts +277 -0
  231. package/src/components/DataTable/hooks/useDataTableStateAndPersistence.ts +222 -0
  232. package/src/components/DataTable/hooks/useDataTableSuperAdmin.test.ts +93 -0
  233. package/src/components/DataTable/hooks/useDataTableSuperAdmin.ts +86 -0
  234. package/src/components/DataTable/hooks/useDataTableTableInstance.test.ts +185 -0
  235. package/src/components/DataTable/hooks/useDataTableTableInstance.ts +178 -0
  236. package/src/components/DataTable/hooks/useEffectiveColumnOrder.test.ts +183 -0
  237. package/src/components/DataTable/hooks/useHierarchicalState.test.ts +294 -0
  238. package/src/components/DataTable/hooks/useImportModalFocus.test.ts +184 -0
  239. package/src/components/DataTable/hooks/useImportModalFocus.ts +53 -0
  240. package/src/components/DataTable/hooks/useImportModalState.test.ts +390 -0
  241. package/src/components/DataTable/hooks/useImportModalState.ts +345 -0
  242. package/src/components/DataTable/hooks/useKeyboardNavigation.test.ts +787 -0
  243. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +311 -271
  244. package/src/components/DataTable/hooks/usePermissionTracking.test.ts +381 -0
  245. package/src/components/DataTable/hooks/usePermissionTracking.ts +122 -0
  246. package/src/components/DataTable/hooks/useServerSideDataEffect.test.ts +258 -0
  247. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +27 -4
  248. package/src/components/DataTable/hooks/useTableColumns.test.ts +499 -0
  249. package/src/components/DataTable/hooks/useTableColumns.ts +15 -39
  250. package/src/components/DataTable/hooks/useTableHandlers.test.ts +461 -0
  251. package/src/components/DataTable/hooks/useTableHandlers.ts +13 -22
  252. package/src/components/DataTable/index.ts +28 -5
  253. package/src/components/DataTable/keyboard.test.tsx +734 -0
  254. package/src/components/DataTable/mocks/MockRBACProvider.tsx +66 -0
  255. package/src/components/DataTable/pagination.modes.test.tsx +728 -0
  256. package/src/components/DataTable/ssr.strict-mode.test.tsx +319 -0
  257. package/src/components/DataTable/styles.test.ts +379 -0
  258. package/src/components/DataTable/styles.ts +0 -1
  259. package/src/components/DataTable/test-utils/MockDataTableComponents.tsx +55 -0
  260. package/src/components/DataTable/test-utils/dataFactories.ts +103 -0
  261. package/src/components/DataTable/test-utils/featureConfig.ts +10 -0
  262. package/src/components/DataTable/test-utils/sharedTestUtils.ts +419 -0
  263. package/src/components/DataTable/test-utils.ts +94 -0
  264. package/src/components/DataTable/types/actions.ts +71 -0
  265. package/src/components/DataTable/types/base.ts +39 -0
  266. package/src/components/DataTable/types/columns.ts +125 -0
  267. package/src/components/DataTable/types/export.ts +32 -0
  268. package/src/components/DataTable/types/features.ts +81 -0
  269. package/src/components/DataTable/types/hierarchical.ts +44 -0
  270. package/src/components/DataTable/types/index.ts +43 -0
  271. package/src/components/DataTable/types/pagination.ts +85 -0
  272. package/src/components/DataTable/types/performance.ts +47 -0
  273. package/src/components/DataTable/types/props.ts +62 -0
  274. package/src/components/DataTable/types/rbac.ts +45 -0
  275. package/src/components/DataTable/ui/layout/DataTableCore.test.tsx +1194 -0
  276. package/src/components/DataTable/ui/layout/DataTableCore.tsx +345 -0
  277. package/src/components/DataTable/ui/layout/DataTableErrorBoundary.test.tsx +438 -0
  278. package/src/components/DataTable/ui/layout/DataTableErrorBoundary.tsx +225 -0
  279. package/src/components/DataTable/ui/layout/DataTableLayout.test.tsx +1352 -0
  280. package/src/components/DataTable/ui/layout/DataTableLayout.tsx +661 -0
  281. package/src/components/DataTable/ui/modals/BulkDeleteConfirmDialog.test.tsx +91 -0
  282. package/src/components/DataTable/ui/modals/BulkDeleteConfirmDialog.tsx +43 -0
  283. package/src/components/DataTable/ui/modals/DataTableModals.test.tsx +749 -0
  284. package/src/components/DataTable/ui/modals/DataTableModals.tsx +341 -0
  285. package/src/components/DataTable/ui/modals/ImportModal.test.tsx +1834 -0
  286. package/src/components/DataTable/ui/modals/ImportModal.tsx +197 -0
  287. package/src/components/DataTable/ui/modals/ImportModalFailedRowsSection.tsx +60 -0
  288. package/src/components/DataTable/ui/modals/ImportModalFileSection.tsx +148 -0
  289. package/src/components/DataTable/ui/modals/ImportModalPreviewSection.tsx +60 -0
  290. package/src/components/DataTable/ui/modals/ImportModalSummarySection.tsx +59 -0
  291. package/src/components/DataTable/ui/modals/importModalPersistence.ts +73 -0
  292. package/src/components/DataTable/ui/shared/AccessDeniedPage.test.tsx +245 -0
  293. package/src/components/DataTable/ui/shared/AccessDeniedPage.tsx +159 -0
  294. package/src/components/DataTable/ui/shared/ActionButtons.test.tsx +921 -0
  295. package/src/components/DataTable/ui/shared/ActionButtons.tsx +195 -0
  296. package/src/components/DataTable/ui/shared/ColumnFilter.test.tsx +497 -0
  297. package/src/components/DataTable/ui/shared/ColumnFilter.tsx +113 -0
  298. package/src/components/DataTable/ui/shared/PaginationControls.test.tsx +451 -0
  299. package/src/components/DataTable/ui/shared/PaginationControls.tsx +291 -0
  300. package/src/components/DataTable/ui/shared/SortIndicator.test.tsx +135 -0
  301. package/src/components/DataTable/ui/shared/SortIndicator.tsx +50 -0
  302. package/src/components/DataTable/ui/table/EditFields.test.tsx +526 -0
  303. package/src/components/DataTable/ui/table/EditFields.tsx +355 -0
  304. package/src/components/DataTable/ui/table/EditableRow.test.tsx +1003 -0
  305. package/src/components/DataTable/ui/table/EditableRow.tsx +444 -0
  306. package/src/components/DataTable/ui/table/EmptyState.test.tsx +360 -0
  307. package/src/components/DataTable/ui/table/EmptyState.tsx +74 -0
  308. package/src/components/DataTable/ui/table/FilterRow.test.tsx +416 -0
  309. package/src/components/DataTable/ui/table/FilterRow.tsx +148 -0
  310. package/src/components/DataTable/ui/table/LoadingState.test.tsx +77 -0
  311. package/src/components/DataTable/ui/table/LoadingState.tsx +17 -0
  312. package/src/components/DataTable/ui/table/RowComponent.test.tsx +1024 -0
  313. package/src/components/DataTable/ui/table/RowComponent.tsx +429 -0
  314. package/src/components/DataTable/ui/table/UnifiedTableBody.test.tsx +1273 -0
  315. package/src/components/DataTable/ui/table/UnifiedTableBody.tsx +440 -0
  316. package/src/components/DataTable/ui/table/cellValueUtils.test.ts +453 -0
  317. package/src/components/DataTable/ui/table/cellValueUtils.ts +40 -0
  318. package/src/components/DataTable/ui/toolbar/BulkOperationsDropdown.test.tsx +551 -0
  319. package/src/components/DataTable/ui/toolbar/BulkOperationsDropdown.tsx +160 -0
  320. package/src/components/DataTable/ui/toolbar/ColumnVisibilityDropdown.test.tsx +751 -0
  321. package/src/components/DataTable/ui/toolbar/ColumnVisibilityDropdown.tsx +114 -0
  322. package/src/components/DataTable/ui/toolbar/DataTableToolbar.test.tsx +629 -0
  323. package/src/components/DataTable/ui/toolbar/DataTableToolbar.tsx +271 -0
  324. package/src/components/DataTable/ui/toolbar/GroupingDropdown.test.tsx +621 -0
  325. package/src/components/DataTable/ui/toolbar/GroupingDropdown.tsx +107 -0
  326. package/src/components/DataTable/utils/a11yUtils.test.ts +548 -0
  327. package/src/components/DataTable/utils/a11yUtils.ts +1 -1
  328. package/src/components/DataTable/utils/aggregationUtils.test.ts +288 -0
  329. package/src/components/DataTable/utils/aggregationUtils.ts +5 -5
  330. package/src/components/DataTable/utils/columnUtils.test.ts +94 -0
  331. package/src/components/DataTable/utils/csvParse.test.ts +74 -0
  332. package/src/components/DataTable/utils/csvParse.ts +65 -0
  333. package/src/components/DataTable/utils/errorHandling.test.ts +209 -0
  334. package/src/components/DataTable/utils/errorHandling.ts +3 -1
  335. package/src/components/DataTable/utils/exportUtils.test.ts +954 -0
  336. package/src/components/DataTable/utils/exportUtils.ts +1 -1
  337. package/src/components/DataTable/utils/flexibleImport.test.ts +573 -0
  338. package/src/components/DataTable/utils/flexibleImport.ts +3 -186
  339. package/src/components/DataTable/utils/hierarchicalSorting.test.ts +235 -0
  340. package/src/components/DataTable/utils/hierarchicalSorting.ts +3 -3
  341. package/src/components/DataTable/utils/hierarchicalUtils.test.ts +586 -0
  342. package/src/components/DataTable/utils/importDateParser.test.ts +162 -0
  343. package/src/components/DataTable/utils/importDateParser.ts +114 -0
  344. package/src/components/DataTable/utils/importValueParser.test.ts +138 -0
  345. package/src/components/DataTable/utils/importValueParser.ts +91 -0
  346. package/src/components/DataTable/utils/paginationUtils.test.ts +593 -0
  347. package/src/components/DataTable/utils/paginationUtils.ts +7 -4
  348. package/src/components/DataTable/utils/performanceUtils.test.ts +470 -0
  349. package/src/components/DataTable/utils/performanceUtils.ts +1 -1
  350. package/src/components/DataTable/utils/rowUtils.test.ts +235 -0
  351. package/src/components/DataTable/utils/selectFieldUtils.test.ts +271 -0
  352. package/src/components/DataTable/utils/selectFieldUtils.ts +97 -67
  353. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +18 -25
  354. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +1 -1
  355. package/src/components/DateTimeField/DateTimeField.test.tsx +3 -16
  356. package/src/components/DateTimeField/DateTimeField.tsx +1 -1
  357. package/src/components/Dialog/Dialog.test-utils.ts +49 -0
  358. package/src/components/Dialog/Dialog.test.tsx +2865 -458
  359. package/src/components/Dialog/Dialog.tsx +183 -986
  360. package/src/components/Dialog/dialogLock.test.ts +238 -0
  361. package/src/components/Dialog/dialogLock.ts +98 -0
  362. package/src/components/Dialog/index.ts +2 -0
  363. package/src/components/Dialog/useDialogDimensions.test.ts +163 -0
  364. package/src/components/Dialog/useDialogDimensions.ts +140 -0
  365. package/src/components/Dialog/useDialogLifecycle.test.ts +358 -0
  366. package/src/components/Dialog/useDialogLifecycle.ts +135 -0
  367. package/src/components/Dialog/useDialogPersistence.test.ts +381 -0
  368. package/src/components/Dialog/useDialogPersistence.ts +357 -0
  369. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +2 -62
  370. package/src/components/ErrorBoundary/ErrorBoundaryContext.context.ts +17 -0
  371. package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +2 -45
  372. package/src/components/ErrorBoundary/ErrorBoundaryContext.types.ts +41 -0
  373. package/src/components/ErrorBoundary/index.ts +3 -4
  374. package/src/components/ErrorBoundary/useErrorBoundaryContext.ts +20 -0
  375. package/src/components/FileDisplay/FileDisplay.test.tsx +479 -247
  376. package/src/components/FileDisplay/FileDisplay.tsx +29 -659
  377. package/src/components/FileDisplay/FileDisplayContent.test.tsx +395 -0
  378. package/src/components/FileDisplay/FileDisplayContent.tsx +242 -0
  379. package/src/components/FileDisplay/FileDisplayDeleteConfirmDialog.test.tsx +74 -0
  380. package/src/components/FileDisplay/FileDisplayDeleteConfirmDialog.tsx +38 -0
  381. package/src/components/FileDisplay/FileDisplayEmptyView.test.tsx +33 -0
  382. package/src/components/FileDisplay/FileDisplayEmptyView.tsx +33 -0
  383. package/src/components/FileDisplay/FileDisplayErrorView.test.tsx +71 -0
  384. package/src/components/FileDisplay/FileDisplayErrorView.tsx +50 -0
  385. package/src/components/FileDisplay/FileDisplayLoadingFallbackView.test.tsx +22 -0
  386. package/src/components/FileDisplay/FileDisplayLoadingFallbackView.tsx +22 -0
  387. package/src/components/FileDisplay/FileDisplayLoadingView.test.tsx +21 -0
  388. package/src/components/FileDisplay/FileDisplayLoadingView.tsx +23 -0
  389. package/src/components/FileDisplay/FileDisplayMultipleFilesView.test.tsx +101 -0
  390. package/src/components/FileDisplay/FileDisplayMultipleFilesView.tsx +109 -0
  391. package/src/components/FileDisplay/FileDisplaySingleDocumentLinkView.test.tsx +58 -0
  392. package/src/components/FileDisplay/FileDisplaySingleDocumentLinkView.tsx +48 -0
  393. package/src/components/FileDisplay/FileDisplaySingleFileWithActionsView.test.tsx +111 -0
  394. package/src/components/FileDisplay/FileDisplaySingleFileWithActionsView.tsx +270 -0
  395. package/src/components/FileDisplay/FileDisplaySingleImageView.test.tsx +78 -0
  396. package/src/components/FileDisplay/FileDisplaySingleImageView.tsx +67 -0
  397. package/src/components/FileDisplay/fallbackUtils.test.ts +50 -0
  398. package/src/components/FileDisplay/fallbackUtils.ts +44 -0
  399. package/src/components/FileDisplay/fetchFileDisplayData.ts +24 -0
  400. package/src/components/FileDisplay/fetchFileDisplayData.unit.test.ts +183 -0
  401. package/src/components/FileDisplay/fileDisplayUtils.test.ts +58 -0
  402. package/src/components/FileDisplay/fileDisplayUtils.ts +24 -0
  403. package/src/components/FileDisplay/index.tsx +1 -1
  404. package/src/components/FileDisplay/useFileDisplay.test.ts +538 -0
  405. package/src/components/FileDisplay/useFileDisplay.ts +515 -0
  406. package/src/components/FileDisplay/useFileDisplay.unit.test.ts +1438 -0
  407. package/src/components/FileDisplay/useFileDisplayData.ts +126 -0
  408. package/src/components/FileDisplay/usePublicFileDisplay.test.ts +729 -0
  409. package/src/components/FileDisplay/usePublicFileDisplay.ts +579 -0
  410. package/src/components/FileUpload/FileUpload.test.tsx +69 -27
  411. package/src/components/FileUpload/FileUpload.tsx +112 -527
  412. package/src/components/FileUpload/FileUploadDropZone.tsx +112 -0
  413. package/src/components/FileUpload/FileUploadProgressItem.tsx +86 -0
  414. package/src/components/FileUpload/FileUploadProgressList.tsx +40 -0
  415. package/src/components/FileUpload/index.tsx +1 -1
  416. package/src/components/FileUpload/useFileUploadManager.test.ts +308 -0
  417. package/src/components/FileUpload/useFileUploadManager.ts +454 -0
  418. package/src/components/FileUpload/useResolvedAppId.test.ts +102 -0
  419. package/src/components/FileUpload/useResolvedAppId.ts +77 -0
  420. package/src/components/Footer/Footer.test.tsx +15 -382
  421. package/src/components/Footer/Footer.tsx +8 -125
  422. package/src/components/Form/Form.test.tsx +425 -88
  423. package/src/components/Form/Form.tsx +91 -299
  424. package/src/components/Form/useFormPersistence.ts +257 -0
  425. package/src/components/Header/Header.test.tsx +653 -163
  426. package/src/components/Header/Header.tsx +62 -44
  427. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +35 -76
  428. package/src/components/Input/Input.test.tsx +34 -120
  429. package/src/components/Input/Input.tsx +1 -1
  430. package/src/components/Label/Label.test.tsx +46 -45
  431. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +8 -11
  432. package/src/components/LoginForm/LoginForm.test.tsx +0 -1
  433. package/src/components/NavigationMenu/HierarchicalNavItem.tsx +104 -0
  434. package/src/components/NavigationMenu/NavigationMenu.test.tsx +2422 -102
  435. package/src/components/NavigationMenu/NavigationMenu.tsx +62 -362
  436. package/src/components/NavigationMenu/index.ts +6 -1
  437. package/src/components/NavigationMenu/navigationPermissionHelper.ts +188 -0
  438. package/src/components/NavigationMenu/useNavigationFiltering.test.ts +1949 -0
  439. package/src/components/NavigationMenu/useNavigationFiltering.ts +199 -308
  440. package/src/components/NavigationMenu/useNavigationScope.ts +125 -0
  441. package/src/components/PaceAppLayout/PaceAppLayout.edge-cases.test.tsx +1322 -0
  442. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +50 -49
  443. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +81 -38
  444. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +103 -85
  445. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +774 -44
  446. package/src/components/PaceAppLayout/PaceAppLayout.tsx +282 -764
  447. package/src/components/PaceAppLayout/README.md +0 -9
  448. package/src/components/PaceAppLayout/test-setup.tsx +15 -9
  449. package/src/components/PaceAppLayout/useFilteredNavItems.ts +304 -0
  450. package/src/components/PaceAppLayout/usePaceAppLayoutConfig.ts +142 -0
  451. package/src/components/PaceAppLayout/usePaceAppLayoutGate.tsx +150 -0
  452. package/src/components/PaceAppLayout/usePaceAppLayoutPermissions.ts +162 -0
  453. package/src/components/PaceAppLayout/usePaceAppLayoutScope.ts +79 -0
  454. package/src/components/PaceAppLayout/useRoleBasedRouteAccess.ts +157 -0
  455. package/src/components/PaceAppLayout/useSuperAdminFallback.ts +58 -0
  456. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +782 -20
  457. package/src/components/PaceLoginPage/PaceLoginPage.tsx +33 -125
  458. package/src/components/PaceLoginPage/useLoginAppAccess.ts +153 -0
  459. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +1 -1
  460. package/src/components/Progress/Progress.test.tsx +127 -1
  461. package/src/components/Progress/Progress.tsx +1 -2
  462. package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +1196 -4
  463. package/src/components/ProtectedRoute/ProtectedRoute.tsx +29 -217
  464. package/src/components/ProtectedRoute/useProtectedRouteState.ts +128 -0
  465. package/src/components/ProtectedRoute/useVisibilityRedirectGrace.ts +89 -0
  466. package/src/components/PublicLayout/PublicLayout.test.tsx +1640 -38
  467. package/src/components/PublicLayout/PublicPageContext.ts +28 -0
  468. package/src/components/PublicLayout/PublicPageLayout.tsx +134 -75
  469. package/src/components/PublicLayout/PublicPageProvider.tsx +7 -42
  470. package/src/components/PublicLayout/usePublicPageContext.ts +36 -0
  471. package/src/components/Select/Select.test.tsx +45 -8
  472. package/src/components/Select/Select.tsx +57 -40
  473. package/src/components/Select/context.test.tsx +56 -0
  474. package/src/components/Select/text.test.tsx +104 -0
  475. package/src/components/Select/text.ts +26 -0
  476. package/src/components/Select/types.ts +3 -0
  477. package/src/components/Select/useSelectEvents.test.ts +279 -0
  478. package/src/components/Select/useSelectEvents.ts +87 -0
  479. package/src/components/Select/useSelectSearch.test.tsx +295 -0
  480. package/src/components/Select/useSelectSearch.ts +91 -0
  481. package/src/components/Select/useSelectState.test.ts +268 -0
  482. package/src/components/Select/useSelectState.ts +104 -0
  483. package/src/components/SessionRestorationLoader/SessionRestorationLoader.test.tsx +28 -112
  484. package/src/components/Switch/Switch.test.tsx +57 -153
  485. package/src/components/Table/Table.test.tsx +395 -317
  486. package/src/components/Tabs/Tabs.test.tsx +270 -0
  487. package/src/components/Tabs/Tabs.tsx +4 -4
  488. package/src/components/Textarea/Textarea.test.tsx +11 -38
  489. package/src/components/Toast/Toast.test.tsx +425 -496
  490. package/src/components/Tooltip/Tooltip.test.tsx +4 -21
  491. package/src/components/UserMenu/UserMenu.test.tsx +1 -21
  492. package/src/components/UserMenu/UserMenu.tsx +0 -1
  493. package/src/components/index.test.ts +346 -0
  494. package/src/components/index.ts +12 -1
  495. package/src/constants/performance.test.ts +91 -0
  496. package/src/hooks/ServiceHooks.test.tsx +725 -0
  497. package/src/hooks/hooks.integration.test.tsx +608 -0
  498. package/src/hooks/index.ts +18 -3
  499. package/src/hooks/index.unit.test.ts +220 -0
  500. package/src/hooks/public/usePublicEvent.test.ts +304 -0
  501. package/src/hooks/public/usePublicEvent.ts +11 -11
  502. package/src/hooks/public/usePublicEventLogo.test.ts +655 -120
  503. package/src/hooks/public/usePublicEventLogo.ts +2 -2
  504. package/src/hooks/public/usePublicRouteParams.test.ts +595 -0
  505. package/src/hooks/public/usePublicRouteParams.ts +2 -2
  506. package/src/hooks/services/useAuth.ts +9 -7
  507. package/src/hooks/services/useAuthService.ts +1 -1
  508. package/src/hooks/services/useEventService.ts +1 -1
  509. package/src/hooks/useAccessibleApps.test.ts +400 -0
  510. package/src/hooks/useAccessibleApps.ts +264 -0
  511. package/src/hooks/useAddressAutocomplete.test.ts +170 -47
  512. package/src/hooks/useAddressAutocomplete.ts +109 -81
  513. package/src/hooks/useApiFetch.unit.test.ts +111 -0
  514. package/src/hooks/useAppConfig.ts +13 -3
  515. package/src/hooks/useAppConfig.unit.test.ts +712 -0
  516. package/src/hooks/useComponentPerformance.unit.test.tsx +314 -0
  517. package/src/hooks/useDataTablePerformance.ts +111 -130
  518. package/src/hooks/useDataTablePerformance.unit.test.ts +720 -0
  519. package/src/hooks/useDataTableState.test.ts +170 -0
  520. package/src/hooks/useDataTableState.ts +5 -5
  521. package/src/hooks/useDebounce.unit.test.ts +157 -0
  522. package/src/hooks/useEventTheme.test.ts +70 -18
  523. package/src/hooks/useEventTheme.ts +50 -22
  524. package/src/hooks/useEvents.ts +49 -2
  525. package/src/hooks/useEvents.unit.test.ts +227 -0
  526. package/src/hooks/useFileReference.test.ts +388 -107
  527. package/src/hooks/useFileReference.ts +184 -179
  528. package/src/hooks/useFileUrl.ts +1 -1
  529. package/src/hooks/useFileUrl.unit.test.ts +686 -0
  530. package/src/hooks/useFileUrlCache.test.ts +319 -0
  531. package/src/hooks/useFileUrlCache.ts +5 -2
  532. package/src/hooks/useFocusManagement.unit.test.ts +604 -0
  533. package/src/hooks/useFocusTrap.unit.test.tsx +613 -0
  534. package/src/hooks/useFormDialog.test.ts +307 -0
  535. package/src/hooks/useFormDialog.ts +2 -2
  536. package/src/hooks/useInactivityTracker.ts +141 -134
  537. package/src/hooks/useInactivityTracker.unit.test.ts +446 -0
  538. package/src/hooks/useIsMobile.unit.test.ts +317 -0
  539. package/src/hooks/useIsPrint.ts +62 -0
  540. package/src/hooks/useIsPrint.unit.test.ts +545 -0
  541. package/src/hooks/useKeyboardShortcuts.unit.test.ts +907 -0
  542. package/src/hooks/useOrganisationPermissions.test.ts +1 -2
  543. package/src/hooks/useOrganisationPermissions.ts +1 -4
  544. package/src/hooks/useOrganisationPermissions.unit.test.tsx +293 -0
  545. package/src/hooks/useOrganisationSecurity.test.ts +4 -33
  546. package/src/hooks/useOrganisationSecurity.ts +192 -203
  547. package/src/hooks/useOrganisationSecurity.unit.test.tsx +959 -0
  548. package/src/hooks/useOrganisations.ts +1 -1
  549. package/src/hooks/useOrganisations.unit.test.ts +369 -0
  550. package/src/hooks/usePerformanceMonitor.ts +1 -1
  551. package/src/hooks/usePerformanceMonitor.unit.test.ts +693 -0
  552. package/src/hooks/usePermissionCache.test.ts +298 -329
  553. package/src/hooks/usePermissionCache.ts +277 -276
  554. package/src/hooks/usePreventTabReload.test.ts +307 -0
  555. package/src/hooks/usePublicEvent.simple.test.ts +794 -0
  556. package/src/hooks/usePublicEvent.test.ts +670 -0
  557. package/src/hooks/usePublicEvent.unit.test.ts +638 -0
  558. package/src/hooks/usePublicFileDisplay.test.ts +948 -0
  559. package/src/hooks/usePublicRouteParams.unit.test.ts +442 -0
  560. package/src/hooks/useQueryCache.test.ts +391 -0
  561. package/src/hooks/useQueryCache.ts +7 -9
  562. package/src/hooks/useRBAC.unit.test.ts +253 -0
  563. package/src/hooks/useSessionDraft.test.ts +556 -0
  564. package/src/hooks/useSessionDraft.ts +14 -11
  565. package/src/hooks/useSessionRestoration.ts +1 -1
  566. package/src/hooks/useSessionRestoration.unit.test.tsx +381 -0
  567. package/src/hooks/useStorage.ts +94 -54
  568. package/src/hooks/useStorage.unit.test.ts +684 -0
  569. package/src/hooks/useToast.test.ts +413 -0
  570. package/src/hooks/useToast.ts +2 -2
  571. package/src/hooks/useToast.unit.test.tsx +481 -0
  572. package/src/hooks/useZodForm.ts +3 -3
  573. package/src/hooks/useZodForm.unit.test.tsx +191 -0
  574. package/src/icons/index.test.ts +133 -0
  575. package/src/icons/index.ts +3 -1
  576. package/src/index.test.ts +528 -0
  577. package/src/index.ts +56 -9
  578. package/src/providers/AuthProvider.test.tsx +218 -0
  579. package/src/providers/EventProvider.test.tsx +487 -0
  580. package/src/providers/InactivityProvider.test-helper.tsx +40 -0
  581. package/src/providers/InactivityProvider.test.tsx +421 -0
  582. package/src/providers/ProviderLifecycle.test.tsx +308 -0
  583. package/src/providers/UnifiedAuthProvider.smoke.test.tsx +7 -12
  584. package/src/providers/UnifiedAuthProvider.test.tsx +503 -0
  585. package/src/providers/index.test.ts +138 -0
  586. package/src/providers/services/AuthServiceContext.ts +27 -0
  587. package/src/providers/services/AuthServiceProvider.integration.test.tsx +229 -0
  588. package/src/providers/services/AuthServiceProvider.test.tsx +638 -0
  589. package/src/providers/services/AuthServiceProvider.tsx +81 -20
  590. package/src/providers/services/EventServiceContext.ts +25 -0
  591. package/src/providers/services/EventServiceProvider.test.tsx +839 -0
  592. package/src/providers/services/EventServiceProvider.tsx +11 -20
  593. package/src/providers/services/InactivityServiceContext.ts +25 -0
  594. package/src/providers/services/InactivityServiceProvider.test.tsx +662 -0
  595. package/src/providers/services/InactivityServiceProvider.tsx +7 -17
  596. package/src/providers/services/OrganisationServiceContext.ts +25 -0
  597. package/src/providers/services/OrganisationServiceProvider.test.tsx +440 -0
  598. package/src/providers/services/OrganisationServiceProvider.tsx +7 -17
  599. package/src/providers/services/UnifiedAuthContext.ts +102 -0
  600. package/src/providers/services/UnifiedAuthProvider.advanced.test.tsx +434 -0
  601. package/src/providers/services/UnifiedAuthProvider.appId.test.tsx +408 -0
  602. package/src/providers/services/UnifiedAuthProvider.integration.test.tsx +304 -0
  603. package/src/providers/services/UnifiedAuthProvider.test.tsx +212 -0
  604. package/src/providers/services/UnifiedAuthProvider.tsx +147 -497
  605. package/src/providers/services/contexts.test.tsx +281 -0
  606. package/src/providers/services/useUnifiedAuth.test.tsx +251 -0
  607. package/src/providers/services/useUnifiedAuth.ts +29 -0
  608. package/src/providers/services/useUnifiedAuthContextValue.ts +279 -0
  609. package/src/providers/useInactivity.test-helper.ts +27 -0
  610. package/src/rbac/README.md +5 -5
  611. package/src/rbac/adapters.comprehensive.test.tsx +429 -0
  612. package/src/rbac/adapters.test.tsx +654 -0
  613. package/src/rbac/adapters.tsx +53 -38
  614. package/src/rbac/api.test.ts +986 -259
  615. package/src/rbac/api.ts +260 -216
  616. package/src/rbac/audit-batched.test.ts +550 -0
  617. package/src/rbac/audit-batched.ts +5 -4
  618. package/src/rbac/audit.test.ts +225 -28
  619. package/src/rbac/audit.ts +26 -18
  620. package/src/rbac/auth-rbac-security.integration.test.tsx +300 -0
  621. package/src/rbac/auth-rbac.e2e.test.tsx +510 -0
  622. package/src/rbac/cache-invalidation.test.ts +715 -0
  623. package/src/rbac/cache-invalidation.ts +18 -15
  624. package/src/rbac/cache.test.ts +123 -63
  625. package/src/rbac/cache.ts +3 -4
  626. package/src/rbac/components/AccessDenied.test.tsx +324 -0
  627. package/src/rbac/components/AccessDenied.tsx +20 -18
  628. package/src/rbac/components/NavigationGuard.test.tsx +1148 -0
  629. package/src/rbac/components/NavigationGuard.tsx +10 -8
  630. package/src/rbac/components/PagePermissionGuard.guard.test.tsx +236 -0
  631. package/src/rbac/components/PagePermissionGuard.performance.test.tsx +252 -0
  632. package/src/rbac/components/PagePermissionGuard.race-condition.test.tsx +243 -0
  633. package/src/rbac/components/PagePermissionGuard.test.tsx +1430 -0
  634. package/src/rbac/components/PagePermissionGuard.tsx +188 -381
  635. package/src/rbac/components/PagePermissionGuard.verification.test.tsx +185 -0
  636. package/src/rbac/config.test.ts +131 -48
  637. package/src/rbac/config.ts +69 -26
  638. package/src/rbac/docs/event-based-apps.md +26 -13
  639. package/src/rbac/engine.comprehensive.test.ts +808 -0
  640. package/src/rbac/engine.test.ts +974 -130
  641. package/src/rbac/engine.ts +53 -13
  642. package/src/rbac/errors.test.ts +99 -87
  643. package/src/rbac/errors.ts +89 -55
  644. package/src/rbac/eslint-rules.js +2 -2
  645. package/src/rbac/hooks/permissions/runPermissionCheck.ts +77 -0
  646. package/src/rbac/hooks/permissions/useAccessLevel.test.ts +622 -0
  647. package/src/rbac/hooks/permissions/useAccessLevel.ts +23 -14
  648. package/src/rbac/hooks/permissions/useCan.test.ts +798 -0
  649. package/src/rbac/hooks/permissions/useCan.ts +173 -253
  650. package/src/rbac/hooks/permissions/useMultiplePermissions.test.ts +843 -0
  651. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +63 -10
  652. package/src/rbac/hooks/permissions/usePermissions.test.ts +543 -0
  653. package/src/rbac/hooks/permissions/usePermissions.ts +50 -78
  654. package/src/rbac/hooks/useCan.test.ts +348 -32
  655. package/src/rbac/hooks/usePageAccessLogging.ts +160 -0
  656. package/src/rbac/hooks/usePageGuardScope.ts +117 -0
  657. package/src/rbac/hooks/usePagePermissionCheck.ts +67 -0
  658. package/src/rbac/hooks/usePermissions.integration.test.ts +427 -0
  659. package/src/rbac/hooks/usePermissions.stability.test.ts +268 -0
  660. package/src/rbac/hooks/usePermissions.test.ts +459 -33
  661. package/src/rbac/hooks/usePermissions.ts +5 -7
  662. package/src/rbac/hooks/useRBAC.test.ts +1784 -21
  663. package/src/rbac/hooks/useRBAC.ts +148 -88
  664. package/src/rbac/hooks/useResolvedScope.test.ts +442 -5
  665. package/src/rbac/hooks/useResolvedScope.ts +4 -1
  666. package/src/rbac/hooks/useResourcePermissions.test.ts +561 -24
  667. package/src/rbac/hooks/useResourcePermissions.ts +76 -140
  668. package/src/rbac/hooks/useResourcePermissionsSuperAdmin.ts +67 -0
  669. package/src/rbac/hooks/useRoleManagement.test.ts +634 -61
  670. package/src/rbac/hooks/useRoleManagement.ts +158 -586
  671. package/src/rbac/hooks/useSecureSupabase.test.ts +1179 -0
  672. package/src/rbac/hooks/useSecureSupabase.ts +21 -14
  673. package/src/rbac/hooks/useSuperAdminCheck.ts +80 -0
  674. package/src/rbac/index.test.ts +107 -0
  675. package/src/rbac/index.ts +32 -32
  676. package/src/rbac/performance.test.ts +451 -0
  677. package/src/rbac/permissions.test.ts +149 -68
  678. package/src/rbac/permissions.ts +0 -3
  679. package/src/rbac/rbac-core.test.tsx +276 -0
  680. package/src/rbac/rbac-engine-core-logic.test.ts +387 -0
  681. package/src/rbac/rbac-engine-simplified.test.ts +252 -0
  682. package/src/rbac/rbac-functions.test.ts +703 -0
  683. package/src/rbac/rbac-integration.test.ts +523 -0
  684. package/src/rbac/rbac-role-isolation.test.ts +456 -0
  685. package/src/rbac/request-deduplication.test.ts +352 -0
  686. package/src/rbac/request-deduplication.ts +5 -4
  687. package/src/rbac/scenarios.user-role.test.tsx +271 -0
  688. package/src/rbac/secureClient.test.ts +499 -115
  689. package/src/rbac/secureClient.ts +54 -28
  690. package/src/rbac/security.test.ts +448 -44
  691. package/src/rbac/security.ts +7 -6
  692. package/src/rbac/types/roleManagement.ts +66 -0
  693. package/src/rbac/types.test.ts +236 -0
  694. package/src/rbac/types.ts +7 -5
  695. package/src/rbac/utils/clientSecurity.test.ts +192 -0
  696. package/src/rbac/utils/clientSecurity.ts +6 -4
  697. package/src/rbac/utils/contextValidator.test.ts +126 -0
  698. package/src/rbac/utils/contextValidator.ts +6 -3
  699. package/src/rbac/utils/deep-equal.test.ts +76 -0
  700. package/src/rbac/utils/eventContext.test.ts +401 -0
  701. package/src/rbac/utils/eventContext.ts +38 -34
  702. package/src/rbac/utils/fetchPermissionMap.ts +13 -0
  703. package/src/rbac/utils/permissionMapHelpers.ts +34 -0
  704. package/src/rbac/utils/roleManagementRpc.ts +303 -0
  705. package/src/services/AuthService.edge-cases.test.ts +746 -0
  706. package/src/services/AuthService.restoreSession.test.ts +59 -0
  707. package/src/services/AuthService.test.ts +1362 -0
  708. package/src/services/AuthService.ts +197 -216
  709. package/src/services/BaseService.edge-cases.test.ts +506 -0
  710. package/src/services/BaseService.test.ts +363 -0
  711. package/src/services/EventService.edge-cases.test.ts +636 -0
  712. package/src/services/EventService.eventColours.test.ts +64 -0
  713. package/src/services/EventService.test.ts +1250 -0
  714. package/src/services/EventService.ts +244 -315
  715. package/src/services/InactivityService.edge-cases.test.ts +492 -0
  716. package/src/services/InactivityService.lifecycle.test.ts +406 -0
  717. package/src/services/InactivityService.test.ts +829 -0
  718. package/src/services/InactivityService.ts +172 -213
  719. package/src/services/OrganisationService.edge-cases.test.ts +633 -0
  720. package/src/services/OrganisationService.pagination.test.ts +409 -0
  721. package/src/services/OrganisationService.test.ts +1579 -0
  722. package/src/services/OrganisationService.ts +186 -257
  723. package/src/services/base/BaseService.test.ts +214 -0
  724. package/src/services/interfaces/IAuthService.test.ts +184 -0
  725. package/src/services/interfaces/IAuthService.ts +10 -9
  726. package/src/services/interfaces/IEventService.test.ts +176 -0
  727. package/src/services/interfaces/IInactivityService.test.ts +183 -0
  728. package/src/services/interfaces/IOrganisationService.test.ts +207 -0
  729. package/src/services/interfaces/IOrganisationService.ts +0 -1
  730. package/src/styles/core.css +244 -12
  731. package/src/theming/parseEventColours.test.ts +321 -0
  732. package/src/theming/parseEventColours.ts +18 -9
  733. package/src/theming/runtime.test.ts +495 -0
  734. package/src/theming/runtime.ts +72 -7
  735. package/src/types/api-result.ts +53 -0
  736. package/src/types/auth.ts +0 -1
  737. package/src/types/core.test.ts +397 -0
  738. package/src/types/database-generated.test.ts +78 -0
  739. package/src/types/database.generated.ts +45 -10
  740. package/src/types/event.ts +39 -19
  741. package/src/types/file-reference.test.ts +351 -0
  742. package/src/types/file-reference.ts +37 -12
  743. package/src/types/guards.test.ts +246 -0
  744. package/src/types/index.test.ts +265 -0
  745. package/src/types/index.ts +3 -0
  746. package/src/types/organisation.roles.test.ts +55 -0
  747. package/src/types/organisation.test.ts +1105 -0
  748. package/src/types/organisation.ts +15 -15
  749. package/src/types/rpc-responses.ts +33 -0
  750. package/src/types/supabase.ts +14 -6
  751. package/src/types/theme.test.ts +830 -0
  752. package/src/types/type-validation.test.ts +526 -0
  753. package/src/types/validation.test.ts +729 -0
  754. package/src/types/vitest-globals.d.ts +1 -1
  755. package/src/utils/app/appConfig.test.ts +235 -0
  756. package/src/utils/app/appIdResolver.test.ts +252 -57
  757. package/src/utils/app/appIdResolver.ts +31 -20
  758. package/src/utils/app/appNameResolver.test.ts +18 -10
  759. package/src/utils/app/appNameResolver.ts +11 -9
  760. package/src/utils/app/appPortMap.test.ts +125 -0
  761. package/src/utils/app/appPortMap.ts +51 -0
  762. package/src/utils/app/buildAppUrl.test.ts +273 -0
  763. package/src/utils/app/buildAppUrl.ts +114 -0
  764. package/src/utils/appConfig.unit.test.ts +55 -0
  765. package/src/utils/audit/audit.test.ts +354 -39
  766. package/src/utils/audit.unit.test.ts +69 -0
  767. package/src/utils/auth-utils.unit.test.ts +69 -0
  768. package/src/utils/bundleAnalysis.unit.test.ts +326 -0
  769. package/src/utils/cn.unit.test.ts +34 -0
  770. package/src/utils/context/organisationContext.test.ts +115 -95
  771. package/src/utils/context/organisationContext.ts +32 -43
  772. package/src/utils/context/sessionTracking.test.ts +354 -0
  773. package/src/utils/core/cn.test.ts +66 -0
  774. package/src/utils/core/debugLogger.test.ts +113 -0
  775. package/src/utils/core/debugLogger.ts +15 -8
  776. package/src/utils/core/logger.test.ts +217 -0
  777. package/src/utils/core/logger.ts +20 -16
  778. package/src/utils/core/mergeRefs.ts +24 -0
  779. package/src/utils/debugLogger.test.ts +417 -0
  780. package/src/utils/device/deviceFingerprint.test.ts +8 -5
  781. package/src/utils/device/deviceFingerprint.ts +3 -3
  782. package/src/utils/deviceFingerprint.unit.test.ts +818 -0
  783. package/src/utils/dynamic/createLazyComponent.tsx +46 -0
  784. package/src/utils/dynamic/dynamicUtils.test.ts +185 -0
  785. package/src/utils/dynamic/dynamicUtils.ts +6 -6
  786. package/src/utils/dynamic/lazyLoad.test.tsx +156 -0
  787. package/src/utils/dynamic/lazyLoad.tsx +8 -36
  788. package/src/utils/dynamic/papaparseLoader.ts +7 -0
  789. package/src/utils/dynamicUtils.unit.test.ts +331 -0
  790. package/src/utils/file-reference/file-reference.test.ts +1238 -0
  791. package/src/utils/file-reference/index.ts +330 -348
  792. package/src/utils/formatDate.unit.test.ts +109 -0
  793. package/src/utils/formatting/formatDate.test.ts +22 -148
  794. package/src/utils/formatting/formatDateTime.test.ts +41 -119
  795. package/src/utils/formatting/formatDateTimeTimezone.test.ts +41 -85
  796. package/src/utils/formatting/formatNumber.test.ts +259 -0
  797. package/src/utils/formatting/formatTime.test.ts +36 -128
  798. package/src/utils/formatting/formatting.ts +1 -1
  799. package/src/utils/formatting.unit.test.ts +99 -0
  800. package/src/utils/google-places/googlePlacesUtils.test.ts +127 -36
  801. package/src/utils/google-places/googlePlacesUtils.ts +67 -86
  802. package/src/utils/google-places/loadGoogleMapsScript.test.ts +68 -8
  803. package/src/utils/google-places/loadGoogleMapsScript.ts +140 -118
  804. package/src/utils/index.ts +52 -11
  805. package/src/utils/index.unit.test.ts +251 -0
  806. package/src/utils/lazyLoad.unit.test.tsx +319 -0
  807. package/src/utils/location/location.test.ts +19 -116
  808. package/src/utils/logger.unit.test.ts +398 -0
  809. package/src/utils/organisationContext.unit.test.ts +180 -0
  810. package/src/utils/performance/bundleAnalysis.test.ts +148 -0
  811. package/src/utils/performance/bundleAnalysis.ts +16 -22
  812. package/src/utils/performance/performanceBenchmark.test.ts +251 -0
  813. package/src/utils/performance/performanceBenchmark.ts +12 -4
  814. package/src/utils/performance/performanceBudgets.test.ts +241 -0
  815. package/src/utils/performance/performanceBudgets.ts +9 -6
  816. package/src/utils/performanceBenchmark.test.ts +174 -0
  817. package/src/utils/performanceBudgets.unit.test.ts +288 -0
  818. package/src/utils/permissionTypes.unit.test.ts +250 -0
  819. package/src/utils/permissionUtils.unit.test.ts +362 -0
  820. package/src/utils/permissions/permissionTypes.test.ts +149 -0
  821. package/src/utils/permissions/permissionUtils.test.ts +20 -42
  822. package/src/utils/persistence/keyDerivation.test.ts +306 -0
  823. package/src/utils/persistence/sensitiveFieldDetection.test.ts +271 -0
  824. package/src/utils/persistence/sensitiveFieldDetection.ts +2 -2
  825. package/src/utils/request-deduplication.test.ts +349 -0
  826. package/src/utils/request-deduplication.ts +6 -4
  827. package/src/utils/sanitization.unit.test.ts +346 -0
  828. package/src/utils/schemaUtils.unit.test.ts +441 -0
  829. package/src/utils/secureDataAccess.unit.test.ts +334 -0
  830. package/src/utils/secureErrors.unit.test.ts +390 -0
  831. package/src/utils/secureStorage.unit.test.ts +289 -0
  832. package/src/utils/security/auth-utils.ts +38 -27
  833. package/src/utils/security/secureDataAccess.test.ts +22 -191
  834. package/src/utils/security/secureDataAccess.ts +241 -281
  835. package/src/utils/security/secureErrors.test.ts +163 -0
  836. package/src/utils/security/secureStorage.test.ts +156 -0
  837. package/src/utils/security/secureStorage.ts +1 -1
  838. package/src/utils/security/security.test.ts +212 -0
  839. package/src/utils/security/security.ts +15 -18
  840. package/src/utils/security/securityMonitor.test.ts +90 -0
  841. package/src/utils/security/securityMonitor.ts +1 -1
  842. package/src/utils/security.unit.test.ts +155 -0
  843. package/src/utils/securityMonitor.unit.test.ts +276 -0
  844. package/src/utils/sessionTracking.unit.test.ts +218 -0
  845. package/src/utils/storage/config.unit.test.ts +239 -0
  846. package/src/utils/storage/helpers.test.ts +769 -456
  847. package/src/utils/storage/helpers.ts +174 -253
  848. package/src/utils/storage/index.unit.test.ts +68 -0
  849. package/src/utils/storage/storageUtils.ts +32 -0
  850. package/src/utils/storage/types.ts +9 -2
  851. package/src/utils/supabase/createBaseClient.test.ts +201 -0
  852. package/src/utils/supabase/createBaseClient.ts +2 -1
  853. package/src/utils/timezone/timezone.test.ts +26 -44
  854. package/src/utils/timezone.test.ts +345 -0
  855. package/src/utils/validation/common.test.ts +115 -0
  856. package/src/utils/validation/csrf.test.ts +198 -0
  857. package/src/utils/validation/csrf.ts +42 -41
  858. package/src/utils/validation/htmlSanitization.ts +27 -31
  859. package/src/utils/validation/htmlSanitization.unit.test.ts +618 -0
  860. package/src/utils/validation/passwordSchema.test.ts +164 -0
  861. package/src/utils/validation/schema.test.ts +127 -0
  862. package/src/utils/validation/schema.ts +6 -3
  863. package/src/utils/validation/sqlInjectionProtection.test.ts +165 -0
  864. package/src/utils/validation/sqlInjectionProtection.ts +2 -2
  865. package/src/utils/validation/user.test.ts +173 -0
  866. package/src/utils/validation/validation.test.ts +197 -0
  867. package/src/utils/validation/validationUtils.test.ts +294 -0
  868. package/src/utils/validation.unit.test.ts +307 -0
  869. package/src/utils/validationUtils.unit.test.ts +558 -0
  870. package/src/vite-env.d.ts +6 -0
  871. package/dist/AuthService-DmfO5rGS.d.ts +0 -524
  872. package/dist/DataTable-DRUIgtUH.d.ts +0 -166
  873. package/dist/DataTable-SOAFXIWY.js +0 -15
  874. package/dist/PublicPageProvider-CIGSujI2.d.ts +0 -4147
  875. package/dist/UnifiedAuthProvider-7SNDOWYD.js +0 -7
  876. package/dist/UnifiedAuthProvider-CKvHP1MK.d.ts +0 -139
  877. package/dist/api-7P7DI652.js +0 -4
  878. package/dist/audit-MYQXYZFU.js +0 -3
  879. package/dist/auth-BZOJqrdd.d.ts +0 -49
  880. package/dist/chunk-4DDCYDQ3.js +0 -544
  881. package/dist/chunk-5HNSDQWH.js +0 -5046
  882. package/dist/chunk-5W2A3DRC.js +0 -164
  883. package/dist/chunk-6GLLNA6U.js +0 -31
  884. package/dist/chunk-7ILTDCL2.js +0 -80
  885. package/dist/chunk-A3W6LW53.js +0 -70
  886. package/dist/chunk-AHU7G2R5.js +0 -423
  887. package/dist/chunk-C7ZQ5O4C.js +0 -481
  888. package/dist/chunk-EF2UGZWY.js +0 -611
  889. package/dist/chunk-FEJLJNWA.js +0 -181
  890. package/dist/chunk-FYHN4DD5.js +0 -415
  891. package/dist/chunk-GS5672WG.js +0 -2003
  892. package/dist/chunk-HF6O3O37.js +0 -187
  893. package/dist/chunk-J2U36LHD.js +0 -8517
  894. package/dist/chunk-LX6U42O3.js +0 -2177
  895. package/dist/chunk-MPBLMWVR.js +0 -2161
  896. package/dist/chunk-OJ4SKRSV.js +0 -105
  897. package/dist/chunk-S6ZQKDY6.js +0 -62
  898. package/dist/chunk-S7DKJPLT.js +0 -699
  899. package/dist/chunk-T5CVK4R3.js +0 -2816
  900. package/dist/chunk-TTRFSOKR.js +0 -121
  901. package/dist/chunk-Z2FNRKF3.js +0 -994
  902. package/dist/database.generated-DT8JTZiP.d.ts +0 -9406
  903. package/dist/event-CW5YB_2p.d.ts +0 -239
  904. package/dist/file-reference-BavO2eQj.d.ts +0 -148
  905. package/dist/functions-lBy5L2ry.d.ts +0 -208
  906. package/dist/timezone-0AyangqX.d.ts +0 -697
  907. package/dist/types-BeoeWV5I.d.ts +0 -110
  908. package/dist/types-DXstZpNI.d.ts +0 -614
  909. package/dist/types-t9H8qKRw.d.ts +0 -55
  910. package/dist/usePublicRouteParams-DQLrDqDb.d.ts +0 -876
  911. package/dist/useToast-AyaT-x7p.d.ts +0 -68
  912. package/dist/validation-643vUDZW.d.ts +0 -177
  913. package/scripts/build-docs-incremental.js +0 -179
  914. package/scripts/eslint-audit.cjs +0 -123
  915. package/scripts/generate-docs.js +0 -157
  916. package/scripts/install-cursor-rules.cjs +0 -255
  917. package/scripts/install-eslint-config.cjs +0 -349
  918. package/scripts/setup-build-cache.js +0 -73
  919. package/scripts/validate-pre-publish.js +0 -145
  920. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +0 -260
  921. package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +0 -224
  922. package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +0 -273
  923. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +0 -99
  924. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +0 -448
  925. package/src/__tests__/helpers/__tests__/timer-utils.test.ts +0 -371
  926. package/src/__tests__/hooks/usePermissions.test.ts +0 -268
  927. package/src/__tests__/integration/UserProfile.test.tsx +0 -124
  928. package/src/__tests__/public-recipe-view.test.ts +0 -228
  929. package/src/__tests__/rbac/PagePermissionGuard.test.tsx +0 -220
  930. package/src/__tests__/rls-policies.test.ts +0 -471
  931. package/src/components/DataTable/__tests__/DataTable.comprehensive.test.tsx +0 -759
  932. package/src/components/DataTable/__tests__/DataTable.default-state.test.tsx +0 -524
  933. package/src/components/DataTable/__tests__/DataTable.export.test.tsx +0 -705
  934. package/src/components/DataTable/__tests__/DataTable.grouping-aggregation.test.tsx +0 -658
  935. package/src/components/DataTable/__tests__/DataTable.hooks.test.tsx +0 -192
  936. package/src/components/DataTable/__tests__/DataTable.select-label-display.test.tsx +0 -483
  937. package/src/components/DataTable/__tests__/DataTable.test.tsx +0 -876
  938. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +0 -220
  939. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +0 -1474
  940. package/src/components/DataTable/__tests__/README.md +0 -145
  941. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +0 -788
  942. package/src/components/DataTable/__tests__/keyboard.test.tsx +0 -756
  943. package/src/components/DataTable/__tests__/mocks/MockRBACProvider.tsx +0 -66
  944. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +0 -730
  945. package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +0 -325
  946. package/src/components/DataTable/__tests__/styles.test.ts +0 -382
  947. package/src/components/DataTable/__tests__/test-utils/dataFactories.ts +0 -103
  948. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +0 -380
  949. package/src/components/DataTable/__tests__/test-utils.ts +0 -94
  950. package/src/components/DataTable/components/AccessDeniedPage.tsx +0 -159
  951. package/src/components/DataTable/components/ActionButtons.tsx +0 -190
  952. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +0 -160
  953. package/src/components/DataTable/components/ColumnFilter.tsx +0 -118
  954. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +0 -114
  955. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +0 -225
  956. package/src/components/DataTable/components/DataTableLayout.tsx +0 -573
  957. package/src/components/DataTable/components/DataTableModals.tsx +0 -245
  958. package/src/components/DataTable/components/DataTableToolbar.tsx +0 -271
  959. package/src/components/DataTable/components/EditFields.tsx +0 -327
  960. package/src/components/DataTable/components/EditableRow.tsx +0 -462
  961. package/src/components/DataTable/components/EmptyState.tsx +0 -79
  962. package/src/components/DataTable/components/FilterRow.tsx +0 -141
  963. package/src/components/DataTable/components/LoadingState.tsx +0 -17
  964. package/src/components/DataTable/components/PaginationControls.tsx +0 -289
  965. package/src/components/DataTable/components/RowComponent.tsx +0 -403
  966. package/src/components/DataTable/components/SortIndicator.tsx +0 -50
  967. package/src/components/DataTable/components/UnifiedTableBody.tsx +0 -355
  968. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +0 -657
  969. package/src/components/DataTable/components/__tests__/ActionButtons.test.tsx +0 -913
  970. package/src/components/DataTable/components/__tests__/BulkOperationsDropdown.test.tsx +0 -572
  971. package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +0 -612
  972. package/src/components/DataTable/components/__tests__/ColumnVisibilityDropdown.test.tsx +0 -708
  973. package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +0 -479
  974. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +0 -475
  975. package/src/components/DataTable/components/__tests__/DataTableToolbar.test.tsx +0 -157
  976. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +0 -1061
  977. package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +0 -437
  978. package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +0 -474
  979. package/src/components/DataTable/components/__tests__/GroupingDropdown.test.tsx +0 -617
  980. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +0 -1093
  981. package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +0 -139
  982. package/src/components/DataTable/components/__tests__/PaginationControls.test.tsx +0 -519
  983. package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +0 -1004
  984. package/src/components/DataTable/components/cellValueUtils.ts +0 -40
  985. package/src/components/DataTable/components/hooks/useImportModalFocus.ts +0 -53
  986. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -122
  987. package/src/components/DataTable/components/index.ts +0 -16
  988. package/src/components/DataTable/context/__tests__/DataTableContext.test.tsx +0 -342
  989. package/src/components/DataTable/core/ActionManager.ts +0 -235
  990. package/src/components/DataTable/core/ColumnManager.ts +0 -205
  991. package/src/components/DataTable/core/DataManager.ts +0 -188
  992. package/src/components/DataTable/core/LocalDataAdapter.ts +0 -274
  993. package/src/components/DataTable/core/PluginRegistry.ts +0 -229
  994. package/src/components/DataTable/core/StateManager.ts +0 -312
  995. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +0 -123
  996. package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +0 -305
  997. package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +0 -84
  998. package/src/components/DataTable/core/__tests__/DataManager.test.ts +0 -115
  999. package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +0 -100
  1000. package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +0 -120
  1001. package/src/components/DataTable/core/__tests__/StateManager.test.ts +0 -104
  1002. package/src/components/DataTable/core/index.ts +0 -1
  1003. package/src/components/DataTable/core/interfaces.ts +0 -338
  1004. package/src/components/DataTable/hooks/__tests__/useColumnOrderPersistence.test.ts +0 -521
  1005. package/src/components/DataTable/hooks/__tests__/useColumnVisibilityPersistence.test.ts +0 -167
  1006. package/src/components/DataTable/hooks/__tests__/useDataTableConfiguration.test.ts +0 -124
  1007. package/src/components/DataTable/hooks/__tests__/useDataTableDataPipeline.test.ts +0 -117
  1008. package/src/components/DataTable/hooks/__tests__/useDataTablePermissions.test.ts +0 -102
  1009. package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +0 -596
  1010. package/src/components/DataTable/hooks/__tests__/useEffectiveColumnOrder.test.ts +0 -53
  1011. package/src/components/DataTable/hooks/__tests__/useHierarchicalState.test.ts +0 -214
  1012. package/src/components/DataTable/hooks/__tests__/useTableColumns.test.ts +0 -448
  1013. package/src/components/DataTable/hooks/index.ts +0 -13
  1014. package/src/components/DataTable/types.ts +0 -761
  1015. package/src/components/DataTable/utils/__tests__/a11yUtils.test.ts +0 -612
  1016. package/src/components/DataTable/utils/__tests__/columnUtils.test.ts +0 -94
  1017. package/src/components/DataTable/utils/__tests__/errorHandling.test.ts +0 -266
  1018. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +0 -954
  1019. package/src/components/DataTable/utils/__tests__/flexibleImport.test.ts +0 -573
  1020. package/src/components/DataTable/utils/__tests__/hierarchicalSorting.test.ts +0 -247
  1021. package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +0 -570
  1022. package/src/components/DataTable/utils/__tests__/performanceUtils.test.ts +0 -470
  1023. package/src/components/DataTable/utils/__tests__/rowUtils.test.ts +0 -251
  1024. package/src/components/DataTable/utils/__tests__/selectFieldUtils.test.ts +0 -207
  1025. package/src/components/DataTable/utils/index.ts +0 -10
  1026. package/src/components/PublicLayout/index.ts +0 -32
  1027. package/src/components/Select/hooks/useSelectEvents.ts +0 -87
  1028. package/src/components/Select/hooks/useSelectSearch.ts +0 -91
  1029. package/src/components/Select/hooks/useSelectState.ts +0 -104
  1030. package/src/components/Select/utils/text.ts +0 -26
  1031. package/src/hooks/__tests__/ServiceHooks.test.tsx +0 -615
  1032. package/src/hooks/__tests__/hooks.integration.test.tsx +0 -607
  1033. package/src/hooks/__tests__/index.unit.test.ts +0 -220
  1034. package/src/hooks/__tests__/useApiFetch.unit.test.ts +0 -111
  1035. package/src/hooks/__tests__/useAppConfig.unit.test.ts +0 -347
  1036. package/src/hooks/__tests__/useComponentPerformance.unit.test.tsx +0 -144
  1037. package/src/hooks/__tests__/useDataTablePerformance.unit.test.ts +0 -776
  1038. package/src/hooks/__tests__/useDataTableState.test.ts +0 -76
  1039. package/src/hooks/__tests__/useDebounce.unit.test.ts +0 -82
  1040. package/src/hooks/__tests__/useEvents.unit.test.ts +0 -252
  1041. package/src/hooks/__tests__/useFileDisplay.unit.test.ts +0 -1112
  1042. package/src/hooks/__tests__/useFileUrl.unit.test.ts +0 -916
  1043. package/src/hooks/__tests__/useFileUrlCache.test.ts +0 -129
  1044. package/src/hooks/__tests__/useFocusManagement.unit.test.ts +0 -230
  1045. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +0 -828
  1046. package/src/hooks/__tests__/useFormDialog.test.ts +0 -478
  1047. package/src/hooks/__tests__/useInactivityTracker.unit.test.ts +0 -446
  1048. package/src/hooks/__tests__/useIsMobile.unit.test.ts +0 -317
  1049. package/src/hooks/__tests__/useKeyboardShortcuts.unit.test.ts +0 -910
  1050. package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +0 -294
  1051. package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +0 -961
  1052. package/src/hooks/__tests__/useOrganisations.unit.test.ts +0 -369
  1053. package/src/hooks/__tests__/usePerformanceMonitor.unit.test.ts +0 -694
  1054. package/src/hooks/__tests__/usePermissionCache.simple.test.ts +0 -192
  1055. package/src/hooks/__tests__/usePermissionCache.unit.test.ts +0 -741
  1056. package/src/hooks/__tests__/usePreventTabReload.test.ts +0 -88
  1057. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +0 -785
  1058. package/src/hooks/__tests__/usePublicEvent.test.ts +0 -678
  1059. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +0 -630
  1060. package/src/hooks/__tests__/usePublicFileDisplay.test.ts +0 -951
  1061. package/src/hooks/__tests__/usePublicRouteParams.unit.test.ts +0 -443
  1062. package/src/hooks/__tests__/useQueryCache.test.ts +0 -144
  1063. package/src/hooks/__tests__/useRBAC.unit.test.ts +0 -236
  1064. package/src/hooks/__tests__/useSessionDraft.test.ts +0 -163
  1065. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +0 -390
  1066. package/src/hooks/__tests__/useStorage.unit.test.ts +0 -751
  1067. package/src/hooks/__tests__/useToast.unit.test.tsx +0 -481
  1068. package/src/hooks/__tests__/useZodForm.unit.test.tsx +0 -37
  1069. package/src/hooks/public/index.ts +0 -36
  1070. package/src/hooks/public/usePublicFileDisplay.ts +0 -504
  1071. package/src/hooks/useFileDisplay.ts +0 -715
  1072. package/src/providers/OrganisationProvider.tsx +0 -92
  1073. package/src/providers/__tests__/AuthProvider.test.tsx +0 -287
  1074. package/src/providers/__tests__/EventProvider.test.tsx +0 -551
  1075. package/src/providers/__tests__/InactivityProvider.test-helper.tsx +0 -65
  1076. package/src/providers/__tests__/InactivityProvider.test.tsx +0 -572
  1077. package/src/providers/__tests__/OrganisationProvider.test.tsx +0 -617
  1078. package/src/providers/__tests__/ProviderLifecycle.test.tsx +0 -424
  1079. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +0 -596
  1080. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +0 -263
  1081. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +0 -294
  1082. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +0 -434
  1083. package/src/rbac/__tests__/auth-rbac-security.integration.test.tsx +0 -313
  1084. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +0 -486
  1085. package/src/rbac/__tests__/cache-invalidation.test.ts +0 -399
  1086. package/src/rbac/__tests__/engine.comprehensive.test.ts +0 -813
  1087. package/src/rbac/__tests__/isSuperAdmin.real.test.ts +0 -82
  1088. package/src/rbac/__tests__/rbac-core.test.tsx +0 -276
  1089. package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +0 -392
  1090. package/src/rbac/__tests__/rbac-engine-simplified.test.ts +0 -258
  1091. package/src/rbac/__tests__/rbac-functions.test.ts +0 -647
  1092. package/src/rbac/__tests__/rbac-integration.test.ts +0 -524
  1093. package/src/rbac/__tests__/rbac-role-isolation.test.ts +0 -456
  1094. package/src/rbac/__tests__/scenarios.user-role.test.tsx +0 -282
  1095. package/src/rbac/audit-enhanced.ts +0 -384
  1096. package/src/rbac/compliance/database-validator.ts +0 -165
  1097. package/src/rbac/compliance/index.ts +0 -48
  1098. package/src/rbac/compliance/pattern-detector.ts +0 -553
  1099. package/src/rbac/compliance/quick-fix-suggestions.ts +0 -209
  1100. package/src/rbac/compliance/runtime-compliance.ts +0 -99
  1101. package/src/rbac/compliance/setup-validator.ts +0 -131
  1102. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +0 -975
  1103. package/src/rbac/components/__tests__/PagePermissionGuard.performance.test.tsx +0 -248
  1104. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +0 -242
  1105. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +0 -1107
  1106. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +0 -184
  1107. package/src/rbac/components/index.ts +0 -26
  1108. package/src/rbac/hooks/__tests__/usePermissions.integration.test.ts +0 -432
  1109. package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +0 -579
  1110. package/src/rbac/hooks/index.ts +0 -34
  1111. package/src/rbac/hooks/permissions/index.ts +0 -4
  1112. package/src/rbac/hooks/useRBAC.simple.test.ts +0 -95
  1113. package/src/rbac/utils/__tests__/contextValidator.test.ts +0 -128
  1114. package/src/rbac/utils/__tests__/deep-equal.test.ts +0 -53
  1115. package/src/rbac/utils/__tests__/eventContext.test.ts +0 -433
  1116. package/src/rbac/utils/__tests__/eventContext.unit.test.ts +0 -490
  1117. package/src/services/__tests__/AuthService.restoreSession.test.ts +0 -39
  1118. package/src/services/__tests__/AuthService.test.ts +0 -1332
  1119. package/src/services/__tests__/BaseService.test.ts +0 -314
  1120. package/src/services/__tests__/EventService.eventColours.test.ts +0 -76
  1121. package/src/services/__tests__/EventService.test.ts +0 -1025
  1122. package/src/services/__tests__/InactivityService.lifecycle.test.ts +0 -411
  1123. package/src/services/__tests__/InactivityService.test.ts +0 -654
  1124. package/src/services/__tests__/OrganisationService.pagination.test.ts +0 -409
  1125. package/src/services/__tests__/OrganisationService.test.ts +0 -1176
  1126. package/src/theming/__tests__/parseEventColours.test.ts +0 -321
  1127. package/src/theming/__tests__/runtime.test.ts +0 -569
  1128. package/src/types/__tests__/file-reference.test.ts +0 -447
  1129. package/src/types/__tests__/guards.test.ts +0 -246
  1130. package/src/types/__tests__/organisation.roles.test.ts +0 -55
  1131. package/src/types/__tests__/organisation.test.ts +0 -1133
  1132. package/src/types/__tests__/theme.test.ts +0 -830
  1133. package/src/types/__tests__/type-validation.test.ts +0 -526
  1134. package/src/types/__tests__/validation.test.ts +0 -731
  1135. package/src/utils/__tests__/appConfig.unit.test.ts +0 -55
  1136. package/src/utils/__tests__/audit.unit.test.ts +0 -69
  1137. package/src/utils/__tests__/auth-utils.unit.test.ts +0 -70
  1138. package/src/utils/__tests__/bundleAnalysis.unit.test.ts +0 -339
  1139. package/src/utils/__tests__/cn.unit.test.ts +0 -34
  1140. package/src/utils/__tests__/debugLogger.test.ts +0 -417
  1141. package/src/utils/__tests__/deviceFingerprint.unit.test.ts +0 -818
  1142. package/src/utils/__tests__/dynamicUtils.unit.test.ts +0 -318
  1143. package/src/utils/__tests__/formatDate.unit.test.ts +0 -109
  1144. package/src/utils/__tests__/formatting.unit.test.ts +0 -99
  1145. package/src/utils/__tests__/index.unit.test.ts +0 -251
  1146. package/src/utils/__tests__/lazyLoad.unit.test.tsx +0 -321
  1147. package/src/utils/__tests__/logger.unit.test.ts +0 -398
  1148. package/src/utils/__tests__/organisationContext.unit.test.ts +0 -191
  1149. package/src/utils/__tests__/performanceBenchmark.test.ts +0 -175
  1150. package/src/utils/__tests__/performanceBudgets.unit.test.ts +0 -253
  1151. package/src/utils/__tests__/permissionTypes.unit.test.ts +0 -250
  1152. package/src/utils/__tests__/permissionUtils.unit.test.ts +0 -362
  1153. package/src/utils/__tests__/sanitization.unit.test.ts +0 -346
  1154. package/src/utils/__tests__/schemaUtils.unit.test.ts +0 -441
  1155. package/src/utils/__tests__/secureDataAccess.unit.test.ts +0 -335
  1156. package/src/utils/__tests__/secureErrors.unit.test.ts +0 -390
  1157. package/src/utils/__tests__/secureStorage.unit.test.ts +0 -289
  1158. package/src/utils/__tests__/security.unit.test.ts +0 -149
  1159. package/src/utils/__tests__/securityMonitor.unit.test.ts +0 -276
  1160. package/src/utils/__tests__/sessionTracking.unit.test.ts +0 -218
  1161. package/src/utils/__tests__/timezone.test.ts +0 -345
  1162. package/src/utils/__tests__/validation.unit.test.ts +0 -308
  1163. package/src/utils/__tests__/validationUtils.unit.test.ts +0 -555
  1164. package/src/utils/app/appNameResolver.simple.test.ts +0 -212
  1165. package/src/utils/file-reference/__tests__/file-reference.test.ts +0 -875
  1166. package/src/utils/google-places/index.ts +0 -26
  1167. package/src/utils/location/index.ts +0 -16
  1168. package/src/utils/persistence/__tests__/keyDerivation.test.ts +0 -135
  1169. package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +0 -123
  1170. package/src/utils/storage/__tests__/helpers.unit.test.ts +0 -332
  1171. package/src/utils/storage/__tests__/index.unit.test.ts +0 -16
  1172. package/src/utils/storage/index.ts +0 -67
  1173. package/src/utils/timezone/index.ts +0 -17
  1174. package/src/utils/validation/__tests__/csrf.test.ts +0 -105
  1175. package/src/utils/validation/__tests__/htmlSanitization.unit.test.ts +0 -598
  1176. package/src/utils/validation/__tests__/sqlInjectionProtection.test.ts +0 -92
  1177. package/src/utils/validation/__tests__/validationUtils.test.ts +0 -72
  1178. package/src/utils/validation/index.ts +0 -73
  1179. /package/src/components/DataTable/{components/__tests__ → ui}/COVERAGE_NOTE.md +0 -0
  1180. /package/src/components/DataTable/utils/{__tests__/COVERAGE_NOTE.md → COVERAGE_NOTE.md} +0 -0
  1181. /package/src/providers/{__tests__/README.md → README.md} +0 -0
  1182. /package/src/types/{__tests__/README.md → README.md} +0 -0
@@ -1,20 +1,30 @@
1
1
  /**
2
- * @file Storage Helpers Tests
3
- * @description Comprehensive tests for storage utility functions following TEST_STANDARD.md
2
+ * @file Unit Tests for Storage Helpers
3
+ * @package @jmruthers/pace-core
4
+ * @module Utils/Storage
5
+ * @since 1.0.0
4
6
  */
5
7
 
6
- import { vi } from 'vitest';
8
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
7
9
  import {
8
10
  uploadFile,
9
11
  deleteFile,
10
12
  downloadFile,
11
13
  getPublicUrl,
12
14
  getSignedUrl,
13
- generateFilePath
15
+ generateFilePath,
16
+ generateUniqueFileName,
17
+ extractFileMetadata,
18
+ listFiles,
19
+ archiveFile,
20
+ generateFileUrlsBatch
14
21
  } from './helpers';
15
22
  import { getBucketName } from './config';
16
- import { FileCategory } from '../../types/file-reference';
17
- import { createMockSupabaseClient } from '../../__tests__/helpers/test-utils';
23
+ import type { SupabaseClient } from '@supabase/supabase-js';
24
+ import type { Database } from '../../types/database';
25
+
26
+ // Following testing standards: use timeout parameter to prevent hanging
27
+ const TEST_TIMEOUT = 10000; // 10 seconds per test
18
28
 
19
29
  // Setup mocks
20
30
  beforeEach(() => {
@@ -26,12 +36,30 @@ afterEach(() => {
26
36
  });
27
37
 
28
38
  // Test data
29
- const mockSupabase = createMockSupabaseClient();
30
39
  const createTestFile = (name = 'test.pdf', type = 'application/pdf', size = 1024) => {
31
40
  return new File(['test content'], name, { type, size });
32
41
  };
33
42
 
34
- describe('[utility] Storage Helpers', () => {
43
+ const createMockStorageClient = () => {
44
+ const mockStorage = {
45
+ upload: vi.fn(),
46
+ getPublicUrl: vi.fn(),
47
+ createSignedUrl: vi.fn(),
48
+ remove: vi.fn(),
49
+ download: vi.fn(),
50
+ list: vi.fn(),
51
+ copy: vi.fn()
52
+ };
53
+
54
+ return {
55
+ from: vi.fn(() => mockStorage),
56
+ storage: {
57
+ from: vi.fn(() => mockStorage)
58
+ }
59
+ };
60
+ };
61
+
62
+ describe('[unit] Storage Helpers', () => {
35
63
  describe('getBucketName', () => {
36
64
  it('returns public-files bucket for public files', () => {
37
65
  expect(getBucketName(true)).toBe('public-files');
@@ -43,138 +71,422 @@ describe('[utility] Storage Helpers', () => {
43
71
  });
44
72
 
45
73
  describe('generateFilePath', () => {
46
- it('generates correct file path with org and customPath', () => {
74
+ it('generates path for private files with orgId and default folder', () => {
47
75
  const result = generateFilePath(
48
- { orgId: 'test-org-123', isPublic: false, customPath: 'general_documents' },
76
+ { appName: 'test-app', orgId: 'test-org-123', isPublic: false },
49
77
  'test-document.pdf'
50
78
  );
51
- expect(result).toBe('test-org-123/general_documents/test-document.pdf');
52
- });
79
+ expect(result).toBe('test-org-123/files/test-document.pdf');
80
+ }, TEST_TIMEOUT);
53
81
 
54
- it('generates unique paths for same file name', () => {
55
- const path1 = generateFilePath({ orgId: 'org-123', customPath: 'images' }, 'test.jpg');
56
- const path2 = generateFilePath({ orgId: 'org-123', customPath: 'images' }, 'test.jpg');
57
-
58
- expect(path1).toBe(path2);
59
- });
82
+ it('generates path for public files with orgId', () => {
83
+ const result = generateFilePath(
84
+ { appName: 'test-app', orgId: 'test-org-123', isPublic: true },
85
+ 'test-document.pdf'
86
+ );
87
+ expect(result).toBe('test-org-123/public/test-document.pdf');
88
+ }, TEST_TIMEOUT);
89
+
90
+ it('generates path with customPath', () => {
91
+ const result = generateFilePath(
92
+ { appName: 'test-app', orgId: 'test-org-123', isPublic: false, customPath: 'documents' },
93
+ 'test-document.pdf'
94
+ );
95
+ expect(result).toBe('test-org-123/documents/test-document.pdf');
96
+ }, TEST_TIMEOUT);
97
+
98
+ it('generates path for user-scoped files', () => {
99
+ const result = generateFilePath(
100
+ { appName: 'test-app', userId: 'user-123', isPublic: false },
101
+ 'test-document.pdf'
102
+ );
103
+ expect(result).toBe('users/user-123/files/test-document.pdf');
104
+ }, TEST_TIMEOUT);
60
105
 
61
- it('validates required orgId', () => {
62
- expect(() => generateFilePath({ orgId: '' } as any, 'test.jpg'))
106
+ it('throws error when neither orgId nor userId provided', () => {
107
+ expect(() => generateFilePath({ appName: 'test-app' } as any, 'test.jpg'))
63
108
  .toThrow('Either orgId or userId is required for file path generation');
64
- });
109
+ }, TEST_TIMEOUT);
110
+
111
+ it('prioritizes orgId over userId', () => {
112
+ const result = generateFilePath(
113
+ { appName: 'test-app', orgId: 'org-123', userId: 'user-123', isPublic: false },
114
+ 'test.jpg'
115
+ );
116
+ expect(result).toMatch(/^org-123\//);
117
+ expect(result).not.toMatch(/users\//);
118
+ }, TEST_TIMEOUT);
119
+
120
+ it('handles customPath for public files', () => {
121
+ const result = generateFilePath(
122
+ { appName: 'test-app', orgId: 'org-123', isPublic: true, customPath: 'images' },
123
+ 'test.jpg'
124
+ );
125
+ expect(result).toBe('org-123/images/test.jpg');
126
+ }, TEST_TIMEOUT);
127
+
128
+ it('handles customPath for private files', () => {
129
+ const result = generateFilePath(
130
+ { appName: 'test-app', orgId: 'org-123', isPublic: false, customPath: 'documents' },
131
+ 'test.pdf'
132
+ );
133
+ expect(result).toBe('org-123/documents/test.pdf');
134
+ }, TEST_TIMEOUT);
65
135
 
66
- it('handles special characters in file names', () => {
136
+ it('handles customPath for user-scoped files', () => {
67
137
  const result = generateFilePath(
68
- { orgId: 'org-123', customPath: 'images' },
69
- 'test file with spaces & symbols.jpg'
138
+ { appName: 'test-app', userId: 'user-123', isPublic: false, customPath: 'profile' },
139
+ 'avatar.jpg'
70
140
  );
141
+ expect(result).toBe('users/user-123/profile/avatar.jpg');
142
+ }, TEST_TIMEOUT);
143
+
144
+ it('handles customPath with nested paths', () => {
145
+ const result = generateFilePath(
146
+ { appName: 'test-app', orgId: 'org-123', isPublic: false, customPath: 'documents/2024' },
147
+ 'test.pdf'
148
+ );
149
+ expect(result).toBe('org-123/documents/2024/test.pdf');
150
+ }, TEST_TIMEOUT);
151
+ });
152
+
153
+ describe('generateUniqueFileName', () => {
154
+ it('generates unique filename with timestamp and UUID', () => {
155
+ const result = generateUniqueFileName('test.jpg');
156
+ expect(result).toMatch(/^\d+-[a-f0-9-]+-test\.jpg$/);
157
+ }, TEST_TIMEOUT);
158
+
159
+ it('handles files without extension', () => {
160
+ const result = generateUniqueFileName('test');
161
+ expect(result).toMatch(/^\d+-[a-f0-9-]+-test$/);
162
+ }, TEST_TIMEOUT);
163
+
164
+ it('sanitizes filename by removing invalid characters', () => {
165
+ const result = generateUniqueFileName('test<>:"/\\|?*file.jpg');
166
+ expect(result).not.toMatch(/[<>:"/\\|?*]/);
167
+ expect(result).toMatch(/\.jpg$/);
168
+ }, TEST_TIMEOUT);
169
+
170
+ it('replaces spaces with underscores', () => {
171
+ const result = generateUniqueFileName('test file.jpg');
172
+ expect(result).toMatch(/test_file/);
173
+ }, TEST_TIMEOUT);
174
+
175
+ it('removes directory traversal attempts', () => {
176
+ const result = generateUniqueFileName('../../../test.jpg');
177
+ expect(result).not.toMatch(/\.\./);
178
+ }, TEST_TIMEOUT);
179
+
180
+ it('limits filename length', () => {
181
+ const longName = 'a'.repeat(300) + '.jpg';
182
+ const result = generateUniqueFileName(longName);
183
+ // Should be truncated (timestamp + UUID + baseName + extension)
184
+ expect(result.length).toBeLessThan(350);
185
+ }, TEST_TIMEOUT);
186
+
187
+ it('handles filenames with multiple dots', () => {
188
+ const result = generateUniqueFileName('test.file.name.jpg');
189
+ expect(result).toMatch(/\.jpg$/);
190
+ expect(result).toContain('test');
191
+ }, TEST_TIMEOUT);
192
+
193
+ it('handles filenames with leading/trailing dots', () => {
194
+ const result = generateUniqueFileName('.test.jpg');
195
+ expect(result).not.toMatch(/^\./);
196
+ expect(result).toMatch(/\.jpg$/);
197
+ }, TEST_TIMEOUT);
198
+
199
+ it('handles filenames with only extension', () => {
200
+ const result = generateUniqueFileName('.jpg');
201
+ expect(result).toMatch(/\.jpg$/);
202
+ }, TEST_TIMEOUT);
203
+
204
+ it('handles empty filename', () => {
205
+ const result = generateUniqueFileName('');
206
+ expect(result).toMatch(/^\d+-[a-f0-9-]+$/);
207
+ }, TEST_TIMEOUT);
208
+
209
+ it('handles unicode characters in filename', () => {
210
+ const result = generateUniqueFileName('测试文件.jpg');
211
+ expect(result).toMatch(/\.jpg$/);
212
+ }, TEST_TIMEOUT);
213
+
214
+ it('removes multiple consecutive invalid characters', () => {
215
+ const result = generateUniqueFileName('test<<>>file.jpg');
216
+ expect(result).not.toMatch(/[<>]/);
217
+ expect(result).toMatch(/test.*file/);
218
+ }, TEST_TIMEOUT);
219
+ });
220
+
221
+ describe('extractFileMetadata', () => {
222
+ it('extracts basic metadata for non-image files', async () => {
223
+ const file = createTestFile('test.pdf', 'application/pdf', 1024);
224
+ const options = { appName: 'test-app', orgId: 'org-123' };
71
225
 
72
- expect(result).toBe('org-123/images/test file with spaces & symbols.jpg');
73
- });
226
+ const result = await extractFileMetadata(file, options, 'user-123');
227
+ expect(result.ok).toBe(true);
228
+ const data = result.data;
229
+ expect(data.mimeType).toBe('application/pdf');
230
+ expect(data.size).toBe(file.size); // Use actual file size
231
+ expect(data.orgId).toBe('org-123');
232
+ expect(data.appName).toBe('test-app');
233
+ expect(data.uploadedBy).toBe('user-123');
234
+ expect(data.uploadedAt).toBeDefined();
235
+ expect(data.isPublic).toBe(false);
236
+ }, TEST_TIMEOUT);
237
+
238
+ it('extracts image dimensions for image files', async () => {
239
+ const file = createTestFile('test.jpg', 'image/jpeg', 2048);
240
+
241
+ const mockImage = {
242
+ width: 800,
243
+ height: 600,
244
+ onload: null as any,
245
+ onerror: null as any,
246
+ _src: '',
247
+ get src() {
248
+ return this._src;
249
+ },
250
+ set src(value: string) {
251
+ this._src = value;
252
+ Promise.resolve().then(() => {
253
+ if (this.onload) {
254
+ this.onload();
255
+ }
256
+ });
257
+ }
258
+ };
259
+
260
+ vi.stubGlobal('Image', vi.fn(() => mockImage));
261
+ vi.stubGlobal('URL', {
262
+ createObjectURL: vi.fn(() => 'blob:mock-url'),
263
+ revokeObjectURL: vi.fn()
264
+ });
74
265
 
75
- it('preserves file extension', () => {
76
- const testCases = [
77
- { fileName: 'test.pdf', expectedExt: '.pdf' },
78
- { fileName: 'image.jpeg', expectedExt: '.jpeg' },
79
- { fileName: 'document.docx', expectedExt: '.docx' },
80
- { fileName: 'noextension', expectedExt: '' }
81
- ];
266
+ const options = { appName: 'test-app', orgId: 'org-123' };
267
+ const result = await extractFileMetadata(file, options, 'user-123');
268
+ expect(result.ok).toBe(true);
269
+ const data = result.data;
270
+ expect(data.mimeType).toBe('image/jpeg');
271
+ // Dimensions may or may not be extracted depending on async timing
272
+ if (data.width) {
273
+ expect(data.width).toBe(800);
274
+ expect(data.height).toBe(600);
275
+ }
276
+ }, TEST_TIMEOUT);
277
+
278
+ it('handles hash generation errors gracefully', async () => {
279
+ const file = createTestFile('test.pdf', 'application/pdf', 1024);
280
+ const options = { appName: 'test-app', orgId: 'org-123' };
281
+
282
+ // Mock crypto.subtle.digest to throw an error
283
+ const originalDigest = crypto.subtle.digest;
284
+ (crypto.subtle as any).digest = vi.fn().mockRejectedValue(new Error('Hash generation failed'));
285
+
286
+ const result = await extractFileMetadata(file, options, 'user-123');
287
+ expect(result.ok).toBe(true);
288
+ // Hash should be undefined when generation fails
289
+ expect(result.data.hash).toBeUndefined();
290
+
291
+ // Restore original function
292
+ (crypto.subtle as any).digest = originalDigest;
293
+ }, TEST_TIMEOUT);
294
+
295
+ it('includes custom metadata when provided', async () => {
296
+ const file = createTestFile('test.pdf', 'application/pdf', 1024);
297
+ const options = {
298
+ appName: 'test-app',
299
+ orgId: 'org-123',
300
+ metadata: { customField: 'customValue' }
301
+ };
302
+
303
+ const result = await extractFileMetadata(file, options, 'user-123');
304
+ expect(result.ok).toBe(true);
305
+ expect(result.data.customMetadata).toEqual({ customField: 'customValue' });
306
+ }, TEST_TIMEOUT);
307
+
308
+ it('handles user-scoped files with userId', async () => {
309
+ const file = createTestFile('test.pdf', 'application/pdf', 1024);
310
+ const options = {
311
+ appName: 'test-app',
312
+ userId: 'user-123'
313
+ };
314
+
315
+ const result = await extractFileMetadata(file, options, 'user-123');
316
+ expect(result.ok).toBe(true);
317
+ expect(result.data.userId).toBe('user-123');
318
+ expect(result.data.orgId).toBeUndefined();
319
+ }, TEST_TIMEOUT);
320
+
321
+ it('handles public files', async () => {
322
+ const file = createTestFile('test.jpg', 'image/jpeg', 1024);
323
+ const options = {
324
+ appName: 'test-app',
325
+ orgId: 'org-123',
326
+ isPublic: true
327
+ };
328
+
329
+ const result = await extractFileMetadata(file, options, 'user-123');
330
+ expect(result.ok).toBe(true);
331
+ expect(result.data.isPublic).toBe(true);
332
+ }, TEST_TIMEOUT);
333
+
334
+ it('handles tags when provided', async () => {
335
+ const file = createTestFile('test.pdf', 'application/pdf', 1024);
336
+ const options = {
337
+ appName: 'test-app',
338
+ orgId: 'org-123',
339
+ tags: ['tag1', 'tag2']
340
+ };
341
+
342
+ const result = await extractFileMetadata(file, options, 'user-123');
343
+ expect(result.ok).toBe(true);
344
+ expect(result.data.tags).toEqual(['tag1', 'tag2']);
345
+ }, TEST_TIMEOUT);
346
+
347
+ it('handles image dimension extraction errors gracefully', async () => {
348
+ const file = createTestFile('test.jpg', 'image/jpeg', 1024);
349
+
350
+ const mockImage = {
351
+ width: 0,
352
+ height: 0,
353
+ onload: null as any,
354
+ onerror: null as any,
355
+ _src: '',
356
+ get src() {
357
+ return this._src;
358
+ },
359
+ set src(value: string) {
360
+ this._src = value;
361
+ Promise.resolve().then(() => {
362
+ if (this.onerror) {
363
+ this.onerror();
364
+ }
365
+ });
366
+ }
367
+ };
82
368
 
83
- testCases.forEach(({ fileName, expectedExt }) => {
84
- const result = generateFilePath({ orgId: 'org-123', customPath: 'general_documents' }, fileName);
85
- expect(result).toMatch(new RegExp(`${expectedExt.replace('.', '\\.')}$`));
369
+ vi.stubGlobal('Image', vi.fn(() => mockImage));
370
+ vi.stubGlobal('URL', {
371
+ createObjectURL: vi.fn(() => 'blob:mock-url'),
372
+ revokeObjectURL: vi.fn()
86
373
  });
87
- });
374
+
375
+ const options = { appName: 'test-app', orgId: 'org-123' };
376
+ const result = await extractFileMetadata(file, options, 'user-123');
377
+ expect(result.ok).toBe(true);
378
+ // Should still return metadata even if image dimensions fail
379
+ expect(result.data.mimeType).toBe('image/jpeg');
380
+ expect(result.data.width).toBeUndefined();
381
+ expect(result.data.height).toBeUndefined();
382
+ }, TEST_TIMEOUT);
383
+
384
+ it('handles different image types', async () => {
385
+ const imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
386
+
387
+ for (const type of imageTypes) {
388
+ const file = createTestFile('test.jpg', type, 1024);
389
+ const options = { appName: 'test-app', orgId: 'org-123' };
390
+ const result = await extractFileMetadata(file, options, 'user-123');
391
+ expect(result.ok).toBe(true);
392
+ expect(result.data.mimeType).toBe(type);
393
+ }
394
+ }, TEST_TIMEOUT);
88
395
  });
89
396
 
90
397
  describe('uploadFile', () => {
91
398
  it('uploads file to correct bucket successfully', async () => {
92
399
  const testFile = createTestFile();
400
+ const mockClient = createMockStorageClient() as any;
93
401
  const options = {
402
+ appName: 'test-app',
94
403
  orgId: 'test-org-123',
95
- category: FileCategory.GENERAL_DOCUMENTS,
96
404
  isPublic: false
97
405
  };
98
406
 
99
- mockSupabase.storage = {
100
- from: vi.fn(() => ({
101
- upload: vi.fn().mockResolvedValue({
102
- data: { path: 'test-path' },
103
- error: null
104
- })
105
- }))
106
- };
107
-
108
- const result = await uploadFile(mockSupabase, testFile, options);
407
+ (mockClient.storage.from().upload as any).mockResolvedValue({
408
+ data: { path: 'test-path' },
409
+ error: null
410
+ });
411
+ (mockClient.storage.from().list as any).mockResolvedValue({
412
+ data: [],
413
+ error: null
414
+ });
109
415
 
110
- expect(mockSupabase.storage.from).toHaveBeenCalledWith('files');
111
- expect(result.success).toBe(true);
112
- expect(result.path).toMatch(/^test-org-123\//);
113
- });
416
+ const result = await uploadFile(mockClient as SupabaseClient<Database>, testFile, options);
417
+ expect(mockClient.storage.from).toHaveBeenCalledWith('files');
418
+ expect(result.ok).toBe(true);
419
+ expect(result.data.path).toMatch(/^test-org-123\//);
420
+ }, TEST_TIMEOUT);
114
421
 
115
422
  it('uploads to public bucket when isPublic is true', async () => {
116
423
  const testFile = createTestFile();
424
+ const mockClient = createMockStorageClient() as any;
117
425
  const options = {
426
+ appName: 'test-app',
118
427
  orgId: 'test-org-123',
119
- category: FileCategory.EVENT_LOGOS,
120
428
  isPublic: true
121
429
  };
122
430
 
123
- mockSupabase.storage = {
124
- from: vi.fn(() => ({
125
- upload: vi.fn().mockResolvedValue({
126
- data: { path: 'test-path' },
127
- error: null
128
- })
129
- }))
130
- };
431
+ (mockClient.storage.from().upload as any).mockResolvedValue({
432
+ data: { path: 'test-path' },
433
+ error: null
434
+ });
435
+ (mockClient.storage.from().list as any).mockResolvedValue({
436
+ data: [],
437
+ error: null
438
+ });
439
+ (mockClient.storage.from().getPublicUrl as any).mockReturnValue({
440
+ data: { publicUrl: 'https://example.com/test-path' }
441
+ });
131
442
 
132
- await uploadFile(mockSupabase, testFile, options);
443
+ await uploadFile(mockClient as SupabaseClient<Database>, testFile, options);
133
444
 
134
- expect(mockSupabase.storage.from).toHaveBeenCalledWith('public-files');
135
- });
445
+ expect(mockClient.storage.from).toHaveBeenCalledWith('public-files');
446
+ }, TEST_TIMEOUT);
136
447
 
137
448
  it('handles upload errors gracefully', async () => {
138
449
  const testFile = createTestFile();
450
+ const mockClient = createMockStorageClient() as any;
139
451
  const options = {
452
+ appName: 'test-app',
140
453
  orgId: 'test-org-123',
141
- category: FileCategory.GENERAL_DOCUMENTS,
142
454
  isPublic: false
143
455
  };
144
456
 
145
- mockSupabase.storage = {
146
- from: vi.fn(() => ({
147
- upload: vi.fn().mockResolvedValue({
148
- data: null,
149
- error: { message: 'Upload failed' }
150
- })
151
- }))
152
- };
457
+ (mockClient.storage.from().upload as any).mockResolvedValue({
458
+ data: null,
459
+ error: { message: 'Upload failed' }
460
+ });
153
461
 
154
- const result = await uploadFile(mockSupabase, testFile, options);
462
+ const result = await uploadFile(mockClient as SupabaseClient<Database>, testFile, options);
155
463
 
156
- expect(result.success).toBe(false);
157
- expect(result.error).toBe('Upload failed: Upload failed');
158
- });
464
+ expect(result.ok).toBe(false);
465
+ expect(result.error.message).toBe('Upload failed');
466
+ }, TEST_TIMEOUT);
159
467
 
160
- it('validates required parameters', async () => {
161
- const testFile = createTestFile();
162
-
163
- const noClient = await uploadFile(null as any, testFile, {} as any);
164
- expect(noClient.success).toBe(false);
165
-
166
- const noFile = await uploadFile(mockSupabase, null as any, {} as any);
167
- expect(noFile.success).toBe(false);
468
+ it('validates file size before upload', async () => {
469
+ // PDF limit is 50MB, so use 60MB to exceed limit
470
+ const largeFile = createTestFile('large.pdf', 'application/pdf', 60 * 1024 * 1024);
471
+ const mockClient = createMockStorageClient() as any;
472
+ const options = {
473
+ appName: 'test-app',
474
+ orgId: 'test-org-123',
475
+ isPublic: false
476
+ };
168
477
 
169
- const noOrg = await uploadFile(mockSupabase, testFile, { orgId: '' } as any);
170
- expect(noOrg.success).toBe(false);
171
- });
478
+ const result = await uploadFile(mockClient as SupabaseClient<Database>, largeFile, options);
479
+ expect(result.ok).toBe(false);
480
+ expect(result.error).toBeDefined();
481
+ // The error comes from validateFileSize which returns error message about size
482
+ }, TEST_TIMEOUT);
172
483
 
173
- it('includes file metadata in result', { timeout: 10000 }, async () => {
484
+ it('includes file metadata in result', async () => {
174
485
  const testFile = createTestFile('test.jpg', 'image/jpeg', 2048);
486
+ const mockClient = createMockStorageClient() as any;
175
487
  const options = {
488
+ appName: 'test-app',
176
489
  orgId: 'test-org-123',
177
- category: FileCategory.IMAGES,
178
490
  isPublic: false
179
491
  };
180
492
 
@@ -190,347 +502,283 @@ describe('[utility] Storage Helpers', () => {
190
502
  },
191
503
  set src(value: string) {
192
504
  this._src = value;
193
- // Trigger onload asynchronously when src is set
194
- setTimeout(() => {
505
+ Promise.resolve().then(() => {
195
506
  if (this.onload) {
196
507
  this.onload();
197
508
  }
198
- }, 0);
509
+ });
199
510
  }
200
511
  };
201
512
 
202
- const ImageConstructor = vi.fn(() => mockImage);
203
-
204
- vi.stubGlobal('Image', ImageConstructor);
513
+ vi.stubGlobal('Image', vi.fn(() => mockImage));
205
514
  vi.stubGlobal('URL', {
206
515
  createObjectURL: vi.fn(() => 'blob:mock-url'),
207
516
  revokeObjectURL: vi.fn()
208
517
  });
209
518
 
210
- mockSupabase.storage = {
211
- from: vi.fn(() => ({
212
- upload: vi.fn().mockResolvedValue({
213
- data: { path: 'test-path' },
214
- error: null
215
- }),
216
- list: vi.fn().mockResolvedValue({
217
- data: [],
218
- error: null
219
- })
220
- }))
221
- };
222
-
223
- const result = await uploadFile(mockSupabase, testFile, options);
519
+ (mockClient.storage.from().upload as any).mockResolvedValue({
520
+ data: { path: 'test-path' },
521
+ error: null
522
+ });
523
+ (mockClient.storage.from().list as any).mockResolvedValue({
524
+ data: [],
525
+ error: null
526
+ });
224
527
 
225
- expect(result.metadata.mimeType).toBe('image/jpeg');
226
- expect(result.metadata.size).toBe(testFile.size);
227
- expect(result.metadata.orgId).toBe('test-org-123');
228
- });
528
+ const result = await uploadFile(mockClient as SupabaseClient<Database>, testFile, options);
529
+ expect(result.ok).toBe(true);
530
+ expect(result.data.metadata?.mimeType).toBe('image/jpeg');
531
+ expect(result.data.metadata?.size).toBe(testFile.size);
532
+ expect(result.data.metadata?.orgId).toBe('test-org-123');
533
+ }, TEST_TIMEOUT);
229
534
 
230
535
  it('uses custom path when provided', async () => {
231
536
  const testFile = createTestFile();
537
+ const mockClient = createMockStorageClient() as any;
232
538
  const options = {
539
+ appName: 'test-app',
233
540
  orgId: 'test-org-123',
234
- category: FileCategory.GENERAL_DOCUMENTS,
235
541
  isPublic: false,
236
- customPath: 'custom/path/file.pdf'
542
+ customPath: 'custom/path'
237
543
  };
238
544
 
239
- const mockUpload = vi.fn().mockResolvedValue({
240
- data: { path: 'custom/path/file.pdf' },
545
+ (mockClient.storage.from().upload as any).mockResolvedValue({
546
+ data: { path: 'test-org-123/custom/path/test.pdf' },
547
+ error: null
548
+ });
549
+ (mockClient.storage.from().list as any).mockResolvedValue({
550
+ data: [],
241
551
  error: null
242
552
  });
243
553
 
244
- mockSupabase.storage = {
245
- from: vi.fn(() => ({ upload: mockUpload }))
246
- };
247
-
248
- await uploadFile(mockSupabase, testFile, options);
249
-
250
- expect(mockUpload).toHaveBeenCalled();
251
- });
554
+ const result = await uploadFile(mockClient as SupabaseClient<Database>, testFile, options);
555
+ expect(result.ok).toBe(true);
556
+ expect(result.data.path).toContain('custom/path');
557
+ }, TEST_TIMEOUT);
252
558
  });
253
559
 
254
560
  describe('deleteFile', () => {
255
561
  it('deletes file from correct bucket successfully', async () => {
256
562
  const filePath = 'org-123/documents/test.pdf';
563
+ const mockClient = createMockStorageClient() as any;
257
564
 
258
- const mockRemove = vi.fn().mockResolvedValue({
565
+ (mockClient.storage.from().remove as any).mockResolvedValue({
259
566
  data: null,
260
567
  error: null
261
568
  });
262
569
 
263
- mockSupabase.storage = {
264
- from: vi.fn(() => ({ remove: mockRemove }))
265
- };
266
-
267
- const result = await deleteFile(mockSupabase, filePath, false);
268
-
269
- expect(mockSupabase.storage.from).toHaveBeenCalledWith('files');
270
- expect(mockRemove).toHaveBeenCalledWith([filePath]);
271
- expect(result.success).toBe(true);
272
- });
570
+ const result = await deleteFile(mockClient as SupabaseClient<Database>, filePath, false);
571
+ expect(mockClient.storage.from).toHaveBeenCalledWith('files');
572
+ expect(mockClient.storage.from().remove).toHaveBeenCalledWith([filePath]);
573
+ expect(result.ok).toBe(true);
574
+ }, TEST_TIMEOUT);
273
575
 
274
576
  it('deletes from public bucket when isPublic is true', async () => {
275
577
  const filePath = 'org-123/logos/logo.png';
578
+ const mockClient = createMockStorageClient() as any;
276
579
 
277
- const mockRemove = vi.fn().mockResolvedValue({
580
+ (mockClient.storage.from().remove as any).mockResolvedValue({
278
581
  data: null,
279
582
  error: null
280
583
  });
281
584
 
282
- mockSupabase.storage = {
283
- from: vi.fn(() => ({ remove: mockRemove }))
284
- };
285
-
286
- await deleteFile(mockSupabase, filePath, true);
585
+ await deleteFile(mockClient as SupabaseClient<Database>, filePath, true);
287
586
 
288
- expect(mockSupabase.storage.from).toHaveBeenCalledWith('public-files');
289
- });
587
+ expect(mockClient.storage.from).toHaveBeenCalledWith('public-files');
588
+ }, TEST_TIMEOUT);
290
589
 
291
590
  it('handles delete errors gracefully', async () => {
292
591
  const filePath = 'org-123/documents/test.pdf';
592
+ const mockClient = createMockStorageClient() as any;
293
593
 
294
- const mockRemove = vi.fn().mockResolvedValue({
594
+ (mockClient.storage.from().remove as any).mockResolvedValue({
295
595
  data: null,
296
596
  error: { message: 'File not found' }
297
597
  });
298
598
 
299
- mockSupabase.storage = {
300
- from: vi.fn(() => ({ remove: mockRemove }))
301
- };
302
-
303
- const result = await deleteFile(mockSupabase, filePath, false);
304
-
305
- expect(result.success).toBe(false);
306
- expect(result.error).toBe('Delete failed: File not found');
307
- });
308
-
309
- it('validates required parameters', async () => {
310
- const delNoClient = await deleteFile(null as any, 'path', false);
311
- expect(delNoClient.success).toBe(false);
312
-
313
- const delNoPath = await deleteFile(mockSupabase, '', false);
314
- expect(delNoPath.success).toBe(false);
315
- });
599
+ const result = await deleteFile(mockClient as SupabaseClient<Database>, filePath, false);
600
+ expect(result.ok).toBe(false);
601
+ expect(result.error.message).toBe('File not found');
602
+ }, TEST_TIMEOUT);
316
603
  });
317
604
 
318
605
  describe('downloadFile', () => {
319
606
  it('downloads file from correct bucket successfully', async () => {
320
607
  const filePath = 'org-123/documents/test.pdf';
321
608
  const mockBlob = new Blob(['file content'], { type: 'application/pdf' });
609
+ const mockClient = createMockStorageClient() as any;
322
610
 
323
- const mockDownload = vi.fn().mockResolvedValue({
611
+ (mockClient.storage.from().download as any).mockResolvedValue({
324
612
  data: mockBlob,
325
613
  error: null
326
614
  });
327
615
 
328
- const mockList = vi.fn().mockResolvedValue({
616
+ (mockClient.storage.from().list as any).mockResolvedValue({
329
617
  data: [{ metadata: { size: mockBlob.size, mimetype: mockBlob.type } }],
330
618
  error: null
331
619
  });
332
- mockSupabase.storage = {
333
- from: vi.fn(() => ({ download: mockDownload, list: mockList }))
334
- };
335
-
336
- const result = await downloadFile(mockSupabase, filePath, false);
337
620
 
338
- expect(mockSupabase.storage.from).toHaveBeenCalledWith('files');
339
- expect(mockDownload).toHaveBeenCalledWith(filePath);
340
- expect(result?.blob).toBe(mockBlob);
341
- });
621
+ const result = await downloadFile(mockClient as SupabaseClient<Database>, filePath, false);
622
+ expect(mockClient.storage.from).toHaveBeenCalledWith('files');
623
+ expect(mockClient.storage.from().download).toHaveBeenCalledWith(filePath);
624
+ expect(result.ok).toBe(true);
625
+ expect(result.data.blob).toBe(mockBlob);
626
+ expect(result.data.metadata.name).toBe('test.pdf');
627
+ }, TEST_TIMEOUT);
342
628
 
343
629
  it('downloads from public bucket when isPublic is true', async () => {
344
630
  const filePath = 'org-123/logos/logo.png';
345
631
  const mockBlob = new Blob(['image content'], { type: 'image/png' });
632
+ const mockClient = createMockStorageClient() as any;
346
633
 
347
- const mockDownload = vi.fn().mockResolvedValue({
634
+ (mockClient.storage.from().download as any).mockResolvedValue({
348
635
  data: mockBlob,
349
636
  error: null
350
637
  });
351
638
 
352
- mockSupabase.storage = {
353
- from: vi.fn(() => ({ download: mockDownload }))
354
- };
355
-
356
- await downloadFile(mockSupabase, filePath, true);
639
+ await downloadFile(mockClient as SupabaseClient<Database>, filePath, true);
357
640
 
358
- expect(mockSupabase.storage.from).toHaveBeenCalledWith('public-files');
359
- });
641
+ expect(mockClient.storage.from).toHaveBeenCalledWith('public-files');
642
+ }, TEST_TIMEOUT);
360
643
 
361
644
  it('returns null when download fails', async () => {
362
645
  const filePath = 'org-123/documents/test.pdf';
646
+ const mockClient = createMockStorageClient() as any;
363
647
 
364
- const mockDownload = vi.fn().mockResolvedValue({
648
+ (mockClient.storage.from().download as any).mockResolvedValue({
365
649
  data: null,
366
650
  error: { message: 'File not found' }
367
651
  });
368
652
 
369
- mockSupabase.storage = {
370
- from: vi.fn(() => ({ download: mockDownload }))
371
- };
372
-
373
- const result = await downloadFile(mockSupabase, filePath, false);
374
-
375
- expect(result).toBe(null);
376
- });
377
-
378
- it('validates required parameters', async () => {
379
- const dlNoClient = await downloadFile(null as any, 'path', false);
380
- expect(dlNoClient).toBe(null);
381
-
382
- const dlNoPath = await downloadFile(mockSupabase, '', false);
383
- expect(dlNoPath).toBe(null);
384
- });
653
+ const result = await downloadFile(mockClient as SupabaseClient<Database>, filePath, false);
654
+ expect(result.ok).toBe(false);
655
+ expect(result.error).toBeDefined();
656
+ }, TEST_TIMEOUT);
385
657
  });
386
658
 
387
659
  describe('getPublicUrl', () => {
388
660
  it('generates public URL for public bucket', () => {
389
661
  const filePath = 'org-123/logos/logo.png';
662
+ const mockClient = createMockStorageClient() as any;
390
663
 
391
- const mockGetPublicUrl = vi.fn().mockReturnValue({
664
+ (mockClient.storage.from().getPublicUrl as any).mockReturnValue({
392
665
  data: { publicUrl: 'https://example.com/public/logo.png' }
393
666
  });
394
667
 
395
- mockSupabase.storage = {
396
- from: vi.fn(() => ({ getPublicUrl: mockGetPublicUrl }))
397
- };
398
-
399
- const result = getPublicUrl(mockSupabase, filePath, true);
668
+ const result = getPublicUrl(mockClient as SupabaseClient<Database>, filePath, true);
400
669
 
401
- expect(mockSupabase.storage.from).toHaveBeenCalledWith('public-files');
402
- expect(mockGetPublicUrl).toHaveBeenCalledWith(filePath);
670
+ expect(mockClient.storage.from).toHaveBeenCalledWith('public-files');
671
+ expect(mockClient.storage.from().getPublicUrl).toHaveBeenCalledWith(filePath);
403
672
  expect(result).toBe('https://example.com/public/logo.png');
404
- });
673
+ }, TEST_TIMEOUT);
405
674
 
406
675
  it('returns direct URLs without calling storage', () => {
407
676
  const directUrl = 'https://cdn.example.com/logo.png';
677
+ const mockClient = createMockStorageClient() as any;
408
678
 
409
- const localSupabase = createMockSupabaseClient();
410
- localSupabase.storage = {
411
- from: vi.fn()
412
- };
413
-
414
- const result = getPublicUrl(localSupabase as any, directUrl, true);
679
+ const result = getPublicUrl(mockClient as SupabaseClient<Database>, directUrl, true);
415
680
 
416
- expect(localSupabase.storage.from).not.toHaveBeenCalled();
681
+ expect(mockClient.storage.from).not.toHaveBeenCalled();
417
682
  expect(result).toBe(directUrl);
418
- });
683
+ }, TEST_TIMEOUT);
419
684
 
420
685
  it('generates public URL for private bucket', () => {
421
686
  const filePath = 'org-123/documents/test.pdf';
687
+ const mockClient = createMockStorageClient() as any;
422
688
 
423
- const mockGetPublicUrl = vi.fn().mockReturnValue({
689
+ (mockClient.storage.from().getPublicUrl as any).mockReturnValue({
424
690
  data: { publicUrl: 'https://example.com/files/test.pdf' }
425
691
  });
426
692
 
427
- mockSupabase.storage = {
428
- from: vi.fn(() => ({ getPublicUrl: mockGetPublicUrl }))
429
- };
430
-
431
- const result = getPublicUrl(mockSupabase, filePath, false);
693
+ const result = getPublicUrl(mockClient as SupabaseClient<Database>, filePath, false);
432
694
 
433
- expect(mockSupabase.storage.from).toHaveBeenCalledWith('files');
695
+ expect(mockClient.storage.from).toHaveBeenCalledWith('files');
434
696
  expect(result).toBe('https://example.com/files/test.pdf');
435
- });
697
+ }, TEST_TIMEOUT);
436
698
 
437
699
  it('uses explicit bucket prefix when provided', () => {
438
700
  const filePath = 'public-files/org-123/logo.png';
701
+ const mockClient = createMockStorageClient() as any;
439
702
 
440
- const mockGetPublicUrl = vi.fn().mockReturnValue({
703
+ (mockClient.storage.from().getPublicUrl as any).mockReturnValue({
441
704
  data: { publicUrl: 'https://example.com/public/logo.png' }
442
705
  });
443
706
 
444
- mockSupabase.storage = {
445
- from: vi.fn(() => ({ getPublicUrl: mockGetPublicUrl }))
446
- };
447
-
448
- const result = getPublicUrl(mockSupabase, filePath, true);
707
+ const result = getPublicUrl(mockClient as SupabaseClient<Database>, filePath, true);
449
708
 
450
- expect(mockSupabase.storage.from).toHaveBeenCalledWith('public-files');
451
- expect(mockGetPublicUrl).toHaveBeenCalledWith('org-123/logo.png');
709
+ expect(mockClient.storage.from).toHaveBeenCalledWith('public-files');
710
+ expect(mockClient.storage.from().getPublicUrl).toHaveBeenCalledWith('org-123/logo.png');
452
711
  expect(result).toBe('https://example.com/public/logo.png');
453
- });
454
-
455
- it('supports custom bucket hints without hyphen characters', () => {
456
- const filePath = 'branding/org-123/logo.png';
457
-
458
- const mockGetPublicUrl = vi.fn().mockReturnValue({
459
- data: { publicUrl: 'https://example.com/branding/logo.png' }
460
- });
461
-
462
- mockSupabase.storage = {
463
- from: vi.fn(() => ({ getPublicUrl: mockGetPublicUrl }))
464
- };
465
-
466
- const result = getPublicUrl(mockSupabase, filePath, true);
467
-
468
- expect(mockSupabase.storage.from).toHaveBeenCalledWith('branding');
469
- expect(mockGetPublicUrl).toHaveBeenCalledWith('org-123/logo.png');
470
- expect(result).toBe('https://example.com/branding/logo.png');
471
- });
712
+ }, TEST_TIMEOUT);
472
713
 
473
714
  it('supports double colon bucket hints to avoid path ambiguity', () => {
474
715
  const filePath = 'brand-assets::org-123/logo.png';
716
+ const mockClient = createMockStorageClient() as any;
475
717
 
476
- const mockGetPublicUrl = vi.fn().mockReturnValue({
718
+ (mockClient.storage.from().getPublicUrl as any).mockReturnValue({
477
719
  data: { publicUrl: 'https://example.com/brand/logo.png' }
478
720
  });
479
721
 
480
- mockSupabase.storage = {
481
- from: vi.fn(() => ({ getPublicUrl: mockGetPublicUrl }))
482
- };
483
-
484
- const result = getPublicUrl(mockSupabase, filePath, true);
722
+ const result = getPublicUrl(mockClient as SupabaseClient<Database>, filePath, true);
485
723
 
486
- expect(mockSupabase.storage.from).toHaveBeenCalledWith('brand-assets');
487
- expect(mockGetPublicUrl).toHaveBeenCalledWith('org-123/logo.png');
724
+ expect(mockClient.storage.from).toHaveBeenCalledWith('brand-assets');
725
+ expect(mockClient.storage.from().getPublicUrl).toHaveBeenCalledWith('org-123/logo.png');
488
726
  expect(result).toBe('https://example.com/brand/logo.png');
489
- });
727
+ }, TEST_TIMEOUT);
490
728
 
491
729
  it('treats numeric leading path segments as directories rather than buckets', () => {
492
730
  const filePath = '2024/05/logo.png';
731
+ const mockClient = createMockStorageClient() as any;
493
732
 
494
- const mockGetPublicUrl = vi.fn().mockReturnValue({
733
+ (mockClient.storage.from().getPublicUrl as any).mockReturnValue({
495
734
  data: { publicUrl: 'https://example.com/public/logo.png' }
496
735
  });
497
736
 
498
- mockSupabase.storage = {
499
- from: vi.fn(() => ({ getPublicUrl: mockGetPublicUrl }))
500
- };
501
-
502
- const result = getPublicUrl(mockSupabase, filePath, true);
737
+ const result = getPublicUrl(mockClient as SupabaseClient<Database>, filePath, true);
503
738
 
504
- expect(mockSupabase.storage.from).toHaveBeenCalledWith('public-files');
505
- expect(mockGetPublicUrl).toHaveBeenCalledWith(filePath);
739
+ expect(mockClient.storage.from).toHaveBeenCalledWith('public-files');
740
+ expect(mockClient.storage.from().getPublicUrl).toHaveBeenCalledWith(filePath);
506
741
  expect(result).toBe('https://example.com/public/logo.png');
507
- });
742
+ }, TEST_TIMEOUT);
508
743
 
509
744
  it('ignores bucket prefix when path starts with UUID', () => {
510
745
  const filePath = '123e4567-e89b-12d3-a456-426614174000/event_logos/logo.png';
746
+ const mockClient = createMockStorageClient() as any;
511
747
 
512
- const mockGetPublicUrl = vi.fn().mockReturnValue({
748
+ (mockClient.storage.from().getPublicUrl as any).mockReturnValue({
513
749
  data: { publicUrl: 'https://example.com/public/logo.png' }
514
750
  });
515
751
 
516
- mockSupabase.storage = {
517
- from: vi.fn(() => ({ getPublicUrl: mockGetPublicUrl }))
518
- };
519
-
520
- const result = getPublicUrl(mockSupabase, filePath, true);
752
+ const result = getPublicUrl(mockClient as SupabaseClient<Database>, filePath, true);
521
753
 
522
- expect(mockSupabase.storage.from).toHaveBeenCalledWith('public-files');
523
- expect(mockGetPublicUrl).toHaveBeenCalledWith(filePath);
754
+ expect(mockClient.storage.from).toHaveBeenCalledWith('public-files');
755
+ expect(mockClient.storage.from().getPublicUrl).toHaveBeenCalledWith(filePath);
524
756
  expect(result).toBe('https://example.com/public/logo.png');
525
- });
757
+ }, TEST_TIMEOUT);
526
758
 
527
759
  it('validates required parameters', () => {
760
+ const mockClient = createMockStorageClient() as any;
761
+
528
762
  expect(() => getPublicUrl(null as any, 'path', false))
529
- .toThrow();
763
+ .toThrow('Supabase client is required');
530
764
 
531
- expect(() => getPublicUrl(mockSupabase, '', false))
532
- .toThrow();
533
- });
765
+ expect(() => getPublicUrl(mockClient as SupabaseClient<Database>, '', false))
766
+ .toThrow('A valid storage path is required');
767
+ }, TEST_TIMEOUT);
768
+
769
+ it('normalizes path by trimming whitespace and leading slashes', () => {
770
+ const filePath = ' /org-123/logo.png ';
771
+ const mockClient = createMockStorageClient() as any;
772
+
773
+ (mockClient.storage.from().getPublicUrl as any).mockReturnValue({
774
+ data: { publicUrl: 'https://example.com/public/logo.png' }
775
+ });
776
+
777
+ const result = getPublicUrl(mockClient as SupabaseClient<Database>, filePath, true);
778
+
779
+ expect(mockClient.storage.from().getPublicUrl).toHaveBeenCalledWith('org-123/logo.png');
780
+ expect(result).toBe('https://example.com/public/logo.png');
781
+ }, TEST_TIMEOUT);
534
782
  });
535
783
 
536
784
  describe('getSignedUrl', () => {
@@ -540,22 +788,19 @@ describe('[utility] Storage Helpers', () => {
540
788
  appName: 'test-app',
541
789
  orgId: 'org-123'
542
790
  };
791
+ const mockClient = createMockStorageClient() as any;
543
792
 
544
- const mockCreateSignedUrl = vi.fn().mockResolvedValue({
793
+ (mockClient.storage.from().createSignedUrl as any).mockResolvedValue({
545
794
  data: { signedUrl: 'https://example.com/signed/test.pdf?token=abc123' },
546
795
  error: null
547
796
  });
548
797
 
549
- mockSupabase.storage = {
550
- from: vi.fn(() => ({ createSignedUrl: mockCreateSignedUrl }))
551
- };
552
-
553
- const result = await getSignedUrl(mockSupabase, filePath, options);
554
-
555
- expect(mockSupabase.storage.from).toHaveBeenCalledWith('files');
556
- expect(mockCreateSignedUrl).toHaveBeenCalledWith(filePath, 3600);
557
- expect(result?.url).toBe('https://example.com/signed/test.pdf?token=abc123');
558
- });
798
+ const result = await getSignedUrl(mockClient as SupabaseClient<Database>, filePath, options);
799
+ expect(mockClient.storage.from).toHaveBeenCalledWith('files');
800
+ expect(mockClient.storage.from().createSignedUrl).toHaveBeenCalledWith(filePath, 3600);
801
+ expect(result.ok).toBe(true);
802
+ expect(result.data.url).toBe('https://example.com/signed/test.pdf?token=abc123');
803
+ }, TEST_TIMEOUT);
559
804
 
560
805
  it('generates signed URL with custom expiration', async () => {
561
806
  const filePath = 'org-123/documents/test.pdf';
@@ -564,20 +809,17 @@ describe('[utility] Storage Helpers', () => {
564
809
  orgId: 'org-123',
565
810
  expiresIn: 7200
566
811
  };
812
+ const mockClient = createMockStorageClient() as any;
567
813
 
568
- const mockCreateSignedUrl = vi.fn().mockResolvedValue({
814
+ (mockClient.storage.from().createSignedUrl as any).mockResolvedValue({
569
815
  data: { signedUrl: 'https://example.com/signed/test.pdf?token=def456' },
570
816
  error: null
571
817
  });
572
818
 
573
- mockSupabase.storage = {
574
- from: vi.fn(() => ({ createSignedUrl: mockCreateSignedUrl }))
575
- };
576
-
577
- await getSignedUrl(mockSupabase, filePath, options);
819
+ await getSignedUrl(mockClient as SupabaseClient<Database>, filePath, options);
578
820
 
579
- expect(mockCreateSignedUrl).toHaveBeenCalledWith(filePath, 7200);
580
- });
821
+ expect(mockClient.storage.from().createSignedUrl).toHaveBeenCalledWith(filePath, 7200);
822
+ }, TEST_TIMEOUT);
581
823
 
582
824
  it('returns null when signed URL generation fails', async () => {
583
825
  const filePath = 'org-123/documents/test.pdf';
@@ -585,36 +827,17 @@ describe('[utility] Storage Helpers', () => {
585
827
  appName: 'test-app',
586
828
  orgId: 'org-123'
587
829
  };
830
+ const mockClient = createMockStorageClient() as any;
588
831
 
589
- const mockCreateSignedUrl = vi.fn().mockResolvedValue({
832
+ (mockClient.storage.from().createSignedUrl as any).mockResolvedValue({
590
833
  data: null,
591
834
  error: { message: 'Permission denied' }
592
835
  });
593
836
 
594
- mockSupabase.storage = {
595
- from: vi.fn(() => ({ createSignedUrl: mockCreateSignedUrl }))
596
- };
597
-
598
- const result = await getSignedUrl(mockSupabase, filePath, options);
599
-
600
- expect(result).toBe(null);
601
- });
602
-
603
- it('validates required parameters', async () => {
604
- const options = { appName: 'test-app', orgId: 'org-123' };
605
-
606
- const gsNoClient = await getSignedUrl(null as any, 'path', options);
607
- expect(gsNoClient).toBe(null);
608
-
609
- const gsNoPath = await getSignedUrl(mockSupabase, '', options);
610
- expect(gsNoPath).toBe(null);
611
-
612
- const gsNoOpts = await getSignedUrl(mockSupabase, 'path', {} as any);
613
- expect(gsNoOpts).toBe(null);
614
-
615
- const gsNoOrg = await getSignedUrl(mockSupabase, 'path', { appName: 'test' } as any);
616
- expect(gsNoOrg).toBe(null);
617
- });
837
+ const result = await getSignedUrl(mockClient as SupabaseClient<Database>, filePath, options);
838
+ expect(result.ok).toBe(false);
839
+ expect(result.error).toBeDefined();
840
+ }, TEST_TIMEOUT);
618
841
 
619
842
  it('includes expiration metadata in result', async () => {
620
843
  const filePath = 'org-123/documents/test.pdf';
@@ -623,179 +846,269 @@ describe('[utility] Storage Helpers', () => {
623
846
  orgId: 'org-123',
624
847
  expiresIn: 3600
625
848
  };
849
+ const mockClient = createMockStorageClient() as any;
626
850
 
627
- const mockCreateSignedUrl = vi.fn().mockResolvedValue({
851
+ (mockClient.storage.from().createSignedUrl as any).mockResolvedValue({
628
852
  data: { signedUrl: 'https://example.com/signed/test.pdf?token=abc123' },
629
853
  error: null
630
854
  });
631
855
 
632
- mockSupabase.storage = {
633
- from: vi.fn(() => ({ createSignedUrl: mockCreateSignedUrl }))
634
- };
635
-
636
- const result = await getSignedUrl(mockSupabase, filePath, options);
637
-
638
- expect(result?.url).toContain('signed');
639
- expect(typeof result?.expiresAt).toBe('string');
640
- });
856
+ const result = await getSignedUrl(mockClient as SupabaseClient<Database>, filePath, options);
857
+ expect(result.ok).toBe(true);
858
+ expect(result.data.url).toContain('signed');
859
+ expect(typeof result.data.expiresAt).toBe('string');
860
+ expect(new Date(result.data.expiresAt).getTime()).toBeGreaterThan(Date.now());
861
+ }, TEST_TIMEOUT);
641
862
  });
642
863
 
643
- describe('Error Handling', () => {
644
- it('handles storage client errors gracefully', async () => {
645
- const testFile = createTestFile();
864
+ describe('listFiles', () => {
865
+ it('lists files successfully with orgId', async () => {
866
+ const mockClient = createMockStorageClient() as any;
646
867
  const options = {
647
- orgId: 'test-org-123',
648
- category: FileCategory.GENERAL_DOCUMENTS,
868
+ appName: 'test-app',
869
+ orgId: 'org-123',
649
870
  isPublic: false
650
871
  };
651
872
 
652
- mockSupabase.storage = {
653
- from: vi.fn(() => {
654
- throw new Error('Storage client error');
655
- })
656
- };
873
+ (mockClient.storage.from().list as any).mockResolvedValue({
874
+ data: [
875
+ {
876
+ name: 'file1.jpg',
877
+ metadata: { size: 1024, mimetype: 'image/jpeg' },
878
+ created_at: '2023-01-01T00:00:00Z',
879
+ updated_at: '2023-01-01T00:00:00Z'
880
+ }
881
+ ],
882
+ error: null
883
+ });
657
884
 
658
- const res = await uploadFile(mockSupabase, testFile, options);
659
- expect(res.success).toBe(false);
660
- });
885
+ const result = await listFiles(mockClient as SupabaseClient<Database>, options);
886
+ expect(result.ok).toBe(true);
887
+ expect(result.data.files).toHaveLength(1);
888
+ expect(result.data.files[0].name).toBe('file1.jpg');
889
+ expect(result.data.totalCount).toBe(1);
890
+ }, TEST_TIMEOUT);
661
891
 
662
- it('handles network timeouts gracefully', async () => {
663
- const filePath = 'org-123/documents/test.pdf';
892
+ it('lists files with userId', async () => {
893
+ const mockClient = createMockStorageClient() as any;
894
+ const options = {
895
+ appName: 'test-app',
896
+ userId: 'user-123',
897
+ isPublic: false
898
+ };
664
899
 
665
- const mockDownload = vi.fn().mockRejectedValue(new Error('Network timeout'));
900
+ (mockClient.storage.from().list as any).mockResolvedValue({
901
+ data: [],
902
+ error: null
903
+ });
666
904
 
667
- mockSupabase.storage = {
668
- from: vi.fn(() => ({ download: mockDownload }))
905
+ const result = await listFiles(mockClient as SupabaseClient<Database>, options);
906
+ expect(result.ok).toBe(true);
907
+ expect(result.data.files).toEqual([]);
908
+ expect(mockClient.storage.from().list).toHaveBeenCalledWith(
909
+ 'users/user-123/',
910
+ expect.any(Object)
911
+ );
912
+ }, TEST_TIMEOUT);
913
+
914
+ it('handles missing orgId and userId gracefully', async () => {
915
+ const mockClient = createMockStorageClient() as any;
916
+ const options = {
917
+ appName: 'test-app',
918
+ isPublic: false
669
919
  };
670
920
 
671
- const dl = await downloadFile(mockSupabase, filePath, false);
672
- expect(dl).toBe(null);
673
- });
674
- });
921
+ // listFiles returns err when neither orgId nor userId is provided
922
+ const result = await listFiles(mockClient as SupabaseClient<Database>, options as any);
923
+ expect(result.ok).toBe(false);
924
+ expect(result.error.code).toBe('LIST_FILES_INVALID_OPTIONS');
925
+ }, TEST_TIMEOUT);
675
926
 
676
- describe('Integration Scenarios', () => {
677
- it('handles complete upload-download cycle', async () => {
678
- const testFile = createTestFile('integration-test.pdf', 'application/pdf', 2048);
679
- const uploadOptions = {
680
- orgId: 'test-org-123',
681
- category: FileCategory.GENERAL_DOCUMENTS,
927
+ it('handles list errors gracefully', async () => {
928
+ const mockClient = createMockStorageClient() as any;
929
+ const options = {
930
+ appName: 'test-app',
931
+ orgId: 'org-123',
682
932
  isPublic: false
683
933
  };
684
934
 
685
- // Mock upload
686
- const mockUpload = vi.fn().mockResolvedValue({
687
- data: { path: 'org-123/documents/integration-test.pdf' },
688
- error: null
689
- });
690
-
691
- // Mock download
692
- const mockDownload = vi.fn().mockResolvedValue({
693
- data: new Blob(['file content'], { type: 'application/pdf' }),
694
- error: null
935
+ (mockClient.storage.from().list as any).mockResolvedValue({
936
+ data: null,
937
+ error: { message: 'List failed' }
695
938
  });
696
939
 
697
- const mockList = vi.fn().mockResolvedValue({ data: [{ metadata: { size: 2048, mimetype: 'application/pdf' } }], error: null });
698
- mockSupabase.storage = {
699
- from: vi.fn(() => ({
700
- upload: mockUpload,
701
- download: mockDownload,
702
- list: mockList
703
- }))
940
+ const result = await listFiles(mockClient as SupabaseClient<Database>, options);
941
+ expect(result.ok).toBe(false);
942
+ expect(result.error.message).toBe('List failed');
943
+ }, TEST_TIMEOUT);
944
+ });
945
+
946
+ describe('archiveFile', () => {
947
+ it('archives file successfully with orgId', async () => {
948
+ const mockClient = createMockStorageClient() as any;
949
+ const filePath = 'org-123/documents/test.pdf';
950
+ const options = {
951
+ appName: 'test-app',
952
+ orgId: 'org-123',
953
+ isPublic: false
704
954
  };
705
955
 
706
- // Upload file
707
- const uploadResult = await uploadFile(mockSupabase, testFile, uploadOptions);
708
- expect(uploadResult.success).toBe(true);
956
+ (mockClient.storage.from().copy as any).mockResolvedValue({ error: null });
957
+ (mockClient.storage.from().remove as any).mockResolvedValue({ error: null });
709
958
 
710
- // Download file
711
- const downloadResult = await downloadFile(mockSupabase, uploadResult.path!, false);
712
- expect(downloadResult?.blob).toBeInstanceOf(Blob);
713
- expect(downloadResult?.metadata.type).toBe('application/pdf');
714
- });
959
+ const result = await archiveFile(mockClient as SupabaseClient<Database>, filePath, options);
960
+ expect(result.ok).toBe(true);
961
+ expect(mockClient.storage.from().copy).toHaveBeenCalledWith(
962
+ filePath,
963
+ 'archived/org-123/documents/test.pdf'
964
+ );
965
+ }, TEST_TIMEOUT);
715
966
 
716
- it('handles public vs private file workflows', { timeout: 10000 }, async () => {
717
- const publicFile = createTestFile('public.jpg', 'image/jpeg');
718
- const privateFile = createTestFile('private.pdf', 'application/pdf');
967
+ it('archives file successfully with userId', async () => {
968
+ const mockClient = createMockStorageClient() as any;
969
+ const filePath = 'users/user-123/documents/test.pdf';
970
+ const options = {
971
+ appName: 'test-app',
972
+ userId: 'user-123',
973
+ isPublic: false
974
+ };
719
975
 
720
- // Mock Image and URL for extractFileMetadata
721
- const mockImage = {
722
- width: 800,
723
- height: 600,
724
- onload: null as any,
725
- onerror: null as any,
726
- _src: '',
727
- get src() {
728
- return this._src;
729
- },
730
- set src(value: string) {
731
- this._src = value;
732
- // Trigger onload asynchronously when src is set
733
- setTimeout(() => {
734
- if (this.onload) {
735
- this.onload();
736
- }
737
- }, 0);
738
- }
976
+ (mockClient.storage.from().copy as any).mockResolvedValue({ error: null });
977
+ (mockClient.storage.from().remove as any).mockResolvedValue({ error: null });
978
+
979
+ const result = await archiveFile(mockClient as SupabaseClient<Database>, filePath, options);
980
+ expect(result.ok).toBe(true);
981
+ expect(mockClient.storage.from().copy).toHaveBeenCalledWith(
982
+ filePath,
983
+ 'archived/users/user-123/documents/test.pdf'
984
+ );
985
+ }, TEST_TIMEOUT);
986
+
987
+ it('returns error when neither orgId nor userId provided', async () => {
988
+ const mockClient = createMockStorageClient() as any;
989
+ const filePath = 'test.pdf';
990
+ const options = {
991
+ appName: 'test-app',
992
+ isPublic: false
739
993
  };
740
994
 
741
- const ImageConstructor = vi.fn(() => mockImage);
995
+ const result = await archiveFile(mockClient as SupabaseClient<Database>, filePath, options as any);
996
+ expect(result.ok).toBe(false);
997
+ expect(result.error.message).toContain('Either orgId or userId is required');
998
+ }, TEST_TIMEOUT);
999
+
1000
+ it('handles archive errors gracefully', async () => {
1001
+ const mockClient = createMockStorageClient() as any;
1002
+ const filePath = 'org-123/documents/test.pdf';
1003
+ const options = {
1004
+ appName: 'test-app',
1005
+ orgId: 'org-123',
1006
+ isPublic: false
1007
+ };
742
1008
 
743
- vi.stubGlobal('Image', ImageConstructor);
744
- vi.stubGlobal('URL', {
745
- createObjectURL: vi.fn(() => 'blob:mock-url'),
746
- revokeObjectURL: vi.fn()
1009
+ (mockClient.storage.from().copy as any).mockResolvedValue({
1010
+ error: { message: 'Copy failed' }
747
1011
  });
748
1012
 
749
- const mockUpload = vi.fn().mockResolvedValue({
750
- data: { path: 'test-path' },
751
- error: null
752
- });
1013
+ const result = await archiveFile(mockClient as SupabaseClient<Database>, filePath, options);
1014
+ expect(result.ok).toBe(false);
1015
+ expect(result.error.message).toContain('Copy failed');
1016
+ }, TEST_TIMEOUT);
1017
+ });
1018
+
1019
+ describe('generateFileUrlsBatch', () => {
1020
+ it('generates URLs for multiple public files', async () => {
1021
+ const mockClient = createMockStorageClient() as any;
1022
+ const fileReferences = [
1023
+ { id: 'file-1', file_path: 'org-123/logo1.png', is_public: true },
1024
+ { id: 'file-2', file_path: 'org-123/logo2.png', is_public: true }
1025
+ ];
1026
+ const options = {
1027
+ appName: 'test-app',
1028
+ orgId: 'org-123',
1029
+ expiresIn: 3600
1030
+ };
753
1031
 
754
- const mockGetPublicUrl = vi.fn().mockReturnValue({
755
- data: { publicUrl: 'https://example.com/public.jpg' }
1032
+ (mockClient.storage.from().getPublicUrl as any).mockReturnValue({
1033
+ data: { publicUrl: 'https://example.com/file.png' }
756
1034
  });
757
1035
 
758
- const mockCreateSignedUrl = vi.fn().mockResolvedValue({
759
- data: { signedUrl: 'https://example.com/signed/private.pdf?token=abc' },
1036
+ const result = await generateFileUrlsBatch(
1037
+ mockClient as SupabaseClient<Database>,
1038
+ fileReferences,
1039
+ options
1040
+ );
1041
+ expect(result.ok).toBe(true);
1042
+ expect(result.data.size).toBe(2);
1043
+ expect(result.data.get('file-1')).toBeDefined();
1044
+ expect(result.data.get('file-2')).toBeDefined();
1045
+ }, TEST_TIMEOUT);
1046
+
1047
+ it('generates signed URLs for private files', async () => {
1048
+ const mockClient = createMockStorageClient() as any;
1049
+ const fileReferences = [
1050
+ { id: 'file-1', file_path: 'org-123/doc1.pdf', is_public: false },
1051
+ { id: 'file-2', file_path: 'org-123/doc2.pdf', is_public: false }
1052
+ ];
1053
+ const options = {
1054
+ appName: 'test-app',
1055
+ orgId: 'org-123',
1056
+ expiresIn: 3600
1057
+ };
1058
+
1059
+ (mockClient.storage.from().createSignedUrl as any).mockResolvedValue({
1060
+ data: { signedUrl: 'https://example.com/signed/file.pdf?token=abc' },
760
1061
  error: null
761
1062
  });
762
1063
 
763
- mockSupabase.storage = {
764
- from: vi.fn(() => ({
765
- upload: mockUpload,
766
- getPublicUrl: mockGetPublicUrl,
767
- createSignedUrl: mockCreateSignedUrl,
768
- list: vi.fn().mockResolvedValue({
769
- data: [],
770
- error: null
771
- })
772
- }))
1064
+ const result = await generateFileUrlsBatch(
1065
+ mockClient as SupabaseClient<Database>,
1066
+ fileReferences,
1067
+ options
1068
+ );
1069
+ expect(result.ok).toBe(true);
1070
+ expect(result.data.size).toBe(2);
1071
+ expect(result.data.get('file-1')).toContain('signed');
1072
+ }, TEST_TIMEOUT);
1073
+
1074
+ it('handles mixed public and private files', async () => {
1075
+ const mockClient = createMockStorageClient() as any;
1076
+ const fileReferences = [
1077
+ { id: 'file-1', file_path: 'org-123/logo.png', is_public: true },
1078
+ { id: 'file-2', file_path: 'org-123/doc.pdf', is_public: false }
1079
+ ];
1080
+ const options = {
1081
+ appName: 'test-app',
1082
+ orgId: 'org-123',
1083
+ expiresIn: 3600
773
1084
  };
774
1085
 
775
- // Upload public file
776
- await uploadFile(mockSupabase, publicFile, {
777
- orgId: 'org-123',
778
- category: FileCategory.IMAGES,
779
- isPublic: true
1086
+ (mockClient.storage.from().getPublicUrl as any).mockReturnValue({
1087
+ data: { publicUrl: 'https://example.com/public/logo.png' }
780
1088
  });
781
-
782
- // Upload private file
783
- await uploadFile(mockSupabase, privateFile, {
784
- orgId: 'org-123',
785
- category: FileCategory.GENERAL_DOCUMENTS,
786
- isPublic: false
1089
+ (mockClient.storage.from().createSignedUrl as any).mockResolvedValue({
1090
+ data: { signedUrl: 'https://example.com/signed/doc.pdf?token=abc' },
1091
+ error: null
787
1092
  });
788
1093
 
789
- // Get public URL
790
- const publicUrl = getPublicUrl(mockSupabase, 'org-123/images/public.jpg', true);
791
- expect(publicUrl).toBe('https://example.com/public.jpg');
792
-
793
- // Get signed URL
794
- const signedResult = await getSignedUrl(mockSupabase, 'org-123/documents/private.pdf', {
795
- appName: 'test-app',
796
- orgId: 'org-123'
797
- });
798
- expect(signedResult?.url).toBe('https://example.com/signed/private.pdf?token=abc');
799
- });
1094
+ const result = await generateFileUrlsBatch(
1095
+ mockClient as SupabaseClient<Database>,
1096
+ fileReferences,
1097
+ options
1098
+ );
1099
+ expect(result.ok).toBe(true);
1100
+ expect(result.data.size).toBe(2);
1101
+ }, TEST_TIMEOUT);
1102
+
1103
+ it('returns empty map for empty file references', async () => {
1104
+ const mockClient = createMockStorageClient() as any;
1105
+ const result = await generateFileUrlsBatch(
1106
+ mockClient as SupabaseClient<Database>,
1107
+ [],
1108
+ { appName: 'test-app', orgId: 'org-123' }
1109
+ );
1110
+ expect(result.ok).toBe(true);
1111
+ expect(result.data.size).toBe(0);
1112
+ }, TEST_TIMEOUT);
800
1113
  });
801
1114
  });