@jmruthers/pace-core 0.6.10 → 0.6.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (726) 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-SAXFG4XI.js → DataTable-EFYP2QLE.js} +10 -7
  24. package/dist/{InactivityServiceProvider-DHryoh6K.d.ts → InactivityServiceProvider-BbxwwDz1.d.ts} +10 -1
  25. package/dist/{UnifiedAuthProvider-CiBAl9-s.d.ts → UnifiedAuthProvider-Bkt_tzdS.d.ts} +56 -24
  26. package/dist/{api-F47QJ7FX.js → api-BZR2CYXL.js} +3 -2
  27. package/dist/api-result-USV1Czr-.d.ts +51 -0
  28. package/dist/{audit-Z6ZZBWLU.js → audit-HI2DHUVU.js} +2 -1
  29. package/dist/{auth-BZOJqrdd.d.ts → auth-JvdRVaud.d.ts} +1 -1
  30. package/dist/{chunk-KSNLMI7N.js → chunk-2DL2WSOE.js} +1 -155
  31. package/dist/{chunk-MPY44PWB.js → chunk-2OEVOGGR.js} +4648 -3560
  32. package/dist/chunk-44CNXN4P.js +15 -0
  33. package/dist/{chunk-Y4PF6HIM.js → chunk-4R3T5ENU.js} +867 -786
  34. package/dist/{chunk-LNHFAF4X.js → chunk-7A6IMHH2.js} +289 -247
  35. package/dist/chunk-CU2BU2MQ.js +2 -0
  36. package/dist/{chunk-JJEYZ3DX.js → chunk-D6BMFMQZ.js} +37 -2
  37. package/dist/{chunk-BCTXBU6U.js → chunk-ENLXB7GP.js} +88 -71
  38. package/dist/{chunk-FBZ7U3ID.js → chunk-J2KQK6DG.js} +937 -987
  39. package/dist/{chunk-TFIPNIPE.js → chunk-KJXRL3XE.js} +3300 -2245
  40. package/dist/{chunk-3GWSPISD.js → chunk-L5LFKKLJ.js} +1 -1
  41. package/dist/{chunk-X5EAU5G7.js → chunk-PCSHBLPB.js} +132 -114
  42. package/dist/{chunk-NIU6DPQV.js → chunk-QRYSEPHB.js} +2 -0
  43. package/dist/{chunk-KYURMOQM.js → chunk-V7FTM2LU.js} +423 -320
  44. package/dist/chunk-WY6Y7KC3.js +264 -0
  45. package/dist/{chunk-FN52B75D.js → chunk-XOJME5T7.js} +176 -15
  46. package/dist/{chunk-7YDC7LMU.js → chunk-XPFVT3GN.js} +71 -66
  47. package/dist/{chunk-66R6RLUZ.js → chunk-YFTFFJIV.js} +3 -3
  48. package/dist/{chunk-W46INAVW.js → chunk-YYTWKVHO.js} +688 -570
  49. package/dist/components.d.ts +8 -7
  50. package/dist/components.js +17 -15
  51. package/dist/{database.generated-DT8JTZiP.d.ts → database.generated-qkdoiVrJ.d.ts} +45 -10
  52. package/dist/eslint-rules/index.cjs +3 -0
  53. package/dist/eslint-rules/rules/03-architecture.cjs +74 -0
  54. package/dist/eslint-rules/rules/06-security-rbac.cjs +74 -0
  55. package/dist/{event-WTAQuGcq.d.ts → event-BfCox3N2.d.ts} +36 -10
  56. package/dist/{file-reference-BavO2eQj.d.ts → file-reference-DU1hcawx.d.ts} +29 -13
  57. package/dist/hooks.d.ts +22 -9
  58. package/dist/hooks.js +34 -25
  59. package/dist/icons/index.d.ts +1 -0
  60. package/dist/icons/index.js +1 -0
  61. package/dist/index.d.ts +66 -177
  62. package/dist/index.js +316 -340
  63. package/dist/pagination-BW1mqywp.d.ts +201 -0
  64. package/dist/providers.d.ts +6 -5
  65. package/dist/providers.js +5 -3
  66. package/dist/rbac/index.d.ts +123 -138
  67. package/dist/rbac/index.js +10 -8
  68. package/dist/theming/runtime.d.ts +19 -2
  69. package/dist/theming/runtime.js +1 -1
  70. package/dist/{timezone-K-ptz3HO.d.ts → timezone-BTWWXKVY.d.ts} +1 -1
  71. package/dist/types.d.ts +17 -10
  72. package/dist/types.js +1 -0
  73. package/dist/{usePublicPageContext-vxBlEHO9.d.ts → usePublicPageContext-B91dGYW1.d.ts} +433 -356
  74. package/dist/{usePublicRouteParams-G3Ks53mk.d.ts → usePublicRouteParams-BgV6VhMi.d.ts} +73 -4
  75. package/dist/utils.d.ts +163 -145
  76. package/dist/utils.js +42 -25
  77. package/docs/api/modules.md +782 -643
  78. package/docs/api-reference/rpc-functions.md +12 -3
  79. package/docs/core-concepts/rbac-system.md +8 -0
  80. package/docs/getting-started/cursor-rules.md +17 -20
  81. package/docs/getting-started/dependencies.md +1 -1
  82. package/docs/getting-started/setup.md +235 -0
  83. package/docs/implementation-guides/authentication.md +27 -0
  84. package/docs/implementation-guides/data-tables.md +176 -3
  85. package/docs/migration/ApiResult-migration.md +25 -0
  86. package/docs/rbac/api-reference.md +33 -31
  87. package/docs/standards/0-standards-overview.md +50 -15
  88. package/docs/standards/1-pace-core-compliance-standards.md +62 -57
  89. package/docs/standards/2-project-structure-standards.md +33 -16
  90. package/docs/standards/3-architecture-standards.md +41 -1
  91. package/docs/standards/4-code-quality-standards.md +26 -6
  92. package/docs/standards/5-styling-standards.md +35 -1
  93. package/docs/standards/6-security-rbac-standards.md +66 -0
  94. package/docs/standards/7-api-tech-stack-standards.md +25 -14
  95. package/docs/standards/8-testing-documentation-standards.md +31 -0
  96. package/docs/standards/9-operations-standards.md +19 -0
  97. package/docs/standards/README.md +20 -201
  98. package/docs/testing/test-setup-for-consumers.md +2 -0
  99. package/docs/troubleshooting/common-issues.md +17 -1
  100. package/docs/troubleshooting/organisation-context-setup.md +8 -0
  101. package/docs/troubleshooting/print-event-name-css-variable-analysis.md +217 -0
  102. package/eslint-config-pace-core.cjs +20 -0
  103. package/package.json +14 -20
  104. package/scripts/{build-docs-incremental.js → build-docs.js} +3 -2
  105. package/scripts/setup.cjs +536 -0
  106. package/scripts/validate.cjs +480 -0
  107. package/src/__tests__/helpers/{__tests__/component-test-utils.test.tsx → component-test-utils.test.tsx} +3 -3
  108. package/src/__tests__/helpers/{__tests__/optimized-test-setup.test.ts → optimized-test-setup.test.ts} +2 -2
  109. package/src/__tests__/helpers/{__tests__/supabaseMock.test.ts → supabaseMock.test.ts} +2 -2
  110. package/src/__tests__/helpers/{__tests__/test-providers.test.tsx → test-providers.test.tsx} +1 -1
  111. package/src/__tests__/helpers/test-providers.tsx +37 -39
  112. package/src/__tests__/helpers/{__tests__/test-utils.test.tsx → test-utils.test.tsx} +4 -3
  113. package/src/__tests__/helpers/{__tests__/timer-utils.test.ts → timer-utils.test.ts} +2 -2
  114. package/src/assets/app-icons/index.test.ts +304 -0
  115. package/src/components/AddressField/AddressField.test.tsx +1 -1
  116. package/src/components/AddressField/AddressField.tsx +238 -212
  117. package/src/components/Button/Button.tsx +1 -1
  118. package/src/components/Card/Card.test.tsx +172 -17
  119. package/src/components/Card/Card.tsx +19 -10
  120. package/src/components/ContextSelector/ContextSelector.internals.tsx +204 -0
  121. package/src/components/ContextSelector/{__tests__/ContextSelector.test.tsx → ContextSelector.test.tsx} +6 -6
  122. package/src/components/ContextSelector/ContextSelector.tsx +66 -280
  123. package/src/components/ContextSelector/ContextSelector.types.ts +35 -0
  124. package/src/components/ContextSelector/useContextSelectorState.tsx +195 -0
  125. package/src/components/DataTable/AUDIT_REPORT.md +59 -44
  126. package/src/components/DataTable/{__tests__/DataTable.comprehensive.test.tsx → DataTable.comprehensive.test.tsx} +6 -6
  127. package/src/components/DataTable/{__tests__/DataTable.default-state.test.tsx → DataTable.default-state.test.tsx} +5 -5
  128. package/src/components/DataTable/{__tests__/DataTable.export.test.tsx → DataTable.export.test.tsx} +10 -10
  129. package/src/components/DataTable/{__tests__/DataTable.grouping-aggregation.test.tsx → DataTable.grouping-aggregation.test.tsx} +6 -6
  130. package/src/components/DataTable/{__tests__/DataTable.hooks.test.tsx → DataTable.hooks.test.tsx} +6 -6
  131. package/src/components/DataTable/{__tests__/DataTable.select-label-display.test.tsx → DataTable.select-label-display.test.tsx} +6 -6
  132. package/src/components/DataTable/DataTable.test.tsx +787 -416
  133. package/src/components/DataTable/DataTable.tsx +12 -12
  134. package/src/components/DataTable/DataTableCore.integration.test.tsx +458 -0
  135. package/src/components/DataTable/{__tests__/DataTableCore.test-setup.ts → DataTableCore.test-setup.ts} +10 -9
  136. package/src/components/DataTable/{__tests__/DataTableCore.test.tsx → DataTableCore.test.tsx} +8 -8
  137. package/src/components/DataTable/{__tests__/README.md → README.md} +17 -7
  138. package/src/components/DataTable/TESTING.md +101 -0
  139. package/src/components/DataTable/{__tests__/a11y.basic.test.tsx → a11y.basic.test.tsx} +34 -34
  140. package/src/components/DataTable/components/DataTableCore.tsx +104 -864
  141. package/src/components/DataTable/components/{__tests__/GroupingDropdown.test.tsx → GroupingDropdown.test.tsx} +17 -8
  142. package/src/components/DataTable/components/GroupingDropdown.tsx +2 -2
  143. package/src/components/DataTable/components/ImportModal.tsx +61 -559
  144. package/src/components/DataTable/components/ImportModalFileSection.tsx +148 -0
  145. package/src/components/DataTable/context/{__tests__/DataTableContext.test.tsx → DataTableContext.test.tsx} +2 -2
  146. package/src/components/DataTable/context/DataTableContext.tsx +7 -6
  147. package/src/components/DataTable/core/{__tests__/ColumnFactory.test.ts → ColumnFactory.test.ts} +2 -2
  148. package/src/components/DataTable/hooks/{__tests__/useColumnOrderPersistence.test.ts → useColumnOrderPersistence.test.ts} +2 -2
  149. package/src/components/DataTable/hooks/{__tests__/useColumnVisibilityPersistence.test.ts → useColumnVisibilityPersistence.test.ts} +2 -2
  150. package/src/components/DataTable/hooks/{__tests__/useDataTableConfiguration.test.ts → useDataTableConfiguration.test.ts} +3 -3
  151. package/src/components/DataTable/hooks/useDataTableConfiguration.ts +14 -2
  152. package/src/components/DataTable/hooks/{__tests__/useDataTableDataPipeline.test.ts → useDataTableDataPipeline.test.ts} +6 -6
  153. package/src/components/DataTable/hooks/useDataTableDeletionBatching.test.ts +127 -0
  154. package/src/components/DataTable/hooks/useDataTableDeletionBatching.ts +106 -0
  155. package/src/components/DataTable/hooks/useDataTableEffectiveActions.test.ts +461 -0
  156. package/src/components/DataTable/hooks/useDataTableEffectiveActions.ts +238 -0
  157. package/src/components/DataTable/hooks/useDataTableLayoutHandlers.test.ts +296 -0
  158. package/src/components/DataTable/hooks/useDataTableLayoutHandlers.ts +175 -0
  159. package/src/components/DataTable/hooks/useDataTablePaginationSync.test.ts +203 -0
  160. package/src/components/DataTable/hooks/useDataTablePaginationSync.ts +109 -0
  161. package/src/components/DataTable/hooks/{__tests__/useDataTablePermissions.test.ts → useDataTablePermissions.test.ts} +11 -11
  162. package/src/components/DataTable/hooks/useDataTablePermissions.ts +79 -247
  163. package/src/components/DataTable/hooks/useDataTablePipeline.test.tsx +219 -0
  164. package/src/components/DataTable/hooks/useDataTablePipeline.tsx +239 -0
  165. package/src/components/DataTable/hooks/useDataTableRenderGuard.test.tsx +316 -0
  166. package/src/components/DataTable/hooks/useDataTableRenderGuard.tsx +195 -0
  167. package/src/components/DataTable/hooks/useDataTableScope.test.ts +110 -0
  168. package/src/components/DataTable/hooks/useDataTableScope.ts +123 -0
  169. package/src/components/DataTable/hooks/{__tests__/useDataTableState.test.ts → useDataTableState.test.ts} +47 -5
  170. package/src/components/DataTable/hooks/useDataTableState.ts +145 -94
  171. package/src/components/DataTable/hooks/useDataTableStateAndPersistence.test.ts +277 -0
  172. package/src/components/DataTable/hooks/useDataTableStateAndPersistence.ts +222 -0
  173. package/src/components/DataTable/hooks/useDataTableSuperAdmin.test.ts +93 -0
  174. package/src/components/DataTable/hooks/useDataTableSuperAdmin.ts +86 -0
  175. package/src/components/DataTable/hooks/useDataTableTableInstance.test.ts +185 -0
  176. package/src/components/DataTable/hooks/useDataTableTableInstance.ts +178 -0
  177. package/src/components/DataTable/hooks/{__tests__/useEffectiveColumnOrder.test.ts → useEffectiveColumnOrder.test.ts} +2 -2
  178. package/src/components/DataTable/hooks/{__tests__/useHierarchicalState.test.ts → useHierarchicalState.test.ts} +2 -2
  179. package/src/components/DataTable/{components/hooks → hooks}/useImportModalFocus.test.ts +3 -3
  180. package/src/components/DataTable/{components/hooks → hooks}/useImportModalFocus.ts +2 -2
  181. package/src/components/DataTable/hooks/useImportModalState.test.ts +390 -0
  182. package/src/components/DataTable/hooks/useImportModalState.ts +345 -0
  183. package/src/components/DataTable/hooks/{__tests__/useKeyboardNavigation.test.ts → useKeyboardNavigation.test.ts} +3 -3
  184. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +309 -269
  185. package/src/components/DataTable/{components/hooks → hooks}/usePermissionTracking.test.ts +3 -3
  186. package/src/components/DataTable/{components/hooks → hooks}/usePermissionTracking.ts +3 -3
  187. package/src/components/DataTable/hooks/{__tests__/useServerSideDataEffect.test.ts → useServerSideDataEffect.test.ts} +2 -2
  188. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +14 -3
  189. package/src/components/DataTable/hooks/{__tests__/useTableColumns.test.ts → useTableColumns.test.ts} +2 -2
  190. package/src/components/DataTable/hooks/{__tests__/useTableHandlers.test.ts → useTableHandlers.test.ts} +25 -4
  191. package/src/components/DataTable/hooks/useTableHandlers.ts +5 -2
  192. package/src/components/DataTable/index.ts +18 -17
  193. package/src/components/DataTable/{__tests__/keyboard.test.tsx → keyboard.test.tsx} +41 -63
  194. package/src/components/DataTable/{__tests__/mocks → mocks}/MockRBACProvider.tsx +1 -1
  195. package/src/components/DataTable/{__tests__/pagination.modes.test.tsx → pagination.modes.test.tsx} +6 -6
  196. package/src/components/DataTable/{__tests__/ssr.strict-mode.test.tsx → ssr.strict-mode.test.tsx} +2 -2
  197. package/src/components/DataTable/{__tests__/styles.test.ts → styles.test.ts} +1 -4
  198. package/src/components/DataTable/styles.ts +0 -1
  199. package/src/components/DataTable/test-utils/MockDataTableComponents.tsx +55 -0
  200. package/src/components/DataTable/{__tests__/test-utils → test-utils}/dataFactories.ts +2 -2
  201. package/src/components/DataTable/test-utils/featureConfig.ts +10 -0
  202. package/src/components/DataTable/{__tests__/test-utils/sharedTestUtils.tsx → test-utils/sharedTestUtils.ts} +97 -66
  203. package/src/components/DataTable/{__tests__/test-utils.ts → test-utils.ts} +1 -1
  204. package/src/components/DataTable/types/actions.ts +71 -0
  205. package/src/components/DataTable/types/base.ts +39 -0
  206. package/src/components/DataTable/types/columns.ts +125 -0
  207. package/src/components/DataTable/types/export.ts +32 -0
  208. package/src/components/DataTable/types/features.ts +81 -0
  209. package/src/components/DataTable/types/hierarchical.ts +44 -0
  210. package/src/components/DataTable/types/index.ts +43 -0
  211. package/src/components/DataTable/types/pagination.ts +85 -0
  212. package/src/components/DataTable/types/performance.ts +47 -0
  213. package/src/components/DataTable/types/props.ts +62 -0
  214. package/src/components/DataTable/types/rbac.ts +45 -0
  215. package/src/components/DataTable/{components/__tests__ → ui/layout}/DataTableCore.test.tsx +430 -28
  216. package/src/components/DataTable/ui/layout/DataTableCore.tsx +345 -0
  217. package/src/components/DataTable/{components/__tests__ → ui/layout}/DataTableErrorBoundary.test.tsx +4 -4
  218. package/src/components/DataTable/{components → ui/layout}/DataTableErrorBoundary.tsx +7 -7
  219. package/src/components/DataTable/ui/layout/DataTableLayout.test.tsx +1352 -0
  220. package/src/components/DataTable/ui/layout/DataTableLayout.tsx +661 -0
  221. package/src/components/DataTable/ui/modals/BulkDeleteConfirmDialog.test.tsx +91 -0
  222. package/src/components/DataTable/ui/modals/BulkDeleteConfirmDialog.tsx +43 -0
  223. package/src/components/DataTable/ui/modals/DataTableModals.test.tsx +749 -0
  224. package/src/components/DataTable/{components → ui/modals}/DataTableModals.tsx +36 -28
  225. package/src/components/DataTable/ui/modals/ImportModal.test.tsx +1834 -0
  226. package/src/components/DataTable/ui/modals/ImportModal.tsx +197 -0
  227. package/src/components/DataTable/ui/modals/ImportModalFailedRowsSection.tsx +60 -0
  228. package/src/components/DataTable/ui/modals/ImportModalFileSection.tsx +148 -0
  229. package/src/components/DataTable/ui/modals/ImportModalPreviewSection.tsx +60 -0
  230. package/src/components/DataTable/ui/modals/ImportModalSummarySection.tsx +59 -0
  231. package/src/components/DataTable/ui/modals/importModalPersistence.ts +73 -0
  232. package/src/components/DataTable/{components/__tests__ → ui/shared}/AccessDeniedPage.test.tsx +2 -2
  233. package/src/components/DataTable/{components → ui/shared}/AccessDeniedPage.tsx +2 -2
  234. package/src/components/DataTable/{components/__tests__ → ui/shared}/ActionButtons.test.tsx +6 -4
  235. package/src/components/DataTable/{components → ui/shared}/ActionButtons.tsx +4 -4
  236. package/src/components/DataTable/{components/__tests__ → ui/shared}/ColumnFilter.test.tsx +29 -16
  237. package/src/components/DataTable/{components → ui/shared}/ColumnFilter.tsx +4 -4
  238. package/src/components/DataTable/{components/__tests__ → ui/shared}/PaginationControls.test.tsx +38 -16
  239. package/src/components/DataTable/{components → ui/shared}/PaginationControls.tsx +21 -15
  240. package/src/components/DataTable/{components/__tests__ → ui/shared}/SortIndicator.test.tsx +2 -2
  241. package/src/components/DataTable/{components → ui/shared}/SortIndicator.tsx +1 -1
  242. package/src/components/DataTable/{components/__tests__ → ui/table}/EditFields.test.tsx +3 -3
  243. package/src/components/DataTable/{components → ui/table}/EditFields.tsx +138 -69
  244. package/src/components/DataTable/{components/__tests__ → ui/table}/EditableRow.test.tsx +36 -27
  245. package/src/components/DataTable/{components → ui/table}/EditableRow.tsx +86 -104
  246. package/src/components/DataTable/{components/__tests__ → ui/table}/EmptyState.test.tsx +2 -62
  247. package/src/components/DataTable/{components → ui/table}/EmptyState.tsx +7 -15
  248. package/src/components/DataTable/{components/__tests__ → ui/table}/FilterRow.test.tsx +5 -4
  249. package/src/components/DataTable/{components → ui/table}/FilterRow.tsx +3 -3
  250. package/src/components/DataTable/{components/__tests__ → ui/table}/LoadingState.test.tsx +6 -10
  251. package/src/components/DataTable/{components → ui/table}/LoadingState.tsx +4 -4
  252. package/src/components/DataTable/{components/__tests__ → ui/table}/RowComponent.test.tsx +412 -17
  253. package/src/components/DataTable/{components → ui/table}/RowComponent.tsx +183 -177
  254. package/src/components/DataTable/{components/__tests__ → ui/table}/UnifiedTableBody.test.tsx +425 -16
  255. package/src/components/DataTable/ui/table/UnifiedTableBody.tsx +440 -0
  256. package/src/components/DataTable/{components/__tests__ → ui/table}/cellValueUtils.test.ts +2 -2
  257. package/src/components/DataTable/{components → ui/table}/cellValueUtils.ts +1 -1
  258. package/src/components/DataTable/{components/__tests__ → ui/toolbar}/BulkOperationsDropdown.test.tsx +12 -5
  259. package/src/components/DataTable/{components → ui/toolbar}/BulkOperationsDropdown.tsx +3 -3
  260. package/src/components/DataTable/{components/__tests__ → ui/toolbar}/ColumnVisibilityDropdown.test.tsx +7 -4
  261. package/src/components/DataTable/{components → ui/toolbar}/ColumnVisibilityDropdown.tsx +7 -7
  262. package/src/components/DataTable/{components/__tests__ → ui/toolbar}/DataTableToolbar.test.tsx +4 -4
  263. package/src/components/DataTable/{components → ui/toolbar}/DataTableToolbar.tsx +4 -4
  264. package/src/components/DataTable/ui/toolbar/GroupingDropdown.test.tsx +621 -0
  265. package/src/components/DataTable/ui/toolbar/GroupingDropdown.tsx +107 -0
  266. package/src/components/DataTable/utils/{__tests__/a11yUtils.test.ts → a11yUtils.test.ts} +2 -2
  267. package/src/components/DataTable/utils/{__tests__/aggregationUtils.test.ts → aggregationUtils.test.ts} +3 -3
  268. package/src/components/DataTable/utils/{__tests__/columnUtils.test.ts → columnUtils.test.ts} +2 -2
  269. package/src/components/DataTable/utils/csvParse.test.ts +74 -0
  270. package/src/components/DataTable/utils/csvParse.ts +65 -0
  271. package/src/components/DataTable/utils/{__tests__/errorHandling.test.ts → errorHandling.test.ts} +2 -2
  272. package/src/components/DataTable/utils/{__tests__/exportUtils.test.ts → exportUtils.test.ts} +3 -3
  273. package/src/components/DataTable/utils/{__tests__/flexibleImport.test.ts → flexibleImport.test.ts} +2 -2
  274. package/src/components/DataTable/utils/flexibleImport.ts +3 -186
  275. package/src/components/DataTable/utils/{__tests__/hierarchicalSorting.test.ts → hierarchicalSorting.test.ts} +3 -3
  276. package/src/components/DataTable/utils/{__tests__/hierarchicalUtils.test.ts → hierarchicalUtils.test.ts} +3 -3
  277. package/src/components/DataTable/utils/importDateParser.test.ts +162 -0
  278. package/src/components/DataTable/utils/importDateParser.ts +114 -0
  279. package/src/components/DataTable/utils/importValueParser.test.ts +138 -0
  280. package/src/components/DataTable/utils/importValueParser.ts +91 -0
  281. package/src/components/DataTable/utils/{__tests__/paginationUtils.test.ts → paginationUtils.test.ts} +2 -2
  282. package/src/components/DataTable/utils/paginationUtils.ts +6 -3
  283. package/src/components/DataTable/utils/{__tests__/performanceUtils.test.ts → performanceUtils.test.ts} +3 -3
  284. package/src/components/DataTable/utils/{__tests__/rowUtils.test.ts → rowUtils.test.ts} +3 -3
  285. package/src/components/DataTable/utils/{__tests__/selectFieldUtils.test.ts → selectFieldUtils.test.ts} +66 -3
  286. package/src/components/DataTable/utils/selectFieldUtils.ts +97 -60
  287. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +1 -1
  288. package/src/components/DateTimeField/DateTimeField.test.tsx +1 -1
  289. package/src/components/Dialog/Dialog.test-utils.ts +49 -0
  290. package/src/components/Dialog/Dialog.test.tsx +896 -89
  291. package/src/components/Dialog/Dialog.tsx +174 -882
  292. package/src/components/Dialog/dialogLock.test.ts +238 -0
  293. package/src/components/Dialog/dialogLock.ts +98 -0
  294. package/src/components/Dialog/index.ts +2 -0
  295. package/src/components/Dialog/useDialogDimensions.test.ts +163 -0
  296. package/src/components/Dialog/useDialogDimensions.ts +140 -0
  297. package/src/components/Dialog/useDialogLifecycle.test.ts +358 -0
  298. package/src/components/Dialog/useDialogLifecycle.ts +135 -0
  299. package/src/components/Dialog/useDialogPersistence.test.ts +381 -0
  300. package/src/components/Dialog/useDialogPersistence.ts +357 -0
  301. package/src/components/FileDisplay/FileDisplay.test.tsx +40 -40
  302. package/src/components/FileDisplay/FileDisplay.tsx +24 -656
  303. package/src/components/FileDisplay/FileDisplayContent.test.tsx +395 -0
  304. package/src/components/FileDisplay/FileDisplayContent.tsx +242 -0
  305. package/src/components/FileDisplay/FileDisplayDeleteConfirmDialog.test.tsx +74 -0
  306. package/src/components/FileDisplay/FileDisplayDeleteConfirmDialog.tsx +38 -0
  307. package/src/components/FileDisplay/FileDisplayEmptyView.test.tsx +33 -0
  308. package/src/components/FileDisplay/FileDisplayEmptyView.tsx +33 -0
  309. package/src/components/FileDisplay/FileDisplayErrorView.test.tsx +71 -0
  310. package/src/components/FileDisplay/FileDisplayErrorView.tsx +50 -0
  311. package/src/components/FileDisplay/FileDisplayLoadingFallbackView.test.tsx +22 -0
  312. package/src/components/FileDisplay/FileDisplayLoadingFallbackView.tsx +22 -0
  313. package/src/components/FileDisplay/FileDisplayLoadingView.test.tsx +21 -0
  314. package/src/components/FileDisplay/FileDisplayLoadingView.tsx +23 -0
  315. package/src/components/FileDisplay/FileDisplayMultipleFilesView.test.tsx +101 -0
  316. package/src/components/FileDisplay/FileDisplayMultipleFilesView.tsx +109 -0
  317. package/src/components/FileDisplay/FileDisplaySingleDocumentLinkView.test.tsx +58 -0
  318. package/src/components/FileDisplay/FileDisplaySingleDocumentLinkView.tsx +48 -0
  319. package/src/components/FileDisplay/FileDisplaySingleFileWithActionsView.test.tsx +111 -0
  320. package/src/components/FileDisplay/FileDisplaySingleFileWithActionsView.tsx +270 -0
  321. package/src/components/FileDisplay/FileDisplaySingleImageView.test.tsx +78 -0
  322. package/src/components/FileDisplay/FileDisplaySingleImageView.tsx +67 -0
  323. package/src/components/FileDisplay/fallbackUtils.test.ts +50 -0
  324. package/src/components/FileDisplay/fallbackUtils.ts +44 -0
  325. package/src/components/FileDisplay/fetchFileDisplayData.ts +24 -0
  326. package/src/components/FileDisplay/fetchFileDisplayData.unit.test.ts +183 -0
  327. package/src/components/FileDisplay/fileDisplayUtils.test.ts +58 -0
  328. package/src/components/FileDisplay/fileDisplayUtils.ts +24 -0
  329. package/src/{hooks/__tests__ → components/FileDisplay}/useFileDisplay.test.ts +40 -42
  330. package/src/components/FileDisplay/useFileDisplay.ts +515 -0
  331. package/src/{hooks/__tests__ → components/FileDisplay}/useFileDisplay.unit.test.ts +406 -77
  332. package/src/components/FileDisplay/useFileDisplayData.ts +126 -0
  333. package/src/{hooks/public → components/FileDisplay}/usePublicFileDisplay.test.ts +94 -88
  334. package/src/components/FileDisplay/usePublicFileDisplay.ts +579 -0
  335. package/src/components/FileUpload/FileUpload.test.tsx +16 -10
  336. package/src/components/FileUpload/FileUpload.tsx +107 -525
  337. package/src/components/FileUpload/FileUploadDropZone.tsx +112 -0
  338. package/src/components/FileUpload/FileUploadProgressItem.tsx +86 -0
  339. package/src/components/FileUpload/FileUploadProgressList.tsx +40 -0
  340. package/src/components/FileUpload/useFileUploadManager.test.ts +308 -0
  341. package/src/components/FileUpload/useFileUploadManager.ts +454 -0
  342. package/src/components/FileUpload/useResolvedAppId.test.ts +102 -0
  343. package/src/components/FileUpload/useResolvedAppId.ts +77 -0
  344. package/src/components/Footer/Footer.test.tsx +6 -292
  345. package/src/components/Footer/Footer.tsx +8 -125
  346. package/src/components/Form/Form.test.tsx +44 -27
  347. package/src/components/Form/Form.tsx +64 -287
  348. package/src/components/Form/useFormPersistence.ts +257 -0
  349. package/src/components/Header/Header.test.tsx +17 -18
  350. package/src/components/Header/Header.tsx +10 -1
  351. package/src/components/Input/Input.tsx +1 -1
  352. package/src/components/Label/Label.test.tsx +1 -1
  353. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +1 -1
  354. package/src/components/NavigationMenu/HierarchicalNavItem.tsx +104 -0
  355. package/src/components/NavigationMenu/NavigationMenu.test.tsx +1029 -26
  356. package/src/components/NavigationMenu/NavigationMenu.tsx +61 -361
  357. package/src/components/NavigationMenu/index.ts +6 -1
  358. package/src/components/NavigationMenu/navigationPermissionHelper.ts +188 -0
  359. package/src/components/NavigationMenu/{__tests__/useNavigationFiltering.test.ts → useNavigationFiltering.test.ts} +68 -53
  360. package/src/components/NavigationMenu/useNavigationFiltering.ts +197 -296
  361. package/src/components/NavigationMenu/useNavigationScope.ts +125 -0
  362. package/src/components/PaceAppLayout/PaceAppLayout.edge-cases.test.tsx +77 -62
  363. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +3 -3
  364. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +16 -19
  365. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +529 -5
  366. package/src/components/PaceAppLayout/PaceAppLayout.tsx +280 -756
  367. package/src/components/PaceAppLayout/useFilteredNavItems.ts +304 -0
  368. package/src/components/PaceAppLayout/usePaceAppLayoutConfig.ts +142 -0
  369. package/src/components/PaceAppLayout/usePaceAppLayoutGate.tsx +150 -0
  370. package/src/components/PaceAppLayout/usePaceAppLayoutPermissions.ts +162 -0
  371. package/src/components/PaceAppLayout/usePaceAppLayoutScope.ts +79 -0
  372. package/src/components/PaceAppLayout/useRoleBasedRouteAccess.ts +157 -0
  373. package/src/components/PaceAppLayout/useSuperAdminFallback.ts +58 -0
  374. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +31 -25
  375. package/src/components/PaceLoginPage/PaceLoginPage.tsx +31 -122
  376. package/src/components/PaceLoginPage/useLoginAppAccess.ts +153 -0
  377. package/src/components/Progress/Progress.tsx +1 -2
  378. package/src/components/ProtectedRoute/ProtectedRoute.tsx +29 -235
  379. package/src/components/ProtectedRoute/useProtectedRouteState.ts +128 -0
  380. package/src/components/ProtectedRoute/useVisibilityRedirectGrace.ts +89 -0
  381. package/src/components/PublicLayout/PublicLayout.test.tsx +217 -36
  382. package/src/components/PublicLayout/PublicPageLayout.tsx +132 -73
  383. package/src/components/PublicLayout/PublicPageProvider.tsx +5 -1
  384. package/src/components/Select/Select.test.tsx +1 -1
  385. package/src/components/Select/Select.tsx +28 -18
  386. package/src/components/Select/{__tests__/context.test.tsx → context.test.tsx} +3 -3
  387. package/src/components/Select/{utils/__tests__/text.test.tsx → text.test.tsx} +2 -2
  388. package/src/components/Select/{utils/text.ts → text.ts} +1 -1
  389. package/src/components/Select/{hooks/__tests__/useSelectEvents.test.ts → useSelectEvents.test.ts} +5 -5
  390. package/src/components/Select/{hooks/useSelectEvents.ts → useSelectEvents.ts} +2 -2
  391. package/src/components/Select/{hooks/__tests__/useSelectSearch.test.tsx → useSelectSearch.test.tsx} +7 -7
  392. package/src/components/Select/{hooks/useSelectSearch.ts → useSelectSearch.ts} +2 -2
  393. package/src/components/Select/{hooks/__tests__/useSelectState.test.ts → useSelectState.test.ts} +16 -2
  394. package/src/components/Select/{hooks/useSelectState.ts → useSelectState.ts} +3 -3
  395. package/src/components/Table/Table.test.tsx +348 -0
  396. package/src/components/Tabs/Tabs.test.tsx +270 -0
  397. package/src/components/Tabs/Tabs.tsx +1 -1
  398. package/src/components/Toast/Toast.test.tsx +420 -0
  399. package/src/components/{__tests__/index.test.ts → index.test.ts} +2 -2
  400. package/src/constants/{__tests__/performance.test.ts → performance.test.ts} +2 -2
  401. package/src/hooks/{__tests__/ServiceHooks.test.tsx → ServiceHooks.test.tsx} +8 -8
  402. package/src/hooks/{__tests__/hooks.integration.test.tsx → hooks.integration.test.tsx} +11 -11
  403. package/src/hooks/index.ts +7 -4
  404. package/src/hooks/{__tests__/index.unit.test.ts → index.unit.test.ts} +2 -2
  405. package/src/hooks/public/usePublicEvent.test.ts +1 -1
  406. package/src/hooks/public/usePublicEventLogo.test.ts +1 -1
  407. package/src/hooks/public/usePublicRouteParams.test.ts +1 -1
  408. package/src/hooks/services/useAuth.ts +9 -7
  409. package/src/hooks/useAddressAutocomplete.test.ts +22 -22
  410. package/src/hooks/useAddressAutocomplete.ts +90 -75
  411. package/src/hooks/{__tests__/useAppConfig.unit.test.ts → useAppConfig.unit.test.ts} +328 -22
  412. package/src/hooks/{__tests__/useComponentPerformance.unit.test.tsx → useComponentPerformance.unit.test.tsx} +27 -41
  413. package/src/hooks/useDataTablePerformance.ts +100 -120
  414. package/src/hooks/{__tests__/useDataTablePerformance.unit.test.ts → useDataTablePerformance.unit.test.ts} +5 -5
  415. package/src/hooks/{__tests__/useDataTableState.test.ts → useDataTableState.test.ts} +2 -2
  416. package/src/hooks/{__tests__/useDebounce.unit.test.ts → useDebounce.unit.test.ts} +2 -2
  417. package/src/hooks/useEventTheme.test.ts +4 -1
  418. package/src/hooks/useEventTheme.ts +49 -21
  419. package/src/hooks/useEvents.ts +41 -1
  420. package/src/hooks/{__tests__/useEvents.unit.test.ts → useEvents.unit.test.ts} +5 -5
  421. package/src/hooks/useFileReference.test.ts +44 -41
  422. package/src/hooks/useFileReference.ts +182 -173
  423. package/src/hooks/useFileUrl.ts +1 -1
  424. package/src/hooks/{__tests__/useFileUrl.unit.test.ts → useFileUrl.unit.test.ts} +26 -36
  425. package/src/hooks/{__tests__/useFileUrlCache.test.ts → useFileUrlCache.test.ts} +8 -8
  426. package/src/hooks/useFileUrlCache.ts +1 -1
  427. package/src/hooks/{__tests__/useFocusManagement.unit.test.ts → useFocusManagement.unit.test.ts} +2 -2
  428. package/src/hooks/{__tests__/useFocusTrap.unit.test.tsx → useFocusTrap.unit.test.tsx} +2 -2
  429. package/src/hooks/{__tests__/useFormDialog.test.ts → useFormDialog.test.ts} +2 -2
  430. package/src/hooks/useInactivityTracker.ts +138 -131
  431. package/src/hooks/{__tests__/useInactivityTracker.unit.test.ts → useInactivityTracker.unit.test.ts} +3 -3
  432. package/src/hooks/{__tests__/useIsMobile.unit.test.ts → useIsMobile.unit.test.ts} +2 -2
  433. package/src/hooks/useIsPrint.ts +62 -0
  434. package/src/hooks/useIsPrint.unit.test.ts +545 -0
  435. package/src/hooks/{__tests__/useKeyboardShortcuts.unit.test.ts → useKeyboardShortcuts.unit.test.ts} +2 -2
  436. package/src/hooks/{__tests__/useOrganisationPermissions.unit.test.tsx → useOrganisationPermissions.unit.test.tsx} +4 -4
  437. package/src/hooks/useOrganisationSecurity.test.ts +3 -3
  438. package/src/hooks/useOrganisationSecurity.ts +190 -201
  439. package/src/hooks/{__tests__/useOrganisationSecurity.unit.test.tsx → useOrganisationSecurity.unit.test.tsx} +61 -63
  440. package/src/hooks/{__tests__/useOrganisations.unit.test.ts → useOrganisations.unit.test.ts} +5 -5
  441. package/src/hooks/{__tests__/usePerformanceMonitor.unit.test.ts → usePerformanceMonitor.unit.test.ts} +13 -14
  442. package/src/hooks/{__tests__/usePermissionCache.test.ts → usePermissionCache.test.ts} +26 -27
  443. package/src/hooks/usePermissionCache.ts +276 -271
  444. package/src/hooks/{__tests__/usePreventTabReload.test.ts → usePreventTabReload.test.ts} +2 -2
  445. package/src/hooks/{__tests__/usePublicEvent.simple.test.ts → usePublicEvent.simple.test.ts} +4 -4
  446. package/src/hooks/{__tests__/usePublicEvent.test.ts → usePublicEvent.test.ts} +4 -4
  447. package/src/hooks/{__tests__/usePublicEvent.unit.test.ts → usePublicEvent.unit.test.ts} +4 -4
  448. package/src/hooks/{__tests__/usePublicFileDisplay.test.ts → usePublicFileDisplay.test.ts} +12 -12
  449. package/src/hooks/{__tests__/usePublicRouteParams.unit.test.ts → usePublicRouteParams.unit.test.ts} +3 -3
  450. package/src/hooks/{__tests__/useQueryCache.test.ts → useQueryCache.test.ts} +2 -2
  451. package/src/hooks/useQueryCache.ts +0 -2
  452. package/src/hooks/{__tests__/useRBAC.unit.test.ts → useRBAC.unit.test.ts} +55 -38
  453. package/src/hooks/{__tests__/useSessionDraft.test.ts → useSessionDraft.test.ts} +2 -2
  454. package/src/hooks/{__tests__/useSessionRestoration.unit.test.tsx → useSessionRestoration.unit.test.tsx} +10 -19
  455. package/src/hooks/useStorage.ts +21 -16
  456. package/src/hooks/{__tests__/useStorage.unit.test.ts → useStorage.unit.test.ts} +38 -75
  457. package/src/hooks/{__tests__/useToast.test.ts → useToast.test.ts} +2 -2
  458. package/src/hooks/{__tests__/useToast.unit.test.tsx → useToast.unit.test.tsx} +2 -2
  459. package/src/hooks/{__tests__/useZodForm.unit.test.tsx → useZodForm.unit.test.tsx} +2 -2
  460. package/src/icons/{__tests__/index.test.ts → index.test.ts} +2 -2
  461. package/src/icons/index.ts +2 -0
  462. package/src/{__tests__/index.test.ts → index.test.ts} +3 -7
  463. package/src/index.ts +15 -7
  464. package/src/providers/{__tests__/AuthProvider.test.tsx → AuthProvider.test.tsx} +3 -3
  465. package/src/providers/{__tests__/EventProvider.test.tsx → EventProvider.test.tsx} +3 -3
  466. package/src/providers/InactivityProvider.test-helper.tsx +40 -0
  467. package/src/providers/{__tests__/InactivityProvider.test.tsx → InactivityProvider.test.tsx} +14 -21
  468. package/src/providers/{__tests__/ProviderLifecycle.test.tsx → ProviderLifecycle.test.tsx} +4 -4
  469. package/src/providers/{__tests__/UnifiedAuthProvider.test.tsx → UnifiedAuthProvider.test.tsx} +1 -1
  470. package/src/providers/{__tests__/index.test.ts → index.test.ts} +2 -2
  471. package/src/providers/services/{__tests__/AuthServiceProvider.integration.test.tsx → AuthServiceProvider.integration.test.tsx} +4 -4
  472. package/src/providers/services/{__tests__/AuthServiceProvider.test.tsx → AuthServiceProvider.test.tsx} +7 -7
  473. package/src/providers/services/{__tests__/EventServiceProvider.test.tsx → EventServiceProvider.test.tsx} +7 -7
  474. package/src/providers/services/{__tests__/InactivityServiceProvider.test.tsx → InactivityServiceProvider.test.tsx} +5 -5
  475. package/src/providers/services/{__tests__/OrganisationServiceProvider.test.tsx → OrganisationServiceProvider.test.tsx} +6 -6
  476. package/src/providers/services/UnifiedAuthContext.ts +30 -27
  477. package/src/providers/services/{__tests__/UnifiedAuthProvider.advanced.test.tsx → UnifiedAuthProvider.advanced.test.tsx} +8 -9
  478. package/src/providers/services/{__tests__/UnifiedAuthProvider.appId.test.tsx → UnifiedAuthProvider.appId.test.tsx} +25 -25
  479. package/src/providers/services/{__tests__/UnifiedAuthProvider.integration.test.tsx → UnifiedAuthProvider.integration.test.tsx} +14 -11
  480. package/src/providers/services/UnifiedAuthProvider.tsx +115 -360
  481. package/src/providers/services/{__tests__/contexts.test.tsx → contexts.test.tsx} +6 -6
  482. package/src/providers/services/{__tests__/useUnifiedAuth.test.tsx → useUnifiedAuth.test.tsx} +6 -6
  483. package/src/providers/services/useUnifiedAuthContextValue.ts +279 -0
  484. package/src/providers/useInactivity.test-helper.ts +27 -0
  485. package/src/rbac/{__tests__/adapters.comprehensive.test.tsx → adapters.comprehensive.test.tsx} +24 -24
  486. package/src/rbac/adapters.test.tsx +22 -22
  487. package/src/rbac/adapters.tsx +29 -29
  488. package/src/rbac/api.test.ts +973 -42
  489. package/src/rbac/api.ts +228 -253
  490. package/src/rbac/{__tests__/audit-batched.test.ts → audit-batched.test.ts} +6 -6
  491. package/src/rbac/audit.ts +4 -1
  492. package/src/rbac/{__tests__/auth-rbac-security.integration.test.tsx → auth-rbac-security.integration.test.tsx} +1 -1
  493. package/src/rbac/{__tests__/auth-rbac.e2e.test.tsx → auth-rbac.e2e.test.tsx} +27 -34
  494. package/src/rbac/cache-invalidation.test.ts +715 -0
  495. package/src/rbac/components/{__tests__/AccessDenied.test.tsx → AccessDenied.test.tsx} +3 -3
  496. package/src/rbac/components/{__tests__/NavigationGuard.test.tsx → NavigationGuard.test.tsx} +13 -11
  497. package/src/{__tests__/rbac/PagePermissionGuard.test.tsx → rbac/components/PagePermissionGuard.guard.test.tsx} +33 -19
  498. package/src/rbac/components/{__tests__/PagePermissionGuard.performance.test.tsx → PagePermissionGuard.performance.test.tsx} +30 -9
  499. package/src/rbac/components/{__tests__/PagePermissionGuard.race-condition.test.tsx → PagePermissionGuard.race-condition.test.tsx} +7 -7
  500. package/src/rbac/components/{__tests__/PagePermissionGuard.test.tsx → PagePermissionGuard.test.tsx} +10 -10
  501. package/src/rbac/components/PagePermissionGuard.tsx +177 -372
  502. package/src/rbac/components/{__tests__/PagePermissionGuard.verification.test.tsx → PagePermissionGuard.verification.test.tsx} +7 -7
  503. package/src/rbac/config.ts +58 -18
  504. package/src/rbac/{__tests__/engine.comprehensive.test.ts → engine.comprehensive.test.ts} +3 -3
  505. package/src/rbac/engine.test.ts +494 -0
  506. package/src/rbac/errors.ts +89 -55
  507. package/src/rbac/hooks/permissions/runPermissionCheck.ts +77 -0
  508. package/src/rbac/hooks/permissions/{__tests__/useAccessLevel.test.ts → useAccessLevel.test.ts} +40 -40
  509. package/src/rbac/hooks/permissions/useAccessLevel.ts +16 -6
  510. package/src/rbac/hooks/permissions/{__tests__/useCan.test.ts → useCan.test.ts} +41 -41
  511. package/src/rbac/hooks/permissions/useCan.ts +170 -252
  512. package/src/rbac/hooks/permissions/{__tests__/useMultiplePermissions.test.ts → useMultiplePermissions.test.ts} +49 -49
  513. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +6 -2
  514. package/src/rbac/hooks/permissions/{__tests__/usePermissions.test.ts → usePermissions.test.ts} +10 -12
  515. package/src/rbac/hooks/permissions/usePermissions.ts +36 -65
  516. package/src/rbac/hooks/useCan.test.ts +42 -42
  517. package/src/rbac/hooks/usePageAccessLogging.ts +160 -0
  518. package/src/rbac/hooks/usePageGuardScope.ts +117 -0
  519. package/src/rbac/hooks/usePagePermissionCheck.ts +67 -0
  520. package/src/rbac/hooks/{__tests__/usePermissions.integration.test.ts → usePermissions.integration.test.ts} +9 -9
  521. package/src/{__tests__/hooks/usePermissions.test.ts → rbac/hooks/usePermissions.stability.test.ts} +18 -18
  522. package/src/rbac/hooks/usePermissions.test.ts +54 -54
  523. package/src/rbac/hooks/useRBAC.test.ts +313 -217
  524. package/src/rbac/hooks/useRBAC.ts +145 -81
  525. package/src/rbac/hooks/useResourcePermissions.test.ts +25 -25
  526. package/src/rbac/hooks/useResourcePermissions.ts +68 -134
  527. package/src/rbac/hooks/useResourcePermissionsSuperAdmin.ts +67 -0
  528. package/src/rbac/hooks/useRoleManagement.test.ts +27 -112
  529. package/src/rbac/hooks/useRoleManagement.ts +153 -585
  530. package/src/rbac/hooks/{__tests__/useSecureSupabase.test.ts → useSecureSupabase.test.ts} +17 -17
  531. package/src/rbac/hooks/useSecureSupabase.ts +10 -2
  532. package/src/rbac/hooks/useSuperAdminCheck.ts +80 -0
  533. package/src/rbac/{__tests__/performance.test.ts → performance.test.ts} +1 -1
  534. package/src/rbac/{__tests__/rbac-core.test.tsx → rbac-core.test.tsx} +3 -3
  535. package/src/rbac/{__tests__/rbac-engine-core-logic.test.ts → rbac-engine-core-logic.test.ts} +2 -2
  536. package/src/rbac/{__tests__/rbac-engine-simplified.test.ts → rbac-engine-simplified.test.ts} +3 -3
  537. package/src/rbac/{__tests__/rbac-functions.test.ts → rbac-functions.test.ts} +57 -0
  538. package/src/rbac/{__tests__/rbac-role-isolation.test.ts → rbac-role-isolation.test.ts} +2 -2
  539. package/src/rbac/request-deduplication.test.ts +14 -9
  540. package/src/rbac/request-deduplication.ts +5 -4
  541. package/src/rbac/{__tests__/scenarios.user-role.test.tsx → scenarios.user-role.test.tsx} +23 -23
  542. package/src/rbac/secureClient.test.ts +514 -83
  543. package/src/rbac/secureClient.ts +8 -2
  544. package/src/rbac/security.test.ts +323 -0
  545. package/src/rbac/types/roleManagement.ts +66 -0
  546. package/src/rbac/utils/{__tests__/clientSecurity.test.ts → clientSecurity.test.ts} +4 -4
  547. package/src/rbac/utils/{__tests__/contextValidator.test.ts → contextValidator.test.ts} +4 -4
  548. package/src/rbac/utils/contextValidator.ts +5 -1
  549. package/src/rbac/utils/{__tests__/deep-equal.test.ts → deep-equal.test.ts} +1 -1
  550. package/src/rbac/utils/{__tests__/eventContext.test.ts → eventContext.test.ts} +36 -21
  551. package/src/rbac/utils/eventContext.ts +37 -33
  552. package/src/rbac/utils/fetchPermissionMap.ts +13 -0
  553. package/src/rbac/utils/permissionMapHelpers.ts +34 -0
  554. package/src/rbac/utils/roleManagementRpc.ts +303 -0
  555. package/src/services/{__tests__/AuthService.edge-cases.test.ts → AuthService.edge-cases.test.ts} +19 -19
  556. package/src/services/{__tests__/AuthService.restoreSession.test.ts → AuthService.restoreSession.test.ts} +2 -2
  557. package/src/services/{__tests__/AuthService.test.ts → AuthService.test.ts} +89 -55
  558. package/src/services/AuthService.ts +184 -205
  559. package/src/services/{__tests__/BaseService.edge-cases.test.ts → BaseService.edge-cases.test.ts} +3 -3
  560. package/src/services/{__tests__/BaseService.test.ts → BaseService.test.ts} +2 -2
  561. package/src/services/{__tests__/EventService.edge-cases.test.ts → EventService.edge-cases.test.ts} +27 -24
  562. package/src/services/{__tests__/EventService.eventColours.test.ts → EventService.eventColours.test.ts} +1 -1
  563. package/src/services/{__tests__/EventService.test.ts → EventService.test.ts} +256 -24
  564. package/src/services/EventService.ts +242 -312
  565. package/src/services/{__tests__/InactivityService.edge-cases.test.ts → InactivityService.edge-cases.test.ts} +3 -3
  566. package/src/services/{__tests__/InactivityService.lifecycle.test.ts → InactivityService.lifecycle.test.ts} +2 -2
  567. package/src/services/{__tests__/InactivityService.test.ts → InactivityService.test.ts} +179 -4
  568. package/src/services/InactivityService.ts +172 -213
  569. package/src/services/{__tests__/OrganisationService.edge-cases.test.ts → OrganisationService.edge-cases.test.ts} +5 -5
  570. package/src/services/{__tests__/OrganisationService.pagination.test.ts → OrganisationService.pagination.test.ts} +4 -4
  571. package/src/services/{__tests__/OrganisationService.test.ts → OrganisationService.test.ts} +410 -7
  572. package/src/services/OrganisationService.ts +184 -238
  573. package/src/services/base/BaseService.test.ts +1 -1
  574. package/src/services/interfaces/{__tests__/IAuthService.test.ts → IAuthService.test.ts} +21 -27
  575. package/src/services/interfaces/IAuthService.ts +10 -9
  576. package/src/services/interfaces/{__tests__/IEventService.test.ts → IEventService.test.ts} +4 -4
  577. package/src/services/interfaces/{__tests__/IInactivityService.test.ts → IInactivityService.test.ts} +3 -3
  578. package/src/services/interfaces/{__tests__/IOrganisationService.test.ts → IOrganisationService.test.ts} +3 -3
  579. package/src/styles/core.css +243 -12
  580. package/src/theming/{__tests__/parseEventColours.test.ts → parseEventColours.test.ts} +1 -1
  581. package/src/theming/{__tests__/runtime.test.ts → runtime.test.ts} +8 -17
  582. package/src/theming/runtime.ts +71 -2
  583. package/src/types/api-result.ts +53 -0
  584. package/src/types/{__tests__/core.test.ts → core.test.ts} +2 -2
  585. package/src/types/{__tests__/database-generated.test.ts → database-generated.test.ts} +3 -3
  586. package/src/types/database.generated.ts +45 -10
  587. package/src/types/event.ts +38 -18
  588. package/src/types/{__tests__/file-reference.test.ts → file-reference.test.ts} +13 -13
  589. package/src/types/file-reference.ts +37 -12
  590. package/src/types/{__tests__/guards.test.ts → guards.test.ts} +2 -2
  591. package/src/types/{__tests__/index.test.ts → index.test.ts} +2 -2
  592. package/src/types/index.ts +3 -0
  593. package/src/types/{__tests__/organisation.roles.test.ts → organisation.roles.test.ts} +1 -1
  594. package/src/types/{__tests__/organisation.test.ts → organisation.test.ts} +3 -31
  595. package/src/types/organisation.ts +15 -15
  596. package/src/types/supabase.ts +13 -4
  597. package/src/types/{__tests__/theme.test.ts → theme.test.ts} +1 -1
  598. package/src/types/{__tests__/type-validation.test.ts → type-validation.test.ts} +1 -1
  599. package/src/types/{__tests__/validation.test.ts → validation.test.ts} +2 -2
  600. package/src/utils/app/appIdResolver.test.ts +98 -71
  601. package/src/utils/app/appIdResolver.ts +31 -20
  602. package/src/utils/{__tests__/appConfig.unit.test.ts → appConfig.unit.test.ts} +1 -1
  603. package/src/utils/{__tests__/audit.unit.test.ts → audit.unit.test.ts} +1 -1
  604. package/src/utils/{__tests__/auth-utils.unit.test.ts → auth-utils.unit.test.ts} +16 -17
  605. package/src/utils/{__tests__/bundleAnalysis.unit.test.ts → bundleAnalysis.unit.test.ts} +35 -35
  606. package/src/utils/{__tests__/cn.unit.test.ts → cn.unit.test.ts} +1 -1
  607. package/src/utils/context/organisationContext.test.ts +105 -91
  608. package/src/utils/context/organisationContext.ts +29 -40
  609. package/src/utils/core/{__tests__/cn.test.ts → cn.test.ts} +3 -3
  610. package/src/utils/core/{__tests__/debugLogger.test.ts → debugLogger.test.ts} +2 -2
  611. package/src/utils/core/{__tests__/logger.test.ts → logger.test.ts} +2 -2
  612. package/src/utils/core/mergeRefs.ts +24 -0
  613. package/src/utils/{__tests__/debugLogger.test.ts → debugLogger.test.ts} +1 -1
  614. package/src/utils/{__tests__/deviceFingerprint.unit.test.ts → deviceFingerprint.unit.test.ts} +1 -1
  615. package/src/utils/dynamic/createLazyComponent.tsx +9 -1
  616. package/src/utils/dynamic/{__tests__/dynamicUtils.test.ts → dynamicUtils.test.ts} +2 -2
  617. package/src/utils/dynamic/{__tests__/lazyLoad.test.tsx → lazyLoad.test.tsx} +2 -2
  618. package/src/utils/{__tests__/dynamicUtils.unit.test.ts → dynamicUtils.unit.test.ts} +1 -1
  619. package/src/utils/file-reference/{__tests__/file-reference.test.ts → file-reference.test.ts} +214 -289
  620. package/src/utils/file-reference/index.ts +330 -347
  621. package/src/utils/{__tests__/formatDate.unit.test.ts → formatDate.unit.test.ts} +2 -2
  622. package/src/utils/formatting/formatDateTimeTimezone.test.ts +1 -1
  623. package/src/utils/formatting/formatNumber.test.ts +1 -1
  624. package/src/utils/{__tests__/formatting.unit.test.ts → formatting.unit.test.ts} +1 -1
  625. package/src/utils/google-places/googlePlacesUtils.test.ts +70 -48
  626. package/src/utils/google-places/googlePlacesUtils.ts +67 -99
  627. package/src/utils/google-places/loadGoogleMapsScript.test.ts +25 -22
  628. package/src/utils/google-places/loadGoogleMapsScript.ts +138 -117
  629. package/src/utils/{__tests__/index.unit.test.ts → index.unit.test.ts} +1 -1
  630. package/src/utils/{__tests__/lazyLoad.unit.test.tsx → lazyLoad.unit.test.tsx} +13 -14
  631. package/src/utils/location/location.test.ts +1 -1
  632. package/src/utils/{__tests__/logger.unit.test.ts → logger.unit.test.ts} +1 -1
  633. package/src/utils/{__tests__/organisationContext.unit.test.ts → organisationContext.unit.test.ts} +37 -48
  634. package/src/utils/performance/{__tests__/bundleAnalysis.test.ts → bundleAnalysis.test.ts} +2 -2
  635. package/src/utils/performance/{__tests__/performanceBenchmark.test.ts → performanceBenchmark.test.ts} +2 -2
  636. package/src/utils/performance/{__tests__/performanceBudgets.test.ts → performanceBudgets.test.ts} +2 -2
  637. package/src/utils/{__tests__/performanceBenchmark.test.ts → performanceBenchmark.test.ts} +2 -2
  638. package/src/utils/{__tests__/performanceBudgets.unit.test.ts → performanceBudgets.unit.test.ts} +2 -2
  639. package/src/utils/{__tests__/permissionTypes.unit.test.ts → permissionTypes.unit.test.ts} +1 -1
  640. package/src/utils/{__tests__/permissionUtils.unit.test.ts → permissionUtils.unit.test.ts} +1 -1
  641. package/src/utils/permissions/{__tests__/permissionTypes.test.ts → permissionTypes.test.ts} +2 -2
  642. package/src/utils/persistence/{__tests__/keyDerivation.test.ts → keyDerivation.test.ts} +2 -2
  643. package/src/utils/persistence/{__tests__/sensitiveFieldDetection.test.ts → sensitiveFieldDetection.test.ts} +2 -2
  644. package/src/utils/{__tests__/request-deduplication.test.ts → request-deduplication.test.ts} +2 -2
  645. package/src/utils/{__tests__/sanitization.unit.test.ts → sanitization.unit.test.ts} +1 -1
  646. package/src/utils/{__tests__/schemaUtils.unit.test.ts → schemaUtils.unit.test.ts} +1 -1
  647. package/src/utils/{__tests__/secureDataAccess.unit.test.ts → secureDataAccess.unit.test.ts} +2 -2
  648. package/src/utils/{__tests__/secureErrors.unit.test.ts → secureErrors.unit.test.ts} +4 -4
  649. package/src/utils/{__tests__/secureStorage.unit.test.ts → secureStorage.unit.test.ts} +1 -1
  650. package/src/utils/security/auth-utils.ts +34 -23
  651. package/src/utils/security/secureDataAccess.ts +241 -281
  652. package/src/utils/security/secureErrors.test.ts +1 -1
  653. package/src/utils/security/secureStorage.test.ts +1 -1
  654. package/src/utils/security/security.test.ts +25 -17
  655. package/src/utils/security/security.ts +15 -18
  656. package/src/utils/security/securityMonitor.test.ts +1 -1
  657. package/src/utils/{__tests__/security.unit.test.ts → security.unit.test.ts} +21 -15
  658. package/src/utils/{__tests__/securityMonitor.unit.test.ts → securityMonitor.unit.test.ts} +1 -1
  659. package/src/utils/{__tests__/sessionTracking.unit.test.ts → sessionTracking.unit.test.ts} +12 -12
  660. package/src/utils/storage/{__tests__/config.unit.test.ts → config.unit.test.ts} +2 -2
  661. package/src/utils/storage/helpers.test.ts +88 -102
  662. package/src/utils/storage/helpers.ts +173 -251
  663. package/src/utils/storage/{__tests__/index.unit.test.ts → index.unit.test.ts} +3 -3
  664. package/src/utils/storage/types.ts +7 -0
  665. package/src/utils/supabase/createBaseClient.test.ts +1 -1
  666. package/src/utils/timezone/timezone.test.ts +1 -1
  667. package/src/utils/{__tests__/timezone.test.ts → timezone.test.ts} +2 -2
  668. package/src/utils/validation/{__tests__/common.test.ts → common.test.ts} +2 -2
  669. package/src/utils/validation/{__tests__/csrf.test.ts → csrf.test.ts} +56 -28
  670. package/src/utils/validation/csrf.ts +42 -41
  671. package/src/utils/validation/{__tests__/htmlSanitization.unit.test.ts → htmlSanitization.unit.test.ts} +2 -2
  672. package/src/utils/validation/{__tests__/passwordSchema.test.ts → passwordSchema.test.ts} +2 -2
  673. package/src/utils/validation/{__tests__/schema.test.ts → schema.test.ts} +2 -2
  674. package/src/utils/validation/{__tests__/sqlInjectionProtection.test.ts → sqlInjectionProtection.test.ts} +2 -2
  675. package/src/utils/validation/{__tests__/user.test.ts → user.test.ts} +2 -2
  676. package/src/utils/validation/{__tests__/validation.test.ts → validation.test.ts} +2 -2
  677. package/src/utils/validation/{__tests__/validationUtils.test.ts → validationUtils.test.ts} +2 -2
  678. package/src/utils/{__tests__/validation.unit.test.ts → validation.unit.test.ts} +1 -1
  679. package/src/utils/{__tests__/validationUtils.unit.test.ts → validationUtils.unit.test.ts} +5 -2
  680. package/dist/UnifiedAuthProvider-BBD2PS3Q.js +0 -7
  681. package/dist/chunk-KPYQWGFQ.js +0 -183
  682. package/dist/types-D05dCGma.d.ts +0 -521
  683. package/scripts/eslint-audit.cjs +0 -222
  684. package/scripts/generate-docs.js +0 -157
  685. package/scripts/install-cursor-rules.cjs +0 -255
  686. package/scripts/install-eslint-config.cjs +0 -349
  687. package/scripts/setup-build-cache.js +0 -73
  688. package/scripts/validate-pre-publish.js +0 -145
  689. package/src/__tests__/integration/UserProfile.test.tsx +0 -124
  690. package/src/__tests__/public-recipe-view.test.ts +0 -228
  691. package/src/__tests__/rls-policies.test.ts +0 -472
  692. package/src/components/DataTable/__tests__/DataTable.test.tsx +0 -876
  693. package/src/components/DataTable/components/DataTableLayout.tsx +0 -584
  694. package/src/components/DataTable/components/UnifiedTableBody.tsx +0 -395
  695. package/src/components/DataTable/components/__tests__/DataTableLayout.test.tsx +0 -467
  696. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +0 -358
  697. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +0 -957
  698. package/src/components/DataTable/core/ActionManager.ts +0 -235
  699. package/src/components/DataTable/core/ColumnManager.ts +0 -204
  700. package/src/components/DataTable/core/DataManager.ts +0 -190
  701. package/src/components/DataTable/core/LocalDataAdapter.ts +0 -274
  702. package/src/components/DataTable/core/PluginRegistry.ts +0 -229
  703. package/src/components/DataTable/core/StateManager.ts +0 -312
  704. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +0 -235
  705. package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +0 -141
  706. package/src/components/DataTable/core/__tests__/DataManager.test.ts +0 -178
  707. package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +0 -133
  708. package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +0 -142
  709. package/src/components/DataTable/core/__tests__/StateManager.test.ts +0 -158
  710. package/src/components/DataTable/core/interfaces.ts +0 -338
  711. package/src/components/DataTable/types.ts +0 -764
  712. package/src/hooks/public/usePublicFileDisplay.ts +0 -534
  713. package/src/hooks/useFileDisplay.ts +0 -748
  714. package/src/providers/OrganisationProvider.test.tsx +0 -40
  715. package/src/providers/OrganisationProvider.tsx +0 -92
  716. package/src/providers/__tests__/InactivityProvider.test-helper.tsx +0 -65
  717. package/src/providers/__tests__/OrganisationProvider.test.tsx +0 -616
  718. package/src/providers/__tests__/OrganisationProvider.wrapper.test.tsx +0 -591
  719. package/src/rbac/__tests__/cache-invalidation.test.ts +0 -393
  720. /package/src/components/DataTable/{components/__tests__ → ui}/COVERAGE_NOTE.md +0 -0
  721. /package/src/components/DataTable/utils/{__tests__/COVERAGE_NOTE.md → COVERAGE_NOTE.md} +0 -0
  722. /package/src/hooks/{__tests__/useApiFetch.unit.test.ts → useApiFetch.unit.test.ts} +0 -0
  723. /package/src/providers/{__tests__/README.md → README.md} +0 -0
  724. /package/src/rbac/{__tests__/index.test.ts → index.test.ts} +0 -0
  725. /package/src/rbac/{__tests__/rbac-integration.test.ts → rbac-integration.test.ts} +0 -0
  726. /package/src/types/{__tests__/README.md → README.md} +0 -0
