From 64564c6c7543ca61f859cece3c9094bcf066e9cd Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Sun, 26 Dec 2021 19:02:34 +0100 Subject: [PATCH 1/2] use the babel coverage reporter --- jest.config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/jest.config.js b/jest.config.js index d504c0a8a..f48e88eb3 100644 --- a/jest.config.js +++ b/jest.config.js @@ -19,6 +19,5 @@ module.exports = { ] }, coveragePathIgnorePatterns: ['/node_modules/', '/dist/', '/test/'], - coverageProvider: 'v8', coverageReporters: ['text'] } From 254ba30e380be8263f7112ff404a11b0a00fd2f9 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Sun, 26 Dec 2021 20:25:43 +0100 Subject: [PATCH 2/2] improve coverage --- jest.config.js | 2 +- src/index.ts | 2 +- src/use-swr.ts | 15 ++++--- src/utils/config.ts | 5 --- src/utils/web-preset.ts | 5 +-- test/unit/serialize.test.ts | 13 ++++++ test/use-swr-error.test.tsx | 72 ++++++++++++++++++++++++++++++++- test/use-swr-reconnect.test.tsx | 36 ++++++++++++++++- test/utils.tsx | 6 +++ 9 files changed, 137 insertions(+), 19 deletions(-) create mode 100644 test/unit/serialize.test.ts diff --git a/jest.config.js b/jest.config.js index f48e88eb3..42fc575e6 100644 --- a/jest.config.js +++ b/jest.config.js @@ -19,5 +19,5 @@ module.exports = { ] }, coveragePathIgnorePatterns: ['/node_modules/', '/dist/', '/test/'], - coverageReporters: ['text'] + coverageReporters: ['text', 'html'] } diff --git a/src/index.ts b/src/index.ts index 47a9f2f4f..8f9182146 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,7 @@ export { useSWRConfig } from './utils/use-swr-config' export { mutate } from './utils/config' // Types -export { +export type { SWRConfiguration, Revalidator, RevalidatorOptions, diff --git a/src/use-swr.ts b/src/use-swr.ts index bfff2325e..137aff793 100644 --- a/src/use-swr.ts +++ b/src/use-swr.ts @@ -77,6 +77,7 @@ export const useSWRHandler = ( const fetcherRef = useRef(fetcher) const configRef = useRef(config) const getConfig = () => configRef.current + const isActive = () => getConfig().isVisible() && getConfig().isOnline() // Get the current state that SWR should return. const cached = cache.get(key) @@ -306,10 +307,14 @@ export const useSWRHandler = ( getConfig().onError(err, key, config) if (config.shouldRetryOnError) { // When retrying, dedupe is always enabled - getConfig().onErrorRetry(err, key, config, revalidate, { - retryCount: (opts.retryCount || 0) + 1, - dedupe: true - }) + if (isActive()) { + // If it's active, stop. It will auto revalidate when refocusing + // or reconnecting. + getConfig().onErrorRetry(err, key, config, revalidate, { + retryCount: (opts.retryCount || 0) + 1, + dedupe: true + }) + } } } } @@ -367,8 +372,6 @@ export const useSWRHandler = ( const keyChanged = initialMountedRef.current const softRevalidate = revalidate.bind(UNDEFINED, WITH_DEDUPE) - const isActive = () => getConfig().isVisible() && getConfig().isOnline() - // Expose state updater to global event listeners. So we can update hook's // internal state from the outside. const onStateUpdate: StateUpdateCallback = ( diff --git a/src/utils/config.ts b/src/utils/config.ts index 7197dbfc6..a36621557 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -20,11 +20,6 @@ const onErrorRetry = ( revalidate: Revalidator, opts: Required ): void => { - if (!preset.isVisible()) { - // If it's hidden, stop. It will auto revalidate when refocusing. - return - } - const maxRetryCount = config.errorRetryCount const currentRetryCount = opts.retryCount diff --git a/src/utils/web-preset.ts b/src/utils/web-preset.ts index a631ab9fd..27b467669 100644 --- a/src/utils/web-preset.ts +++ b/src/utils/web-preset.ts @@ -30,10 +30,7 @@ const offDocumentEvent = hasDoc const isVisible = () => { const visibilityState = hasDoc && document.visibilityState - if (!isUndefined(visibilityState)) { - return visibilityState !== 'hidden' - } - return true + return isUndefined(visibilityState) || visibilityState !== 'hidden' } const initFocus = (callback: () => void) => { diff --git a/test/unit/serialize.test.ts b/test/unit/serialize.test.ts new file mode 100644 index 000000000..ddaa65a54 --- /dev/null +++ b/test/unit/serialize.test.ts @@ -0,0 +1,13 @@ +import { unstable_serialize } from 'swr' +import { stableHash } from '../../src/utils/hash' + +describe('SWR - unstable_serialize', () => { + it('should serialize arguments correctly', async () => { + expect(unstable_serialize([])).toBe('') + expect(unstable_serialize(null)).toBe('') + expect(unstable_serialize('key')).toBe('key') + expect(unstable_serialize([1, { foo: 2, bar: 1 }, ['a', 'b', 'c']])).toBe( + stableHash([1, { foo: 2, bar: 1 }, ['a', 'b', 'c']]) + ) + }) +}) diff --git a/test/use-swr-error.test.tsx b/test/use-swr-error.test.tsx index 5ca2eac3b..249ada79c 100644 --- a/test/use-swr-error.test.tsx +++ b/test/use-swr-error.test.tsx @@ -1,7 +1,13 @@ import { act, fireEvent, screen } from '@testing-library/react' import React, { useEffect, useState } from 'react' import useSWR from 'swr' -import { sleep, createResponse, createKey, renderWithConfig } from './utils' +import { + sleep, + createResponse, + createKey, + renderWithConfig, + mockVisibilityHidden +} from './utils' describe('useSWR - error', () => { it('should handle errors', async () => { @@ -72,6 +78,70 @@ describe('useSWR - error', () => { screen.getByText('error: 2') }) + it('should stop retrying when document is not visible', async () => { + const key = createKey() + let count = 0 + function Page() { + const { data, error } = useSWR( + key, + () => createResponse(new Error('error: ' + count++), { delay: 100 }), + { + onErrorRetry: (_, __, ___, revalidate, revalidateOpts) => { + revalidate(revalidateOpts) + }, + dedupingInterval: 0 + } + ) + if (error) return
{error.message}
+ return
hello, {data}
+ } + renderWithConfig() + screen.getByText('hello,') + + // mount + await screen.findByText('error: 0') + + // errored, retrying + await act(() => sleep(50)) + const resetVisibility = mockVisibilityHidden() + + await act(() => sleep(100)) + screen.getByText('error: 1') + + await act(() => sleep(100)) // stopped due to invisible + screen.getByText('error: 1') + + resetVisibility() + }) + + it('should not retry when shouldRetryOnError is disabled', async () => { + const key = createKey() + let count = 0 + function Page() { + const { data, error } = useSWR( + key, + () => createResponse(new Error('error: ' + count++), { delay: 100 }), + { + onErrorRetry: (_, __, ___, revalidate, revalidateOpts) => { + revalidate(revalidateOpts) + }, + dedupingInterval: 0, + shouldRetryOnError: false + } + ) + if (error) return
{error.message}
+ return
hello, {data}
+ } + renderWithConfig() + screen.getByText('hello,') + + // mount + await screen.findByText('error: 0') + + await act(() => sleep(150)) + screen.getByText('error: 0') + }) + it('should trigger the onLoadingSlow and onSuccess event', async () => { const key = createKey() let loadingSlow = null, diff --git a/test/use-swr-reconnect.test.tsx b/test/use-swr-reconnect.test.tsx index 66f662aa7..292650d78 100644 --- a/test/use-swr-reconnect.test.tsx +++ b/test/use-swr-reconnect.test.tsx @@ -4,7 +4,8 @@ import useSWR from 'swr' import { nextTick as waitForNextTick, renderWithConfig, - createKey + createKey, + mockVisibilityHidden } from './utils' describe('useSWR - reconnect', () => { @@ -90,4 +91,37 @@ describe('useSWR - reconnect', () => { // should not be revalidated screen.getByText('data: 0') }) + + it("shouldn't revalidate on reconnect if invisible", async () => { + let value = 0 + + const key = createKey() + function Page() { + const { data } = useSWR(key, () => value++, { + dedupingInterval: 0, + isOnline: () => false + }) + return
data: {data}
+ } + + renderWithConfig() + // hydration + screen.getByText('data:') + + // mount + await screen.findByText('data: 0') + + await waitForNextTick() + + const resetVisibility = mockVisibilityHidden() + + // trigger reconnect + fireEvent(window, createEvent('offline', window)) + fireEvent(window, createEvent('online', window)) + + // should not be revalidated + screen.getByText('data: 0') + + resetVisibility() + }) }) diff --git a/test/utils.tsx b/test/utils.tsx index 325c24954..56c964b24 100644 --- a/test/utils.tsx +++ b/test/utils.tsx @@ -53,3 +53,9 @@ export const renderWithGlobalCache = ( ): ReturnType => { return _renderWithConfig(element, { ...config }) } + +export const mockVisibilityHidden = () => { + const mockVisibilityState = jest.spyOn(document, 'visibilityState', 'get') + mockVisibilityState.mockImplementation(() => 'hidden') + return () => mockVisibilityState.mockRestore() +}