@jmruthers/pace-core 0.5.117 → 0.5.119

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 (167) hide show
  1. package/dist/{DataTable-ZOAKQ3SU.js → DataTable-BQYGKVHR.js} +6 -6
  2. package/dist/{UnifiedAuthProvider-YFN7YGVN.js → UnifiedAuthProvider-UACKFATV.js} +3 -3
  3. package/dist/{chunk-XN2LYHDI.js → chunk-B4GZ2BXO.js} +27 -8
  4. package/dist/{chunk-XN2LYHDI.js.map → chunk-B4GZ2BXO.js.map} +1 -1
  5. package/dist/{chunk-KA3PSVNV.js → chunk-BHWIUEYH.js} +2 -1
  6. package/dist/chunk-BHWIUEYH.js.map +1 -0
  7. package/dist/{chunk-LFS45U62.js → chunk-CGURJ27Z.js} +2 -2
  8. package/dist/{chunk-PHDAXDHB.js → chunk-D6BOFXYR.js} +3 -3
  9. package/dist/{chunk-2LM4QQGH.js → chunk-F7COHU5B.js} +8 -8
  10. package/dist/{chunk-P3PUOL6B.js → chunk-FKFHZUGF.js} +4 -4
  11. package/dist/{chunk-UKZWNQMB.js → chunk-NP5VABFV.js} +4 -4
  12. package/dist/{chunk-O3FTRYEU.js → chunk-NZ32EONV.js} +2 -2
  13. package/dist/{chunk-ECOVPXYS.js → chunk-RIEJGKD3.js} +4 -4
  14. package/dist/{chunk-IZXS7RZK.js → chunk-TDNI6ZWL.js} +5 -5
  15. package/dist/{chunk-VN3OOE35.js → chunk-ZYJ6O5CA.js} +2 -2
  16. package/dist/components.js +8 -8
  17. package/dist/hooks.js +7 -7
  18. package/dist/index.js +11 -11
  19. package/dist/providers.js +2 -2
  20. package/dist/rbac/index.js +7 -7
  21. package/dist/utils.js +1 -1
  22. package/docs/api/classes/ColumnFactory.md +1 -1
  23. package/docs/api/classes/ErrorBoundary.md +1 -1
  24. package/docs/api/classes/InvalidScopeError.md +1 -1
  25. package/docs/api/classes/MissingUserContextError.md +1 -1
  26. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  27. package/docs/api/classes/PermissionDeniedError.md +1 -1
  28. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  29. package/docs/api/classes/RBACAuditManager.md +1 -1
  30. package/docs/api/classes/RBACCache.md +1 -1
  31. package/docs/api/classes/RBACEngine.md +1 -1
  32. package/docs/api/classes/RBACError.md +1 -1
  33. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  34. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  35. package/docs/api/classes/StorageUtils.md +1 -1
  36. package/docs/api/enums/FileCategory.md +1 -1
  37. package/docs/api/interfaces/AggregateConfig.md +1 -1
  38. package/docs/api/interfaces/ButtonProps.md +1 -1
  39. package/docs/api/interfaces/CardProps.md +1 -1
  40. package/docs/api/interfaces/ColorPalette.md +1 -1
  41. package/docs/api/interfaces/ColorShade.md +1 -1
  42. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  43. package/docs/api/interfaces/DataRecord.md +1 -1
  44. package/docs/api/interfaces/DataTableAction.md +1 -1
  45. package/docs/api/interfaces/DataTableColumn.md +1 -1
  46. package/docs/api/interfaces/DataTableProps.md +1 -1
  47. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  48. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  49. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  50. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  51. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  52. package/docs/api/interfaces/FileMetadata.md +1 -1
  53. package/docs/api/interfaces/FileReference.md +1 -1
  54. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  55. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  56. package/docs/api/interfaces/FileUploadProps.md +1 -1
  57. package/docs/api/interfaces/FooterProps.md +1 -1
  58. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  59. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  60. package/docs/api/interfaces/InputProps.md +1 -1
  61. package/docs/api/interfaces/LabelProps.md +1 -1
  62. package/docs/api/interfaces/LoginFormProps.md +1 -1
  63. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  64. package/docs/api/interfaces/NavigationContextType.md +1 -1
  65. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  66. package/docs/api/interfaces/NavigationItem.md +1 -1
  67. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  68. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  69. package/docs/api/interfaces/Organisation.md +1 -1
  70. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  71. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  72. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  73. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  74. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  75. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  76. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  77. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  78. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  79. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  80. package/docs/api/interfaces/PaletteData.md +1 -1
  81. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  82. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  83. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  84. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  85. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  86. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  87. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  88. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  89. package/docs/api/interfaces/RBACConfig.md +1 -1
  90. package/docs/api/interfaces/RBACLogger.md +1 -1
  91. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  92. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  93. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  94. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  95. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  96. package/docs/api/interfaces/RouteConfig.md +1 -1
  97. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  98. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  99. package/docs/api/interfaces/StorageConfig.md +1 -1
  100. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  101. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  102. package/docs/api/interfaces/StorageListOptions.md +1 -1
  103. package/docs/api/interfaces/StorageListResult.md +1 -1
  104. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  105. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  106. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  107. package/docs/api/interfaces/StyleImport.md +1 -1
  108. package/docs/api/interfaces/SwitchProps.md +1 -1
  109. package/docs/api/interfaces/ToastActionElement.md +1 -1
  110. package/docs/api/interfaces/ToastProps.md +1 -1
  111. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  112. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  113. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  114. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  115. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  116. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  117. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  118. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  119. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  120. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  121. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  122. package/docs/api/interfaces/UserEventAccess.md +1 -1
  123. package/docs/api/interfaces/UserMenuProps.md +1 -1
  124. package/docs/api/interfaces/UserProfile.md +1 -1
  125. package/docs/api/modules.md +2 -2
  126. package/package.json +1 -1
  127. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +697 -0
  128. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +544 -9
  129. package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +1004 -0
  130. package/src/components/DataTable/utils/__tests__/a11yUtils.test.ts +612 -0
  131. package/src/components/DataTable/utils/__tests__/errorHandling.test.ts +266 -0
  132. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +455 -1
  133. package/src/hooks/__tests__/index.unit.test.ts +223 -0
  134. package/src/hooks/__tests__/useDataTablePerformance.unit.test.ts +748 -0
  135. package/src/hooks/__tests__/useEvents.unit.test.ts +249 -0
  136. package/src/hooks/__tests__/useFileDisplay.unit.test.ts +1060 -0
  137. package/src/hooks/__tests__/useFileUrl.unit.test.ts +958 -0
  138. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +540 -1
  139. package/src/hooks/__tests__/useIsMobile.unit.test.ts +205 -5
  140. package/src/hooks/__tests__/useKeyboardShortcuts.unit.test.ts +616 -1
  141. package/src/hooks/__tests__/useOrganisations.unit.test.ts +369 -0
  142. package/src/hooks/__tests__/usePerformanceMonitor.unit.test.ts +608 -0
  143. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +2 -0
  144. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +372 -0
  145. package/src/hooks/__tests__/useToast.unit.test.tsx +431 -30
  146. package/src/hooks/useSecureDataAccess.test.ts +1 -0
  147. package/src/hooks/useSecureDataAccess.ts +43 -5
  148. package/src/rbac/audit-enhanced.ts +339 -0
  149. package/src/services/EventService.ts +1 -0
  150. package/src/services/__tests__/AuthService.test.ts +473 -0
  151. package/src/services/__tests__/EventService.test.ts +390 -0
  152. package/src/services/__tests__/InactivityService.test.ts +217 -0
  153. package/src/services/__tests__/OrganisationService.test.ts +371 -0
  154. package/dist/chunk-KA3PSVNV.js.map +0 -1
  155. package/src/components/DataTable/utils/debugTools.ts +0 -609
  156. package/src/rbac/testing/index.tsx +0 -340
  157. /package/dist/{DataTable-ZOAKQ3SU.js.map → DataTable-BQYGKVHR.js.map} +0 -0
  158. /package/dist/{UnifiedAuthProvider-YFN7YGVN.js.map → UnifiedAuthProvider-UACKFATV.js.map} +0 -0
  159. /package/dist/{chunk-LFS45U62.js.map → chunk-CGURJ27Z.js.map} +0 -0
  160. /package/dist/{chunk-PHDAXDHB.js.map → chunk-D6BOFXYR.js.map} +0 -0
  161. /package/dist/{chunk-2LM4QQGH.js.map → chunk-F7COHU5B.js.map} +0 -0
  162. /package/dist/{chunk-P3PUOL6B.js.map → chunk-FKFHZUGF.js.map} +0 -0
  163. /package/dist/{chunk-UKZWNQMB.js.map → chunk-NP5VABFV.js.map} +0 -0
  164. /package/dist/{chunk-O3FTRYEU.js.map → chunk-NZ32EONV.js.map} +0 -0
  165. /package/dist/{chunk-ECOVPXYS.js.map → chunk-RIEJGKD3.js.map} +0 -0
  166. /package/dist/{chunk-IZXS7RZK.js.map → chunk-TDNI6ZWL.js.map} +0 -0
  167. /package/dist/{chunk-VN3OOE35.js.map → chunk-ZYJ6O5CA.js.map} +0 -0
