@jmruthers/pace-core 0.5.68 → 0.5.70

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 (394) hide show
  1. package/dist/{DataTable-4IUY7BXB.js → DataTable-OSELOGMA.js} +6 -6
  2. package/dist/{PublicLoadingSpinner-DdKXTkCZ.d.ts → PublicLoadingSpinner-DLpF5bbs.d.ts} +78 -2
  3. package/dist/{chunk-OPCWH3A4.js → chunk-4YMVZ76F.js} +7 -6
  4. package/dist/chunk-4YMVZ76F.js.map +1 -0
  5. package/dist/{chunk-NN45OBIS.js → chunk-5G7JA3L5.js} +3 -5
  6. package/dist/{chunk-NN45OBIS.js.map → chunk-5G7JA3L5.js.map} +1 -1
  7. package/dist/{chunk-U6GPOF6J.js → chunk-5NV76BYF.js} +666 -110
  8. package/dist/chunk-5NV76BYF.js.map +1 -0
  9. package/dist/{chunk-D7ARGIA3.js → chunk-6RBH67W7.js} +23 -6
  10. package/dist/chunk-6RBH67W7.js.map +1 -0
  11. package/dist/{chunk-ZPG4XPV5.js → chunk-BHBMXMLT.js} +5 -7
  12. package/dist/chunk-BHBMXMLT.js.map +1 -0
  13. package/dist/{chunk-ZMS23NS5.js → chunk-FOT3WUV6.js} +3 -5
  14. package/dist/{chunk-ZMS23NS5.js.map → chunk-FOT3WUV6.js.map} +1 -1
  15. package/dist/{chunk-MOJXHWDE.js → chunk-GCUIIBLB.js} +382 -5
  16. package/dist/chunk-GCUIIBLB.js.map +1 -0
  17. package/dist/{chunk-PXWEDX7Y.js → chunk-KWQH4VO3.js} +3 -3
  18. package/dist/{chunk-ZPK5656W.js → chunk-O3NWNXDY.js} +4 -5
  19. package/dist/chunk-O3NWNXDY.js.map +1 -0
  20. package/dist/{chunk-KRCRNXPD.js → chunk-OTJUAYBG.js} +81 -18
  21. package/dist/chunk-OTJUAYBG.js.map +1 -0
  22. package/dist/chunk-SMJZMKYN.js +141 -0
  23. package/dist/chunk-SMJZMKYN.js.map +1 -0
  24. package/dist/{chunk-UYA6U6H7.js → chunk-V2TE7LOF.js} +4 -4
  25. package/dist/{chunk-L3RV2ALE.js → chunk-VKOCWWVY.js} +6 -1
  26. package/dist/{chunk-L3RV2ALE.js.map → chunk-VKOCWWVY.js.map} +1 -1
  27. package/dist/components.d.ts +4 -79
  28. package/dist/components.js +23 -581
  29. package/dist/components.js.map +1 -1
  30. package/dist/hooks.d.ts +1 -1
  31. package/dist/hooks.js +9 -6
  32. package/dist/hooks.js.map +1 -1
  33. package/dist/index.d.ts +4 -3
  34. package/dist/index.js +32 -19
  35. package/dist/index.js.map +1 -1
  36. package/dist/providers.js +6 -7
  37. package/dist/rbac/index.js +6 -6
  38. package/dist/styles/index.js +2 -2
  39. package/dist/theming/runtime.d.ts +4 -3
  40. package/dist/theming/runtime.js +3 -1
  41. package/dist/{usePublicRouteParams-CdoFxnJK.d.ts → usePublicRouteParams-Ua1Vz-HG.d.ts} +35 -1
  42. package/dist/utils.d.ts +4 -1
  43. package/dist/utils.js +3 -3
  44. package/docs/DOCUMENTATION_CHECKLIST.md +281 -0
  45. package/docs/README.md +22 -10
  46. package/docs/api/classes/ColumnFactory.md +1 -1
  47. package/docs/api/classes/ErrorBoundary.md +1 -1
  48. package/docs/api/classes/InvalidScopeError.md +1 -1
  49. package/docs/api/classes/MissingUserContextError.md +1 -1
  50. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  51. package/docs/api/classes/PermissionDeniedError.md +1 -1
  52. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  53. package/docs/api/classes/RBACAuditManager.md +1 -1
  54. package/docs/api/classes/RBACCache.md +1 -1
  55. package/docs/api/classes/RBACEngine.md +1 -1
  56. package/docs/api/classes/RBACError.md +1 -1
  57. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  58. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  59. package/docs/api/classes/StorageUtils.md +1 -1
  60. package/docs/api/enums/FileCategory.md +129 -0
  61. package/docs/api/interfaces/AggregateConfig.md +1 -1
  62. package/docs/api/interfaces/ButtonProps.md +1 -1
  63. package/docs/api/interfaces/CardProps.md +1 -1
  64. package/docs/api/interfaces/ColorPalette.md +1 -1
  65. package/docs/api/interfaces/ColorShade.md +1 -1
  66. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  67. package/docs/api/interfaces/DataTableAction.md +1 -1
  68. package/docs/api/interfaces/DataTableColumn.md +1 -1
  69. package/docs/api/interfaces/DataTableProps.md +1 -1
  70. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  71. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  72. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  73. package/docs/api/interfaces/EventContextType.md +7 -7
  74. package/docs/api/interfaces/EventLogoProps.md +1 -1
  75. package/docs/api/interfaces/EventProviderProps.md +2 -2
  76. package/docs/api/interfaces/FileDisplayProps.md +107 -0
  77. package/docs/api/interfaces/FileMetadata.md +129 -0
  78. package/docs/api/interfaces/FileReference.md +118 -0
  79. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  80. package/docs/api/interfaces/FileUploadOptions.md +85 -0
  81. package/docs/api/interfaces/FileUploadProps.md +1 -1
  82. package/docs/api/interfaces/FooterProps.md +1 -1
  83. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  84. package/docs/api/interfaces/InputProps.md +1 -1
  85. package/docs/api/interfaces/LabelProps.md +1 -1
  86. package/docs/api/interfaces/LoginFormProps.md +1 -1
  87. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  88. package/docs/api/interfaces/NavigationContextType.md +1 -1
  89. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  90. package/docs/api/interfaces/NavigationItem.md +1 -1
  91. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  92. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  93. package/docs/api/interfaces/Organisation.md +1 -1
  94. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  95. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  96. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  97. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  98. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  99. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  100. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  101. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  102. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  103. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  104. package/docs/api/interfaces/PaletteData.md +1 -1
  105. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  106. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  107. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  108. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  109. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  110. package/docs/api/interfaces/PublicPageHeaderProps.md +2 -2
  111. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  112. package/docs/api/interfaces/RBACConfig.md +1 -1
  113. package/docs/api/interfaces/RBACContextType.md +1 -1
  114. package/docs/api/interfaces/RBACLogger.md +1 -1
  115. package/docs/api/interfaces/RBACProviderProps.md +1 -1
  116. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  117. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  118. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  119. package/docs/api/interfaces/RouteConfig.md +1 -1
  120. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  121. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  122. package/docs/api/interfaces/StorageConfig.md +1 -1
  123. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  124. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  125. package/docs/api/interfaces/StorageListOptions.md +1 -1
  126. package/docs/api/interfaces/StorageListResult.md +1 -1
  127. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  128. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  129. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  130. package/docs/api/interfaces/StyleImport.md +1 -1
  131. package/docs/api/interfaces/SwitchProps.md +1 -1
  132. package/docs/api/interfaces/ToastActionElement.md +1 -1
  133. package/docs/api/interfaces/ToastProps.md +1 -1
  134. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  135. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  136. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  137. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  138. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  139. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  140. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  141. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  142. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  143. package/docs/api/interfaces/UserEventAccess.md +1 -1
  144. package/docs/api/interfaces/UserMenuProps.md +1 -1
  145. package/docs/api/interfaces/UserProfile.md +1 -1
  146. package/docs/api/modules.md +228 -23
  147. package/docs/architecture/services.md +374 -0
  148. package/docs/best-practices/README.md +1 -1
  149. package/docs/best-practices/testing.md +1 -1
  150. package/docs/breaking-changes.md +182 -0
  151. package/docs/common-patterns.md +445 -0
  152. package/docs/core-concepts/authentication.md +26 -11
  153. package/docs/core-concepts/events.md +2 -0
  154. package/docs/core-concepts/organisations.md +2 -0
  155. package/docs/core-concepts/permissions.md +2 -0
  156. package/docs/{INDEX.md → documentation-index.md} +26 -38
  157. package/docs/faq.md +286 -0
  158. package/docs/{FILE_REFERENCE_SYSTEM.md → file-reference-system.md} +1 -1
  159. package/docs/getting-started/installation-guide.md +284 -0
  160. package/docs/getting-started/quick-start.md +8 -1
  161. package/docs/implementation-guides/app-layout.md +3 -1
  162. package/docs/implementation-guides/data-tables.md +2 -0
  163. package/docs/implementation-guides/dynamic-colors.md +47 -2
  164. package/docs/implementation-guides/event-theming-summary.md +220 -0
  165. package/docs/implementation-guides/forms.md +9 -7
  166. package/docs/implementation-guides/navigation.md +2 -0
  167. package/docs/migration/service-architecture.md +351 -0
  168. package/docs/rbac/README-rbac-rls-integration.md +2 -2
  169. package/docs/rbac/README.md +1 -1
  170. package/docs/rbac/examples/rbac-rls-integration-example.md +3 -3
  171. package/docs/rbac/quick-start.md +2 -0
  172. package/docs/rbac/rbac-rls-integration.md +2 -2
  173. package/docs/style-guide.md +136 -1
  174. package/docs/testing/README.md +1 -1
  175. package/docs/troubleshooting/authentication-issues.md +334 -0
  176. package/docs/troubleshooting/common-issues.md +2 -0
  177. package/docs/troubleshooting/styling-issues.md +199 -144
  178. package/docs/usage.md +23 -2
  179. package/package.json +3 -2
  180. package/src/__tests__/{TESTING_GUIDELINES.md → TEST_GUIDE_CURSOR.md} +20 -0
  181. package/src/__tests__/TEST_GUIDE_HUMAN.md +103 -0
  182. package/src/__tests__/fixtures/test-data.ts +90 -0
  183. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +260 -0
  184. package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +224 -0
  185. package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +273 -0
  186. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +98 -0
  187. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +436 -0
  188. package/src/__tests__/helpers/__tests__/timer-utils.test.ts +371 -0
  189. package/src/__tests__/helpers/component-test-utils.tsx +14 -4
  190. package/src/__tests__/helpers/optimized-test-setup.ts +68 -0
  191. package/src/__tests__/helpers/test-providers.tsx +329 -0
  192. package/src/__tests__/helpers/test-utils.tsx +91 -45
  193. package/src/__tests__/helpers/timer-utils.ts +71 -0
  194. package/src/__tests__/hooks/usePermissions.test.ts +1 -5
  195. package/src/__tests__/integration/UserProfile.test.tsx +1 -5
  196. package/src/__tests__/rbac/PagePermissionGuard.test.tsx +42 -12
  197. package/src/__tests__/setup.ts +34 -28
  198. package/src/components/Alert/Alert.test.tsx +1 -5
  199. package/src/components/Avatar/Avatar.test.tsx +1 -5
  200. package/src/components/Button/Button.test.tsx +4 -20
  201. package/src/components/Card/Card.test.tsx +1 -5
  202. package/src/components/Checkbox/Checkbox.test.tsx +1 -5
  203. package/src/components/DataTable/__tests__/DataTable.comprehensive.test.tsx +1 -5
  204. package/src/components/DataTable/__tests__/DataTable.test.tsx +45 -49
  205. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +1 -5
  206. package/src/components/DataTable/__tests__/styles.test.ts +382 -0
  207. package/src/components/DataTable/context/__tests__/DataTableContext.test.tsx +409 -0
  208. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +634 -0
  209. package/src/components/DataTable/core/__tests__/DataManager.test.ts +519 -0
  210. package/src/components/DataTable/core/__tests__/StateManager.test.ts +714 -0
  211. package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +592 -0
  212. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +354 -0
  213. package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +539 -0
  214. package/src/components/Dialog/examples/__tests__/SmartDialogExample.unit.test.tsx +1 -5
  215. package/src/components/Dialog/utils/__tests__/safeHtml.unit.test.ts +1 -8
  216. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +34 -38
  217. package/src/components/Footer/Footer.test.tsx +1 -5
  218. package/src/components/Form/Form.test.tsx +22 -35
  219. package/src/components/Header/Header.test.tsx +1 -9
  220. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +1 -5
  221. package/src/components/Input/Input.test.tsx +2 -10
  222. package/src/components/LoginForm/LoginForm.test.tsx +1 -5
  223. package/src/components/NavigationMenu/NavigationMenu.test.tsx +24 -24
  224. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.accessibility.test.tsx +1 -6
  225. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +6 -16
  226. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +1 -4
  227. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +1 -5
  228. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +1 -7
  229. package/src/components/PasswordReset/PasswordChangeForm.test.tsx +1 -9
  230. package/src/components/PasswordReset/PasswordResetForm.test.tsx +1 -9
  231. package/src/components/PublicLayout/PublicErrorBoundary.tsx +4 -5
  232. package/src/components/PublicLayout/PublicPageHeader.tsx +13 -9
  233. package/src/components/PublicLayout/__tests__/EventLogo.test.tsx +666 -0
  234. package/src/components/PublicLayout/__tests__/PublicErrorBoundary.test.tsx +457 -0
  235. package/src/components/PublicLayout/__tests__/PublicLoadingSpinner.test.tsx +393 -0
  236. package/src/components/PublicLayout/__tests__/PublicPageFooter.test.tsx +351 -0
  237. package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +374 -0
  238. package/src/components/PublicLayout/__tests__/PublicPageLayout.test.tsx +388 -0
  239. package/src/components/Select/Select.bug-test.tsx +69 -0
  240. package/src/components/Select/Select.refactored.tsx +497 -0
  241. package/src/components/Select/Select.test.tsx +42 -49
  242. package/src/components/Select/Select.tsx +5 -2
  243. package/src/components/Select/hooks.ts +254 -0
  244. package/src/components/Switch/Switch.test.tsx +1 -5
  245. package/src/components/Table/__tests__/Table.test.tsx +775 -0
  246. package/src/components/Toast/Toast.test.tsx +15 -8
  247. package/src/components/Tooltip/Tooltip.test.tsx +1 -5
  248. package/src/components/UserMenu/UserMenu.test.tsx +3 -15
  249. package/src/components/__tests__/FileDisplay.test.tsx +575 -0
  250. package/src/components/__tests__/FileUpload.test.tsx +446 -0
  251. package/src/components/__tests__/SuperAdminGuard.test.tsx +422 -354
  252. package/src/hooks/__tests__/ServiceHooks.test.tsx +613 -0
  253. package/src/hooks/__tests__/hooks.integration.test.tsx +1 -10
  254. package/src/hooks/__tests__/useApiFetch.unit.test.ts +10 -14
  255. package/src/hooks/__tests__/useAppConfig.unit.test.ts +307 -0
  256. package/src/hooks/__tests__/useComponentPerformance.unit.test.tsx +1 -6
  257. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +1 -5
  258. package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +6 -9
  259. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +321 -0
  260. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +583 -0
  261. package/src/hooks/__tests__/usePublicEventLogo.unit.test.ts +640 -0
  262. package/src/hooks/__tests__/usePublicRouteParams.unit.test.ts +435 -0
  263. package/src/hooks/__tests__/useRBAC.unit.test.ts +10 -10
  264. package/src/hooks/__tests__/useStorage.unit.test.ts +751 -0
  265. package/src/hooks/index.ts +3 -0
  266. package/src/hooks/public/usePublicEvent.ts +30 -9
  267. package/src/hooks/public/usePublicRouteParams.ts +13 -3
  268. package/src/hooks/services/useAuth.ts +50 -0
  269. package/src/hooks/services/useAuthService.ts +30 -0
  270. package/src/hooks/services/useCurrentEvent.ts +36 -0
  271. package/src/hooks/services/useCurrentOrganisation.ts +52 -0
  272. package/src/hooks/services/useEventService.ts +30 -0
  273. package/src/hooks/services/useInactivityService.ts +30 -0
  274. package/src/hooks/services/useOrganisationService.ts +30 -0
  275. package/src/hooks/services/usePermissions.ts +70 -0
  276. package/src/hooks/services/useRBACService.ts +30 -0
  277. package/src/hooks/useCounter.test.ts +1 -5
  278. package/src/hooks/useEventTheme.ts +86 -0
  279. package/src/hooks/useOrganisationPermissions.test.ts +2 -5
  280. package/src/hooks/useOrganisationSecurity.test.ts +1 -5
  281. package/src/hooks/usePermissionCache.test.ts +1 -5
  282. package/src/hooks/usePermissionCheck.ts +150 -0
  283. package/src/hooks/useSecureDataAccess.test.ts +1 -5
  284. package/src/index.ts +7 -0
  285. package/src/providers/EventProvider.tsx +58 -2
  286. package/src/providers/OrganisationProvider.test.tsx +1 -5
  287. package/src/providers/OrganisationProvider.tsx +56 -4
  288. package/src/providers/UnifiedAuthProvider.test.tsx +1 -5
  289. package/src/providers/__tests__/AuthProvider.test.tsx +105 -439
  290. package/src/providers/__tests__/AuthProvider.test.tsx.backup +771 -0
  291. package/src/providers/__tests__/EventProvider.test.tsx +211 -110
  292. package/src/providers/__tests__/EventProvider.test.tsx.backup +824 -0
  293. package/src/providers/__tests__/InactivityProvider.test.tsx +1 -5
  294. package/src/providers/__tests__/OrganisationProvider.test.tsx +97 -261
  295. package/src/providers/__tests__/OrganisationProvider.test.tsx.backup +820 -0
  296. package/src/providers/__tests__/ServiceProviders.test.tsx +477 -0
  297. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +72 -504
  298. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx.backup +911 -0
  299. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx.backup2 +166 -0
  300. package/src/providers/services/AuthServiceProvider.tsx +65 -0
  301. package/src/providers/services/EventServiceProvider.tsx +83 -0
  302. package/src/providers/services/InactivityServiceProvider.tsx +83 -0
  303. package/src/providers/services/OrganisationServiceProvider.tsx +77 -0
  304. package/src/providers/services/RBACServiceProvider.tsx +79 -0
  305. package/src/providers/services/UnifiedAuthProvider.tsx +368 -0
  306. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +210 -0
  307. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +269 -0
  308. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +892 -0
  309. package/src/rbac/__tests__/engine.comprehensive.test.ts +954 -0
  310. package/src/rbac/__tests__/integration.authflow.test.tsx +1 -5
  311. package/src/rbac/__tests__/integration.navigation.test.tsx +1 -4
  312. package/src/rbac/__tests__/rbac-core.test.tsx +2 -7
  313. package/src/rbac/__tests__/rbac-functions.test.ts +1 -9
  314. package/src/rbac/__tests__/rbac-integration.test.ts +1 -9
  315. package/src/rbac/api.test.ts +1 -9
  316. package/src/rbac/cache.test.ts +10 -8
  317. package/src/rbac/cli/__tests__/policy-manager.test.ts +339 -0
  318. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +1 -5
  319. package/src/rbac/components/NavigationProvider.test.tsx +1 -5
  320. package/src/rbac/components/PagePermissionProvider.test.tsx +1 -5
  321. package/src/rbac/components/SecureDataProvider.test.tsx +1 -5
  322. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +25 -29
  323. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +27 -30
  324. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +23 -27
  325. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +18 -22
  326. package/src/rbac/config.test.ts +1 -5
  327. package/src/rbac/hooks/useCan.test.ts +262 -9
  328. package/src/rbac/hooks/usePermissions.test.ts +246 -6
  329. package/src/rbac/hooks/useRBAC.simple.test.ts +1 -5
  330. package/src/rbac/hooks/useRBAC.test.ts +472 -198
  331. package/src/rbac/providers/__tests__/RBACProvider.test.tsx +1 -9
  332. package/src/services/AuthService.ts +416 -0
  333. package/src/services/EventService.ts +366 -0
  334. package/src/services/InactivityService.ts +388 -0
  335. package/src/services/OrganisationService.ts +592 -0
  336. package/src/services/RBACService.ts +522 -0
  337. package/src/services/__tests__/AuthService.test.ts +356 -0
  338. package/src/services/__tests__/BaseService.test.ts +314 -0
  339. package/src/services/__tests__/EventService.test.ts +489 -0
  340. package/src/services/__tests__/InactivityService.test.ts +403 -0
  341. package/src/services/__tests__/OrganisationService.test.ts +660 -0
  342. package/src/services/__tests__/RBACService.test.ts +492 -0
  343. package/src/services/base/BaseService.ts +87 -0
  344. package/src/services/interfaces/IAuthService.ts +39 -0
  345. package/src/services/interfaces/IEventService.ts +30 -0
  346. package/src/services/interfaces/IInactivityService.ts +31 -0
  347. package/src/services/interfaces/IOrganisationService.ts +41 -0
  348. package/src/services/interfaces/IRBACService.ts +62 -0
  349. package/src/theming/__tests__/runtime.test.ts +560 -0
  350. package/src/theming/runtime.ts +71 -28
  351. package/src/types/__tests__/file-reference.test.ts +447 -0
  352. package/src/types/__tests__/organisation.test.ts +1133 -0
  353. package/src/types/__tests__/theme.test.ts +830 -0
  354. package/src/types/__tests__/type-validation.test.ts +527 -0
  355. package/src/utils/__tests__/bundleAnalysis.unit.test.ts +1 -5
  356. package/src/utils/__tests__/debugLogger.test.ts +417 -0
  357. package/src/utils/__tests__/deviceFingerprint.unit.test.ts +1 -6
  358. package/src/utils/__tests__/dynamicUtils.unit.test.ts +1 -5
  359. package/src/utils/__tests__/lazyLoad.unit.test.tsx +35 -35
  360. package/src/utils/__tests__/organisationContext.unit.test.ts +1 -5
  361. package/src/utils/__tests__/performanceBudgets.unit.test.ts +5 -11
  362. package/src/utils/__tests__/secureErrors.unit.test.ts +1 -6
  363. package/src/utils/__tests__/secureStorage.unit.test.ts +1 -5
  364. package/src/utils/__tests__/securityMonitor.unit.test.ts +1 -5
  365. package/src/utils/__tests__/sessionTracking.unit.test.ts +1 -5
  366. package/src/utils/appIdResolver.test.ts +6 -10
  367. package/src/utils/appNameResolver.simple.test.ts +142 -0
  368. package/src/utils/appNameResolver.test.ts +31 -458
  369. package/src/utils/appNameResolver.test.ts.backup +494 -0
  370. package/src/utils/debugLogger.ts +26 -5
  371. package/src/utils/formatDate.test.ts +1 -5
  372. package/src/utils/organisationContext.test.ts +1 -5
  373. package/src/utils/performanceBudgets.ts +3 -4
  374. package/src/utils/secureDataAccess.test.ts +1 -5
  375. package/src/utils/storage/__tests__/helpers.unit.test.ts +1 -5
  376. package/src/validation/__tests__/sqlInjectionProtection.unit.test.ts +1 -5
  377. package/dist/chunk-D7ARGIA3.js.map +0 -1
  378. package/dist/chunk-IPCH4YPT.js +0 -315
  379. package/dist/chunk-IPCH4YPT.js.map +0 -1
  380. package/dist/chunk-KRCRNXPD.js.map +0 -1
  381. package/dist/chunk-MOJXHWDE.js.map +0 -1
  382. package/dist/chunk-N2EUGZRW.js +0 -98
  383. package/dist/chunk-N2EUGZRW.js.map +0 -1
  384. package/dist/chunk-OPCWH3A4.js.map +0 -1
  385. package/dist/chunk-U6GPOF6J.js.map +0 -1
  386. package/dist/chunk-ZPG4XPV5.js.map +0 -1
  387. package/dist/chunk-ZPK5656W.js.map +0 -1
  388. package/docs/getting-started/installation.md +0 -269
  389. package/src/__tests__/REBUILD_PLAN.md +0 -223
  390. package/src/styles/base.css +0 -208
  391. package/src/styles/semantic.css +0 -24
  392. /package/dist/{DataTable-4IUY7BXB.js.map → DataTable-OSELOGMA.js.map} +0 -0
  393. /package/dist/{chunk-PXWEDX7Y.js.map → chunk-KWQH4VO3.js.map} +0 -0
  394. /package/dist/{chunk-UYA6U6H7.js.map → chunk-V2TE7LOF.js.map} +0 -0
