@jmruthers/pace-core 0.6.10 → 0.6.12

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 (1500) 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 +13 -3
  5. package/audit-tool/audits/03-architecture.cjs +78 -4
  6. package/audit-tool/audits/04-code-quality.cjs +9 -2
  7. package/audit-tool/audits/05-styling.cjs +19 -7
  8. package/audit-tool/audits/06-security-rbac.cjs +105 -14
  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 +1 -0
  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 +41 -7
  19. package/cursor-rules/06-security-rbac.mdc +2 -1
  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-AQAHSFLM.js +17 -0
  24. package/dist/InactivityServiceProvider-BbxwwDz1.d.ts +308 -0
  25. package/dist/UnifiedAuthProvider-Bkt_tzdS.d.ts +183 -0
  26. package/dist/api-6OQXYT67.js +6 -0
  27. package/dist/api-result-USV1Czr-.d.ts +51 -0
  28. package/dist/audit-HI2DHUVU.js +4 -0
  29. package/dist/auth-JvdRVaud.d.ts +49 -0
  30. package/dist/chunk-2DL2WSOE.js +327 -0
  31. package/dist/chunk-2GBDPPUC.js +61 -0
  32. package/dist/chunk-44CNXN4P.js +15 -0
  33. package/dist/chunk-AP5FG7W4.js +2159 -0
  34. package/dist/chunk-CU2BU2MQ.js +2 -0
  35. package/dist/chunk-D6BMFMQZ.js +200 -0
  36. package/dist/chunk-ENLXB7GP.js +721 -0
  37. package/dist/chunk-GHCUP64P.js +105 -0
  38. package/dist/chunk-H6RTU4DZ.js +1098 -0
  39. package/dist/chunk-HQTYP6BX.js +6468 -0
  40. package/dist/chunk-M7QE7XOA.js +529 -0
  41. package/dist/chunk-MVVWZ7JV.js +1329 -0
  42. package/dist/chunk-NJ7FGQWB.js +811 -0
  43. package/dist/chunk-QRYSEPHB.js +429 -0
  44. package/dist/chunk-QWIG36BZ.js +264 -0
  45. package/dist/chunk-S57OLCLO.js +2946 -0
  46. package/dist/chunk-VFLR5K2H.js +23 -0
  47. package/dist/chunk-XOJME5T7.js +407 -0
  48. package/dist/chunk-XPFVT3GN.js +492 -0
  49. package/dist/chunk-Y2LWSLLB.js +9614 -0
  50. package/dist/chunk-YFGNMB67.js +2390 -0
  51. package/dist/components.d.ts +10 -9
  52. package/dist/components.js +20 -17
  53. package/dist/database.generated-qkdoiVrJ.d.ts +9441 -0
  54. package/dist/eslint-rules/index.cjs +3 -0
  55. package/dist/eslint-rules/rules/03-architecture.cjs +74 -0
  56. package/dist/eslint-rules/rules/06-security-rbac.cjs +74 -0
  57. package/dist/event-BfCox3N2.d.ts +265 -0
  58. package/dist/file-reference-DU1hcawx.d.ts +164 -0
  59. package/dist/functions-hF5ImHCr.d.ts +208 -0
  60. package/dist/hooks.d.ts +22 -9
  61. package/dist/hooks.js +35 -25
  62. package/dist/icons/index.d.ts +1 -0
  63. package/dist/icons/index.js +1 -0
  64. package/dist/index.d.ts +69 -180
  65. package/dist/index.js +319 -342
  66. package/dist/pagination-BW1mqywp.d.ts +201 -0
  67. package/dist/providers.d.ts +6 -5
  68. package/dist/providers.js +6 -3
  69. package/dist/rbac/index.d.ts +133 -148
  70. package/dist/rbac/index.js +12 -9
  71. package/dist/theming/runtime.d.ts +19 -2
  72. package/dist/theming/runtime.js +1 -1
  73. package/dist/timezone-BTWWXKVY.d.ts +696 -0
  74. package/dist/types-Besvoyzb.d.ts +55 -0
  75. package/dist/types-CGHrxfqc.d.ts +114 -0
  76. package/dist/types.d.ts +19 -12
  77. package/dist/types.js +1 -0
  78. package/dist/usePublicPageContext-BQrHf95t.d.ts +4367 -0
  79. package/dist/usePublicRouteParams-BgV6VhMi.d.ts +946 -0
  80. package/dist/utils.d.ts +163 -145
  81. package/dist/utils.js +45 -27
  82. package/docs/api/@jmruthers/namespaces/DialogPortal/README.md +14 -0
  83. package/docs/api/@jmruthers/namespaces/DialogPortal/variables/displayName.md +11 -0
  84. package/docs/api/README.md +6 -2
  85. package/docs/api/_media/README.md +186 -0
  86. package/docs/api/classes/ColumnFactory.md +225 -0
  87. package/docs/api/classes/Logger.md +246 -0
  88. package/docs/api/classes/RBACAuditManager.md +299 -0
  89. package/docs/api/classes/RBACCache.md +389 -0
  90. package/docs/api/classes/RBACEngine.md +181 -0
  91. package/docs/api/classes/SecureSupabaseClient.md +168 -0
  92. package/docs/api/classes/StorageUtils.md +324 -0
  93. package/docs/api/enumerations/FileCategory.md +137 -0
  94. package/docs/api/enumerations/LogLevel.md +43 -0
  95. package/docs/api/enumerations/RBACErrorCode.md +169 -0
  96. package/docs/api/enumerations/RPCFunction.md +89 -0
  97. package/docs/api/functions/AccessDenied.md +30 -0
  98. package/docs/api/functions/AppSwitcher.md +21 -0
  99. package/docs/api/functions/Badge.md +42 -0
  100. package/docs/api/functions/ContextSelector.md +43 -0
  101. package/docs/api/functions/DataTable.md +36 -0
  102. package/docs/api/functions/DatePickerWithTimezone.md +28 -0
  103. package/docs/api/functions/DialogBody.md +24 -0
  104. package/docs/api/functions/DialogFooter.md +24 -0
  105. package/docs/api/functions/DialogHeader.md +24 -0
  106. package/docs/api/functions/ErrorBoundaryProvider.md +28 -0
  107. package/docs/api/functions/EventServiceProvider.md +28 -0
  108. package/docs/api/functions/FileDisplay.md +25 -0
  109. package/docs/api/functions/FileUpload.md +21 -0
  110. package/docs/api/functions/Form.md +50 -0
  111. package/docs/api/functions/FormField.md +102 -0
  112. package/docs/api/functions/Header.md +21 -0
  113. package/docs/api/functions/InactivityServiceProvider.md +28 -0
  114. package/docs/api/functions/InactivityWarningModal.md +21 -0
  115. package/docs/api/functions/Input.md +49 -0
  116. package/docs/api/functions/NavigationGuard.md +31 -0
  117. package/docs/api/functions/OrganisationServiceProvider.md +28 -0
  118. package/docs/api/functions/PaceAppLayout.md +169 -0
  119. package/docs/api/functions/PasswordChangeForm.md +21 -0
  120. package/docs/api/functions/ProtectedRoute.md +37 -0
  121. package/docs/api/functions/PublicPageFooter.md +21 -0
  122. package/docs/api/functions/PublicPageHeader.md +21 -0
  123. package/docs/api/functions/PublicPageLayout.md +33 -0
  124. package/docs/api/functions/PublicPageProvider.md +30 -0
  125. package/docs/api/functions/Textarea.md +49 -0
  126. package/docs/api/functions/Toaster.md +34 -0
  127. package/docs/api/functions/UnifiedAuthProvider.md +28 -0
  128. package/docs/api/functions/applyPalette.md +33 -0
  129. package/docs/api/functions/archiveFile.md +47 -0
  130. package/docs/api/functions/average.md +63 -0
  131. package/docs/api/functions/buildAppUrl.md +46 -0
  132. package/docs/api/functions/clearInFlightRequests.md +17 -0
  133. package/docs/api/functions/clearPalette.md +18 -0
  134. package/docs/api/functions/clearPublicEventCache.md +18 -0
  135. package/docs/api/functions/clearPublicFileDisplayCache.md +18 -0
  136. package/docs/api/functions/clearPublicLogoCache.md +18 -0
  137. package/docs/api/functions/cn.md +21 -0
  138. package/docs/api/functions/count.md +56 -0
  139. package/docs/api/functions/createAuditManager.md +45 -0
  140. package/docs/api/functions/createBaseClient.md +75 -0
  141. package/docs/api/functions/createLogger.md +95 -0
  142. package/docs/api/functions/createRBACConfig.md +21 -0
  143. package/docs/api/functions/createRBACEngine.md +33 -0
  144. package/docs/api/functions/createRBACExpressMiddleware.md +84 -0
  145. package/docs/api/functions/createRBACMiddleware.md +88 -0
  146. package/docs/api/functions/createSecureClient.md +80 -0
  147. package/docs/api/functions/createSecureDataAccess.md +39 -0
  148. package/docs/api/functions/deleteFile.md +33 -0
  149. package/docs/api/functions/disablePerformanceMonitoring.md +17 -0
  150. package/docs/api/functions/downloadFile.md +33 -0
  151. package/docs/api/functions/emitAuditEvent.md +27 -0
  152. package/docs/api/functions/enablePerformanceMonitoring.md +17 -0
  153. package/docs/api/functions/err.md +23 -0
  154. package/docs/api/functions/exportToCSV.md +56 -0
  155. package/docs/api/functions/exportToCSVWithTableRows.md +46 -0
  156. package/docs/api/functions/extractEventCodeFromPath.md +24 -0
  157. package/docs/api/functions/extractFileMetadata.md +33 -0
  158. package/docs/api/functions/formatCompactNumber.md +27 -0
  159. package/docs/api/functions/formatCurrency.md +31 -0
  160. package/docs/api/functions/formatDate.md +23 -0
  161. package/docs/api/functions/formatDateTime.md +24 -0
  162. package/docs/api/functions/formatFileSize.md +23 -0
  163. package/docs/api/functions/formatInTimeZone.md +46 -0
  164. package/docs/api/functions/formatNumber.md +31 -0
  165. package/docs/api/functions/formatPercent.md +64 -0
  166. package/docs/api/functions/formatTime.md +24 -0
  167. package/docs/api/functions/formatTimeInTimeZone.md +40 -0
  168. package/docs/api/functions/fromSupabaseClient.md +49 -0
  169. package/docs/api/functions/fromZonedTime.md +41 -0
  170. package/docs/api/functions/generateCSVContent.md +55 -0
  171. package/docs/api/functions/generateFilePath.md +29 -0
  172. package/docs/api/functions/generateFileUrlsBatch.md +33 -0
  173. package/docs/api/functions/generatePublicRoutePath.md +27 -0
  174. package/docs/api/functions/generateUniqueFileName.md +24 -0
  175. package/docs/api/functions/getAccessLevel.md +48 -0
  176. package/docs/api/functions/getAllAppPorts.md +19 -0
  177. package/docs/api/functions/getAllStylePaths.md +15 -0
  178. package/docs/api/functions/getAppConfig.md +17 -0
  179. package/docs/api/functions/getAppPort.md +34 -0
  180. package/docs/api/functions/getBucketName.md +27 -0
  181. package/docs/api/functions/getCurrentAppId.md +17 -0
  182. package/docs/api/functions/getCurrentAppName.md +17 -0
  183. package/docs/api/functions/getFileSizeLimit.md +23 -0
  184. package/docs/api/functions/getGlobalAuditManager.md +19 -0
  185. package/docs/api/functions/getInFlightRequestCount.md +19 -0
  186. package/docs/api/functions/getPerformanceMetrics.md +17 -0
  187. package/docs/api/functions/getPerformanceSummary.md +17 -0
  188. package/docs/api/functions/getPermissionMap.md +52 -0
  189. package/docs/api/functions/getPublicEventCacheStats.md +25 -0
  190. package/docs/api/functions/getPublicFileDisplayCacheStats.md +25 -0
  191. package/docs/api/functions/getPublicLogoCacheStats.md +25 -0
  192. package/docs/api/functions/getPublicUrl.md +39 -0
  193. package/docs/api/functions/getRBACConfig.md +15 -0
  194. package/docs/api/functions/getRBACLogger.md +15 -0
  195. package/docs/api/functions/getRoleContext.md +31 -0
  196. package/docs/api/functions/getSignedUrl.md +34 -0
  197. package/docs/api/functions/getStylePath.md +21 -0
  198. package/docs/api/functions/getTimeZoneDifference.md +40 -0
  199. package/docs/api/functions/getTimezoneAbbreviation.md +40 -0
  200. package/docs/api/functions/getUserTimeZone.md +26 -0
  201. package/docs/api/functions/hasAllPermissions.md +41 -0
  202. package/docs/api/functions/hasAnyPermission.md +41 -0
  203. package/docs/api/functions/isDebugMode.md +15 -0
  204. package/docs/api/functions/isDevelopmentMode.md +15 -0
  205. package/docs/api/functions/isErr.md +29 -0
  206. package/docs/api/functions/isOk.md +29 -0
  207. package/docs/api/functions/isPerformanceMonitoringEnabled.md +17 -0
  208. package/docs/api/functions/isPermitted.md +58 -0
  209. package/docs/api/functions/isPermittedCached.md +36 -0
  210. package/docs/api/functions/isRBACInitialized.md +19 -0
  211. package/docs/api/functions/isSecureClient.md +38 -0
  212. package/docs/api/functions/isValidPermission.md +27 -0
  213. package/docs/api/functions/listFiles.md +29 -0
  214. package/docs/api/functions/max.md +63 -0
  215. package/docs/api/functions/min.md +63 -0
  216. package/docs/api/functions/ok.md +29 -0
  217. package/docs/api/functions/parseAndNormalizeEventColours.md +105 -0
  218. package/docs/api/functions/recordAuditEvent.md +23 -0
  219. package/docs/api/functions/recordPermissionCheck.md +31 -0
  220. package/docs/api/functions/resetPerformanceMetrics.md +17 -0
  221. package/docs/api/functions/resolveAppContext.md +27 -0
  222. package/docs/api/functions/roundToNearestMinutes.md +41 -0
  223. package/docs/api/functions/sanitizeFormData.md +49 -0
  224. package/docs/api/functions/sanitizeHtml.md +39 -0
  225. package/docs/api/functions/sanitizeUserInput.md +27 -0
  226. package/docs/api/functions/setAppConfig.md +23 -0
  227. package/docs/api/functions/setGlobalAuditManager.md +25 -0
  228. package/docs/api/functions/setupRBAC.md +31 -0
  229. package/docs/api/functions/sum.md +63 -0
  230. package/docs/api/functions/toZonedTime.md +41 -0
  231. package/docs/api/functions/uploadFile.md +32 -0
  232. package/docs/api/functions/useAccessLevel.md +71 -0
  233. package/docs/api/functions/useAccessibleApps.md +55 -0
  234. package/docs/api/functions/useAppConfig.md +20 -0
  235. package/docs/api/functions/useAuthService.md +15 -0
  236. package/docs/api/functions/useCan.md +99 -0
  237. package/docs/api/functions/useEventService.md +15 -0
  238. package/docs/api/functions/useEventTheme.md +26 -0
  239. package/docs/api/functions/useEvents.md +45 -0
  240. package/docs/api/functions/useFileReference.md +264 -0
  241. package/docs/api/functions/useFileReferenceById.md +63 -0
  242. package/docs/api/functions/useFileReferenceForRecord.md +129 -0
  243. package/docs/api/functions/useFilesByCategory.md +80 -0
  244. package/docs/api/functions/useFormDialog.md +62 -0
  245. package/docs/api/functions/useInactivityService.md +15 -0
  246. package/docs/api/functions/useInactivityTracker.md +21 -0
  247. package/docs/api/functions/useIsPublicPage.md +19 -0
  248. package/docs/api/functions/useMultiplePermissions.md +88 -0
  249. package/docs/api/functions/useOptionalEvents.md +31 -0
  250. package/docs/api/functions/useOrganisationPermissions.md +27 -0
  251. package/docs/api/functions/useOrganisationSecurity.md +15 -0
  252. package/docs/api/functions/useOrganisationService.md +15 -0
  253. package/docs/api/functions/useOrganisations.md +48 -0
  254. package/docs/api/functions/usePermissions.md +130 -0
  255. package/docs/api/functions/usePublicEvent.md +36 -0
  256. package/docs/api/functions/usePublicEventCode.md +32 -0
  257. package/docs/api/functions/usePublicEventLogo.md +48 -0
  258. package/docs/api/functions/usePublicFileDisplay.md +54 -0
  259. package/docs/api/functions/usePublicPageContext.md +19 -0
  260. package/docs/api/functions/usePublicRouteParams.md +31 -0
  261. package/docs/api/functions/useRBAC.md +21 -0
  262. package/docs/api/functions/useResolvedScope.md +46 -0
  263. package/docs/api/functions/useResourcePermissions.md +25 -0
  264. package/docs/api/functions/useRoleManagement.md +121 -0
  265. package/docs/api/functions/useSecureSupabase.md +51 -0
  266. package/docs/api/functions/useSessionRestoration.md +15 -0
  267. package/docs/api/functions/useSessionTracking.md +62 -0
  268. package/docs/api/functions/useToast.md +83 -0
  269. package/docs/api/functions/useUnifiedAuth.md +24 -0
  270. package/docs/api/functions/useZodForm.md +27 -0
  271. package/docs/api/functions/validateFileSize.md +31 -0
  272. package/docs/api/functions/warnIfInsecureClient.md +40 -0
  273. package/docs/api/functions/withAccessLevelGuard.md +67 -0
  274. package/docs/api/functions/withPermissionGuard.md +73 -0
  275. package/docs/api/functions/withRoleGuard.md +86 -0
  276. package/docs/api/globals.md +502 -0
  277. package/docs/api/interfaces/AccessDeniedProps.md +87 -0
  278. package/docs/api/interfaces/AccessibleApp.md +41 -0
  279. package/docs/api/interfaces/AddressFieldProps.md +195 -0
  280. package/docs/api/interfaces/AddressFieldRef.md +67 -0
  281. package/docs/api/interfaces/AggregateConfig.md +35 -0
  282. package/docs/api/interfaces/AppSwitcherProps.md +51 -0
  283. package/docs/api/interfaces/AuthSessionData.md +27 -0
  284. package/docs/api/interfaces/AutocompleteOptions.md +61 -0
  285. package/docs/api/interfaces/AvatarProps.md +97 -0
  286. package/docs/api/interfaces/BadgeProps.md +30 -0
  287. package/docs/api/interfaces/BuildAppUrlOptions.md +41 -0
  288. package/docs/api/interfaces/ButtonProps.md +46 -0
  289. package/docs/api/interfaces/CalendarProps.md +60 -0
  290. package/docs/api/interfaces/CardProps.md +56 -0
  291. package/docs/api/interfaces/ColorPalette.md +13 -0
  292. package/docs/api/interfaces/ColorShade.md +58 -0
  293. package/docs/api/interfaces/ContextSelectorProps.md +131 -0
  294. package/docs/api/interfaces/DataRecord.md +16 -0
  295. package/docs/api/interfaces/DataTableAction.md +198 -0
  296. package/docs/api/interfaces/DataTableColumn.md +422 -0
  297. package/docs/api/interfaces/DataTableProps.md +511 -0
  298. package/docs/api/interfaces/DataTableToolbarButton.md +75 -0
  299. package/docs/api/interfaces/DatePickerWithTimezoneProps.md +75 -0
  300. package/docs/api/interfaces/DialogBodyProps.md +55 -0
  301. package/docs/api/interfaces/DialogCloseProps.md +25 -0
  302. package/docs/api/interfaces/DialogContentProps.md +160 -0
  303. package/docs/api/interfaces/DialogFooterProps.md +25 -0
  304. package/docs/api/interfaces/DialogHeaderProps.md +25 -0
  305. package/docs/api/interfaces/DialogPortalProps.md +19 -0
  306. package/docs/api/interfaces/DialogProps.md +53 -0
  307. package/docs/api/interfaces/DialogTriggerProps.md +53 -0
  308. package/docs/api/interfaces/EmptyStateConfig.md +55 -0
  309. package/docs/api/interfaces/ErrorBoundaryProps.md +131 -0
  310. package/docs/api/interfaces/ErrorBoundaryProviderProps.md +31 -0
  311. package/docs/api/interfaces/ErrorBoundaryState.md +61 -0
  312. package/docs/api/interfaces/EventAppRoleData.md +54 -0
  313. package/docs/api/interfaces/ExportColumn.md +69 -0
  314. package/docs/api/interfaces/ExportOptions.md +109 -0
  315. package/docs/api/interfaces/FileDisplayProps.md +192 -0
  316. package/docs/api/interfaces/FileMetadata.md +97 -0
  317. package/docs/api/interfaces/FileReference.md +89 -0
  318. package/docs/api/interfaces/FileSizeLimits.md +13 -0
  319. package/docs/api/interfaces/FileUploadOptions.md +107 -0
  320. package/docs/api/interfaces/FooterProps.md +37 -0
  321. package/docs/api/interfaces/FormFieldProps.md +171 -0
  322. package/docs/api/interfaces/FormProps.md +93 -0
  323. package/docs/api/interfaces/GrantEventAppRoleParams.md +97 -0
  324. package/docs/api/interfaces/ImportSummary.md +49 -0
  325. package/docs/api/interfaces/InactivityWarningModalProps.md +87 -0
  326. package/docs/api/interfaces/InputProps.md +46 -0
  327. package/docs/api/interfaces/InvalidScopeError.md +37 -0
  328. package/docs/api/interfaces/LabelProps.md +85 -0
  329. package/docs/api/interfaces/LoggerConfig.md +51 -0
  330. package/docs/api/interfaces/LoginFormProps.md +146 -0
  331. package/docs/api/interfaces/MissingUserContextError.md +37 -0
  332. package/docs/api/interfaces/NavigationGuardProps.md +109 -0
  333. package/docs/api/interfaces/NavigationItem.md +91 -0
  334. package/docs/api/interfaces/NavigationMenuProps.md +169 -0
  335. package/docs/api/interfaces/Organisation.md +105 -0
  336. package/docs/api/interfaces/OrganisationContextRequiredError.md +37 -0
  337. package/docs/api/interfaces/OrganisationMembership.md +105 -0
  338. package/docs/api/interfaces/OrganisationSecurityError.md +49 -0
  339. package/docs/api/interfaces/PaceAppLayoutPermissionConfig.md +127 -0
  340. package/docs/api/interfaces/PaceAppLayoutRouteConfigItem.md +91 -0
  341. package/docs/api/interfaces/PaceAppLayoutRoutingConfig.md +79 -0
  342. package/docs/api/interfaces/PaceLoginPageProps.md +41 -0
  343. package/docs/api/interfaces/PagePermissionGuardProps.md +143 -0
  344. package/docs/api/interfaces/PaletteData.md +33 -0
  345. package/docs/api/interfaces/ParsedAddress.md +91 -0
  346. package/docs/api/interfaces/PermissionDeniedError.md +37 -0
  347. package/docs/api/interfaces/ProgressProps.md +35 -0
  348. package/docs/api/interfaces/ProtectedRouteProps.md +67 -0
  349. package/docs/api/interfaces/PublicPageFooterProps.md +97 -0
  350. package/docs/api/interfaces/PublicPageHeaderProps.md +99 -0
  351. package/docs/api/interfaces/PublicPageLayoutProps.md +153 -0
  352. package/docs/api/interfaces/RBACAccessValidateParams.md +41 -0
  353. package/docs/api/interfaces/RBACAccessValidateResult.md +33 -0
  354. package/docs/api/interfaces/RBACAuditLogParams.md +65 -0
  355. package/docs/api/interfaces/RBACAuditLogResult.md +41 -0
  356. package/docs/api/interfaces/RBACContext.md +41 -0
  357. package/docs/api/interfaces/RBACError.md +37 -0
  358. package/docs/api/interfaces/RBACLogger.md +97 -0
  359. package/docs/api/interfaces/RBACNotInitializedError.md +37 -0
  360. package/docs/api/interfaces/RBACPageAccessCheckParams.md +57 -0
  361. package/docs/api/interfaces/RBACPerformanceMetrics.md +109 -0
  362. package/docs/api/interfaces/RBACPermissionCheckParams.md +57 -0
  363. package/docs/api/interfaces/RBACPermissionCheckResult.md +41 -0
  364. package/docs/api/interfaces/RBACPermissionsGetParams.md +49 -0
  365. package/docs/api/interfaces/RBACPermissionsGetResult.md +49 -0
  366. package/docs/api/interfaces/RBACResult.md +47 -0
  367. package/docs/api/interfaces/RBACRoleGrantParams.md +49 -0
  368. package/docs/api/interfaces/RBACRoleGrantResult.md +41 -0
  369. package/docs/api/interfaces/RBACRoleRevokeParams.md +49 -0
  370. package/docs/api/interfaces/RBACRoleRevokeResult.md +41 -0
  371. package/docs/api/interfaces/RBACRoleValidateParams.md +41 -0
  372. package/docs/api/interfaces/RBACRoleValidateResult.md +49 -0
  373. package/docs/api/interfaces/RBACRolesListParams.md +41 -0
  374. package/docs/api/interfaces/RBACRolesListResult.md +57 -0
  375. package/docs/api/interfaces/RBACSessionTrackParams.md +57 -0
  376. package/docs/api/interfaces/RBACSessionTrackResult.md +41 -0
  377. package/docs/api/interfaces/ResourcePermissions.md +119 -0
  378. package/docs/api/interfaces/RevokeEventAppRoleParams.md +81 -0
  379. package/docs/api/interfaces/RoleManagementResult.md +41 -0
  380. package/docs/api/interfaces/SessionRestorationLoaderProps.md +29 -0
  381. package/docs/api/interfaces/StorageConfig.md +33 -0
  382. package/docs/api/interfaces/StorageFileInfo.md +57 -0
  383. package/docs/api/interfaces/StorageFileMetadata.md +113 -0
  384. package/docs/api/interfaces/StorageListOptions.md +79 -0
  385. package/docs/api/interfaces/StorageListResult.md +33 -0
  386. package/docs/api/interfaces/StorageUploadOptions.md +81 -0
  387. package/docs/api/interfaces/StorageUploadResult.md +49 -0
  388. package/docs/api/interfaces/StorageUploadSuccess.md +35 -0
  389. package/docs/api/interfaces/StorageUrlOptions.md +49 -0
  390. package/docs/api/interfaces/StyleImport.md +17 -0
  391. package/docs/api/interfaces/SwitchProps.md +30 -0
  392. package/docs/api/interfaces/TabsContentProps.md +13 -0
  393. package/docs/api/interfaces/TabsListProps.md +13 -0
  394. package/docs/api/interfaces/TabsProps.md +13 -0
  395. package/docs/api/interfaces/TabsTriggerProps.md +41 -0
  396. package/docs/api/interfaces/TextareaProps.md +43 -0
  397. package/docs/api/interfaces/ToastActionElement.md +16 -0
  398. package/docs/api/interfaces/ToastProps.md +13 -0
  399. package/docs/api/interfaces/UnifiedAuthProviderProps.md +129 -0
  400. package/docs/api/interfaces/UseAccessibleAppsReturn.md +55 -0
  401. package/docs/api/interfaces/UseFormDialogOptions.md +49 -0
  402. package/docs/api/interfaces/UseFormDialogReturn.md +95 -0
  403. package/docs/api/interfaces/UseInactivityTrackerOptions.md +103 -0
  404. package/docs/api/interfaces/UseInactivityTrackerReturn.md +91 -0
  405. package/docs/api/interfaces/UsePublicEventLogoOptions.md +69 -0
  406. package/docs/api/interfaces/UsePublicEventLogoReturn.md +66 -0
  407. package/docs/api/interfaces/UsePublicEventOptions.md +29 -0
  408. package/docs/api/interfaces/UsePublicEventReturn.md +56 -0
  409. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +39 -0
  410. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +96 -0
  411. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +76 -0
  412. package/docs/api/interfaces/UseResolvedScopeOptions.md +49 -0
  413. package/docs/api/interfaces/UseResolvedScopeReturn.md +39 -0
  414. package/docs/api/interfaces/UseResourcePermissionsOptions.md +29 -0
  415. package/docs/api/interfaces/UserEventAccess.md +92 -0
  416. package/docs/api/interfaces/UserMenuProps.md +69 -0
  417. package/docs/api/interfaces/UserProfile.md +49 -0
  418. package/docs/api/type-aliases/AccessLevel.md +11 -0
  419. package/docs/api/type-aliases/AccessLevelContext.md +14 -0
  420. package/docs/api/type-aliases/AllPermissions.md +11 -0
  421. package/docs/api/type-aliases/ApiError.md +37 -0
  422. package/docs/api/type-aliases/ApiResult.md +19 -0
  423. package/docs/api/type-aliases/AuditEventType.md +11 -0
  424. package/docs/api/type-aliases/BadgeVariant.md +18 -0
  425. package/docs/api/type-aliases/DataTableFeatureConfig.md +14 -0
  426. package/docs/api/type-aliases/DialogSize.md +13 -0
  427. package/docs/api/type-aliases/EventAppRole.md +11 -0
  428. package/docs/api/type-aliases/FileUploadProps.md +15 -0
  429. package/docs/api/type-aliases/GetRowId.md +33 -0
  430. package/docs/api/type-aliases/GlobalErrorHandler.md +35 -0
  431. package/docs/api/type-aliases/GlobalRole.md +11 -0
  432. package/docs/api/type-aliases/ImportHandlerResult.md +13 -0
  433. package/docs/api/type-aliases/NavigationMode.md +13 -0
  434. package/docs/api/type-aliases/Operation.md +11 -0
  435. package/docs/api/type-aliases/OrganisationContextType.md +13 -0
  436. package/docs/api/type-aliases/OrganisationRole.md +11 -0
  437. package/docs/api/type-aliases/PaceAppLayoutProps.md +14 -0
  438. package/docs/api/type-aliases/Permission.md +11 -0
  439. package/docs/api/type-aliases/PermissionCheck.md +46 -0
  440. package/docs/api/type-aliases/PermissionMap.md +11 -0
  441. package/docs/api/type-aliases/PermissionSource.md +13 -0
  442. package/docs/api/type-aliases/RBACConfig.md +13 -0
  443. package/docs/api/type-aliases/RBACFunctionResponse.md +57 -0
  444. package/docs/api/type-aliases/Scope.md +41 -0
  445. package/docs/api/type-aliases/SessionType.md +11 -0
  446. package/docs/api/type-aliases/UUID.md +11 -0
  447. package/docs/api/type-aliases/UnifiedAuthContextType.md +13 -0
  448. package/docs/api/type-aliases/UseFileReferenceForRecordReturn.md +161 -0
  449. package/docs/api/type-aliases/UseFileReferenceOptions.md +35 -0
  450. package/docs/api/type-aliases/UseFileReferenceReturn.md +13 -0
  451. package/docs/api/variables/ALL_PERMISSIONS.md +281 -0
  452. package/docs/api/variables/APP_PATH_MAPPING.md +14 -0
  453. package/docs/api/variables/AddressField.md +41 -0
  454. package/docs/api/variables/Alert.md +11 -0
  455. package/docs/api/variables/AlertDescription.md +11 -0
  456. package/docs/api/variables/AlertTitle.md +11 -0
  457. package/docs/api/variables/Avatar.md +13 -0
  458. package/docs/api/variables/Button.md +31 -0
  459. package/docs/api/variables/CACHE_PATTERNS.md +89 -0
  460. package/docs/api/variables/Calendar.md +74 -0
  461. package/docs/api/variables/Card.md +11 -0
  462. package/docs/api/variables/CardActions.md +11 -0
  463. package/docs/api/variables/CardContent.md +11 -0
  464. package/docs/api/variables/CardDescription.md +11 -0
  465. package/docs/api/variables/CardFooter.md +11 -0
  466. package/docs/api/variables/CardHeader.md +11 -0
  467. package/docs/api/variables/CardTitle.md +11 -0
  468. package/docs/api/variables/Checkbox.md +11 -0
  469. package/docs/api/variables/DEFAULT_APP_PORT_MAP.md +14 -0
  470. package/docs/api/variables/DEFAULT_FILE_SIZE_LIMIT.md +13 -0
  471. package/docs/api/variables/Dialog.md +14 -0
  472. package/docs/api/variables/DialogClose.md +14 -0
  473. package/docs/api/variables/DialogContent.md +28 -0
  474. package/docs/api/variables/DialogDescription.md +14 -0
  475. package/docs/api/variables/DialogPortal.md +14 -0
  476. package/docs/api/variables/DialogTitle.md +14 -0
  477. package/docs/api/variables/DialogTrigger.md +14 -0
  478. package/docs/api/variables/EVENT_APP_PERMISSIONS.md +109 -0
  479. package/docs/api/variables/ErrorBoundary.md +15 -0
  480. package/docs/api/variables/FILE_SIZE_LIMITS.md +13 -0
  481. package/docs/api/variables/Footer.md +11 -0
  482. package/docs/api/variables/GLOBAL_PERMISSIONS.md +29 -0
  483. package/docs/api/variables/Label.md +34 -0
  484. package/docs/api/variables/LoadingSpinner.md +28 -0
  485. package/docs/api/variables/LoginForm.md +34 -0
  486. package/docs/api/variables/NavigationMenu.md +203 -0
  487. package/docs/api/variables/ORGANISATION_PERMISSIONS.md +89 -0
  488. package/docs/api/variables/PAGE_PERMISSIONS.md +93 -0
  489. package/docs/api/variables/PaceLoginPage.md +40 -0
  490. package/docs/api/variables/PagePermissionGuard.md +11 -0
  491. package/docs/api/variables/Progress.md +32 -0
  492. package/docs/api/variables/SECURE_CLIENT_SYMBOL.md +14 -0
  493. package/docs/api/variables/STORAGE_CONFIG.md +13 -0
  494. package/docs/api/variables/Select.md +26 -0
  495. package/docs/api/variables/SelectContent.md +26 -0
  496. package/docs/api/variables/SelectGroup.md +26 -0
  497. package/docs/api/variables/SelectItem.md +26 -0
  498. package/docs/api/variables/SelectLabel.md +26 -0
  499. package/docs/api/variables/SelectSeparator.md +26 -0
  500. package/docs/api/variables/SelectTrigger.md +26 -0
  501. package/docs/api/variables/SelectValue.md +26 -0
  502. package/docs/api/variables/SessionRestorationLoader.md +11 -0
  503. package/docs/api/variables/Switch.md +23 -0
  504. package/docs/api/variables/Table.md +35 -0
  505. package/docs/api/variables/TableBody.md +11 -0
  506. package/docs/api/variables/TableCaption.md +11 -0
  507. package/docs/api/variables/TableCell.md +11 -0
  508. package/docs/api/variables/TableFooter.md +11 -0
  509. package/docs/api/variables/TableHead.md +11 -0
  510. package/docs/api/variables/TableHeader.md +11 -0
  511. package/docs/api/variables/TableRow.md +11 -0
  512. package/docs/api/variables/Tabs.md +25 -0
  513. package/docs/api/variables/TabsContent.md +24 -0
  514. package/docs/api/variables/TabsList.md +25 -0
  515. package/docs/api/variables/TabsTrigger.md +34 -0
  516. package/docs/api/variables/Toast.md +36 -0
  517. package/docs/api/variables/ToastAction.md +32 -0
  518. package/docs/api/variables/ToastClose.md +32 -0
  519. package/docs/api/variables/ToastDescription.md +32 -0
  520. package/docs/api/variables/ToastProvider.md +11 -0
  521. package/docs/api/variables/ToastTitle.md +32 -0
  522. package/docs/api/variables/ToastViewport.md +26 -0
  523. package/docs/api/variables/Tooltip.md +34 -0
  524. package/docs/api/variables/TooltipContent.md +34 -0
  525. package/docs/api/variables/TooltipProvider.md +11 -0
  526. package/docs/api/variables/TooltipRoot.md +11 -0
  527. package/docs/api/variables/TooltipTrigger.md +11 -0
  528. package/docs/api/variables/UserMenu.md +11 -0
  529. package/docs/api/variables/emailSchema.md +13 -0
  530. package/docs/api/variables/logger.md +203 -0
  531. package/docs/api/variables/nameSchema.md +13 -0
  532. package/docs/api/variables/passwordSchema.md +13 -0
  533. package/docs/api/variables/phoneSchema.md +13 -0
  534. package/docs/api/variables/rbacCache.md +16 -0
  535. package/docs/api/variables/styleConfig.md +25 -0
  536. package/docs/api/variables/urlSchema.md +13 -0
  537. package/docs/api-reference/hooks.md +2 -0
  538. package/docs/api-reference/rpc-functions.md +12 -3
  539. package/docs/core-concepts/rbac-system.md +8 -0
  540. package/docs/getting-started/cursor-rules.md +17 -20
  541. package/docs/getting-started/dependencies.md +1 -1
  542. package/docs/getting-started/setup.md +235 -0
  543. package/docs/implementation-guides/authentication.md +27 -0
  544. package/docs/implementation-guides/data-tables.md +184 -3
  545. package/docs/migration/ApiResult-migration.md +25 -0
  546. package/docs/rbac/api-reference.md +33 -31
  547. package/docs/rbac/getting-started.md +7 -0
  548. package/docs/rbac/troubleshooting.md +5 -1
  549. package/docs/standards/0-standards-overview.md +50 -15
  550. package/docs/standards/1-pace-core-compliance-standards.md +62 -57
  551. package/docs/standards/2-project-structure-standards.md +33 -16
  552. package/docs/standards/3-architecture-standards.md +41 -1
  553. package/docs/standards/4-code-quality-standards.md +26 -6
  554. package/docs/standards/5-styling-standards.md +35 -1
  555. package/docs/standards/6-security-rbac-standards.md +66 -0
  556. package/docs/standards/7-api-tech-stack-standards.md +25 -14
  557. package/docs/standards/8-testing-documentation-standards.md +31 -0
  558. package/docs/standards/9-operations-standards.md +19 -0
  559. package/docs/standards/README.md +20 -201
  560. package/docs/testing/test-setup-for-consumers.md +2 -0
  561. package/docs/troubleshooting/common-issues.md +17 -1
  562. package/docs/troubleshooting/organisation-context-setup.md +8 -0
  563. package/docs/troubleshooting/print-event-name-css-variable-analysis.md +217 -0
  564. package/eslint-config-pace-core.cjs +20 -0
  565. package/package.json +16 -22
  566. package/scripts/build-docs.js +180 -0
  567. package/scripts/setup.cjs +536 -0
  568. package/scripts/validate.cjs +480 -0
  569. package/src/__tests__/helpers/component-test-utils.test.tsx +260 -0
  570. package/src/__tests__/helpers/optimized-test-setup.test.ts +224 -0
  571. package/src/__tests__/helpers/supabaseMock.test.ts +273 -0
  572. package/src/__tests__/helpers/test-providers.test.tsx +99 -0
  573. package/src/__tests__/helpers/test-providers.tsx +37 -39
  574. package/src/__tests__/helpers/test-utils.test.tsx +447 -0
  575. package/src/__tests__/helpers/timer-utils.test.ts +371 -0
  576. package/src/assets/app-icons/index.test.ts +304 -0
  577. package/src/components/AddressField/AddressField.test.tsx +1 -1
  578. package/src/components/AddressField/AddressField.tsx +238 -212
  579. package/src/components/Button/Button.tsx +1 -1
  580. package/src/components/Card/Card.test.tsx +172 -17
  581. package/src/components/Card/Card.tsx +19 -10
  582. package/src/components/ContextSelector/ContextSelector.internals.tsx +204 -0
  583. package/src/components/ContextSelector/ContextSelector.test.tsx +360 -0
  584. package/src/components/ContextSelector/ContextSelector.tsx +66 -280
  585. package/src/components/ContextSelector/ContextSelector.types.ts +35 -0
  586. package/src/components/ContextSelector/useContextSelectorState.tsx +195 -0
  587. package/src/components/DataTable/AUDIT_REPORT.md +59 -44
  588. package/src/components/DataTable/DataTable.comprehensive.test.tsx +759 -0
  589. package/src/components/DataTable/DataTable.default-state.test.tsx +524 -0
  590. package/src/components/DataTable/DataTable.export.test.tsx +705 -0
  591. package/src/components/DataTable/DataTable.grouping-aggregation.test.tsx +658 -0
  592. package/src/components/DataTable/DataTable.hooks.test.tsx +192 -0
  593. package/src/components/DataTable/DataTable.select-label-display.test.tsx +485 -0
  594. package/src/components/DataTable/DataTable.test.tsx +787 -416
  595. package/src/components/DataTable/DataTable.tsx +12 -12
  596. package/src/components/DataTable/DataTableCore.integration.test.tsx +458 -0
  597. package/src/components/DataTable/DataTableCore.test-setup.ts +221 -0
  598. package/src/components/DataTable/DataTableCore.test.tsx +970 -0
  599. package/src/components/DataTable/README.md +155 -0
  600. package/src/components/DataTable/TESTING.md +101 -0
  601. package/src/components/DataTable/a11y.basic.test.tsx +788 -0
  602. package/src/components/DataTable/components/DataTableCore.tsx +104 -864
  603. package/src/components/DataTable/components/GroupingDropdown.test.tsx +621 -0
  604. package/src/components/DataTable/components/GroupingDropdown.tsx +2 -2
  605. package/src/components/DataTable/components/ImportModal.tsx +61 -559
  606. package/src/components/DataTable/components/ImportModalFileSection.tsx +148 -0
  607. package/src/components/DataTable/context/DataTableContext.test.tsx +328 -0
  608. package/src/components/DataTable/context/DataTableContext.tsx +7 -6
  609. package/src/components/DataTable/core/ColumnFactory.test.ts +403 -0
  610. package/src/components/DataTable/hooks/useColumnOrderPersistence.test.ts +516 -0
  611. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.test.ts +256 -0
  612. package/src/components/DataTable/hooks/useDataTableConfiguration.test.ts +297 -0
  613. package/src/components/DataTable/hooks/useDataTableConfiguration.ts +14 -2
  614. package/src/components/DataTable/hooks/useDataTableDataPipeline.test.ts +270 -0
  615. package/src/components/DataTable/hooks/useDataTableDeletionBatching.test.ts +127 -0
  616. package/src/components/DataTable/hooks/useDataTableDeletionBatching.ts +106 -0
  617. package/src/components/DataTable/hooks/useDataTableEffectiveActions.test.ts +461 -0
  618. package/src/components/DataTable/hooks/useDataTableEffectiveActions.ts +248 -0
  619. package/src/components/DataTable/hooks/useDataTableLayoutHandlers.test.ts +296 -0
  620. package/src/components/DataTable/hooks/useDataTableLayoutHandlers.ts +175 -0
  621. package/src/components/DataTable/hooks/useDataTablePaginationSync.test.ts +203 -0
  622. package/src/components/DataTable/hooks/useDataTablePaginationSync.ts +109 -0
  623. package/src/components/DataTable/hooks/useDataTablePermissions.test.ts +280 -0
  624. package/src/components/DataTable/hooks/useDataTablePermissions.ts +79 -247
  625. package/src/components/DataTable/hooks/useDataTablePipeline.test.tsx +219 -0
  626. package/src/components/DataTable/hooks/useDataTablePipeline.tsx +239 -0
  627. package/src/components/DataTable/hooks/useDataTableRenderGuard.test.tsx +316 -0
  628. package/src/components/DataTable/hooks/useDataTableRenderGuard.tsx +195 -0
  629. package/src/components/DataTable/hooks/useDataTableScope.test.ts +102 -0
  630. package/src/components/DataTable/hooks/useDataTableScope.ts +125 -0
  631. package/src/components/DataTable/hooks/useDataTableState.test.ts +733 -0
  632. package/src/components/DataTable/hooks/useDataTableState.ts +145 -94
  633. package/src/components/DataTable/hooks/useDataTableStateAndPersistence.test.ts +277 -0
  634. package/src/components/DataTable/hooks/useDataTableStateAndPersistence.ts +222 -0
  635. package/src/components/DataTable/hooks/useDataTableSuperAdmin.test.ts +93 -0
  636. package/src/components/DataTable/hooks/useDataTableSuperAdmin.ts +86 -0
  637. package/src/components/DataTable/hooks/useDataTableTableInstance.test.ts +185 -0
  638. package/src/components/DataTable/hooks/useDataTableTableInstance.ts +178 -0
  639. package/src/components/DataTable/hooks/useEffectiveColumnOrder.test.ts +183 -0
  640. package/src/components/DataTable/hooks/useHierarchicalState.test.ts +294 -0
  641. package/src/components/DataTable/hooks/useImportModalFocus.test.ts +184 -0
  642. package/src/components/DataTable/hooks/useImportModalFocus.ts +53 -0
  643. package/src/components/DataTable/hooks/useImportModalState.test.ts +390 -0
  644. package/src/components/DataTable/hooks/useImportModalState.ts +345 -0
  645. package/src/components/DataTable/hooks/useKeyboardNavigation.test.ts +787 -0
  646. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +309 -269
  647. package/src/components/DataTable/hooks/usePermissionTracking.test.ts +381 -0
  648. package/src/components/DataTable/hooks/usePermissionTracking.ts +122 -0
  649. package/src/components/DataTable/hooks/useServerSideDataEffect.test.ts +258 -0
  650. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +14 -3
  651. package/src/components/DataTable/hooks/useTableColumns.test.ts +499 -0
  652. package/src/components/DataTable/hooks/useTableHandlers.test.ts +461 -0
  653. package/src/components/DataTable/hooks/useTableHandlers.ts +5 -2
  654. package/src/components/DataTable/index.ts +18 -17
  655. package/src/components/DataTable/keyboard.test.tsx +734 -0
  656. package/src/components/DataTable/mocks/MockRBACProvider.tsx +66 -0
  657. package/src/components/DataTable/pagination.modes.test.tsx +728 -0
  658. package/src/components/DataTable/ssr.strict-mode.test.tsx +319 -0
  659. package/src/components/DataTable/styles.test.ts +379 -0
  660. package/src/components/DataTable/styles.ts +0 -1
  661. package/src/components/DataTable/test-utils/MockDataTableComponents.tsx +55 -0
  662. package/src/components/DataTable/test-utils/dataFactories.ts +103 -0
  663. package/src/components/DataTable/test-utils/featureConfig.ts +10 -0
  664. package/src/components/DataTable/test-utils/sharedTestUtils.ts +419 -0
  665. package/src/components/DataTable/test-utils.ts +94 -0
  666. package/src/components/DataTable/types/actions.ts +71 -0
  667. package/src/components/DataTable/types/base.ts +39 -0
  668. package/src/components/DataTable/types/columns.ts +125 -0
  669. package/src/components/DataTable/types/export.ts +32 -0
  670. package/src/components/DataTable/types/features.ts +81 -0
  671. package/src/components/DataTable/types/hierarchical.ts +44 -0
  672. package/src/components/DataTable/types/index.ts +43 -0
  673. package/src/components/DataTable/types/pagination.ts +85 -0
  674. package/src/components/DataTable/types/performance.ts +47 -0
  675. package/src/components/DataTable/types/props.ts +62 -0
  676. package/src/components/DataTable/types/rbac.ts +45 -0
  677. package/src/components/DataTable/ui/layout/DataTableCore.test.tsx +1194 -0
  678. package/src/components/DataTable/ui/layout/DataTableCore.tsx +345 -0
  679. package/src/components/DataTable/ui/layout/DataTableErrorBoundary.test.tsx +438 -0
  680. package/src/components/DataTable/ui/layout/DataTableErrorBoundary.tsx +225 -0
  681. package/src/components/DataTable/ui/layout/DataTableLayout.test.tsx +1352 -0
  682. package/src/components/DataTable/ui/layout/DataTableLayout.tsx +661 -0
  683. package/src/components/DataTable/ui/modals/BulkDeleteConfirmDialog.test.tsx +91 -0
  684. package/src/components/DataTable/ui/modals/BulkDeleteConfirmDialog.tsx +43 -0
  685. package/src/components/DataTable/ui/modals/DataTableModals.test.tsx +749 -0
  686. package/src/components/DataTable/ui/modals/DataTableModals.tsx +341 -0
  687. package/src/components/DataTable/ui/modals/ImportModal.test.tsx +1834 -0
  688. package/src/components/DataTable/ui/modals/ImportModal.tsx +197 -0
  689. package/src/components/DataTable/ui/modals/ImportModalFailedRowsSection.tsx +60 -0
  690. package/src/components/DataTable/ui/modals/ImportModalFileSection.tsx +148 -0
  691. package/src/components/DataTable/ui/modals/ImportModalPreviewSection.tsx +60 -0
  692. package/src/components/DataTable/ui/modals/ImportModalSummarySection.tsx +59 -0
  693. package/src/components/DataTable/ui/modals/importModalPersistence.ts +73 -0
  694. package/src/components/DataTable/ui/shared/AccessDeniedPage.test.tsx +245 -0
  695. package/src/components/DataTable/ui/shared/AccessDeniedPage.tsx +159 -0
  696. package/src/components/DataTable/ui/shared/ActionButtons.test.tsx +921 -0
  697. package/src/components/DataTable/ui/shared/ActionButtons.tsx +195 -0
  698. package/src/components/DataTable/ui/shared/ColumnFilter.test.tsx +497 -0
  699. package/src/components/DataTable/ui/shared/ColumnFilter.tsx +113 -0
  700. package/src/components/DataTable/ui/shared/PaginationControls.test.tsx +451 -0
  701. package/src/components/DataTable/ui/shared/PaginationControls.tsx +291 -0
  702. package/src/components/DataTable/ui/shared/SortIndicator.test.tsx +135 -0
  703. package/src/components/DataTable/ui/shared/SortIndicator.tsx +50 -0
  704. package/src/components/DataTable/ui/table/EditFields.test.tsx +526 -0
  705. package/src/components/DataTable/ui/table/EditFields.tsx +355 -0
  706. package/src/components/DataTable/ui/table/EditableRow.test.tsx +1003 -0
  707. package/src/components/DataTable/ui/table/EditableRow.tsx +444 -0
  708. package/src/components/DataTable/ui/table/EmptyState.test.tsx +360 -0
  709. package/src/components/DataTable/ui/table/EmptyState.tsx +74 -0
  710. package/src/components/DataTable/ui/table/FilterRow.test.tsx +416 -0
  711. package/src/components/DataTable/ui/table/FilterRow.tsx +148 -0
  712. package/src/components/DataTable/ui/table/LoadingState.test.tsx +77 -0
  713. package/src/components/DataTable/ui/table/LoadingState.tsx +17 -0
  714. package/src/components/DataTable/ui/table/RowComponent.test.tsx +1024 -0
  715. package/src/components/DataTable/ui/table/RowComponent.tsx +429 -0
  716. package/src/components/DataTable/ui/table/UnifiedTableBody.test.tsx +1273 -0
  717. package/src/components/DataTable/ui/table/UnifiedTableBody.tsx +440 -0
  718. package/src/components/DataTable/ui/table/cellValueUtils.test.ts +453 -0
  719. package/src/components/DataTable/ui/table/cellValueUtils.ts +40 -0
  720. package/src/components/DataTable/ui/toolbar/BulkOperationsDropdown.test.tsx +551 -0
  721. package/src/components/DataTable/ui/toolbar/BulkOperationsDropdown.tsx +160 -0
  722. package/src/components/DataTable/ui/toolbar/ColumnVisibilityDropdown.test.tsx +751 -0
  723. package/src/components/DataTable/ui/toolbar/ColumnVisibilityDropdown.tsx +114 -0
  724. package/src/components/DataTable/ui/toolbar/DataTableToolbar.test.tsx +629 -0
  725. package/src/components/DataTable/ui/toolbar/DataTableToolbar.tsx +271 -0
  726. package/src/components/DataTable/ui/toolbar/GroupingDropdown.test.tsx +621 -0
  727. package/src/components/DataTable/ui/toolbar/GroupingDropdown.tsx +107 -0
  728. package/src/components/DataTable/utils/a11yUtils.test.ts +548 -0
  729. package/src/components/DataTable/utils/aggregationUtils.test.ts +288 -0
  730. package/src/components/DataTable/utils/columnUtils.test.ts +94 -0
  731. package/src/components/DataTable/utils/csvParse.test.ts +74 -0
  732. package/src/components/DataTable/utils/csvParse.ts +65 -0
  733. package/src/components/DataTable/utils/errorHandling.test.ts +209 -0
  734. package/src/components/DataTable/utils/exportUtils.test.ts +954 -0
  735. package/src/components/DataTable/utils/flexibleImport.test.ts +573 -0
  736. package/src/components/DataTable/utils/flexibleImport.ts +3 -186
  737. package/src/components/DataTable/utils/hierarchicalSorting.test.ts +235 -0
  738. package/src/components/DataTable/utils/hierarchicalUtils.test.ts +586 -0
  739. package/src/components/DataTable/utils/importDateParser.test.ts +162 -0
  740. package/src/components/DataTable/utils/importDateParser.ts +114 -0
  741. package/src/components/DataTable/utils/importValueParser.test.ts +138 -0
  742. package/src/components/DataTable/utils/importValueParser.ts +91 -0
  743. package/src/components/DataTable/utils/paginationUtils.test.ts +593 -0
  744. package/src/components/DataTable/utils/paginationUtils.ts +6 -3
  745. package/src/components/DataTable/utils/performanceUtils.test.ts +470 -0
  746. package/src/components/DataTable/utils/rowUtils.test.ts +235 -0
  747. package/src/components/DataTable/utils/selectFieldUtils.test.ts +271 -0
  748. package/src/components/DataTable/utils/selectFieldUtils.ts +97 -60
  749. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +1 -1
  750. package/src/components/DateTimeField/DateTimeField.test.tsx +1 -1
  751. package/src/components/Dialog/Dialog.test-utils.ts +49 -0
  752. package/src/components/Dialog/Dialog.test.tsx +896 -89
  753. package/src/components/Dialog/Dialog.tsx +174 -882
  754. package/src/components/Dialog/dialogLock.test.ts +238 -0
  755. package/src/components/Dialog/dialogLock.ts +98 -0
  756. package/src/components/Dialog/index.ts +2 -0
  757. package/src/components/Dialog/useDialogDimensions.test.ts +163 -0
  758. package/src/components/Dialog/useDialogDimensions.ts +140 -0
  759. package/src/components/Dialog/useDialogLifecycle.test.ts +361 -0
  760. package/src/components/Dialog/useDialogLifecycle.ts +135 -0
  761. package/src/components/Dialog/useDialogPersistence.test.ts +381 -0
  762. package/src/components/Dialog/useDialogPersistence.ts +357 -0
  763. package/src/components/FileDisplay/FileDisplay.test.tsx +40 -40
  764. package/src/components/FileDisplay/FileDisplay.tsx +24 -656
  765. package/src/components/FileDisplay/FileDisplayContent.test.tsx +395 -0
  766. package/src/components/FileDisplay/FileDisplayContent.tsx +242 -0
  767. package/src/components/FileDisplay/FileDisplayDeleteConfirmDialog.test.tsx +74 -0
  768. package/src/components/FileDisplay/FileDisplayDeleteConfirmDialog.tsx +38 -0
  769. package/src/components/FileDisplay/FileDisplayEmptyView.test.tsx +33 -0
  770. package/src/components/FileDisplay/FileDisplayEmptyView.tsx +33 -0
  771. package/src/components/FileDisplay/FileDisplayErrorView.test.tsx +71 -0
  772. package/src/components/FileDisplay/FileDisplayErrorView.tsx +50 -0
  773. package/src/components/FileDisplay/FileDisplayLoadingFallbackView.test.tsx +22 -0
  774. package/src/components/FileDisplay/FileDisplayLoadingFallbackView.tsx +22 -0
  775. package/src/components/FileDisplay/FileDisplayLoadingView.test.tsx +21 -0
  776. package/src/components/FileDisplay/FileDisplayLoadingView.tsx +23 -0
  777. package/src/components/FileDisplay/FileDisplayMultipleFilesView.test.tsx +101 -0
  778. package/src/components/FileDisplay/FileDisplayMultipleFilesView.tsx +109 -0
  779. package/src/components/FileDisplay/FileDisplaySingleDocumentLinkView.test.tsx +58 -0
  780. package/src/components/FileDisplay/FileDisplaySingleDocumentLinkView.tsx +48 -0
  781. package/src/components/FileDisplay/FileDisplaySingleFileWithActionsView.test.tsx +111 -0
  782. package/src/components/FileDisplay/FileDisplaySingleFileWithActionsView.tsx +270 -0
  783. package/src/components/FileDisplay/FileDisplaySingleImageView.test.tsx +78 -0
  784. package/src/components/FileDisplay/FileDisplaySingleImageView.tsx +67 -0
  785. package/src/components/FileDisplay/fallbackUtils.test.ts +50 -0
  786. package/src/components/FileDisplay/fallbackUtils.ts +44 -0
  787. package/src/components/FileDisplay/fetchFileDisplayData.ts +24 -0
  788. package/src/components/FileDisplay/fetchFileDisplayData.unit.test.ts +183 -0
  789. package/src/components/FileDisplay/fileDisplayUtils.test.ts +58 -0
  790. package/src/components/FileDisplay/fileDisplayUtils.ts +24 -0
  791. package/src/components/FileDisplay/useFileDisplay.test.ts +538 -0
  792. package/src/components/FileDisplay/useFileDisplay.ts +515 -0
  793. package/src/components/FileDisplay/useFileDisplay.unit.test.ts +1442 -0
  794. package/src/components/FileDisplay/useFileDisplayData.ts +126 -0
  795. package/src/components/FileDisplay/usePublicFileDisplay.test.ts +729 -0
  796. package/src/components/FileDisplay/usePublicFileDisplay.ts +579 -0
  797. package/src/components/FileUpload/FileUpload.test.tsx +16 -10
  798. package/src/components/FileUpload/FileUpload.tsx +107 -525
  799. package/src/components/FileUpload/FileUploadDropZone.tsx +112 -0
  800. package/src/components/FileUpload/FileUploadProgressItem.tsx +86 -0
  801. package/src/components/FileUpload/FileUploadProgressList.tsx +40 -0
  802. package/src/components/FileUpload/useFileUploadManager.test.ts +308 -0
  803. package/src/components/FileUpload/useFileUploadManager.ts +454 -0
  804. package/src/components/FileUpload/useResolvedAppId.test.ts +102 -0
  805. package/src/components/FileUpload/useResolvedAppId.ts +77 -0
  806. package/src/components/Footer/Footer.test.tsx +6 -292
  807. package/src/components/Footer/Footer.tsx +8 -125
  808. package/src/components/Form/Form.test.tsx +44 -27
  809. package/src/components/Form/Form.tsx +64 -287
  810. package/src/components/Form/useFormPersistence.ts +257 -0
  811. package/src/components/Header/Header.test.tsx +17 -18
  812. package/src/components/Header/Header.tsx +10 -1
  813. package/src/components/Input/Input.tsx +1 -1
  814. package/src/components/Label/Label.test.tsx +1 -1
  815. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +1 -1
  816. package/src/components/NavigationMenu/HierarchicalNavItem.tsx +104 -0
  817. package/src/components/NavigationMenu/NavigationMenu.test.tsx +1029 -26
  818. package/src/components/NavigationMenu/NavigationMenu.tsx +61 -361
  819. package/src/components/NavigationMenu/index.ts +6 -1
  820. package/src/components/NavigationMenu/navigationPermissionHelper.ts +188 -0
  821. package/src/components/NavigationMenu/useNavigationFiltering.test.ts +1949 -0
  822. package/src/components/NavigationMenu/useNavigationFiltering.ts +197 -296
  823. package/src/components/NavigationMenu/useNavigationScope.ts +125 -0
  824. package/src/components/PaceAppLayout/PaceAppLayout.edge-cases.test.tsx +109 -70
  825. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +3 -3
  826. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +16 -19
  827. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +529 -5
  828. package/src/components/PaceAppLayout/PaceAppLayout.tsx +280 -756
  829. package/src/components/PaceAppLayout/useFilteredNavItems.ts +319 -0
  830. package/src/components/PaceAppLayout/usePaceAppLayoutConfig.ts +163 -0
  831. package/src/components/PaceAppLayout/usePaceAppLayoutGate.tsx +150 -0
  832. package/src/components/PaceAppLayout/usePaceAppLayoutPermissions.ts +162 -0
  833. package/src/components/PaceAppLayout/usePaceAppLayoutScope.ts +81 -0
  834. package/src/components/PaceAppLayout/useRoleBasedRouteAccess.ts +157 -0
  835. package/src/components/PaceAppLayout/useSuperAdminFallback.ts +58 -0
  836. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +31 -25
  837. package/src/components/PaceLoginPage/PaceLoginPage.tsx +31 -122
  838. package/src/components/PaceLoginPage/useLoginAppAccess.ts +153 -0
  839. package/src/components/Progress/Progress.tsx +1 -2
  840. package/src/components/ProtectedRoute/ProtectedRoute.tsx +29 -235
  841. package/src/components/ProtectedRoute/useProtectedRouteState.ts +128 -0
  842. package/src/components/ProtectedRoute/useVisibilityRedirectGrace.ts +89 -0
  843. package/src/components/PublicLayout/PublicLayout.test.tsx +217 -36
  844. package/src/components/PublicLayout/PublicPageLayout.tsx +132 -73
  845. package/src/components/PublicLayout/PublicPageProvider.tsx +5 -1
  846. package/src/components/Select/Select.test.tsx +1 -1
  847. package/src/components/Select/Select.tsx +28 -18
  848. package/src/components/Select/context.test.tsx +56 -0
  849. package/src/components/Select/text.test.tsx +104 -0
  850. package/src/components/Select/text.ts +26 -0
  851. package/src/components/Select/useSelectEvents.test.ts +279 -0
  852. package/src/components/Select/useSelectEvents.ts +87 -0
  853. package/src/components/Select/useSelectSearch.test.tsx +295 -0
  854. package/src/components/Select/useSelectSearch.ts +91 -0
  855. package/src/components/Select/useSelectState.test.ts +268 -0
  856. package/src/components/Select/useSelectState.ts +104 -0
  857. package/src/components/Table/Table.test.tsx +348 -0
  858. package/src/components/Tabs/Tabs.test.tsx +270 -0
  859. package/src/components/Tabs/Tabs.tsx +1 -1
  860. package/src/components/Toast/Toast.test.tsx +420 -0
  861. package/src/components/index.test.ts +346 -0
  862. package/src/constants/performance.test.ts +91 -0
  863. package/src/hooks/ServiceHooks.test.tsx +725 -0
  864. package/src/hooks/hooks.integration.test.tsx +608 -0
  865. package/src/hooks/index.ts +7 -4
  866. package/src/hooks/index.unit.test.ts +220 -0
  867. package/src/hooks/public/usePublicEvent.test.ts +1 -1
  868. package/src/hooks/public/usePublicEventLogo.test.ts +1 -1
  869. package/src/hooks/public/usePublicRouteParams.test.ts +1 -1
  870. package/src/hooks/services/useAuth.ts +9 -7
  871. package/src/hooks/useAddressAutocomplete.test.ts +22 -22
  872. package/src/hooks/useAddressAutocomplete.ts +90 -75
  873. package/src/hooks/useAppConfig.unit.test.ts +720 -0
  874. package/src/hooks/useComponentPerformance.unit.test.tsx +316 -0
  875. package/src/hooks/useDataTablePerformance.ts +100 -120
  876. package/src/hooks/useDataTablePerformance.unit.test.ts +720 -0
  877. package/src/hooks/useDataTableState.test.ts +170 -0
  878. package/src/hooks/useDebounce.unit.test.ts +157 -0
  879. package/src/hooks/useEventTheme.test.ts +4 -1
  880. package/src/hooks/useEventTheme.ts +49 -21
  881. package/src/hooks/useEvents.ts +41 -1
  882. package/src/hooks/useEvents.unit.test.ts +227 -0
  883. package/src/hooks/useFileReference.test.ts +44 -41
  884. package/src/hooks/useFileReference.ts +182 -173
  885. package/src/hooks/useFileUrl.ts +1 -1
  886. package/src/hooks/useFileUrl.unit.test.ts +684 -0
  887. package/src/hooks/useFileUrlCache.test.ts +319 -0
  888. package/src/hooks/useFileUrlCache.ts +1 -1
  889. package/src/hooks/useFocusManagement.unit.test.ts +604 -0
  890. package/src/hooks/useFocusTrap.unit.test.tsx +613 -0
  891. package/src/hooks/useFormDialog.test.ts +307 -0
  892. package/src/hooks/useInactivityTracker.ts +138 -131
  893. package/src/hooks/useInactivityTracker.unit.test.ts +450 -0
  894. package/src/hooks/useIsMobile.unit.test.ts +317 -0
  895. package/src/hooks/useIsPrint.ts +62 -0
  896. package/src/hooks/useIsPrint.unit.test.ts +545 -0
  897. package/src/hooks/useKeyboardShortcuts.unit.test.ts +907 -0
  898. package/src/hooks/useOrganisationPermissions.unit.test.tsx +293 -0
  899. package/src/hooks/useOrganisationSecurity.test.ts +3 -3
  900. package/src/hooks/useOrganisationSecurity.ts +190 -201
  901. package/src/hooks/useOrganisationSecurity.unit.test.tsx +959 -0
  902. package/src/hooks/useOrganisations.unit.test.ts +369 -0
  903. package/src/hooks/usePerformanceMonitor.unit.test.ts +683 -0
  904. package/src/hooks/usePermissionCache.test.ts +505 -0
  905. package/src/hooks/usePermissionCache.ts +276 -271
  906. package/src/hooks/usePreventTabReload.test.ts +307 -0
  907. package/src/hooks/usePublicEvent.simple.test.ts +779 -0
  908. package/src/hooks/usePublicEvent.test.ts +664 -0
  909. package/src/hooks/usePublicEvent.unit.test.ts +638 -0
  910. package/src/hooks/usePublicFileDisplay.test.ts +948 -0
  911. package/src/hooks/usePublicRouteParams.unit.test.ts +442 -0
  912. package/src/hooks/useQueryCache.test.ts +391 -0
  913. package/src/hooks/useQueryCache.ts +0 -2
  914. package/src/hooks/useRBAC.unit.test.ts +253 -0
  915. package/src/hooks/useSessionDraft.test.ts +556 -0
  916. package/src/hooks/useSessionRestoration.unit.test.tsx +381 -0
  917. package/src/hooks/useStorage.ts +21 -16
  918. package/src/hooks/useStorage.unit.test.ts +684 -0
  919. package/src/hooks/useToast.test.ts +413 -0
  920. package/src/hooks/useToast.unit.test.tsx +481 -0
  921. package/src/hooks/useZodForm.unit.test.tsx +191 -0
  922. package/src/icons/index.test.ts +133 -0
  923. package/src/icons/index.ts +2 -0
  924. package/src/index.test.ts +528 -0
  925. package/src/index.ts +15 -7
  926. package/src/providers/AuthProvider.test.tsx +218 -0
  927. package/src/providers/EventProvider.test.tsx +487 -0
  928. package/src/providers/InactivityProvider.test-helper.tsx +40 -0
  929. package/src/providers/InactivityProvider.test.tsx +421 -0
  930. package/src/providers/ProviderLifecycle.test.tsx +308 -0
  931. package/src/providers/UnifiedAuthProvider.test.tsx +503 -0
  932. package/src/providers/index.test.ts +138 -0
  933. package/src/providers/services/AuthServiceProvider.integration.test.tsx +229 -0
  934. package/src/providers/services/AuthServiceProvider.test.tsx +643 -0
  935. package/src/providers/services/EventServiceProvider.test.tsx +844 -0
  936. package/src/providers/services/InactivityServiceProvider.test.tsx +667 -0
  937. package/src/providers/services/OrganisationServiceProvider.test.tsx +445 -0
  938. package/src/providers/services/UnifiedAuthContext.ts +30 -27
  939. package/src/providers/services/UnifiedAuthProvider.advanced.test.tsx +434 -0
  940. package/src/providers/services/UnifiedAuthProvider.appId.test.tsx +408 -0
  941. package/src/providers/services/UnifiedAuthProvider.integration.test.tsx +304 -0
  942. package/src/providers/services/UnifiedAuthProvider.tsx +115 -360
  943. package/src/providers/services/contexts.test.tsx +281 -0
  944. package/src/providers/services/useUnifiedAuth.test.tsx +251 -0
  945. package/src/providers/services/useUnifiedAuthContextValue.ts +279 -0
  946. package/src/providers/useInactivity.test-helper.ts +27 -0
  947. package/src/rbac/README.md +7 -5
  948. package/src/rbac/adapters.comprehensive.test.tsx +429 -0
  949. package/src/rbac/adapters.test.tsx +22 -22
  950. package/src/rbac/adapters.tsx +29 -29
  951. package/src/rbac/api.test.ts +1075 -87
  952. package/src/rbac/api.ts +300 -255
  953. package/src/rbac/audit-batched.test.ts +550 -0
  954. package/src/rbac/audit.ts +4 -1
  955. package/src/rbac/auth-rbac-security.integration.test.tsx +300 -0
  956. package/src/rbac/auth-rbac.e2e.test.tsx +510 -0
  957. package/src/rbac/cache-invalidation.test.ts +715 -0
  958. package/src/rbac/components/AccessDenied.test.tsx +324 -0
  959. package/src/rbac/components/NavigationGuard.test.tsx +1148 -0
  960. package/src/rbac/components/NavigationGuard.tsx +2 -1
  961. package/src/rbac/components/PagePermissionGuard.guard.test.tsx +236 -0
  962. package/src/rbac/components/PagePermissionGuard.performance.test.tsx +252 -0
  963. package/src/rbac/components/PagePermissionGuard.race-condition.test.tsx +243 -0
  964. package/src/rbac/components/PagePermissionGuard.test.tsx +1443 -0
  965. package/src/rbac/components/PagePermissionGuard.tsx +177 -372
  966. package/src/rbac/components/PagePermissionGuard.verification.test.tsx +185 -0
  967. package/src/rbac/config.ts +58 -18
  968. package/src/rbac/engine.comprehensive.test.ts +808 -0
  969. package/src/rbac/engine.test.ts +494 -0
  970. package/src/rbac/engine.ts +23 -1
  971. package/src/rbac/errors.ts +89 -55
  972. package/src/rbac/hooks/permissions/runPermissionCheck.ts +91 -0
  973. package/src/rbac/hooks/permissions/useAccessLevel.test.ts +622 -0
  974. package/src/rbac/hooks/permissions/useAccessLevel.ts +16 -6
  975. package/src/rbac/hooks/permissions/useCan.test.ts +837 -0
  976. package/src/rbac/hooks/permissions/useCan.ts +177 -255
  977. package/src/rbac/hooks/permissions/useMultiplePermissions.test.ts +843 -0
  978. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +24 -11
  979. package/src/rbac/hooks/permissions/usePermissions.test.ts +543 -0
  980. package/src/rbac/hooks/permissions/usePermissions.ts +36 -65
  981. package/src/rbac/hooks/useCan.test.ts +44 -45
  982. package/src/rbac/hooks/usePageAccessLogging.ts +160 -0
  983. package/src/rbac/hooks/usePageGuardScope.ts +119 -0
  984. package/src/rbac/hooks/usePagePermissionCheck.ts +67 -0
  985. package/src/rbac/hooks/usePermissions.integration.test.ts +427 -0
  986. package/src/rbac/hooks/usePermissions.stability.test.ts +268 -0
  987. package/src/rbac/hooks/usePermissions.test.ts +54 -54
  988. package/src/rbac/hooks/useRBAC.test.ts +313 -217
  989. package/src/rbac/hooks/useRBAC.ts +145 -81
  990. package/src/rbac/hooks/useResolvedScope.ts +16 -10
  991. package/src/rbac/hooks/useResourcePermissions.test.ts +73 -83
  992. package/src/rbac/hooks/useResourcePermissions.ts +77 -152
  993. package/src/rbac/hooks/useResourcePermissionsSuperAdmin.ts +67 -0
  994. package/src/rbac/hooks/useRoleManagement.test.ts +27 -112
  995. package/src/rbac/hooks/useRoleManagement.ts +153 -585
  996. package/src/rbac/hooks/useSecureSupabase.test.ts +1179 -0
  997. package/src/rbac/hooks/useSecureSupabase.ts +10 -2
  998. package/src/rbac/hooks/useSuperAdminCheck.ts +80 -0
  999. package/src/rbac/performance.test.ts +451 -0
  1000. package/src/rbac/rbac-core.test.tsx +276 -0
  1001. package/src/rbac/rbac-engine-core-logic.test.ts +387 -0
  1002. package/src/rbac/rbac-engine-simplified.test.ts +252 -0
  1003. package/src/rbac/rbac-functions.test.ts +703 -0
  1004. package/src/rbac/rbac-role-isolation.test.ts +456 -0
  1005. package/src/rbac/request-deduplication.test.ts +14 -9
  1006. package/src/rbac/request-deduplication.ts +5 -4
  1007. package/src/rbac/scenarios.user-role.test.tsx +271 -0
  1008. package/src/rbac/secureClient.test.ts +514 -83
  1009. package/src/rbac/secureClient.ts +8 -2
  1010. package/src/rbac/security.test.ts +323 -0
  1011. package/src/rbac/types/roleManagement.ts +66 -0
  1012. package/src/rbac/types.ts +3 -0
  1013. package/src/rbac/utils/clientSecurity.test.ts +192 -0
  1014. package/src/rbac/utils/contextValidator.test.ts +126 -0
  1015. package/src/rbac/utils/contextValidator.ts +5 -1
  1016. package/src/rbac/utils/deep-equal.test.ts +76 -0
  1017. package/src/rbac/utils/eventContext.test.ts +401 -0
  1018. package/src/rbac/utils/eventContext.ts +37 -33
  1019. package/src/rbac/utils/fetchPermissionMap.ts +13 -0
  1020. package/src/rbac/utils/permissionMapHelpers.ts +34 -0
  1021. package/src/rbac/utils/roleManagementRpc.ts +303 -0
  1022. package/src/services/AuthService.edge-cases.test.ts +746 -0
  1023. package/src/services/AuthService.restoreSession.test.ts +59 -0
  1024. package/src/services/AuthService.test.ts +1362 -0
  1025. package/src/services/AuthService.ts +184 -205
  1026. package/src/services/BaseService.edge-cases.test.ts +506 -0
  1027. package/src/services/BaseService.test.ts +363 -0
  1028. package/src/services/EventService.edge-cases.test.ts +636 -0
  1029. package/src/services/EventService.eventColours.test.ts +64 -0
  1030. package/src/services/EventService.test.ts +1250 -0
  1031. package/src/services/EventService.ts +251 -316
  1032. package/src/services/InactivityService.edge-cases.test.ts +492 -0
  1033. package/src/services/InactivityService.lifecycle.test.ts +406 -0
  1034. package/src/services/InactivityService.test.ts +829 -0
  1035. package/src/services/InactivityService.ts +172 -213
  1036. package/src/services/OrganisationService.edge-cases.test.ts +633 -0
  1037. package/src/services/OrganisationService.pagination.test.ts +409 -0
  1038. package/src/services/OrganisationService.test.ts +1579 -0
  1039. package/src/services/OrganisationService.ts +184 -238
  1040. package/src/services/base/BaseService.test.ts +1 -1
  1041. package/src/services/interfaces/IAuthService.test.ts +184 -0
  1042. package/src/services/interfaces/IAuthService.ts +10 -9
  1043. package/src/services/interfaces/IEventService.test.ts +176 -0
  1044. package/src/services/interfaces/IInactivityService.test.ts +183 -0
  1045. package/src/services/interfaces/IOrganisationService.test.ts +207 -0
  1046. package/src/styles/core.css +243 -12
  1047. package/src/theming/parseEventColours.test.ts +321 -0
  1048. package/src/theming/runtime.test.ts +495 -0
  1049. package/src/theming/runtime.ts +71 -2
  1050. package/src/types/api-result.ts +53 -0
  1051. package/src/types/core.test.ts +397 -0
  1052. package/src/types/database-generated.test.ts +78 -0
  1053. package/src/types/database.generated.ts +45 -10
  1054. package/src/types/event.ts +38 -18
  1055. package/src/types/file-reference.test.ts +351 -0
  1056. package/src/types/file-reference.ts +37 -12
  1057. package/src/types/guards.test.ts +246 -0
  1058. package/src/types/index.test.ts +265 -0
  1059. package/src/types/index.ts +3 -0
  1060. package/src/types/organisation.roles.test.ts +55 -0
  1061. package/src/types/organisation.test.ts +1105 -0
  1062. package/src/types/organisation.ts +15 -15
  1063. package/src/types/supabase.ts +13 -4
  1064. package/src/types/theme.test.ts +830 -0
  1065. package/src/types/type-validation.test.ts +526 -0
  1066. package/src/types/validation.test.ts +729 -0
  1067. package/src/utils/app/appIdResolver.test.ts +98 -71
  1068. package/src/utils/app/appIdResolver.ts +31 -20
  1069. package/src/utils/appConfig.unit.test.ts +55 -0
  1070. package/src/utils/audit.unit.test.ts +69 -0
  1071. package/src/utils/auth-utils.unit.test.ts +69 -0
  1072. package/src/utils/bundleAnalysis.unit.test.ts +326 -0
  1073. package/src/utils/cn.unit.test.ts +34 -0
  1074. package/src/utils/context/organisationContext.test.ts +105 -91
  1075. package/src/utils/context/organisationContext.ts +29 -40
  1076. package/src/utils/core/cn.test.ts +66 -0
  1077. package/src/utils/core/debugLogger.test.ts +113 -0
  1078. package/src/utils/core/logger.test.ts +217 -0
  1079. package/src/utils/core/mergeRefs.ts +24 -0
  1080. package/src/utils/debugLogger.test.ts +417 -0
  1081. package/src/utils/deviceFingerprint.unit.test.ts +818 -0
  1082. package/src/utils/dynamic/createLazyComponent.tsx +9 -1
  1083. package/src/utils/dynamic/dynamicUtils.test.ts +185 -0
  1084. package/src/utils/dynamic/lazyLoad.test.tsx +156 -0
  1085. package/src/utils/dynamicUtils.unit.test.ts +331 -0
  1086. package/src/utils/file-reference/file-reference.test.ts +1249 -0
  1087. package/src/utils/file-reference/index.ts +330 -347
  1088. package/src/utils/formatDate.unit.test.ts +109 -0
  1089. package/src/utils/formatting/formatDateTimeTimezone.test.ts +1 -1
  1090. package/src/utils/formatting/formatNumber.test.ts +1 -1
  1091. package/src/utils/formatting.unit.test.ts +99 -0
  1092. package/src/utils/google-places/googlePlacesUtils.test.ts +70 -48
  1093. package/src/utils/google-places/googlePlacesUtils.ts +67 -99
  1094. package/src/utils/google-places/loadGoogleMapsScript.test.ts +25 -22
  1095. package/src/utils/google-places/loadGoogleMapsScript.ts +138 -117
  1096. package/src/utils/index.unit.test.ts +251 -0
  1097. package/src/utils/lazyLoad.unit.test.tsx +319 -0
  1098. package/src/utils/location/location.test.ts +1 -1
  1099. package/src/utils/logger.unit.test.ts +398 -0
  1100. package/src/utils/organisationContext.unit.test.ts +180 -0
  1101. package/src/utils/performance/bundleAnalysis.test.ts +148 -0
  1102. package/src/utils/performance/performanceBenchmark.test.ts +251 -0
  1103. package/src/utils/performance/performanceBudgets.test.ts +241 -0
  1104. package/src/utils/performanceBenchmark.test.ts +174 -0
  1105. package/src/utils/performanceBudgets.unit.test.ts +288 -0
  1106. package/src/utils/permissionTypes.unit.test.ts +250 -0
  1107. package/src/utils/permissionUtils.unit.test.ts +362 -0
  1108. package/src/utils/permissions/permissionTypes.test.ts +149 -0
  1109. package/src/utils/persistence/keyDerivation.test.ts +306 -0
  1110. package/src/utils/persistence/sensitiveFieldDetection.test.ts +271 -0
  1111. package/src/utils/request-deduplication.test.ts +349 -0
  1112. package/src/utils/sanitization.unit.test.ts +346 -0
  1113. package/src/utils/schemaUtils.unit.test.ts +441 -0
  1114. package/src/utils/secureDataAccess.unit.test.ts +334 -0
  1115. package/src/utils/secureErrors.unit.test.ts +390 -0
  1116. package/src/utils/secureStorage.unit.test.ts +289 -0
  1117. package/src/utils/security/auth-utils.ts +34 -23
  1118. package/src/utils/security/secureDataAccess.ts +241 -281
  1119. package/src/utils/security/secureErrors.test.ts +1 -1
  1120. package/src/utils/security/secureStorage.test.ts +1 -1
  1121. package/src/utils/security/security.test.ts +25 -17
  1122. package/src/utils/security/security.ts +15 -18
  1123. package/src/utils/security/securityMonitor.test.ts +1 -1
  1124. package/src/utils/security.unit.test.ts +155 -0
  1125. package/src/utils/securityMonitor.unit.test.ts +276 -0
  1126. package/src/utils/sessionTracking.unit.test.ts +218 -0
  1127. package/src/utils/storage/config.unit.test.ts +239 -0
  1128. package/src/utils/storage/helpers.test.ts +88 -102
  1129. package/src/utils/storage/helpers.ts +173 -251
  1130. package/src/utils/storage/index.unit.test.ts +68 -0
  1131. package/src/utils/storage/types.ts +7 -0
  1132. package/src/utils/supabase/createBaseClient.test.ts +31 -14
  1133. package/src/utils/timezone/timezone.test.ts +1 -1
  1134. package/src/utils/timezone.test.ts +345 -0
  1135. package/src/utils/validation/common.test.ts +115 -0
  1136. package/src/utils/validation/csrf.test.ts +198 -0
  1137. package/src/utils/validation/csrf.ts +42 -41
  1138. package/src/utils/validation/htmlSanitization.unit.test.ts +618 -0
  1139. package/src/utils/validation/passwordSchema.test.ts +164 -0
  1140. package/src/utils/validation/schema.test.ts +127 -0
  1141. package/src/utils/validation/sqlInjectionProtection.test.ts +165 -0
  1142. package/src/utils/validation/user.test.ts +173 -0
  1143. package/src/utils/validation/validation.test.ts +197 -0
  1144. package/src/utils/validation/validationUtils.test.ts +294 -0
  1145. package/src/utils/validation.unit.test.ts +307 -0
  1146. package/src/utils/validationUtils.unit.test.ts +558 -0
  1147. package/dist/DataTable-SAXFG4XI.js +0 -13
  1148. package/dist/InactivityServiceProvider-DHryoh6K.d.ts +0 -299
  1149. package/dist/UnifiedAuthProvider-BBD2PS3Q.js +0 -7
  1150. package/dist/UnifiedAuthProvider-CiBAl9-s.d.ts +0 -151
  1151. package/dist/api-F47QJ7FX.js +0 -4
  1152. package/dist/audit-Z6ZZBWLU.js +0 -3
  1153. package/dist/auth-BZOJqrdd.d.ts +0 -49
  1154. package/dist/chunk-3GWSPISD.js +0 -61
  1155. package/dist/chunk-66R6RLUZ.js +0 -529
  1156. package/dist/chunk-7YDC7LMU.js +0 -487
  1157. package/dist/chunk-BCTXBU6U.js +0 -704
  1158. package/dist/chunk-FBZ7U3ID.js +0 -2209
  1159. package/dist/chunk-FN52B75D.js +0 -246
  1160. package/dist/chunk-JJEYZ3DX.js +0 -165
  1161. package/dist/chunk-KPYQWGFQ.js +0 -183
  1162. package/dist/chunk-KSNLMI7N.js +0 -481
  1163. package/dist/chunk-KYURMOQM.js +0 -977
  1164. package/dist/chunk-LNHFAF4X.js +0 -2279
  1165. package/dist/chunk-MPY44PWB.js +0 -8510
  1166. package/dist/chunk-NIU6DPQV.js +0 -427
  1167. package/dist/chunk-TFIPNIPE.js +0 -5379
  1168. package/dist/chunk-UZNAFKGW.js +0 -125
  1169. package/dist/chunk-W46INAVW.js +0 -1216
  1170. package/dist/chunk-X5EAU5G7.js +0 -793
  1171. package/dist/chunk-Y4PF6HIM.js +0 -2862
  1172. package/dist/database.generated-DT8JTZiP.d.ts +0 -9406
  1173. package/dist/event-WTAQuGcq.d.ts +0 -239
  1174. package/dist/file-reference-BavO2eQj.d.ts +0 -148
  1175. package/dist/functions-DH45k8ec.d.ts +0 -208
  1176. package/dist/timezone-K-ptz3HO.d.ts +0 -696
  1177. package/dist/types-BE2sEHKd.d.ts +0 -55
  1178. package/dist/types-CvOPXWWZ.d.ts +0 -111
  1179. package/dist/types-D05dCGma.d.ts +0 -521
  1180. package/dist/usePublicPageContext-vxBlEHO9.d.ts +0 -4290
  1181. package/dist/usePublicRouteParams-G3Ks53mk.d.ts +0 -877
  1182. package/docs/api/modules.md +0 -9889
  1183. package/scripts/build-docs-incremental.js +0 -179
  1184. package/scripts/eslint-audit.cjs +0 -222
  1185. package/scripts/generate-docs.js +0 -157
  1186. package/scripts/install-cursor-rules.cjs +0 -255
  1187. package/scripts/install-eslint-config.cjs +0 -349
  1188. package/scripts/setup-build-cache.js +0 -73
  1189. package/scripts/validate-pre-publish.js +0 -145
  1190. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +0 -260
  1191. package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +0 -224
  1192. package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +0 -273
  1193. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +0 -99
  1194. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +0 -446
  1195. package/src/__tests__/helpers/__tests__/timer-utils.test.ts +0 -371
  1196. package/src/__tests__/hooks/usePermissions.test.ts +0 -268
  1197. package/src/__tests__/index.test.ts +0 -532
  1198. package/src/__tests__/integration/UserProfile.test.tsx +0 -124
  1199. package/src/__tests__/public-recipe-view.test.ts +0 -228
  1200. package/src/__tests__/rbac/PagePermissionGuard.test.tsx +0 -222
  1201. package/src/__tests__/rls-policies.test.ts +0 -472
  1202. package/src/components/ContextSelector/__tests__/ContextSelector.test.tsx +0 -360
  1203. package/src/components/DataTable/__tests__/DataTable.comprehensive.test.tsx +0 -759
  1204. package/src/components/DataTable/__tests__/DataTable.default-state.test.tsx +0 -524
  1205. package/src/components/DataTable/__tests__/DataTable.export.test.tsx +0 -705
  1206. package/src/components/DataTable/__tests__/DataTable.grouping-aggregation.test.tsx +0 -658
  1207. package/src/components/DataTable/__tests__/DataTable.hooks.test.tsx +0 -192
  1208. package/src/components/DataTable/__tests__/DataTable.select-label-display.test.tsx +0 -485
  1209. package/src/components/DataTable/__tests__/DataTable.test.tsx +0 -876
  1210. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +0 -220
  1211. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +0 -970
  1212. package/src/components/DataTable/__tests__/README.md +0 -145
  1213. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +0 -788
  1214. package/src/components/DataTable/__tests__/keyboard.test.tsx +0 -756
  1215. package/src/components/DataTable/__tests__/mocks/MockRBACProvider.tsx +0 -66
  1216. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +0 -728
  1217. package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +0 -319
  1218. package/src/components/DataTable/__tests__/styles.test.ts +0 -382
  1219. package/src/components/DataTable/__tests__/test-utils/dataFactories.ts +0 -103
  1220. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +0 -388
  1221. package/src/components/DataTable/__tests__/test-utils.ts +0 -94
  1222. package/src/components/DataTable/components/AccessDeniedPage.tsx +0 -159
  1223. package/src/components/DataTable/components/ActionButtons.tsx +0 -195
  1224. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +0 -160
  1225. package/src/components/DataTable/components/ColumnFilter.tsx +0 -113
  1226. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +0 -114
  1227. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +0 -225
  1228. package/src/components/DataTable/components/DataTableLayout.tsx +0 -584
  1229. package/src/components/DataTable/components/DataTableModals.tsx +0 -333
  1230. package/src/components/DataTable/components/DataTableToolbar.tsx +0 -271
  1231. package/src/components/DataTable/components/EditFields.tsx +0 -286
  1232. package/src/components/DataTable/components/EditableRow.tsx +0 -462
  1233. package/src/components/DataTable/components/EmptyState.tsx +0 -82
  1234. package/src/components/DataTable/components/FilterRow.tsx +0 -148
  1235. package/src/components/DataTable/components/LoadingState.tsx +0 -17
  1236. package/src/components/DataTable/components/PaginationControls.tsx +0 -285
  1237. package/src/components/DataTable/components/RowComponent.tsx +0 -423
  1238. package/src/components/DataTable/components/SortIndicator.tsx +0 -50
  1239. package/src/components/DataTable/components/UnifiedTableBody.tsx +0 -395
  1240. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +0 -245
  1241. package/src/components/DataTable/components/__tests__/ActionButtons.test.tsx +0 -919
  1242. package/src/components/DataTable/components/__tests__/BulkOperationsDropdown.test.tsx +0 -544
  1243. package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +0 -484
  1244. package/src/components/DataTable/components/__tests__/ColumnVisibilityDropdown.test.tsx +0 -748
  1245. package/src/components/DataTable/components/__tests__/DataTableCore.test.tsx +0 -792
  1246. package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +0 -438
  1247. package/src/components/DataTable/components/__tests__/DataTableLayout.test.tsx +0 -467
  1248. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +0 -358
  1249. package/src/components/DataTable/components/__tests__/DataTableToolbar.test.tsx +0 -629
  1250. package/src/components/DataTable/components/__tests__/EditFields.test.tsx +0 -526
  1251. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +0 -994
  1252. package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +0 -420
  1253. package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +0 -415
  1254. package/src/components/DataTable/components/__tests__/GroupingDropdown.test.tsx +0 -612
  1255. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +0 -957
  1256. package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +0 -81
  1257. package/src/components/DataTable/components/__tests__/PaginationControls.test.tsx +0 -429
  1258. package/src/components/DataTable/components/__tests__/RowComponent.test.tsx +0 -629
  1259. package/src/components/DataTable/components/__tests__/SortIndicator.test.tsx +0 -135
  1260. package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +0 -864
  1261. package/src/components/DataTable/components/__tests__/cellValueUtils.test.ts +0 -453
  1262. package/src/components/DataTable/components/cellValueUtils.ts +0 -40
  1263. package/src/components/DataTable/components/hooks/useImportModalFocus.test.ts +0 -184
  1264. package/src/components/DataTable/components/hooks/useImportModalFocus.ts +0 -53
  1265. package/src/components/DataTable/components/hooks/usePermissionTracking.test.ts +0 -381
  1266. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -122
  1267. package/src/components/DataTable/context/__tests__/DataTableContext.test.tsx +0 -328
  1268. package/src/components/DataTable/core/ActionManager.ts +0 -235
  1269. package/src/components/DataTable/core/ColumnManager.ts +0 -204
  1270. package/src/components/DataTable/core/DataManager.ts +0 -190
  1271. package/src/components/DataTable/core/LocalDataAdapter.ts +0 -274
  1272. package/src/components/DataTable/core/PluginRegistry.ts +0 -229
  1273. package/src/components/DataTable/core/StateManager.ts +0 -312
  1274. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +0 -235
  1275. package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +0 -403
  1276. package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +0 -141
  1277. package/src/components/DataTable/core/__tests__/DataManager.test.ts +0 -178
  1278. package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +0 -133
  1279. package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +0 -142
  1280. package/src/components/DataTable/core/__tests__/StateManager.test.ts +0 -158
  1281. package/src/components/DataTable/core/interfaces.ts +0 -338
  1282. package/src/components/DataTable/hooks/__tests__/useColumnOrderPersistence.test.ts +0 -516
  1283. package/src/components/DataTable/hooks/__tests__/useColumnVisibilityPersistence.test.ts +0 -256
  1284. package/src/components/DataTable/hooks/__tests__/useDataTableConfiguration.test.ts +0 -297
  1285. package/src/components/DataTable/hooks/__tests__/useDataTableDataPipeline.test.ts +0 -270
  1286. package/src/components/DataTable/hooks/__tests__/useDataTablePermissions.test.ts +0 -280
  1287. package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +0 -691
  1288. package/src/components/DataTable/hooks/__tests__/useEffectiveColumnOrder.test.ts +0 -183
  1289. package/src/components/DataTable/hooks/__tests__/useHierarchicalState.test.ts +0 -294
  1290. package/src/components/DataTable/hooks/__tests__/useKeyboardNavigation.test.ts +0 -787
  1291. package/src/components/DataTable/hooks/__tests__/useServerSideDataEffect.test.ts +0 -258
  1292. package/src/components/DataTable/hooks/__tests__/useTableColumns.test.ts +0 -499
  1293. package/src/components/DataTable/hooks/__tests__/useTableHandlers.test.ts +0 -440
  1294. package/src/components/DataTable/types.ts +0 -764
  1295. package/src/components/DataTable/utils/__tests__/a11yUtils.test.ts +0 -548
  1296. package/src/components/DataTable/utils/__tests__/aggregationUtils.test.ts +0 -288
  1297. package/src/components/DataTable/utils/__tests__/columnUtils.test.ts +0 -94
  1298. package/src/components/DataTable/utils/__tests__/errorHandling.test.ts +0 -209
  1299. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +0 -954
  1300. package/src/components/DataTable/utils/__tests__/flexibleImport.test.ts +0 -573
  1301. package/src/components/DataTable/utils/__tests__/hierarchicalSorting.test.ts +0 -235
  1302. package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +0 -586
  1303. package/src/components/DataTable/utils/__tests__/paginationUtils.test.ts +0 -593
  1304. package/src/components/DataTable/utils/__tests__/performanceUtils.test.ts +0 -470
  1305. package/src/components/DataTable/utils/__tests__/rowUtils.test.ts +0 -235
  1306. package/src/components/DataTable/utils/__tests__/selectFieldUtils.test.ts +0 -208
  1307. package/src/components/NavigationMenu/__tests__/useNavigationFiltering.test.ts +0 -1934
  1308. package/src/components/Select/__tests__/context.test.tsx +0 -56
  1309. package/src/components/Select/hooks/__tests__/useSelectEvents.test.ts +0 -279
  1310. package/src/components/Select/hooks/__tests__/useSelectSearch.test.tsx +0 -295
  1311. package/src/components/Select/hooks/__tests__/useSelectState.test.ts +0 -254
  1312. package/src/components/Select/hooks/useSelectEvents.ts +0 -87
  1313. package/src/components/Select/hooks/useSelectSearch.ts +0 -91
  1314. package/src/components/Select/hooks/useSelectState.ts +0 -104
  1315. package/src/components/Select/utils/__tests__/text.test.tsx +0 -104
  1316. package/src/components/Select/utils/text.ts +0 -26
  1317. package/src/components/__tests__/index.test.ts +0 -346
  1318. package/src/constants/__tests__/performance.test.ts +0 -91
  1319. package/src/hooks/__tests__/ServiceHooks.test.tsx +0 -725
  1320. package/src/hooks/__tests__/hooks.integration.test.tsx +0 -608
  1321. package/src/hooks/__tests__/index.unit.test.ts +0 -220
  1322. package/src/hooks/__tests__/useAppConfig.unit.test.ts +0 -406
  1323. package/src/hooks/__tests__/useComponentPerformance.unit.test.tsx +0 -328
  1324. package/src/hooks/__tests__/useDataTablePerformance.unit.test.ts +0 -720
  1325. package/src/hooks/__tests__/useDataTableState.test.ts +0 -170
  1326. package/src/hooks/__tests__/useDebounce.unit.test.ts +0 -157
  1327. package/src/hooks/__tests__/useEvents.unit.test.ts +0 -227
  1328. package/src/hooks/__tests__/useFileDisplay.test.ts +0 -540
  1329. package/src/hooks/__tests__/useFileDisplay.unit.test.ts +0 -1109
  1330. package/src/hooks/__tests__/useFileUrl.unit.test.ts +0 -696
  1331. package/src/hooks/__tests__/useFileUrlCache.test.ts +0 -319
  1332. package/src/hooks/__tests__/useFocusManagement.unit.test.ts +0 -604
  1333. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +0 -613
  1334. package/src/hooks/__tests__/useFormDialog.test.ts +0 -307
  1335. package/src/hooks/__tests__/useInactivityTracker.unit.test.ts +0 -446
  1336. package/src/hooks/__tests__/useIsMobile.unit.test.ts +0 -317
  1337. package/src/hooks/__tests__/useKeyboardShortcuts.unit.test.ts +0 -907
  1338. package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +0 -293
  1339. package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +0 -961
  1340. package/src/hooks/__tests__/useOrganisations.unit.test.ts +0 -369
  1341. package/src/hooks/__tests__/usePerformanceMonitor.unit.test.ts +0 -694
  1342. package/src/hooks/__tests__/usePermissionCache.test.ts +0 -506
  1343. package/src/hooks/__tests__/usePreventTabReload.test.ts +0 -307
  1344. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +0 -794
  1345. package/src/hooks/__tests__/usePublicEvent.test.ts +0 -670
  1346. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +0 -638
  1347. package/src/hooks/__tests__/usePublicFileDisplay.test.ts +0 -948
  1348. package/src/hooks/__tests__/usePublicRouteParams.unit.test.ts +0 -442
  1349. package/src/hooks/__tests__/useQueryCache.test.ts +0 -391
  1350. package/src/hooks/__tests__/useRBAC.unit.test.ts +0 -236
  1351. package/src/hooks/__tests__/useSessionDraft.test.ts +0 -556
  1352. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +0 -390
  1353. package/src/hooks/__tests__/useStorage.unit.test.ts +0 -721
  1354. package/src/hooks/__tests__/useToast.test.ts +0 -413
  1355. package/src/hooks/__tests__/useToast.unit.test.tsx +0 -481
  1356. package/src/hooks/__tests__/useZodForm.unit.test.tsx +0 -191
  1357. package/src/hooks/public/usePublicFileDisplay.test.ts +0 -723
  1358. package/src/hooks/public/usePublicFileDisplay.ts +0 -534
  1359. package/src/hooks/useFileDisplay.ts +0 -748
  1360. package/src/icons/__tests__/index.test.ts +0 -133
  1361. package/src/providers/OrganisationProvider.test.tsx +0 -40
  1362. package/src/providers/OrganisationProvider.tsx +0 -92
  1363. package/src/providers/__tests__/AuthProvider.test.tsx +0 -218
  1364. package/src/providers/__tests__/EventProvider.test.tsx +0 -487
  1365. package/src/providers/__tests__/InactivityProvider.test-helper.tsx +0 -65
  1366. package/src/providers/__tests__/InactivityProvider.test.tsx +0 -428
  1367. package/src/providers/__tests__/OrganisationProvider.test.tsx +0 -616
  1368. package/src/providers/__tests__/OrganisationProvider.wrapper.test.tsx +0 -591
  1369. package/src/providers/__tests__/ProviderLifecycle.test.tsx +0 -308
  1370. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +0 -503
  1371. package/src/providers/__tests__/index.test.ts +0 -138
  1372. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +0 -229
  1373. package/src/providers/services/__tests__/AuthServiceProvider.test.tsx +0 -638
  1374. package/src/providers/services/__tests__/EventServiceProvider.test.tsx +0 -839
  1375. package/src/providers/services/__tests__/InactivityServiceProvider.test.tsx +0 -662
  1376. package/src/providers/services/__tests__/OrganisationServiceProvider.test.tsx +0 -440
  1377. package/src/providers/services/__tests__/UnifiedAuthProvider.advanced.test.tsx +0 -435
  1378. package/src/providers/services/__tests__/UnifiedAuthProvider.appId.test.tsx +0 -408
  1379. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +0 -301
  1380. package/src/providers/services/__tests__/contexts.test.tsx +0 -281
  1381. package/src/providers/services/__tests__/useUnifiedAuth.test.tsx +0 -251
  1382. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +0 -429
  1383. package/src/rbac/__tests__/audit-batched.test.ts +0 -550
  1384. package/src/rbac/__tests__/auth-rbac-security.integration.test.tsx +0 -300
  1385. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +0 -517
  1386. package/src/rbac/__tests__/cache-invalidation.test.ts +0 -393
  1387. package/src/rbac/__tests__/engine.comprehensive.test.ts +0 -808
  1388. package/src/rbac/__tests__/performance.test.ts +0 -451
  1389. package/src/rbac/__tests__/rbac-core.test.tsx +0 -276
  1390. package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +0 -387
  1391. package/src/rbac/__tests__/rbac-engine-simplified.test.ts +0 -252
  1392. package/src/rbac/__tests__/rbac-functions.test.ts +0 -646
  1393. package/src/rbac/__tests__/rbac-role-isolation.test.ts +0 -456
  1394. package/src/rbac/__tests__/scenarios.user-role.test.tsx +0 -271
  1395. package/src/rbac/components/__tests__/AccessDenied.test.tsx +0 -324
  1396. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +0 -1146
  1397. package/src/rbac/components/__tests__/PagePermissionGuard.performance.test.tsx +0 -231
  1398. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +0 -243
  1399. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +0 -1430
  1400. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +0 -185
  1401. package/src/rbac/hooks/__tests__/usePermissions.integration.test.ts +0 -427
  1402. package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +0 -1179
  1403. package/src/rbac/hooks/permissions/__tests__/useAccessLevel.test.ts +0 -622
  1404. package/src/rbac/hooks/permissions/__tests__/useCan.test.ts +0 -798
  1405. package/src/rbac/hooks/permissions/__tests__/useMultiplePermissions.test.ts +0 -843
  1406. package/src/rbac/hooks/permissions/__tests__/usePermissions.test.ts +0 -545
  1407. package/src/rbac/utils/__tests__/clientSecurity.test.ts +0 -192
  1408. package/src/rbac/utils/__tests__/contextValidator.test.ts +0 -126
  1409. package/src/rbac/utils/__tests__/deep-equal.test.ts +0 -76
  1410. package/src/rbac/utils/__tests__/eventContext.test.ts +0 -386
  1411. package/src/services/__tests__/AuthService.edge-cases.test.ts +0 -746
  1412. package/src/services/__tests__/AuthService.restoreSession.test.ts +0 -59
  1413. package/src/services/__tests__/AuthService.test.ts +0 -1328
  1414. package/src/services/__tests__/BaseService.edge-cases.test.ts +0 -506
  1415. package/src/services/__tests__/BaseService.test.ts +0 -363
  1416. package/src/services/__tests__/EventService.edge-cases.test.ts +0 -633
  1417. package/src/services/__tests__/EventService.eventColours.test.ts +0 -64
  1418. package/src/services/__tests__/EventService.test.ts +0 -1018
  1419. package/src/services/__tests__/InactivityService.edge-cases.test.ts +0 -492
  1420. package/src/services/__tests__/InactivityService.lifecycle.test.ts +0 -406
  1421. package/src/services/__tests__/InactivityService.test.ts +0 -654
  1422. package/src/services/__tests__/OrganisationService.edge-cases.test.ts +0 -633
  1423. package/src/services/__tests__/OrganisationService.pagination.test.ts +0 -409
  1424. package/src/services/__tests__/OrganisationService.test.ts +0 -1176
  1425. package/src/services/interfaces/__tests__/IAuthService.test.ts +0 -190
  1426. package/src/services/interfaces/__tests__/IEventService.test.ts +0 -176
  1427. package/src/services/interfaces/__tests__/IInactivityService.test.ts +0 -183
  1428. package/src/services/interfaces/__tests__/IOrganisationService.test.ts +0 -207
  1429. package/src/theming/__tests__/parseEventColours.test.ts +0 -321
  1430. package/src/theming/__tests__/runtime.test.ts +0 -504
  1431. package/src/types/__tests__/core.test.ts +0 -397
  1432. package/src/types/__tests__/database-generated.test.ts +0 -78
  1433. package/src/types/__tests__/file-reference.test.ts +0 -351
  1434. package/src/types/__tests__/guards.test.ts +0 -246
  1435. package/src/types/__tests__/index.test.ts +0 -265
  1436. package/src/types/__tests__/organisation.roles.test.ts +0 -55
  1437. package/src/types/__tests__/organisation.test.ts +0 -1133
  1438. package/src/types/__tests__/theme.test.ts +0 -830
  1439. package/src/types/__tests__/type-validation.test.ts +0 -526
  1440. package/src/types/__tests__/validation.test.ts +0 -729
  1441. package/src/utils/__tests__/appConfig.unit.test.ts +0 -55
  1442. package/src/utils/__tests__/audit.unit.test.ts +0 -69
  1443. package/src/utils/__tests__/auth-utils.unit.test.ts +0 -70
  1444. package/src/utils/__tests__/bundleAnalysis.unit.test.ts +0 -326
  1445. package/src/utils/__tests__/cn.unit.test.ts +0 -34
  1446. package/src/utils/__tests__/debugLogger.test.ts +0 -417
  1447. package/src/utils/__tests__/deviceFingerprint.unit.test.ts +0 -818
  1448. package/src/utils/__tests__/dynamicUtils.unit.test.ts +0 -331
  1449. package/src/utils/__tests__/formatDate.unit.test.ts +0 -109
  1450. package/src/utils/__tests__/formatting.unit.test.ts +0 -99
  1451. package/src/utils/__tests__/index.unit.test.ts +0 -251
  1452. package/src/utils/__tests__/lazyLoad.unit.test.tsx +0 -320
  1453. package/src/utils/__tests__/logger.unit.test.ts +0 -398
  1454. package/src/utils/__tests__/organisationContext.unit.test.ts +0 -191
  1455. package/src/utils/__tests__/performanceBenchmark.test.ts +0 -174
  1456. package/src/utils/__tests__/performanceBudgets.unit.test.ts +0 -288
  1457. package/src/utils/__tests__/permissionTypes.unit.test.ts +0 -250
  1458. package/src/utils/__tests__/permissionUtils.unit.test.ts +0 -362
  1459. package/src/utils/__tests__/request-deduplication.test.ts +0 -349
  1460. package/src/utils/__tests__/sanitization.unit.test.ts +0 -346
  1461. package/src/utils/__tests__/schemaUtils.unit.test.ts +0 -441
  1462. package/src/utils/__tests__/secureDataAccess.unit.test.ts +0 -334
  1463. package/src/utils/__tests__/secureErrors.unit.test.ts +0 -390
  1464. package/src/utils/__tests__/secureStorage.unit.test.ts +0 -289
  1465. package/src/utils/__tests__/security.unit.test.ts +0 -149
  1466. package/src/utils/__tests__/securityMonitor.unit.test.ts +0 -276
  1467. package/src/utils/__tests__/sessionTracking.unit.test.ts +0 -218
  1468. package/src/utils/__tests__/timezone.test.ts +0 -345
  1469. package/src/utils/__tests__/validation.unit.test.ts +0 -307
  1470. package/src/utils/__tests__/validationUtils.unit.test.ts +0 -555
  1471. package/src/utils/core/__tests__/cn.test.ts +0 -66
  1472. package/src/utils/core/__tests__/debugLogger.test.ts +0 -113
  1473. package/src/utils/core/__tests__/logger.test.ts +0 -217
  1474. package/src/utils/dynamic/__tests__/dynamicUtils.test.ts +0 -185
  1475. package/src/utils/dynamic/__tests__/lazyLoad.test.tsx +0 -156
  1476. package/src/utils/file-reference/__tests__/file-reference.test.ts +0 -1313
  1477. package/src/utils/performance/__tests__/bundleAnalysis.test.ts +0 -148
  1478. package/src/utils/performance/__tests__/performanceBenchmark.test.ts +0 -251
  1479. package/src/utils/performance/__tests__/performanceBudgets.test.ts +0 -241
  1480. package/src/utils/permissions/__tests__/permissionTypes.test.ts +0 -149
  1481. package/src/utils/persistence/__tests__/keyDerivation.test.ts +0 -306
  1482. package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +0 -271
  1483. package/src/utils/storage/__tests__/config.unit.test.ts +0 -239
  1484. package/src/utils/storage/__tests__/index.unit.test.ts +0 -68
  1485. package/src/utils/validation/__tests__/common.test.ts +0 -115
  1486. package/src/utils/validation/__tests__/csrf.test.ts +0 -170
  1487. package/src/utils/validation/__tests__/htmlSanitization.unit.test.ts +0 -618
  1488. package/src/utils/validation/__tests__/passwordSchema.test.ts +0 -164
  1489. package/src/utils/validation/__tests__/schema.test.ts +0 -127
  1490. package/src/utils/validation/__tests__/sqlInjectionProtection.test.ts +0 -165
  1491. package/src/utils/validation/__tests__/user.test.ts +0 -173
  1492. package/src/utils/validation/__tests__/validation.test.ts +0 -197
  1493. package/src/utils/validation/__tests__/validationUtils.test.ts +0 -294
  1494. /package/src/components/DataTable/{components/__tests__ → ui}/COVERAGE_NOTE.md +0 -0
  1495. /package/src/components/DataTable/utils/{__tests__/COVERAGE_NOTE.md → COVERAGE_NOTE.md} +0 -0
  1496. /package/src/hooks/{__tests__/useApiFetch.unit.test.ts → useApiFetch.unit.test.ts} +0 -0
  1497. /package/src/providers/{__tests__/README.md → README.md} +0 -0
  1498. /package/src/rbac/{__tests__/index.test.ts → index.test.ts} +0 -0
  1499. /package/src/rbac/{__tests__/rbac-integration.test.ts → rbac-integration.test.ts} +0 -0
  1500. /package/src/types/{__tests__/README.md → README.md} +0 -0
