@jmruthers/pace-core 0.5.73 → 0.5.75

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 (283) hide show
  1. package/dist/{DataTable-INW5YIFV.js → DataTable-HWZQGASI.js} +8 -8
  2. package/dist/{PublicLoadingSpinner-DLpF5bbs.d.ts → PublicLoadingSpinner-BKNBT6b6.d.ts} +2 -2
  3. package/dist/RBACService-C4udt_Zp.d.ts +528 -0
  4. package/dist/{UnifiedAuthProvider-6SYT5WFN.js → UnifiedAuthProvider-3NKDOSOK.js} +6 -4
  5. package/dist/UnifiedAuthProvider-Bj6YCf7c.d.ts +113 -0
  6. package/dist/{chunk-2PRPDH66.js → chunk-2CHATWBF.js} +5 -7
  7. package/dist/chunk-2CHATWBF.js.map +1 -0
  8. package/dist/{chunk-43C63KLH.js → chunk-2DFZ432F.js} +496 -30
  9. package/dist/chunk-2DFZ432F.js.map +1 -0
  10. package/dist/{chunk-M4UMXYNK.js → chunk-33PHABLB.js} +36 -3
  11. package/dist/chunk-33PHABLB.js.map +1 -0
  12. package/dist/chunk-5F3NDPJV.js +232 -0
  13. package/dist/chunk-5F3NDPJV.js.map +1 -0
  14. package/dist/chunk-A4FUBC7B.js +17 -0
  15. package/dist/chunk-A4FUBC7B.js.map +1 -0
  16. package/dist/{chunk-SMJZMKYN.js → chunk-A6HBIY5P.js} +2 -11
  17. package/dist/{chunk-SMJZMKYN.js.map → chunk-A6HBIY5P.js.map} +1 -1
  18. package/dist/{chunk-GBC5PC3N.js → chunk-CY3AHGO4.js} +6256 -1937
  19. package/dist/chunk-CY3AHGO4.js.map +1 -0
  20. package/dist/{chunk-BYG6OSTC.js → chunk-DAXLNIDY.js} +48 -50
  21. package/dist/chunk-DAXLNIDY.js.map +1 -0
  22. package/dist/{chunk-VKOCWWVY.js → chunk-L3RV2ALE.js} +1 -6
  23. package/dist/{chunk-VKOCWWVY.js.map → chunk-L3RV2ALE.js.map} +1 -1
  24. package/dist/chunk-LW7MMEAQ.js +59 -0
  25. package/dist/chunk-LW7MMEAQ.js.map +1 -0
  26. package/dist/{chunk-LANO5IFV.js → chunk-NTNILOBC.js} +7 -9
  27. package/dist/chunk-NTNILOBC.js.map +1 -0
  28. package/dist/chunk-PYUXFQJ3.js +11 -0
  29. package/dist/chunk-PYUXFQJ3.js.map +1 -0
  30. package/dist/chunk-URUTVZ7N.js +27 -0
  31. package/dist/chunk-URUTVZ7N.js.map +1 -0
  32. package/dist/chunk-WN6XJWOS.js +2468 -0
  33. package/dist/chunk-WN6XJWOS.js.map +1 -0
  34. package/dist/{chunk-3SP4P7NS.js → chunk-XLZ7U46Z.js} +59 -1
  35. package/dist/chunk-XLZ7U46Z.js.map +1 -0
  36. package/dist/{chunk-UC2BWIK7.js → chunk-ZTT2AXMX.js} +9 -14
  37. package/dist/chunk-ZTT2AXMX.js.map +1 -0
  38. package/dist/components.d.ts +4 -5
  39. package/dist/components.js +32 -39
  40. package/dist/components.js.map +1 -1
  41. package/dist/hooks.d.ts +3 -3
  42. package/dist/hooks.js +9 -8
  43. package/dist/hooks.js.map +1 -1
  44. package/dist/index.d.ts +156 -10
  45. package/dist/index.js +188 -93
  46. package/dist/index.js.map +1 -1
  47. package/dist/{organisation-t-vvQC3g.d.ts → organisation-BtshODVF.d.ts} +4 -3
  48. package/dist/providers.d.ts +27 -38
  49. package/dist/providers.js +33 -23
  50. package/dist/rbac/index.d.ts +61 -5
  51. package/dist/rbac/index.js +13 -14
  52. package/dist/styles/index.js +2 -2
  53. package/dist/theming/runtime.js +1 -3
  54. package/dist/types.d.ts +3 -3
  55. package/dist/types.js +1 -1
  56. package/dist/types.js.map +1 -1
  57. package/dist/{unified-CMPjE_fv.d.ts → unified-CM7T0aTK.d.ts} +1 -1
  58. package/dist/useInactivityTracker-MRUU55XI.js +10 -0
  59. package/dist/useInactivityTracker-MRUU55XI.js.map +1 -0
  60. package/dist/{usePublicRouteParams-Ua1Vz-HG.d.ts → usePublicRouteParams-B-CumWRc.d.ts} +3 -3
  61. package/dist/utils.js +7 -9
  62. package/dist/utils.js.map +1 -1
  63. package/dist/validation.d.ts +1 -1
  64. package/docs/api/classes/ColumnFactory.md +1 -1
  65. package/docs/api/classes/ErrorBoundary.md +1 -1
  66. package/docs/api/classes/InvalidScopeError.md +1 -1
  67. package/docs/api/classes/MissingUserContextError.md +1 -1
  68. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  69. package/docs/api/classes/PermissionDeniedError.md +1 -1
  70. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  71. package/docs/api/classes/RBACAuditManager.md +1 -1
  72. package/docs/api/classes/RBACCache.md +1 -1
  73. package/docs/api/classes/RBACEngine.md +1 -1
  74. package/docs/api/classes/RBACError.md +1 -1
  75. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  76. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  77. package/docs/api/classes/StorageUtils.md +1 -1
  78. package/docs/api/enums/FileCategory.md +1 -1
  79. package/docs/api/interfaces/AggregateConfig.md +1 -1
  80. package/docs/api/interfaces/ButtonProps.md +3 -3
  81. package/docs/api/interfaces/CardProps.md +2 -2
  82. package/docs/api/interfaces/ColorPalette.md +1 -1
  83. package/docs/api/interfaces/ColorShade.md +1 -1
  84. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  85. package/docs/api/interfaces/DataTableAction.md +1 -1
  86. package/docs/api/interfaces/DataTableColumn.md +1 -1
  87. package/docs/api/interfaces/DataTableProps.md +1 -1
  88. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  89. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  90. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  91. package/docs/api/interfaces/EventLogoProps.md +2 -2
  92. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  93. package/docs/api/interfaces/FileMetadata.md +1 -1
  94. package/docs/api/interfaces/FileReference.md +1 -1
  95. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  96. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  97. package/docs/api/interfaces/FileUploadProps.md +1 -1
  98. package/docs/api/interfaces/FooterProps.md +1 -1
  99. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  100. package/docs/api/interfaces/InputProps.md +2 -2
  101. package/docs/api/interfaces/LabelProps.md +1 -1
  102. package/docs/api/interfaces/LoginFormProps.md +1 -1
  103. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  104. package/docs/api/interfaces/NavigationContextType.md +1 -1
  105. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  106. package/docs/api/interfaces/NavigationItem.md +1 -1
  107. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  108. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  109. package/docs/api/interfaces/Organisation.md +1 -1
  110. package/docs/api/interfaces/OrganisationContextType.md +28 -17
  111. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  112. package/docs/api/interfaces/OrganisationProviderProps.md +2 -2
  113. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  114. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  115. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  116. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  117. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  118. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  119. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  120. package/docs/api/interfaces/PaletteData.md +1 -1
  121. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  122. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  123. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  124. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +2 -2
  125. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  126. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  127. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  128. package/docs/api/interfaces/RBACConfig.md +1 -1
  129. package/docs/api/interfaces/RBACContextType.md +5 -11
  130. package/docs/api/interfaces/RBACLogger.md +1 -1
  131. package/docs/api/interfaces/RBACProviderProps.md +1 -1
  132. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  133. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  134. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  135. package/docs/api/interfaces/RouteConfig.md +1 -1
  136. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  137. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  138. package/docs/api/interfaces/StorageConfig.md +1 -1
  139. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  140. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  141. package/docs/api/interfaces/StorageListOptions.md +1 -1
  142. package/docs/api/interfaces/StorageListResult.md +1 -1
  143. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  144. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  145. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  146. package/docs/api/interfaces/StyleImport.md +1 -1
  147. package/docs/api/interfaces/SwitchProps.md +1 -1
  148. package/docs/api/interfaces/ToastActionElement.md +1 -1
  149. package/docs/api/interfaces/ToastProps.md +1 -1
  150. package/docs/api/interfaces/UnifiedAuthContextType.md +524 -440
  151. package/docs/api/interfaces/UnifiedAuthProviderProps.md +14 -14
  152. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  153. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  154. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  155. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  156. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  157. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  158. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  159. package/docs/api/interfaces/UserEventAccess.md +11 -11
  160. package/docs/api/interfaces/UserMenuProps.md +1 -1
  161. package/docs/api/interfaces/UserProfile.md +1 -1
  162. package/docs/api/modules.md +179 -52
  163. package/docs/architecture/services.md +30 -32
  164. package/docs/breaking-changes.md +2 -5
  165. package/docs/implementation-guides/data-tables.md +82 -1
  166. package/docs/migration/service-architecture.md +121 -260
  167. package/docs/rbac/README-rbac-rls-integration.md +48 -38
  168. package/{src/rbac/examples → examples/RBAC}/CompleteRBACExample.tsx +3 -2
  169. package/{src/rbac/examples → examples/RBAC}/EventBasedApp.tsx +5 -4
  170. package/{src/components/examples → examples/RBAC}/PermissionExample.tsx +7 -6
  171. package/examples/RBAC/__tests__/PermissionExample.test.tsx +150 -0
  172. package/examples/RBAC/index.ts +13 -0
  173. package/examples/README.md +37 -0
  174. package/examples/index.ts +22 -0
  175. package/{src/examples → examples/public-pages}/CorrectPublicPageImplementation.tsx +1 -1
  176. package/{src/examples → examples/public-pages}/PublicEventPage.tsx +1 -1
  177. package/{src/examples → examples/public-pages}/PublicPageApp.tsx +1 -1
  178. package/{src/examples → examples/public-pages}/PublicPageUsageExample.tsx +1 -1
  179. package/examples/public-pages/__tests__/PublicPageUsageExample.test.tsx +159 -0
  180. package/examples/public-pages/index.ts +14 -0
  181. package/package.json +22 -18
  182. package/src/__tests__/TEST_GUIDE_CURSOR.md +650 -9
  183. package/src/__tests__/helpers/README.md +255 -0
  184. package/src/__tests__/helpers/index.ts +62 -0
  185. package/src/__tests__/helpers/supabaseMock.ts +27 -3
  186. package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -8
  187. package/src/components/DataTable/components/DataTableCore.tsx +37 -3
  188. package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +55 -0
  189. package/src/components/DataTable/core/ColumnManager.ts +10 -0
  190. package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +254 -0
  191. package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +193 -0
  192. package/src/components/DataTable/examples/__tests__/HierarchicalExample.test.tsx +45 -0
  193. package/src/components/DataTable/examples/__tests__/PerformanceExample.test.tsx +117 -0
  194. package/src/components/Dialog/Dialog.tsx +2 -2
  195. package/src/components/Dialog/examples/__tests__/HtmlDialogExample.test.tsx +71 -0
  196. package/src/components/Dialog/examples/__tests__/SimpleHtmlTest.test.tsx +122 -0
  197. package/src/components/EventSelector/EventSelector.tsx +1 -1
  198. package/src/components/Header/Header.test.tsx +35 -1
  199. package/src/components/Header/Header.tsx +3 -1
  200. package/src/components/OrganisationSelector/OrganisationSelector.tsx +3 -3
  201. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +24 -4
  202. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +3 -2
  203. package/src/components/Toast/Toast.test.tsx +1 -1
  204. package/src/components/Toast/Toast.tsx +1 -1
  205. package/src/hooks/__tests__/useFocusManagement.unit.test.ts +220 -0
  206. package/src/hooks/__tests__/useIsMobile.unit.test.ts +117 -0
  207. package/src/hooks/__tests__/useKeyboardShortcuts.unit.test.ts +295 -0
  208. package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +29 -19
  209. package/src/hooks/__tests__/useRBAC.unit.test.ts +7 -3
  210. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +115 -19
  211. package/src/hooks/useEventTheme.test.ts +350 -0
  212. package/src/hooks/useEventTheme.ts +1 -1
  213. package/src/hooks/useEvents.ts +61 -0
  214. package/src/hooks/useOrganisationSecurity.test.ts +4 -4
  215. package/src/hooks/useOrganisationSecurity.ts +2 -2
  216. package/src/hooks/useOrganisations.ts +64 -0
  217. package/src/hooks/useSecureDataAccess.test.ts +9 -5
  218. package/src/hooks/useSecureDataAccess.ts +2 -2
  219. package/src/index.ts +18 -3
  220. package/src/providers/AuthProvider.tsx +8 -292
  221. package/src/providers/EventProvider.tsx +15 -425
  222. package/src/providers/InactivityProvider.tsx +8 -231
  223. package/src/providers/OrganisationProvider.test.simple.tsx +3 -2
  224. package/src/providers/OrganisationProvider.tsx +11 -890
  225. package/src/providers/UnifiedAuthProvider.tsx +8 -320
  226. package/src/providers/__tests__/AuthProvider.test.tsx +18 -17
  227. package/src/providers/__tests__/EventProvider.test.tsx +253 -2
  228. package/src/providers/__tests__/InactivityProvider.test-helper.tsx +65 -0
  229. package/src/providers/__tests__/InactivityProvider.test.tsx +46 -114
  230. package/src/providers/__tests__/OrganisationProvider.test.tsx +313 -3
  231. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +383 -2
  232. package/src/providers/index.ts +8 -7
  233. package/src/providers/services/EventServiceProvider.tsx +3 -0
  234. package/src/providers/services/UnifiedAuthProvider.tsx +3 -0
  235. package/src/rbac/hooks/usePermissions.test.ts +296 -0
  236. package/src/rbac/hooks/useRBAC.test.ts +9 -5
  237. package/src/rbac/hooks/useRBAC.ts +3 -3
  238. package/src/rbac/providers/__tests__/RBACProvider.integration.test.tsx +688 -0
  239. package/src/rbac/providers/__tests__/RBACProvider.test.tsx +507 -0
  240. package/src/services/AuthService.ts +19 -4
  241. package/src/services/__tests__/AuthService.test.ts +288 -0
  242. package/src/styles/core.css +2 -0
  243. package/src/types/__tests__/guards.test.ts +246 -0
  244. package/src/types/guards.ts +1 -0
  245. package/src/types/organisation.ts +3 -2
  246. package/src/validation/__tests__/sanitization.unit.test.ts +250 -0
  247. package/src/validation/__tests__/schemaUtils.unit.test.ts +451 -0
  248. package/src/validation/__tests__/user.unit.test.ts +440 -0
  249. package/dist/RBACProvider-BO4ilsQB.d.ts +0 -63
  250. package/dist/UnifiedAuthProvider-D02AMXgO.d.ts +0 -103
  251. package/dist/chunk-2PRPDH66.js.map +0 -1
  252. package/dist/chunk-3SP4P7NS.js.map +0 -1
  253. package/dist/chunk-43C63KLH.js.map +0 -1
  254. package/dist/chunk-5A4RL4BC.js +0 -5670
  255. package/dist/chunk-5A4RL4BC.js.map +0 -1
  256. package/dist/chunk-BYG6OSTC.js.map +0 -1
  257. package/dist/chunk-CDDYJCYU.js +0 -79
  258. package/dist/chunk-CDDYJCYU.js.map +0 -1
  259. package/dist/chunk-F24P24TZ.js +0 -17
  260. package/dist/chunk-F24P24TZ.js.map +0 -1
  261. package/dist/chunk-GBC5PC3N.js.map +0 -1
  262. package/dist/chunk-LANO5IFV.js.map +0 -1
  263. package/dist/chunk-M4UMXYNK.js.map +0 -1
  264. package/dist/chunk-RJNE764D.js +0 -953
  265. package/dist/chunk-RJNE764D.js.map +0 -1
  266. package/dist/chunk-UC2BWIK7.js.map +0 -1
  267. package/dist/rbac/cli/policy-manager.js +0 -278
  268. package/dist/rbac/cli/policy-manager.js.map +0 -1
  269. package/docs/api/interfaces/EventContextType.md +0 -96
  270. package/docs/api/interfaces/EventProviderProps.md +0 -19
  271. package/src/providers/OrganisationProvider.test.tsx +0 -164
  272. package/src/providers/UnifiedAuthProvider.test.tsx +0 -124
  273. package/src/providers/__tests__/AuthProvider.test.tsx.backup +0 -771
  274. package/src/providers/__tests__/EventProvider.test.tsx.backup +0 -824
  275. package/src/providers/__tests__/OrganisationProvider.test.tsx.backup +0 -820
  276. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx.backup +0 -911
  277. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx.backup2 +0 -166
  278. package/src/rbac/cli/__tests__/policy-manager.test.ts +0 -339
  279. package/src/rbac/cli/policy-manager.ts +0 -443
  280. package/dist/{DataTable-INW5YIFV.js.map → DataTable-HWZQGASI.js.map} +0 -0
  281. package/dist/{UnifiedAuthProvider-6SYT5WFN.js.map → UnifiedAuthProvider-3NKDOSOK.js.map} +0 -0
  282. package/dist/{validation-PM_iOaTI.d.ts → validation-D8VcbTzC.d.ts} +2 -2
  283. /package/src/utils/{appNameResolver.test.ts.backup → appNameResolver.test 2.ts} +0 -0
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @file useIsMobile Hook Unit Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module Hooks/__tests__/useIsMobile
5
+ * @since 0.4.0
6
+ *
7
+ * Comprehensive tests for the useIsMobile hook covering all critical functionality.
8
+ */
9
+
10
+ import { renderHook } from '@testing-library/react';
11
+ import { vi, describe, it, expect, beforeEach } from 'vitest';
12
+ import { useIsMobile } from '../useIsMobile';
13
+
14
+ describe('useIsMobile', () => {
15
+ beforeEach(() => {
16
+ vi.clearAllMocks();
17
+ // Reset window properties for each test
18
+ Object.defineProperty(window, 'innerWidth', {
19
+ writable: true,
20
+ configurable: true,
21
+ value: 1024
22
+ });
23
+ });
24
+
25
+ describe('Initial detection', () => {
26
+ it('detects mobile viewport initially', () => {
27
+ window.innerWidth = 500;
28
+
29
+ const mql = {
30
+ matches: true,
31
+ media: '(max-width: 767px)',
32
+ onchange: null,
33
+ addEventListener: vi.fn(),
34
+ removeEventListener: vi.fn(),
35
+ addListener: vi.fn(),
36
+ removeListener: vi.fn(),
37
+ dispatchEvent: vi.fn()
38
+ };
39
+
40
+ window.matchMedia = vi.fn(() => mql) as any;
41
+
42
+ const { result } = renderHook(() => useIsMobile());
43
+
44
+ expect(result.current).toBe(true);
45
+ });
46
+
47
+ it('detects desktop viewport initially', () => {
48
+ window.innerWidth = 1200;
49
+
50
+ const mql = {
51
+ matches: false,
52
+ media: '(max-width: 767px)',
53
+ onchange: null,
54
+ addEventListener: vi.fn(),
55
+ removeEventListener: vi.fn(),
56
+ addListener: vi.fn(),
57
+ removeListener: vi.fn(),
58
+ dispatchEvent: vi.fn()
59
+ };
60
+
61
+ window.matchMedia = vi.fn(() => mql) as any;
62
+
63
+ const { result } = renderHook(() => useIsMobile());
64
+
65
+ expect(result.current).toBe(false);
66
+ });
67
+ });
68
+
69
+ describe('Event listener management', () => {
70
+ it('registers event listener for media query changes', () => {
71
+ const addEventListener = vi.fn();
72
+ const removeEventListener = vi.fn();
73
+
74
+ const mql = {
75
+ matches: false,
76
+ media: '(max-width: 767px)',
77
+ onchange: null,
78
+ addEventListener,
79
+ removeEventListener,
80
+ addListener: vi.fn(),
81
+ removeListener: vi.fn(),
82
+ dispatchEvent: vi.fn()
83
+ };
84
+
85
+ window.matchMedia = vi.fn(() => mql) as any;
86
+
87
+ const { unmount } = renderHook(() => useIsMobile());
88
+
89
+ expect(addEventListener).toHaveBeenCalledWith('change', expect.any(Function));
90
+
91
+ unmount();
92
+
93
+ expect(removeEventListener).toHaveBeenCalledWith('change', expect.any(Function));
94
+ });
95
+ });
96
+
97
+ describe('Hook returns boolean', () => {
98
+ it('returns a boolean value', () => {
99
+ const mql = {
100
+ matches: false,
101
+ media: '(max-width: 767px)',
102
+ onchange: null,
103
+ addEventListener: vi.fn(),
104
+ removeEventListener: vi.fn(),
105
+ addListener: vi.fn(),
106
+ removeListener: vi.fn(),
107
+ dispatchEvent: vi.fn()
108
+ };
109
+
110
+ window.matchMedia = vi.fn(() => mql) as any;
111
+
112
+ const { result } = renderHook(() => useIsMobile());
113
+
114
+ expect(typeof result.current).toBe('boolean');
115
+ });
116
+ });
117
+ });
@@ -0,0 +1,295 @@
1
+ /**
2
+ * @file useKeyboardShortcuts Hook Unit Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module Hooks/__tests__/useKeyboardShortcuts
5
+ * @since 0.4.0
6
+ *
7
+ * Comprehensive tests for the useKeyboardShortcuts hook covering all critical functionality.
8
+ */
9
+
10
+ import { renderHook } from '@testing-library/react';
11
+ import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
12
+ import { useKeyboardShortcuts } from '../useKeyboardShortcuts';
13
+ import { fireEvent } from '@testing-library/react';
14
+
15
+ describe('useKeyboardShortcuts', () => {
16
+ let mockElement: HTMLElement;
17
+ let addEventListenerSpy: ReturnType<typeof vi.spyOn>;
18
+ let removeEventListenerSpy: ReturnType<typeof vi.spyOn>;
19
+
20
+ beforeEach(() => {
21
+ mockElement = document.createElement('div');
22
+ addEventListenerSpy = vi.spyOn(document, 'addEventListener');
23
+ removeEventListenerSpy = vi.spyOn(document, 'removeEventListener');
24
+ });
25
+
26
+ afterEach(() => {
27
+ addEventListenerSpy.mockRestore();
28
+ removeEventListenerSpy.mockRestore();
29
+ vi.clearAllMocks();
30
+ });
31
+
32
+ describe('Event listener registration', () => {
33
+ it('registers keydown event listener on document', () => {
34
+ const shortcuts = [
35
+ {
36
+ key: 'Escape',
37
+ handler: vi.fn()
38
+ }
39
+ ];
40
+
41
+ renderHook(() => useKeyboardShortcuts(shortcuts));
42
+
43
+ expect(addEventListenerSpy).toHaveBeenCalledWith('keydown', expect.any(Function));
44
+ });
45
+
46
+ it('unregisters event listener on unmount', () => {
47
+ const shortcuts = [
48
+ {
49
+ key: 'Escape',
50
+ handler: vi.fn()
51
+ }
52
+ ];
53
+
54
+ const { unmount } = renderHook(() => useKeyboardShortcuts(shortcuts));
55
+ unmount();
56
+
57
+ expect(removeEventListenerSpy).toHaveBeenCalled();
58
+ });
59
+
60
+ it('registers listener on custom element', () => {
61
+ const mockAddEventListener = vi.fn();
62
+ const mockRemoveEventListener = vi.fn();
63
+
64
+ const customElement = {
65
+ addEventListener: mockAddEventListener,
66
+ removeEventListener: mockRemoveEventListener
67
+ } as any;
68
+
69
+ const shortcuts = [
70
+ {
71
+ key: 'Escape',
72
+ handler: vi.fn()
73
+ }
74
+ ];
75
+
76
+ renderHook(() => useKeyboardShortcuts(shortcuts, { element: customElement }));
77
+
78
+ expect(mockAddEventListener).toHaveBeenCalledWith('keydown', expect.any(Function));
79
+ });
80
+ });
81
+
82
+ describe('Keyboard shortcut handling', () => {
83
+ it('triggers handler when Escape key is pressed', () => {
84
+ const handler = vi.fn();
85
+ const shortcuts = [
86
+ {
87
+ key: 'Escape',
88
+ handler
89
+ }
90
+ ];
91
+
92
+ renderHook(() => useKeyboardShortcuts(shortcuts));
93
+
94
+ const event = new KeyboardEvent('keydown', { key: 'Escape' });
95
+ document.dispatchEvent(event);
96
+
97
+ expect(handler).toHaveBeenCalledTimes(1);
98
+ expect(handler).toHaveBeenCalledWith(expect.objectContaining({ key: 'Escape' }));
99
+ });
100
+
101
+ it('does not trigger handler for wrong key', () => {
102
+ const handler = vi.fn();
103
+ const shortcuts = [
104
+ {
105
+ key: 'Escape',
106
+ handler
107
+ }
108
+ ];
109
+
110
+ renderHook(() => useKeyboardShortcuts(shortcuts));
111
+
112
+ const event = new KeyboardEvent('keydown', { key: 'Enter' });
113
+ document.dispatchEvent(event);
114
+
115
+ expect(handler).not.toHaveBeenCalled();
116
+ });
117
+
118
+ it('triggers handler for Enter key', () => {
119
+ const handler = vi.fn();
120
+ const shortcuts = [
121
+ {
122
+ key: 'Enter',
123
+ handler
124
+ }
125
+ ];
126
+
127
+ renderHook(() => useKeyboardShortcuts(shortcuts));
128
+
129
+ const event = new KeyboardEvent('keydown', { key: 'Enter' });
130
+ document.dispatchEvent(event);
131
+
132
+ expect(handler).toHaveBeenCalledTimes(1);
133
+ });
134
+
135
+ it('handles multiple shortcuts and triggers the first matching one', () => {
136
+ const handler1 = vi.fn();
137
+ const handler2 = vi.fn();
138
+
139
+ const shortcuts = [
140
+ {
141
+ key: 'Escape',
142
+ handler: handler1
143
+ },
144
+ {
145
+ key: 'Escape',
146
+ handler: handler2
147
+ }
148
+ ];
149
+
150
+ renderHook(() => useKeyboardShortcuts(shortcuts));
151
+
152
+ const event = new KeyboardEvent('keydown', { key: 'Escape' });
153
+ document.dispatchEvent(event);
154
+
155
+ expect(handler1).toHaveBeenCalledTimes(1);
156
+ expect(handler2).not.toHaveBeenCalled();
157
+ });
158
+ });
159
+
160
+ describe('Modifier key combinations', () => {
161
+ it('handles Ctrl+key combination', () => {
162
+ const handler = vi.fn();
163
+ const shortcuts = [
164
+ {
165
+ key: 'ctrl+s',
166
+ handler
167
+ }
168
+ ];
169
+
170
+ renderHook(() => useKeyboardShortcuts(shortcuts));
171
+
172
+ const event = new KeyboardEvent('keydown', {
173
+ key: 's',
174
+ ctrlKey: true,
175
+ bubbles: true
176
+ });
177
+ document.dispatchEvent(event);
178
+
179
+ expect(handler).toHaveBeenCalledTimes(1);
180
+ });
181
+
182
+ it('does not trigger when modifiers do not match', () => {
183
+ const handler = vi.fn();
184
+ const shortcuts = [
185
+ {
186
+ key: 'ctrl+s',
187
+ handler
188
+ }
189
+ ];
190
+
191
+ renderHook(() => useKeyboardShortcuts(shortcuts));
192
+
193
+ const event = new KeyboardEvent('keydown', {
194
+ key: 's',
195
+ ctrlKey: false,
196
+ bubbles: true
197
+ });
198
+ document.dispatchEvent(event);
199
+
200
+ expect(handler).not.toHaveBeenCalled();
201
+ });
202
+ });
203
+
204
+ describe('Options', () => {
205
+ it('does not register listeners when disabled', () => {
206
+ const shortcuts = [
207
+ {
208
+ key: 'Escape',
209
+ handler: vi.fn()
210
+ }
211
+ ];
212
+
213
+ renderHook(() => useKeyboardShortcuts(shortcuts, { enabled: false }));
214
+
215
+ const event = new KeyboardEvent('keydown', { key: 'Escape' });
216
+ document.dispatchEvent(event);
217
+
218
+ expect(addEventListenerSpy).not.toHaveBeenCalled();
219
+ });
220
+
221
+ it('skips disabled shortcuts', () => {
222
+ const handler = vi.fn();
223
+ const shortcuts = [
224
+ {
225
+ key: 'Escape',
226
+ handler,
227
+ enabled: false
228
+ }
229
+ ];
230
+
231
+ renderHook(() => useKeyboardShortcuts(shortcuts));
232
+
233
+ const event = new KeyboardEvent('keydown', { key: 'Escape' });
234
+ document.dispatchEvent(event);
235
+
236
+ expect(handler).not.toHaveBeenCalled();
237
+ });
238
+
239
+ it('calls preventDefault when option is set', () => {
240
+ const handler = vi.fn();
241
+ const shortcuts = [
242
+ {
243
+ key: 'Escape',
244
+ handler,
245
+ preventDefault: true
246
+ }
247
+ ];
248
+
249
+ renderHook(() => useKeyboardShortcuts(shortcuts));
250
+
251
+ const event = new KeyboardEvent('keydown', { key: 'Escape', cancelable: true });
252
+ document.dispatchEvent(event);
253
+
254
+ expect(handler).toHaveBeenCalled();
255
+ // Note: preventDefault() is called on the event object by the handler
256
+ });
257
+ });
258
+
259
+ describe('Case insensitivity', () => {
260
+ it('handles lowercase key correctly', () => {
261
+ const handler = vi.fn();
262
+ const shortcuts = [
263
+ {
264
+ key: 'escape',
265
+ handler
266
+ }
267
+ ];
268
+
269
+ renderHook(() => useKeyboardShortcuts(shortcuts));
270
+
271
+ const event = new KeyboardEvent('keydown', { key: 'Escape' });
272
+ document.dispatchEvent(event);
273
+
274
+ expect(handler).toHaveBeenCalledTimes(1);
275
+ });
276
+
277
+ it('handles uppercase key correctly', () => {
278
+ const handler = vi.fn();
279
+ const shortcuts = [
280
+ {
281
+ key: 'ESCAPE',
282
+ handler
283
+ }
284
+ ];
285
+
286
+ renderHook(() => useKeyboardShortcuts(shortcuts));
287
+
288
+ const event = new KeyboardEvent('keydown', { key: 'Escape' });
289
+ document.dispatchEvent(event);
290
+
291
+ expect(handler).toHaveBeenCalledTimes(1);
292
+ });
293
+ });
294
+ });
295
+
@@ -1,11 +1,20 @@
1
1
  import { renderHook, act } from '@testing-library/react';