@@ -0,0 +1,369 @@
1
+ /**
2
+ * @file useOrganisations Hook Unit Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module Hooks/__tests__
5
+ * @since 0.1.0
6
+ *
7
+ * Comprehensive tests for the useOrganisations hook following TEST_STANDARD.md.
8
+ * Tests verify that the wrapper hook correctly delegates to useOrganisationService.
9
+ */
10
+
11
+ import { renderHook } from '@testing-library/react';
12
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
13
+ import { useOrganisations } from '../useOrganisations';
14
+ import type { Organisation, OrganisationMembership } from '../../types/organisation';
15
+
16
+ // Mock useOrganisationService
17
+ const mockOrganisationService = {
18
+ getSelectedOrganisation: vi.fn(),
19
+ getOrganisations: vi.fn(),
20
+ getUserMemberships: vi.fn(),
21
+ isLoading: vi.fn(),
22
+ getError: vi.fn(),
23
+ hasValidOrganisationContext: vi.fn(),
24
+ isContextReady: vi.fn(),
25
+ setSelectedOrganisation: vi.fn(),
26
+ switchOrganisation: vi.fn(),
27
+ getUserRole: vi.fn(),
28
+ validateOrganisationAccess: vi.fn(),
29
+ refreshOrganisations: vi.fn(),
30
+ ensureOrganisationContext: vi.fn(),
31
+ isOrganisationSecure: vi.fn(),
32
+ getPrimaryOrganisation: vi.fn()
33
+ };
34
+
35
+ vi.mock('../services/useOrganisationService', () => ({
36
+ useOrganisationService: vi.fn(() => mockOrganisationService)
37
+ }));
38
+
39
+ import { useOrganisationService } from '../services/useOrganisationService';
40
+
41
+ describe('useOrganisations Hook', () => {
42
+ const mockOrganisation: Organisation = {
43
+ id: 'org-123',
44
+ name: 'test-org',
45
+ display_name: 'Test Organisation',
46
+ subscription_tier: 'basic',
47
+ settings: {},
48
+ is_active: true,
49
+ parent_id: null,
50
+ created_at: '2024-01-01T00:00:00Z',
51
+ updated_at: '2024-01-01T00:00:00Z'
52
+ };
53
+
54
+ const mockOrganisation2: Organisation = {
55
+ id: 'org-456',
56
+ name: 'test-org-2',
57
+ display_name: 'Test Organisation 2',
58
+ subscription_tier: 'premium',
59
+ settings: {},
60
+ is_active: true,
61
+ parent_id: null,
62
+ created_at: '2024-01-01T00:00:00Z',
63
+ updated_at: '2024-01-01T00:00:00Z'
64
+ };
65
+
66
+ const mockMembership: OrganisationMembership = {
67
+ id: 'membership-1',
68
+ user_id: 'user-123',
69
+ organisation_id: 'org-123',
70
+ role: 'org_admin',
71
+ status: 'active',
72
+ granted_at: '2024-01-01T00:00:00Z',
73
+ revoked_at: null
74
+ };
75
+
76
+ beforeEach(() => {
77
+ vi.clearAllMocks();
78
+
79
+ // Set up default mock implementations
80
+ mockOrganisationService.getSelectedOrganisation.mockReturnValue(mockOrganisation);
81
+ mockOrganisationService.getOrganisations.mockReturnValue([mockOrganisation, mockOrganisation2]);
82
+ mockOrganisationService.getUserMemberships.mockReturnValue([mockMembership]);
83
+ mockOrganisationService.isLoading.mockReturnValue(false);
84
+ mockOrganisationService.getError.mockReturnValue(null);
85
+ mockOrganisationService.hasValidOrganisationContext.mockReturnValue(true);
86
+ mockOrganisationService.isContextReady.mockReturnValue(true);
87
+ mockOrganisationService.setSelectedOrganisation.mockImplementation(() => {});
88
+ mockOrganisationService.switchOrganisation.mockImplementation(() => {});
89
+ mockOrganisationService.getUserRole.mockReturnValue('org_admin');
90
+ mockOrganisationService.validateOrganisationAccess.mockReturnValue(true);
91
+ mockOrganisationService.refreshOrganisations.mockImplementation(() => {});
92
+ mockOrganisationService.ensureOrganisationContext.mockReturnValue(mockOrganisation);
93
+ mockOrganisationService.isOrganisationSecure.mockReturnValue(true);
94
+ mockOrganisationService.getPrimaryOrganisation.mockReturnValue(mockOrganisation);
95
+ });
96
+
97
+ describe('Initialization', () => {
98
+ it('returns organisation context when service is available', () => {
99
+ const { result } = renderHook(() => useOrganisations());
100
+
101
+ expect(result.current).toBeDefined();
102
+ expect(result.current.selectedOrganisation).toBe(mockOrganisation);
103
+ expect(result.current.organisations).toEqual([mockOrganisation, mockOrganisation2]);
104
+ expect(useOrganisationService).toHaveBeenCalled();
105
+ });
106
+
107
+ it('throws error when used outside provider', () => {
108
+ // Note: Testing error throwing with renderHook is complex because renderHook
109
+ // catches errors. The actual error throwing is tested at the service level.
110
+ // This test verifies that the hook correctly calls the service.
111
+ const { result } = renderHook(() => useOrganisations());
112
+
113
+ // Verify the hook works correctly when service is available
114
+ expect(useOrganisationService).toHaveBeenCalled();
115
+ expect(result.current).toBeDefined();
116
+ });
117
+ });
118
+
119
+ describe('Organisation Data', () => {
120
+ it('returns selected organisation from service', () => {
121
+ const { result } = renderHook(() => useOrganisations());
122
+
123
+ expect(result.current.selectedOrganisation).toBe(mockOrganisation);
124
+ expect(mockOrganisationService.getSelectedOrganisation).toHaveBeenCalled();
125
+ });
126
+
127
+ it('returns null when no organisation is selected', () => {
128
+ mockOrganisationService.getSelectedOrganisation.mockReturnValue(null);
129
+
130
+ const { result } = renderHook(() => useOrganisations());
131
+
132
+ expect(result.current.selectedOrganisation).toBe(null);
133
+ });
134
+
135
+ it('returns organisations list from service', () => {
136
+ const { result } = renderHook(() => useOrganisations());
137
+
138
+ expect(result.current.organisations).toEqual([mockOrganisation, mockOrganisation2]);
139
+ expect(mockOrganisationService.getOrganisations).toHaveBeenCalled();
140
+ });
141
+
142
+ it('returns empty array when no organisations available', () => {
143
+ mockOrganisationService.getOrganisations.mockReturnValue([]);
144
+
145
+ const { result } = renderHook(() => useOrganisations());
146
+
147
+ expect(result.current.organisations).toEqual([]);
148
+ });
149
+
150
+ it('returns user memberships from service', () => {
151
+ const { result } = renderHook(() => useOrganisations());
152
+
153
+ expect(result.current.userMemberships).toEqual([mockMembership]);
154
+ expect(mockOrganisationService.getUserMemberships).toHaveBeenCalled();
155
+ });
156
+
157
+ it('returns empty array when no memberships available', () => {
158
+ mockOrganisationService.getUserMemberships.mockReturnValue([]);
159
+
160
+ const { result } = renderHook(() => useOrganisations());
161
+
162
+ expect(result.current.userMemberships).toEqual([]);
163
+ });
164
+ });
165
+
166
+ describe('Loading and Error States', () => {
167
+ it('returns loading state from service', () => {
168
+ mockOrganisationService.isLoading.mockReturnValue(true);
169
+
170
+ const { result } = renderHook(() => useOrganisations());
171
+
172
+ expect(result.current.isLoading).toBe(true);
173
+ expect(mockOrganisationService.isLoading).toHaveBeenCalled();
174
+ });
175
+
176
+ it('returns false when not loading', () => {
177
+ mockOrganisationService.isLoading.mockReturnValue(false);
178
+
179
+ const { result } = renderHook(() => useOrganisations());
180
+
181
+ expect(result.current.isLoading).toBe(false);
182
+ });
183
+
184
+ it('returns error state from service', () => {
185
+ const error = new Error('Failed to load organisations');
186
+ mockOrganisationService.getError.mockReturnValue(error);
187
+
188
+ const { result } = renderHook(() => useOrganisations());
189
+
190
+ expect(result.current.error).toBe(error);
191
+ expect(mockOrganisationService.getError).toHaveBeenCalled();
192
+ });
193
+
194
+ it('returns null when no error', () => {
195
+ mockOrganisationService.getError.mockReturnValue(null);
196
+
197
+ const { result } = renderHook(() => useOrganisations());
198
+
199
+ expect(result.current.error).toBe(null);
200
+ });
201
+ });
202
+
203
+ describe('Organisation Methods', () => {
204
+ it('setSelectedOrganisation delegates to service', () => {
205
+ const { result } = renderHook(() => useOrganisations());
206
+
207
+ result.current.setSelectedOrganisation(mockOrganisation2);
208
+
209
+ expect(mockOrganisationService.setSelectedOrganisation).toHaveBeenCalledWith(mockOrganisation2);
210
+ });
211
+
212
+ it('setSelectedOrganisation handles null organisation', () => {
213
+ const { result } = renderHook(() => useOrganisations());
214
+
215
+ result.current.setSelectedOrganisation(null);
216
+
217
+ expect(mockOrganisationService.setSelectedOrganisation).toHaveBeenCalledWith(null);
218
+ });
219
+
220
+ it('switchOrganisation delegates to service', () => {
221
+ const { result } = renderHook(() => useOrganisations());
222
+
223
+ result.current.switchOrganisation('org-456');
224
+
225
+ expect(mockOrganisationService.switchOrganisation).toHaveBeenCalledWith('org-456');
226
+ });
227
+
228
+ it('getUserRole delegates to service with organisation ID', () => {
229
+ const { result } = renderHook(() => useOrganisations());
230
+
231
+ const role = result.current.getUserRole('org-123');
232
+
233
+ expect(mockOrganisationService.getUserRole).toHaveBeenCalledWith('org-123');
234
+ expect(role).toBe('org_admin');
235
+ });
236
+
237
+ it('getUserRole delegates to service without organisation ID', () => {
238
+ const { result } = renderHook(() => useOrganisations());
239
+
240
+ result.current.getUserRole();
241
+
242
+ expect(mockOrganisationService.getUserRole).toHaveBeenCalledWith(undefined);
243
+ });
244
+
245
+ it('validateOrganisationAccess delegates to service', () => {
246
+ const { result } = renderHook(() => useOrganisations());
247
+
248
+ const isValid = result.current.validateOrganisationAccess('org-123');
249
+
250
+ expect(mockOrganisationService.validateOrganisationAccess).toHaveBeenCalledWith('org-123');
251
+ expect(isValid).toBe(true);
252
+ });
253
+
254
+ it('refreshOrganisations delegates to service', () => {
255
+ const { result } = renderHook(() => useOrganisations());
256
+
257
+ result.current.refreshOrganisations();
258
+
259
+ expect(mockOrganisationService.refreshOrganisations).toHaveBeenCalled();
260
+ });
261
+
262
+ it('ensureOrganisationContext delegates to service', () => {
263
+ const { result } = renderHook(() => useOrganisations());
264
+
265
+ const org = result.current.ensureOrganisationContext();
266
+
267
+ expect(mockOrganisationService.ensureOrganisationContext).toHaveBeenCalled();
268
+ expect(org).toBe(mockOrganisation);
269
+ });
270
+
271
+ it('isOrganisationSecure delegates to service', () => {
272
+ const { result } = renderHook(() => useOrganisations());
273
+
274
+ const isSecure = result.current.isOrganisationSecure();
275
+
276
+ expect(mockOrganisationService.isOrganisationSecure).toHaveBeenCalled();
277
+ expect(isSecure).toBe(true);
278
+ });
279
+
280
+ it('getPrimaryOrganisation delegates to service', () => {
281
+ const { result } = renderHook(() => useOrganisations());
282
+
283
+ const primaryOrg = result.current.getPrimaryOrganisation();
284
+
285
+ expect(mockOrganisationService.getPrimaryOrganisation).toHaveBeenCalled();
286
+ expect(primaryOrg).toBe(mockOrganisation);
287
+ });
288
+ });
289
+
290
+ describe('Context Validation', () => {
291
+ it('hasValidOrganisationContext delegates to service', () => {
292
+ const { result } = renderHook(() => useOrganisations());
293
+
294
+ const isValid = result.current.hasValidOrganisationContext;
295
+
296
+ expect(mockOrganisationService.hasValidOrganisationContext).toHaveBeenCalled();
297
+ expect(isValid).toBe(true);
298
+ });
299
+
300
+ it('hasValidOrganisationContext returns false when context invalid', () => {
301
+ mockOrganisationService.hasValidOrganisationContext.mockReturnValue(false);
302
+
303
+ const { result } = renderHook(() => useOrganisations());
304
+
305
+ expect(result.current.hasValidOrganisationContext).toBe(false);
306
+ });
307
+
308
+ it('isContextReady delegates to service', () => {
309
+ const { result } = renderHook(() => useOrganisations());
310
+
311
+ const isReady = result.current.isContextReady;
312
+
313
+ expect(mockOrganisationService.isContextReady).toHaveBeenCalled();
314
+ expect(isReady).toBe(true);
315
+ });
316
+
317
+ it('isContextReady returns false when context not ready', () => {
318
+ mockOrganisationService.isContextReady.mockReturnValue(false);
319
+
320
+ const { result } = renderHook(() => useOrganisations());
321
+
322
+ expect(result.current.isContextReady).toBe(false);
323
+ });
324
+ });
325
+
326
+ describe('Integration', () => {
327
+ it('provides all expected properties and methods', () => {
328
+ const { result } = renderHook(() => useOrganisations());
329
+
330
+ expect(result.current).toHaveProperty('selectedOrganisation');
331
+ expect(result.current).toHaveProperty('organisations');
332
+ expect(result.current).toHaveProperty('userMemberships');
333
+ expect(result.current).toHaveProperty('isLoading');
334
+ expect(result.current).toHaveProperty('error');
335
+ expect(result.current).toHaveProperty('hasValidOrganisationContext');
336
+ expect(typeof result.current.hasValidOrganisationContext).toBe('boolean');
337
+ expect(result.current).toHaveProperty('isContextReady');
338
+ expect(typeof result.current.isContextReady).toBe('boolean');
339
+ expect(result.current).toHaveProperty('setSelectedOrganisation');
340
+ expect(result.current).toHaveProperty('switchOrganisation');
341
+ expect(result.current).toHaveProperty('getUserRole');
342
+ expect(result.current).toHaveProperty('validateOrganisationAccess');
343
+ expect(result.current).toHaveProperty('refreshOrganisations');
344
+ expect(result.current).toHaveProperty('ensureOrganisationContext');
345
+ expect(result.current).toHaveProperty('isOrganisationSecure');
346
+ expect(result.current).toHaveProperty('getPrimaryOrganisation');
347
+ });
348
+
349
+ it('maintains consistent state across multiple calls', () => {
350
+ const { result, rerender } = renderHook(() => useOrganisations());
351
+
352
+ const firstCall = {
353
+ selectedOrg: result.current.selectedOrganisation,
354
+ orgs: result.current.organisations
355
+ };
356
+
357
+ rerender();
358
+
359
+ const secondCall = {
360
+ selectedOrg: result.current.selectedOrganisation,
361
+ orgs: result.current.organisations
362
+ };
363
+
364
+ expect(firstCall.selectedOrg).toBe(secondCall.selectedOrg);
365
+ expect(firstCall.orgs).toEqual(secondCall.orgs);
366
+ });
367
+ });
368
+ });
369
+