diff --git a/src/renderer/hooks/useOnlineStatus.test.ts b/src/renderer/hooks/useOnlineStatus.test.ts index db8f819d6..16189caa4 100644 --- a/src/renderer/hooks/useOnlineStatus.test.ts +++ b/src/renderer/hooks/useOnlineStatus.test.ts @@ -12,6 +12,7 @@ describe('renderer/hooks/useOnlineStatus.ts', () => { act(() => { onlineManager.setOnline(true); }); + Object.defineProperty(navigator, 'onLine', { value: true, configurable: true }); }); it('reflects the online manager status', () => { @@ -32,6 +33,16 @@ describe('renderer/hooks/useOnlineStatus.ts', () => { expect(result.current).toBe(true); }); + it('reports offline on the very first render, before any effect runs', () => { + // `queryClient.ts` syncs onlineManager from the browser at import time, + // so a cold start while offline must not paint one frame as online. + onlineManager.setOnline(false); + + const { result } = renderHook(() => useOnlineStatus()); + + expect(result.current).toBe(false); + }); + it('re-probes online state when the system wakes', () => { act(() => { onlineManager.setOnline(false); diff --git a/src/renderer/hooks/useOnlineStatus.ts b/src/renderer/hooks/useOnlineStatus.ts index d99717ded..2110ee37c 100644 --- a/src/renderer/hooks/useOnlineStatus.ts +++ b/src/renderer/hooks/useOnlineStatus.ts @@ -8,7 +8,7 @@ import { onlineManager } from '@tanstack/react-query'; * pause/resume behaviour. */ export function useOnlineStatus(): boolean { - const [isOnline, setIsOnline] = useState(true); + const [isOnline, setIsOnline] = useState(() => onlineManager.isOnline()); useEffect(() => { const handle = () => { diff --git a/src/renderer/utils/api/queryClient.test.ts b/src/renderer/utils/api/queryClient.test.ts new file mode 100644 index 000000000..c050c273e --- /dev/null +++ b/src/renderer/utils/api/queryClient.test.ts @@ -0,0 +1,30 @@ +import { onlineManager } from '@tanstack/react-query'; + +import { syncOnlineManagerWithBrowser } from './queryClient'; + +describe('renderer/utils/api/queryClient.ts', () => { + afterEach(() => { + onlineManager.setOnline(true); + Object.defineProperty(navigator, 'onLine', { value: true, configurable: true }); + }); + + describe('syncOnlineManagerWithBrowser', () => { + it('corrects onlineManager to offline when the device is offline', () => { + onlineManager.setOnline(true); + Object.defineProperty(navigator, 'onLine', { value: false, configurable: true }); + + syncOnlineManagerWithBrowser(); + + expect(onlineManager.isOnline()).toBe(false); + }); + + it('corrects onlineManager to online when the device is online', () => { + onlineManager.setOnline(false); + Object.defineProperty(navigator, 'onLine', { value: true, configurable: true }); + + syncOnlineManagerWithBrowser(); + + expect(onlineManager.isOnline()).toBe(true); + }); + }); +}); diff --git a/src/renderer/utils/api/queryClient.ts b/src/renderer/utils/api/queryClient.ts index 0dd974e9b..b1162d05f 100644 --- a/src/renderer/utils/api/queryClient.ts +++ b/src/renderer/utils/api/queryClient.ts @@ -1,7 +1,23 @@ -import { QueryClient } from '@tanstack/react-query'; +import { onlineManager, QueryClient } from '@tanstack/react-query'; import { Constants } from '../../constants'; +/** + * Set TanStack Query's online state from the browser's actual network state. + * + * `onlineManager` initializes to `online: true` regardless of reality, and + * self-corrects only once the browser fires a native online/offline event. + * Browsers emit those on transitions only, so a device already offline when + * Gitify launches never gets one. + */ +export function syncOnlineManagerWithBrowser(): void { + onlineManager.setOnline(navigator.onLine); +} + +// Runs at import, before the client below exists and therefore before any +// query can fire, so a cold start while offline pauses rather than fetches. +syncOnlineManagerWithBrowser(); + /** * TanStack Query client for all API state. *