2
2
  import { vi, describe, it, expect, beforeEach } from 'vitest';
3
3
  import { useOrganisationSecurity } from '../useOrganisationSecurity';
4
- import { useUnifiedAuth } from '../../providers/UnifiedAuthProvider';
5
- import { useOrganisations } from '../../providers/OrganisationProvider';
4
+ import { useUnifiedAuth } from '../../providers';
5
+ import { useOrganisations } from '../../hooks/useOrganisations';
6
6
  // Mock dependencies
7
- vi.mock('../../providers/UnifiedAuthProvider');
8
- vi.mock('../../providers/OrganisationProvider');
7
+ vi.mock('../../providers', () => ({
8
+ useUnifiedAuth: vi.fn(),
9
+ }));
10
+
11
+ vi.mock('../../providers/services/UnifiedAuthProvider', () => ({
12
+ useUnifiedAuth: vi.fn(),
13
+ }));
14
+
15
+ vi.mock('../../hooks/useOrganisations', () => ({
16
+ useOrganisations: vi.fn(),
17
+ }));
9
18
 
10
19
  // Mock the RBAC API
11
20
  vi.mock('../../rbac/api', () => ({
@@ -45,16 +54,17 @@ describe('useOrganisationSecurity', () => {
45
54
  it('should detect super admin from user metadata', () => {
46
55
  const mockUser = {
47
56
  id: 'user-123',
48
- user_metadata: { globalRole: 'super_admin' }
57
+ user_metadata: { globalRole: 'super_admin' },
58
+ app_metadata: { globalRole: 'super_admin' }
49
59
  };
50
60
  vi.mocked(useUnifiedAuth).mockReturnValue({
51
61
  ...mockUseUnifiedAuth,
52
62
  user: mockUser
53
63
  } as any);
54
- // Global role now derived from user metadata directly
55
64
 
56
65
  const { result } = renderHook(() => useOrganisationSecurity());
57
-
66
+
67
+ console.log('User metadata:', result.current);
58
68
  expect(result.current.superAdminContext.isSuperAdmin).toBe(true);
59
69
  expect(result.current.superAdminContext.hasGlobalAccess).toBe(true);
60
70
  expect(result.current.superAdminContext.canManageAllOrganisations).toBe(true);
@@ -98,7 +108,8 @@ describe('useOrganisationSecurity', () => {
98
108
  it('should return true for super admin', async () => {
99
109
  const mockUser = {
100
110
  id: 'user-123',
101
- user_metadata: { globalRole: 'super_admin' }
111
+ user_metadata: { globalRole: 'super_admin' },
112
+ app_metadata: { globalRole: 'super_admin' }
102
113
  };
103
114
  const mockSession = { access_token: 'test-token' };
104
115
  const mockSupabase = { from: vi.fn() };
@@ -109,7 +120,6 @@ describe('useOrganisationSecurity', () => {
109
120
  session: mockSession,
110
121
  supabase: mockSupabase
111
122
  } as any);
112
- // Global role now derived from user metadata directly
113
123
 
114
124
  const { result } = renderHook(() => useOrganisationSecurity());
115
125
 
@@ -213,13 +223,13 @@ describe('useOrganisationSecurity', () => {
213
223
  it('should return true for super admin regardless of role', () => {
214
224
  const mockUser = {
215
225
  id: 'user-123',
216
- user_metadata: { globalRole: 'super_admin' }
226
+ user_metadata: { globalRole: 'super_admin' },
227
+ app_metadata: { globalRole: 'super_admin' }
217
228
  };
218
229
  vi.mocked(useUnifiedAuth).mockReturnValue({
219
230
  ...mockUseUnifiedAuth,
220
231
  user: mockUser
221
232
  } as any);
222
- // Global role now derived from user metadata directly
223
233
 
224
234
  const { result } = renderHook(() => useOrganisationSecurity());
225
235
 
@@ -273,13 +283,13 @@ describe('useOrganisationSecurity', () => {
273
283
  it('should return true for super admin', () => {
274
284
  const mockUser = {
275
285
  id: 'user-123',
276
- user_metadata: { globalRole: 'super_admin' }
286
+ user_metadata: { globalRole: 'super_admin' },
287
+ app_metadata: { globalRole: 'super_admin' }
277
288
  };
278
289
  vi.mocked(useUnifiedAuth).mockReturnValue({
279
290
  ...mockUseUnifiedAuth,
280
291
  user: mockUser
281
292
  } as any);
282
- // Global role now derived from user metadata directly
283
293
 
284
294
  const { result } = renderHook(() => useOrganisationSecurity());
285
295
 
@@ -331,7 +341,8 @@ describe('useOrganisationSecurity', () => {
331
341
  it('should return true for super admin', async () => {
332
342
  const mockUser = {
333
343
  id: 'user-123',
334
- user_metadata: { globalRole: 'super_admin' }
344
+ user_metadata: { globalRole: 'super_admin' },
345
+ app_metadata: { globalRole: 'super_admin' }
335
346
  };
336
347
  const mockSupabase = { rpc: vi.fn() };
337
348
 
@@ -340,7 +351,6 @@ describe('useOrganisationSecurity', () => {
340
351
  user: mockUser,
341
352
  supabase: mockSupabase
342
353
  } as any);
343
- // Global role now derived from user metadata directly
344
354
 
345
355
  const { result } = renderHook(() => useOrganisationSecurity());
346
356
 
@@ -476,7 +486,8 @@ describe('useOrganisationSecurity', () => {
476
486
  it('should return all permissions for super admin', async () => {
477
487
  const mockUser = {
478
488
  id: 'user-123',
479
- user_metadata: { globalRole: 'super_admin' }
489
+ user_metadata: { globalRole: 'super_admin' },
490
+ app_metadata: { globalRole: 'super_admin' }
480
491
  };
481
492
  const mockSupabase = { rpc: vi.fn() };
482
493
 
@@ -485,7 +496,6 @@ describe('useOrganisationSecurity', () => {
485
496
  user: mockUser,
486
497
  supabase: mockSupabase
487
498
  } as any);
488
- // Global role now derived from user metadata directly
489
499
 
490
500
  const { result } = renderHook(() => useOrganisationSecurity());
491
501
 
@@ -744,7 +754,8 @@ describe('useOrganisationSecurity', () => {
744
754
  it('should return true for super admin', async () => {
745
755
  const mockUser = {
746
756
  id: 'user-123',
747
- user_metadata: { globalRole: 'super_admin' }
757
+ user_metadata: { globalRole: 'super_admin' },
758
+ app_metadata: { globalRole: 'super_admin' }
748
759
  };
749
760
  const mockSupabase = { from: vi.fn() };
750
761
 
@@ -753,7 +764,6 @@ describe('useOrganisationSecurity', () => {
753
764
  user: mockUser,
754
765
  supabase: mockSupabase
755
766
  } as any);
756
- // Global role now derived from user metadata directly
757
767
 
758
768
  const { result } = renderHook(() => useOrganisationSecurity());
759
769
 
@@ -8,15 +8,19 @@ const mockUseUnifiedAuth = vi.fn();
8
8
  const mockUseOrganisations = vi.fn();
9
9
  const mockUseEvents = vi.fn();
10
10
 
11
- vi.mock('../../providers/UnifiedAuthProvider', () => ({
11
+ vi.mock('../../providers', () => ({
12
12
  useUnifiedAuth: () => mockUseUnifiedAuth()
13
13
  }));
14
14
 
15
- vi.mock('../../providers/OrganisationProvider', () => ({
15
+ vi.mock('../../providers/services/UnifiedAuthProvider', () => ({
16
+ useUnifiedAuth: () => mockUseUnifiedAuth()
17
+ }));
18
+
19
+ vi.mock('../../hooks/useOrganisations', () => ({
16
20
  useOrganisations: () => mockUseOrganisations()
17
21
  }));
18
22
 
19
- vi.mock('../../providers/EventProvider', () => ({
23
+ vi.mock('../../hooks/useEvents', () => ({
20
24
  useEvents: () => mockUseEvents()
21
25
  }));
22
26