@jmruthers/pace-core 0.5.75 → 0.5.77

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 (507) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/{RBACService-C4udt_Zp.d.ts → AuthService-SBHZQtCH.d.ts} +5 -118
  3. package/dist/{DataTable-ntgmhO2W.d.ts → DataTable-BE0OXZKQ.d.ts} +9 -2
  4. package/dist/DataTable-QCNCV6IK.js +157 -0
  5. package/dist/{PublicLoadingSpinner-BKNBT6b6.d.ts → PublicLoadingSpinner-CnUaz0vG.d.ts} +33 -19
  6. package/dist/{UnifiedAuthProvider-Bj6YCf7c.d.ts → UnifiedAuthProvider-B391Aqum.d.ts} +42 -45
  7. package/dist/{UnifiedAuthProvider-3NKDOSOK.js → UnifiedAuthProvider-Z2FWNW7O.js} +4 -5
  8. package/dist/{api-DDMUKIUD.js → api-KG4A2X7P.js} +9 -3
  9. package/dist/{audit-6TOCAMKO.js → audit-65VNHEV2.js} +2 -2
  10. package/dist/{chunk-2DFZ432F.js → chunk-7PX43UYN.js} +197 -629
  11. package/dist/chunk-7PX43UYN.js.map +1 -0
  12. package/dist/{chunk-DAXLNIDY.js → chunk-C4RQ3GQA.js} +108 -32
  13. package/dist/chunk-C4RQ3GQA.js.map +1 -0
  14. package/dist/{chunk-LW7MMEAQ.js → chunk-CRKP3HXI.js} +2 -2
  15. package/dist/{chunk-XLZ7U46Z.js → chunk-CVMVPYAL.js} +9 -60
  16. package/dist/chunk-CVMVPYAL.js.map +1 -0
  17. package/dist/{chunk-CY3AHGO4.js → chunk-DDPG7FCX.js} +3395 -3254
  18. package/dist/chunk-DDPG7FCX.js.map +1 -0
  19. package/dist/{chunk-URUTVZ7N.js → chunk-DVHZ5L55.js} +2 -2
  20. package/dist/{chunk-5BSLGBYI.js → chunk-JCQZ6LA7.js} +2 -8
  21. package/dist/{chunk-5BSLGBYI.js.map → chunk-JCQZ6LA7.js.map} +1 -1
  22. package/dist/{chunk-WN6XJWOS.js → chunk-JDQ7T3QB.js} +256 -743
  23. package/dist/chunk-JDQ7T3QB.js.map +1 -0
  24. package/dist/{chunk-ZTT2AXMX.js → chunk-LMYTEMUH.js} +153 -132
  25. package/dist/chunk-LMYTEMUH.js.map +1 -0
  26. package/dist/{chunk-33PHABLB.js → chunk-NKT2DLZI.js} +13 -130
  27. package/dist/chunk-NKT2DLZI.js.map +1 -0
  28. package/dist/chunk-PUKTJMRT.js +732 -0
  29. package/dist/chunk-PUKTJMRT.js.map +1 -0
  30. package/dist/{chunk-B2WTCLCV.js → chunk-Q7APDV6H.js} +18 -8
  31. package/dist/chunk-Q7APDV6H.js.map +1 -0
  32. package/dist/{chunk-FGMFQSHX.js → chunk-S63MFSY6.js} +500 -551
  33. package/dist/chunk-S63MFSY6.js.map +1 -0
  34. package/dist/{chunk-NTNILOBC.js → chunk-TLD5BEU6.js} +4 -4
  35. package/dist/chunk-WUXCWRL6.js +20 -0
  36. package/dist/chunk-WUXCWRL6.js.map +1 -0
  37. package/dist/{chunk-YNUBMSMV.js → chunk-YCKPEMJA.js} +186 -263
  38. package/dist/chunk-YCKPEMJA.js.map +1 -0
  39. package/dist/{chunk-A4FUBC7B.js → chunk-Z3T6RK3K.js} +2 -4
  40. package/dist/{chunk-A4FUBC7B.js.map → chunk-Z3T6RK3K.js.map} +1 -1
  41. package/dist/components.d.ts +6 -6
  42. package/dist/components.js +17 -20
  43. package/dist/components.js.map +1 -1
  44. package/dist/{database-C3Szpi5J.d.ts → database-BXAfr2Y_.d.ts} +18 -0
  45. package/dist/hooks.d.ts +21 -44
  46. package/dist/hooks.js +12 -13
  47. package/dist/hooks.js.map +1 -1
  48. package/dist/index.d.ts +19 -27
  49. package/dist/index.js +27 -33
  50. package/dist/index.js.map +1 -1
  51. package/dist/{organisation-BtshODVF.d.ts → organisation-D6qRDtbF.d.ts} +1 -1
  52. package/dist/providers.d.ts +7 -21
  53. package/dist/providers.js +3 -10
  54. package/dist/rbac/index.d.ts +118 -215
  55. package/dist/rbac/index.js +18 -18
  56. package/dist/{types-CGX9Vyf5.d.ts → types-BDg1mAGG.d.ts} +36 -6
  57. package/dist/types.d.ts +3 -3
  58. package/dist/types.js +61 -18
  59. package/dist/types.js.map +1 -1
  60. package/dist/{unified-CM7T0aTK.d.ts → unified-DQ4VcT7H.d.ts} +1 -1
  61. package/dist/{usePublicRouteParams-B-CumWRc.d.ts → usePublicRouteParams-BlgwXweB.d.ts} +3 -3
  62. package/dist/utils.d.ts +2 -2
  63. package/dist/utils.js +52 -9
  64. package/dist/utils.js.map +1 -1
  65. package/docs/CONTENT_AUDIT_REPORT.md +253 -0
  66. package/docs/DOCUMENTATION_AUDIT.md +172 -0
  67. package/docs/README.md +142 -147
  68. package/docs/STYLE_GUIDE.md +37 -0
  69. package/docs/api/classes/ColumnFactory.md +17 -17
  70. package/docs/api/classes/ErrorBoundary.md +1 -1
  71. package/docs/api/classes/InvalidScopeError.md +4 -4
  72. package/docs/api/classes/MissingUserContextError.md +4 -4
  73. package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
  74. package/docs/api/classes/PermissionDeniedError.md +5 -5
  75. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  76. package/docs/api/classes/RBACAuditManager.md +8 -8
  77. package/docs/api/classes/RBACCache.md +35 -5
  78. package/docs/api/classes/RBACEngine.md +49 -20
  79. package/docs/api/classes/RBACError.md +4 -4
  80. package/docs/api/classes/RBACNotInitializedError.md +4 -4
  81. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  82. package/docs/api/classes/StorageUtils.md +1 -1
  83. package/docs/api/enums/FileCategory.md +1 -1
  84. package/docs/api/interfaces/AggregateConfig.md +4 -4
  85. package/docs/api/interfaces/ButtonProps.md +1 -1
  86. package/docs/api/interfaces/CardProps.md +1 -1
  87. package/docs/api/interfaces/ColorPalette.md +1 -1
  88. package/docs/api/interfaces/ColorShade.md +1 -1
  89. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  90. package/docs/api/interfaces/DataRecord.md +11 -0
  91. package/docs/api/interfaces/DataTableAction.md +65 -29
  92. package/docs/api/interfaces/DataTableColumn.md +36 -23
  93. package/docs/api/interfaces/DataTableProps.md +80 -38
  94. package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
  95. package/docs/api/interfaces/EmptyStateConfig.md +5 -5
  96. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  97. package/docs/api/interfaces/EventLogoProps.md +1 -1
  98. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  99. package/docs/api/interfaces/FileMetadata.md +1 -1
  100. package/docs/api/interfaces/FileReference.md +1 -1
  101. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  102. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  103. package/docs/api/interfaces/FileUploadProps.md +1 -1
  104. package/docs/api/interfaces/FooterProps.md +1 -1
  105. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  106. package/docs/api/interfaces/InputProps.md +1 -1
  107. package/docs/api/interfaces/LabelProps.md +1 -1
  108. package/docs/api/interfaces/LoginFormProps.md +1 -1
  109. package/docs/api/interfaces/NavigationAccessRecord.md +11 -11
  110. package/docs/api/interfaces/NavigationContextType.md +9 -9
  111. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  112. package/docs/api/interfaces/NavigationItem.md +1 -1
  113. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  114. package/docs/api/interfaces/NavigationProviderProps.md +7 -7
  115. package/docs/api/interfaces/Organisation.md +1 -1
  116. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  117. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  118. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  119. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  120. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  121. package/docs/api/interfaces/PaceLoginPageProps.md +16 -3
  122. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  123. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  124. package/docs/api/interfaces/PagePermissionGuardProps.md +2 -2
  125. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  126. package/docs/api/interfaces/PaletteData.md +1 -1
  127. package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
  128. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  129. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  130. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  131. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  132. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  133. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  134. package/docs/api/interfaces/RBACConfig.md +1 -1
  135. package/docs/api/interfaces/RBACLogger.md +1 -1
  136. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  137. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  138. package/docs/api/interfaces/RouteAccessRecord.md +2 -2
  139. package/docs/api/interfaces/RouteConfig.md +2 -2
  140. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  141. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  142. package/docs/api/interfaces/StorageConfig.md +1 -1
  143. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  144. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  145. package/docs/api/interfaces/StorageListOptions.md +1 -1
  146. package/docs/api/interfaces/StorageListResult.md +1 -1
  147. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  148. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  149. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  150. package/docs/api/interfaces/StyleImport.md +1 -1
  151. package/docs/api/interfaces/SwitchProps.md +1 -1
  152. package/docs/api/interfaces/ToastActionElement.md +1 -1
  153. package/docs/api/interfaces/ToastProps.md +1 -1
  154. package/docs/api/interfaces/UnifiedAuthContextType.md +94 -521
  155. package/docs/api/interfaces/UnifiedAuthProviderProps.md +16 -16
  156. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  157. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  158. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  159. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  160. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  161. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  162. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  163. package/docs/api/interfaces/UseResolvedScopeOptions.md +47 -0
  164. package/docs/api/interfaces/UseResolvedScopeReturn.md +47 -0
  165. package/docs/api/interfaces/UserEventAccess.md +11 -11
  166. package/docs/api/interfaces/UserMenuProps.md +1 -1
  167. package/docs/api/interfaces/UserProfile.md +1 -1
  168. package/docs/api/modules.md +303 -275
  169. package/docs/api-reference/components.md +193 -0
  170. package/docs/api-reference/hooks.md +265 -0
  171. package/docs/api-reference/providers.md +32 -7
  172. package/docs/api-reference/types.md +6 -0
  173. package/docs/api-reference/utilities.md +207 -0
  174. package/docs/architecture/README.md +6 -0
  175. package/docs/{database-schema-requirements.md → architecture/database-schema-requirements.md} +6 -0
  176. package/docs/architecture/rbac-security-architecture.md +258 -0
  177. package/docs/architecture/services.md +9 -1
  178. package/docs/best-practices/README.md +26 -0
  179. package/docs/best-practices/accessibility.md +572 -0
  180. package/docs/{common-patterns.md → best-practices/common-patterns.md} +6 -0
  181. package/docs/best-practices/deployment.md +6 -0
  182. package/docs/best-practices/performance.md +475 -2
  183. package/docs/best-practices/security.md +6 -0
  184. package/docs/best-practices/testing.md +6 -0
  185. package/docs/core-concepts/authentication.md +21 -7
  186. package/docs/core-concepts/events.md +6 -0
  187. package/docs/core-concepts/organisations.md +6 -0
  188. package/docs/core-concepts/permissions.md +6 -0
  189. package/docs/core-concepts/rbac-system.md +6 -0
  190. package/docs/documentation-index.md +121 -182
  191. package/docs/{consuming-app-vite-config.md → getting-started/consuming-app-vite-config.md} +6 -0
  192. package/docs/getting-started/documentation-index.md +40 -0
  193. package/docs/getting-started/examples/README.md +878 -35
  194. package/docs/{faq.md → getting-started/faq.md} +7 -1
  195. package/docs/getting-started/installation-guide.md +6 -0
  196. package/docs/{quick-reference.md → getting-started/quick-reference.md} +6 -0
  197. package/docs/implementation-guides/app-layout.md +6 -0
  198. package/docs/implementation-guides/authentication.md +1021 -0
  199. package/docs/implementation-guides/component-styling.md +416 -0
  200. package/docs/implementation-guides/data-tables.md +1264 -2076
  201. package/docs/implementation-guides/dynamic-colors.md +6 -0
  202. package/docs/implementation-guides/event-theming-summary.md +6 -0
  203. package/docs/{file-reference-system.md → implementation-guides/file-reference-system.md} +6 -0
  204. package/docs/implementation-guides/file-upload-storage.md +6 -0
  205. package/docs/implementation-guides/forms.md +6 -0
  206. package/docs/implementation-guides/inactivity-tracking.md +6 -0
  207. package/docs/implementation-guides/navigation.md +6 -0
  208. package/docs/implementation-guides/organisation-security.md +6 -0
  209. package/docs/implementation-guides/permission-enforcement.md +6 -0
  210. package/docs/implementation-guides/public-pages-advanced.md +6 -0
  211. package/docs/implementation-guides/public-pages.md +6 -0
  212. package/docs/migration/MIGRATION_GUIDE.md +827 -351
  213. package/docs/migration/README.md +7 -1
  214. package/docs/migration/organisation-context-timing-fix.md +6 -0
  215. package/docs/migration/rbac-migration.md +44 -1
  216. package/docs/migration/service-architecture.md +6 -0
  217. package/docs/migration/v0.4.15-tailwind-scanning.md +6 -0
  218. package/docs/migration/v0.4.16-css-first-approach.md +6 -0
  219. package/docs/migration/v0.4.17-source-path-fix.md +6 -0
  220. package/docs/rbac/README-rbac-rls-integration.md +6 -0
  221. package/docs/rbac/README.md +6 -0
  222. package/docs/rbac/advanced-patterns.md +6 -0
  223. package/docs/rbac/api-reference.md +7 -1
  224. package/docs/rbac/breaking-changes-v3.md +222 -0
  225. package/docs/rbac/examples/rbac-rls-integration-example.md +6 -0
  226. package/docs/rbac/examples.md +6 -0
  227. package/docs/rbac/getting-started.md +6 -0
  228. package/docs/rbac/migration-guide.md +260 -0
  229. package/docs/rbac/quick-start.md +6 -0
  230. package/docs/rbac/rbac-rls-integration.md +6 -0
  231. package/docs/rbac/super-admin-guide.md +6 -0
  232. package/docs/rbac/troubleshooting.md +6 -0
  233. package/docs/security/README.md +6 -0
  234. package/docs/security/checklist.md +6 -0
  235. package/docs/styles/README.md +7 -1
  236. package/docs/{usage.md → styles/usage.md} +6 -0
  237. package/docs/testing/README.md +6 -0
  238. package/docs/{visual-testing.md → testing/visual-testing.md} +6 -0
  239. package/docs/troubleshooting/README.md +387 -5
  240. package/docs/troubleshooting/cake-page-permission-guard-issue-summary.md +6 -0
  241. package/docs/troubleshooting/common-issues.md +6 -0
  242. package/docs/troubleshooting/database-view-compatibility.md +6 -0
  243. package/docs/troubleshooting/organisation-context-setup.md +6 -0
  244. package/docs/troubleshooting/react-hooks-issue-analysis.md +6 -0
  245. package/docs/troubleshooting/styling-issues.md +6 -0
  246. package/docs/troubleshooting/tailwind-content-scanning.md +6 -0
  247. package/package.json +1 -1
  248. package/src/__tests__/TEST_GUIDE_CURSOR.md +290 -0
  249. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -1
  250. package/src/__tests__/helpers/supabaseMock.ts +48 -2
  251. package/src/__tests__/helpers/test-providers.tsx +3 -53
  252. package/src/components/DataTable/DataTable.test.tsx +319 -0
  253. package/src/components/DataTable/DataTable.tsx +32 -11
  254. package/src/components/DataTable/__tests__/{DataTable.comprehensive.test.tsx → DataTable.comprehensive.test.tsx.skip} +6 -4
  255. package/src/components/DataTable/__tests__/DataTable.default-state.test.tsx +17 -6
  256. package/src/components/DataTable/__tests__/{DataTable.test.tsx → DataTable.test.tsx.skip} +6 -4
  257. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +96 -10
  258. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +601 -0
  259. package/src/components/DataTable/__tests__/keyboard.test.tsx +615 -0
  260. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +639 -0
  261. package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx.skip +330 -0
  262. package/src/components/DataTable/components/AccessDeniedPage.tsx +2 -2
  263. package/src/components/DataTable/components/ActionButtons.tsx +88 -104
  264. package/src/components/DataTable/components/DataTableCore.tsx +442 -665
  265. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +4 -2
  266. package/src/components/DataTable/components/DataTableModals.tsx +22 -1
  267. package/src/components/DataTable/components/EditableRow.tsx +69 -84
  268. package/src/components/DataTable/components/EmptyState.tsx +5 -1
  269. package/src/components/DataTable/components/ImportModal.tsx +65 -36
  270. package/src/components/DataTable/components/PaginationControls.tsx +40 -100
  271. package/src/components/DataTable/components/UnifiedTableBody.tsx +222 -278
  272. package/src/components/DataTable/components/index.ts +1 -2
  273. package/src/components/DataTable/context/DataTableContext.tsx +1 -1
  274. package/src/components/DataTable/context/__tests__/DataTableContext.test.tsx +208 -275
  275. package/src/components/DataTable/core/ColumnFactory.ts +5 -0
  276. package/src/components/DataTable/core/index.ts +1 -8
  277. package/src/components/DataTable/examples/HierarchicalActionsExample.tsx +12 -10
  278. package/src/components/DataTable/examples/HierarchicalExample.tsx +1 -1
  279. package/src/components/DataTable/examples/InitialPageSizeExample.tsx +1 -0
  280. package/src/components/DataTable/examples/PerformanceExample.tsx +1 -0
  281. package/src/components/DataTable/hooks/__tests__/useColumnOrderPersistence.test.ts +521 -0
  282. package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +570 -0
  283. package/src/components/DataTable/hooks/__tests__/useColumnVisibilityPersistence.test.ts +167 -0
  284. package/src/components/DataTable/hooks/__tests__/useHierarchicalState.test.ts +214 -0
  285. package/src/components/DataTable/hooks/__tests__/useTableColumns.test.ts +224 -0
  286. package/src/components/DataTable/hooks/index.ts +13 -0
  287. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +32 -15
  288. package/src/components/DataTable/hooks/useColumnReordering.ts +1 -0
  289. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +102 -0
  290. package/src/components/DataTable/hooks/useDataTableConfiguration.ts +89 -0
  291. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +117 -0
  292. package/src/components/DataTable/hooks/useDataTablePermissions.ts +193 -0
  293. package/src/components/DataTable/hooks/useDataTableState.ts +51 -17
  294. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +33 -0
  295. package/src/components/DataTable/hooks/useHierarchicalState.ts +41 -9
  296. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +447 -0
  297. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +94 -0
  298. package/src/components/DataTable/hooks/useTableColumns.ts +156 -0
  299. package/src/components/DataTable/hooks/useTableHandlers.ts +174 -0
  300. package/src/components/DataTable/index.ts +13 -12
  301. package/src/components/DataTable/types.ts +129 -9
  302. package/src/components/DataTable/utils/__tests__/COVERAGE_NOTE.md +89 -0
  303. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +162 -28
  304. package/src/components/DataTable/utils/__tests__/flexibleImport.test.ts +573 -0
  305. package/src/components/DataTable/utils/__tests__/hierarchicalSorting.test.ts +247 -0
  306. package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +8 -6
  307. package/src/components/DataTable/utils/__tests__/performanceUtils.test.ts +466 -0
  308. package/src/components/DataTable/utils/__tests__/rowUtils.test.ts +251 -0
  309. package/src/components/DataTable/utils/a11yUtils.ts +244 -0
  310. package/src/components/DataTable/utils/debugTools.ts +47 -21
  311. package/src/components/DataTable/utils/errorHandling.ts +52 -460
  312. package/src/components/DataTable/utils/exportUtils.ts +157 -28
  313. package/src/components/DataTable/utils/flexibleImport.ts +202 -32
  314. package/src/components/DataTable/utils/hierarchicalSorting.ts +50 -3
  315. package/src/components/DataTable/utils/hierarchicalUtils.ts +167 -34
  316. package/src/components/DataTable/utils/index.ts +7 -0
  317. package/src/components/DataTable/utils/paginationUtils.ts +350 -0
  318. package/src/components/DataTable/utils/rowUtils.ts +69 -0
  319. package/src/components/EventSelector/EventSelector.test.tsx +672 -0
  320. package/src/components/Label/__tests__/Label.test.tsx +434 -0
  321. package/src/components/NavigationMenu/NavigationMenu.test.tsx +19 -24
  322. package/src/components/NavigationMenu/NavigationMenu.tsx +19 -8
  323. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +1 -23
  324. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +56 -6
  325. package/src/components/PaceLoginPage/PaceLoginPage.tsx +137 -13
  326. package/src/components/PublicLayout/__tests__/PublicPageContextChecker.test.tsx +190 -0
  327. package/src/components/PublicLayout/__tests__/PublicPageDebugger.test.tsx +185 -0
  328. package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +1 -1
  329. package/src/components/PublicLayout/__tests__/PublicPageProvider.test.tsx +313 -0
  330. package/src/components/Select/Select.test.tsx +143 -120
  331. package/src/components/Select/Select.tsx +48 -212
  332. package/src/components/Select/hooks.ts +36 -1
  333. package/src/components/Select/index.ts +2 -1
  334. package/src/components/examples/PermissionExample.tsx +173 -0
  335. package/src/examples/CorrectPublicPageImplementation.tsx +301 -0
  336. package/src/examples/PublicEventPage.tsx +274 -0
  337. package/src/examples/PublicPageApp.tsx +308 -0
  338. package/src/examples/PublicPageUsageExample.tsx +216 -0
  339. package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +12 -1
  340. package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +129 -17
  341. package/src/hooks/__tests__/useRBAC.unit.test.ts +151 -846
  342. package/src/hooks/useOrganisationPermissions.test.ts +42 -18
  343. package/src/hooks/useOrganisationPermissions.ts +12 -6
  344. package/src/hooks/useOrganisationSecurity.test.ts +138 -85
  345. package/src/hooks/useOrganisationSecurity.ts +41 -10
  346. package/src/hooks/useSecureDataAccess.test.ts +32 -29
  347. package/src/index.ts +0 -1
  348. package/src/providers/AuthProvider.simplified.tsx +880 -0
  349. package/src/providers/UnifiedAuthProvider.test.simple.tsx +8 -8
  350. package/src/providers/__tests__/ProviderLifecycle.test.tsx +341 -0
  351. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +29 -19
  352. package/src/providers/index.ts +0 -1
  353. package/src/providers/services/EventServiceProvider.tsx +19 -15
  354. package/src/providers/services/InactivityServiceProvider.tsx +19 -15
  355. package/src/providers/services/OrganisationServiceProvider.tsx +19 -15
  356. package/src/providers/services/UnifiedAuthProvider.tsx +156 -127
  357. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +1 -1
  358. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -3
  359. package/src/rbac/README.md +1 -1
  360. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +25 -27
  361. package/src/rbac/__tests__/auth-rbac-security.integration.test.tsx +313 -0
  362. package/src/rbac/__tests__/engine.comprehensive.test.ts +114 -348
  363. package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +28 -110
  364. package/src/rbac/__tests__/rbac-engine-simplified.test.ts +33 -85
  365. package/src/rbac/__tests__/scenarios.user-role.test.tsx +2 -2
  366. package/src/rbac/adapters.tsx +26 -69
  367. package/src/rbac/api.test.ts +90 -27
  368. package/src/rbac/api.ts +61 -10
  369. package/src/rbac/audit.test.ts +33 -38
  370. package/src/rbac/audit.ts +21 -6
  371. package/src/rbac/cache.ts +33 -1
  372. package/src/rbac/components/NavigationGuard.tsx +11 -11
  373. package/src/rbac/components/NavigationProvider.test.tsx +11 -5
  374. package/src/rbac/components/NavigationProvider.tsx +37 -13
  375. package/src/rbac/components/PagePermissionGuard.tsx +111 -50
  376. package/src/rbac/components/PagePermissionProvider.tsx +5 -5
  377. package/src/rbac/components/PermissionEnforcer.tsx +11 -11
  378. package/src/rbac/components/RoleBasedRouter.tsx +5 -5
  379. package/src/rbac/components/SecureDataProvider.tsx +5 -5
  380. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +8 -8
  381. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +14 -14
  382. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +12 -12
  383. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +6 -6
  384. package/src/rbac/engine.test.simple.ts +19 -13
  385. package/src/rbac/engine.test.ts +1 -0
  386. package/src/rbac/engine.ts +330 -766
  387. package/src/rbac/errors.ts +156 -0
  388. package/src/rbac/hooks/__tests__/usePermissions.integration.test.ts +437 -0
  389. package/src/rbac/hooks/index.ts +2 -0
  390. package/src/rbac/hooks/usePermissions.ts +32 -10
  391. package/src/rbac/hooks/useRBAC.test.ts +126 -512
  392. package/src/rbac/hooks/useRBAC.ts +147 -193
  393. package/src/rbac/hooks/useResolvedScope.ts +244 -0
  394. package/src/rbac/index.ts +7 -4
  395. package/src/rbac/security.ts +109 -18
  396. package/src/rbac/types.ts +12 -1
  397. package/src/services/AuthService.ts +2 -15
  398. package/src/services/EventService.ts +26 -46
  399. package/src/services/OrganisationService.ts +51 -31
  400. package/src/services/__tests__/AuthService.test.ts +1 -1
  401. package/src/services/__tests__/EventService.test.ts +1 -1
  402. package/src/services/__tests__/InactivityService.lifecycle.test.ts +411 -0
  403. package/src/services/__tests__/OrganisationService.pagination.test.ts +375 -0
  404. package/src/services/__tests__/OrganisationService.test.ts +1 -1
  405. package/src/styles/base.css +208 -0
  406. package/src/styles/semantic.css +24 -0
  407. package/src/types/__tests__/README.md +114 -0
  408. package/src/types/__tests__/validation.test.ts +731 -0
  409. package/src/types/database.generated.ts +7347 -0
  410. package/src/types/database.ts +20 -0
  411. package/src/utils/__tests__/file-reference.test.ts +383 -0
  412. package/src/utils/__tests__/performanceBenchmark.test.ts +175 -0
  413. package/src/utils/appNameResolver.test.ts +54 -0
  414. package/src/utils/logger.ts +179 -0
  415. package/src/utils/organisationContext.ts +11 -4
  416. package/src/utils/storage/__tests__/helpers.unit.test.ts +6 -2
  417. package/src/validation/__tests__/csrf.unit.test.ts +63 -0
  418. package/src/validation/__tests__/passwordSchema.unit.test.ts +105 -0
  419. package/dist/DataTable-HWZQGASI.js +0 -102
  420. package/dist/appNameResolver-UURKN7NF.js +0 -22
  421. package/dist/audit-6TOCAMKO.js.map +0 -1
  422. package/dist/chunk-2CHATWBF.js +0 -523
  423. package/dist/chunk-2CHATWBF.js.map +0 -1
  424. package/dist/chunk-2DFZ432F.js.map +0 -1
  425. package/dist/chunk-33PHABLB.js.map +0 -1
  426. package/dist/chunk-B2WTCLCV.js.map +0 -1
  427. package/dist/chunk-CY3AHGO4.js.map +0 -1
  428. package/dist/chunk-DAXLNIDY.js.map +0 -1
  429. package/dist/chunk-FGMFQSHX.js.map +0 -1
  430. package/dist/chunk-TYHR5X4W.js +0 -33
  431. package/dist/chunk-TYHR5X4W.js.map +0 -1
  432. package/dist/chunk-ULBI5JGB.js +0 -109
  433. package/dist/chunk-ULBI5JGB.js.map +0 -1
  434. package/dist/chunk-WN6XJWOS.js.map +0 -1
  435. package/dist/chunk-XLZ7U46Z.js.map +0 -1
  436. package/dist/chunk-YNUBMSMV.js.map +0 -1
  437. package/dist/chunk-ZTT2AXMX.js.map +0 -1
  438. package/dist/eventContext-BBA42P6G.js +0 -14
  439. package/dist/eventContext-BBA42P6G.js.map +0 -1
  440. package/docs/DOCUMENTATION_CHECKLIST.md +0 -281
  441. package/docs/api/interfaces/RBACContextType.md +0 -468
  442. package/docs/api/interfaces/RBACProviderProps.md +0 -107
  443. package/docs/breaking-changes.md +0 -179
  444. package/docs/consuming-app-example.md +0 -290
  445. package/docs/documentation-style-checklist.md +0 -294
  446. package/docs/examples/navigation-menu-auth-fix.md +0 -344
  447. package/docs/getting-started/examples/basic-auth-app.md +0 -520
  448. package/docs/getting-started/examples/full-featured-app.md +0 -616
  449. package/docs/getting-started/quick-start.md +0 -426
  450. package/docs/implementation-guides/datatable-filtering.md +0 -313
  451. package/docs/implementation-guides/datatable-rbac-usage.md +0 -317
  452. package/docs/implementation-guides/hierarchical-datatable.md +0 -850
  453. package/docs/implementation-guides/large-datasets.md +0 -281
  454. package/docs/implementation-guides/performance.md +0 -403
  455. package/docs/migration/quick-migration-guide.md +0 -320
  456. package/docs/migration-guide.md +0 -193
  457. package/docs/migration-guides/unified-auth-provider-mandatory-timeouts.md +0 -226
  458. package/docs/performance/README.md +0 -551
  459. package/docs/style-guide.md +0 -925
  460. package/docs/troubleshooting/authentication-issues.md +0 -334
  461. package/docs/troubleshooting/debugging.md +0 -1117
  462. package/docs/troubleshooting/migration.md +0 -918
  463. package/src/__tests__/hooks/usePermissions.test.ts +0 -261
  464. package/src/components/DataTable/components/DataTableBody.tsx +0 -488
  465. package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -144
  466. package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -515
  467. package/src/components/DataTable/core/ActionManager.ts +0 -235
  468. package/src/components/DataTable/core/ColumnManager.ts +0 -215
  469. package/src/components/DataTable/core/DataManager.ts +0 -188
  470. package/src/components/DataTable/core/DataTableContext.tsx +0 -181
  471. package/src/components/DataTable/core/LocalDataAdapter.ts +0 -264
  472. package/src/components/DataTable/core/PluginRegistry.ts +0 -229
  473. package/src/components/DataTable/core/StateManager.ts +0 -311
  474. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +0 -634
  475. package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +0 -193
  476. package/src/components/DataTable/core/__tests__/DataManager.test.ts +0 -519
  477. package/src/components/DataTable/core/__tests__/StateManager.test.ts +0 -714
  478. package/src/components/DataTable/core/interfaces.ts +0 -338
  479. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +0 -574
  480. package/src/components/Select/Select.bug-test.tsx +0 -69
  481. package/src/components/Select/Select.refactored.tsx +0 -497
  482. package/src/hooks/__tests__/ServiceHooks.test.tsx +0 -613
  483. package/src/hooks/services/usePermissions.ts +0 -70
  484. package/src/hooks/services/useRBACService.ts +0 -30
  485. package/src/hooks/usePermissionCheck.ts +0 -150
  486. package/src/providers/__tests__/ServiceProviders.test.tsx +0 -477
  487. package/src/providers/services/RBACServiceProvider.tsx +0 -79
  488. package/src/rbac/__tests__/integration.authflow.test.tsx +0 -119
  489. package/src/rbac/__tests__/integration.navigation.test.tsx +0 -69
  490. package/src/rbac/__tests__/integration.securedata.test.tsx +0 -92
  491. package/src/rbac/__tests__/integration.smoke.test.tsx +0 -73
  492. package/src/rbac/providers/RBACProvider.tsx +0 -645
  493. package/src/rbac/providers/__tests__/RBACProvider.integration.test.tsx +0 -688
  494. package/src/rbac/providers/__tests__/RBACProvider.test.tsx +0 -1186
  495. package/src/rbac/providers/index.ts +0 -11
  496. package/src/services/RBACService.ts +0 -522
  497. package/src/services/__tests__/RBACService.test.ts +0 -492
  498. package/src/services/interfaces/IRBACService.ts +0 -62
  499. package/src/utils/appNameResolver.test 2.ts +0 -494
  500. /package/dist/{DataTable-HWZQGASI.js.map → DataTable-QCNCV6IK.js.map} +0 -0
  501. /package/dist/{UnifiedAuthProvider-3NKDOSOK.js.map → UnifiedAuthProvider-Z2FWNW7O.js.map} +0 -0
  502. /package/dist/{api-DDMUKIUD.js.map → api-KG4A2X7P.js.map} +0 -0
  503. /package/dist/{appNameResolver-UURKN7NF.js.map → audit-65VNHEV2.js.map} +0 -0
  504. /package/dist/{chunk-LW7MMEAQ.js.map → chunk-CRKP3HXI.js.map} +0 -0
  505. /package/dist/{chunk-URUTVZ7N.js.map → chunk-DVHZ5L55.js.map} +0 -0
  506. /package/dist/{chunk-NTNILOBC.js.map → chunk-TLD5BEU6.js.map} +0 -0
  507. /package/docs/{app.css.example → styles/app.css.example} +0 -0