@@ -0,0 +1,1834 @@
1
+ /**
2
+ * @file Import Modal Component Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module Components/DataTable/Components
5
+ * @since 0.4.0
6
+ *
7
+ * Comprehensive test suite for ImportModal component following testing guidelines.
8
+ * Tests cover all major functionality, edge cases, and user interactions.
9
+ */
10
+
11
+ import React from 'react';
12
+ import { render, screen, waitFor, cleanup } from '@testing-library/react';
13
+ import userEvent from '@testing-library/user-event';
14
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
15
+ import { ImportModal } from './ImportModal';
16
+
17
+ // Helper function to wait for dialog to be accessible
18
+ // Native dialog elements are only accessible after showModal() completes
19
+ // In test environments, we use querySelector as fallback since getByRole may not work
20
+ // Note: In test environments (jsdom), dialog.open may not be set even when dialog is rendered
21
+ // Also note: Dialog uses requestAnimationFrame before showModal(), so we need to wait for content
22
+ const waitForDialog = async (): Promise<HTMLElement> => {
23
+ return await waitFor(
24
+ () => {
25
+ // Try getByRole first (works in browsers with full dialog support)
26
+ try {
27
+ const dialog = screen.getByRole('dialog');
28
+ expect(dialog).toBeInTheDocument();
29
+ return dialog;
30
+ } catch (_e) {
31
+ // Fallback: use querySelector for test environments that don't fully support dialog accessibility
32
+ const dialog = document.querySelector('dialog[role="dialog"]') as HTMLDialogElement;
33
+ if (!dialog) {
34
+ throw new Error('Dialog not found in DOM');
35
+ }
36
+ // In test environments, dialog.open may not be set even when dialog is rendered
37
+ // Just check that dialog exists in DOM - that's sufficient for testing
38
+ return dialog;
39
+ }
40
+ },
41
+ { timeout: 5000 }
42
+ );
43
+ };
44
+
45
+ // Helper function to find buttons in dialogs (more reliable than getByRole in test environments)
46
+ const findButtonByText = (text: string | RegExp): HTMLButtonElement | null => {
47
+ // Try getByRole first
48
+ try {
49
+ const button = screen.getByRole('button', { name: text });
50
+ return button as HTMLButtonElement;
51
+ } catch (_e) {
52
+ // Fallback: search all buttons by text content
53
+ const buttons = Array.from(document.querySelectorAll('button'));
54
+ const regex = typeof text === 'string' ? new RegExp(text, 'i') : text;
55
+ return buttons.find(btn => regex.test(btn.textContent || '')) as HTMLButtonElement || null;
56
+ }
57
+ };
58
+
59
+ // Helper function to ensure fresh modal state (clears any persistent state from previous tests)
60
+ // This works by rendering a modal, opening it, clicking close button to trigger handleClose,
61
+ // then closing it programmatically to ensure cleanup
62
+ const ensureFreshModalState = async () => {
63
+ const onClose = vi.fn();
64
+ const user = userEvent.setup();
65
+
66
+ // Render a modal and open it
67
+ const { rerender, unmount } = render(
68
+ <ImportModal
69
+ isOpen={true}
70
+ onClose={onClose}
71
+ onImport={vi.fn()}
72
+ />
73
+ );
74
+
75
+ // Wait for modal to initialize
76
+ try {
77
+ await waitFor(() => {
78
+ const dialog = document.querySelector('dialog[role="dialog"]');
79
+ if (!dialog) throw new Error('Dialog not found');
80
+ }, { timeout: 1000 });
81
+ } catch (_e) {
82
+ // If modal didn't render, that's okay, we'll still close it
83
+ }
84
+
85
+ // Check if there's a summary - if so, click close button to trigger handleClose
86
+ await waitForDialog();
87
+ const hasSummary = screen.queryByText(/import completed/i);
88
+ if (hasSummary) {
89
+ const closeButton = findButtonByText(/^close$/i) || findButtonByText(/cancel/i);
90
+ if (closeButton) {
91
+ await user.click(closeButton);
92
+ await new Promise(resolve => setTimeout(resolve, 100));
93
+ }
94
+ }
95
+
96
+ // Close the modal by setting isOpen to false
97
+ // This triggers the useEffect that clears persistent storage when there's no importSummary
98
+ rerender(
99
+ <ImportModal
100
+ isOpen={false}
101
+ onClose={onClose}
102
+ onImport={vi.fn() as (data: Array<Record<string, unknown>>) => void | Promise<void>}
103
+ />
104
+ );
105
+
106
+ // Wait for React to process the state change and run cleanup effects
107
+ // The component's useEffect (lines 152-173) clears persistent storage when isOpen becomes false
108
+ // We need to wait for this effect to complete
109
+ await new Promise(resolve => setTimeout(resolve, 500));
110
+
111
+ // Unmount to ensure cleanup
112
+ unmount();
113
+ cleanup();
114
+
115
+ // Additional wait to ensure module-level state is cleared
116
+ await new Promise(resolve => setTimeout(resolve, 200));
117
+ };
118
+
119
+ // Helper function to wait for file input to be available
120
+ // This function handles the case where persistent state might have leaked from previous tests
121
+ const waitForFileInput = async (
122
+ rerender?: (element: React.ReactElement) => void,
123
+ props?: { isOpen?: boolean; onClose: () => void; onImport: (data: Array<Record<string, unknown>>) => void | Promise<void>; config?: any }
124
+ ): Promise<HTMLInputElement> => {
125
+ // First wait for dialog to be accessible
126
+ await waitForDialog();
127
+
128
+ // Then wait for dialog content to be rendered (check for title)
129
+ await waitFor(() => {
130
+ try {
131
+ expect(screen.getByText('Import Data')).toBeInTheDocument();
132
+ } catch (_e) {
133
+ // Fallback: check if any element with "Import Data" exists
134
+ const elements = screen.getAllByText('Import Data');
135
+ expect(elements.length).toBeGreaterThan(0);
136
+ }
137
+ }, { timeout: 5000 });
138
+
139
+ // Check if a file is already selected OR if there's a summary (from persistent state leakage)
140
+ const selectedFileText = screen.queryByText(/selected:/i);
141
+ const hasSummary = screen.queryByText(/import completed/i);
142
+
143
+ if ((selectedFileText || hasSummary) && rerender) {
144
+ // Persistent state leaked from a previous test - clear it by closing and reopening the modal
145
+ const closeProps = props || getBaseProps();
146
+
147
+ // Strategy: Always click cancel/close button to trigger handleClose which clears everything
148
+ // This is more reliable than relying on useEffect cleanup
149
+ const closeButton = findButtonByText(/^close$/i) || findButtonByText(/cancel/i);
150
+ if (closeButton) {
151
+ const user = userEvent.setup();
152
+ await user.click(closeButton);
153
+ // Wait for handleClose to execute (clears all persistent storage)
154
+ await new Promise(resolve => setTimeout(resolve, 150));
155
+ }
156
+
157
+ // Close the modal programmatically (onClose is just a mock, so we need to set isOpen=false)
158
+ rerender(<ImportModal {...closeProps} isOpen={false} />);
159
+
160
+ // Wait for modal to close
161
+ await waitFor(() => {
162
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
163
+ }, { timeout: 2000 });
164
+
165
+ // Wait for cleanup effect to run (clears persistent storage)
166
+ // The useEffect at line 152-173 clears persistentFile when isOpen=false and !importSummary
167
+ // Since handleClose already cleared persistentSummary, the cleanup should run
168
+ await new Promise(resolve => setTimeout(resolve, 500));
169
+
170
+ // Reopen the modal with fresh state
171
+ const reopenProps = props || getBaseProps();
172
+ rerender(<ImportModal {...reopenProps} isOpen={true} />);
173
+
174
+ // Wait for dialog to be accessible again
175
+ await waitForDialog();
176
+
177
+ // Wait for dialog content to be rendered
178
+ await waitFor(() => {
179
+ expect(screen.getByText('Import Data')).toBeInTheDocument();
180
+ }, { timeout: 5000 });
181
+
182
+ // Wait for any state restoration to complete (useEffect at line 134-149)
183
+ // But since we cleared persistent storage, nothing should be restored
184
+ await new Promise(resolve => setTimeout(resolve, 300));
185
+
186
+ // Verify state was cleared - check multiple times
187
+ for (let i = 0; i < 5; i++) {
188
+ const stillSelected = screen.queryByText(/selected:/i);
189
+ const stillHasSummary = screen.queryByText(/import completed/i);
190
+ if (!stillSelected && !stillHasSummary) {
191
+ break; // State is cleared
192
+ }
193
+ if (i === 4) {
194
+ // Last attempt - if still not cleared, we have a problem
195
+ throw new Error('Persistent state was not cleared after close/reopen. Still seeing selected file or summary.');
196
+ }
197
+ // Wait a bit more and check again
198
+ await new Promise(resolve => setTimeout(resolve, 200));
199
+ // Re-render to force state refresh
200
+ rerender(<ImportModal {...reopenProps} isOpen={true} />);
201
+ await waitForDialog();
202
+ await new Promise(resolve => setTimeout(resolve, 200));
203
+ }
204
+ } else if (selectedFileText || hasSummary) {
205
+ // Can't clear it without rerender function
206
+ throw new Error('File input not available because a file is already selected or summary exists. The test needs to pass rerender (and optionally props) to waitForFileInput to clear persistent state.');
207
+ }
208
+
209
+ // Wait for file input to be in the DOM (it's only rendered when !file)
210
+ // After clearing state, wait a bit more to ensure component is fully initialized
211
+ if ((selectedFileText || hasSummary) && rerender) {
212
+ await new Promise(resolve => setTimeout(resolve, 200));
213
+ }
214
+
215
+ return await waitFor(() => {
216
+ const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
217
+ if (!fileInput) {
218
+ // Double-check if file is still selected
219
+ const stillSelected = screen.queryByText(/selected:/i);
220
+ if (stillSelected) {
221
+ throw new Error('File input not found because a file is already selected. Persistent state may not have been cleared.');
222
+ }
223
+ throw new Error('File input not found in DOM. The dialog may not be fully rendered yet.');
224
+ }
225
+ // Ensure file input is enabled and ready
226
+ if (fileInput.disabled) {
227
+ throw new Error('File input is disabled. Component may not be fully initialized.');
228
+ }
229
+ return fileInput;
230
+ }, { timeout: 5000 });
231
+ };
232
+
233
+ // Helper function to upload a file and wait for preview to appear
234
+ const uploadFileAndWaitForPreview = async (user: ReturnType<typeof userEvent.setup>, file: File) => {
235
+ // Wait for dialog and file input
236
+ const fileInput = await waitForFileInput();
237
+
238
+ // Upload the file
239
+ await user.upload(fileInput, file);
240
+
241
+ // Wait for preview table to appear (file processing is async)
242
+ // Try multiple ways to find the table since queryByRole may not work in all test environments
243
+ await waitFor(() => {
244
+ const table = screen.queryByRole('table') ||
245
+ document.querySelector('table') ||
246
+ document.querySelector('table.min-w-full');
247
+ expect(table).toBeInTheDocument();
248
+ }, { timeout: 10000 });
249
+ };
250
+
251
+ // Mock Button component (Dialog uses IconButton from same module - must export both)
252
+ vi.mock('../../Button/Button', () => ({
253
+ Button: ({ children, onClick, variant, size, disabled, className }: any) => (
254
+ <button
255
+ onClick={onClick}
256
+ disabled={disabled}
257
+ data-variant={variant}
258
+ data-size={size}
259
+ className={className}
260
+ >
261
+ {children}
262
+ </button>
263
+ ),
264
+ IconButton: React.forwardRef(({ onClick, icon, 'aria-label': ariaLabel, className, ...props }: any, ref: any) => (
265
+ <button ref={ref} onClick={onClick} aria-label={ariaLabel} className={className} {...props}>
266
+ {icon}
267
+ </button>
268
+ )),
269
+ }));
270
+
271
+ // Mock Input component
272
+ vi.mock('../../Input/Input', () => ({
273
+ Input: React.forwardRef(({ type, accept, onChange, className, ...props }: any, ref: any) => (
274
+ <input
275
+ ref={ref}
276
+ type={type}
277
+ accept={accept}
278
+ onChange={onChange}
279
+ className={className}
280
+ {...props}
281
+ />
282
+ )),
283
+ }));
284
+
285
+ // Mock lucide-react icons
286
+ vi.mock('lucide-react', () => ({
287
+ Upload: ({ className }: { className?: string }) => (
288
+ <span data-testid="upload-icon" className={className}>Upload</span>
289
+ ),
290
+ FileText: ({ className }: { className?: string }) => (
291
+ <span data-testid="file-text-icon" className={className}>File</span>
292
+ ),
293
+ AlertCircle: ({ className }: { className?: string }) => (
294
+ <span data-testid="alert-circle-icon" className={className}>Alert</span>
295
+ ),
296
+ CheckCircle2: ({ className }: { className?: string }) => (
297
+ <span data-testid="check-circle-icon" className={className}>Check</span>
298
+ ),
299
+ X: ({ className }: { className?: string }) => (
300
+ <span data-testid="x-icon" className={className}>X</span>
301
+ ),
302
+ }));
303
+
304
+ // Mock logger
305
+ vi.mock('../../../utils/core/logger', () => ({
306
+ createLogger: () => ({
307
+ debug: vi.fn(),
308
+ info: vi.fn(),
309
+ warn: vi.fn(),
310
+ error: vi.fn(),
311
+ }),
312
+ }));
313
+
314
+ // Base props for tests - defined outside describe so helpers can access it
315
+ const getBaseProps = () => ({
316
+ isOpen: true,
317
+ onClose: vi.fn(),
318
+ onImport: vi.fn(),
319
+ });
320
+
321
+ describe('[component] ImportModal', () => {
322
+ const baseProps = getBaseProps();
323
+
324
+ const createCSVFile = (content: string, filename = 'test.csv'): File => {
325
+ const blob = new Blob([content], { type: 'text/csv' });
326
+ const file = new File([blob], filename, { type: 'text/csv' });
327
+ // Store content for File.text() mock to access
328
+ (file as any)._content = content;
329
+ return file;
330
+ };
331
+
332
+ beforeEach(async () => {
333
+ vi.clearAllMocks();
334
+
335
+ // Clear any rendered components from previous tests
336
+ cleanup();
337
+
338
+ // Mock showModal for dialog elements (needed for test environments)
339
+ // MUST be set up before any components are rendered
340
+ HTMLDialogElement.prototype.showModal = vi.fn(function(this: HTMLDialogElement) {
341
+ this.setAttribute('open', '');
342
+ this.dispatchEvent(new Event('show', { bubbles: true }));
343
+ });
344
+ HTMLDialogElement.prototype.close = vi.fn(function(this: HTMLDialogElement) {
345
+ this.removeAttribute('open');
346
+ this.dispatchEvent(new Event('close', { bubbles: true }));
347
+ });
348
+
349
+ // Mock File.text() method for jsdom compatibility
350
+ // File.text() reads the file content asynchronously
351
+ // In tests, we read directly from the Blob content synchronously
352
+ if (!File.prototype.text) {
353
+ Object.defineProperty(File.prototype, 'text', {
354
+ writable: true,
355
+ configurable: true,
356
+ value: async function(this: File) {
357
+ // For test files, read from the stored content or from the Blob
358
+ const file = this as any;
359
+ if (file._content) {
360
+ // Use stored content if available
361
+ return Promise.resolve(file._content);
362
+ }
363
+ // Otherwise, try to read from Blob using FileReader
364
+ return new Promise((resolve, reject) => {
365
+ const reader = new FileReader();
366
+ reader.onload = (e) => {
367
+ resolve(e.target?.result as string);
368
+ };
369
+ reader.onerror = () => {
370
+ reject(new Error('Failed to read file'));
371
+ };
372
+ // Read the file as text
373
+ reader.readAsText(this);
374
+ });
375
+ },
376
+ });
377
+ }
378
+
379
+ // Clear ImportModal's persistent state by opening and closing a modal
380
+ // The ImportModal uses module-level persistent storage that can leak between tests
381
+ // We need to ensure it's cleared by closing a modal (which calls handleClose and clears persistent storage)
382
+ await ensureFreshModalState();
383
+ });
384
+
385
+ afterEach(() => {
386
+ // Clean up all rendered components to ensure state doesn't leak
387
+ cleanup();
388
+ vi.clearAllMocks();
389
+ });
390
+
391
+ describe('Rendering', () => {
392
+ it('returns null when modal is closed', () => {
393
+ const { container } = render(
394
+ <ImportModal {...baseProps} isOpen={false} />
395
+ );
396
+ expect(container.firstChild).toBeNull();
397
+ });
398
+
399
+ it('renders modal when open', async () => {
400
+ render(<ImportModal {...baseProps} />);
401
+
402
+ // Wait for dialog to be accessible
403
+ await waitForDialog();
404
+ // Check for content instead of testids
405
+ expect(screen.getByText('Import Data')).toBeInTheDocument();
406
+ });
407
+
408
+ it('renders default title', async () => {
409
+ render(<ImportModal {...baseProps} />);
410
+
411
+ // Wait for dialog content to be rendered (showModal is async via requestAnimationFrame)
412
+ await waitFor(() => {
413
+ // Try by role first, fallback to text content
414
+ try {
415
+ expect(screen.getByRole('heading', { name: 'Import Data' })).toBeInTheDocument();
416
+ } catch (_e) {
417
+ expect(screen.getByText('Import Data')).toBeInTheDocument();
418
+ }
419
+ }, { timeout: 5000 });
420
+ });
421
+
422
+ it('renders custom title from config', async () => {
423
+ render(
424
+ <ImportModal
425
+ {...baseProps}
426
+ config={{ title: 'Custom Import Title' }}
427
+ />
428
+ );
429
+
430
+ // Wait for dialog content to be rendered (showModal is async via requestAnimationFrame)
431
+ await waitFor(() => {
432
+ // Try by role first, fallback to text content
433
+ try {
434
+ expect(screen.getByRole('heading', { name: 'Custom Import Title' })).toBeInTheDocument();
435
+ } catch (_e) {
436
+ expect(screen.getByText('Custom Import Title')).toBeInTheDocument();
437
+ }
438
+ }, { timeout: 5000 });
439
+ });
440
+
441
+ it('renders default description', async () => {
442
+ render(<ImportModal {...baseProps} />);
443
+
444
+ // Wait for dialog to be accessible
445
+ await waitForDialog();
446
+ // Description is rendered as p in DialogHeader
447
+ expect(screen.getByText('Upload a CSV file to import multiple records at once.')).toBeInTheDocument();
448
+ });
449
+
450
+ it('renders custom description from config', async () => {
451
+ render(
452
+ <ImportModal
453
+ {...baseProps}
454
+ config={{ description: 'Custom description' }}
455
+ />
456
+ );
457
+
458
+ // Wait for dialog to be accessible
459
+ await waitForDialog();
460
+ // Description is rendered as p in DialogHeader
461
+ expect(screen.getByText('Custom description')).toBeInTheDocument();
462
+ });
463
+
464
+ it('renders file upload area', async () => {
465
+ render(<ImportModal {...baseProps} />);
466
+
467
+ // Wait for dialog title first (most reliable indicator dialog is rendered)
468
+ // Use getByRole for heading to avoid multiple matches (h2 and button both have "Import Data")
469
+ await waitFor(() => {
470
+ try {
471
+ expect(screen.getByRole('heading', { name: 'Import Data' })).toBeInTheDocument();
472
+ } catch (_e) {
473
+ // Fallback: check if any element with "Import Data" exists
474
+ const elements = screen.getAllByText('Import Data');
475
+ expect(elements.length).toBeGreaterThan(0);
476
+ }
477
+ }, { timeout: 5000 });
478
+
479
+ // Then check for upload area text and button (use querySelector as fallback for buttons in dialogs)
480
+ await waitFor(() => {
481
+ expect(screen.getByText(/choose a csv file/i)).toBeInTheDocument();
482
+ const selectFileButton = findButtonByText(/select file/i);
483
+ expect(selectFileButton).toBeInTheDocument();
484
+ }, { timeout: 5000 });
485
+ });
486
+
487
+ it('renders cancel button', async () => {
488
+ render(<ImportModal {...baseProps} />);
489
+
490
+ // Wait for dialog title first (most reliable indicator dialog is rendered)
491
+ // Use getByRole for heading to avoid multiple matches (h2 and button both have "Import Data")
492
+ await waitFor(() => {
493
+ try {
494
+ expect(screen.getByRole('heading', { name: 'Import Data' })).toBeInTheDocument();
495
+ } catch (_e) {
496
+ // Fallback: check if any element with "Import Data" exists
497
+ const elements = screen.getAllByText('Import Data');
498
+ expect(elements.length).toBeGreaterThan(0);
499
+ }
500
+ }, { timeout: 5000 });
501
+
502
+ // Then check for cancel button (use querySelector as fallback for buttons in dialogs)
503
+ await waitFor(() => {
504
+ const cancelButton = screen.queryByRole('button', { name: /cancel/i })
505
+ || Array.from(document.querySelectorAll('button')).find(btn => btn.textContent?.match(/cancel/i));
506
+ expect(cancelButton).toBeInTheDocument();
507
+ }, { timeout: 5000 });
508
+ });
509
+
510
+ it('renders import button', async () => {
511
+ render(<ImportModal {...baseProps} />);
512
+
513
+ // Wait for dialog title first (most reliable indicator dialog is rendered)
514
+ // Use getByRole for heading to avoid multiple matches (h2 and button both have "Import Data")
515
+ await waitFor(() => {
516
+ try {
517
+ expect(screen.getByRole('heading', { name: 'Import Data' })).toBeInTheDocument();
518
+ } catch (_e) {
519
+ // Fallback: check if any element with "Import Data" exists
520
+ const elements = screen.getAllByText('Import Data');
521
+ expect(elements.length).toBeGreaterThan(0);
522
+ }
523
+ }, { timeout: 5000 });
524
+
525
+ // Then check for import button (use querySelector as fallback for buttons in dialogs)
526
+ await waitFor(() => {
527
+ const importButton = screen.queryByRole('button', { name: /import/i })
528
+ || Array.from(document.querySelectorAll('button')).find(btn => btn.textContent?.match(/^import$/i));
529
+ expect(importButton).toBeInTheDocument();
530
+ }, { timeout: 5000 });
531
+ });
532
+ });
533
+
534
+ describe('File Selection', () => {
535
+ it('displays selected file name', async () => {
536
+ const user = userEvent.setup();
537
+ const csvContent = 'name,email\nJohn,john@example.com';
538
+ const file = createCSVFile(csvContent);
539
+
540
+ render(<ImportModal {...baseProps} />);
541
+
542
+ // Wait for file input to be available
543
+ const fileInput = await waitForFileInput();
544
+ await user.upload(fileInput, file);
545
+
546
+ await waitFor(() => {
547
+ // Text is split across elements with <br/>, so use getAllByText and check first match
548
+ const elements = screen.getAllByText((content, element) => {
549
+ return element?.textContent?.includes(`Selected: ${file.name}`) ?? false;
550
+ });
551
+ expect(elements.length).toBeGreaterThan(0);
552
+ });
553
+ });
554
+
555
+ it('resets file when modal closes and reopens', async () => {
556
+ const user = userEvent.setup();
557
+ const csvContent = 'name,email\nJohn,john@example.com';
558
+ const file = createCSVFile(csvContent);
559
+
560
+ const { rerender } = render(<ImportModal {...baseProps} />);
561
+
562
+ // Wait for file input to be available (pass rerender and props to handle persistent state)
563
+ const fileInput = await waitForFileInput(rerender, baseProps);
564
+ await user.upload(fileInput, file);
565
+
566
+ await waitFor(() => {
567
+ // Text is split across elements with <br/>, so use getAllByText and check first match
568
+ const elements = screen.getAllByText((content, element) => {
569
+ return element?.textContent?.includes(`Selected: ${file.name}`) ?? false;
570
+ });
571
+ expect(elements.length).toBeGreaterThan(0);
572
+ });
573
+
574
+ rerender(<ImportModal {...baseProps} isOpen={false} />);
575
+
576
+ // Wait for dialog to close
577
+ await waitFor(() => {
578
+ const dialog = document.querySelector('dialog[role="dialog"]');
579
+ expect(dialog).not.toBeInTheDocument();
580
+ });
581
+
582
+ rerender(<ImportModal {...baseProps} isOpen={true} />);
583
+
584
+ // Wait for dialog to reopen
585
+ await waitForDialog();
586
+
587
+ // File should be reset - use queryAllByText since text may be split
588
+ const elements = screen.queryAllByText((content, element) => {
589
+ return element?.textContent?.includes(`Selected: ${file.name}`) ?? false;
590
+ });
591
+ expect(elements.length).toBe(0);
592
+ });
593
+ });
594
+
595
+ describe('CSV Parsing', () => {
596
+ it('parses valid CSV and shows preview with data and row count', async () => {
597
+ const user = userEvent.setup();
598
+ const csvContent = 'name,email\nJohn,john@example.com\nJane,jane@example.com\nBob,bob@example.com';
599
+ const file = createCSVFile(csvContent);
600
+
601
+ render(<ImportModal {...baseProps} />);
602
+
603
+ await uploadFileAndWaitForPreview(user, file);
604
+
605
+ await waitFor(() => {
606
+ expect(screen.getByText(/name/i)).toBeInTheDocument();
607
+ expect(screen.getByText(/email/i)).toBeInTheDocument();
608
+ expect(screen.getByText('John')).toBeInTheDocument();
609
+ expect(screen.getByText('john@example.com')).toBeInTheDocument();
610
+ expect(screen.getByText(/total rows to import: 3/i)).toBeInTheDocument();
611
+ }, { timeout: 5000 });
612
+ });
613
+
614
+ it('handles CSV with quoted values and commas in values', async () => {
615
+ const user = userEvent.setup();
616
+ const csvContent = 'name,description\n"John Doe","Description, with comma"\n"Jane Smith","Another, description"';
617
+ const file = createCSVFile(csvContent);
618
+
619
+ render(<ImportModal {...baseProps} />);
620
+
621
+ await waitForDialog();
622
+ await waitFor(() => {
623
+ const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
624
+ expect(fileInput).toBeInTheDocument();
625
+ }, { timeout: 5000 });
626
+
627
+ const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
628
+ await user.upload(fileInput, file);
629
+
630
+ await waitFor(() => {
631
+ const table = screen.queryByRole('table') ||
632
+ document.querySelector('table') ||
633
+ document.querySelector('table.min-w-full');
634
+ expect(table).toBeInTheDocument();
635
+ }, { timeout: 10000 });
636
+
637
+ await waitFor(() => {
638
+ expect(screen.getByText('John Doe')).toBeInTheDocument();
639
+ expect(screen.getByText(/description, with comma/i)).toBeInTheDocument();
640
+ }, { timeout: 2000 });
641
+ });
642
+ });
643
+
644
+ describe('Error Handling', () => {
645
+ it('displays error for invalid CSV files (empty, only header, whitespace)', async () => {
646
+ // Clear any persistent state from previous tests first
647
+ await ensureFreshModalState();
648
+
649
+ const user = userEvent.setup();
650
+ const onClose = vi.fn();
651
+
652
+ // Test empty file
653
+ const emptyFile = createCSVFile('');
654
+ const testProps = { ...baseProps, onClose };
655
+ const { rerender } = render(<ImportModal {...testProps} />);
656
+
657
+ // Wait for file input to be available (pass rerender and props to handle persistent state)
658
+ let fileInput = await waitForFileInput(rerender, testProps);
659
+
660
+ // Ensure we have a fresh file input reference (in case it was recreated after state clear)
661
+ fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
662
+ expect(fileInput).toBeInTheDocument();
663
+ expect(fileInput.value).toBe(''); // Ensure file input is cleared
664
+
665
+ await user.upload(fileInput, emptyFile);
666
+
667
+ // When a file is selected but invalid, the error is set but file is also set
668
+ // The error should be displayed - wait for it to appear
669
+ // Note: The error might be in the CardContent section even when file is selected
670
+ await waitFor(() => {
671
+ // Check for error text or alert icon - error should be displayed somewhere
672
+ const errorText = screen.queryByText(/failed to preview file|CSV must have at least/i);
673
+ const alertIcon = screen.queryByTestId('alert-circle-icon');
674
+ if (!errorText && !alertIcon) {
675
+ throw new Error('Error message or alert icon not found after uploading invalid file');
676
+ }
677
+ }, { timeout: 3000 });
678
+
679
+ // Test CSV with only header
680
+ // Clear the file input value first to allow new file selection
681
+ fileInput.value = '';
682
+ const headerOnlyFile = createCSVFile('name,email');
683
+ await user.upload(fileInput, headerOnlyFile);
684
+
685
+ await waitFor(() => {
686
+ expect(screen.getByTestId('alert-circle-icon')).toBeInTheDocument();
687
+ }, { timeout: 3000 });
688
+
689
+ // Test CSV with only whitespace
690
+ fileInput.value = '';
691
+ const whitespaceFile = createCSVFile(' \n \n ');
692
+ await user.upload(fileInput, whitespaceFile);
693
+
694
+ await waitFor(() => {
695
+ expect(screen.getByTestId('alert-circle-icon')).toBeInTheDocument();
696
+ }, { timeout: 3000 });
697
+ });
698
+
699
+ it('clears error when new file is selected', async () => {
700
+ // Clear any persistent state from previous tests first
701
+ await ensureFreshModalState();
702
+
703
+ const user = userEvent.setup();
704
+ const onClose = vi.fn();
705
+ const invalidFile = createCSVFile('invalid');
706
+ const validFile = createCSVFile('name,email\nJohn,john@example.com');
707
+
708
+ const testProps = { ...baseProps, onClose };
709
+ const { rerender } = render(<ImportModal {...testProps} />);
710
+
711
+ // Wait for file input to be available (pass rerender and props to handle persistent state)
712
+ let fileInput = await waitForFileInput(rerender, testProps);
713
+
714
+ // Ensure we have a fresh file input reference (in case it was recreated after state clear)
715
+ fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
716
+ expect(fileInput).toBeInTheDocument();
717
+ expect(fileInput.value).toBe(''); // Ensure file input is cleared
718
+
719
+ await user.upload(fileInput, invalidFile);
720
+
721
+ await waitFor(() => {
722
+ expect(screen.getByTestId('alert-circle-icon')).toBeInTheDocument();
723
+ }, { timeout: 3000 });
724
+
725
+ // Clear the file input value first to allow new file selection
726
+ fileInput.value = '';
727
+
728
+ // Get a fresh file input reference (it might have been recreated)
729
+ await waitFor(() => {
730
+ const freshInput = document.querySelector('input[type="file"]') as HTMLInputElement;
731
+ expect(freshInput).toBeInTheDocument();
732
+ expect(freshInput.value).toBe('');
733
+ }, { timeout: 1000 });
734
+
735
+ const freshFileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
736
+ await user.upload(freshFileInput, validFile);
737
+
738
+ // Wait for error to be cleared and preview to appear
739
+ await waitFor(() => {
740
+ expect(screen.queryByTestId('alert-circle-icon')).not.toBeInTheDocument();
741
+ // Also verify that preview appears (indicating valid file was processed)
742
+ const table = screen.queryByRole('table') || document.querySelector('table');
743
+ expect(table).toBeInTheDocument();
744
+ }, { timeout: 3000 });
745
+ });
746
+ });
747
+
748
+ describe('Import Action', () => {
749
+ it('calls onImport with parsed data when import button is clicked', async () => {
750
+ const user = userEvent.setup();
751
+ const onImport = vi.fn();
752
+ const csvContent = 'name,email\nJohn,john@example.com';
753
+ const file = createCSVFile(csvContent);
754
+
755
+ render(<ImportModal {...baseProps} onImport={onImport} />);
756
+
757
+ // Wait for file input to be available
758
+ const fileInput = await waitForFileInput();
759
+ await user.upload(fileInput, file);
760
+
761
+ // Wait for preview to appear
762
+ await waitFor(() => {
763
+ const table = screen.queryByRole('table') ||
764
+ document.querySelector('table') ||
765
+ document.querySelector('table.min-w-full');
766
+ expect(table).toBeInTheDocument();
767
+ }, { timeout: 10000 });
768
+
769
+ await waitFor(() => {
770
+ expect(screen.getByText('John')).toBeInTheDocument();
771
+ }, { timeout: 2000 });
772
+
773
+ const importButton = findButtonByText(/^import$/i);
774
+ expect(importButton).toBeInTheDocument();
775
+ if (importButton) {
776
+ await user.click(importButton);
777
+ }
778
+
779
+ await waitFor(() => {
780
+ expect(onImport).toHaveBeenCalledTimes(1);
781
+ expect(onImport).toHaveBeenCalledWith(
782
+ expect.arrayContaining([
783
+ expect.objectContaining({
784
+ name: 'John',
785
+ email: 'john@example.com',
786
+ }),
787
+ ])
788
+ );
789
+ }, { timeout: 3000 });
790
+ });
791
+
792
+ it('shows app-returned ImportSummary counts in summary section', async () => {
793
+ const user = userEvent.setup();
794
+ const onImport = vi.fn().mockResolvedValue({
795
+ successCount: 2,
796
+ totalCount: 3,
797
+ failedCount: 1,
798
+ failedRows: [{ row: 3, reason: 'Invalid meal type' }],
799
+ });
800
+ const csvContent = 'name,email\nAlice,a@x.com\nBob,b@x.com\nCarol,c@x.com';
801
+ const file = createCSVFile(csvContent);
802
+
803
+ render(<ImportModal {...baseProps} onImport={onImport} />);
804
+
805
+ const fileInput = await waitForFileInput();
806
+ await user.upload(fileInput, file);
807
+
808
+ await waitFor(() => {
809
+ const table = screen.queryByRole('table') || document.querySelector('table');
810
+ expect(table).toBeInTheDocument();
811
+ }, { timeout: 10000 });
812
+
813
+ const importButton = findButtonByText(/^import$/i);
814
+ expect(importButton).toBeInTheDocument();
815
+ if (importButton) {
816
+ await user.click(importButton);
817
+ }
818
+
819
+ await waitFor(() => {
820
+ expect(screen.getByText(/2 of 3.*imported successfully/i)).toBeInTheDocument();
821
+ }, { timeout: 5000 });
822
+ await waitFor(() => {
823
+ const failedTexts = screen.getAllByText(/1.*row.*failed to import/i);
824
+ expect(failedTexts.length).toBeGreaterThanOrEqual(1);
825
+ }, { timeout: 2000 });
826
+ });
827
+
828
+ it('disables import button when no file is selected', async () => {
829
+ const { rerender } = render(<ImportModal {...baseProps} />);
830
+
831
+ // Check if there's persistent state (file selected or summary) - if so, clear it first
832
+ await waitForDialog();
833
+ const selectedFileText = screen.queryByText(/selected:/i);
834
+ const hasSummary = screen.queryByText(/import completed/i);
835
+
836
+ if (selectedFileText || hasSummary) {
837
+ // Clear persistent state by closing and reopening
838
+ if (hasSummary) {
839
+ const closeButton = findButtonByText(/^close$/i);
840
+ if (closeButton) {
841
+ const user = userEvent.setup();
842
+ await user.click(closeButton);
843
+ await new Promise(resolve => setTimeout(resolve, 100));
844
+ }
845
+ }
846
+ // Close programmatically (onClose is just a mock, so we need to set isOpen=false)
847
+ rerender(<ImportModal {...baseProps} isOpen={false} />);
848
+ await waitFor(() => {
849
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
850
+ }, { timeout: 2000 });
851
+
852
+ await new Promise(resolve => setTimeout(resolve, 500));
853
+ rerender(<ImportModal {...baseProps} isOpen={true} />);
854
+ await waitForDialog();
855
+ // Verify state was cleared
856
+ await waitFor(() => {
857
+ const stillSelected = screen.queryByText(/selected:/i);
858
+ const stillHasSummary = screen.queryByText(/import completed/i);
859
+ if (stillSelected || stillHasSummary) {
860
+ throw new Error('State was not cleared');
861
+ }
862
+ }, { timeout: 2000 });
863
+ }
864
+
865
+ // Wait for dialog title first (most reliable indicator dialog is rendered)
866
+ // Use getByRole for heading to avoid multiple matches (h2 and button both have "Import Data")
867
+ await waitFor(() => {
868
+ try {
869
+ expect(screen.getByRole('heading', { name: 'Import Data' })).toBeInTheDocument();
870
+ } catch (_e) {
871
+ // Fallback: check if any element with "Import Data" exists
872
+ const elements = screen.getAllByText('Import Data');
873
+ expect(elements.length).toBeGreaterThan(0);
874
+ }
875
+ }, { timeout: 5000 });
876
+
877
+ // Then check for import button and its disabled state
878
+ await waitFor(() => {
879
+ const importButton = findButtonByText(/^import$/i);
880
+ expect(importButton).toBeInTheDocument();
881
+ expect(importButton).toBeDisabled();
882
+ }, { timeout: 5000 });
883
+ });
884
+
885
+ it('disables import button while processing', async () => {
886
+ const user = userEvent.setup();
887
+ const onImport = vi.fn(async () => {
888
+ await new Promise<void>(resolve => setTimeout(resolve, 100));
889
+ });
890
+ const csvContent = 'name,email\nJohn,john@example.com';
891
+ const file = createCSVFile(csvContent);
892
+
893
+ const testProps = { ...baseProps, onImport };
894
+ const { rerender } = render(<ImportModal {...testProps} />);
895
+
896
+ // Wait for file input to be available (pass rerender and props to handle persistent state)
897
+ const fileInput = await waitForFileInput(rerender, testProps);
898
+ await user.upload(fileInput, file);
899
+
900
+ // Wait for preview to appear
901
+ await waitFor(() => {
902
+ const table = screen.queryByRole('table') ||
903
+ document.querySelector('table') ||
904
+ document.querySelector('table.min-w-full');
905
+ expect(table).toBeInTheDocument();
906
+ }, { timeout: 10000 });
907
+
908
+ await waitFor(() => {
909
+ expect(screen.getByText('John')).toBeInTheDocument();
910
+ }, { timeout: 2000 });
911
+
912
+ const importButton = findButtonByText(/^import$/i);
913
+ expect(importButton).toBeInTheDocument();
914
+ if (importButton) {
915
+ await user.click(importButton);
916
+ }
917
+
918
+ await waitFor(() => {
919
+ expect(importButton).toBeDisabled();
920
+ }, { timeout: 1000 });
921
+ });
922
+
923
+ it('shows processing text while importing', async () => {
924
+ const user = userEvent.setup();
925
+ const onImport = vi.fn(async () => {
926
+ await new Promise<void>(resolve => setTimeout(resolve, 100));
927
+ });
928
+ const csvContent = 'name,email\nJohn,john@example.com';
929
+ const file = createCSVFile(csvContent);
930
+
931
+ const testProps = { ...baseProps, onImport };
932
+ const { rerender } = render(<ImportModal {...testProps} />);
933
+
934
+ // Wait for file input to be available (pass rerender and props to handle persistent state)
935
+ const fileInput = await waitForFileInput(rerender, testProps);
936
+ await user.upload(fileInput, file);
937
+
938
+ // Wait for preview to appear
939
+ await waitFor(() => {
940
+ const table = screen.queryByRole('table') ||
941
+ document.querySelector('table') ||
942
+ document.querySelector('table.min-w-full');
943
+ expect(table).toBeInTheDocument();
944
+ }, { timeout: 10000 });
945
+
946
+ await waitFor(() => {
947
+ expect(screen.getByText('John')).toBeInTheDocument();
948
+ }, { timeout: 2000 });
949
+
950
+ const importButton = findButtonByText(/^import$/i);
951
+ expect(importButton).toBeInTheDocument();
952
+ if (importButton) {
953
+ await user.click(importButton);
954
+ }
955
+
956
+ // Button text changes to "Processing..." when isProcessing is true
957
+ await waitFor(() => {
958
+ const processingButton = findButtonByText(/processing/i);
959
+ expect(processingButton).toBeInTheDocument();
960
+ }, { timeout: 1000 });
961
+ });
962
+
963
+ it('calls onClose after successful import', async () => {
964
+ const user = userEvent.setup();
965
+ const onClose = vi.fn();
966
+ const csvContent = 'name,email\nJohn,john@example.com';
967
+ const file = createCSVFile(csvContent);
968
+ const onImport = vi.fn().mockResolvedValue({ successCount: 1, totalCount: 1, failedCount: 0 });
969
+
970
+ const testProps = { ...baseProps, onClose, onImport };
971
+ const { rerender } = render(<ImportModal {...testProps} />);
972
+
973
+ // Wait for file input to be available (pass rerender and props to handle persistent state)
974
+ const fileInput = await waitForFileInput(rerender, testProps);
975
+ await user.upload(fileInput, file);
976
+
977
+ // Wait for preview to appear
978
+ await waitFor(() => {
979
+ const table = screen.queryByRole('table') ||
980
+ document.querySelector('table') ||
981
+ document.querySelector('table.min-w-full');
982
+ expect(table).toBeInTheDocument();
983
+ }, { timeout: 10000 });
984
+
985
+ await waitFor(() => {
986
+ expect(screen.getByText('John')).toBeInTheDocument();
987
+ }, { timeout: 2000 });
988
+
989
+ const importButton = findButtonByText(/^import$/i);
990
+ expect(importButton).toBeInTheDocument();
991
+ if (importButton) {
992
+ await user.click(importButton);
993
+ }
994
+
995
+ // Wait for import to complete and summary to appear
996
+ await waitFor(() => {
997
+ expect(screen.getByText(/import completed successfully/i)).toBeInTheDocument();
998
+ }, { timeout: 5000 });
999
+
1000
+ // Click the Close button to trigger onClose
1001
+ const closeButton = findButtonByText(/^close$/i);
1002
+ expect(closeButton).toBeInTheDocument();
1003
+ if (closeButton) {
1004
+ await user.click(closeButton);
1005
+ }
1006
+
1007
+ // Verify onClose was called
1008
+ expect(onClose).toHaveBeenCalled();
1009
+ });
1010
+ });
1011
+
1012
+ describe('Close Action', () => {
1013
+ it('calls onClose when cancel button is clicked', async () => {
1014
+ const user = userEvent.setup();
1015
+ const onClose = vi.fn();
1016
+ const { rerender } = render(<ImportModal {...baseProps} onClose={onClose} />);
1017
+
1018
+ // Wait for dialog title first (most reliable indicator dialog is rendered)
1019
+ await waitFor(() => {
1020
+ try {
1021
+ expect(screen.getByRole('heading', { name: 'Import Data' })).toBeInTheDocument();
1022
+ } catch (_e) {
1023
+ const elements = screen.getAllByText('Import Data');
1024
+ expect(elements.length).toBeGreaterThan(0);
1025
+ }
1026
+ }, { timeout: 5000 });
1027
+
1028
+ // Check if there's persistent state (summary or selected file) - if so, clear it first
1029
+ const hasSummary = screen.queryByText(/import completed/i);
1030
+ const hasSelectedFile = screen.queryByText(/selected:/i);
1031
+ if (hasSummary || hasSelectedFile) {
1032
+ // If there's a summary, click close button to trigger handleClose
1033
+ if (hasSummary) {
1034
+ const closeButton = findButtonByText(/^close$/i);
1035
+ if (closeButton) {
1036
+ await user.click(closeButton);
1037
+ await new Promise(resolve => setTimeout(resolve, 100));
1038
+ }
1039
+ }
1040
+ // Close and reopen to clear state
1041
+ rerender(<ImportModal {...baseProps} isOpen={false} onClose={onClose} />);
1042
+ await waitFor(() => {
1043
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
1044
+ }, { timeout: 2000 });
1045
+ await new Promise(resolve => setTimeout(resolve, 500));
1046
+ // Reset the mock call count since we called onClose when clearing state
1047
+ onClose.mockClear();
1048
+ rerender(<ImportModal {...baseProps} isOpen={true} onClose={onClose} />);
1049
+ await waitForDialog();
1050
+ await waitFor(() => {
1051
+ expect(screen.getByText('Import Data')).toBeInTheDocument();
1052
+ }, { timeout: 5000 });
1053
+ // Verify state was cleared
1054
+ await waitFor(() => {
1055
+ const stillHasSummary = screen.queryByText(/import completed/i);
1056
+ const stillHasSelected = screen.queryByText(/selected:/i);
1057
+ if (stillHasSummary || stillHasSelected) {
1058
+ throw new Error('State was not cleared');
1059
+ }
1060
+ }, { timeout: 2000 });
1061
+ }
1062
+
1063
+ // Then wait for cancel button (should exist when no summary)
1064
+ await waitFor(() => {
1065
+ const cancelButton = findButtonByText(/cancel/i);
1066
+ expect(cancelButton).toBeInTheDocument();
1067
+ }, { timeout: 5000 });
1068
+
1069
+ const cancelButton = findButtonByText(/cancel/i);
1070
+ expect(cancelButton).toBeInTheDocument();
1071
+ if (cancelButton) {
1072
+ await user.click(cancelButton);
1073
+ }
1074
+
1075
+ expect(onClose).toHaveBeenCalledTimes(1);
1076
+ });
1077
+
1078
+ it('resets state when modal closes', async () => {
1079
+ const user = userEvent.setup();
1080
+ const csvContent = 'name,email\nJohn,john@example.com';
1081
+ const file = createCSVFile(csvContent);
1082
+
1083
+ const { rerender } = render(<ImportModal {...baseProps} />);
1084
+
1085
+ // Wait for file input to be available (pass rerender to handle persistent state)
1086
+ const fileInput = await waitForFileInput(rerender, baseProps);
1087
+ await user.upload(fileInput, file);
1088
+
1089
+ // Wait for preview to appear
1090
+ await waitFor(() => {
1091
+ const table = screen.queryByRole('table') ||
1092
+ document.querySelector('table') ||
1093
+ document.querySelector('table.min-w-full');
1094
+ expect(table).toBeInTheDocument();
1095
+ }, { timeout: 10000 });
1096
+
1097
+ await waitFor(() => {
1098
+ expect(screen.getByText('John')).toBeInTheDocument();
1099
+ }, { timeout: 2000 });
1100
+
1101
+ rerender(<ImportModal {...baseProps} isOpen={false} />);
1102
+ rerender(<ImportModal {...baseProps} isOpen={true} />);
1103
+
1104
+ // State should be reset - wait for modal to reopen
1105
+ await waitFor(() => {
1106
+ expect(screen.queryByText('John')).not.toBeInTheDocument();
1107
+ }, { timeout: 1000 });
1108
+ });
1109
+ });
1110
+
1111
+ describe('Custom Configuration', () => {
1112
+ it('uses custom button texts from config', async () => {
1113
+ const testProps = {
1114
+ ...baseProps,
1115
+ config: {
1116
+ selectFileButtonText: 'Browse Files',
1117
+ importButtonText: 'Import Data',
1118
+ cancelButtonText: 'Close',
1119
+ }
1120
+ };
1121
+ const { rerender } = render(<ImportModal {...testProps} />);
1122
+
1123
+ // Wait for dialog title first
1124
+ await waitFor(() => {
1125
+ try {
1126
+ expect(screen.getByRole('heading', { name: 'Import Data' })).toBeInTheDocument();
1127
+ } catch (_e) {
1128
+ const elements = screen.getAllByText('Import Data');
1129
+ expect(elements.length).toBeGreaterThan(0);
1130
+ }
1131
+ }, { timeout: 5000 });
1132
+
1133
+ // Check if there's a summary - if so, clear it first
1134
+ const hasSummary = screen.queryByText(/import completed/i);
1135
+ if (hasSummary) {
1136
+ rerender(
1137
+ <ImportModal
1138
+ {...baseProps}
1139
+ isOpen={false}
1140
+ config={{
1141
+ selectFileButtonText: 'Browse Files',
1142
+ importButtonText: 'Import Data',
1143
+ cancelButtonText: 'Close',
1144
+ }}
1145
+ />
1146
+ );
1147
+ await waitFor(() => {
1148
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
1149
+ }, { timeout: 2000 });
1150
+ await new Promise(resolve => setTimeout(resolve, 300));
1151
+ rerender(
1152
+ <ImportModal
1153
+ {...baseProps}
1154
+ isOpen={true}
1155
+ config={{
1156
+ selectFileButtonText: 'Browse Files',
1157
+ importButtonText: 'Import Data',
1158
+ cancelButtonText: 'Close',
1159
+ }}
1160
+ />
1161
+ );
1162
+ await waitForDialog();
1163
+ await waitFor(() => {
1164
+ expect(screen.getByText('Import Data')).toBeInTheDocument();
1165
+ }, { timeout: 5000 });
1166
+ }
1167
+
1168
+ // Then wait for buttons (should exist when no summary)
1169
+ await waitFor(() => {
1170
+ const browseButton = findButtonByText(/browse files/i);
1171
+ expect(browseButton).toBeInTheDocument();
1172
+ }, { timeout: 5000 });
1173
+
1174
+ await waitFor(() => {
1175
+ const importDataButton = findButtonByText(/import data/i);
1176
+ expect(importDataButton).toBeInTheDocument();
1177
+ }, { timeout: 5000 });
1178
+ // Dialog has a close button too, so find the Cancel one by text
1179
+ const cancelButton = findButtonByText(/^close$/i);
1180
+ expect(cancelButton).toBeInTheDocument();
1181
+ });
1182
+
1183
+ it('uses custom preview header text from config', async () => {
1184
+ const user = userEvent.setup();
1185
+ const csvContent = 'name,email\nJohn,john@example.com';
1186
+ const file = createCSVFile(csvContent);
1187
+
1188
+ const testProps = { ...baseProps, config: { previewHeaderText: 'Data Preview' } };
1189
+ const { rerender } = render(<ImportModal {...testProps} />);
1190
+
1191
+ // Wait for file input to be available (pass rerender and props to handle persistent state)
1192
+ const fileInput = await waitForFileInput(rerender, testProps);
1193
+ await user.upload(fileInput, file);
1194
+
1195
+ // Wait for preview to appear
1196
+ await waitFor(() => {
1197
+ expect(screen.getByText('Data Preview')).toBeInTheDocument();
1198
+ }, { timeout: 5000 });
1199
+ });
1200
+
1201
+ it('uses custom total rows text from config', async () => {
1202
+ const user = userEvent.setup();
1203
+ const csvContent = 'name,email\nJohn,john@example.com\nJane,jane@example.com';
1204
+ const file = createCSVFile(csvContent);
1205
+
1206
+ const testProps = { ...baseProps, config: { totalRowsText: 'Found {count} records' } };
1207
+ const { rerender } = render(<ImportModal {...testProps} />);
1208
+
1209
+ // Wait for file input to be available (pass rerender and props to handle persistent state)
1210
+ const fileInput = await waitForFileInput(rerender, testProps);
1211
+ await user.upload(fileInput, file);
1212
+
1213
+ // Wait for preview to appear
1214
+ await waitFor(() => {
1215
+ const table = screen.queryByRole('table') ||
1216
+ document.querySelector('table') ||
1217
+ document.querySelector('table.min-w-full');
1218
+ expect(table).toBeInTheDocument();
1219
+ }, { timeout: 10000 });
1220
+
1221
+ await waitFor(() => {
1222
+ expect(screen.getByText(/found 2 records/i)).toBeInTheDocument();
1223
+ }, { timeout: 2000 });
1224
+ });
1225
+ });
1226
+
1227
+ describe('Import Progress Tracking', () => {
1228
+ it('shows parsing progress during CSV parsing', async () => {
1229
+ const user = userEvent.setup();
1230
+ const headers = 'name,email\n';
1231
+ const rows = Array.from({ length: 200 }, (_, i) => `User${i},user${i}@example.com`).join('\n');
1232
+ const csvContent = headers + rows;
1233
+ const file = createCSVFile(csvContent);
1234
+ const onImport = vi.fn(async () => {
1235
+ await new Promise(resolve => setTimeout(resolve, 100));
1236
+ });
1237
+
1238
+ const testProps = { ...baseProps, onImport };
1239
+ const { rerender } = render(<ImportModal {...testProps} />);
1240
+
1241
+ const fileInput = await waitForFileInput(rerender, testProps);
1242
+ await user.upload(fileInput, file);
1243
+
1244
+ await waitFor(() => {
1245
+ const table = screen.queryByRole('table') || document.querySelector('table');
1246
+ expect(table).toBeInTheDocument();
1247
+ }, { timeout: 10000 });
1248
+
1249
+ const importButton = findButtonByText(/^import$/i);
1250
+ expect(importButton).toBeInTheDocument();
1251
+ if (importButton && !importButton.disabled) {
1252
+ await user.click(importButton);
1253
+ }
1254
+
1255
+ // Should show parsing progress (may be very brief)
1256
+ await waitFor(() => {
1257
+ const parsingText = screen.queryByText(/parsing csv file|importing data/i);
1258
+ if (parsingText) {
1259
+ expect(parsingText).toBeInTheDocument();
1260
+ }
1261
+ }, { timeout: 5000 });
1262
+ });
1263
+
1264
+ it('shows importing progress during data import', async () => {
1265
+ const user = userEvent.setup();
1266
+ const csvContent = 'name,email\nJohn,john@example.com\nJane,jane@example.com';
1267
+ const file = createCSVFile(csvContent);
1268
+ const onImport = vi.fn(async () => {
1269
+ await new Promise(resolve => setTimeout(resolve, 100));
1270
+ });
1271
+
1272
+ const testProps = { ...baseProps, onImport };
1273
+ const { rerender } = render(<ImportModal {...testProps} />);
1274
+
1275
+ const fileInput = await waitForFileInput(rerender, testProps);
1276
+ await user.upload(fileInput, file);
1277
+
1278
+ await waitFor(() => {
1279
+ const table = screen.queryByRole('table') || document.querySelector('table');
1280
+ expect(table).toBeInTheDocument();
1281
+ }, { timeout: 10000 });
1282
+
1283
+ const importButton = findButtonByText(/^import$/i);
1284
+ if (importButton) {
1285
+ await user.click(importButton);
1286
+ }
1287
+
1288
+ // Should show importing progress
1289
+ await waitFor(() => {
1290
+ expect(screen.getByText(/importing data/i)).toBeInTheDocument();
1291
+ }, { timeout: 2000 });
1292
+ });
1293
+
1294
+ it('shows chunk progress when processing large datasets', async () => {
1295
+ const user = userEvent.setup();
1296
+ const headers = 'name,email\n';
1297
+ const rows = Array.from({ length: 250 }, (_, i) => `User${i},user${i}@example.com`).join('\n');
1298
+ const csvContent = headers + rows;
1299
+ const file = createCSVFile(csvContent);
1300
+ const onImport = vi.fn(async () => {
1301
+ await new Promise(resolve => setTimeout(resolve, 50));
1302
+ });
1303
+
1304
+ const testProps = { ...baseProps, onImport };
1305
+ const { rerender } = render(<ImportModal {...testProps} />);
1306
+
1307
+ const fileInput = await waitForFileInput(rerender, testProps);
1308
+ await user.upload(fileInput, file);
1309
+
1310
+ await waitFor(() => {
1311
+ const table = screen.queryByRole('table') || document.querySelector('table');
1312
+ expect(table).toBeInTheDocument();
1313
+ }, { timeout: 10000 });
1314
+
1315
+ const importButton = findButtonByText(/^import$/i);
1316
+ if (importButton) {
1317
+ await user.click(importButton);
1318
+ }
1319
+
1320
+ // Should show chunk progress
1321
+ await waitFor(() => {
1322
+ const progressText = screen.queryByText(/chunk \d+ of \d+/i);
1323
+ if (progressText) {
1324
+ expect(progressText).toBeInTheDocument();
1325
+ }
1326
+ }, { timeout: 3000 });
1327
+ });
1328
+ });
1329
+
1330
+ describe('Import Summary', () => {
1331
+ it('displays success summary when all rows imported successfully', async () => {
1332
+ const user = userEvent.setup();
1333
+ const csvContent = 'name,email\nJohn,john@example.com\nJane,jane@example.com';
1334
+ const file = createCSVFile(csvContent);
1335
+ const onImport = vi.fn().mockResolvedValue({ successCount: 2, totalCount: 2, failedCount: 0 });
1336
+
1337
+ const testProps = { ...baseProps, onImport };
1338
+ const { rerender } = render(<ImportModal {...testProps} />);
1339
+
1340
+ const fileInput = await waitForFileInput(rerender, testProps);
1341
+ await user.upload(fileInput, file);
1342
+
1343
+ await waitFor(() => {
1344
+ const table = screen.queryByRole('table') || document.querySelector('table');
1345
+ expect(table).toBeInTheDocument();
1346
+ }, { timeout: 10000 });
1347
+
1348
+ const importButton = findButtonByText(/^import$/i);
1349
+ if (importButton) {
1350
+ await user.click(importButton);
1351
+ }
1352
+
1353
+ await waitFor(() => {
1354
+ expect(screen.getByText(/import completed successfully/i)).toBeInTheDocument();
1355
+ expect(screen.getByText(/2 of 2.*imported/i)).toBeInTheDocument();
1356
+ }, { timeout: 5000 });
1357
+ });
1358
+
1359
+ it('displays error summary when some rows fail', async () => {
1360
+ const user = userEvent.setup();
1361
+ const csvContent = 'name,email\nJohn,john@example.com\nJane,jane@example.com';
1362
+ const file = createCSVFile(csvContent);
1363
+ let callCount = 0;
1364
+ const onImport = vi.fn(async () => {
1365
+ callCount++;
1366
+ if (callCount === 1) {
1367
+ throw new Error('Import failed for chunk');
1368
+ }
1369
+ });
1370
+
1371
+ const testProps = { ...baseProps, onImport };
1372
+ const { rerender } = render(<ImportModal {...testProps} />);
1373
+
1374
+ const fileInput = await waitForFileInput(rerender, testProps);
1375
+ await user.upload(fileInput, file);
1376
+
1377
+ await waitFor(() => {
1378
+ const table = screen.queryByRole('table') || document.querySelector('table');
1379
+ expect(table).toBeInTheDocument();
1380
+ }, { timeout: 10000 });
1381
+
1382
+ const importButton = findButtonByText(/^import$/i);
1383
+ if (importButton) {
1384
+ await user.click(importButton);
1385
+ }
1386
+
1387
+ await waitFor(() => {
1388
+ expect(screen.getByText(/import completed with errors/i)).toBeInTheDocument();
1389
+ }, { timeout: 5000 });
1390
+ });
1391
+
1392
+ it('displays failed rows table when failures occur', async () => {
1393
+ const user = userEvent.setup();
1394
+ const csvContent = 'name,email\nJohn,john@example.com\nJane,jane@example.com';
1395
+ const file = createCSVFile(csvContent);
1396
+ const onImport = vi.fn().mockRejectedValue(new Error('Import failed'));
1397
+
1398
+ const testProps = { ...baseProps, onImport };
1399
+ const { rerender } = render(<ImportModal {...testProps} />);
1400
+
1401
+ const fileInput = await waitForFileInput(rerender, testProps);
1402
+ await user.upload(fileInput, file);
1403
+
1404
+ await waitFor(() => {
1405
+ const table = screen.queryByRole('table') || document.querySelector('table');
1406
+ expect(table).toBeInTheDocument();
1407
+ }, { timeout: 10000 });
1408
+
1409
+ const importButton = findButtonByText(/^import$/i);
1410
+ if (importButton) {
1411
+ await user.click(importButton);
1412
+ }
1413
+
1414
+ await waitFor(() => {
1415
+ const failedRowsTable = screen.queryByText(/failed rows/i);
1416
+ if (failedRowsTable) {
1417
+ expect(failedRowsTable).toBeInTheDocument();
1418
+ }
1419
+ }, { timeout: 5000 });
1420
+ });
1421
+
1422
+ it('limits failed rows display to 50 rows', async () => {
1423
+ const user = userEvent.setup();
1424
+ const headers = 'name,email\n';
1425
+ const rows = Array.from({ length: 100 }, (_, i) => `User${i},user${i}@example.com`).join('\n');
1426
+ const csvContent = headers + rows;
1427
+ const file = createCSVFile(csvContent);
1428
+ const onImport = vi.fn().mockRejectedValue(new Error('Import failed'));
1429
+
1430
+ const testProps = { ...baseProps, onImport };
1431
+ const { rerender } = render(<ImportModal {...testProps} />);
1432
+
1433
+ const fileInput = await waitForFileInput(rerender, testProps);
1434
+ await user.upload(fileInput, file);
1435
+
1436
+ await waitFor(() => {
1437
+ const table = screen.queryByRole('table') || document.querySelector('table');
1438
+ expect(table).toBeInTheDocument();
1439
+ }, { timeout: 10000 });
1440
+
1441
+ const importButton = findButtonByText(/^import$/i);
1442
+ if (importButton) {
1443
+ await user.click(importButton);
1444
+ }
1445
+
1446
+ await waitFor(() => {
1447
+ const failedRowsText = screen.queryByText(/showing first \d+ of \d+ failed/i);
1448
+ if (failedRowsText) {
1449
+ expect(failedRowsText).toBeInTheDocument();
1450
+ }
1451
+ }, { timeout: 5000 });
1452
+ });
1453
+ });
1454
+
1455
+ describe('Chunk Processing', () => {
1456
+ it('processes data in chunks for large datasets', async () => {
1457
+ const user = userEvent.setup();
1458
+ const headers = 'name,email\n';
1459
+ const rows = Array.from({ length: 150 }, (_, i) => `User${i},user${i}@example.com`).join('\n');
1460
+ const csvContent = headers + rows;
1461
+ const file = createCSVFile(csvContent);
1462
+ const onImport = vi.fn().mockResolvedValue(undefined);
1463
+
1464
+ const testProps = { ...baseProps, onImport };
1465
+ const { rerender } = render(<ImportModal {...testProps} />);
1466
+
1467
+ const fileInput = await waitForFileInput(rerender, testProps);
1468
+ await user.upload(fileInput, file);
1469
+
1470
+ await waitFor(() => {
1471
+ const table = screen.queryByRole('table') || document.querySelector('table');
1472
+ expect(table).toBeInTheDocument();
1473
+ }, { timeout: 10000 });
1474
+
1475
+ const importButton = findButtonByText(/^import$/i);
1476
+ if (importButton) {
1477
+ await user.click(importButton);
1478
+ }
1479
+
1480
+ await waitFor(() => {
1481
+ // Should be called multiple times for chunks
1482
+ expect(onImport).toHaveBeenCalled();
1483
+ }, { timeout: 5000 });
1484
+ });
1485
+
1486
+ it('handles chunk processing errors gracefully', async () => {
1487
+ const user = userEvent.setup();
1488
+ const headers = 'name,email\n';
1489
+ const rows = Array.from({ length: 150 }, (_, i) => `User${i},user${i}@example.com`).join('\n');
1490
+ const csvContent = headers + rows;
1491
+ const file = createCSVFile(csvContent);
1492
+ let callCount = 0;
1493
+ const onImport = vi.fn(async () => {
1494
+ callCount++;
1495
+ if (callCount === 2) {
1496
+ throw new Error('Chunk processing failed');
1497
+ }
1498
+ });
1499
+
1500
+ const testProps = { ...baseProps, onImport };
1501
+ const { rerender } = render(<ImportModal {...testProps} />);
1502
+
1503
+ const fileInput = await waitForFileInput(rerender, testProps);
1504
+ await user.upload(fileInput, file);
1505
+
1506
+ await waitFor(() => {
1507
+ const table = screen.queryByRole('table') || document.querySelector('table');
1508
+ expect(table).toBeInTheDocument();
1509
+ }, { timeout: 10000 });
1510
+
1511
+ const importButton = findButtonByText(/^import$/i);
1512
+ if (importButton) {
1513
+ await user.click(importButton);
1514
+ }
1515
+
1516
+ await waitFor(() => {
1517
+ // Should show summary with failures
1518
+ const summary = screen.queryByText(/import completed/i);
1519
+ expect(summary).toBeInTheDocument();
1520
+ }, { timeout: 5000 });
1521
+ });
1522
+ });
1523
+
1524
+ describe('Edge Cases', () => {
1525
+ it('handles very large CSV files', async () => {
1526
+ const user = userEvent.setup();
1527
+ const headers = 'name,email\n';
1528
+ const rows = Array.from({ length: 1000 }, (_, i) => `User${i},user${i}@example.com`).join('\n');
1529
+ const csvContent = headers + rows;
1530
+ const file = createCSVFile(csvContent);
1531
+
1532
+ const { rerender } = render(<ImportModal {...baseProps} />);
1533
+
1534
+ // Wait for file input to be available (pass rerender to handle persistent state)
1535
+ const fileInput = await waitForFileInput(rerender, baseProps);
1536
+ await user.upload(fileInput, file);
1537
+
1538
+ await waitFor(() => {
1539
+ expect(screen.getByText(/total rows to import: 1000/i)).toBeInTheDocument();
1540
+ }, { timeout: 5000 });
1541
+ });
1542
+
1543
+ it('handles CSV with only one data row', async () => {
1544
+ const user = userEvent.setup();
1545
+ const csvContent = 'name,email\nJohn,john@example.com';
1546
+ const file = createCSVFile(csvContent);
1547
+
1548
+ const { rerender } = render(<ImportModal {...baseProps} />);
1549
+
1550
+ const fileInput = await waitForFileInput(rerender, baseProps);
1551
+ await user.upload(fileInput, file);
1552
+
1553
+ await waitFor(() => {
1554
+ expect(screen.getByText(/total rows to import: 1/i)).toBeInTheDocument();
1555
+ }, { timeout: 5000 });
1556
+ });
1557
+
1558
+ it('handles CSV with empty values', async () => {
1559
+ const user = userEvent.setup();
1560
+ const csvContent = 'name,email\nJohn,\n,jane@example.com';
1561
+ const file = createCSVFile(csvContent);
1562
+
1563
+ const { rerender } = render(<ImportModal {...baseProps} />);
1564
+
1565
+ const fileInput = await waitForFileInput(rerender, baseProps);
1566
+ await user.upload(fileInput, file);
1567
+
1568
+ await waitFor(() => {
1569
+ const table = screen.queryByRole('table') || document.querySelector('table');
1570
+ expect(table).toBeInTheDocument();
1571
+ }, { timeout: 10000 });
1572
+ });
1573
+
1574
+ it('handles CSV with special characters in values', async () => {
1575
+ const user = userEvent.setup();
1576
+ const csvContent = 'name,email\n"John, Doe","john@example.com"\n"Jane; Smith","jane@example.com"';
1577
+ const file = createCSVFile(csvContent);
1578
+
1579
+ const { rerender } = render(<ImportModal {...baseProps} />);
1580
+
1581
+ const fileInput = await waitForFileInput(rerender, baseProps);
1582
+ await user.upload(fileInput, file);
1583
+
1584
+ await waitFor(() => {
1585
+ const table = screen.queryByRole('table') || document.querySelector('table');
1586
+ expect(table).toBeInTheDocument();
1587
+ }, { timeout: 10000 });
1588
+ });
1589
+
1590
+ it('handles async onImport that returns Promise', async () => {
1591
+ const user = userEvent.setup();
1592
+ const csvContent = 'name,email\nJohn,john@example.com';
1593
+ const file = createCSVFile(csvContent);
1594
+ const onImport = vi.fn(() => Promise.resolve());
1595
+
1596
+ const testProps = { ...baseProps, onImport };
1597
+ const { rerender } = render(<ImportModal {...testProps} />);
1598
+
1599
+ const fileInput = await waitForFileInput(rerender, testProps);
1600
+ await user.upload(fileInput, file);
1601
+
1602
+ await waitFor(() => {
1603
+ const table = screen.queryByRole('table') || document.querySelector('table');
1604
+ expect(table).toBeInTheDocument();
1605
+ }, { timeout: 10000 });
1606
+
1607
+ const importButton = findButtonByText(/^import$/i);
1608
+ if (importButton) {
1609
+ await user.click(importButton);
1610
+ }
1611
+
1612
+ await waitFor(() => {
1613
+ expect(onImport).toHaveBeenCalled();
1614
+ }, { timeout: 5000 });
1615
+ });
1616
+
1617
+ it('prevents closing modal during processing', async () => {
1618
+ const user = userEvent.setup();
1619
+ const csvContent = 'name,email\nJohn,john@example.com';
1620
+ const file = createCSVFile(csvContent);
1621
+ const onImport = vi.fn(async () => {
1622
+ await new Promise(resolve => setTimeout(resolve, 200));
1623
+ });
1624
+ const onClose = vi.fn();
1625
+
1626
+ const testProps = { ...baseProps, onImport, onClose };
1627
+ const { rerender } = render(<ImportModal {...testProps} />);
1628
+
1629
+ const fileInput = await waitForFileInput(rerender, testProps);
1630
+ await user.upload(fileInput, file);
1631
+
1632
+ await waitFor(() => {
1633
+ const table = screen.queryByRole('table') || document.querySelector('table');
1634
+ expect(table).toBeInTheDocument();
1635
+ }, { timeout: 10000 });
1636
+
1637
+ const importButton = findButtonByText(/^import$/i);
1638
+ if (importButton) {
1639
+ await user.click(importButton);
1640
+ }
1641
+
1642
+ // Try to close modal during processing
1643
+ await waitFor(() => {
1644
+ const dialog = document.querySelector('dialog[role="dialog"]');
1645
+ if (dialog) {
1646
+ // Simulate escape key or outside click
1647
+ const event = new KeyboardEvent('keydown', { key: 'Escape' });
1648
+ dialog.dispatchEvent(event);
1649
+ }
1650
+ }, { timeout: 1000 });
1651
+
1652
+ // Modal should still be open
1653
+ await waitFor(() => {
1654
+ expect(screen.queryByText('Import Data')).toBeInTheDocument();
1655
+ }, { timeout: 1000 });
1656
+ });
1657
+
1658
+ it('prevents closing modal when summary exists', async () => {
1659
+ const user = userEvent.setup();
1660
+ const csvContent = 'name,email\nJohn,john@example.com';
1661
+ const file = createCSVFile(csvContent);
1662
+ const onImport = vi.fn().mockResolvedValue(undefined);
1663
+ const onClose = vi.fn();
1664
+
1665
+ const testProps = { ...baseProps, onImport, onClose };
1666
+ const { rerender } = render(<ImportModal {...testProps} />);
1667
+
1668
+ const fileInput = await waitForFileInput(rerender, testProps);
1669
+ await user.upload(fileInput, file);
1670
+
1671
+ await waitFor(() => {
1672
+ const table = screen.queryByRole('table') || document.querySelector('table');
1673
+ expect(table).toBeInTheDocument();
1674
+ }, { timeout: 10000 });
1675
+
1676
+ const importButton = findButtonByText(/^import$/i);
1677
+ if (importButton) {
1678
+ await user.click(importButton);
1679
+ }
1680
+
1681
+ // Wait for summary to appear
1682
+ await waitFor(() => {
1683
+ expect(screen.getByText(/import completed/i)).toBeInTheDocument();
1684
+ }, { timeout: 5000 });
1685
+
1686
+ // Try to close modal - should be prevented
1687
+ const dialog = document.querySelector('dialog[role="dialog"]');
1688
+ if (dialog) {
1689
+ const event = new KeyboardEvent('keydown', { key: 'Escape' });
1690
+ dialog.dispatchEvent(event);
1691
+ }
1692
+
1693
+ // Modal should still be open
1694
+ await waitFor(() => {
1695
+ expect(screen.queryByText('Import Data')).toBeInTheDocument();
1696
+ }, { timeout: 1000 });
1697
+ });
1698
+ });
1699
+
1700
+ describe('Full import flow and section content', () => {
1701
+ it('full flow renders and asserts File, Preview, Summary section content', async () => {
1702
+ const user = userEvent.setup();
1703
+ const csvContent = 'name,email\nJohn,john@example.com\nJane,jane@example.com';
1704
+ const file = createCSVFile(csvContent);
1705
+ const onImport = vi.fn().mockResolvedValue({ successCount: 2, totalCount: 2, failedCount: 0 });
1706
+
1707
+ const testProps = { ...baseProps, onImport };
1708
+ const { rerender } = render(<ImportModal {...testProps} />);
1709
+
1710
+ const fileInput = await waitForFileInput(rerender, testProps);
1711
+ await user.upload(fileInput, file);
1712
+
1713
+ await waitFor(() => {
1714
+ expect(screen.getByText(/selected:/i)).toBeInTheDocument();
1715
+ }, { timeout: 3000 });
1716
+ const fileSection = document.body;
1717
+ expect(fileSection.textContent).toMatch(new RegExp(file.name, 'i'));
1718
+
1719
+ await waitFor(() => {
1720
+ const table = screen.queryByRole('table') || document.querySelector('table');
1721
+ expect(table).toBeInTheDocument();
1722
+ }, { timeout: 10000 });
1723
+ expect(screen.getByText(/total rows to import: 2/i)).toBeInTheDocument();
1724
+ expect(screen.getByText('John')).toBeInTheDocument();
1725
+ expect(screen.getByText('Jane')).toBeInTheDocument();
1726
+
1727
+ const importButton = findButtonByText(/^import$/i);
1728
+ if (importButton) {
1729
+ await user.click(importButton);
1730
+ }
1731
+
1732
+ await waitFor(() => {
1733
+ expect(screen.getByText(/import completed successfully/i)).toBeInTheDocument();
1734
+ expect(screen.getByText(/2 of 2.*imported/i)).toBeInTheDocument();
1735
+ }, { timeout: 5000 });
1736
+ });
1737
+
1738
+ it('full flow with import error renders Summary and FailedRows section content', async () => {
1739
+ const user = userEvent.setup();
1740
+ const csvContent = 'name,email\nJohn,john@example.com\nJane,jane@example.com';
1741
+ const file = createCSVFile(csvContent);
1742
+ const onImport = vi.fn().mockRejectedValue(new Error('Import failed'));
1743
+
1744
+ const testProps = { ...baseProps, onImport };
1745
+ const { rerender } = render(<ImportModal {...testProps} />);
1746
+
1747
+ const fileInput = await waitForFileInput(rerender, testProps);
1748
+ await user.upload(fileInput, file);
1749
+
1750
+ await waitFor(() => {
1751
+ const table = screen.queryByRole('table') || document.querySelector('table');
1752
+ expect(table).toBeInTheDocument();
1753
+ }, { timeout: 10000 });
1754
+
1755
+ const importButton = findButtonByText(/^import$/i);
1756
+ if (importButton) {
1757
+ await user.click(importButton);
1758
+ }
1759
+
1760
+ await waitFor(() => {
1761
+ expect(screen.getByText(/import completed with errors/i)).toBeInTheDocument();
1762
+ }, { timeout: 5000 });
1763
+ const failedRowsHeading = screen.queryByText(/failed rows/i);
1764
+ if (failedRowsHeading) {
1765
+ expect(failedRowsHeading).toBeInTheDocument();
1766
+ }
1767
+ });
1768
+ });
1769
+
1770
+ describe('Accessibility', () => {
1771
+ it('provides accessible file input', async () => {
1772
+ const { rerender } = render(<ImportModal {...baseProps} />);
1773
+
1774
+ // Wait for file input to be available (pass rerender to handle persistent state)
1775
+ const fileInput = await waitForFileInput(rerender, baseProps);
1776
+ expect(fileInput).toBeInTheDocument();
1777
+ expect(fileInput).toHaveAttribute('type', 'file');
1778
+ expect(fileInput).toHaveAttribute('accept', '.csv');
1779
+ });
1780
+
1781
+ it('provides accessible button labels', async () => {
1782
+ const { rerender } = render(<ImportModal {...baseProps} />);
1783
+
1784
+ // Wait for dialog title first
1785
+ await waitFor(() => {
1786
+ try {
1787
+ expect(screen.getByRole('heading', { name: 'Import Data' })).toBeInTheDocument();
1788
+ } catch (_e) {
1789
+ const elements = screen.getAllByText('Import Data');
1790
+ expect(elements.length).toBeGreaterThan(0);
1791
+ }
1792
+ }, { timeout: 5000 });
1793
+
1794
+ // Check if there's a summary or file already selected - if so, clear it first
1795
+ const hasSummary = screen.queryByText(/import completed/i);
1796
+ const hasSelectedFile = screen.queryByText(/selected:/i);
1797
+ if (hasSummary || hasSelectedFile) {
1798
+ rerender(<ImportModal {...baseProps} isOpen={false} />);
1799
+ await waitFor(() => {
1800
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
1801
+ }, { timeout: 2000 });
1802
+ await new Promise(resolve => setTimeout(resolve, 300));
1803
+ rerender(<ImportModal {...baseProps} isOpen={true} />);
1804
+ await waitForDialog();
1805
+ await waitFor(() => {
1806
+ expect(screen.getByText('Import Data')).toBeInTheDocument();
1807
+ }, { timeout: 5000 });
1808
+ }
1809
+
1810
+ // Wait for file selection area to be rendered (default uploadText is "Choose a CSV file to upload")
1811
+ await waitFor(() => {
1812
+ const uploadText = screen.queryByText(/choose a csv file/i);
1813
+ expect(uploadText).toBeInTheDocument();
1814
+ }, { timeout: 5000 });
1815
+
1816
+ // Then wait for buttons (should exist when no summary and no file selected)
1817
+ await waitFor(() => {
1818
+ const selectFileButton = findButtonByText(/select file/i);
1819
+ expect(selectFileButton).toBeInTheDocument();
1820
+ }, { timeout: 5000 });
1821
+
1822
+ await waitFor(() => {
1823
+ const importButton = findButtonByText(/^import$/i);
1824
+ expect(importButton).toBeInTheDocument();
1825
+ }, { timeout: 5000 });
1826
+
1827
+ await waitFor(() => {
1828
+ const cancelButton = findButtonByText(/cancel/i);
1829
+ expect(cancelButton).toBeInTheDocument();
1830
+ }, { timeout: 5000 });
1831
+ });
1832
+ });
1833
+ });
1834
+