useSettings error handling

This commit is contained in:
DaKheera47 2026-01-20 23:37:47 +00:00
parent 2d0d91cd2e
commit 2f9845338a
3 changed files with 49 additions and 6 deletions

View File

@ -63,4 +63,18 @@ describe('useSettings', () => {
expect(result.current.settings).toEqual(updatedSettings);
expect(result.current.showSponsorInfo).toBe(false);
});
it('handles errors when fetching settings', async () => {
const mockError = new Error('Failed to fetch');
(api.getSettings as any).mockRejectedValue(mockError);
const { result } = renderHook(() => useSettings());
await waitFor(() => {
expect(result.current.error).toEqual(mockError);
});
expect(result.current.isLoading).toBe(false);
expect(result.current.settings).toBeNull();
});
});

View File

@ -3,29 +3,41 @@ import type { AppSettings } from '../../shared/types';
import * as api from '../api';
let settingsCache: AppSettings | null = null;
let subscribers: Set<(settings: AppSettings) => void> = new Set();
let settingsError: Error | null = null;
let subscribers: Set<(settings: AppSettings | null, error: Error | null) => void> = new Set();
let isFetching = false;
export function useSettings() {
const [settings, setSettings] = useState<AppSettings | null>(settingsCache);
const [error, setError] = useState<Error | null>(settingsError);
useEffect(() => {
if (settingsCache) {
setSettings(settingsCache);
}
if (settingsError) {
setError(settingsError);
}
const handleUpdate = (newSettings: AppSettings) => {
const handleUpdate = (newSettings: AppSettings | null, newError: Error | null) => {
setSettings(newSettings);
setError(newError);
};
subscribers.add(handleUpdate);
if (!settingsCache && !isFetching) {
isFetching = true;
settingsError = null;
api.getSettings()
.then((data) => {
settingsCache = data;
subscribers.forEach(sub => sub(data));
settingsError = null;
subscribers.forEach(sub => sub(data, null));
})
.catch((err) => {
settingsError = err instanceof Error ? err : new Error(String(err));
subscribers.forEach(sub => sub(settingsCache, settingsError));
})
.finally(() => {
isFetching = false;
@ -39,11 +51,19 @@ export function useSettings() {
const refreshSettings = async () => {
isFetching = true;
settingsError = null;
subscribers.forEach(sub => sub(settingsCache, null));
try {
const data = await api.getSettings();
settingsCache = data;
subscribers.forEach(sub => sub(data));
settingsError = null;
subscribers.forEach(sub => sub(data, null));
return data;
} catch (err) {
settingsError = err instanceof Error ? err : new Error(String(err));
subscribers.forEach(sub => sub(settingsCache, settingsError));
throw settingsError;
} finally {
isFetching = false;
}
@ -51,7 +71,8 @@ export function useSettings() {
return {
settings,
isLoading: !settings && isFetching,
error,
isLoading: !settings && isFetching && !error,
showSponsorInfo: settings?.showSponsorInfo ?? true,
refreshSettings,
};
@ -60,6 +81,7 @@ export function useSettings() {
/** @internal For testing only */
export function _resetSettingsCache() {
settingsCache = null;
settingsError = null;
isFetching = false;
subscribers.clear();
}

View File

@ -28,7 +28,7 @@ vi.mock('../../pipeline/index.js', () => {
getPipelineStatus: vi.fn(() => ({ isRunning: false })),
subscribeToProgress: vi.fn((listener: (data: unknown) => void) => {
listener(progress);
return () => {};
return () => { };
}),
};
});
@ -54,6 +54,13 @@ vi.mock('../../services/visa-sponsors/index.js', () => ({
searchSponsors: vi.fn(),
getOrganizationDetails: vi.fn(),
downloadLatestCsv: vi.fn(),
calculateSponsorMatchSummary: vi.fn((results) => {
if (!results || results.length === 0) return { sponsorMatchScore: 0, sponsorMatchNames: null };
return {
sponsorMatchScore: results[0].score,
sponsorMatchNames: JSON.stringify(results.map((r: any) => r.sponsor.organisationName))
};
}),
}));
const originalEnv = { ...process.env };