@@ -1,957 +0,0 @@
1
- /**
2
- * @file Import Modal Component Tests
3
- * @package @jmruthers/pace-core
4
- * @module Components/DataTable/Components/__tests__
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 } 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 upload a file and wait for preview to appear
60
- const uploadFileAndWaitForPreview = async (user: ReturnType<typeof userEvent.setup>, file: File) => {
61
- // Wait for dialog and file input
62
- await waitForDialog();
63
- await waitFor(() => {
64
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
65
- expect(fileInput).toBeInTheDocument();
66
- }, { timeout: 5000 });
67
-
68
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
69
-
70
- // Upload the file
71
- await user.upload(fileInput, file);
72
-
73
- // Wait for preview table to appear (file processing is async)
74
- // Try multiple ways to find the table since queryByRole may not work in all test environments
75
- await waitFor(() => {
76
- const table = screen.queryByRole('table') ||
77
- document.querySelector('table') ||
78
- document.querySelector('table.min-w-full');
79
- expect(table).toBeInTheDocument();
80
- }, { timeout: 10000 });
81
- };
82
-
83
- // Mock Button component
84
- vi.mock('../../Button/Button', () => ({
85
- Button: ({ children, onClick, variant, size, disabled, className }: any) => (
86
- <button
87
- onClick={onClick}
88
- disabled={disabled}
89
- data-variant={variant}
90
- data-size={size}
91
- className={className}
92
- >
93
- {children}
94
- </button>
95
- ),
96
- }));
97
-
98
- // Mock Input component
99
- vi.mock('../../Input/Input', () => ({
100
- Input: React.forwardRef(({ type, accept, onChange, className, ...props }: any, ref: any) => (
101
- <input
102
- ref={ref}
103
- type={type}
104
- accept={accept}
105
- onChange={onChange}
106
- className={className}
107
- {...props}
108
- />
109
- )),
110
- }));
111
-
112
- // Mock lucide-react icons
113
- vi.mock('lucide-react', () => ({
114
- Upload: ({ className }: { className?: string }) => (
115
- <span data-testid="upload-icon" className={className}>Upload</span>
116
- ),
117
- FileText: ({ className }: { className?: string }) => (
118
- <span data-testid="file-text-icon" className={className}>File</span>
119
- ),
120
- AlertCircle: ({ className }: { className?: string }) => (
121
- <span data-testid="alert-circle-icon" className={className}>Alert</span>
122
- ),
123
- X: ({ className }: { className?: string }) => (
124
- <span data-testid="x-icon" className={className}>X</span>
125
- ),
126
- }));
127
-
128
- // Mock logger
129
- vi.mock('../../../utils/core/logger', () => ({
130
- createLogger: () => ({
131
- debug: vi.fn(),
132
- info: vi.fn(),
133
- warn: vi.fn(),
134
- error: vi.fn(),
135
- }),
136
- }));
137
-
138
- describe('[component] ImportModal', () => {
139
- const baseProps = {
140
- isOpen: true,
141
- onClose: vi.fn(),
142
- onImport: vi.fn(),
143
- };
144
-
145
- const createCSVFile = (content: string, filename = 'test.csv'): File => {
146
- const blob = new Blob([content], { type: 'text/csv' });
147
- const file = new File([blob], filename, { type: 'text/csv' });
148
- // Store content for File.text() mock to access
149
- (file as any)._content = content;
150
- return file;
151
- };
152
-
153
- beforeEach(() => {
154
- vi.clearAllMocks();
155
-
156
- // Mock showModal for dialog elements (needed for test environments)
157
- HTMLDialogElement.prototype.showModal = vi.fn(function(this: HTMLDialogElement) {
158
- this.setAttribute('open', '');
159
- this.dispatchEvent(new Event('show', { bubbles: true }));
160
- });
161
- HTMLDialogElement.prototype.close = vi.fn(function(this: HTMLDialogElement) {
162
- this.removeAttribute('open');
163
- this.dispatchEvent(new Event('close', { bubbles: true }));
164
- });
165
-
166
- // Mock File.text() method for jsdom compatibility
167
- // File.text() reads the file content asynchronously
168
- // In tests, we read directly from the Blob content synchronously
169
- if (!File.prototype.text) {
170
- Object.defineProperty(File.prototype, 'text', {
171
- writable: true,
172
- configurable: true,
173
- value: async function(this: File) {
174
- // For test files, read from the stored content or from the Blob
175
- const file = this as any;
176
- if (file._content) {
177
- // Use stored content if available
178
- return Promise.resolve(file._content);
179
- }
180
- // Otherwise, try to read from Blob using FileReader
181
- return new Promise((resolve, reject) => {
182
- const reader = new FileReader();
183
- reader.onload = (e) => {
184
- resolve(e.target?.result as string);
185
- };
186
- reader.onerror = () => {
187
- reject(new Error('Failed to read file'));
188
- };
189
- // Read the file as text
190
- reader.readAsText(this);
191
- });
192
- },
193
- });
194
- }
195
- });
196
-
197
- afterEach(() => {
198
- vi.clearAllMocks();
199
- });
200
-
201
- describe('Rendering', () => {
202
- it('returns null when modal is closed', () => {
203
- const { container } = render(
204
- <ImportModal {...baseProps} isOpen={false} />
205
- );
206
- expect(container.firstChild).toBeNull();
207
- });
208
-
209
- it('renders modal when open', async () => {
210
- render(<ImportModal {...baseProps} />);
211
-
212
- // Wait for dialog to be accessible
213
- await waitForDialog();
214
- // Check for content instead of testids
215
- expect(screen.getByText('Import Data')).toBeInTheDocument();
216
- });
217
-
218
- it('renders default title', async () => {
219
- render(<ImportModal {...baseProps} />);
220
-
221
- // Wait for dialog content to be rendered (showModal is async via requestAnimationFrame)
222
- await waitFor(() => {
223
- // Try by role first, fallback to text content
224
- try {
225
- expect(screen.getByRole('heading', { name: 'Import Data' })).toBeInTheDocument();
226
- } catch (_e) {
227
- expect(screen.getByText('Import Data')).toBeInTheDocument();
228
- }
229
- }, { timeout: 5000 });
230
- });
231
-
232
- it('renders custom title from config', async () => {
233
- render(
234
- <ImportModal
235
- {...baseProps}
236
- config={{ title: 'Custom Import Title' }}
237
- />
238
- );
239
-
240
- // Wait for dialog content to be rendered (showModal is async via requestAnimationFrame)
241
- await waitFor(() => {
242
- // Try by role first, fallback to text content
243
- try {
244
- expect(screen.getByRole('heading', { name: 'Custom Import Title' })).toBeInTheDocument();
245
- } catch (_e) {
246
- expect(screen.getByText('Custom Import Title')).toBeInTheDocument();
247
- }
248
- }, { timeout: 5000 });
249
- });
250
-
251
- it('renders default description', async () => {
252
- render(<ImportModal {...baseProps} />);
253
-
254
- // Wait for dialog to be accessible
255
- await waitForDialog();
256
- // Description is rendered as p in DialogHeader
257
- expect(screen.getByText('Upload a CSV file to import multiple records at once.')).toBeInTheDocument();
258
- });
259
-
260
- it('renders custom description from config', async () => {
261
- render(
262
- <ImportModal
263
- {...baseProps}
264
- config={{ description: 'Custom description' }}
265
- />
266
- );
267
-
268
- // Wait for dialog to be accessible
269
- await waitForDialog();
270
- // Description is rendered as p in DialogHeader
271
- expect(screen.getByText('Custom description')).toBeInTheDocument();
272
- });
273
-
274
- it('renders file upload area', async () => {
275
- render(<ImportModal {...baseProps} />);
276
-
277
- // Wait for dialog title first (most reliable indicator dialog is rendered)
278
- // Use getByRole for heading to avoid multiple matches (h2 and button both have "Import Data")
279
- await waitFor(() => {
280
- try {
281
- expect(screen.getByRole('heading', { name: 'Import Data' })).toBeInTheDocument();
282
- } catch (_e) {
283
- // Fallback: check if any element with "Import Data" exists
284
- const elements = screen.getAllByText('Import Data');
285
- expect(elements.length).toBeGreaterThan(0);
286
- }
287
- }, { timeout: 5000 });
288
-
289
- // Then check for upload area text and button (use querySelector as fallback for buttons in dialogs)
290
- await waitFor(() => {
291
- expect(screen.getByText(/choose a csv file/i)).toBeInTheDocument();
292
- const selectFileButton = findButtonByText(/select file/i);
293
- expect(selectFileButton).toBeInTheDocument();
294
- }, { timeout: 5000 });
295
- });
296
-
297
- it('renders cancel button', async () => {
298
- render(<ImportModal {...baseProps} />);
299
-
300
- // Wait for dialog title first (most reliable indicator dialog is rendered)
301
- // Use getByRole for heading to avoid multiple matches (h2 and button both have "Import Data")
302
- await waitFor(() => {
303
- try {
304
- expect(screen.getByRole('heading', { name: 'Import Data' })).toBeInTheDocument();
305
- } catch (_e) {
306
- // Fallback: check if any element with "Import Data" exists
307
- const elements = screen.getAllByText('Import Data');
308
- expect(elements.length).toBeGreaterThan(0);
309
- }
310
- }, { timeout: 5000 });
311
-
312
- // Then check for cancel button (use querySelector as fallback for buttons in dialogs)
313
- await waitFor(() => {
314
- const cancelButton = screen.queryByRole('button', { name: /cancel/i })
315
- || Array.from(document.querySelectorAll('button')).find(btn => btn.textContent?.match(/cancel/i));
316
- expect(cancelButton).toBeInTheDocument();
317
- }, { timeout: 5000 });
318
- });
319
-
320
- it('renders import button', async () => {
321
- render(<ImportModal {...baseProps} />);
322
-
323
- // Wait for dialog title first (most reliable indicator dialog is rendered)
324
- // Use getByRole for heading to avoid multiple matches (h2 and button both have "Import Data")
325
- await waitFor(() => {
326
- try {
327
- expect(screen.getByRole('heading', { name: 'Import Data' })).toBeInTheDocument();
328
- } catch (_e) {
329
- // Fallback: check if any element with "Import Data" exists
330
- const elements = screen.getAllByText('Import Data');
331
- expect(elements.length).toBeGreaterThan(0);
332
- }
333
- }, { timeout: 5000 });
334
-
335
- // Then check for import button (use querySelector as fallback for buttons in dialogs)
336
- await waitFor(() => {
337
- const importButton = screen.queryByRole('button', { name: /import/i })
338
- || Array.from(document.querySelectorAll('button')).find(btn => btn.textContent?.match(/^import$/i));
339
- expect(importButton).toBeInTheDocument();
340
- }, { timeout: 5000 });
341
- });
342
- });
343
-
344
- describe('File Selection', () => {
345
- it('displays selected file name', async () => {
346
- const user = userEvent.setup();
347
- const csvContent = 'name,email\nJohn,john@example.com';
348
- const file = createCSVFile(csvContent);
349
-
350
- render(<ImportModal {...baseProps} />);
351
-
352
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
353
- await user.upload(fileInput, file);
354
-
355
- await waitFor(() => {
356
- // Text is split across elements with <br/>, so use getAllByText and check first match
357
- const elements = screen.getAllByText((content, element) => {
358
- return element?.textContent?.includes(`Selected: ${file.name}`) ?? false;
359
- });
360
- expect(elements.length).toBeGreaterThan(0);
361
- });
362
- });
363
-
364
- it('resets file when modal closes and reopens', async () => {
365
- const user = userEvent.setup();
366
- const csvContent = 'name,email\nJohn,john@example.com';
367
- const file = createCSVFile(csvContent);
368
-
369
- const { rerender } = render(<ImportModal {...baseProps} />);
370
-
371
- // Wait for dialog to be accessible first, then wait for file input
372
- await waitForDialog();
373
- await waitFor(() => {
374
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
375
- expect(fileInput).toBeInTheDocument();
376
- }, { timeout: 5000 });
377
-
378
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
379
- await user.upload(fileInput, file);
380
-
381
- await waitFor(() => {
382
- // Text is split across elements with <br/>, so use getAllByText and check first match
383
- const elements = screen.getAllByText((content, element) => {
384
- return element?.textContent?.includes(`Selected: ${file.name}`) ?? false;
385
- });
386
- expect(elements.length).toBeGreaterThan(0);
387
- });
388
-
389
- rerender(<ImportModal {...baseProps} isOpen={false} />);
390
-
391
- // Wait for dialog to close
392
- await waitFor(() => {
393
- const dialog = document.querySelector('dialog[role="dialog"]');
394
- expect(dialog).not.toBeInTheDocument();
395
- });
396
-
397
- rerender(<ImportModal {...baseProps} isOpen={true} />);
398
-
399
- // Wait for dialog to reopen
400
- await waitForDialog();
401
-
402
- // File should be reset - use queryAllByText since text may be split
403
- const elements = screen.queryAllByText((content, element) => {
404
- return element?.textContent?.includes(`Selected: ${file.name}`) ?? false;
405
- });
406
- expect(elements.length).toBe(0);
407
- });
408
- });
409
-
410
- describe('CSV Parsing', () => {
411
- it('parses valid CSV and shows preview with data and row count', async () => {
412
- const user = userEvent.setup();
413
- const csvContent = 'name,email\nJohn,john@example.com\nJane,jane@example.com\nBob,bob@example.com';
414
- const file = createCSVFile(csvContent);
415
-
416
- render(<ImportModal {...baseProps} />);
417
-
418
- await uploadFileAndWaitForPreview(user, file);
419
-
420
- await waitFor(() => {
421
- expect(screen.getByText(/name/i)).toBeInTheDocument();
422
- expect(screen.getByText(/email/i)).toBeInTheDocument();
423
- expect(screen.getByText('John')).toBeInTheDocument();
424
- expect(screen.getByText('john@example.com')).toBeInTheDocument();
425
- expect(screen.getByText(/total rows to import: 3/i)).toBeInTheDocument();
426
- }, { timeout: 5000 });
427
- });
428
-
429
- it('handles CSV with quoted values and commas in values', async () => {
430
- const user = userEvent.setup();
431
- const csvContent = 'name,description\n"John Doe","Description, with comma"\n"Jane Smith","Another, description"';
432
- const file = createCSVFile(csvContent);
433
-
434
- render(<ImportModal {...baseProps} />);
435
-
436
- await waitForDialog();
437
- await waitFor(() => {
438
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
439
- expect(fileInput).toBeInTheDocument();
440
- }, { timeout: 5000 });
441
-
442
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
443
- await user.upload(fileInput, file);
444
-
445
- await waitFor(() => {
446
- const table = screen.queryByRole('table') ||
447
- document.querySelector('table') ||
448
- document.querySelector('table.min-w-full');
449
- expect(table).toBeInTheDocument();
450
- }, { timeout: 10000 });
451
-
452
- await waitFor(() => {
453
- expect(screen.getByText('John Doe')).toBeInTheDocument();
454
- expect(screen.getByText(/description, with comma/i)).toBeInTheDocument();
455
- }, { timeout: 2000 });
456
- });
457
- });
458
-
459
- describe('Error Handling', () => {
460
- it('displays error for invalid CSV files (empty, only header, whitespace)', async () => {
461
- const user = userEvent.setup();
462
-
463
- // Test empty file
464
- const emptyFile = createCSVFile('');
465
- const { rerender } = render(<ImportModal {...baseProps} />);
466
-
467
- await waitForDialog();
468
- await waitFor(() => {
469
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
470
- expect(fileInput).toBeInTheDocument();
471
- }, { timeout: 5000 });
472
-
473
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
474
- await user.upload(fileInput, emptyFile);
475
-
476
- await waitFor(() => {
477
- expect(screen.getByTestId('alert-circle-icon')).toBeInTheDocument();
478
- }, { timeout: 3000 });
479
-
480
- // Test CSV with only header
481
- fileInput.value = '';
482
- const headerOnlyFile = createCSVFile('name,email');
483
- await user.upload(fileInput, headerOnlyFile);
484
-
485
- await waitFor(() => {
486
- expect(screen.getByTestId('alert-circle-icon')).toBeInTheDocument();
487
- }, { timeout: 3000 });
488
-
489
- // Test CSV with only whitespace
490
- fileInput.value = '';
491
- const whitespaceFile = createCSVFile(' \n \n ');
492
- await user.upload(fileInput, whitespaceFile);
493
-
494
- await waitFor(() => {
495
- expect(screen.getByTestId('alert-circle-icon')).toBeInTheDocument();
496
- }, { timeout: 3000 });
497
- });
498
-
499
- it('clears error when new file is selected', async () => {
500
- const user = userEvent.setup();
501
- const invalidFile = createCSVFile('invalid');
502
- const validFile = createCSVFile('name,email\nJohn,john@example.com');
503
-
504
- render(<ImportModal {...baseProps} />);
505
-
506
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
507
- await user.upload(fileInput, invalidFile);
508
-
509
- await waitFor(() => {
510
- expect(screen.getByTestId('alert-circle-icon')).toBeInTheDocument();
511
- }, { timeout: 3000 });
512
-
513
- // Clear the file input value first
514
- fileInput.value = '';
515
- await user.upload(fileInput, validFile);
516
-
517
- await waitFor(() => {
518
- expect(screen.queryByTestId('alert-circle-icon')).not.toBeInTheDocument();
519
- }, { timeout: 3000 });
520
- });
521
- });
522
-
523
- describe('Import Action', () => {
524
- it('calls onImport with parsed data when import button is clicked', async () => {
525
- const user = userEvent.setup();
526
- const onImport = vi.fn();
527
- const csvContent = 'name,email\nJohn,john@example.com';
528
- const file = createCSVFile(csvContent);
529
-
530
- render(<ImportModal {...baseProps} onImport={onImport} />);
531
-
532
- // Wait for dialog to be accessible first, then wait for file input
533
- await waitForDialog();
534
- await waitFor(() => {
535
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
536
- expect(fileInput).toBeInTheDocument();
537
- }, { timeout: 5000 });
538
-
539
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
540
- await user.upload(fileInput, file);
541
-
542
- // Wait for preview to appear
543
- await waitFor(() => {
544
- const table = screen.queryByRole('table') ||
545
- document.querySelector('table') ||
546
- document.querySelector('table.min-w-full');
547
- expect(table).toBeInTheDocument();
548
- }, { timeout: 10000 });
549
-
550
- await waitFor(() => {
551
- expect(screen.getByText('John')).toBeInTheDocument();
552
- }, { timeout: 2000 });
553
-
554
- const importButton = findButtonByText(/^import$/i);
555
- expect(importButton).toBeInTheDocument();
556
- if (importButton) {
557
- await user.click(importButton);
558
- }
559
-
560
- await waitFor(() => {
561
- expect(onImport).toHaveBeenCalledTimes(1);
562
- expect(onImport).toHaveBeenCalledWith(
563
- expect.arrayContaining([
564
- expect.objectContaining({
565
- name: 'John',
566
- email: 'john@example.com',
567
- }),
568
- ])
569
- );
570
- }, { timeout: 3000 });
571
- });
572
-
573
- it('disables import button when no file is selected', async () => {
574
- render(<ImportModal {...baseProps} />);
575
-
576
- // Wait for dialog title first (most reliable indicator dialog is rendered)
577
- // Use getByRole for heading to avoid multiple matches (h2 and button both have "Import Data")
578
- await waitFor(() => {
579
- try {
580
- expect(screen.getByRole('heading', { name: 'Import Data' })).toBeInTheDocument();
581
- } catch (_e) {
582
- // Fallback: check if any element with "Import Data" exists
583
- const elements = screen.getAllByText('Import Data');
584
- expect(elements.length).toBeGreaterThan(0);
585
- }
586
- }, { timeout: 5000 });
587
-
588
- // Then check for import button and its disabled state
589
- await waitFor(() => {
590
- const importButton = findButtonByText(/^import$/i);
591
- expect(importButton).toBeInTheDocument();
592
- expect(importButton).toBeDisabled();
593
- }, { timeout: 5000 });
594
- });
595
-
596
- it('disables import button while processing', async () => {
597
- const user = userEvent.setup();
598
- const onImport = vi.fn(async () => {
599
- await new Promise<void>(resolve => setTimeout(resolve, 100));
600
- });
601
- const csvContent = 'name,email\nJohn,john@example.com';
602
- const file = createCSVFile(csvContent);
603
-
604
- render(<ImportModal {...baseProps} onImport={onImport} />);
605
-
606
- // Wait for dialog to be accessible first, then wait for file input
607
- await waitForDialog();
608
- await waitFor(() => {
609
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
610
- expect(fileInput).toBeInTheDocument();
611
- }, { timeout: 5000 });
612
-
613
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
614
- await user.upload(fileInput, file);
615
-
616
- // Wait for preview to appear
617
- await waitFor(() => {
618
- const table = screen.queryByRole('table') ||
619
- document.querySelector('table') ||
620
- document.querySelector('table.min-w-full');
621
- expect(table).toBeInTheDocument();
622
- }, { timeout: 10000 });
623
-
624
- await waitFor(() => {
625
- expect(screen.getByText('John')).toBeInTheDocument();
626
- }, { timeout: 2000 });
627
-
628
- const importButton = findButtonByText(/^import$/i);
629
- expect(importButton).toBeInTheDocument();
630
- if (importButton) {
631
- await user.click(importButton);
632
- }
633
-
634
- await waitFor(() => {
635
- expect(importButton).toBeDisabled();
636
- }, { timeout: 1000 });
637
- });
638
-
639
- it('shows processing text while importing', async () => {
640
- const user = userEvent.setup();
641
- const onImport = vi.fn(async () => {
642
- await new Promise<void>(resolve => setTimeout(resolve, 100));
643
- });
644
- const csvContent = 'name,email\nJohn,john@example.com';
645
- const file = createCSVFile(csvContent);
646
-
647
- render(<ImportModal {...baseProps} onImport={onImport} />);
648
-
649
- // Wait for dialog to be accessible first, then wait for file input
650
- await waitForDialog();
651
- await waitFor(() => {
652
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
653
- expect(fileInput).toBeInTheDocument();
654
- }, { timeout: 5000 });
655
-
656
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
657
- await user.upload(fileInput, file);
658
-
659
- // Wait for preview to appear
660
- await waitFor(() => {
661
- const table = screen.queryByRole('table') ||
662
- document.querySelector('table') ||
663
- document.querySelector('table.min-w-full');
664
- expect(table).toBeInTheDocument();
665
- }, { timeout: 10000 });
666
-
667
- await waitFor(() => {
668
- expect(screen.getByText('John')).toBeInTheDocument();
669
- }, { timeout: 2000 });
670
-
671
- const importButton = findButtonByText(/^import$/i);
672
- expect(importButton).toBeInTheDocument();
673
- if (importButton) {
674
- await user.click(importButton);
675
- }
676
-
677
- // Button text changes to "Processing..." when isProcessing is true
678
- await waitFor(() => {
679
- const processingButton = findButtonByText(/processing/i);
680
- expect(processingButton).toBeInTheDocument();
681
- }, { timeout: 1000 });
682
- });
683
-
684
- it('calls onClose after successful import', async () => {
685
- const user = userEvent.setup();
686
- const onClose = vi.fn();
687
- const csvContent = 'name,email\nJohn,john@example.com';
688
- const file = createCSVFile(csvContent);
689
-
690
- render(<ImportModal {...baseProps} onClose={onClose} />);
691
-
692
- // Wait for dialog to be accessible first, then wait for file input
693
- await waitForDialog();
694
- await waitFor(() => {
695
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
696
- expect(fileInput).toBeInTheDocument();
697
- }, { timeout: 5000 });
698
-
699
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
700
- await user.upload(fileInput, file);
701
-
702
- // Wait for preview to appear
703
- await waitFor(() => {
704
- const table = screen.queryByRole('table') ||
705
- document.querySelector('table') ||
706
- document.querySelector('table.min-w-full');
707
- expect(table).toBeInTheDocument();
708
- }, { timeout: 10000 });
709
-
710
- await waitFor(() => {
711
- expect(screen.getByText('John')).toBeInTheDocument();
712
- }, { timeout: 2000 });
713
-
714
- const importButton = findButtonByText(/^import$/i);
715
- expect(importButton).toBeInTheDocument();
716
- if (importButton) {
717
- await user.click(importButton);
718
- }
719
-
720
- await waitFor(() => {
721
- expect(onClose).toHaveBeenCalled();
722
- }, { timeout: 3000 });
723
- });
724
- });
725
-
726
- describe('Close Action', () => {
727
- it('calls onClose when cancel button is clicked', async () => {
728
- const user = userEvent.setup();
729
- const onClose = vi.fn();
730
-
731
- render(<ImportModal {...baseProps} onClose={onClose} />);
732
-
733
- // Wait for dialog title first (most reliable indicator dialog is rendered)
734
- // Use getByRole for heading to avoid multiple matches (h2 and button both have "Import Data")
735
- await waitFor(() => {
736
- try {
737
- expect(screen.getByRole('heading', { name: 'Import Data' })).toBeInTheDocument();
738
- } catch (_e) {
739
- // Fallback: check if any element with "Import Data" exists
740
- const elements = screen.getAllByText('Import Data');
741
- expect(elements.length).toBeGreaterThan(0);
742
- }
743
- }, { timeout: 5000 });
744
-
745
- // Then wait for cancel button
746
- await waitFor(() => {
747
- const cancelButton = findButtonByText(/cancel/i);
748
- expect(cancelButton).toBeInTheDocument();
749
- }, { timeout: 5000 });
750
-
751
- const cancelButton = findButtonByText(/cancel/i);
752
- expect(cancelButton).toBeInTheDocument();
753
- if (cancelButton) {
754
- await user.click(cancelButton);
755
- }
756
-
757
- expect(onClose).toHaveBeenCalledTimes(1);
758
- });
759
-
760
- it('resets state when modal closes', async () => {
761
- const user = userEvent.setup();
762
- const csvContent = 'name,email\nJohn,john@example.com';
763
- const file = createCSVFile(csvContent);
764
-
765
- const { rerender } = render(<ImportModal {...baseProps} />);
766
-
767
- // Wait for dialog to be accessible first, then wait for file input
768
- await waitForDialog();
769
- await waitFor(() => {
770
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
771
- expect(fileInput).toBeInTheDocument();
772
- }, { timeout: 5000 });
773
-
774
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
775
- await user.upload(fileInput, file);
776
-
777
- // Wait for preview to appear
778
- await waitFor(() => {
779
- const table = screen.queryByRole('table') ||
780
- document.querySelector('table') ||
781
- document.querySelector('table.min-w-full');
782
- expect(table).toBeInTheDocument();
783
- }, { timeout: 10000 });
784
-
785
- await waitFor(() => {
786
- expect(screen.getByText('John')).toBeInTheDocument();
787
- }, { timeout: 2000 });
788
-
789
- rerender(<ImportModal {...baseProps} isOpen={false} />);
790
- rerender(<ImportModal {...baseProps} isOpen={true} />);
791
-
792
- // State should be reset - wait for modal to reopen
793
- await waitFor(() => {
794
- expect(screen.queryByText('John')).not.toBeInTheDocument();
795
- }, { timeout: 1000 });
796
- });
797
- });
798
-
799
- describe('Custom Configuration', () => {
800
- it('uses custom button texts from config', async () => {
801
- render(
802
- <ImportModal
803
- {...baseProps}
804
- config={{
805
- selectFileButtonText: 'Browse Files',
806
- importButtonText: 'Import Data',
807
- cancelButtonText: 'Close',
808
- }}
809
- />
810
- );
811
-
812
- // Wait for dialog title first (most reliable indicator dialog is rendered)
813
- // Use getByRole for heading to avoid multiple matches (h2 and button both have "Import Data")
814
- await waitFor(() => {
815
- try {
816
- expect(screen.getByRole('heading', { name: 'Import Data' })).toBeInTheDocument();
817
- } catch (_e) {
818
- // Fallback: check if any element with "Import Data" exists
819
- const elements = screen.getAllByText('Import Data');
820
- expect(elements.length).toBeGreaterThan(0);
821
- }
822
- }, { timeout: 5000 });
823
-
824
- // Then wait for buttons
825
- await waitFor(() => {
826
- const browseButton = findButtonByText(/browse files/i);
827
- const importDataButton = findButtonByText(/import data/i);
828
- expect(browseButton).toBeInTheDocument();
829
- expect(importDataButton).toBeInTheDocument();
830
- }, { timeout: 5000 });
831
- // Dialog has a close button too, so find the Cancel one by text
832
- const cancelButton = findButtonByText(/^close$/i);
833
- expect(cancelButton).toBeInTheDocument();
834
- });
835
-
836
- it('uses custom preview header text from config', async () => {
837
- const user = userEvent.setup();
838
- const csvContent = 'name,email\nJohn,john@example.com';
839
- const file = createCSVFile(csvContent);
840
-
841
- render(
842
- <ImportModal
843
- {...baseProps}
844
- config={{ previewHeaderText: 'Data Preview' }}
845
- />
846
- );
847
-
848
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
849
- await user.upload(fileInput, file);
850
-
851
- // Wait for preview to appear
852
- await waitFor(() => {
853
- expect(screen.getByText('Data Preview')).toBeInTheDocument();
854
- }, { timeout: 5000 });
855
- });
856
-
857
- it('uses custom total rows text from config', async () => {
858
- const user = userEvent.setup();
859
- const csvContent = 'name,email\nJohn,john@example.com\nJane,jane@example.com';
860
- const file = createCSVFile(csvContent);
861
-
862
- render(
863
- <ImportModal
864
- {...baseProps}
865
- config={{ totalRowsText: 'Found {count} records' }}
866
- />
867
- );
868
-
869
- // Wait for dialog to be accessible first, then wait for file input
870
- await waitForDialog();
871
- await waitFor(() => {
872
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
873
- expect(fileInput).toBeInTheDocument();
874
- }, { timeout: 5000 });
875
-
876
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
877
- await user.upload(fileInput, file);
878
-
879
- // Wait for preview to appear
880
- await waitFor(() => {
881
- const table = screen.queryByRole('table') ||
882
- document.querySelector('table') ||
883
- document.querySelector('table.min-w-full');
884
- expect(table).toBeInTheDocument();
885
- }, { timeout: 10000 });
886
-
887
- await waitFor(() => {
888
- expect(screen.getByText(/found 2 records/i)).toBeInTheDocument();
889
- }, { timeout: 2000 });
890
- });
891
- });
892
-
893
- describe('Edge Cases', () => {
894
-
895
- it('handles very large CSV files', async () => {
896
- const user = userEvent.setup();
897
- const headers = 'name,email\n';
898
- const rows = Array.from({ length: 1000 }, (_, i) => `User${i},user${i}@example.com`).join('\n');
899
- const csvContent = headers + rows;
900
- const file = createCSVFile(csvContent);
901
-
902
- render(<ImportModal {...baseProps} />);
903
-
904
- // Wait for dialog to be accessible first, then wait for file input
905
- await waitForDialog();
906
- await waitFor(() => {
907
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
908
- expect(fileInput).toBeInTheDocument();
909
- }, { timeout: 5000 });
910
-
911
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
912
- await user.upload(fileInput, file);
913
-
914
- await waitFor(() => {
915
- expect(screen.getByText(/total rows to import: 1000/i)).toBeInTheDocument();
916
- }, { timeout: 5000 });
917
- });
918
- });
919
-
920
- describe('Accessibility', () => {
921
- it('provides accessible file input', () => {
922
- render(<ImportModal {...baseProps} />);
923
-
924
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
925
- expect(fileInput).toBeInTheDocument();
926
- expect(fileInput).toHaveAttribute('type', 'file');
927
- expect(fileInput).toHaveAttribute('accept', '.csv');
928
- });
929
-
930
- it('provides accessible button labels', async () => {
931
- render(<ImportModal {...baseProps} />);
932
-
933
- // Wait for dialog title first (most reliable indicator dialog is rendered)
934
- // Use getByRole for heading to avoid multiple matches (h2 and button both have "Import Data")
935
- await waitFor(() => {
936
- try {
937
- expect(screen.getByRole('heading', { name: 'Import Data' })).toBeInTheDocument();
938
- } catch (_e) {
939
- // Fallback: check if any element with "Import Data" exists
940
- const elements = screen.getAllByText('Import Data');
941
- expect(elements.length).toBeGreaterThan(0);
942
- }
943
- }, { timeout: 5000 });
944
-
945
- // Then wait for buttons
946
- await waitFor(() => {
947
- const selectFileButton = findButtonByText(/select file/i);
948
- const importButton = findButtonByText(/^import$/i);
949
- const cancelButton = findButtonByText(/cancel/i);
950
- expect(selectFileButton).toBeInTheDocument();
951
- expect(importButton).toBeInTheDocument();
952
- expect(cancelButton).toBeInTheDocument();
953
- }, { timeout: 5000 });
954
- });
955
- });
956
- });
957
-