@jmruthers/pace-core 0.6.5 → 0.6.7

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 (473) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +5 -403
  3. package/audit-tool/00-dependencies.cjs +394 -0
  4. package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
  5. package/audit-tool/audits/02-project-structure.cjs +255 -0
  6. package/audit-tool/audits/03-architecture.cjs +196 -0
  7. package/audit-tool/audits/04-code-quality.cjs +149 -0
  8. package/audit-tool/audits/05-styling.cjs +224 -0
  9. package/audit-tool/audits/06-security-rbac.cjs +544 -0
  10. package/audit-tool/audits/07-api-tech-stack.cjs +301 -0
  11. package/audit-tool/audits/08-testing-documentation.cjs +202 -0
  12. package/audit-tool/audits/09-operations.cjs +208 -0
  13. package/audit-tool/index.cjs +291 -0
  14. package/audit-tool/utils/code-utils.cjs +218 -0
  15. package/audit-tool/utils/file-utils.cjs +230 -0
  16. package/audit-tool/utils/report-utils.cjs +241 -0
  17. package/core-usage-manifest.json +93 -0
  18. package/cursor-rules/00-standards-overview.mdc +156 -0
  19. package/cursor-rules/01-pace-core-compliance.mdc +586 -0
  20. package/cursor-rules/02-project-structure.mdc +42 -4
  21. package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +126 -10
  22. package/cursor-rules/04-code-quality.mdc +419 -0
  23. package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +104 -34
  24. package/cursor-rules/06-security-rbac.mdc +518 -0
  25. package/cursor-rules/07-api-tech-stack.mdc +377 -0
  26. package/cursor-rules/08-testing-documentation.mdc +324 -0
  27. package/cursor-rules/09-operations.mdc +365 -0
  28. package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
  29. package/dist/DataTable-7PMH7XN7.js +15 -0
  30. package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
  31. package/dist/{PublicPageProvider-QTFVrL-Z.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +33 -72
  32. package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
  33. package/dist/api-Y4MQWOFW.js +4 -0
  34. package/dist/audit-MYQXYZFU.js +3 -0
  35. package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
  36. package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
  37. package/dist/{chunk-UPPMRMYG.js → chunk-5X4QLXRG.js} +73 -151
  38. package/dist/chunk-6F3IILHI.js +62 -0
  39. package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
  40. package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
  41. package/dist/{chunk-FMUCXFII.js → chunk-7ILTDCL2.js} +9 -5
  42. package/dist/{chunk-M43Y4SSO.js → chunk-A3W6LW53.js} +15 -13
  43. package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
  44. package/dist/{chunk-HU2C6SSC.js → chunk-BM4CQ5P3.js} +606 -559
  45. package/dist/chunk-C7NSAPTL.js +1 -0
  46. package/dist/{chunk-J36DSWQK.js → chunk-FEJLJNWA.js} +7 -41
  47. package/dist/{chunk-IHB5DR3H.js → chunk-FTCRZOG2.js} +188 -387
  48. package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
  49. package/dist/chunk-GHYHJTYV.js +994 -0
  50. package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
  51. package/dist/{chunk-FFQEQTNW.js → chunk-IUBRCBSY.js} +134 -45
  52. package/dist/{chunk-6COVEUS7.js → chunk-JGWDVX64.js} +983 -1034
  53. package/dist/{chunk-RGAWHO7N.js → chunk-L4XMVJKY.js} +77 -222
  54. package/dist/chunk-MBADTM7L.js +64 -0
  55. package/dist/{chunk-M7MPQISP.js → chunk-OJ4SKRSV.js} +3 -16
  56. package/dist/{chunk-IVOFDYWT.js → chunk-Q7Q7V5NV.js} +2109 -1604
  57. package/dist/{chunk-JGRYX5UX.js → chunk-S7DKJPLT.js} +29 -58
  58. package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
  59. package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
  60. package/dist/{chunk-NTM7ZSB6.js → chunk-VBCS3DUA.js} +261 -168
  61. package/dist/{chunk-EFN2EIMK.js → chunk-ZFYPMX46.js} +271 -87
  62. package/dist/{chunk-L4OXEN46.js → chunk-ZKAWKYT4.js} +10 -24
  63. package/dist/components.d.ts +7 -5
  64. package/dist/components.js +46 -257
  65. package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
  66. package/dist/eslint-rules/index.cjs +35 -0
  67. package/{src/eslint-rules/pace-core-compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +234 -235
  68. package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
  69. package/dist/eslint-rules/rules/05-styling.cjs +61 -0
  70. package/dist/eslint-rules/rules/06-security-rbac.cjs +806 -0
  71. package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
  72. package/dist/eslint-rules/rules/08-testing.cjs +94 -0
  73. package/dist/eslint-rules/utils/helpers.cjs +42 -0
  74. package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
  75. package/dist/hooks.d.ts +6 -6
  76. package/dist/hooks.js +62 -172
  77. package/dist/icons/index.d.ts +1 -0
  78. package/dist/icons/index.js +1 -0
  79. package/dist/index.d.ts +12 -11
  80. package/dist/index.js +67 -660
  81. package/dist/providers.d.ts +2 -2
  82. package/dist/providers.js +8 -35
  83. package/dist/rbac/eslint-rules.d.ts +46 -44
  84. package/dist/rbac/eslint-rules.js +7 -4
  85. package/dist/rbac/index.d.ts +109 -586
  86. package/dist/rbac/index.js +14 -207
  87. package/dist/styles/index.js +2 -12
  88. package/dist/theming/runtime.d.ts +14 -1
  89. package/dist/theming/runtime.js +3 -19
  90. package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
  91. package/dist/{types-CkbwOr4Y.d.ts → types-DXstZpNI.d.ts} +4 -17
  92. package/dist/types-t9H8qKRw.d.ts +55 -0
  93. package/dist/types.d.ts +1 -1
  94. package/dist/types.js +7 -94
  95. package/dist/{usePublicRouteParams-ClnV4tnv.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +20 -20
  96. package/dist/utils.d.ts +24 -117
  97. package/dist/utils.js +54 -392
  98. package/docs/README.md +17 -7
  99. package/docs/api/README.md +4 -402
  100. package/docs/api/modules.md +301 -871
  101. package/docs/api-reference/components.md +21 -21
  102. package/docs/api-reference/deprecated.md +31 -6
  103. package/docs/api-reference/hooks.md +80 -80
  104. package/docs/api-reference/rpc-functions.md +78 -3
  105. package/docs/api-reference/types.md +1 -1
  106. package/docs/api-reference/utilities.md +1 -1
  107. package/docs/architecture/README.md +1 -1
  108. package/docs/core-concepts/events.md +3 -3
  109. package/docs/core-concepts/organisations.md +6 -6
  110. package/docs/core-concepts/permissions.md +6 -6
  111. package/docs/documentation-index.md +12 -18
  112. package/docs/getting-started/cursor-rules.md +3 -23
  113. package/docs/getting-started/dependencies.md +650 -0
  114. package/docs/getting-started/documentation-index.md +1 -1
  115. package/docs/getting-started/examples/README.md +4 -4
  116. package/docs/getting-started/examples/full-featured-app.md +1 -1
  117. package/docs/getting-started/faq.md +2 -2
  118. package/docs/getting-started/installation-guide.md +20 -7
  119. package/docs/getting-started/quick-reference.md +4 -4
  120. package/docs/getting-started/quick-start.md +23 -12
  121. package/docs/implementation-guides/authentication.md +15 -15
  122. package/docs/implementation-guides/component-styling.md +1 -1
  123. package/docs/implementation-guides/data-tables.md +126 -33
  124. package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
  125. package/docs/implementation-guides/dynamic-colors.md +3 -3
  126. package/docs/implementation-guides/file-upload-storage.md +2 -2
  127. package/docs/implementation-guides/hierarchical-datatable.md +40 -60
  128. package/docs/implementation-guides/inactivity-tracking.md +3 -3
  129. package/docs/implementation-guides/large-datasets.md +3 -2
  130. package/docs/implementation-guides/organisation-security.md +2 -2
  131. package/docs/implementation-guides/performance.md +2 -2
  132. package/docs/implementation-guides/permission-enforcement.md +5 -1
  133. package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
  134. package/docs/migration/V0.4.0_rbac-migration.md +6 -6
  135. package/docs/rbac/MIGRATION_GUIDE.md +819 -0
  136. package/docs/rbac/RBAC_CONTRACT.md +724 -0
  137. package/docs/rbac/README.md +17 -8
  138. package/docs/rbac/advanced-patterns.md +6 -6
  139. package/docs/rbac/api-reference.md +20 -20
  140. package/docs/rbac/edge-functions-guide.md +376 -0
  141. package/docs/rbac/event-based-apps.md +3 -3
  142. package/docs/rbac/examples.md +41 -41
  143. package/docs/rbac/getting-started.md +37 -37
  144. package/docs/rbac/performance.md +1 -1
  145. package/docs/rbac/quick-start.md +52 -52
  146. package/docs/rbac/secure-client-protection.md +1 -35
  147. package/docs/rbac/troubleshooting.md +1 -1
  148. package/docs/security/README.md +5 -5
  149. package/docs/standards/0-standards-overview.md +220 -0
  150. package/docs/standards/1-pace-core-compliance-standards.md +986 -0
  151. package/docs/standards/2-project-structure-standards.md +949 -0
  152. package/docs/standards/3-architecture-standards.md +606 -0
  153. package/docs/standards/4-code-quality-standards.md +728 -0
  154. package/docs/standards/5-styling-standards.md +348 -0
  155. package/docs/standards/{07-rbac-and-rls-standard.md → 6-security-rbac-standards.md} +269 -66
  156. package/docs/standards/7-api-tech-stack-standards.md +662 -0
  157. package/docs/standards/8-testing-documentation-standards.md +401 -0
  158. package/docs/standards/9-operations-standards.md +1102 -0
  159. package/docs/standards/README.md +185 -57
  160. package/docs/troubleshooting/README.md +4 -4
  161. package/docs/troubleshooting/common-issues.md +2 -2
  162. package/docs/troubleshooting/debugging.md +9 -9
  163. package/docs/troubleshooting/migration.md +4 -4
  164. package/docs/troubleshooting/organisation-context-setup.md +42 -19
  165. package/eslint-config-pace-core.cjs +33 -6
  166. package/package.json +35 -23
  167. package/scripts/install-cursor-rules.cjs +25 -6
  168. package/scripts/install-eslint-config.cjs +284 -0
  169. package/src/__tests__/fixtures/supabase.ts +1 -1
  170. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +3 -3
  171. package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
  172. package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
  173. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
  174. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +13 -13
  175. package/src/__tests__/helpers/component-test-utils.tsx +1 -1
  176. package/src/__tests__/helpers/supabaseMock.ts +2 -2
  177. package/src/__tests__/integration/UserProfile.test.tsx +14 -14
  178. package/src/__tests__/public-recipe-view.test.ts +38 -9
  179. package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
  180. package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
  181. package/src/__tests__/templates/component.test.template.tsx +18 -15
  182. package/src/components/Button/Button.tsx +5 -1
  183. package/src/components/Calendar/Calendar.tsx +201 -47
  184. package/src/components/ContextSelector/ContextSelector.tsx +106 -119
  185. package/src/components/DataTable/AUDIT_REPORT.md +293 -0
  186. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
  187. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
  188. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
  189. package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
  190. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
  191. package/src/components/DataTable/components/DataTableCore.tsx +186 -13
  192. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
  193. package/src/components/DataTable/components/DataTableLayout.tsx +35 -21
  194. package/src/components/DataTable/components/EditFields.tsx +23 -3
  195. package/src/components/DataTable/components/EditableRow.tsx +12 -9
  196. package/src/components/DataTable/components/EmptyState.tsx +10 -9
  197. package/src/components/DataTable/components/FilterRow.tsx +2 -4
  198. package/src/components/DataTable/components/ImportModal.tsx +124 -126
  199. package/src/components/DataTable/components/LoadingState.tsx +5 -6
  200. package/src/components/DataTable/components/RowComponent.tsx +12 -0
  201. package/src/components/DataTable/components/SortIndicator.tsx +50 -0
  202. package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
  203. package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
  204. package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
  205. package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
  206. package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
  207. package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +41 -27
  208. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
  209. package/src/components/DataTable/components/index.ts +2 -1
  210. package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
  211. package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
  212. package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
  213. package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
  214. package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
  215. package/src/components/DataTable/types.ts +5 -18
  216. package/src/components/DataTable/utils/a11yUtils.ts +17 -0
  217. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
  218. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
  219. package/src/components/DateTimeField/DateTimeField.tsx +10 -9
  220. package/src/components/Dialog/Dialog.test.tsx +128 -104
  221. package/src/components/Dialog/Dialog.tsx +742 -24
  222. package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
  223. package/src/components/FileDisplay/FileDisplay.test.tsx +4 -2
  224. package/src/components/FileDisplay/FileDisplay.tsx +23 -17
  225. package/src/components/FileUpload/FileUpload.test.tsx +52 -14
  226. package/src/components/FileUpload/FileUpload.tsx +112 -130
  227. package/src/components/Form/Form.test.tsx +6 -8
  228. package/src/components/Form/Form.tsx +365 -4
  229. package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
  230. package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
  231. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
  232. package/src/components/PaceAppLayout/PaceAppLayout.tsx +11 -15
  233. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
  234. package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
  235. package/src/components/Progress/Progress.tsx +2 -4
  236. package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
  237. package/src/components/Select/Select.tsx +109 -98
  238. package/src/components/Select/types.ts +4 -1
  239. package/src/components/UserMenu/UserMenu.tsx +9 -6
  240. package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
  241. package/src/hooks/__tests__/hooks.integration.test.tsx +55 -57
  242. package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
  243. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
  244. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
  245. package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
  246. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
  247. package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
  248. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
  249. package/src/hooks/public/usePublicEvent.ts +67 -195
  250. package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
  251. package/src/hooks/public/usePublicEventLogo.ts +24 -14
  252. package/src/hooks/public/usePublicFileDisplay.ts +2 -2
  253. package/src/hooks/public/usePublicRouteParams.ts +5 -5
  254. package/src/hooks/useAppConfig.ts +28 -26
  255. package/src/hooks/useEventTheme.test.ts +217 -239
  256. package/src/hooks/useEventTheme.ts +16 -28
  257. package/src/hooks/useFileDisplay.ts +2 -2
  258. package/src/hooks/useOrganisationPermissions.ts +5 -7
  259. package/src/hooks/useQueryCache.ts +0 -1
  260. package/src/hooks/useSessionDraft.ts +380 -0
  261. package/src/hooks/useSessionRestoration.ts +3 -1
  262. package/src/icons/index.ts +27 -0
  263. package/src/index.ts +5 -0
  264. package/src/providers/OrganisationProvider.tsx +23 -14
  265. package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
  266. package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
  267. package/src/providers/__tests__/EventProvider.test.tsx +61 -61
  268. package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
  269. package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
  270. package/src/providers/__tests__/ProviderLifecycle.test.tsx +37 -37
  271. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
  272. package/src/providers/services/EventServiceProvider.tsx +1 -24
  273. package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
  274. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
  275. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +13 -10
  276. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
  277. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
  278. package/src/rbac/adapters.tsx +7 -295
  279. package/src/rbac/api.test.ts +44 -56
  280. package/src/rbac/api.ts +10 -17
  281. package/src/rbac/cache-invalidation.ts +0 -1
  282. package/src/rbac/compliance/index.ts +10 -0
  283. package/src/rbac/compliance/pattern-detector.ts +553 -0
  284. package/src/rbac/compliance/runtime-compliance.ts +22 -0
  285. package/src/rbac/components/AccessDenied.tsx +150 -0
  286. package/src/rbac/components/NavigationGuard.tsx +12 -20
  287. package/src/rbac/components/PagePermissionGuard.tsx +4 -24
  288. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
  289. package/src/rbac/components/index.ts +3 -41
  290. package/src/rbac/eslint-rules.js +1 -1
  291. package/src/rbac/hooks/index.ts +0 -3
  292. package/src/rbac/hooks/permissions/index.ts +0 -3
  293. package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
  294. package/src/rbac/hooks/usePermissions.ts +0 -3
  295. package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
  296. package/src/rbac/hooks/useResolvedScope.ts +58 -140
  297. package/src/rbac/hooks/useResourcePermissions.test.ts +124 -38
  298. package/src/rbac/hooks/useResourcePermissions.ts +139 -48
  299. package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
  300. package/src/rbac/hooks/useRoleManagement.ts +147 -19
  301. package/src/rbac/hooks/useSecureSupabase.ts +4 -8
  302. package/src/rbac/index.ts +7 -9
  303. package/src/rbac/utils/contextValidator.ts +9 -7
  304. package/src/services/AuthService.ts +130 -18
  305. package/src/services/EventService.ts +4 -97
  306. package/src/services/InactivityService.ts +16 -0
  307. package/src/services/OrganisationService.ts +7 -44
  308. package/src/services/__tests__/OrganisationService.test.ts +26 -8
  309. package/src/services/base/BaseService.ts +0 -3
  310. package/src/styles/core.css +7 -0
  311. package/src/theming/__tests__/parseEventColours.test.ts +9 -3
  312. package/src/theming/parseEventColours.ts +22 -10
  313. package/src/types/database.generated.ts +4733 -3809
  314. package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
  315. package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
  316. package/src/utils/context/organisationContext.test.ts +13 -28
  317. package/src/utils/context/organisationContext.ts +21 -52
  318. package/src/utils/dynamic/dynamicUtils.ts +1 -1
  319. package/src/utils/file-reference/index.ts +39 -15
  320. package/src/utils/formatting/formatDateTime.test.ts +3 -2
  321. package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
  322. package/src/utils/index.ts +4 -1
  323. package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
  324. package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
  325. package/src/utils/persistence/keyDerivation.ts +304 -0
  326. package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
  327. package/src/utils/security/secureStorage.ts +5 -5
  328. package/src/utils/storage/README.md +1 -1
  329. package/src/utils/storage/helpers.ts +3 -3
  330. package/src/utils/supabase/createBaseClient.ts +147 -0
  331. package/src/utils/timezone/timezone.test.ts +1 -2
  332. package/src/utils/timezone/timezone.ts +1 -1
  333. package/src/utils/validation/csrf.ts +4 -4
  334. package/cursor-rules/00-pace-core-compliance.mdc +0 -331
  335. package/cursor-rules/01-standards-compliance.mdc +0 -244
  336. package/cursor-rules/04-testing-standards.mdc +0 -268
  337. package/cursor-rules/05-bug-reports-and-features.mdc +0 -246
  338. package/cursor-rules/06-code-quality.mdc +0 -309
  339. package/cursor-rules/07-tech-stack-compliance.mdc +0 -214
  340. package/cursor-rules/CHANGELOG.md +0 -119
  341. package/cursor-rules/README.md +0 -192
  342. package/dist/DataTable-AOVNCPTX.js +0 -175
  343. package/dist/DataTable-AOVNCPTX.js.map +0 -1
  344. package/dist/UnifiedAuthProvider-4SBX4LU5.js +0 -18
  345. package/dist/UnifiedAuthProvider-4SBX4LU5.js.map +0 -1
  346. package/dist/api-O6HTBX5Y.js +0 -52
  347. package/dist/api-O6HTBX5Y.js.map +0 -1
  348. package/dist/audit-V53FV5AG.js +0 -17
  349. package/dist/audit-V53FV5AG.js.map +0 -1
  350. package/dist/chunk-5DRSZLL2.js.map +0 -1
  351. package/dist/chunk-63FOKYGO.js.map +0 -1
  352. package/dist/chunk-6COVEUS7.js.map +0 -1
  353. package/dist/chunk-AFVQODI2.js +0 -263
  354. package/dist/chunk-AFVQODI2.js.map +0 -1
  355. package/dist/chunk-DGUM43GV.js.map +0 -1
  356. package/dist/chunk-E66EQZE6.js.map +0 -1
  357. package/dist/chunk-EFN2EIMK.js.map +0 -1
  358. package/dist/chunk-FFQEQTNW.js.map +0 -1
  359. package/dist/chunk-FMUCXFII.js.map +0 -1
  360. package/dist/chunk-G37KK66H.js.map +0 -1
  361. package/dist/chunk-G7QEZTYQ.js +0 -2053
  362. package/dist/chunk-G7QEZTYQ.js.map +0 -1
  363. package/dist/chunk-HU2C6SSC.js.map +0 -1
  364. package/dist/chunk-IHB5DR3H.js.map +0 -1
  365. package/dist/chunk-IVOFDYWT.js.map +0 -1
  366. package/dist/chunk-J36DSWQK.js.map +0 -1
  367. package/dist/chunk-JGRYX5UX.js.map +0 -1
  368. package/dist/chunk-KQCRWDSA.js +0 -1
  369. package/dist/chunk-KQCRWDSA.js.map +0 -1
  370. package/dist/chunk-L4OXEN46.js.map +0 -1
  371. package/dist/chunk-LMC26NLJ.js +0 -84
  372. package/dist/chunk-LMC26NLJ.js.map +0 -1
  373. package/dist/chunk-M43Y4SSO.js.map +0 -1
  374. package/dist/chunk-M7MPQISP.js.map +0 -1
  375. package/dist/chunk-NTM7ZSB6.js.map +0 -1
  376. package/dist/chunk-PWLANIRT.js.map +0 -1
  377. package/dist/chunk-QXHPKYJV.js.map +0 -1
  378. package/dist/chunk-RGAWHO7N.js.map +0 -1
  379. package/dist/chunk-UPPMRMYG.js.map +0 -1
  380. package/dist/chunk-VBXEHIUJ.js.map +0 -1
  381. package/dist/chunk-ZSAAAMVR.js.map +0 -1
  382. package/dist/components.js.map +0 -1
  383. package/dist/contextValidator-5OGXSPKS.js +0 -9
  384. package/dist/contextValidator-5OGXSPKS.js.map +0 -1
  385. package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
  386. package/dist/hooks.js.map +0 -1
  387. package/dist/index.js.map +0 -1
  388. package/dist/providers.js.map +0 -1
  389. package/dist/rbac/eslint-rules.js.map +0 -1
  390. package/dist/rbac/index.js.map +0 -1
  391. package/dist/styles/index.js.map +0 -1
  392. package/dist/theming/runtime.js.map +0 -1
  393. package/dist/types.js.map +0 -1
  394. package/dist/utils.js.map +0 -1
  395. package/docs/best-practices/README.md +0 -472
  396. package/docs/best-practices/accessibility.md +0 -601
  397. package/docs/best-practices/common-patterns.md +0 -516
  398. package/docs/best-practices/deployment.md +0 -1103
  399. package/docs/best-practices/performance.md +0 -1328
  400. package/docs/best-practices/security.md +0 -940
  401. package/docs/best-practices/testing.md +0 -1034
  402. package/docs/rbac/compliance/compliance-guide.md +0 -544
  403. package/docs/standards/01-architecture-standard.md +0 -44
  404. package/docs/standards/02-api-and-rpc-standard.md +0 -39
  405. package/docs/standards/03-component-standard.md +0 -32
  406. package/docs/standards/04-code-style-standard.md +0 -32
  407. package/docs/standards/05-security-standard.md +0 -44
  408. package/docs/standards/06-testing-and-docs-standard.md +0 -29
  409. package/docs/standards/pace-core-compliance.md +0 -432
  410. package/scripts/audit/core/checks/accessibility.cjs +0 -197
  411. package/scripts/audit/core/checks/api-usage.cjs +0 -191
  412. package/scripts/audit/core/checks/bundle.cjs +0 -142
  413. package/scripts/audit/core/checks/compliance.cjs +0 -2706
  414. package/scripts/audit/core/checks/config.cjs +0 -54
  415. package/scripts/audit/core/checks/coverage.cjs +0 -84
  416. package/scripts/audit/core/checks/dependencies.cjs +0 -994
  417. package/scripts/audit/core/checks/documentation.cjs +0 -268
  418. package/scripts/audit/core/checks/environment.cjs +0 -116
  419. package/scripts/audit/core/checks/error-handling.cjs +0 -340
  420. package/scripts/audit/core/checks/forms.cjs +0 -172
  421. package/scripts/audit/core/checks/heuristics.cjs +0 -68
  422. package/scripts/audit/core/checks/hooks.cjs +0 -334
  423. package/scripts/audit/core/checks/imports.cjs +0 -244
  424. package/scripts/audit/core/checks/performance.cjs +0 -325
  425. package/scripts/audit/core/checks/routes.cjs +0 -117
  426. package/scripts/audit/core/checks/state.cjs +0 -130
  427. package/scripts/audit/core/checks/structure.cjs +0 -65
  428. package/scripts/audit/core/checks/style.cjs +0 -584
  429. package/scripts/audit/core/checks/testing.cjs +0 -122
  430. package/scripts/audit/core/checks/typescript.cjs +0 -61
  431. package/scripts/audit/core/scanner.cjs +0 -199
  432. package/scripts/audit/core/utils.cjs +0 -137
  433. package/scripts/audit/index.cjs +0 -223
  434. package/scripts/audit/reporters/console.cjs +0 -151
  435. package/scripts/audit/reporters/json.cjs +0 -54
  436. package/scripts/audit/reporters/markdown.cjs +0 -124
  437. package/scripts/audit-consuming-app.cjs +0 -86
  438. package/src/components/DataTable/components/DataTableBody.tsx +0 -454
  439. package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
  440. package/src/components/DataTable/components/ExpandButton.tsx +0 -113
  441. package/src/components/DataTable/components/GroupHeader.tsx +0 -54
  442. package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
  443. package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
  444. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
  445. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
  446. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
  447. package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
  448. package/src/components/DataTable/core/DataTableContext.tsx +0 -216
  449. package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
  450. package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
  451. package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
  452. package/src/components/DataTable/utils/debugTools.ts +0 -514
  453. package/src/eslint-rules/pace-core-compliance.js +0 -638
  454. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
  455. package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
  456. package/src/rbac/components/NavigationProvider.test.tsx +0 -481
  457. package/src/rbac/components/NavigationProvider.tsx +0 -345
  458. package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
  459. package/src/rbac/components/PagePermissionProvider.tsx +0 -279
  460. package/src/rbac/components/PermissionEnforcer.tsx +0 -312
  461. package/src/rbac/components/RoleBasedRouter.tsx +0 -440
  462. package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
  463. package/src/rbac/components/SecureDataProvider.tsx +0 -339
  464. package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
  465. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
  466. package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
  467. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
  468. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
  469. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
  470. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
  471. package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
  472. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
  473. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
@@ -1,1034 +0,0 @@
1
- ---
2
- lastUpdated: 2025-11-18T17:00:00+11:00
3
- version: 0.5.181
4
- reviewedBy: documentation-standards-audit
5
- ---
6
-
7
- # Testing Best Practices
8
-
9
- Comprehensive testing is essential for maintaining code quality and preventing regressions. This guide provides testing strategies and patterns for `@jmruthers/pace-core` applications.
10
-
11
- > 📖 **For detailed testing standards and implementation patterns**, see the [Testing Standard](../standards/testing-standard.md) - the authoritative guide for writing world-class tests in pace-core.
12
-
13
- ## Overview
14
-
15
- Testing in `@jmruthers/pace-core` covers multiple layers:
16
-
17
- - **Unit Testing**: Individual components and functions
18
- - **Integration Testing**: Component interactions and hooks
19
- - **E2E Testing**: Complete user workflows
20
- - **Accessibility Testing**: Screen reader and keyboard navigation
21
- - **Performance Testing**: Component rendering and memory usage
22
- - **Security Testing**: Authentication and authorization
23
-
24
- ## Testing Strategy
25
-
26
- ### 1. Testing Pyramid
27
-
28
- ```typescript
29
- // Base: Unit Tests (70%)
30
- // - Individual components
31
- // - Utility functions
32
- // - Hooks
33
- // - Form validation
34
-
35
- // Middle: Integration Tests (20%)
36
- // - Component interactions
37
- // - Hook combinations
38
- // - API integration
39
-
40
- // Top: E2E Tests (10%)
41
- // - Complete user workflows
42
- // - Critical user journeys
43
- ```
44
-
45
- ### 2. Test Organisation
46
-
47
- ```typescript
48
- // File structure
49
- src/
50
- ├── components/
51
- │ ├── Button/
52
- │ │ ├── Button.tsx
53
- │ │ ├── Button.test.tsx
54
- │ │ └── __snapshots__/
55
- │ └── DataTable/
56
- │ ├── DataTable.tsx
57
- │ ├── DataTable.test.tsx
58
- │ └── __snapshots__/
59
- ├── hooks/
60
- │ ├── useEvents.ts
61
- │ ├── useEvents.test.ts
62
- │ └── __mocks__/
63
- └── utils/
64
- ├── validation.ts
65
- ├── validation.test.ts
66
- └── __mocks__/
67
- ```
68
-
69
- ## Unit Testing
70
-
71
- ### 1. Component Testing
72
-
73
- ```typescript
74
- import { render, screen, fireEvent } from '@testing-library/react';
75
- import { Button } from '@jmruthers/pace-core';
76
-
77
- describe('Button', () => {
78
- test('renders with correct text', () => {
79
- render(<Button>Click me</Button>);
80
- expect(screen.getByText('Click me')).toBeInTheDocument();
81
- });
82
-
83
- test('calls onClick when clicked', () => {
84
- const handleClick = jest.fn();
85
- render(<Button onClick={handleClick}>Click me</Button>);
86
-
87
- fireEvent.click(screen.getByText('Click me'));
88
- expect(handleClick).toHaveBeenCalledTimes(1);
89
- });
90
-
91
- test('is disabled when disabled prop is true', () => {
92
- render(<Button disabled>Click me</Button>);
93
- expect(screen.getByText('Click me')).toBeDisabled();
94
- });
95
-
96
- test('shows loading state', () => {
97
- render(<Button loading>Click me</Button>);
98
- expect(screen.getByText('Click me')).toHaveClass('loading');
99
- });
100
-
101
- test('applies variant classes correctly', () => {
102
- const { rerender } = render(<Button variant="primary">Button</Button>);
103
- expect(screen.getByText('Button')).toHaveClass('btn-primary');
104
-
105
- rerender(<Button variant="secondary">Button</Button>);
106
- expect(screen.getByText('Button')).toHaveClass('btn-secondary');
107
- });
108
- });
109
- ```
110
-
111
- ### 2. Hook Testing
112
-
113
- ```typescript
114
- import { renderHook, act } from '@testing-library/react';
115
- import { useEvents } from '@jmruthers/pace-core';
116
-
117
- // Mock the Supabase client
118
- jest.mock('@jmruthers/pace-core', () => ({
119
- ...jest.requireActual('@jmruthers/pace-core'),
120
- useSupabase: () => ({
121
- supabase: {
122
- from: jest.fn().mockReturnThis(),
123
- select: jest.fn().mockReturnThis(),
124
- eq: jest.fn().mockReturnThis(),
125
- },
126
- }),
127
- }));
128
-
129
- describe('useEvents', () => {
130
- test('returns initial state', () => {
131
- const { result } = renderHook(() => useEvents());
132
-
133
- expect(result.current.events).toEqual([]);
134
- expect(result.current.loading).toBe(true);
135
- expect(result.current.error).toBeNull();
136
- });
137
-
138
- test('fetches events successfully', async () => {
139
- const mockEvents = [
140
- { id: '1', name: 'Event 1', start_date: '2024-01-01' },
141
- { id: '2', name: 'Event 2', start_date: '2024-01-02' },
142
- ];
143
-
144
- // Mock successful API response
145
- const mockSupabase = {
146
- from: jest.fn().mockReturnThis(),
147
- select: jest.fn().mockReturnThis(),
148
- eq: jest.fn().mockResolvedValue({ data: mockEvents, error: null }),
149
- };
150
-
151
- const { result } = renderHook(() => useEvents());
152
-
153
- await act(async () => {
154
- // Wait for the hook to complete its async operation
155
- await new Promise(resolve => setTimeout(resolve, 0));
156
- });
157
-
158
- expect(result.current.events).toEqual(mockEvents);
159
- expect(result.current.loading).toBe(false);
160
- expect(result.current.error).toBeNull();
161
- });
162
-
163
- test('handles API errors', async () => {
164
- const mockError = new Error('Failed to fetch events');
165
-
166
- // Mock failed API response
167
- const mockSupabase = {
168
- from: jest.fn().mockReturnThis(),
169
- select: jest.fn().mockReturnThis(),
170
- eq: jest.fn().mockResolvedValue({ data: null, error: mockError }),
171
- };
172
-
173
- const { result } = renderHook(() => useEvents());
174
-
175
- await act(async () => {
176
- await new Promise(resolve => setTimeout(resolve, 0));
177
- });
178
-
179
- expect(result.current.events).toEqual([]);
180
- expect(result.current.loading).toBe(false);
181
- expect(result.current.error).toBe(mockError.message);
182
- });
183
- });
184
- ```
185
-
186
- ### 3. Utility Function Testing
187
-
188
- ```typescript
189
- import { validateEmail, formatDate, calculateEventDuration } from '@jmruthers/pace-core';
190
-
191
- describe('validation utilities', () => {
192
- describe('validateEmail', () => {
193
- test('returns true for valid emails', () => {
194
- expect(validateEmail('test@example.com')).toBe(true);
195
- expect(validateEmail('user.name@domain.co.uk')).toBe(true);
196
- expect(validateEmail('user+tag@example.com')).toBe(true);
197
- });
198
-
199
- test('returns false for invalid emails', () => {
200
- expect(validateEmail('invalid-email')).toBe(false);
201
- expect(validateEmail('test@')).toBe(false);
202
- expect(validateEmail('@example.com')).toBe(false);
203
- expect(validateEmail('')).toBe(false);
204
- });
205
- });
206
-
207
- describe('formatDate', () => {
208
- test('formats date correctly', () => {
209
- const date = new Date('2024-01-15T10:30:00Z');
210
- expect(formatDate(date)).toMatch(/^\d{1,2} [A-Za-z]{3} \d{4}$/); // "15 Jan 2024" format
211
- });
212
-
213
- test('handles different input types', () => {
214
- const dateString = '2024-01-15T10:30:00Z';
215
- const timestamp = new Date(dateString).getTime();
216
-
217
- expect(formatDate(dateString)).toMatch(/^\d{1,2} [A-Za-z]{3} \d{4}$/);
218
- expect(formatDate(timestamp)).toMatch(/^\d{1,2} [A-Za-z]{3} \d{4}$/);
219
- });
220
- });
221
-
222
- describe('formatTime', () => {
223
- test('formats time correctly in 24-hour format', () => {
224
- const date = new Date('2024-01-15T14:30:00Z');
225
- expect(formatTime(date)).toMatch(/^\d{2}:\d{2}$/); // "14:30" format
226
- });
227
- });
228
-
229
- describe('formatDateTime', () => {
230
- test('formats date and time correctly', () => {
231
- const date = new Date('2024-01-15T14:30:00Z');
232
- expect(formatDateTime(date)).toMatch(/^\d{1,2} [A-Za-z]{3} \d{4}, \d{2}:\d{2}$/); // "15 Jan 2024, 14:30" format
233
- });
234
- });
235
-
236
- describe('calculateEventDuration', () => {
237
- test('calculates duration in hours', () => {
238
- const startDate = '2024-01-15T09:00:00Z';
239
- const endDate = '2024-01-15T17:00:00Z';
240
- expect(calculateEventDuration(startDate, endDate, 'hours')).toBe(8);
241
- });
242
-
243
- test('calculates duration in days', () => {
244
- const startDate = '2024-01-15T09:00:00Z';
245
- const endDate = '2024-01-17T09:00:00Z';
246
- expect(calculateEventDuration(startDate, endDate, 'days')).toBe(2);
247
- });
248
- });
249
- });
250
- ```
251
-
252
- ## Integration Testing
253
-
254
- ### 1. Component Integration
255
-
256
- ```typescript
257
- import { render, screen, fireEvent, waitFor } from '@testing-library/react';
258
- import { useEvents, useUnifiedAuth } from '@jmruthers/pace-core';
259
-
260
- // Mock the hooks
261
- jest.mock('@jmruthers/pace-core', () => ({
262
- useEvents: jest.fn(),
263
- useUnifiedAuth: jest.fn(),
264
- }));
265
-
266
- describe('EventList Integration', () => {
267
- beforeEach(() => {
268
- // Setup default mocks
269
- (useUnifiedAuth as jest.Mock).mockReturnValue({
270
- user: { id: '1', email: 'test@example.com' },
271
- loading: false,
272
- });
273
-
274
- (useEvents as jest.Mock).mockReturnValue({
275
- events: [],
276
- loading: false,
277
- error: null,
278
- });
279
- });
280
-
281
- test('displays events when user is authenticated', async () => {
282
- const mockEvents = [
283
- { id: '1', name: 'Event 1', start_date: '2024-01-01' },
284
- { id: '2', name: 'Event 2', start_date: '2024-01-02' },
285
- ];
286
-
287
- (useEvents as jest.Mock).mockReturnValue({
288
- events: mockEvents,
289
- loading: false,
290
- error: null,
291
- });
292
-
293
- render(<EventList />);
294
-
295
- await waitFor(() => {
296
- expect(screen.getByText('Event 1')).toBeInTheDocument();
297
- expect(screen.getByText('Event 2')).toBeInTheDocument();
298
- });
299
- });
300
-
301
- test('shows loading state', () => {
302
- (useEvents as jest.Mock).mockReturnValue({
303
- events: [],
304
- loading: true,
305
- error: null,
306
- });
307
-
308
- render(<EventList />);
309
- expect(screen.getByText('Loading events...')).toBeInTheDocument();
310
- });
311
-
312
- test('shows error state', () => {
313
- (useEvents as jest.Mock).mockReturnValue({
314
- events: [],
315
- loading: false,
316
- error: 'Failed to load events',
317
- });
318
-
319
- render(<EventList />);
320
- expect(screen.getByText('Error: Failed to load events')).toBeInTheDocument();
321
- });
322
- });
323
- ```
324
-
325
- ### 2. Form Integration Testing
326
-
327
- ```typescript
328
- import { render, screen, fireEvent, waitFor } from '@testing-library/react';
329
- import { useZodForm } from '@jmruthers/pace-core';
330
-
331
- describe('UserForm Integration', () => {
332
- test('submits form with valid data', async () => {
333
- const mockSubmit = jest.fn();
334
-
335
- render(
336
- <UserForm onSubmit={mockSubmit} />
337
- );
338
-
339
- // Fill out the form
340
- fireEvent.change(screen.getByLabelText('Name'), {
341
- target: { value: 'John Doe' },
342
- });
343
- fireEvent.change(screen.getByLabelText('Email'), {
344
- target: { value: 'john@example.com' },
345
- });
346
-
347
- // Submit the form
348
- fireEvent.click(screen.getByText('Submit'));
349
-
350
- await waitFor(() => {
351
- expect(mockSubmit).toHaveBeenCalledWith({
352
- name: 'John Doe',
353
- email: 'john@example.com',
354
- });
355
- });
356
- });
357
-
358
- test('shows validation errors for invalid data', async () => {
359
- render(<UserForm onSubmit={jest.fn()} />);
360
-
361
- // Submit without filling required fields
362
- fireEvent.click(screen.getByText('Submit'));
363
-
364
- await waitFor(() => {
365
- expect(screen.getByText('Name is required')).toBeInTheDocument();
366
- expect(screen.getByText('Invalid email')).toBeInTheDocument();
367
- });
368
- });
369
-
370
- test('clears form after successful submission', async () => {
371
- const mockSubmit = jest.fn().mockResolvedValue(undefined);
372
-
373
- render(<UserForm onSubmit={mockSubmit} />);
374
-
375
- // Fill and submit form
376
- fireEvent.change(screen.getByLabelText('Name'), {
377
- target: { value: 'John Doe' },
378
- });
379
- fireEvent.change(screen.getByLabelText('Email'), {
380
- target: { value: 'john@example.com' },
381
- });
382
- fireEvent.click(screen.getByText('Submit'));
383
-
384
- await waitFor(() => {
385
- expect(screen.getByLabelText('Name')).toHaveValue('');
386
- expect(screen.getByLabelText('Email')).toHaveValue('');
387
- });
388
- });
389
- });
390
- ```
391
-
392
- ## Authentication Testing
393
-
394
- ### 1. Auth Hook Testing
395
-
396
- ```typescript
397
- import { renderHook, act } from '@testing-library/react';
398
- import { useUnifiedAuth } from '@jmruthers/pace-core';
399
-
400
- // Mock Supabase auth
401
- const mockSupabase = {
402
- auth: {
403
- signIn: jest.fn(),
404
- signUp: jest.fn(),
405
- signOut: jest.fn(),
406
- getSession: jest.fn(),
407
- },
408
- };
409
-
410
- jest.mock('@supabase/supabase-js', () => ({
411
- createClient: () => mockSupabase,
412
- }));
413
-
414
- describe('useUnifiedAuth', () => {
415
- beforeEach(() => {
416
- jest.clearAllMocks();
417
- });
418
-
419
- test('signs in successfully', async () => {
420
- const mockUser = { id: '1', email: 'test@example.com' };
421
- const mockSession = { user: mockUser, access_token: 'token' };
422
-
423
- mockSupabase.auth.signIn.mockResolvedValue({
424
- data: { user: mockUser, session: mockSession },
425
- error: null,
426
- });
427
-
428
- const { result } = renderHook(() => useUnifiedAuth());
429
-
430
- await act(async () => {
431
- await result.current.signIn({
432
- email: 'test@example.com',
433
- password: 'password123',
434
- });
435
- });
436
-
437
- expect(mockSupabase.auth.signIn).toHaveBeenCalledWith({
438
- email: 'test@example.com',
439
- password: 'password123',
440
- });
441
- expect(result.current.user).toEqual(mockUser);
442
- });
443
-
444
- test('handles sign in errors', async () => {
445
- const mockError = { message: 'Invalid credentials' };
446
-
447
- mockSupabase.auth.signIn.mockResolvedValue({
448
- data: { user: null, session: null },
449
- error: mockError,
450
- });
451
-
452
- const { result } = renderHook(() => useUnifiedAuth());
453
-
454
- await act(async () => {
455
- await result.current.signIn({
456
- email: 'test@example.com',
457
- password: 'wrongpassword',
458
- });
459
- });
460
-
461
- expect(result.current.error).toEqual(mockError);
462
- });
463
-
464
- test('signs out successfully', async () => {
465
- mockSupabase.auth.signOut.mockResolvedValue({ error: null });
466
-
467
- const { result } = renderHook(() => useUnifiedAuth());
468
-
469
- await act(async () => {
470
- await result.current.signOut();
471
- });
472
-
473
- expect(mockSupabase.auth.signOut).toHaveBeenCalled();
474
- expect(result.current.user).toBeNull();
475
- });
476
- });
477
- ```
478
-
479
- ### 2. Protected Route Testing
480
-
481
- ```typescript
482
- import { render, screen } from '@testing-library/react';
483
- import { useUnifiedAuth, useRBAC } from '@jmruthers/pace-core';
484
-
485
- jest.mock('@jmruthers/pace-core', () => ({
486
- useUnifiedAuth: jest.fn(),
487
- useRBAC: jest.fn(),
488
- }));
489
-
490
- describe('ProtectedRoute', () => {
491
- test('renders children when user is authenticated and has permission', () => {
492
- (useUnifiedAuth as jest.Mock).mockReturnValue({
493
- user: { id: '1', email: 'test@example.com' },
494
- loading: false,
495
- });
496
-
497
- (useRBAC as jest.Mock).mockReturnValue({
498
- hasPermission: jest.fn().mockReturnValue(true),
499
- });
500
-
501
- render(
502
- <ProtectedRoute permission="read:users">
503
- <div>Protected Content</div>
504
- </ProtectedRoute>
505
- );
506
-
507
- expect(screen.getByText('Protected Content')).toBeInTheDocument();
508
- });
509
-
510
- test('shows loading when auth is loading', () => {
511
- (useUnifiedAuth as jest.Mock).mockReturnValue({
512
- user: null,
513
- loading: true,
514
- });
515
-
516
- render(
517
- <ProtectedRoute permission="read:users">
518
- <div>Protected Content</div>
519
- </ProtectedRoute>
520
- );
521
-
522
- expect(screen.getByText('Loading...')).toBeInTheDocument();
523
- });
524
-
525
- test('redirects when user is not authenticated', () => {
526
- (useUnifiedAuth as jest.Mock).mockReturnValue({
527
- user: null,
528
- loading: false,
529
- });
530
-
531
- render(
532
- <ProtectedRoute permission="read:users">
533
- <div>Protected Content</div>
534
- </ProtectedRoute>
535
- );
536
-
537
- expect(screen.getByText('Access denied')).toBeInTheDocument();
538
- });
539
-
540
- test('shows access denied when user lacks permission', () => {
541
- (useUnifiedAuth as jest.Mock).mockReturnValue({
542
- user: { id: '1', email: 'test@example.com' },
543
- loading: false,
544
- });
545
-
546
- (useRBAC as jest.Mock).mockReturnValue({
547
- hasPermission: jest.fn().mockReturnValue(false),
548
- });
549
-
550
- render(
551
- <ProtectedRoute permission="read:users">
552
- <div>Protected Content</div>
553
- </ProtectedRoute>
554
- );
555
-
556
- expect(screen.getByText('Access denied')).toBeInTheDocument();
557
- });
558
- });
559
- ```
560
-
561
- ## Accessibility Testing
562
-
563
- ### 1. Screen Reader Testing
564
-
565
- ```typescript
566
- import { render, screen } from '@testing-library/react';
567
- import { axe, toHaveNoViolations } from 'jest-axe';
568
-
569
- expect.extend(toHaveNoViolations);
570
-
571
- describe('Accessibility', () => {
572
- test('Button has no accessibility violations', async () => {
573
- const { container } = render(
574
- <Button aria-label="Submit form">Submit</Button>
575
- );
576
-
577
- const results = await axe(container);
578
- expect(results).toHaveNoViolations();
579
- });
580
-
581
- test('DataTable has proper ARIA attributes', () => {
582
- const columns = [
583
- { accessorKey: 'name', header: 'Name' },
584
- { accessorKey: 'email', header: 'Email' },
585
- ];
586
-
587
- const data = [
588
- { id: '1', name: 'John Doe', email: 'john@example.com' },
589
- { id: '2', name: 'Jane Smith', email: 'jane@example.com' },
590
- ];
591
-
592
- render(<DataTable data={data} columns={columns} />);
593
-
594
- // Check for table role
595
- expect(screen.getByRole('table')).toBeInTheDocument();
596
-
597
- // Check for table headers
598
- expect(screen.getByRole('columnheader', { name: 'Name' })).toBeInTheDocument();
599
- expect(screen.getByRole('columnheader', { name: 'Email' })).toBeInTheDocument();
600
-
601
- // Check for table rows
602
- expect(screen.getAllByRole('row')).toHaveLength(3); // Header + 2 data rows
603
- });
604
-
605
- test('Form has proper labels and associations', () => {
606
- render(
607
- <form>
608
- <label htmlFor="name">Name</label>
609
- <input id="name" type="text" />
610
- <label htmlFor="email">Email</label>
611
- <input id="email" type="email" />
612
- </form>
613
- );
614
-
615
- // Check that labels are properly associated
616
- expect(screen.getByLabelText('Name')).toBeInTheDocument();
617
- expect(screen.getByLabelText('Email')).toBeInTheDocument();
618
- });
619
- });
620
- ```
621
-
622
- ### 2. Keyboard Navigation Testing
623
-
624
- ```typescript
625
- import { render, screen } from '@testing-library/react';
626
- import userEvent from '@testing-library/user-event';
627
-
628
- describe('Keyboard Navigation', () => {
629
- test('supports tab navigation', async () => {
630
- const user = userEvent.setup();
631
-
632
- render(
633
- <div>
634
- <button>First Button</button>
635
- <input type="text" placeholder="Input field" />
636
- <button>Second Button</button>
637
- </div>
638
- );
639
-
640
- // Focus first element
641
- await user.tab();
642
- expect(screen.getByText('First Button')).toHaveFocus();
643
-
644
- // Tab to next element
645
- await user.tab();
646
- expect(screen.getByPlaceholderText('Input field')).toHaveFocus();
647
-
648
- // Tab to next element
649
- await user.tab();
650
- expect(screen.getByText('Second Button')).toHaveFocus();
651
-
652
- // Tab should wrap around
653
- await user.tab();
654
- expect(screen.getByText('First Button')).toHaveFocus();
655
- });
656
-
657
- test('supports Enter key activation', async () => {
658
- const user = userEvent.setup();
659
- const handleClick = jest.fn();
660
-
661
- render(<Button onClick={handleClick}>Click me</Button>);
662
-
663
- await user.tab();
664
- expect(screen.getByText('Click me')).toHaveFocus();
665
-
666
- await user.keyboard('{Enter}');
667
- expect(handleClick).toHaveBeenCalledTimes(1);
668
- });
669
-
670
- test('supports Escape key for modal closing', async () => {
671
- const user = userEvent.setup();
672
- const onClose = jest.fn();
673
-
674
- render(
675
- <Modal isOpen={true} onClose={onClose}>
676
- <div>Modal content</div>
677
- </Modal>
678
- );
679
-
680
- await user.keyboard('{Escape}');
681
- expect(onClose).toHaveBeenCalledTimes(1);
682
- });
683
- });
684
- ```
685
-
686
- ## Performance Testing
687
-
688
- ### 1. Component Performance Testing
689
-
690
- ```typescript
691
- import { render } from '@testing-library/react';
692
- import { useComponentPerformance } from '@jmruthers/pace-core';
693
-
694
- describe('Component Performance', () => {
695
- test('renders within performance budget', () => {
696
- const startTime = performance.now();
697
-
698
- render(<DataTable data={largeDataset} columns={columns} />);
699
-
700
- const endTime = performance.now();
701
- const renderTime = endTime - startTime;
702
-
703
- // Should render within 100ms
704
- expect(renderTime).toBeLessThan(100);
705
- });
706
-
707
- test('does not cause memory leaks', () => {
708
- const initialMemory = performance.memory?.usedJSHeapSize || 0;
709
-
710
- // Render and unmount component multiple times
711
- for (let i = 0; i < 100; i++) {
712
- const { unmount } = render(<EventList />);
713
- unmount();
714
- }
715
-
716
- const finalMemory = performance.memory?.usedJSHeapSize || 0;
717
- const memoryIncrease = finalMemory - initialMemory;
718
-
719
- // Memory increase should be minimal (less than 1MB)
720
- expect(memoryIncrease).toBeLessThan(1024 * 1024);
721
- });
722
- });
723
- ```
724
-
725
- ### 2. Hook Performance Testing
726
-
727
- ```typescript
728
- import { renderHook } from '@testing-library/react';
729
- import { useEvents } from '@jmruthers/pace-core';
730
-
731
- describe('Hook Performance', () => {
732
- test('useEvents renders efficiently', () => {
733
- const startTime = performance.now();
734
-
735
- renderHook(() => useEvents());
736
-
737
- const endTime = performance.now();
738
- const renderTime = endTime - startTime;
739
-
740
- // Hook should initialize within 50ms
741
- expect(renderTime).toBeLessThan(50);
742
- });
743
-
744
- test('useEvents handles large datasets efficiently', () => {
745
- const largeDataset = Array.from({ length: 1000 }, (_, i) => ({
746
- id: i.toString(),
747
- name: `Event ${i}`,
748
- start_date: '2024-01-01',
749
- }));
750
-
751
- const { result } = renderHook(() => useEvents());
752
-
753
- // Simulate large dataset
754
- act(() => {
755
- result.current.setEvents(largeDataset);
756
- });
757
-
758
- expect(result.current.events).toHaveLength(1000);
759
- expect(result.current.loading).toBe(false);
760
- });
761
- });
762
- ```
763
-
764
- ## E2E Testing
765
-
766
- ### 1. User Workflow Testing
767
-
768
- ```typescript
769
- import { test, expect } from '@playwright/test';
770
-
771
- test('complete user registration and event creation workflow', async ({ page }) => {
772
- // Navigate to the app
773
- await page.goto('http://localhost:3000');
774
-
775
- // Register new user
776
- await page.click('text=Sign Up');
777
- await page.fill('[data-testid="email-input"]', 'test@example.com');
778
- await page.fill('[data-testid="password-input"]', 'password123');
779
- await page.click('[data-testid="signup-button"]');
780
-
781
- // Wait for registration to complete
782
- await page.waitForSelector('[data-testid="dashboard"]');
783
-
784
- // Create an event
785
- await page.click('[data-testid="create-event-button"]');
786
- await page.fill('[data-testid="event-name-input"]', 'Test Event');
787
- await page.fill('[data-testid="event-description-input"]', 'Test Description');
788
- await page.fill('[data-testid="event-date-input"]', '2024-12-31');
789
- await page.click('[data-testid="save-event-button"]');
790
-
791
- // Verify event was created
792
- await page.waitForSelector('text=Test Event');
793
- expect(await page.isVisible('text=Test Event')).toBeTruthy();
794
- });
795
-
796
- test('user authentication and permission-based access', async ({ page }) => {
797
- await page.goto('http://localhost:3000');
798
-
799
- // Sign in as admin user
800
- await page.fill('[data-testid="email-input"]', 'admin@example.com');
801
- await page.fill('[data-testid="password-input"]', 'adminpass');
802
- await page.click('[data-testid="signin-button"]');
803
-
804
- await page.waitForSelector('[data-testid="dashboard"]');
805
-
806
- // Verify admin features are available
807
- expect(await page.isVisible('[data-testid="admin-panel"]')).toBeTruthy();
808
- expect(await page.isVisible('[data-testid="user-management"]')).toBeTruthy();
809
-
810
- // Sign in as regular user
811
- await page.click('[data-testid="signout-button"]');
812
- await page.fill('[data-testid="email-input"]', 'user@example.com');
813
- await page.fill('[data-testid="password-input"]', 'userpass');
814
- await page.click('[data-testid="signin-button"]');
815
-
816
- await page.waitForSelector('[data-testid="dashboard"]');
817
-
818
- // Verify admin features are not available
819
- expect(await page.isVisible('[data-testid="admin-panel"]')).toBeFalsy();
820
- expect(await page.isVisible('[data-testid="user-management"]')).toBeFalsy();
821
- });
822
- ```
823
-
824
- ### 2. Critical Path Testing
825
-
826
- ```typescript
827
- test('event management critical path', async ({ page }) => {
828
- await page.goto('http://localhost:3000');
829
-
830
- // Sign in
831
- await page.fill('[data-testid="email-input"]', 'test@example.com');
832
- await page.fill('[data-testid="password-input"]', 'password123');
833
- await page.click('[data-testid="signin-button"]');
834
-
835
- await page.waitForSelector('[data-testid="dashboard"]');
836
-
837
- // Create event
838
- await page.click('[data-testid="create-event"]');
839
- await page.fill('[data-testid="event-name"]', 'Critical Test Event');
840
- await page.fill('[data-testid="event-date"]', '2024-12-31');
841
- await page.click('[data-testid="save-event"]');
842
-
843
- // Verify event appears in list
844
- await page.waitForSelector('text=Critical Test Event');
845
-
846
- // Edit event
847
- await page.click('[data-testid="edit-event-Critical Test Event"]');
848
- await page.fill('[data-testid="event-name"]', 'Updated Critical Test Event');
849
- await page.click('[data-testid="save-event"]');
850
-
851
- // Verify event was updated
852
- await page.waitForSelector('text=Updated Critical Test Event');
853
-
854
- // Delete event
855
- await page.click('[data-testid="delete-event-Updated Critical Test Event"]');
856
- await page.click('[data-testid="confirm-delete"]');
857
-
858
- // Verify event was deleted
859
- await page.waitForSelector('text=Updated Critical Test Event', { state: 'hidden' });
860
- });
861
- ```
862
-
863
- ## Test Utilities
864
-
865
- ### 1. Custom Test Helpers
866
-
867
- ```typescript
868
- // test-utils.tsx
869
- import { render, RenderOptions } from '@testing-library/react';
870
- import { UnifiedAuthProvider, OrganisationProvider } from '@jmruthers/pace-core';
871
-
872
- const AllTheProviders = ({ children }: { children: React.ReactNode }) => {
873
- return (
874
- <UnifiedAuthProvider>
875
- <OrganisationProvider>
876
- {children}
877
- </OrganisationProvider>
878
- </UnifiedAuthProvider>
879
- );
880
- };
881
-
882
- const customRender = (
883
- ui: React.ReactElement,
884
- options?: Omit<RenderOptions, 'wrapper'>
885
- ) => render(ui, { wrapper: AllTheProviders, ...options });
886
-
887
- export * from '@testing-library/react';
888
- export { customRender as render };
889
- ```
890
-
891
- ### 2. Mock Data Factories
892
-
893
- ```typescript
894
- // test-factories.ts
895
- export const createMockUser = (overrides = {}) => ({
896
- id: '1',
897
- email: 'test@example.com',
898
- role: 'user',
899
- created_at: '2024-01-01T00:00:00Z',
900
- ...overrides,
901
- });
902
-
903
- export const createMockEvent = (overrides = {}) => ({
904
- id: '1',
905
- name: 'Test Event',
906
- description: 'Test Description',
907
- start_date: '2024-12-31T10:00:00Z',
908
- end_date: '2024-12-31T18:00:00Z',
909
- organisation_id: '1',
910
- created_at: '2024-01-01T00:00:00Z',
911
- ...overrides,
912
- });
913
-
914
- export const createMockOrganisation = (overrides = {}) => ({
915
- id: '1',
916
- name: 'Test Organisation',
917
- slug: 'test-organisation',
918
- created_at: '2024-01-01T00:00:00Z',
919
- ...overrides,
920
- });
921
- ```
922
-
923
- ### 3. Test Setup and Teardown
924
-
925
- ```typescript
926
- // setupTests.ts
927
- import '@testing-library/jest-dom';
928
- import { server } from './mocks/server';
929
-
930
- beforeAll(() => server.listen());
931
- afterEach(() => server.resetHandlers());
932
- afterAll(() => server.close());
933
-
934
- // Mock IntersectionObserver
935
- global.IntersectionObserver = class IntersectionObserver {
936
- constructor() {}
937
- disconnect() {}
938
- observe() {}
939
- unobserve() {}
940
- };
941
-
942
- // Mock ResizeObserver
943
- global.ResizeObserver = class ResizeObserver {
944
- constructor() {}
945
- disconnect() {}
946
- observe() {}
947
- unobserve() {}
948
- };
949
- ```
950
-
951
- ## Testing Best Practices Checklist
952
-
953
- ### Unit Testing Checklist
954
-
955
- - [ ] Test component rendering
956
- - [ ] Test user interactions
957
- - [ ] Test prop variations
958
- - [ ] Test error states
959
- - [ ] Test loading states
960
- - [ ] Test accessibility features
961
- - [ ] Test edge cases
962
- - [ ] Mock external dependencies
963
- - [ ] Use descriptive test names
964
- - [ ] Keep tests focused and simple
965
-
966
- ### Integration Testing Checklist
967
-
968
- - [ ] Test component interactions
969
- - [ ] Test hook combinations
970
- - [ ] Test form submissions
971
- - [ ] Test API integrations
972
- - [ ] Test authentication flows
973
- - [ ] Test permission checks
974
- - [ ] Test error handling
975
- - [ ] Test loading states
976
- - [ ] Test data flow
977
- - [ ] Test user workflows
978
-
979
- ### E2E Testing Checklist
980
-
981
- - [ ] Test critical user journeys
982
- - [ ] Test authentication flows
983
- - [ ] Test permission-based access
984
- - [ ] Test form submissions
985
- - [ ] Test data persistence
986
- - [ ] Test error scenarios
987
- - [ ] Test responsive design
988
- - [ ] Test cross-browser compatibility
989
- - [ ] Test performance under load
990
- - [ ] Test accessibility compliance
991
-
992
- ### Accessibility Testing Checklist
993
-
994
- - [ ] Test screen reader compatibility
995
- - [ ] Test keyboard navigation
996
- - [ ] Test focus management
997
- - [ ] Test ARIA attributes
998
- - [ ] Test color contrast
999
- - [ ] Test semantic HTML
1000
- - [ ] Test alternative text
1001
- - [ ] Test form labels
1002
- - [ ] Test error announcements
1003
- - [ ] Test skip links
1004
-
1005
- ## ⚠️ Edge Cases
1006
-
1007
- ### Testing Complex Scenarios
1008
-
1009
- When testing complex scenarios:
1010
- - Test with realistic data volumes
1011
- - Verify edge cases are covered
1012
- - Test with different user roles and permissions
1013
- - Ensure tests handle error conditions
1014
- - Test with concurrent operations
1015
-
1016
- ### Flaky Tests
1017
-
1018
- When tests are flaky:
1019
- - Identify root causes of flakiness
1020
- - Use proper wait strategies
1021
- - Avoid timing-dependent tests
1022
- - Mock external dependencies consistently
1023
- - Document known flaky tests
1024
-
1025
- ### Test Performance Issues
1026
-
1027
- When tests are slow:
1028
- - Optimize test setup and teardown
1029
- - Use test parallelization where possible
1030
- - Mock expensive operations
1031
- - Profile slow tests
1032
- - Consider test prioritization
1033
-
1034
- For more information about testing your application, see the [Security Guide](./security.md) and [Performance Guide](./performance.md).