@@ -0,0 +1,466 @@
1
+ /**
2
+ * @file Unit Tests for performanceUtils
3
+ * @package @jmruthers/pace-core
4
+ * @module Components/DataTable/Utils/__tests__
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
8
+ import {
9
+ determinePaginationMode,
10
+ getOptimalPageSizeOptions,
11
+ chunkData,
12
+ DataChunkManager,
13
+ SearchIndex,
14
+ debounce,
15
+ throttle,
16
+ VisibilityTracker
17
+ } from '../performanceUtils';
18
+ import type { ChunkingConfig, SearchIndexConfig, DataRecord } from '../../types';
19
+
20
+ describe('[unit] determinePaginationMode', () => {
21
+ it('returns client for small datasets', () => {
22
+ expect(determinePaginationMode(100)).toBe('client');
23
+ expect(determinePaginationMode(500)).toBe('client');
24
+ expect(determinePaginationMode(999)).toBe('client');
25
+ });
26
+
27
+ it('returns hybrid for medium datasets', () => {
28
+ expect(determinePaginationMode(1001)).toBe('hybrid');
29
+ expect(determinePaginationMode(5000)).toBe('hybrid');
30
+ });
31
+
32
+ it('returns server for large datasets', () => {
33
+ expect(determinePaginationMode(10001)).toBe('server');
34
+ expect(determinePaginationMode(50000)).toBe('server');
35
+ });
36
+
37
+ it('allows custom serverSideThreshold', () => {
38
+ expect(determinePaginationMode(5000, 10000)).toBe('hybrid');
39
+ expect(determinePaginationMode(10001, 10000)).toBe('server');
40
+ });
41
+
42
+ it('handles edge at threshold', () => {
43
+ expect(determinePaginationMode(5000, 5000)).toBe('hybrid');
44
+ expect(determinePaginationMode(5001, 5000)).toBe('server');
45
+ });
46
+ });
47
+
48
+ describe('[unit] getOptimalPageSizeOptions', () => {
49
+ it('returns correct options for client mode', () => {
50
+ const options100 = getOptimalPageSizeOptions('client', 100);
51
+ expect(options100).toEqual([10, 25, 50]);
52
+
53
+ const options200 = getOptimalPageSizeOptions('client', 200);
54
+ expect(options200).toEqual([10, 25, 50, 100]);
55
+ });
56
+
57
+ it('returns correct options for hybrid mode', () => {
58
+ const options = getOptimalPageSizeOptions('hybrid', 5000);
59
+ expect(options).toEqual([50, 100, 250, 500]);
60
+ });
61
+
62
+ it('returns correct options for server mode', () => {
63
+ const options = getOptimalPageSizeOptions('server', 10000);
64
+ expect(options).toEqual([25, 50, 100, 250]);
65
+ });
66
+
67
+ it('returns default options for invalid mode', () => {
68
+ const options = getOptimalPageSizeOptions('invalid' as any, 1000);
69
+ expect(options).toEqual([10, 25, 50, 100]);
70
+ });
71
+ });
72
+
73
+ describe('[unit] chunkData', () => {
74
+ const testData: DataRecord[] = Array.from({ length: 25 }, (_, i) => ({ id: i + 1 }));
75
+
76
+ it('splits data into correct chunks', () => {
77
+ const chunks = chunkData(testData, 10);
78
+
79
+ expect(chunks.length).toBe(3);
80
+ expect(chunks[0].length).toBe(10);
81
+ expect(chunks[1].length).toBe(10);
82
+ expect(chunks[2].length).toBe(5);
83
+ });
84
+
85
+ it('handles chunk size larger than data', () => {
86
+ const chunks = chunkData(testData, 100);
87
+
88
+ expect(chunks.length).toBe(1);
89
+ expect(chunks[0].length).toBe(25);
90
+ });
91
+
92
+ it('handles exact division', () => {
93
+ const chunks = chunkData(testData, 5);
94
+
95
+ expect(chunks.length).toBe(5);
96
+ chunks.forEach(chunk => {
97
+ expect(chunk.length).toBe(5);
98
+ });
99
+ });
100
+
101
+ it('handles empty data', () => {
102
+ const chunks = chunkData([], 10);
103
+
104
+ expect(chunks).toEqual([]);
105
+ });
106
+
107
+ it('handles chunk size of 1', () => {
108
+ const chunks = chunkData(testData, 1);
109
+
110
+ expect(chunks.length).toBe(25);
111
+ chunks.forEach(chunk => {
112
+ expect(chunk.length).toBe(1);
113
+ });
114
+ });
115
+ });
116
+
117
+ describe('[unit] DataChunkManager', () => {
118
+ let manager: DataChunkManager<DataRecord>;
119
+ const testData: DataRecord[] = Array.from({ length: 1000 }, (_, i) => ({ id: i + 1 }));
120
+ const config: ChunkingConfig = {
121
+ chunkSize: 100,
122
+ maxChunksInMemory: 3
123
+ };
124
+
125
+ beforeEach(() => {
126
+ manager = new DataChunkManager(config);
127
+ });
128
+
129
+ it('loads chunks on demand', () => {
130
+ const chunk = manager.getChunk(0, testData);
131
+
132
+ expect(chunk.length).toBe(100);
133
+ expect(chunk[0]).toEqual({ id: 1 });
134
+ expect(chunk[99]).toEqual({ id: 100 });
135
+ });
136
+
137
+ it('caches chunks in memory', () => {
138
+ const chunk1 = manager.getChunk(0, testData);
139
+ const chunk2 = manager.getChunk(0, testData);
140
+
141
+ // Should return same reference
142
+ expect(chunk1).toBe(chunk2);
143
+ });
144
+
145
+ it('evicts oldest chunks when at capacity', () => {
146
+ // Fill cache
147
+ manager.getChunk(0, testData); // Load chunk 0
148
+ manager.getChunk(1, testData); // Load chunk 1
149
+ manager.getChunk(2, testData); // Load chunk 2
150
+
151
+ // Now at capacity (3 chunks)
152
+ const chunk3 = manager.getChunk(3, testData); // Should evict chunk 0
153
+
154
+ expect(manager.getChunk(0, testData)).not.toBe(chunk3); // Chunk 0 was evicted
155
+ });
156
+
157
+ it('updates access order correctly', () => {
158
+ manager.getChunk(0, testData); // Oldest
159
+ manager.getChunk(1, testData); // Middle
160
+ manager.getChunk(2, testData); // Newest
161
+
162
+ manager.getChunk(0, testData); // Now 0 is most recently used
163
+
164
+ const chunk3 = manager.getChunk(3, testData); // Should evict 1 (now oldest)
165
+ expect(chunk3).toBeDefined();
166
+ });
167
+
168
+ it('handles last chunk correctly', () => {
169
+ const lastChunk = manager.getChunk(9, testData); // 10th chunk, 100 items each = last 100 items
170
+
171
+ expect(lastChunk.length).toBe(100);
172
+ expect(lastChunk[0]).toEqual({ id: 901 });
173
+ expect(lastChunk[99]).toEqual({ id: 1000 });
174
+ });
175
+
176
+ it('handles out of bounds chunk index', () => {
177
+ const chunk = manager.getChunk(20, testData); // Beyond data
178
+
179
+ expect(chunk.length).toBe(0);
180
+ });
181
+
182
+ it('clears all cached chunks', () => {
183
+ manager.getChunk(0, testData);
184
+ manager.getChunk(1, testData);
185
+
186
+ manager.clear();
187
+
188
+ // After clearing, the chunks should be recreated
189
+ const chunkAfterClear = manager.getChunk(0, testData);
190
+ // The chunk will be recreated, but we can't easily test the internal state
191
+ expect(chunkAfterClear.length).toBeGreaterThan(0); // Should reload
192
+ });
193
+
194
+ it('returns correct memory usage', () => {
195
+ manager.getChunk(0, testData);
196
+ manager.getChunk(1, testData);
197
+
198
+ const memoryUsage = manager.getMemoryUsage();
199
+
200
+ expect(typeof memoryUsage).toBe('number');
201
+ expect(memoryUsage).toBeGreaterThan(0);
202
+ });
203
+ });
204
+
205
+ describe('[unit] SearchIndex', () => {
206
+ let searchIndex: SearchIndex<DataRecord>;
207
+ const testData: DataRecord[] = [
208
+ { name: 'John Doe', email: 'john@example.com' },
209
+ { name: 'Jane Smith', email: 'jane@example.com' },
210
+ { name: 'Bob Johnson', email: 'bob@example.com' }
211
+ ];
212
+
213
+ const config: SearchIndexConfig = {
214
+ indexedFields: ['name', 'email'],
215
+ fuzzySearch: false
216
+ };
217
+
218
+ beforeEach(() => {
219
+ searchIndex = new SearchIndex(config);
220
+ searchIndex.buildIndex(testData);
221
+ });
222
+
223
+ it('builds index from data', () => {
224
+ // Index should be built in beforeEach
225
+ const results = searchIndex.search('John');
226
+
227
+ expect(results.length).toBeGreaterThan(0);
228
+ });
229
+
230
+ it('finds exact matches', () => {
231
+ const results = searchIndex.search('John Doe');
232
+
233
+ expect(results).toContain(0);
234
+ });
235
+
236
+ it('finds matches across multiple fields', () => {
237
+ const results = searchIndex.search('john@example.com');
238
+
239
+ expect(results).toContain(0);
240
+ });
241
+
242
+ it('handles empty search query', () => {
243
+ const results = searchIndex.search('');
244
+
245
+ expect(results).toEqual([]);
246
+ });
247
+
248
+ it('handles whitespace in query', () => {
249
+ const results = searchIndex.search(' John ');
250
+
251
+ expect(results).toContain(0);
252
+ });
253
+
254
+ it('handles case-insensitive search', () => {
255
+ const results = searchIndex.search('JANE');
256
+
257
+ expect(results).toContain(1);
258
+ });
259
+
260
+ it('handles partial word matches', () => {
261
+ const results = searchIndex.search('Jane');
262
+
263
+ expect(results).toContain(1);
264
+ });
265
+
266
+ it('requires all search terms to match with multiple words', () => {
267
+ const results = searchIndex.search('Jane Smith');
268
+
269
+ expect(results).toContain(1);
270
+
271
+ const noResults = searchIndex.search('Jane Johnson');
272
+
273
+ expect(noResults).not.toContain(1);
274
+ });
275
+
276
+ it('handles special characters in search', () => {
277
+ const results = searchIndex.search('john@example');
278
+
279
+ expect(results).toContain(0);
280
+ });
281
+
282
+ it('finds no results for non-matching query', () => {
283
+ const results = searchIndex.search('NonExistentUser');
284
+
285
+ expect(results).toEqual([]);
286
+ });
287
+ });
288
+
289
+ describe('[unit] SearchIndex with Fuzzy Search', () => {
290
+ it('finds fuzzy matches when enabled', () => {
291
+ const testData: DataRecord[] = [
292
+ { name: 'JavaScript' },
293
+ { name: 'TypeScript' },
294
+ { name: 'Python' }
295
+ ];
296
+
297
+ const fuzzyConfig: SearchIndexConfig = {
298
+ indexedFields: ['name'],
299
+ fuzzySearch: true,
300
+ fuzzyThreshold: 0.6
301
+ };
302
+
303
+ const searchIndex = new SearchIndex(fuzzyConfig);
304
+ searchIndex.buildIndex(testData);
305
+
306
+ const results = searchIndex.search('JavScript'); // Typo
307
+
308
+ expect(results.length).toBeGreaterThan(0);
309
+ });
310
+
311
+ it('respects fuzzy threshold', () => {
312
+ const testData: DataRecord[] = [
313
+ { name: 'test' }
314
+ ];
315
+
316
+ const fuzzyConfig: SearchIndexConfig = {
317
+ indexedFields: ['name'],
318
+ fuzzySearch: true,
319
+ fuzzyThreshold: 0.8 // High threshold
320
+ };
321
+
322
+ const searchIndex = new SearchIndex(fuzzyConfig);
323
+ searchIndex.buildIndex(testData);
324
+
325
+ const results = searchIndex.search('testt');
326
+
327
+ // Should still match due to high similarity
328
+ expect(results.length).toBeGreaterThan(0);
329
+ });
330
+
331
+ it('does not match very different strings', () => {
332
+ const testData: DataRecord[] = [
333
+ { name: 'test' }
334
+ ];
335
+
336
+ const fuzzyConfig: SearchIndexConfig = {
337
+ indexedFields: ['name'],
338
+ fuzzySearch: true,
339
+ fuzzyThreshold: 0.8
340
+ };
341
+
342
+ const searchIndex = new SearchIndex(fuzzyConfig);
343
+ searchIndex.buildIndex(testData);
344
+
345
+ const results = searchIndex.search('completelydifferent');
346
+
347
+ expect(results.length).toBe(0);
348
+ });
349
+ });
350
+
351
+ describe('[unit] debounce', () => {
352
+ beforeEach(() => {
353
+ vi.useFakeTimers();
354
+ });
355
+
356
+ afterEach(() => {
357
+ vi.useRealTimers();
358
+ });
359
+
360
+ it('delays function execution', () => {
361
+ const fn = vi.fn();
362
+ const debouncedFn = debounce(fn, 100);
363
+
364
+ debouncedFn();
365
+ expect(fn).not.toHaveBeenCalled();
366
+
367
+ vi.advanceTimersByTime(100);
368
+ expect(fn).toHaveBeenCalledTimes(1);
369
+ });
370
+
371
+ it('cancels previous calls when called multiple times', () => {
372
+ const fn = vi.fn();
373
+ const debouncedFn = debounce(fn, 100);
374
+
375
+ debouncedFn();
376
+ debouncedFn();
377
+ debouncedFn();
378
+
379
+ vi.advanceTimersByTime(100);
380
+ expect(fn).toHaveBeenCalledTimes(1);
381
+ });
382
+
383
+ it('preserves function arguments', () => {
384
+ const fn = vi.fn();
385
+ const debouncedFn = debounce(fn, 100);
386
+
387
+ debouncedFn('arg1', 'arg2');
388
+
389
+ vi.advanceTimersByTime(100);
390
+ expect(fn).toHaveBeenCalledWith('arg1', 'arg2');
391
+ });
392
+ });
393
+
394
+ describe('[unit] throttle', () => {
395
+ beforeEach(() => {
396
+ vi.useFakeTimers();
397
+ });
398
+
399
+ afterEach(() => {
400
+ vi.useRealTimers();
401
+ });
402
+
403
+ it('limits function execution rate', () => {
404
+ const fn = vi.fn();
405
+ const throttledFn = throttle(fn, 100);
406
+
407
+ throttledFn();
408
+ expect(fn).toHaveBeenCalledTimes(1);
409
+
410
+ throttledFn();
411
+ throttledFn();
412
+ throttledFn();
413
+
414
+ // Should not be called again within throttle period
415
+ expect(fn).toHaveBeenCalledTimes(1);
416
+
417
+ // After throttle period
418
+ vi.advanceTimersByTime(100);
419
+ throttledFn();
420
+ expect(fn).toHaveBeenCalledTimes(2);
421
+ });
422
+
423
+ it('preserves function arguments', () => {
424
+ const fn = vi.fn();
425
+ const throttledFn = throttle(fn, 100);
426
+
427
+ throttledFn('arg1', 'arg2');
428
+ expect(fn).toHaveBeenCalledWith('arg1', 'arg2');
429
+ });
430
+ });
431
+
432
+ describe('[unit] VisibilityTracker', () => {
433
+ it('creates tracker successfully', () => {
434
+ const tracker = new VisibilityTracker();
435
+
436
+ expect(tracker).toBeDefined();
437
+ });
438
+
439
+ it('tracks visible elements count', () => {
440
+ const tracker = new VisibilityTracker();
441
+
442
+ // In test environment, IntersectionObserver may not be available
443
+ // So we can only test basic instantiation
444
+ expect(tracker.getVisibleCount()).toBe(0);
445
+ });
446
+
447
+ it('provides callback for visibility changes', () => {
448
+ const tracker = new VisibilityTracker();
449
+ const callback = vi.fn();
450
+
451
+ const unsubscribe = tracker.onVisibilityChange(callback);
452
+
453
+ // Cleanup
454
+ unsubscribe();
455
+ });
456
+
457
+ it('destroys tracker cleanly', () => {
458
+ const tracker = new VisibilityTracker();
459
+
460
+ tracker.destroy();
461
+
462
+ // Should not throw
463
+ expect(() => tracker.getVisibleCount()).not.toThrow();
464
+ });
465
+ });
466
+
@@ -0,0 +1,251 @@
1
+ /**
2
+ * @file Unit Tests for rowUtils
3
+ * @package @jmruthers/pace-core
4
+ * @module Components/DataTable/Utils/__tests__
5
+ */
6
+
7
+ import { describe, it, expect } from 'vitest';
8
+ import { getRowIdSafe, hasValidRowId } from '../rowUtils';
9
+ import type { GetRowId, DataRecord } from '../../types';
10
+
11
+ describe('[unit] getRowIdSafe', () => {
12
+ it('uses custom getRowId when provided', () => {
13
+ const customGetRowId: GetRowId<DataRecord> = (row: DataRecord) => `custom-${(row as any).id}`;
14
+ const row = { id: 'test-123' };
15
+
16
+ const result = getRowIdSafe(row, 0, customGetRowId);
17
+
18
+ expect(result).toBe('custom-test-123');
19
+ });
20
+
21
+ it('falls back to row.id when custom getRowId not provided', () => {
22
+ const row = { id: 'test-123' };
23
+
24
+ const result = getRowIdSafe(row, 0);
25
+
26
+ expect(result).toBe('test-123');
27
+ });
28
+
29
+ it('throws when row.id is undefined and no custom getRowId exists', () => {
30
+ const row = { name: 'Test' };
31
+
32
+ expect(() => getRowIdSafe(row as any, 0)).toThrow('[DataTable] Unable to determine a stable row id');
33
+ });
34
+
35
+ it('throws when row.id is null', () => {
36
+ const row = { id: null };
37
+
38
+ expect(() => getRowIdSafe(row as any, 5)).toThrow('[DataTable] Unable to determine a stable row id');
39
+ });
40
+
41
+ it('converts numeric IDs to strings', () => {
42
+ const row = { id: 123 };
43
+
44
+ const result = getRowIdSafe(row, 0);
45
+
46
+ expect(result).toBe('123');
47
+ });
48
+
49
+ it('converts zero to string', () => {
50
+ const row = { id: 0 };
51
+
52
+ const result = getRowIdSafe(row, 0);
53
+
54
+ expect(result).toBe('0');
55
+ });
56
+
57
+ it('handles empty string ID', () => {
58
+ const row = { id: '' };
59
+
60
+ const result = getRowIdSafe(row, 5);
61
+
62
+ expect(result).toBe(''); // Empty string is valid
63
+ });
64
+
65
+ it('handles custom getRowId that returns non-string', () => {
66
+ const customGetRowId = () => 123 as any;
67
+ const row = { id: 'test' };
68
+
69
+ const result = getRowIdSafe(row, 0, customGetRowId);
70
+
71
+ // getRowIdSafe always returns a string
72
+ expect(result).toBe('123');
73
+ });
74
+
75
+ it('preserves string IDs exactly', () => {
76
+ const row = { id: 'special-id_123' };
77
+
78
+ const result = getRowIdSafe(row, 0);
79
+
80
+ expect(result).toBe('special-id_123');
81
+ });
82
+ });
83
+
84
+ describe('[unit] hasValidRowId', () => {
85
+ it('returns true for valid string ID', () => {
86
+ const row = { id: 'valid-id-123' };
87
+
88
+ const result = hasValidRowId(row);
89
+
90
+ expect(result).toBe(true);
91
+ });
92
+
93
+ it('returns false when row lacks an id', () => {
94
+ const row = { name: 'Test' };
95
+
96
+ const result = hasValidRowId(row);
97
+
98
+ expect(result).toBe(false);
99
+ });
100
+
101
+ it('returns false for empty string ID', () => {
102
+ const row = { id: '' };
103
+
104
+ const result = hasValidRowId(row);
105
+
106
+ expect(result).toBe(false);
107
+ });
108
+
109
+ it('returns false for "undefined" string', () => {
110
+ const row = { id: 'undefined' };
111
+
112
+ const result = hasValidRowId(row);
113
+
114
+ expect(result).toBe(false);
115
+ });
116
+
117
+ it('returns false for "null" string', () => {
118
+ const row = { id: 'null' };
119
+
120
+ const result = hasValidRowId(row);
121
+
122
+ expect(result).toBe(false);
123
+ });
124
+
125
+ it('returns true for valid numeric ID as string', () => {
126
+ const row = { id: '123' };
127
+
128
+ const result = hasValidRowId(row);
129
+
130
+ expect(result).toBe(true);
131
+ });
132
+
133
+ it('returns true for UUID format ID', () => {
134
+ const row = { id: '550e8400-e29b-41d4-a716-446655440000' };
135
+
136
+ const result = hasValidRowId(row);
137
+
138
+ expect(result).toBe(true);
139
+ });
140
+
141
+ it('uses custom getRowId when provided', () => {
142
+ const customGetRowId: GetRowId<DataRecord> = () => 'custom-valid-id';
143
+ const row = { id: '' };
144
+
145
+ const result = hasValidRowId(row, customGetRowId);
146
+
147
+ expect(result).toBe(true);
148
+ });
149
+
150
+ it('returns false when custom getRowId returns invalid ID', () => {
151
+ const customGetRowId: GetRowId<DataRecord> = () => '0';
152
+ const row = { id: 'valid' };
153
+
154
+ const result = hasValidRowId(row, customGetRowId);
155
+
156
+ expect(result).toBe(false);
157
+ });
158
+
159
+ it('handles getRowId errors gracefully', () => {
160
+ const errorGetRowId = () => {
161
+ throw new Error('GetRowId failed');
162
+ };
163
+
164
+ const row = { id: 'test' };
165
+
166
+ const result = hasValidRowId(row, errorGetRowId as any);
167
+
168
+ expect(result).toBe(false);
169
+ });
170
+
171
+ it('returns true for complex valid IDs', () => {
172
+ const row = { id: 'user-123-item-456-action_789' };
173
+
174
+ const result = hasValidRowId(row);
175
+
176
+ expect(result).toBe(true);
177
+ });
178
+
179
+ it('returns false for edge case invalid IDs', () => {
180
+ const invalidIds = ['0', '', 'undefined', 'null'];
181
+
182
+ invalidIds.forEach(invalidId => {
183
+ const row = { id: invalidId };
184
+ const result = hasValidRowId(row);
185
+ expect(result).toBe(false);
186
+ });
187
+ });
188
+
189
+ it('returns true for edge case valid IDs', () => {
190
+ const validIds = ['1', 'a', 'A', '123', 'abc-123'];
191
+
192
+ validIds.forEach(validId => {
193
+ const row = { id: validId };
194
+ const result = hasValidRowId(row);
195
+ expect(result).toBe(true);
196
+ });
197
+ });
198
+
199
+ it('handles null and undefined in custom getRowId by falling back', () => {
200
+ const customGetRowId1: GetRowId<DataRecord> = () => null as any;
201
+ const customGetRowId2: GetRowId<DataRecord> = () => undefined as any;
202
+ const row = { id: 'test' };
203
+
204
+ // When custom getRowId returns null/undefined, it falls back to row.id
205
+ expect(hasValidRowId(row, customGetRowId1)).toBe(true); // row.id='test' is valid
206
+ expect(hasValidRowId(row, customGetRowId2)).toBe(true); // row.id='test' is valid
207
+
208
+ // Test with row that has no id
209
+ const rowNoId = { name: 'Test' };
210
+ expect(hasValidRowId(rowNoId, customGetRowId1)).toBe(false);
211
+ expect(hasValidRowId(rowNoId, customGetRowId2)).toBe(false);
212
+ });
213
+ });
214
+
215
+ describe('[unit] Integration', () => {
216
+ it('getRowIdSafe and hasValidRowId work together', () => {
217
+ const row = { id: 'test-id-123' };
218
+
219
+ const rowId = getRowIdSafe(row, 0);
220
+ const isValid = hasValidRowId(row);
221
+
222
+ expect(rowId).toBe('test-id-123');
223
+ expect(isValid).toBe(true);
224
+ });
225
+
226
+ it('hasValidRowId correctly identifies getRowIdSafe fallback cases', () => {
227
+ const rowWithValidId = { id: 'valid' };
228
+ const rowWithoutId = { name: 'Test' };
229
+
230
+ expect(hasValidRowId(rowWithValidId)).toBe(true);
231
+ expect(hasValidRowId(rowWithoutId)).toBe(false);
232
+ });
233
+
234
+ it('handles various row structures', () => {
235
+ const testCases = [
236
+ { row: { id: 'abc' }, expected: true },
237
+ { row: { id: '' }, expected: false },
238
+ { row: { id: '0' }, expected: false },
239
+ { row: { id: '1' }, expected: true },
240
+ { row: { id: null }, getRowId: undefined, expected: false },
241
+ { row: { id: undefined }, expected: false },
242
+ { row: { name: 'Test' }, expected: false },
243
+ ];
244
+
245
+ testCases.forEach(({ row, getRowId, expected }) => {
246
+ const result = hasValidRowId(row, getRowId);
247
+ expect(result).toBe(expected);
248
+ });
249
+ });
250
+ });
251
+