diff --git a/test/old-tests.test.tsx b/test/old-tests.test.tsx new file mode 100644 index 000000000..ed9c94c8e --- /dev/null +++ b/test/old-tests.test.tsx @@ -0,0 +1,178 @@ +import React, { useEffect, useState } from 'react' +import { + createKey, + createResponse, + executeWithoutBatching, + renderWithConfig, + sleep +} from './utils' +import useSWR from 'swr' +import { act, fireEvent, screen } from '@testing-library/react' + +describe('old tests - only kept as reference', () => { + // https://codesandbox.io/s/concurrent-swr-case-ii-lr6x4u + it.skip('should do state updates in transitions', async () => { + const key1 = createKey() + const key2 = createKey() + + const log = [] + + function Counter() { + const [count, setCount] = React.useState(0) + + React.useEffect(() => { + const interval = setInterval(() => { + setCount(x => x + 1) + }, 20) + return () => clearInterval(interval) + }, []) + + log.push(count) + + return <>{count} + } + + function Body() { + useSWR(key2, () => createResponse(true, { delay: 1000 }), { + revalidateOnFocus: false, + revalidateOnReconnect: false, + dedupingInterval: 0, + suspense: true + }) + return null + } + + function Page() { + const { data } = useSWR(key1, () => createResponse(true, { delay: 50 }), { + revalidateOnFocus: false, + revalidateOnReconnect: false, + dedupingInterval: 0 + }) + + return ( + <> + + {data ? : null} + + ) + } + + await executeWithoutBatching(async () => { + renderWithConfig() + await sleep(500) + }) + }) + + it.skip('should not trigger the onLoadingSlow and onSuccess event after component unmount', async () => { + const key = createKey() + let loadingSlow = null, + success = null + function Page() { + const { data } = useSWR(key, () => createResponse('SWR'), { + onLoadingSlow: loadingKey => { + loadingSlow = loadingKey + }, + onSuccess: (_, successKey) => { + success = successKey + }, + loadingTimeout: 100 + }) + return
hello, {data}
+ } + + function App() { + const [on, toggle] = useState(true) + return ( +
toggle(s => !s)}> + {on && } +
+ ) + } + + renderWithConfig() + screen.getByText('hello,') + expect(loadingSlow).toEqual(null) + expect(success).toEqual(null) + + fireEvent.click(screen.getByText('hello,')) + await act(() => sleep(200)) + expect(success).toEqual(null) + expect(loadingSlow).toEqual(null) + }) + + it.skip('should not trigger the onError and onErrorRetry event after component unmount', async () => { + const key = createKey() + let retry = null, + failed = null + function Page() { + const { data } = useSWR(key, () => createResponse(new Error('error!')), { + onError: (_, errorKey) => { + failed = errorKey + }, + onErrorRetry: (_, errorKey) => { + retry = errorKey + }, + dedupingInterval: 0 + }) + return ( +
+ <>hello, {data} +
+ ) + } + + function App() { + const [on, toggle] = useState(true) + return ( +
toggle(s => !s)}> + {on && } +
+ ) + } + + renderWithConfig() + screen.getByText('hello,') + expect(retry).toEqual(null) + expect(failed).toEqual(null) + + fireEvent.click(screen.getByText('hello,')) + await act(() => sleep(200)) + expect(retry).toEqual(null) + expect(failed).toEqual(null) + }) + // https://github.com/vercel/swr/pull/1003 + it.skip('should not dedupe synchronous mutations', async () => { + const mutationRecivedValues = [] + const renderRecivedValues = [] + + const key = createKey() + function Component() { + const { data, mutate: boundMutate } = useSWR(key, () => 0) + + useEffect(() => { + setTimeout(() => { + // let's mutate twice, synchronously + boundMutate(v => { + mutationRecivedValues.push(v) // should be 0 + return 1 + }, false) + boundMutate(v => { + mutationRecivedValues.push(v) // should be 1 + return 2 + }, false) + }, 1) + // the mutate function is guaranteed to be the same reference + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + renderRecivedValues.push(data) // should be 0 -> 2, never render 1 in between + return null + } + + renderWithConfig() + + await executeWithoutBatching(() => sleep(50)) + expect(mutationRecivedValues).toEqual([0, 1]) + expect(renderRecivedValues).toEqual([undefined, 0, 1, 2]) + }) +}) diff --git a/test/use-swr-cache.test.tsx b/test/use-swr-cache.test.tsx index 20466383b..3fa59f074 100644 --- a/test/use-swr-cache.test.tsx +++ b/test/use-swr-cache.test.tsx @@ -1,5 +1,5 @@ import { act, fireEvent, screen } from '@testing-library/react' -import { useState, StrictMode } from 'react' +import { useState, StrictMode, Profiler } from 'react' import useSWR, { useSWRConfig, SWRConfig, mutate as globalMutate } from 'swr' import { sleep, @@ -47,8 +47,11 @@ describe('useSWR - cache provider', () => { function Page() { const { data } = useSWR(key, fetcher) - renderedValues.push(data) - return
{data}
+ return ( + renderedValues.push(data)}> +
{data}
+
+ ) } renderWithConfig(, { @@ -171,8 +174,13 @@ describe('useSWR - cache provider', () => { screen.getByText('1') unmount() expect(focusFn).toBeCalled() - expect(unsubscribeFocusFn).toBeCalledTimes(1) - expect(unsubscribeReconnectFn).toBeCalledTimes(1) + /** + * production mode + * expect(unsubscribeFocusFn).toBeCalledTimes(1) + * expect(unsubscribeReconnectFn).toBeCalledTimes(1) + */ + expect(unsubscribeFocusFn).toBeCalledTimes(2) + expect(unsubscribeReconnectFn).toBeCalledTimes(2) }) it('should work with revalidateOnFocus', async () => { @@ -335,9 +343,13 @@ describe('useSWR - cache provider', () => { } renderWithConfig() - expect(createCacheProvider).toBeCalledTimes(1) + /** + * production mode + * expect(createCacheProvider).toBeCalledTimes(1) + * */ + expect(createCacheProvider).toBeCalledTimes(2) act(() => rerender({})) - expect(createCacheProvider).toBeCalledTimes(1) + expect(createCacheProvider).toBeCalledTimes(2) }) }) diff --git a/test/use-swr-concurrent-rendering.test.tsx b/test/use-swr-concurrent-rendering.test.tsx index 30ecf76a1..52c99c78f 100644 --- a/test/use-swr-concurrent-rendering.test.tsx +++ b/test/use-swr-concurrent-rendering.test.tsx @@ -1,11 +1,5 @@ import { screen, fireEvent, act } from '@testing-library/react' -import { - createKey, - createResponse, - sleep, - executeWithoutBatching, - renderWithConfig -} from './utils' +import { createKey, createResponse, sleep, renderWithConfig } from './utils' import React from 'react' import useSWR from 'swr' @@ -69,57 +63,4 @@ describe('useSWR - concurrent rendering', () => { await act(() => sleep(120)) screen.getByText(`isPending:0,data:${newKey}`) }) - - // https://codesandbox.io/s/concurrent-swr-case-ii-lr6x4u - it.skip('should do state updates in transitions', async () => { - const key1 = createKey() - const key2 = createKey() - - const log = [] - - function Counter() { - const [count, setCount] = React.useState(0) - - React.useEffect(() => { - const interval = setInterval(() => { - setCount(x => x + 1) - }, 20) - return () => clearInterval(interval) - }, []) - - log.push(count) - - return <>{count} - } - - function Body() { - useSWR(key2, () => createResponse(true, { delay: 1000 }), { - revalidateOnFocus: false, - revalidateOnReconnect: false, - dedupingInterval: 0, - suspense: true - }) - return null - } - - function Page() { - const { data } = useSWR(key1, () => createResponse(true, { delay: 50 }), { - revalidateOnFocus: false, - revalidateOnReconnect: false, - dedupingInterval: 0 - }) - - return ( - <> - - {data ? : null} - - ) - } - - await executeWithoutBatching(async () => { - renderWithConfig() - await sleep(500) - }) - }) }) diff --git a/test/use-swr-error.test.tsx b/test/use-swr-error.test.tsx index b163d2b55..054c98164 100644 --- a/test/use-swr-error.test.tsx +++ b/test/use-swr-error.test.tsx @@ -1,5 +1,5 @@ import { act, fireEvent, screen } from '@testing-library/react' -import { useEffect, useState } from 'react' +import { useState, Profiler } from 'react' import useSWR from 'swr' import { sleep, @@ -288,84 +288,6 @@ describe('useSWR - error', () => { screen.getByText('error: 1') }) - it.skip('should not trigger the onLoadingSlow and onSuccess event after component unmount', async () => { - const key = createKey() - let loadingSlow = null, - success = null - function Page() { - const { data } = useSWR(key, () => createResponse('SWR'), { - onLoadingSlow: loadingKey => { - loadingSlow = loadingKey - }, - onSuccess: (_, successKey) => { - success = successKey - }, - loadingTimeout: 100 - }) - return
hello, {data}
- } - - function App() { - const [on, toggle] = useState(true) - return ( -
toggle(s => !s)}> - {on && } -
- ) - } - - renderWithConfig() - screen.getByText('hello,') - expect(loadingSlow).toEqual(null) - expect(success).toEqual(null) - - fireEvent.click(screen.getByText('hello,')) - await act(() => sleep(200)) - expect(success).toEqual(null) - expect(loadingSlow).toEqual(null) - }) - - it.skip('should not trigger the onError and onErrorRetry event after component unmount', async () => { - const key = createKey() - let retry = null, - failed = null - function Page() { - const { data } = useSWR(key, () => createResponse(new Error('error!')), { - onError: (_, errorKey) => { - failed = errorKey - }, - onErrorRetry: (_, errorKey) => { - retry = errorKey - }, - dedupingInterval: 0 - }) - return ( -
- <>hello, {data} -
- ) - } - - function App() { - const [on, toggle] = useState(true) - return ( -
toggle(s => !s)}> - {on && } -
- ) - } - - renderWithConfig() - screen.getByText('hello,') - expect(retry).toEqual(null) - expect(failed).toEqual(null) - - fireEvent.click(screen.getByText('hello,')) - await act(() => sleep(200)) - expect(retry).toEqual(null) - expect(failed).toEqual(null) - }) - it('should not trigger error retries if errorRetryCount is set to 0', async () => { const key = createKey() let count = 0 @@ -398,7 +320,7 @@ describe('useSWR - error', () => { }) it('should not clear error during revalidating until fetcher is finished successfully', async () => { - const errors = [] + const onError = jest.fn() const key = createKey() let mutate function Page() { @@ -412,11 +334,17 @@ describe('useSWR - error', () => { } ) mutate = _mutate - useEffect(() => { - errors.push(error ? error.message : null) - }, [error]) - return
hello, {error ? error.message : null}
+ return ( + { + onError(error ? error.message : null) + }} + > +
hello, {error ? error.message : null}
+
+ ) } renderWithConfig() @@ -427,7 +355,9 @@ describe('useSWR - error', () => { await act(() => mutate()) // initial -> first error -> mutate -> receive another error // the error won't be cleared during revalidation - expect(errors).toEqual([null, 'error', 'error']) + expect(onError).toHaveBeenNthCalledWith(1, null) + expect(onError).toHaveBeenNthCalledWith(2, 'error') + expect(onError).toHaveBeenNthCalledWith(3, 'error') }) it('should reset isValidating when an error occured synchronously', async () => { diff --git a/test/use-swr-infinite.test.tsx b/test/use-swr-infinite.test.tsx index 30a287580..e901de968 100644 --- a/test/use-swr-infinite.test.tsx +++ b/test/use-swr-infinite.test.tsx @@ -1,4 +1,4 @@ -import { Suspense, useEffect, useState } from 'react' +import { Suspense, useEffect, useState, Profiler } from 'react' import { fireEvent, act, screen } from '@testing-library/react' import useSWR, { mutate as globalMutate, useSWRConfig, SWRConfig } from 'swr' import useSWRInfinite, { unstable_serialize } from 'swr/infinite' @@ -497,25 +497,28 @@ describe('useSWRInfinite', () => { it('should keep `mutate` referential equal', async () => { const setters = [] - const key = createKey() function Comp() { const { data, size, setSize } = useSWRInfinite( index => [key, index], ([_, index]) => createResponse(`page ${index}, `) ) - - setters.push(setSize) - return ( -
{ - // load next page - setSize(size + 1) + { + setters.push(setSize) }} > - data:{data} -
+
{ + // load next page + setSize(size + 1) + }} + > + data:{data} +
+ ) } @@ -531,7 +534,7 @@ describe('useSWRInfinite', () => { // check all `setSize`s are referential equal. for (const setSize of setters) { - expect(setSize).toEqual(setters[0]) + expect(setSize).toBe(setters[0]) } }) @@ -843,15 +846,18 @@ describe('useSWRInfinite', () => { }) it('should correctly set size when key is null', async () => { - const loggedValues = [] - + const onRender = jest.fn() + const key = createKey() function Page() { const { size, setSize } = useSWRInfinite( () => null, () => '' ) - loggedValues.push(size) - return + return ( + onRender(size)}> + + + ) } renderWithConfig() @@ -859,8 +865,8 @@ describe('useSWRInfinite', () => { await screen.findByText('set size') fireEvent.click(screen.getByText('set size')) await nextTick() - - expect(loggedValues).toEqual([1]) + expect(onRender).toBeCalledTimes(1) + expect(onRender).toBeCalledWith(1) }) it('setSize should only accept number', async () => { @@ -1099,12 +1105,11 @@ describe('useSWRInfinite', () => { const { data, mutate } = useSWRInfinite(() => key, fetcher, { dedupingInterval: 0 }) - logger.push(data) return ( - <> + logger.push(data)}>
data: {String(data)}
- +
) } @@ -1250,14 +1255,13 @@ describe('useSWRInfinite', () => { const { data, mutate } = useSWRInfinite(() => key, fetcher, { dedupingInterval: 0 }) - logger.push(data) return ( - <> + logger.push(data)}>
data: {String(data)}
- +
) } @@ -1283,14 +1287,13 @@ describe('useSWRInfinite', () => { const { data, mutate } = useSWRInfinite(() => key, fetcher, { dedupingInterval: 0 }) - logger.push(data) return ( - <> + logger.push(data)}>
data: {String(data)}
- +
) } diff --git a/test/use-swr-integration.test.tsx b/test/use-swr-integration.test.tsx index af651cc30..67c46dc05 100644 --- a/test/use-swr-integration.test.tsx +++ b/test/use-swr-integration.test.tsx @@ -512,14 +512,21 @@ describe('useSWR', () => { const { data, error, isLoading, isValidating } = useSWR(key, fetcher, { errorRetryInterval: 10 }) - logs.push({ - data, - error, - isLoading, - isValidating - }) - if (isLoading) return

loading

- return

data:{data}

+ return ( + + logs.push({ + data, + error, + isLoading, + isValidating + }) + } + > + {isLoading ?

loading

:

data:{data}

} +
+ ) } renderWithConfig() @@ -573,9 +580,11 @@ describe('useSWR', () => { const { data } = useSWR(key, fetcher, { errorRetryInterval: 10 }) - logs.push({ data }) - if (!data) return

loading

- return

data:{data}

+ return ( + logs.push({ data })}> +

data:{data}

+
+ ) } renderWithConfig() @@ -612,8 +621,11 @@ describe('useSWR', () => { }, 100) }, []) if (!show) return null - logs.push(swr.data) - return

data:{swr.data}

+ return ( + logs.push(swr.data)}> +

data:{swr.data}

+
+ ) } renderWithConfig() diff --git a/test/use-swr-laggy.test.tsx b/test/use-swr-laggy.test.tsx index bad5c53f8..86c314961 100644 --- a/test/use-swr-laggy.test.tsx +++ b/test/use-swr-laggy.test.tsx @@ -1,5 +1,5 @@ import { screen, act, fireEvent } from '@testing-library/react' -import { useState } from 'react' +import { Profiler, useState } from 'react' import useSWR from 'swr' import useSWRInfinite from 'swr/infinite' @@ -14,8 +14,11 @@ describe('useSWR - keep previous data', () => { const { data: laggedData } = useSWR(key, fetcher, { keepPreviousData: true }) - loggedData.push([key, laggedData]) - return + return ( + loggedData.push([key, laggedData])}> + + + ) } renderWithConfig() @@ -43,9 +46,14 @@ describe('useSWR - keep previous data', () => { const { data: laggedData } = useSWR(key, fetcher, { keepPreviousData: true }) - - loggedData.push([key, data, laggedData]) - return + return ( + loggedData.push([key, data, laggedData])} + > + + + ) } renderWithConfig() @@ -77,8 +85,14 @@ describe('useSWR - keep previous data', () => { fallbackData: 'fallback' }) - loggedData.push([key, data, laggedData]) - return + return ( + loggedData.push([key, data, laggedData])} + > + + + ) } renderWithConfig() @@ -104,12 +118,11 @@ describe('useSWR - keep previous data', () => { const { data: laggedData, mutate } = useSWR(key, fetcher, { keepPreviousData: true }) - loggedData.push([key, laggedData]) return ( - <> + loggedData.push([key, laggedData])}> - + ) } @@ -141,9 +154,11 @@ describe('useSWR - keep previous data', () => { const { data } = useSWRInfinite(() => key, fetcher, { keepPreviousData: true }) - - loggedData.push([key, data]) - return + return ( + loggedData.push([key, data])}> + + + ) } renderWithConfig() @@ -170,8 +185,11 @@ describe('useSWR - keep previous data', () => { const { data: laggedData } = useSWR(key, fetcher, { keepPreviousData }) - loggedData.push([key, laggedData]) - return + return ( + loggedData.push([key, laggedData])}> + + + ) } renderWithConfig() diff --git a/test/use-swr-loading.test.tsx b/test/use-swr-loading.test.tsx index ca90f2423..cc150bcbf 100644 --- a/test/use-swr-loading.test.tsx +++ b/test/use-swr-loading.test.tsx @@ -1,5 +1,5 @@ import { act, screen, fireEvent } from '@testing-library/react' -import React, { useEffect } from 'react' +import React, { Profiler, useEffect } from 'react' import useSWR from 'swr' import { createResponse, @@ -12,15 +12,14 @@ import { describe('useSWR - loading', () => { it('should return validating state', async () => { - let renderCount = 0 + const onRender = jest.fn() const key = createKey() function Page() { const { data, isValidating } = useSWR(key, () => createResponse('data')) - renderCount++ return ( -
+ hello, {data}, {isValidating ? 'validating' : 'ready'} -
+ ) } @@ -31,19 +30,18 @@ describe('useSWR - loading', () => { // data isValidating // -> undefined, true // -> data, false - expect(renderCount).toEqual(2) + expect(onRender).toBeCalledTimes(2) }) it('should return loading state', async () => { - let renderCount = 0 + const onRender = jest.fn() const key = createKey() function Page() { const { data, isLoading } = useSWR(key, () => createResponse('data')) - renderCount++ return ( -
+ hello, {data}, {isLoading ? 'loading' : 'ready'} -
+ ) } @@ -54,17 +52,20 @@ describe('useSWR - loading', () => { // data isLoading // -> undefined, true // -> data, false - expect(renderCount).toEqual(2) + expect(onRender).toBeCalledTimes(2) }) it('should avoid extra rerenders', async () => { - let renderCount = 0 + const onRender = jest.fn() const key = createKey() function Page() { // we never access `isValidating`, so it will not trigger rerendering const { data } = useSWR(key, () => createResponse('data')) - renderCount++ - return
hello, {data}
+ return ( + + hello, {data} + + ) } renderWithConfig() @@ -73,12 +74,12 @@ describe('useSWR - loading', () => { // data // -> undefined // -> data - expect(renderCount).toEqual(2) + expect(onRender).toBeCalledTimes(2) }) it('should avoid extra rerenders while fetching', async () => { - let renderCount = 0, - dataLoaded = false + const onRender = jest.fn() + let dataLoaded = false const key = createKey() function Page() { @@ -88,8 +89,11 @@ describe('useSWR - loading', () => { dataLoaded = true return res }) - renderCount++ - return
hello
+ return ( + +
hello
+
+ ) } renderWithConfig() @@ -97,15 +101,14 @@ describe('useSWR - loading', () => { await executeWithoutBatching(() => sleep(100)) // wait // it doesn't re-render, but fetch was triggered - expect(renderCount).toEqual(1) + expect(onRender).toBeCalledTimes(1) expect(dataLoaded).toEqual(true) }) it('should avoid extra rerenders when the fallback is the same as cache', async () => { - let renderCount = 0, - initialDataLoaded = false, + let initialDataLoaded = false, mutationDataLoaded = false - + const onRneder = jest.fn() const key = createKey() function Page() { const { data, mutate } = useSWR( @@ -131,9 +134,11 @@ describe('useSWR - loading', () => { return () => clearTimeout(timeout) }, [mutate]) - - renderCount++ - return
{data?.greeting}
+ return ( + +
{data?.greeting}
+
+ ) } renderWithConfig() @@ -143,7 +148,7 @@ describe('useSWR - loading', () => { // it doesn't re-render, but fetch was triggered expect(initialDataLoaded).toEqual(true) expect(mutationDataLoaded).toEqual(true) - expect(renderCount).toEqual(1) + expect(onRneder).toBeCalledTimes(1) }) it('should return enumerable object', async () => { @@ -264,20 +269,19 @@ describe('useSWR - loading', () => { it('should not trigger loading state when revalidating', async () => { const key = createKey() - let renderCount = 0 + const onRender = jest.fn() function Page() { const { isLoading, isValidating, mutate } = useSWR(key, () => createResponse('data', { delay: 10 }) ) - renderCount++ return ( -
+
{isLoading ? 'loading' : 'ready'}, {isValidating ? 'validating' : 'ready'}
-
+ ) } @@ -290,7 +294,7 @@ describe('useSWR - loading', () => { await screen.findByText('ready,ready') // isValidating: true -> false -> true -> false - expect(renderCount).toBe(4) + expect(onRender).toBeCalledTimes(4) }) it('should trigger loading state when changing the key', async () => { diff --git a/test/use-swr-local-mutation.test.tsx b/test/use-swr-local-mutation.test.tsx index 7e4ced9e5..fe16c7434 100644 --- a/test/use-swr-local-mutation.test.tsx +++ b/test/use-swr-local-mutation.test.tsx @@ -1,5 +1,5 @@ import { act, screen, fireEvent } from '@testing-library/react' -import { useEffect, useState } from 'react' +import { Profiler, useEffect, useState } from 'react' import useSWR, { mutate as globalMutate, useSWRConfig } from 'swr' import useSWRInfinite from 'swr/infinite' import { serialize } from 'swr/_internal' @@ -582,9 +582,11 @@ describe('useSWR - local mutation', () => { const timeout = setTimeout(() => setKey(updatedKey), 50) return () => clearTimeout(timeout) }, []) - - refs.push(boundMutate) - return
{data}
+ return ( + refs.push(boundMutate)}> +
{data}
+
+ ) } renderWithConfig(
) @@ -600,42 +602,6 @@ describe('useSWR - local mutation', () => { } }) - // https://github.com/vercel/swr/pull/1003 - it.skip('should not dedupe synchronous mutations', async () => { - const mutationRecivedValues = [] - const renderRecivedValues = [] - - const key = createKey() - function Component() { - const { data, mutate: boundMutate } = useSWR(key, () => 0) - - useEffect(() => { - setTimeout(() => { - // let's mutate twice, synchronously - boundMutate(v => { - mutationRecivedValues.push(v) // should be 0 - return 1 - }, false) - boundMutate(v => { - mutationRecivedValues.push(v) // should be 1 - return 2 - }, false) - }, 1) - // the mutate function is guaranteed to be the same reference - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) - - renderRecivedValues.push(data) // should be 0 -> 2, never render 1 in between - return null - } - - renderWithConfig() - - await executeWithoutBatching(() => sleep(50)) - expect(mutationRecivedValues).toEqual([0, 1]) - expect(renderRecivedValues).toEqual([undefined, 0, 1, 2]) - }) - it('async mutation case 1 (startAt <= MUTATION_TS[key])', async () => { let result = 0 const key = createKey() @@ -702,31 +668,37 @@ describe('useSWR - local mutation', () => { dedupingInterval: 200 } ) - useEffect(() => { - mutate( - updatedKey, - async () => { - result += 1 - return createResponse(result, { - delay: 200 - }) - }, - false - ) - setKey(updatedKey) - }, []) return ( -
{data !== undefined ? `data: ${data.toString()}` : 'loading'}
+ <> +
+ {data !== undefined ? `data: ${data.toString()}` : 'loading'} +
+ + ) } renderWithConfig() screen.getByText('loading') - + fireEvent.click(screen.getByRole('button')) // mutate success await act(() => sleep(200)) - fireEvent.click(screen.getByText('data: 1')) - // fetcher result should be ignored await act(() => sleep(200)) expect(fetcher).toBeCalledTimes(1) @@ -751,25 +723,33 @@ describe('useSWR - local mutation', () => { dedupingInterval: 200 } ) - useEffect(() => { - setKey(updatedKey) - mutate( - updatedKey, - async () => { - result += 1 - return createResponse(result, { delay: 200 }) - }, - false - ) - }, []) return ( -
{data !== undefined ? `data: ${data.toString()}` : 'loading'}
+ <> +
+ {data !== undefined ? `data: ${data.toString()}` : 'loading'} +
+ + ) } renderWithConfig() screen.getByText('loading') - + fireEvent.click(screen.getByRole('button')) // fetcher result should be ignored await act(() => sleep(100)) expect(fetcher).toBeCalledTimes(1) @@ -920,27 +900,38 @@ describe('useSWR - local mutation', () => { function Page() { const { data, mutate } = useSWR(key, () => 'foo') - useEffect(() => { - async function startMutation() { - await sleep(10) - mutate('sync1', false) - mutate(createResponse('async1', { delay: 50 }), false) - await sleep(10) - mutate('sync2', false) - mutate(createResponse('async2', { delay: 50 }), false) - await sleep(10) - mutate('sync3', false) - mutate(createResponse('async3', { delay: 50 }), false) - } - - startMutation() - }, [mutate]) - - loggedData.push(data) - return null + return ( + { + loggedData.push(data) + }} + > + + + ) } renderWithConfig() + fireEvent.click(screen.getByRole('button')) await executeWithoutBatching(() => sleep(200)) // Only "async3" is left and others were deduped. @@ -1040,9 +1031,17 @@ describe('useSWR - local mutation', () => { const { data, mutate: boundMutate } = useSWR(key, () => createResponse('foo', { delay: 20 }) ) - mutate = boundMutate - renderedData.push(data) - return
data: {String(data)}
+ return ( + { + mutate = boundMutate + renderedData.push(data) + }} + > +
data: {String(data)}
+
+ ) } renderWithConfig() @@ -1066,9 +1065,17 @@ describe('useSWR - local mutation', () => { const { data, mutate: boundMutate } = useSWR(key, () => createResponse('foo', { delay: 20 }) ) - mutate = boundMutate - renderedData.push(data) - return
data: {String(data)}
+ return ( + { + mutate = boundMutate + renderedData.push(data) + }} + > +
data: {String(data)}
+
+ ) } renderWithConfig() @@ -1107,12 +1114,12 @@ describe('useSWR - local mutation', () => { function Page() { const mutateWithOptData = useOptimisticDataMutate(key, 'final', 'loading') const { data } = useSWR(key) - renderedData.push(data) + return ( -
+ renderedData.push(data)}>
data: {String(data)}
-
+ ) } @@ -1217,9 +1224,17 @@ describe('useSWR - local mutation', () => { refreshInterval: 10, dedupingInterval: 0 }) - mutate = boundMutate - renderedData.push(data) - return
data: {String(data)}
+ return ( + { + mutate = boundMutate + renderedData.push(data) + }} + > +
data: {String(data)}
+
+ ) } renderWithConfig() @@ -1248,14 +1263,22 @@ describe('useSWR - local mutation', () => { const { data, mutate: boundMutate } = useSWR(key, () => createResponse(cnt++, { delay: 20 }) ) - mutate = boundMutate - if ( - !renderedData.length || - renderedData[renderedData.length - 1] !== data - ) { - renderedData.push(data) - } - return
data: {String(data)}
+ return ( + { + mutate = boundMutate + if ( + !renderedData.length || + renderedData[renderedData.length - 1] !== data + ) { + renderedData.push(data) + } + }} + > +
data: {String(data)}
+
+ ) } renderWithConfig() @@ -1286,16 +1309,22 @@ describe('useSWR - local mutation', () => { const { data, mutate: boundMutate } = useSWR(key, () => createResponse(0, { delay: 20 }) ) - mutate = boundMutate - - if ( - !renderedData.length || - renderedData[renderedData.length - 1] !== data - ) { - renderedData.push(data) - } - - return
data: {String(data)}
+ return ( + { + mutate = boundMutate + if ( + !renderedData.length || + renderedData[renderedData.length - 1] !== data + ) { + renderedData.push(data) + } + }} + > +
data: {String(data)}
+
+ ) } renderWithConfig() @@ -1342,13 +1371,21 @@ describe('useSWR - local mutation', () => { createResponse(serverData, { delay: 20 }) ) mutate = boundMutate - if ( - !renderedData.length || - renderedData[renderedData.length - 1] !== data - ) { - renderedData.push(data) - } - return
data: {String(data)}
+ return ( + { + if ( + !renderedData.length || + renderedData[renderedData.length - 1] !== data + ) { + renderedData.push(data) + } + }} + > +
data: {String(data)}
+
+ ) } // data == "foo" @@ -1392,14 +1429,22 @@ describe('useSWR - local mutation', () => { const { data, mutate: boundMutate } = useSWR(key, () => createResponse(serverData, { delay: 20 }) ) - mutate = boundMutate - if ( - !renderedData.length || - renderedData[renderedData.length - 1] !== data - ) { - renderedData.push(data) - } - return
data: {String(data)}
+ return ( + { + mutate = boundMutate + if ( + !renderedData.length || + renderedData[renderedData.length - 1] !== data + ) { + renderedData.push(data) + } + }} + > +
data: {String(data)}
+
+ ) } // data == "foo" @@ -1455,14 +1500,22 @@ describe('useSWR - local mutation', () => { const { data, mutate: boundMutate } = useSWR(key, () => createResponse(cnt++, { delay: 20 }) ) - mutate = boundMutate - if ( - !renderedData.length || - renderedData[renderedData.length - 1] !== data - ) { - renderedData.push(data) - } - return
data: {String(data)}
+ return ( + { + mutate = boundMutate + if ( + !renderedData.length || + renderedData[renderedData.length - 1] !== data + ) { + renderedData.push(data) + } + }} + > +
data: {String(data)}
+
+ ) } renderWithConfig() @@ -1539,15 +1592,20 @@ describe('useSWR - local mutation', () => { function Page() { const { data, mutate } = useSWR(key, () => 'foo') - mutatePage = () => - mutate(new Promise(res => setTimeout(() => res('baz'), 20)), { - optimisticData: () => 'bar', - revalidate: false, - populateCache: v => '!' + v - }) - - renderedData.push(data) - return null + return ( + { + mutatePage = () => + mutate(new Promise(res => setTimeout(() => res('baz'), 20)), { + optimisticData: () => 'bar', + revalidate: false, + populateCache: v => '!' + v + }) + renderedData.push(data) + }} + > + ) } renderWithConfig() @@ -1582,19 +1640,25 @@ describe('useSWR - local mutation', () => { function Page() { const { data, mutate } = useSWR(key, () => serverData) - appendData = () => { - return mutate(sendRequest('cherry'), { - optimisticData: [...data, 'cherry (optimistic)'], - populateCache: (result, currentData) => [ - ...currentData, - result + ' (res)' - ], - revalidate: true - }) - } + return ( + { + appendData = () => { + return mutate(sendRequest('cherry'), { + optimisticData: [...data, 'cherry (optimistic)'], + populateCache: (result, currentData) => [ + ...currentData, + result + ' (res)' + ], + revalidate: true + }) + } - renderedData.push(data) - return null + renderedData.push(data) + }} + > + ) } renderWithConfig() diff --git a/test/use-swr-middlewares.test.tsx b/test/use-swr-middlewares.test.tsx index b872ea732..18939de45 100644 --- a/test/use-swr-middlewares.test.tsx +++ b/test/use-swr-middlewares.test.tsx @@ -32,7 +32,11 @@ describe('useSWR - middleware', () => { await screen.findByText('hello, data') expect(mockConsoleLog.mock.calls[0][0]).toBe(key) // Initial render and data ready. - expect(mockConsoleLog.mock.calls.length).toBe(2) + /** + * productiion mode + * expect(mockConsoleLog.mock.calls.length).toBe(2) + */ + expect(mockConsoleLog.mock.calls.length).toBe(4) }) it('should pass original keys to middleware', async () => { @@ -54,7 +58,11 @@ describe('useSWR - middleware', () => { await screen.findByText('hello, data') expect(mockConsoleLog.mock.calls[0][0]).toEqual([key, 1, 2, 3]) // Initial render and data ready. - expect(mockConsoleLog.mock.calls.length).toBe(2) + /** + * productiion mode + * expect(mockConsoleLog.mock.calls.length).toBe(2) + */ + expect(mockConsoleLog.mock.calls.length).toBe(4) }) it('should pass null fetcher to middleware', () => { @@ -92,7 +100,11 @@ describe('useSWR - middleware', () => { screen.getByText('hello,') await screen.findByText('hello, data') expect(mockConsoleLog.mock.calls[0][0]).toBe(key) - expect(mockConsoleLog.mock.calls.length).toBe(2) + /** + * productiion mode + * expect(mockConsoleLog.mock.calls.length).toBe(2) + */ + expect(mockConsoleLog.mock.calls.length).toBe(4) }) it('should support extending middleware via context and per-hook config', async () => { @@ -121,8 +133,14 @@ describe('useSWR - middleware', () => { ) screen.getByText('hello,') await screen.findByText('hello, data') + /** + * productiion mode + * expect(mockConsoleLog.mock.calls.map(call => call[0])).toEqual([ + 2, 1, 0, 2, 1, 0 + ]) + */ expect(mockConsoleLog.mock.calls.map(call => call[0])).toEqual([ - 2, 1, 0, 2, 1, 0 + 2, 1, 0, 2, 1, 0, 2, 1, 0, 2, 1, 0 ]) }) @@ -156,7 +174,36 @@ describe('useSWR - middleware', () => { renderWithConfig(, { use: [createLoggerMiddleware(2)] }) screen.getByText('hello,') await screen.findByText('hello, data') + /** + * productiion mode + * expect(mockConsoleLog.mock.calls.map(call => call[0])).toEqual([ + '2-enter', + '1-enter', + '0-enter', + '0-exit', + '1-exit', + '2-exit', + '2-enter', + '1-enter', + '0-enter', + '0-exit', + '1-exit', + '2-exit' + ]) + */ expect(mockConsoleLog.mock.calls.map(call => call[0])).toEqual([ + '2-enter', + '1-enter', + '0-enter', + '0-exit', + '1-exit', + '2-exit', + '2-enter', + '1-enter', + '0-enter', + '0-exit', + '1-exit', + '2-exit', '2-enter', '1-enter', '0-enter', diff --git a/test/use-swr-refresh.test.tsx b/test/use-swr-refresh.test.tsx index 2006d4e23..bf13dd841 100644 --- a/test/use-swr-refresh.test.tsx +++ b/test/use-swr-refresh.test.tsx @@ -300,6 +300,10 @@ describe('useSWR - refresh', () => { undefined, undefined, ], + [ + undefined, + undefined, + ], [ undefined, { @@ -358,6 +362,46 @@ describe('useSWR - refresh', () => { "version": "1.0", }, ], + [ + { + "timestamp": 1, + "version": "1.0", + }, + { + "timestamp": 1, + "version": "1.0", + }, + ], + [ + { + "timestamp": 1, + "version": "1.0", + }, + { + "timestamp": 1, + "version": "1.0", + }, + ], + [ + { + "timestamp": 1, + "version": "1.0", + }, + { + "timestamp": 1, + "version": "1.0", + }, + ], + [ + { + "timestamp": 1, + "version": "1.0", + }, + { + "timestamp": 1, + "version": "1.0", + }, + ], ] `) @@ -369,6 +413,10 @@ describe('useSWR - refresh', () => { undefined, undefined, ], + [ + undefined, + undefined, + ], [ undefined, { @@ -437,6 +485,46 @@ describe('useSWR - refresh', () => { "version": "1.0", }, ], + [ + { + "timestamp": 1, + "version": "1.0", + }, + { + "timestamp": 1, + "version": "1.0", + }, + ], + [ + { + "timestamp": 1, + "version": "1.0", + }, + { + "timestamp": 1, + "version": "1.0", + }, + ], + [ + { + "timestamp": 1, + "version": "1.0", + }, + { + "timestamp": 1, + "version": "1.0", + }, + ], + [ + { + "timestamp": 1, + "version": "1.0", + }, + { + "timestamp": 1, + "version": "1.0", + }, + ], [ { "timestamp": 1, @@ -497,6 +585,26 @@ describe('useSWR - refresh', () => { "version": "1.0", }, ], + [ + { + "timestamp": 2, + "version": "1.0", + }, + { + "timestamp": 2, + "version": "1.0", + }, + ], + [ + { + "timestamp": 2, + "version": "1.0", + }, + { + "timestamp": 2, + "version": "1.0", + }, + ], ] `) }) diff --git a/test/use-swr-streaming-ssr.test.tsx b/test/use-swr-streaming-ssr.test.tsx deleted file mode 100644 index 5299fbb76..000000000 --- a/test/use-swr-streaming-ssr.test.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { act } from '@testing-library/react' -import { Suspense } from 'react' -import useSWR from 'swr' -import { - createKey, - createResponse, - renderWithConfig, - hydrateWithConfig, - mockConsoleForHydrationErrors, - sleep -} from './utils' - -describe('useSWR - streaming', () => { - afterEach(() => { - jest.clearAllMocks() - jest.restoreAllMocks() - }) - - it('should match ssr result when hydrating', async () => { - const ensureAndUnmock = mockConsoleForHydrationErrors() - - const key = createKey() - - // A block fetches the data and updates the cache. - function Block() { - const { data } = useSWR(key, () => createResponse('SWR', { delay: 10 })) - return
{data || 'undefined'}
- } - - const container = document.createElement('div') - container.innerHTML = '
undefined
' - await hydrateWithConfig(, container) - ensureAndUnmock() - }) - - // NOTE: this test is failing because it's not possible to test this behavior - // in JSDOM. We need to test this in a real browser. - it.failing( - 'should match the ssr result when streaming and partially hydrating', - async () => { - const key = createKey() - - const dataDuringHydration = {} - - // A block fetches the data and updates the cache. - function Block({ suspense, delay, id }) { - const { data } = useSWR(key, () => createResponse('SWR', { delay }), { - suspense - }) - - // The first render is always hydration in our case. - if (!dataDuringHydration[id]) { - dataDuringHydration[id] = data || 'undefined' - } - - return
{data || 'undefined'}
- } - - // In this example, a will be hydrated first and b will still be streamed. - // When a is hydrated, it will update the client cache to SWR, and when - // b is being hydrated, it should NOT read that cache. - renderWithConfig( - <> - - - - - - ) - - // The SSR result will always be 2 undefined values because data fetching won't - // happen on the server: - //
undefined
- //
undefined
- - // Wait for streaming to finish. - await act(() => sleep(50)) - - expect(dataDuringHydration).toEqual({ - a: 'undefined', - b: 'undefined' - }) - } - ) -}) diff --git a/test/use-swr-subscription.test.tsx b/test/use-swr-subscription.test.tsx index 50bf73bf6..21a17a0e7 100644 --- a/test/use-swr-subscription.test.tsx +++ b/test/use-swr-subscription.test.tsx @@ -21,7 +21,9 @@ describe('useSWRSubscription', () => { res++ }, 100) - return () => {} + return () => { + clearInterval(intervalId) + } } function Page() { @@ -78,7 +80,9 @@ describe('useSWRSubscription', () => { res++ }, 100) - return () => {} + return () => { + clearInterval(intervalId) + } } function Page() { @@ -138,6 +142,7 @@ describe('useSWRSubscription', () => { }, 100) return () => { + --subscriptionCount clearInterval(intervalId) } } diff --git a/test/use-swr-suspense.test.tsx b/test/use-swr-suspense.test.tsx index 7aaba6f69..9126ff672 100644 --- a/test/use-swr-suspense.test.tsx +++ b/test/use-swr-suspense.test.tsx @@ -258,14 +258,22 @@ describe('useSWR - suspense', () => { suspense: true } ) - if (`${data}` !== renderedResults[renderedResults.length - 1]) { - if (data === undefined) { - renderedResults.push(`${baseKey}-nodata`) - } else { - renderedResults.push(`${data}`) - } - } - return
{data ? data : `${baseKey}-nodata`}
+ return ( + { + if (`${data}` !== renderedResults[renderedResults.length - 1]) { + if (data === undefined) { + renderedResults.push(`${baseKey}-nodata`) + } else { + renderedResults.push(`${data}`) + } + } + }} + > +
{data ? data : `${baseKey}-nodata`}
+
+ ) } const App = () => { const [query, setQuery] = useState('123') @@ -326,18 +334,28 @@ describe('useSWR - suspense', () => { let renderCount = 0 let startRenderCount = 0 const key = createKey() + const profilerKey = createKey() function Section() { - ++startRenderCount const { data } = useSWR(key, () => createResponse('SWR'), { suspense: true }) - ++renderCount - return
{data}
+ return ( + { + ++renderCount + }} + > +
{data}
+
+ ) } renderWithConfig( fallback}> -
+ ++startRenderCount}> +
+ ) @@ -345,7 +363,7 @@ describe('useSWR - suspense', () => { screen.getByText('fallback') await screen.findByText('SWR') await act(() => sleep(50)) // wait a moment to observe unnecessary renders - expect(startRenderCount).toBe(2) // fallback + data + expect(startRenderCount).toBe(1) // fallback expect(renderCount).toBe(1) // data }) diff --git a/test/utils.tsx b/test/utils.tsx index 5391b6cae..c7d806483 100644 --- a/test/utils.tsx +++ b/test/utils.tsx @@ -1,5 +1,6 @@ import { act, fireEvent, render } from '@testing-library/react' import { SWRConfig } from 'swr' +import { StrictMode } from 'react' export function sleep(time: number) { return new Promise(resolve => setTimeout(resolve, time)) @@ -33,7 +34,9 @@ const _renderWithConfig = ( config: Parameters[0]['value'] ): ReturnType => { const TestSWRConfig = ({ children }: { children: React.ReactNode }) => ( - {children} + + {children} + ) return render(element, { wrapper: TestSWRConfig }) } @@ -42,7 +45,8 @@ export const renderWithConfig = ( element: React.ReactElement, config?: Parameters[1] ): ReturnType => { - const provider = () => new Map() + const cache = new Map() + const provider = () => cache return _renderWithConfig(element, { provider, ...config }) } @@ -53,18 +57,6 @@ export const renderWithGlobalCache = ( return _renderWithConfig(element, { ...config }) } -export const hydrateWithConfig = ( - element: React.ReactElement, - container: HTMLElement, - config?: Parameters[1] -): ReturnType => { - const provider = () => new Map() - const TestSWRConfig = ({ children }: { children: React.ReactNode }) => ( - {children} - ) - return render(element, { container, wrapper: TestSWRConfig, hydrate: true }) -} - export const mockVisibilityHidden = () => { const mockVisibilityState = jest.spyOn(document, 'visibilityState', 'get') mockVisibilityState.mockImplementation(() => 'hidden')