@jmruthers/pace-core 0.5.118 → 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.
- package/dist/{DataTable-ZOAKQ3SU.js → DataTable-BQYGKVHR.js} +6 -6
- package/dist/{UnifiedAuthProvider-YFN7YGVN.js → UnifiedAuthProvider-UACKFATV.js} +3 -3
- package/dist/{chunk-7OTQLFVI.js → chunk-B4GZ2BXO.js} +3 -3
- package/dist/{chunk-KA3PSVNV.js → chunk-BHWIUEYH.js} +2 -1
- package/dist/chunk-BHWIUEYH.js.map +1 -0
- package/dist/{chunk-LFS45U62.js → chunk-CGURJ27Z.js} +2 -2
- package/dist/{chunk-PHDAXDHB.js → chunk-D6BOFXYR.js} +3 -3
- package/dist/{chunk-2LM4QQGH.js → chunk-F7COHU5B.js} +8 -8
- package/dist/{chunk-P3PUOL6B.js → chunk-FKFHZUGF.js} +4 -4
- package/dist/{chunk-UKZWNQMB.js → chunk-NP5VABFV.js} +4 -4
- package/dist/{chunk-O3FTRYEU.js → chunk-NZ32EONV.js} +2 -2
- package/dist/{chunk-ECOVPXYS.js → chunk-RIEJGKD3.js} +4 -4
- package/dist/{chunk-HIWXXDXO.js → chunk-TDNI6ZWL.js} +5 -5
- package/dist/{chunk-VN3OOE35.js → chunk-ZYJ6O5CA.js} +2 -2
- package/dist/components.js +8 -8
- package/dist/hooks.js +7 -7
- package/dist/index.js +11 -11
- package/dist/providers.js +2 -2
- package/dist/rbac/index.js +7 -7
- package/dist/utils.js +1 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +2 -2
- package/package.json +1 -1
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +697 -0
- package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +544 -9
- package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +1004 -0
- package/src/components/DataTable/utils/__tests__/a11yUtils.test.ts +612 -0
- package/src/components/DataTable/utils/__tests__/errorHandling.test.ts +266 -0
- package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +455 -1
- package/src/hooks/__tests__/index.unit.test.ts +223 -0
- package/src/hooks/__tests__/useDataTablePerformance.unit.test.ts +748 -0
- package/src/hooks/__tests__/useEvents.unit.test.ts +249 -0
- package/src/hooks/__tests__/useFileDisplay.unit.test.ts +1060 -0
- package/src/hooks/__tests__/useFileUrl.unit.test.ts +958 -0
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +540 -1
- package/src/hooks/__tests__/useIsMobile.unit.test.ts +205 -5
- package/src/hooks/__tests__/useKeyboardShortcuts.unit.test.ts +616 -1
- package/src/hooks/__tests__/useOrganisations.unit.test.ts +369 -0
- package/src/hooks/__tests__/usePerformanceMonitor.unit.test.ts +608 -0
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +2 -0
- package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +372 -0
- package/src/hooks/__tests__/useToast.unit.test.tsx +431 -30
- package/src/hooks/useSecureDataAccess.test.ts +1 -0
- package/src/rbac/audit-enhanced.ts +339 -0
- package/src/services/EventService.ts +1 -0
- package/src/services/__tests__/AuthService.test.ts +473 -0
- package/src/services/__tests__/EventService.test.ts +390 -0
- package/src/services/__tests__/InactivityService.test.ts +217 -0
- package/src/services/__tests__/OrganisationService.test.ts +371 -0
- package/dist/chunk-KA3PSVNV.js.map +0 -1
- package/src/components/DataTable/utils/debugTools.ts +0 -609
- package/src/rbac/testing/index.tsx +0 -340
- /package/dist/{DataTable-ZOAKQ3SU.js.map → DataTable-BQYGKVHR.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-YFN7YGVN.js.map → UnifiedAuthProvider-UACKFATV.js.map} +0 -0
- /package/dist/{chunk-7OTQLFVI.js.map → chunk-B4GZ2BXO.js.map} +0 -0
- /package/dist/{chunk-LFS45U62.js.map → chunk-CGURJ27Z.js.map} +0 -0
- /package/dist/{chunk-PHDAXDHB.js.map → chunk-D6BOFXYR.js.map} +0 -0
- /package/dist/{chunk-2LM4QQGH.js.map → chunk-F7COHU5B.js.map} +0 -0
- /package/dist/{chunk-P3PUOL6B.js.map → chunk-FKFHZUGF.js.map} +0 -0
- /package/dist/{chunk-UKZWNQMB.js.map → chunk-NP5VABFV.js.map} +0 -0
- /package/dist/{chunk-O3FTRYEU.js.map → chunk-NZ32EONV.js.map} +0 -0
- /package/dist/{chunk-ECOVPXYS.js.map → chunk-RIEJGKD3.js.map} +0 -0
- /package/dist/{chunk-HIWXXDXO.js.map → chunk-TDNI6ZWL.js.map} +0 -0
- /package/dist/{chunk-VN3OOE35.js.map → chunk-ZYJ6O5CA.js.map} +0 -0
|
@@ -657,4 +657,375 @@ describe('OrganisationService', () => {
|
|
|
657
657
|
expect(organisationService.getSelectedOrganisation()).toEqual(mockOrganisation);
|
|
658
658
|
});
|
|
659
659
|
});
|
|
660
|
+
|
|
661
|
+
describe('Dependency Updates', () => {
|
|
662
|
+
it('should update dependencies when user changes', () => {
|
|
663
|
+
const newUser = { id: 'user-2', email: 'new@example.com' };
|
|
664
|
+
const newSession = { access_token: 'new-token', user: newUser };
|
|
665
|
+
|
|
666
|
+
organisationService.updateDependencies(newUser, newSession);
|
|
667
|
+
|
|
668
|
+
const deps = organisationService.getDependencies();
|
|
669
|
+
expect(deps.user).toEqual(newUser);
|
|
670
|
+
expect(deps.session).toEqual(newSession);
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
it('should reset initialization state when user logs out', () => {
|
|
674
|
+
// Set up authenticated state
|
|
675
|
+
organisationService.updateDependencies(mockUser, mockSession);
|
|
676
|
+
|
|
677
|
+
// Log out
|
|
678
|
+
organisationService.updateDependencies(null, null);
|
|
679
|
+
|
|
680
|
+
// Should allow re-initialization when user logs back in
|
|
681
|
+
const deps = organisationService.getDependencies();
|
|
682
|
+
expect(deps.user).toBeNull();
|
|
683
|
+
expect(deps.session).toBeNull();
|
|
684
|
+
});
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
describe('Organisation Hierarchy', () => {
|
|
688
|
+
it('should build organisation hierarchy', () => {
|
|
689
|
+
const orgs = [mockOrganisation, mockOrganisation2];
|
|
690
|
+
const hierarchy = organisationService.buildOrganisationHierarchy(orgs);
|
|
691
|
+
|
|
692
|
+
expect(hierarchy).toBeDefined();
|
|
693
|
+
expect(Array.isArray(hierarchy)).toBe(true);
|
|
694
|
+
// Should return root organisations (those without parent_id)
|
|
695
|
+
expect(hierarchy.length).toBeGreaterThanOrEqual(0);
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
it('should handle empty organisation list in hierarchy', () => {
|
|
699
|
+
const hierarchy = organisationService.buildOrganisationHierarchy([]);
|
|
700
|
+
|
|
701
|
+
expect(hierarchy).toEqual([]);
|
|
702
|
+
});
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
describe('Database Context', () => {
|
|
706
|
+
it('should handle database context setting timeout', async () => {
|
|
707
|
+
vi.useFakeTimers();
|
|
708
|
+
|
|
709
|
+
const { setOrganisationContext } = await import('../../utils/organisationContext');
|
|
710
|
+
vi.mocked(setOrganisationContext).mockImplementation(() => {
|
|
711
|
+
return new Promise(() => {
|
|
712
|
+
// Never resolves, will timeout after 5 seconds
|
|
713
|
+
});
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
organisationService.setSelectedOrganisation(mockOrganisation);
|
|
717
|
+
|
|
718
|
+
// Advance timers past the 5 second timeout
|
|
719
|
+
await vi.advanceTimersByTimeAsync(6000);
|
|
720
|
+
|
|
721
|
+
// Wait a bit for async operations to complete
|
|
722
|
+
await vi.runAllTimersAsync();
|
|
723
|
+
|
|
724
|
+
// Context ready should be set even on timeout (non-critical operation)
|
|
725
|
+
// The service sets _isContextReady to true even on error
|
|
726
|
+
expect(organisationService.isContextReady()).toBe(true);
|
|
727
|
+
|
|
728
|
+
vi.useRealTimers();
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
it('should handle database context setting error', async () => {
|
|
732
|
+
const { setOrganisationContext } = await import('../../utils/organisationContext');
|
|
733
|
+
vi.mocked(setOrganisationContext).mockRejectedValue(new Error('Database error'));
|
|
734
|
+
|
|
735
|
+
organisationService.setSelectedOrganisation(mockOrganisation);
|
|
736
|
+
|
|
737
|
+
// Should handle error gracefully
|
|
738
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
739
|
+
|
|
740
|
+
// Context ready should be set even on error
|
|
741
|
+
expect(organisationService.isContextReady()).toBe(true);
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
it('should handle missing Supabase client when setting context', async () => {
|
|
745
|
+
const serviceWithoutClient = new OrganisationService(
|
|
746
|
+
null as any,
|
|
747
|
+
mockUser,
|
|
748
|
+
mockSession
|
|
749
|
+
);
|
|
750
|
+
|
|
751
|
+
serviceWithoutClient.setSelectedOrganisation(mockOrganisation);
|
|
752
|
+
|
|
753
|
+
// Should handle gracefully
|
|
754
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
755
|
+
expect(serviceWithoutClient.isContextReady()).toBe(false);
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
it('should handle missing session when setting context', async () => {
|
|
759
|
+
const serviceWithoutSession = new OrganisationService(
|
|
760
|
+
mockSupabase as any,
|
|
761
|
+
mockUser,
|
|
762
|
+
null
|
|
763
|
+
);
|
|
764
|
+
|
|
765
|
+
serviceWithoutSession.setSelectedOrganisation(mockOrganisation);
|
|
766
|
+
|
|
767
|
+
// Should handle gracefully
|
|
768
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
769
|
+
expect(serviceWithoutSession.isContextReady()).toBe(false);
|
|
770
|
+
});
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
describe('Load User Organisations Edge Cases', () => {
|
|
774
|
+
it('should handle RPC timeout with fallback query', async () => {
|
|
775
|
+
// Mock RPC to timeout
|
|
776
|
+
mockSupabase.rpc.mockImplementation(() => {
|
|
777
|
+
return new Promise((_, reject) => {
|
|
778
|
+
setTimeout(() => reject(new Error('RPC call timeout after 10 seconds')), 100);
|
|
779
|
+
});
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
// Mock fallback query
|
|
783
|
+
mockSupabase.from.mockImplementation((table: string) => {
|
|
784
|
+
if (table === 'rbac_organisation_roles') {
|
|
785
|
+
return {
|
|
786
|
+
select: vi.fn().mockReturnValue({
|
|
787
|
+
eq: vi.fn().mockReturnValue({
|
|
788
|
+
eq: vi.fn().mockReturnValue({
|
|
789
|
+
is: vi.fn().mockReturnValue({
|
|
790
|
+
in: vi.fn().mockResolvedValue({
|
|
791
|
+
data: [mockMembership],
|
|
792
|
+
error: null
|
|
793
|
+
})
|
|
794
|
+
})
|
|
795
|
+
})
|
|
796
|
+
})
|
|
797
|
+
})
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
if (table === 'organisations') {
|
|
801
|
+
return {
|
|
802
|
+
select: vi.fn().mockResolvedValue({
|
|
803
|
+
data: [mockOrganisation],
|
|
804
|
+
error: null
|
|
805
|
+
})
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
return {
|
|
809
|
+
select: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
810
|
+
};
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
await organisationService.initialize();
|
|
814
|
+
|
|
815
|
+
// Should handle timeout and use fallback
|
|
816
|
+
expect(mockSupabase.from).toHaveBeenCalled();
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
it('should handle abort signal during RPC call', async () => {
|
|
820
|
+
const abortController = new AbortController();
|
|
821
|
+
abortController.abort();
|
|
822
|
+
|
|
823
|
+
mockSupabase.rpc.mockImplementation(() => {
|
|
824
|
+
return Promise.reject(new Error('Request aborted'));
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
await organisationService.initialize();
|
|
828
|
+
|
|
829
|
+
// Should handle abort gracefully
|
|
830
|
+
expect(organisationService.getError() || organisationService.getOrganisations()).toBeDefined();
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
it('should prevent duplicate loads when already loading', async () => {
|
|
834
|
+
// Set loading state
|
|
835
|
+
(organisationService as any).isLoadingRef = true;
|
|
836
|
+
|
|
837
|
+
// Call refreshOrganisations which internally calls loadUserOrganisations
|
|
838
|
+
await organisationService.refreshOrganisations();
|
|
839
|
+
|
|
840
|
+
// Should handle duplicate load prevention
|
|
841
|
+
expect(organisationService.isLoading()).toBeDefined();
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
it('should prevent rapid retries', async () => {
|
|
845
|
+
(organisationService as any).lastLoadTimeRef = Date.now();
|
|
846
|
+
|
|
847
|
+
// Call refreshOrganisations which internally calls loadUserOrganisations
|
|
848
|
+
await organisationService.refreshOrganisations();
|
|
849
|
+
|
|
850
|
+
// Should skip if too soon since last load
|
|
851
|
+
expect(organisationService.isLoading()).toBeDefined();
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
it('should handle invalid organisation IDs in memberships', async () => {
|
|
855
|
+
mockSupabase.rpc.mockResolvedValue({
|
|
856
|
+
data: [{ ...mockMembership, organisation_id: '' }], // Empty ID
|
|
857
|
+
error: null
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
mockSupabase.from.mockImplementation((table: string) => {
|
|
861
|
+
if (table === 'organisations') {
|
|
862
|
+
return {
|
|
863
|
+
select: vi.fn().mockResolvedValue({
|
|
864
|
+
data: [],
|
|
865
|
+
error: null
|
|
866
|
+
})
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
return {
|
|
870
|
+
select: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
871
|
+
};
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
await organisationService.initialize();
|
|
875
|
+
|
|
876
|
+
expect(organisationService.getError()).toBeDefined();
|
|
877
|
+
expect(organisationService.getError()?.message).toContain('No valid organisation IDs');
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
it('should handle non-UUID organisation IDs', async () => {
|
|
881
|
+
mockSupabase.rpc.mockResolvedValue({
|
|
882
|
+
data: [{ ...mockMembership, organisation_id: 'invalid-id' }],
|
|
883
|
+
error: null
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
mockSupabase.from.mockImplementation((table: string) => {
|
|
887
|
+
if (table === 'organisations') {
|
|
888
|
+
return {
|
|
889
|
+
select: vi.fn().mockResolvedValue({
|
|
890
|
+
data: [],
|
|
891
|
+
error: null
|
|
892
|
+
})
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
return {
|
|
896
|
+
select: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
897
|
+
};
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
await organisationService.initialize();
|
|
901
|
+
|
|
902
|
+
expect(organisationService.getError()).toBeDefined();
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
it('should clear cached data on error', async () => {
|
|
906
|
+
mockSupabase.rpc.mockRejectedValue(new Error('Network error'));
|
|
907
|
+
|
|
908
|
+
localStorage.setItem('pace-core-selected-organisation', JSON.stringify(mockOrganisation));
|
|
909
|
+
localStorage.setItem('pace-core-organisation-context', 'test');
|
|
910
|
+
|
|
911
|
+
await organisationService.initialize();
|
|
912
|
+
|
|
913
|
+
// Should clear cached data
|
|
914
|
+
expect(localStorage.getItem('pace-core-selected-organisation')).toBeNull();
|
|
915
|
+
expect(localStorage.getItem('pace-core-organisation-context')).toBeNull();
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
it('should handle fallback query failure', async () => {
|
|
919
|
+
// Mock RPC to timeout
|
|
920
|
+
mockSupabase.rpc.mockImplementation(() => {
|
|
921
|
+
return Promise.reject(new Error('RPC call timeout after 10 seconds'));
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
// Mock fallback query to also fail
|
|
925
|
+
mockSupabase.from.mockImplementation((table: string) => {
|
|
926
|
+
if (table === 'rbac_organisation_roles') {
|
|
927
|
+
return {
|
|
928
|
+
select: vi.fn().mockReturnValue({
|
|
929
|
+
eq: vi.fn().mockReturnValue({
|
|
930
|
+
eq: vi.fn().mockReturnValue({
|
|
931
|
+
is: vi.fn().mockReturnValue({
|
|
932
|
+
in: vi.fn().mockRejectedValue(new Error('Fallback query failed'))
|
|
933
|
+
})
|
|
934
|
+
})
|
|
935
|
+
})
|
|
936
|
+
})
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
return {
|
|
940
|
+
select: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
941
|
+
};
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
await organisationService.initialize();
|
|
945
|
+
|
|
946
|
+
// Should handle fallback failure
|
|
947
|
+
expect(organisationService.getError()).toBeDefined();
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
it('should handle invalid persisted organisation ID format', async () => {
|
|
951
|
+
// Set invalid persisted organisation
|
|
952
|
+
localStorage.setItem('pace-core-selected-organisation', JSON.stringify({ id: '' }));
|
|
953
|
+
|
|
954
|
+
mockSupabase.rpc.mockResolvedValue({
|
|
955
|
+
data: [mockMembership],
|
|
956
|
+
error: null
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
mockSupabase.from.mockImplementation((table: string) => {
|
|
960
|
+
if (table === 'organisations') {
|
|
961
|
+
return {
|
|
962
|
+
select: vi.fn().mockResolvedValue({
|
|
963
|
+
data: [mockOrganisation],
|
|
964
|
+
error: null
|
|
965
|
+
})
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
return {
|
|
969
|
+
select: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
970
|
+
};
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
await organisationService.initialize();
|
|
974
|
+
|
|
975
|
+
// Should clear invalid persisted org
|
|
976
|
+
expect(localStorage.getItem('pace-core-selected-organisation')).toBeNull();
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
it('should handle corrupted persisted organisation JSON', async () => {
|
|
980
|
+
localStorage.setItem('pace-core-selected-organisation', 'invalid-json');
|
|
981
|
+
|
|
982
|
+
mockSupabase.rpc.mockResolvedValue({
|
|
983
|
+
data: [mockMembership],
|
|
984
|
+
error: null
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
mockSupabase.from.mockImplementation((table: string) => {
|
|
988
|
+
if (table === 'organisations') {
|
|
989
|
+
return {
|
|
990
|
+
select: vi.fn().mockResolvedValue({
|
|
991
|
+
data: [mockOrganisation],
|
|
992
|
+
error: null
|
|
993
|
+
})
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
return {
|
|
997
|
+
select: vi.fn().mockResolvedValue({ data: [], error: null })
|
|
998
|
+
};
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
await organisationService.initialize();
|
|
1002
|
+
|
|
1003
|
+
// Should clear corrupted JSON
|
|
1004
|
+
expect(localStorage.getItem('pace-core-selected-organisation')).toBeNull();
|
|
1005
|
+
});
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
describe('Context Ready State', () => {
|
|
1009
|
+
it('should return false when context is not ready', () => {
|
|
1010
|
+
expect(organisationService.isContextReady()).toBe(false);
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
it('should return false when organisation is not selected', () => {
|
|
1014
|
+
expect(organisationService.hasValidOrganisationContext()).toBe(false);
|
|
1015
|
+
});
|
|
1016
|
+
|
|
1017
|
+
it('should return false when loading', () => {
|
|
1018
|
+
organisationService.setSelectedOrganisation(mockOrganisation);
|
|
1019
|
+
(organisationService as any)._isLoading = true;
|
|
1020
|
+
|
|
1021
|
+
expect(organisationService.hasValidOrganisationContext()).toBe(false);
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
it('should return false when error exists', () => {
|
|
1025
|
+
organisationService.setSelectedOrganisation(mockOrganisation);
|
|
1026
|
+
(organisationService as any)._error = new Error('Test error');
|
|
1027
|
+
|
|
1028
|
+
expect(organisationService.hasValidOrganisationContext()).toBe(false);
|
|
1029
|
+
});
|
|
1030
|
+
});
|
|
660
1031
|
});
|