@@ -0,0 +1,356 @@
1
+ /**
2
+ * @file AuthService Unit Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module Services/__tests__
5
+ * @since 0.1.0
6
+ *
7
+ * Unit tests for AuthService class.
8
+ * Tests authentication operations, state management, and error handling.
9
+ */
10
+
11
+ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
12
+ import { AuthService } from '../AuthService';
13
+ import { AuthError } from '@supabase/supabase-js';
14
+
15
+ // Mock Supabase client
16
+ const createMockSupabaseClient = () => ({
17
+ auth: {
18
+ signInWithPassword: vi.fn(),
19
+ signUp: vi.fn(),
20
+ signOut: vi.fn(),
21
+ resetPasswordForEmail: vi.fn(),
22
+ updateUser: vi.fn(),
23
+ refreshSession: vi.fn(),
24
+ getSession: vi.fn(),
25
+ getUser: vi.fn(),
26
+ onAuthStateChange: vi.fn(),
27
+ },
28
+ });
29
+
30
+ describe('AuthService', () => {
31
+ let mockSupabase: ReturnType<typeof createMockSupabaseClient>;
32
+ let authService: AuthService;
33
+
34
+ beforeEach(() => {
35
+ mockSupabase = createMockSupabaseClient();
36
+ authService = new AuthService(mockSupabase as any);
37
+ });
38
+
39
+ afterEach(() => {
40
+ authService.cleanup();
41
+ vi.clearAllMocks();
42
+ });
43
+
44
+ describe('Initialization', () => {
45
+ it('should initialize with default state', () => {
46
+ expect(authService.getUser()).toBeNull();
47
+ expect(authService.getSession()).toBeNull();
48
+ expect(authService.isAuthenticated()).toBe(false);
49
+ expect(authService.isLoading()).toBe(true);
50
+ expect(authService.getError()).toBeNull();
51
+ });
52
+
53
+ it('should initialize service when created', async () => {
54
+ mockSupabase.auth.getSession.mockResolvedValue({
55
+ data: { session: null },
56
+ error: null
57
+ });
58
+ mockSupabase.auth.getUser.mockResolvedValue({
59
+ data: { user: null },
60
+ error: null
61
+ });
62
+ mockSupabase.auth.onAuthStateChange.mockReturnValue({
63
+ data: { subscription: { unsubscribe: vi.fn() } }
64
+ });
65
+
66
+ await authService.initialize();
67
+
68
+ expect(mockSupabase.auth.getSession).toHaveBeenCalled();
69
+ expect(mockSupabase.auth.onAuthStateChange).toHaveBeenCalled();
70
+ });
71
+ });
72
+
73
+ describe('Sign In', () => {
74
+ it('should sign in user successfully', async () => {
75
+ const mockUser = { id: '1', email: 'test@example.com' };
76
+ const mockSession = { access_token: 'token', user: mockUser };
77
+
78
+ mockSupabase.auth.signInWithPassword.mockResolvedValue({
79
+ data: { user: mockUser, session: mockSession },
80
+ error: null
81
+ });
82
+
83
+ const result = await authService.signIn('test@example.com', 'password');
84
+
85
+ expect(result.user).toEqual(mockUser);
86
+ expect(result.session).toEqual(mockSession);
87
+ expect(result.error).toBeNull();
88
+ expect(authService.getUser()).toEqual(mockUser);
89
+ expect(authService.getSession()).toEqual(mockSession);
90
+ expect(authService.isAuthenticated()).toBe(true);
91
+ });
92
+
93
+ it('should handle sign in errors', async () => {
94
+ const mockError = new AuthError('Invalid credentials');
95
+
96
+ mockSupabase.auth.signInWithPassword.mockResolvedValue({
97
+ data: { user: null, session: null },
98
+ error: mockError
99
+ });
100
+
101
+ const result = await authService.signIn('test@example.com', 'wrongpassword');
102
+
103
+ expect(result.user).toBeNull();
104
+ expect(result.session).toBeNull();
105
+ expect(result.error).toEqual(mockError);
106
+ expect(authService.getError()).toEqual(mockError);
107
+ expect(authService.isAuthenticated()).toBe(false);
108
+ });
109
+
110
+ it('should handle missing Supabase client', async () => {
111
+ const serviceWithoutClient = new AuthService(null as any);
112
+
113
+ const result = await serviceWithoutClient.signIn('test@example.com', 'password');
114
+
115
+ expect(result.error).toBeInstanceOf(AuthError);
116
+ expect(result.error?.message).toBe('Supabase client not available');
117
+ });
118
+ });
119
+
120
+ describe('Sign Up', () => {
121
+ it('should sign up user successfully', async () => {
122
+ const mockUser = { id: '1', email: 'test@example.com' };
123
+ const mockSession = { access_token: 'token', user: mockUser };
124
+
125
+ mockSupabase.auth.signUp.mockResolvedValue({
126
+ data: { user: mockUser, session: mockSession },
127
+ error: null
128
+ });
129
+
130
+ const result = await authService.signUp('test@example.com', 'password');
131
+
132
+ expect(result.user).toEqual(mockUser);
133
+ expect(result.session).toEqual(mockSession);
134
+ expect(result.error).toBeNull();
135
+ expect(authService.getUser()).toEqual(mockUser);
136
+ expect(authService.getSession()).toEqual(mockSession);
137
+ expect(authService.isAuthenticated()).toBe(true);
138
+ });
139
+
140
+ it('should handle sign up errors', async () => {
141
+ const mockError = new AuthError('Email already registered');
142
+
143
+ mockSupabase.auth.signUp.mockResolvedValue({
144
+ data: { user: null, session: null },
145
+ error: mockError
146
+ });
147
+
148
+ const result = await authService.signUp('test@example.com', 'password');
149
+
150
+ expect(result.user).toBeNull();
151
+ expect(result.session).toBeNull();
152
+ expect(result.error).toEqual(mockError);
153
+ expect(authService.getError()).toEqual(mockError);
154
+ });
155
+ });
156
+
157
+ describe('Sign Out', () => {
158
+ it('should sign out user successfully', async () => {
159
+ // First sign in a user
160
+ const mockUser = { id: '1', email: 'test@example.com' };
161
+ const mockSession = { access_token: 'token', user: mockUser };
162
+
163
+ mockSupabase.auth.signInWithPassword.mockResolvedValue({
164
+ data: { user: mockUser, session: mockSession },
165
+ error: null
166
+ });
167
+
168
+ await authService.signIn('test@example.com', 'password');
169
+ expect(authService.isAuthenticated()).toBe(true);
170
+
171
+ // Then sign out
172
+ mockSupabase.auth.signOut.mockResolvedValue({
173
+ error: null
174
+ });
175
+
176
+ const result = await authService.signOut();
177
+
178
+ expect(result.error).toBeNull();
179
+ expect(authService.getUser()).toBeNull();
180
+ expect(authService.getSession()).toBeNull();
181
+ expect(authService.isAuthenticated()).toBe(false);
182
+ });
183
+
184
+ it('should handle sign out errors', async () => {
185
+ const mockError = new AuthError('Sign out failed');
186
+
187
+ mockSupabase.auth.signOut.mockResolvedValue({
188
+ error: mockError
189
+ });
190
+
191
+ const result = await authService.signOut();
192
+
193
+ expect(result.error).toEqual(mockError);
194
+ expect(authService.getError()).toEqual(mockError);
195
+ });
196
+ });
197
+
198
+ describe('Password Reset', () => {
199
+ it('should reset password successfully', async () => {
200
+ mockSupabase.auth.resetPasswordForEmail.mockResolvedValue({
201
+ error: null
202
+ });
203
+
204
+ const result = await authService.resetPassword('test@example.com');
205
+
206
+ expect(result.error).toBeNull();
207
+ expect(mockSupabase.auth.resetPasswordForEmail).toHaveBeenCalledWith('test@example.com');
208
+ });
209
+
210
+ it('should handle password reset errors', async () => {
211
+ const mockError = new AuthError('Email not found');
212
+
213
+ mockSupabase.auth.resetPasswordForEmail.mockResolvedValue({
214
+ error: mockError
215
+ });
216
+
217
+ const result = await authService.resetPassword('test@example.com');
218
+
219
+ expect(result.error).toEqual(mockError);
220
+ expect(authService.getError()).toEqual(mockError);
221
+ });
222
+ });
223
+
224
+ describe('Update Password', () => {
225
+ it('should update password successfully', async () => {
226
+ mockSupabase.auth.updateUser.mockResolvedValue({
227
+ error: null
228
+ });
229
+
230
+ const result = await authService.updatePassword('newpassword');
231
+
232
+ expect(result.error).toBeNull();
233
+ expect(mockSupabase.auth.updateUser).toHaveBeenCalledWith({
234
+ password: 'newpassword'
235
+ });
236
+ });
237
+
238
+ it('should handle update password errors', async () => {
239
+ const mockError = new AuthError('Password too weak');
240
+
241
+ mockSupabase.auth.updateUser.mockResolvedValue({
242
+ error: mockError
243
+ });
244
+
245
+ const result = await authService.updatePassword('weak');
246
+
247
+ expect(result.error).toEqual(mockError);
248
+ expect(authService.getError()).toEqual(mockError);
249
+ });
250
+ });
251
+
252
+ describe('Refresh Session', () => {
253
+ it('should refresh session successfully', async () => {
254
+ mockSupabase.auth.refreshSession.mockResolvedValue({
255
+ error: null
256
+ });
257
+
258
+ const result = await authService.refreshSession();
259
+
260
+ expect(result.error).toBeNull();
261
+ expect(mockSupabase.auth.refreshSession).toHaveBeenCalled();
262
+ });
263
+
264
+ it('should handle refresh session errors', async () => {
265
+ const mockError = new AuthError('Session expired');
266
+
267
+ mockSupabase.auth.refreshSession.mockResolvedValue({
268
+ error: mockError
269
+ });
270
+
271
+ const result = await authService.refreshSession();
272
+
273
+ expect(result.error).toEqual(mockError);
274
+ expect(authService.getError()).toEqual(mockError);
275
+ });
276
+ });
277
+
278
+ describe('State Management', () => {
279
+ it('should notify subscribers when state changes', async () => {
280
+ const mockUser = { id: '1', email: 'test@example.com' };
281
+ const mockSession = { access_token: 'token', user: mockUser };
282
+
283
+ mockSupabase.auth.signInWithPassword.mockResolvedValue({
284
+ data: { user: mockUser, session: mockSession },
285
+ error: null
286
+ });
287
+
288
+ const subscriber = vi.fn();
289
+ const unsubscribe = authService.subscribe(subscriber);
290
+
291
+ await authService.signIn('test@example.com', 'password');
292
+
293
+ expect(subscriber).toHaveBeenCalled();
294
+
295
+ unsubscribe();
296
+ });
297
+
298
+ it('should cleanup subscriptions on cleanup', () => {
299
+ const subscriber = vi.fn();
300
+ authService.subscribe(subscriber);
301
+
302
+ authService.cleanup();
303
+
304
+ // After cleanup, new state changes shouldn't notify subscribers
305
+ const mockUser = { id: '1', email: 'test@example.com' };
306
+ const mockSession = { access_token: 'token', user: mockUser };
307
+
308
+ mockSupabase.auth.signInWithPassword.mockResolvedValue({
309
+ data: { user: mockUser, session: mockSession },
310
+ error: null
311
+ });
312
+
313
+ authService.signIn('test@example.com', 'password');
314
+
315
+ // Subscriber should not be called after cleanup
316
+ expect(subscriber).not.toHaveBeenCalled();
317
+ });
318
+ });
319
+
320
+ describe('Error Handling', () => {
321
+ it('should handle network errors', async () => {
322
+ const networkError = new Error('Network error');
323
+
324
+ mockSupabase.auth.signInWithPassword.mockRejectedValue(networkError);
325
+
326
+ const result = await authService.signIn('test@example.com', 'password');
327
+
328
+ expect(result.error).toBeInstanceOf(AuthError);
329
+ expect(result.error?.message).toBe('Network error');
330
+ });
331
+
332
+ it('should clear errors on successful operations', async () => {
333
+ // First cause an error
334
+ const mockError = new AuthError('Invalid credentials');
335
+ mockSupabase.auth.signInWithPassword.mockResolvedValue({
336
+ data: { user: null, session: null },
337
+ error: mockError
338
+ });
339
+
340
+ await authService.signIn('test@example.com', 'wrongpassword');
341
+ expect(authService.getError()).toEqual(mockError);
342
+
343
+ // Then succeed
344
+ const mockUser = { id: '1', email: 'test@example.com' };
345
+ const mockSession = { access_token: 'token', user: mockUser };
346
+
347
+ mockSupabase.auth.signInWithPassword.mockResolvedValue({
348
+ data: { user: mockUser, session: mockSession },
349
+ error: null
350
+ });
351
+
352
+ await authService.signIn('test@example.com', 'password');
353
+ expect(authService.getError()).toBeNull();
354
+ });
355
+ });
356
+ });
@@ -0,0 +1,314 @@
1
+ /**
2
+ * @file BaseService Unit Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module Services/__tests__
5
+ * @since 0.1.0
6
+ *
7
+ * Unit tests for BaseService class.
8
+ * Tests the observable pattern implementation.
9
+ */
10
+
11
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
12
+ import { BaseService } from '../base/BaseService';
13
+
14
+ // Create a concrete implementation of BaseService for testing
15
+ class TestService extends BaseService {
16
+ private _value = 0;
17
+
18
+ getValue(): number {
19
+ return this._value;
20
+ }
21
+
22
+ setValue(value: number): void {
23
+ this._value = value;
24
+ this.notify(); // Trigger notification
25
+ }
26
+
27
+ // Expose protected method for testing
28
+ testNotify(): void {
29
+ this.notify();
30
+ }
31
+
32
+ // Implement abstract methods
33
+ protected async doInitialize(): Promise<void> {
34
+ // No initialization needed for test
35
+ }
36
+
37
+ protected doCleanup(): void {
38
+ // No cleanup needed for test
39
+ }
40
+ }
41
+
42
+ describe('BaseService', () => {
43
+ let testService: TestService;
44
+
45
+ beforeEach(() => {
46
+ testService = new TestService();
47
+ });
48
+
49
+ describe('Observable Pattern', () => {
50
+ it('should subscribe to state changes', () => {
51
+ const listener = vi.fn();
52
+ const unsubscribe = testService.subscribe(listener);
53
+
54
+ testService.setValue(42);
55
+
56
+ expect(listener).toHaveBeenCalledTimes(1);
57
+
58
+ unsubscribe();
59
+ });
60
+
61
+ it('should unsubscribe from state changes', () => {
62
+ const listener = vi.fn();
63
+ const unsubscribe = testService.subscribe(listener);
64
+
65
+ testService.setValue(42);
66
+ expect(listener).toHaveBeenCalledTimes(1);
67
+
68
+ unsubscribe();
69
+ testService.setValue(100);
70
+
71
+ expect(listener).toHaveBeenCalledTimes(1); // Should not be called after unsubscribe
72
+ });
73
+
74
+ it('should handle multiple subscribers', () => {
75
+ const listener1 = vi.fn();
76
+ const listener2 = vi.fn();
77
+ const listener3 = vi.fn();
78
+
79
+ const unsubscribe1 = testService.subscribe(listener1);
80
+ const unsubscribe2 = testService.subscribe(listener2);
81
+ const unsubscribe3 = testService.subscribe(listener3);
82
+
83
+ testService.setValue(42);
84
+
85
+ expect(listener1).toHaveBeenCalledTimes(1);
86
+ expect(listener2).toHaveBeenCalledTimes(1);
87
+ expect(listener3).toHaveBeenCalledTimes(1);
88
+
89
+ unsubscribe1();
90
+ unsubscribe2();
91
+ unsubscribe3();
92
+ });
93
+
94
+ it('should handle partial unsubscription', () => {
95
+ const listener1 = vi.fn();
96
+ const listener2 = vi.fn();
97
+ const listener3 = vi.fn();
98
+
99
+ const unsubscribe1 = testService.subscribe(listener1);
100
+ const unsubscribe2 = testService.subscribe(listener2);
101
+ const unsubscribe3 = testService.subscribe(listener3);
102
+
103
+ testService.setValue(42);
104
+
105
+ expect(listener1).toHaveBeenCalledTimes(1);
106
+ expect(listener2).toHaveBeenCalledTimes(1);
107
+ expect(listener3).toHaveBeenCalledTimes(1);
108
+
109
+ unsubscribe2(); // Unsubscribe only listener2
110
+
111
+ testService.setValue(100);
112
+
113
+ expect(listener1).toHaveBeenCalledTimes(2);
114
+ expect(listener2).toHaveBeenCalledTimes(1); // Should not be called
115
+ expect(listener3).toHaveBeenCalledTimes(2);
116
+
117
+ unsubscribe1();
118
+ unsubscribe3();
119
+ });
120
+
121
+ it('should handle duplicate subscriptions', () => {
122
+ const listener = vi.fn();
123
+
124
+ const unsubscribe1 = testService.subscribe(listener);
125
+ const unsubscribe2 = testService.subscribe(listener); // Same listener
126
+
127
+ testService.setValue(42);
128
+
129
+ expect(listener).toHaveBeenCalledTimes(2); // Called twice for duplicate subscription
130
+
131
+ unsubscribe1();
132
+ unsubscribe2();
133
+ });
134
+
135
+ it('should handle unsubscribe called multiple times', () => {
136
+ const listener = vi.fn();
137
+ const unsubscribe = testService.subscribe(listener);
138
+
139
+ testService.setValue(42);
140
+ expect(listener).toHaveBeenCalledTimes(1);
141
+
142
+ unsubscribe();
143
+ unsubscribe(); // Call unsubscribe again
144
+
145
+ testService.setValue(100);
146
+ expect(listener).toHaveBeenCalledTimes(1); // Should not be called
147
+ });
148
+ });
149
+
150
+ describe('Cleanup', () => {
151
+ it('should cleanup all subscriptions', () => {
152
+ const listener1 = vi.fn();
153
+ const listener2 = vi.fn();
154
+ const listener3 = vi.fn();
155
+
156
+ testService.subscribe(listener1);
157
+ testService.subscribe(listener2);
158
+ testService.subscribe(listener3);
159
+
160
+ testService.setValue(42);
161
+
162
+ expect(listener1).toHaveBeenCalledTimes(1);
163
+ expect(listener2).toHaveBeenCalledTimes(1);
164
+ expect(listener3).toHaveBeenCalledTimes(1);
165
+
166
+ testService.cleanup();
167
+
168
+ testService.setValue(100);
169
+
170
+ expect(listener1).toHaveBeenCalledTimes(1); // Should not be called after cleanup
171
+ expect(listener2).toHaveBeenCalledTimes(1);
172
+ expect(listener3).toHaveBeenCalledTimes(1);
173
+ });
174
+
175
+ it('should handle cleanup when no subscriptions exist', () => {
176
+ // Should not throw error
177
+ expect(() => testService.cleanup()).not.toThrow();
178
+ });
179
+
180
+ it('should handle multiple cleanup calls', () => {
181
+ const listener = vi.fn();
182
+ testService.subscribe(listener);
183
+
184
+ testService.cleanup();
185
+ testService.cleanup(); // Second cleanup call
186
+
187
+ // Should not throw error
188
+ expect(() => testService.cleanup()).not.toThrow();
189
+ });
190
+ });
191
+
192
+ describe('Notification', () => {
193
+ it('should notify all subscribers', () => {
194
+ const listener1 = vi.fn();
195
+ const listener2 = vi.fn();
196
+ const listener3 = vi.fn();
197
+
198
+ testService.subscribe(listener1);
199
+ testService.subscribe(listener2);
200
+ testService.subscribe(listener3);
201
+
202
+ testService.testNotify();
203
+
204
+ expect(listener1).toHaveBeenCalledTimes(1);
205
+ expect(listener2).toHaveBeenCalledTimes(1);
206
+ expect(listener3).toHaveBeenCalledTimes(1);
207
+ });
208
+
209
+ it('should handle notification when no subscribers exist', () => {
210
+ // Should not throw error
211
+ expect(() => testService.testNotify()).not.toThrow();
212
+ });
213
+
214
+ it('should handle subscriber errors gracefully', () => {
215
+ const errorListener = vi.fn(() => {
216
+ throw new Error('Subscriber error');
217
+ });
218
+ const normalListener = vi.fn();
219
+
220
+ testService.subscribe(errorListener);
221
+ testService.subscribe(normalListener);
222
+
223
+ // Should not throw error, and normal listener should still be called
224
+ expect(() => testService.testNotify()).not.toThrow();
225
+ expect(normalListener).toHaveBeenCalledTimes(1);
226
+ });
227
+
228
+ it('should handle multiple errors in subscribers', () => {
229
+ const errorListener1 = vi.fn(() => {
230
+ throw new Error('Subscriber error 1');
231
+ });
232
+ const errorListener2 = vi.fn(() => {
233
+ throw new Error('Subscriber error 2');
234
+ });
235
+ const normalListener = vi.fn();
236
+
237
+ testService.subscribe(errorListener1);
238
+ testService.subscribe(errorListener2);
239
+ testService.subscribe(normalListener);
240
+
241
+ // Should not throw error, and normal listener should still be called
242
+ expect(() => testService.testNotify()).not.toThrow();
243
+ expect(normalListener).toHaveBeenCalledTimes(1);
244
+ });
245
+ });
246
+
247
+ describe('Memory Management', () => {
248
+ it('should not leak memory with many subscriptions', () => {
249
+ const listeners = Array.from({ length: 1000 }, () => vi.fn());
250
+ const unsubscribes = listeners.map(listener => testService.subscribe(listener));
251
+
252
+ testService.setValue(42);
253
+
254
+ // All listeners should be called
255
+ listeners.forEach(listener => {
256
+ expect(listener).toHaveBeenCalledTimes(1);
257
+ });
258
+
259
+ // Unsubscribe all
260
+ unsubscribes.forEach(unsubscribe => unsubscribe());
261
+
262
+ testService.setValue(100);
263
+
264
+ // No listeners should be called
265
+ listeners.forEach(listener => {
266
+ expect(listener).toHaveBeenCalledTimes(1);
267
+ });
268
+ });
269
+
270
+ it('should handle rapid subscription/unsubscription', () => {
271
+ const listener = vi.fn();
272
+
273
+ for (let i = 0; i < 100; i++) {
274
+ const unsubscribe = testService.subscribe(listener);
275
+ testService.setValue(i);
276
+ unsubscribe();
277
+ }
278
+
279
+ expect(listener).toHaveBeenCalledTimes(100);
280
+ });
281
+ });
282
+
283
+ describe('Edge Cases', () => {
284
+ it('should handle null listener', () => {
285
+ // Should not throw error
286
+ expect(() => testService.subscribe(null as any)).not.toThrow();
287
+ });
288
+
289
+ it('should handle undefined listener', () => {
290
+ // Should not throw error
291
+ expect(() => testService.subscribe(undefined as any)).not.toThrow();
292
+ });
293
+
294
+ it('should handle function that returns a value', () => {
295
+ const listener = vi.fn(() => 'return value');
296
+ testService.subscribe(listener);
297
+
298
+ testService.setValue(42);
299
+
300
+ expect(listener).toHaveBeenCalledTimes(1);
301
+ });
302
+
303
+ it('should handle async listener', async () => {
304
+ const listener = vi.fn(async () => {
305
+ await new Promise(resolve => setTimeout(resolve, 10));
306
+ });
307
+
308
+ testService.subscribe(listener);
309
+ testService.setValue(42);
310
+
311
+ expect(listener).toHaveBeenCalledTimes(1);
312
+ });
313
+ });
314
+ });