From fcbd763f84ad2910dd44043514831e114883bc5f Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Fri, 14 Jul 2023 19:21:37 +0200 Subject: [PATCH 01/10] fix(onlineManager): always initialize with `online: true` instead of relying on navigator.onLine, because that indicator is broken AF in chrome https://bugs.chromium.org/p/chromium/issues/list?q=navigator.online --- packages/query-core/src/onlineManager.ts | 57 ++++++------------- .../src/tests/onlineManager.test.tsx | 16 ++---- 2 files changed, 22 insertions(+), 51 deletions(-) diff --git a/packages/query-core/src/onlineManager.ts b/packages/query-core/src/onlineManager.ts index 22c4c1375e..daf77d5a4c 100644 --- a/packages/query-core/src/onlineManager.ts +++ b/packages/query-core/src/onlineManager.ts @@ -1,14 +1,11 @@ import { Subscribable } from './subscribable' import { isServer } from './utils' -type SetupFn = ( - setOnline: (online?: boolean) => void, -) => (() => void) | undefined +type Listener = (online: boolean) => void +type SetupFn = (setOnline: Listener) => (() => void) | undefined -const onlineEvents = ['online', 'offline'] as const - -export class OnlineManager extends Subscribable { - #online?: boolean +export class OnlineManager extends Subscribable { + #online = true #cleanup?: () => void #setup: SetupFn @@ -19,17 +16,16 @@ export class OnlineManager extends Subscribable { // addEventListener does not exist in React Native, but window does // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!isServer && window.addEventListener) { - const listener = () => onOnline() + const onlineListener = () => onOnline(true) + const offlineListener = () => onOnline(false) // Listen to online - onlineEvents.forEach((event) => { - window.addEventListener(event, listener, false) - }) + window.addEventListener('online', onlineListener, false) + window.addEventListener('offline', offlineListener, false) return () => { // Be sure to unsubscribe if a new handler is set - onlineEvents.forEach((event) => { - window.removeEventListener(event, listener) - }) + window.removeEventListener('online', onlineListener) + window.removeEventListener('offline', offlineListener) } } @@ -53,43 +49,22 @@ export class OnlineManager extends Subscribable { setEventListener(setup: SetupFn): void { this.#setup = setup this.#cleanup?.() - this.#cleanup = setup((online?: boolean) => { - if (typeof online === 'boolean') { - this.setOnline(online) - } else { - this.onOnline() - } - }) + this.#cleanup = setup(this.setOnline.bind(this)) } - setOnline(online?: boolean): void { + setOnline(online: boolean): void { const changed = this.#online !== online if (changed) { this.#online = online - this.onOnline() + this.listeners.forEach((listener) => { + listener(online) + }) } } - onOnline(): void { - this.listeners.forEach((listener) => { - listener() - }) - } - isOnline(): boolean { - if (typeof this.#online === 'boolean') { - return this.#online - } - - if ( - typeof navigator === 'undefined' || - typeof navigator.onLine === 'undefined' - ) { - return true - } - - return navigator.onLine + return this.#online } } diff --git a/packages/query-core/src/tests/onlineManager.test.tsx b/packages/query-core/src/tests/onlineManager.test.tsx index 7c42e834f6..51a91eb0e2 100644 --- a/packages/query-core/src/tests/onlineManager.test.tsx +++ b/packages/query-core/src/tests/onlineManager.test.tsx @@ -31,7 +31,7 @@ describe('onlineManager', () => { test('setEventListener should use online boolean arg', async () => { let count = 0 - const setup = (setOnline: (online?: boolean) => void) => { + const setup = (setOnline: (online: boolean) => void) => { setTimeout(() => { count++ setOnline(false) @@ -154,19 +154,15 @@ describe('onlineManager', () => { onlineManager.subscribe(listener) - onlineManager.setOnline(true) - onlineManager.setOnline(true) - - expect(listener).toHaveBeenCalledTimes(1) - onlineManager.setOnline(false) onlineManager.setOnline(false) - expect(listener).toHaveBeenCalledTimes(2) + expect(listener).toHaveBeenNthCalledWith(1, false) - onlineManager.setOnline(undefined) - onlineManager.setOnline(undefined) + onlineManager.setOnline(true) + onlineManager.setOnline(true) - expect(listener).toHaveBeenCalledTimes(3) + expect(listener).toHaveBeenCalledTimes(2) + expect(listener).toHaveBeenNthCalledWith(2, true) }) }) From 5609c3299ecc49cdec2d1f5de63cffaf2754e3b6 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Fri, 14 Jul 2023 19:26:43 +0200 Subject: [PATCH 02/10] docs: document subscribe methods --- docs/react/reference/focusManager.md | 15 ++++++++++++++- docs/react/reference/onlineManager.md | 20 +++++++++++++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/docs/react/reference/focusManager.md b/docs/react/reference/focusManager.md index edf2c94e78..1d8abe6144 100644 --- a/docs/react/reference/focusManager.md +++ b/docs/react/reference/focusManager.md @@ -10,6 +10,7 @@ It can be used to change the default event listeners or to manually change the f Its available methods are: - [`setEventListener`](#focusmanagerseteventlistener) +- [`subscribe`](#focusmanagersubscribe) - [`setFocused`](#focusmanagersetfocused) - [`isFocused`](#focusmanagerisfocused) @@ -33,9 +34,21 @@ focusManager.setEventListener((handleFocus) => { }) ``` +## `focusManager.subscribe` + +`subscribe` can be used to subscribe to changes in the visibility state. It returns an unsubscribe function: + +```tsx +import { focusManager } from '@tanstack/react-query' + +const unsubscribe = focusManager.subscribe(isVisible => { + console.log('isVisible', isVisible) +}) +``` + ## `focusManager.setFocused` -`setFocused` can be used to manually set the focus state. Set `undefined` to fallback to the default focus check. +`setFocused` can be used to manually set the focus state. Set `undefined` to fall back to the default focus check. ```tsx import { focusManager } from '@tanstack/react-query' diff --git a/docs/react/reference/onlineManager.md b/docs/react/reference/onlineManager.md index 4b6536e9e0..51d7c7908a 100644 --- a/docs/react/reference/onlineManager.md +++ b/docs/react/reference/onlineManager.md @@ -10,6 +10,7 @@ It can be used to change the default event listeners or to manually change the o Its available methods are: - [`setEventListener`](#onlinemanagerseteventlistener) +- [`subscribe`](#onlinemanagersubscribe) - [`setOnline`](#onlinemanagersetonline) - [`isOnline`](#onlinemanagerisonline) @@ -28,9 +29,21 @@ onlineManager.setEventListener(setOnline => { }) ``` +## `onlineManager.subscribe` + +`subscribe` can be used to subscribe to changes in the online state. It returns an unsubscribe function: + +```tsx +import { onlineManager } from '@tanstack/react-query' + +const unsubscribe = onlineManager.subscribe(isOnline => { + console.log('isOnline', isOnline) +}) +``` + ## `onlineManager.setOnline` -`setOnline` can be used to manually set the online state. Set `undefined` to fallback to the default online check. +`setOnline` can be used to manually set the online state. ```tsx import { onlineManager } from '@tanstack/react-query' @@ -40,14 +53,11 @@ onlineManager.setOnline(true) // Set to offline onlineManager.setOnline(false) - -// Fallback to the default online check -onlineManager.setOnline(undefined) ``` **Options** -- `online: boolean | undefined` +- `online: boolean` ## `onlineManager.isOnline` From c63d1fbfb20cc0b5cdd112511ba912ce285c81a9 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Fri, 14 Jul 2023 19:37:39 +0200 Subject: [PATCH 03/10] docs --- docs/react/guides/migrating-to-v5.md | 8 ++++++++ docs/react/reference/onlineManager.md | 10 ++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/react/guides/migrating-to-v5.md b/docs/react/guides/migrating-to-v5.md index a1c4e8ada7..b19440e22e 100644 --- a/docs/react/guides/migrating-to-v5.md +++ b/docs/react/guides/migrating-to-v5.md @@ -276,6 +276,14 @@ There are some caveats to this change however, which you must be aware of: The `visibilitychange` event is used exclusively now. This is possible because we only support browsers that support the `visibilitychange` event. This fixes a bunch of issues [as listed here](https://github.com/TanStack/query/pull/4805). +### Network status no longer relies on the `navigator.onLine` property + +`navigator.onLine` doesn't work well in Chromium based browsers. There are [a lot of issues](https://bugs.chromium.org/p/chromium/issues/list?q=navigator.online) around false negatives, which lead to Queries being wrongfully marked as `offline`. + +To circumvent this, we now always start with `online: true` and only listen to `online` and `offline` events to update the status. + +This should reduce the likelihood of false negatives, however, it might mean false positives for offline apps that load via serviceWorkers, which can work even without an internet connection. + ### Removed custom `context` prop in favor of custom `queryClient` instance In v4, we introduced the possibility to pass a custom `context` to all react-query hooks. This allowed for proper isolation when using MicroFrontends. diff --git a/docs/react/reference/onlineManager.md b/docs/react/reference/onlineManager.md index 51d7c7908a..b563010ae8 100644 --- a/docs/react/reference/onlineManager.md +++ b/docs/react/reference/onlineManager.md @@ -3,9 +3,15 @@ id: OnlineManager title: OnlineManager --- -The `OnlineManager` manages the online state within TanStack Query. +The `OnlineManager` manages the online state within TanStack Query. It can be used to change the default event listeners or to manually change the online state. -It can be used to change the default event listeners or to manually change the online state. +> Per default, the `onlineManager` assumes an active network connection, and listens to the `online` and `offline` events on the `window` object to detect changes. + +> In previous versions, `navigator.onLine` was used to determine the network status. However, it doesn't work well in Chromium based browsers. There are [a lot of issues](https://bugs.chromium.org/p/chromium/issues/list?q=navigator.online) around false negatives, which lead to Queries being wrongfully marked as `offline`. + +> To circumvent this, we now always start with `online: true` and only listen to `online` and `offline` events to update the status. + +> This should reduce the likelihood of false negatives, however, it might mean false positives for offline apps that load via serviceWorkers, which can work even without an internet connection. Its available methods are: From b5f4c4cc64f23a5c98d9aa82af91220594ab7abd Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Fri, 14 Jul 2023 19:42:45 +0200 Subject: [PATCH 04/10] test: fix types in tests setting to undefined is now illegal, so we have to reset to `true`, which is the default now --- packages/query-core/src/tests/queryClient.test.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/query-core/src/tests/queryClient.test.tsx b/packages/query-core/src/tests/queryClient.test.tsx index 82f2558abd..ecd4835168 100644 --- a/packages/query-core/src/tests/queryClient.test.tsx +++ b/packages/query-core/src/tests/queryClient.test.tsx @@ -1394,7 +1394,7 @@ describe('queryClient', () => { queryCacheOnFocusSpy.mockRestore() queryCacheOnOnlineSpy.mockRestore() mutationCacheResumePausedMutationsSpy.mockRestore() - onlineManager.setOnline(undefined) + onlineManager.setOnline(true) }) test('should resume paused mutations when coming online', async () => { @@ -1424,7 +1424,7 @@ describe('queryClient', () => { expect(observer1.getCurrentResult().status).toBe('success') }) - onlineManager.setOnline(undefined) + onlineManager.setOnline(true) }) test('should resume paused mutations one after the other when invoked manually at the same time', async () => { @@ -1459,7 +1459,7 @@ describe('queryClient', () => { expect(observer2.getCurrentResult().isPaused).toBeTruthy() }) - onlineManager.setOnline(undefined) + onlineManager.setOnline(true) void queryClient.resumePausedMutations() await sleep(5) await queryClient.resumePausedMutations() @@ -1503,7 +1503,7 @@ describe('queryClient', () => { queryCacheOnOnlineSpy.mockRestore() mutationCacheResumePausedMutationsSpy.mockRestore() focusManager.setFocused(undefined) - onlineManager.setOnline(undefined) + onlineManager.setOnline(true) }) test('should not notify queryCache and mutationCache after multiple mounts/unmounts', async () => { @@ -1538,7 +1538,7 @@ describe('queryClient', () => { queryCacheOnOnlineSpy.mockRestore() mutationCacheResumePausedMutationsSpy.mockRestore() focusManager.setFocused(undefined) - onlineManager.setOnline(undefined) + onlineManager.setOnline(true) }) }) From 8f0a25fd06d2be29785e18ca77744030e26db988 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Fri, 14 Jul 2023 19:48:13 +0200 Subject: [PATCH 05/10] fix(tests): switch from mocking navigator.onLine to mocking onlineManager.isOnline --- .../query-core/src/tests/hydration.test.tsx | 4 +-- .../query-core/src/tests/queryClient.test.tsx | 7 +++-- packages/query-core/src/tests/utils.ts | 6 ++-- .../src/__tests__/useMutation.test.tsx | 10 +++---- .../src/__tests__/useQuery.test.tsx | 28 +++++++++---------- packages/react-query/src/__tests__/utils.tsx | 6 ++-- .../src/__tests__/createMutation.test.tsx | 10 +++---- .../src/__tests__/createQuery.test.tsx | 28 +++++++++---------- packages/solid-query/src/__tests__/utils.tsx | 5 ++-- packages/svelte-query/src/__tests__/utils.ts | 14 ---------- 10 files changed, 53 insertions(+), 65 deletions(-) diff --git a/packages/query-core/src/tests/hydration.test.tsx b/packages/query-core/src/tests/hydration.test.tsx index a1c759858a..5609d227b3 100644 --- a/packages/query-core/src/tests/hydration.test.tsx +++ b/packages/query-core/src/tests/hydration.test.tsx @@ -1,7 +1,7 @@ import { createQueryClient, executeMutation, - mockNavigatorOnLine, + mockOnlineManagerIsOnline, sleep, } from './utils' import { QueryCache } from '../queryCache' @@ -346,7 +346,7 @@ describe('dehydration and rehydration', () => { test('should be able to dehydrate mutations and continue on hydration', async () => { const consoleMock = vi.spyOn(console, 'error') consoleMock.mockImplementation(() => undefined) - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) const serverAddTodo = vi .fn() diff --git a/packages/query-core/src/tests/queryClient.test.tsx b/packages/query-core/src/tests/queryClient.test.tsx index ecd4835168..6527fc3def 100644 --- a/packages/query-core/src/tests/queryClient.test.tsx +++ b/packages/query-core/src/tests/queryClient.test.tsx @@ -5,7 +5,7 @@ import { sleep, queryKey, createQueryClient, - mockNavigatorOnLine, + mockOnlineManagerIsOnline, } from './utils' import type { QueryCache, @@ -1074,7 +1074,7 @@ describe('queryClient', () => { const key1 = queryKey() const queryFn1 = vi.fn().mockReturnValue('data1') await queryClient.fetchQuery({ queryKey: key1, queryFn: queryFn1 }) - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) await queryClient.refetchQueries({ queryKey: key1 }) @@ -1088,7 +1088,7 @@ describe('queryClient', () => { queryClient.setQueryDefaults(key1, { networkMode: 'always' }) const queryFn1 = vi.fn().mockReturnValue('data1') await queryClient.fetchQuery({ queryKey: key1, queryFn: queryFn1 }) - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) await queryClient.refetchQueries({ queryKey: key1 }) @@ -1491,6 +1491,7 @@ describe('queryClient', () => { 'resumePausedMutations', ) + onlineManager.setOnline(false) onlineManager.setOnline(true) expect(queryCacheOnOnlineSpy).toHaveBeenCalledTimes(1) expect(mutationCacheResumePausedMutationsSpy).toHaveBeenCalledTimes(1) diff --git a/packages/query-core/src/tests/utils.ts b/packages/query-core/src/tests/utils.ts index 343ba3d132..ae7accb66a 100644 --- a/packages/query-core/src/tests/utils.ts +++ b/packages/query-core/src/tests/utils.ts @@ -2,7 +2,7 @@ import { act } from '@testing-library/react' import { vi } from 'vitest' import type { MutationOptions, QueryClientConfig } from '..' -import { QueryClient } from '..' +import { onlineManager, QueryClient } from '..' import * as utils from '../utils' export function createQueryClient(config?: QueryClientConfig): QueryClient { @@ -13,8 +13,8 @@ export function mockVisibilityState(value: DocumentVisibilityState) { return vi.spyOn(document, 'visibilityState', 'get').mockReturnValue(value) } -export function mockNavigatorOnLine(value: boolean) { - return vi.spyOn(navigator, 'onLine', 'get').mockReturnValue(value) +export function mockOnlineManagerIsOnline(value: boolean) { + return vi.spyOn(onlineManager, 'isOnline').mockReturnValue(value) } let queryKeyCount = 0 diff --git a/packages/react-query/src/__tests__/useMutation.test.tsx b/packages/react-query/src/__tests__/useMutation.test.tsx index a1650bfc80..e23b63f612 100644 --- a/packages/react-query/src/__tests__/useMutation.test.tsx +++ b/packages/react-query/src/__tests__/useMutation.test.tsx @@ -7,7 +7,7 @@ import { MutationCache, QueryCache, useMutation } from '..' import type { UseMutationResult } from '../types' import { createQueryClient, - mockNavigatorOnLine, + mockOnlineManagerIsOnline, queryKey, renderWithClient, setActTimeout, @@ -427,7 +427,7 @@ describe('useMutation', () => { }) it('should not retry mutations while offline', async () => { - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) let count = 0 @@ -488,7 +488,7 @@ describe('useMutation', () => { }) it('should call onMutate even if paused', async () => { - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) const onMutate = vi.fn() let count = 0 @@ -536,7 +536,7 @@ describe('useMutation', () => { }) it('should optimistically go to paused state if offline', async () => { - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) let count = 0 const states: Array = [] @@ -583,7 +583,7 @@ describe('useMutation', () => { }) it('should be able to retry a mutation when online', async () => { - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) let count = 0 const states: UseMutationResult[] = [] diff --git a/packages/react-query/src/__tests__/useQuery.test.tsx b/packages/react-query/src/__tests__/useQuery.test.tsx index 94aad908f7..a3e18a6836 100644 --- a/packages/react-query/src/__tests__/useQuery.test.tsx +++ b/packages/react-query/src/__tests__/useQuery.test.tsx @@ -5,7 +5,7 @@ import { Blink, createQueryClient, expectType, - mockNavigatorOnLine, + mockOnlineManagerIsOnline, mockVisibilityState, queryKey, renderWithClient, @@ -5081,7 +5081,7 @@ describe('useQuery', () => { describe('networkMode online', () => { it('online queries should not start fetching if you are offline', async () => { - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) const key = queryKey() const states: Array = [] @@ -5163,7 +5163,7 @@ describe('useQuery', () => { await waitFor(() => rendered.getByText('data: data1')) - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) fireEvent.click(rendered.getByRole('button', { name: /invalidate/i })) await waitFor(() => @@ -5229,7 +5229,7 @@ describe('useQuery', () => { await waitFor(() => rendered.getByText('data: data1')) - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) fireEvent.click(rendered.getByRole('button', { name: /invalidate/i })) await waitFor(() => @@ -5275,7 +5275,7 @@ describe('useQuery', () => { ) } - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) const rendered = renderWithClient(queryClient, ) @@ -5326,7 +5326,7 @@ describe('useQuery', () => { ) } - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) const rendered = renderWithClient(queryClient, ) @@ -5380,7 +5380,7 @@ describe('useQuery', () => { ) } - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) const rendered = renderWithClient(queryClient, ) @@ -5457,7 +5457,7 @@ describe('useQuery', () => { ) await waitFor(() => rendered.getByText('failureReason: failed1')) - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) await sleep(20) @@ -5518,7 +5518,7 @@ describe('useQuery', () => { ) } - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) const rendered = renderWithClient(queryClient, ) @@ -5575,7 +5575,7 @@ describe('useQuery', () => { ) } - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) const rendered = renderWithClient(queryClient, ) @@ -5651,7 +5651,7 @@ describe('useQuery', () => { rendered.getByText('status: success, fetchStatus: idle'), ) - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) fireEvent.click(rendered.getByRole('button', { name: /invalidate/i })) @@ -5681,7 +5681,7 @@ describe('useQuery', () => { describe('networkMode always', () => { it('always queries should start fetching even if you are offline', async () => { - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) const key = queryKey() let count = 0 @@ -5721,7 +5721,7 @@ describe('useQuery', () => { }) it('always queries should not pause retries', async () => { - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) const key = queryKey() let count = 0 @@ -5767,7 +5767,7 @@ describe('useQuery', () => { describe('networkMode offlineFirst', () => { it('offlineFirst queries should start fetching if you are offline, but pause retries', async () => { - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) const key = queryKey() let count = 0 diff --git a/packages/react-query/src/__tests__/utils.tsx b/packages/react-query/src/__tests__/utils.tsx index 268b60672b..f11eb47e20 100644 --- a/packages/react-query/src/__tests__/utils.tsx +++ b/packages/react-query/src/__tests__/utils.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { act, render } from '@testing-library/react' import type { QueryClientConfig } from '..' -import { QueryClient, QueryClientProvider } from '..' +import { onlineManager, QueryClient, QueryClientProvider } from '..' import * as utils from '@tanstack/query-core' import { vi } from 'vitest' @@ -49,8 +49,8 @@ export function mockVisibilityState(value: DocumentVisibilityState) { return vi.spyOn(document, 'visibilityState', 'get').mockReturnValue(value) } -export function mockNavigatorOnLine(value: boolean) { - return vi.spyOn(navigator, 'onLine', 'get').mockReturnValue(value) +export function mockOnlineManagerIsOnline(value: boolean) { + return vi.spyOn(onlineManager, 'isOnline').mockReturnValue(value) } let queryKeyCount = 0 diff --git a/packages/solid-query/src/__tests__/createMutation.test.tsx b/packages/solid-query/src/__tests__/createMutation.test.tsx index 9bd1959a5e..d566590d12 100644 --- a/packages/solid-query/src/__tests__/createMutation.test.tsx +++ b/packages/solid-query/src/__tests__/createMutation.test.tsx @@ -15,7 +15,7 @@ import { import type { CreateMutationResult } from '../types' import { createQueryClient, - mockNavigatorOnLine, + mockOnlineManagerIsOnline, queryKey, setActTimeout, sleep, @@ -495,7 +495,7 @@ describe('createMutation', () => { }) it('should not retry mutations while offline', async () => { - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) let count = 0 @@ -562,7 +562,7 @@ describe('createMutation', () => { }) it('should call onMutate even if paused', async () => { - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) const onMutate = vi.fn() let count = 0 @@ -614,7 +614,7 @@ describe('createMutation', () => { }) it('should optimistically go to paused state if offline', async () => { - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) let count = 0 const states: Array = [] @@ -667,7 +667,7 @@ describe('createMutation', () => { }) it('should be able to retry a mutation when online', async () => { - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) let count = 0 const states: CreateMutationResult[] = [] diff --git a/packages/solid-query/src/__tests__/createQuery.test.tsx b/packages/solid-query/src/__tests__/createQuery.test.tsx index d0b608ef61..807d934de8 100644 --- a/packages/solid-query/src/__tests__/createQuery.test.tsx +++ b/packages/solid-query/src/__tests__/createQuery.test.tsx @@ -27,7 +27,7 @@ import { Blink, createQueryClient, expectType, - mockNavigatorOnLine, + mockOnlineManagerIsOnline, mockVisibilityState, queryKey, setActTimeout, @@ -4999,7 +4999,7 @@ describe('createQuery', () => { describe('networkMode online', () => { it('online queries should not start fetching if you are offline', async () => { - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) const key = queryKey() const states: Array = [] @@ -5087,7 +5087,7 @@ describe('createQuery', () => { await waitFor(() => screen.getByText('data: data1')) - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) fireEvent.click(screen.getByRole('button', { name: /invalidate/i })) await waitFor(() => @@ -5155,7 +5155,7 @@ describe('createQuery', () => { await waitFor(() => screen.getByText('data: data1')) - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) fireEvent.click(screen.getByRole('button', { name: /invalidate/i })) await waitFor(() => @@ -5201,7 +5201,7 @@ describe('createQuery', () => { ) } - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) render(() => ( @@ -5256,7 +5256,7 @@ describe('createQuery', () => { ) } - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) render(() => ( @@ -5314,7 +5314,7 @@ describe('createQuery', () => { ) } - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) render(() => ( @@ -5395,7 +5395,7 @@ describe('createQuery', () => { ) await waitFor(() => screen.getByText('failureReason: failed1')) - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) await sleep(20) @@ -5456,7 +5456,7 @@ describe('createQuery', () => { ) } - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) render(() => ( @@ -5515,7 +5515,7 @@ describe('createQuery', () => { ) } - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) render(() => ( @@ -5599,7 +5599,7 @@ describe('createQuery', () => { screen.getByText('status: success, fetchStatus: idle'), ) - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) fireEvent.click(screen.getByRole('button', { name: /invalidate/i })) @@ -5629,7 +5629,7 @@ describe('createQuery', () => { describe('networkMode always', () => { it('always queries should start fetching even if you are offline', async () => { - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) const key = queryKey() let count = 0 @@ -5671,7 +5671,7 @@ describe('createQuery', () => { }) it('always queries should not pause retries', async () => { - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) const key = queryKey() let count = 0 @@ -5721,7 +5721,7 @@ describe('createQuery', () => { describe('networkMode offlineFirst', () => { it('offlineFirst queries should start fetching if you are offline, but pause retries', async () => { - const onlineMock = mockNavigatorOnLine(false) + const onlineMock = mockOnlineManagerIsOnline(false) const key = queryKey() let count = 0 diff --git a/packages/solid-query/src/__tests__/utils.tsx b/packages/solid-query/src/__tests__/utils.tsx index 3d28152f9f..13ffb1083a 100644 --- a/packages/solid-query/src/__tests__/utils.tsx +++ b/packages/solid-query/src/__tests__/utils.tsx @@ -3,6 +3,7 @@ import { QueryClient } from '../QueryClient' import type { ParentProps } from 'solid-js' import { createEffect, createSignal, onCleanup, Show } from 'solid-js' import { vi } from 'vitest' +import { onlineManager } from '@tanstack/query-core' let queryKeyCount = 0 export function queryKey(): Array { @@ -38,8 +39,8 @@ export function mockVisibilityState(value: DocumentVisibilityState) { return vi.spyOn(document, 'visibilityState', 'get').mockReturnValue(value) } -export function mockNavigatorOnLine(value: boolean) { - return vi.spyOn(navigator, 'onLine', 'get').mockReturnValue(value) +export function mockOnlineManagerIsOnline(value: boolean) { + return vi.spyOn(onlineManager, 'isOnline').mockReturnValue(value) } export function sleep(timeout: number): Promise { diff --git a/packages/svelte-query/src/__tests__/utils.ts b/packages/svelte-query/src/__tests__/utils.ts index 67488053f2..42ef674e09 100644 --- a/packages/svelte-query/src/__tests__/utils.ts +++ b/packages/svelte-query/src/__tests__/utils.ts @@ -1,4 +1,3 @@ -import { vi } from 'vitest' import { act } from '@testing-library/svelte' import { QueryClient, type QueryClientConfig } from '../index' @@ -6,14 +5,6 @@ export function createQueryClient(config?: QueryClientConfig): QueryClient { return new QueryClient(config) } -export function mockVisibilityState(value: DocumentVisibilityState) { - return vi.spyOn(document, 'visibilityState', 'get').mockReturnValue(value) -} - -export function mockNavigatorOnLine(value: boolean) { - return vi.spyOn(navigator, 'onLine', 'get').mockReturnValue(value) -} - let queryKeyCount = 0 export function queryKey(): Array { queryKeyCount++ @@ -26,11 +17,6 @@ export function sleep(timeout: number): Promise { }) } -export async function simplefetcher() { - await sleep(10) - return 'test' -} - export function setActTimeout(fn: () => void, ms?: number) { return setTimeout(() => { act(() => { From 901bc2f074230844ec0a83fcf9152e4b94fba721 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Sat, 15 Jul 2023 10:33:05 +0200 Subject: [PATCH 06/10] fix: offline toggle in devtools it should now be enough to set online to true / false, without firing the event, because we always set & respect that value. we don't override the event handler, so real online / offline events might interfere here --- packages/query-devtools/src/Devtools.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/query-devtools/src/Devtools.tsx b/packages/query-devtools/src/Devtools.tsx index 453a97eaba..1e844dd3ee 100644 --- a/packages/query-devtools/src/Devtools.tsx +++ b/packages/query-devtools/src/Devtools.tsx @@ -485,13 +485,11 @@ export const DevtoolsPanel: Component = (props) => {