Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: refactor use-swr-refresh.test.tsx #1103

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 107 additions & 106 deletions test/use-swr-refresh.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,18 @@ import React, { useState } from 'react'
import useSWR, { cache } from '../src'
import { sleep } from './utils'

// This has to be an async function to wait a microtask to flush updates
const advanceTimers = async (ms: number) => jest.advanceTimersByTime(ms)

// This test heavily depends on setInterval/setTimeout timers, which makes tests slower and flaky.
// So we use Jest's fake timers
describe('useSWR - refresh', () => {
beforeEach(() => {
jest.useFakeTimers()
})
afterEach(() => {
jest.useRealTimers()
})
it('should rerender automatically on interval', async () => {
let count = 0

Expand All @@ -14,20 +25,21 @@ describe('useSWR - refresh', () => {
})
return <div>count: {data}</div>
}
const { container } = render(<Page />)

render(<Page />)

// hydration
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: "`)
screen.getByText('count:')

// mount
await screen.findByText('count: 0')

await act(() => sleep(210)) // update
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 1"`)
await act(() => sleep(50)) // no update
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 1"`)
await act(() => sleep(150)) // update
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 2"`)
await act(() => advanceTimers(200)) // update
screen.getByText('count: 1')
await act(() => advanceTimers(50)) // no update
screen.getByText('count: 1')
await act(() => advanceTimers(150)) // update
screen.getByText('count: 2')
})

it('should dedupe requests combined with intervals', async () => {
Expand All @@ -36,26 +48,29 @@ describe('useSWR - refresh', () => {
function Page() {
const { data } = useSWR('dynamic-2', () => count++, {
refreshInterval: 100,
dedupingInterval: 150
dedupingInterval: 500
})
return <div>count: {data}</div>
}
const { container } = render(<Page />)

render(<Page />)

// hydration
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: "`)
screen.getByText('count:')

// mount
await screen.findByText('count: 0')

await act(() => sleep(110)) // no update (deduped)
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 0"`)
await act(() => sleep(100)) // update
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 1"`)
await act(() => sleep(100)) // no update (deduped)
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 1"`)
await act(() => sleep(100)) // update
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 2"`)
await act(() => advanceTimers(100)) // no update (deduped)
screen.getByText('count: 0')
await act(() => advanceTimers(400)) // reach dudupingInterval
await act(() => advanceTimers(100)) // update
screen.getByText('count: 1')
await act(() => advanceTimers(100)) // no update (deduped)
screen.getByText('count: 1')
await act(() => advanceTimers(400)) // reach dudupingInterval
await act(() => advanceTimers(100)) // update
screen.getByText('count: 2')
})

it('should update data upon interval changes', async () => {
Expand All @@ -72,46 +87,47 @@ describe('useSWR - refresh', () => {
</div>
)
}
const { container } = render(<Page />)
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: "`)

render(<Page />)
screen.getByText('count:')

// mount
await screen.findByText('count: 0')

await act(() => sleep(110))
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 1"`)
await act(() => sleep(25))
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 1"`)
await act(() => sleep(75))
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 2"`)
fireEvent.click(container.firstElementChild)
await act(() => advanceTimers(100))
screen.getByText('count: 1')
await act(() => advanceTimers(50))
screen.getByText('count: 1')
await act(() => advanceTimers(50))
screen.getByText('count: 2')
fireEvent.click(screen.getByText('count: 2'))

await act(() => sleep(100))
await act(() => advanceTimers(100))

expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 2"`)
screen.getByText('count: 2')

await act(() => sleep(60))
await act(() => advanceTimers(50))

expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 3"`)
screen.getByText('count: 3')

await act(() => sleep(160))
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 4"`)
fireEvent.click(container.firstElementChild)
await act(() => advanceTimers(150))
screen.getByText('count: 4')
fireEvent.click(screen.getByText('count: 4'))
await act(() => {
// it will clear 150ms timer and setup a new 200ms timer
return sleep(150)
return advanceTimers(150)
})
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 4"`)
await act(() => sleep(60))
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 5"`)
fireEvent.click(container.firstElementChild)
screen.getByText('count: 4')
await act(() => advanceTimers(50))
screen.getByText('count: 5')
fireEvent.click(screen.getByText('count: 5'))
await act(() => {
// it will clear 200ms timer and stop
return sleep(60)
return advanceTimers(50)
})
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 5"`)
await act(() => sleep(60))
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"count: 5"`)
screen.getByText('count: 5')
await act(() => advanceTimers(50))
screen.getByText('count: 5')
})

it('should update data upon interval changes -- changes happened during revalidate', async () => {
Expand All @@ -137,68 +153,49 @@ describe('useSWR - refresh', () => {
</div>
)
}
const { container } = render(<Page />)
expect(container.firstChild.textContent).toMatchInlineSnapshot(
`"count: 0"`
)

render(<Page />)
screen.getByText('count: 0')

await screen.findByText('count: 0 1')

await act(() => sleep(100))
await act(() => advanceTimers(100))

expect(container.firstChild.textContent).toMatchInlineSnapshot(
`"count: 1 2"`
)
screen.getByText('count: 1 2')

await act(() => sleep(100))
expect(container.firstChild.textContent).toMatchInlineSnapshot(
`"count: 1 2"`
)
await act(() => advanceTimers(100))
screen.getByText('count: 1 2')

await act(() => sleep(100))
expect(container.firstChild.textContent).toMatchInlineSnapshot(
`"count: 1 2"`
)
await act(() => advanceTimers(100))
screen.getByText('count: 1 2')

await act(() => sleep(100))
expect(container.firstChild.textContent).toMatchInlineSnapshot(
`"count: 1 2"`
)
await act(() => advanceTimers(100))
screen.getByText('count: 1 2')

fireEvent.click(container.firstElementChild)
fireEvent.click(screen.getByText('count: 1 2'))

await act(() => {
// it will setup a new 100ms timer
return sleep(50)
return advanceTimers(50)
})

expect(container.firstChild.textContent).toMatchInlineSnapshot(
`"count: 1 0"`
)
screen.getByText('count: 1 0')

await act(() => sleep(50))
await act(() => advanceTimers(50))

expect(container.firstChild.textContent).toMatchInlineSnapshot(
`"count: 2 1"`
)
screen.getByText('count: 2 1')

await act(() => sleep(100))
await act(() => advanceTimers(100))

expect(container.firstChild.textContent).toMatchInlineSnapshot(
`"count: 3 2"`
)
screen.getByText('count: 3 2')

await act(() => sleep(100))
await act(() => advanceTimers(100))

expect(container.firstChild.textContent).toMatchInlineSnapshot(
`"count: 3 2"`
)
screen.getByText('count: 3 2')

await act(() => sleep(100))
await act(() => advanceTimers(100))

expect(container.firstChild.textContent).toMatchInlineSnapshot(
`"count: 3 2"`
)
screen.getByText('count: 3 2')
})

it('should allow use custom compare method', async () => {
Expand Down Expand Up @@ -227,9 +224,9 @@ describe('useSWR - refresh', () => {
return <button onClick={() => change()}>{data.timestamp}</button>
}

const { container } = render(<Page />)
render(<Page />)

expect(container.firstChild.textContent).toMatchInlineSnapshot(`"loading"`)
screen.getByText('loading')

await screen.findByText('1')
expect(fetcher).toBeCalledTimes(1)
Expand All @@ -238,8 +235,8 @@ describe('useSWR - refresh', () => {
version: '1.0'
})

fireEvent.click(container.firstElementChild)
await act(() => sleep(1))
fireEvent.click(screen.getByText('1'))
await act(() => advanceTimers(1))
expect(fetcher).toBeCalledTimes(2)
expect(fetcher).toReturnWith({
timestamp: 2,
Expand All @@ -248,7 +245,7 @@ describe('useSWR - refresh', () => {

const cachedData = cache.get(key)
expect(cachedData.timestamp.toString()).toEqual('1')
expect(container.firstChild.textContent).toMatchInlineSnapshot(`"1"`)
screen.getByText('1')
})

it('should not let the previous interval timer to set new timer if key changes too fast', async () => {
Expand All @@ -268,33 +265,35 @@ describe('useSWR - refresh', () => {
>{`click me ${data}`}</button>
)
}
const { container } = render(<Page />)

render(<Page />)

// initial revalidate
await act(() => sleep(200))
await act(() => advanceTimers(200))
expect(fetcherWithToken).toBeCalledTimes(1)

// first refresh
await act(() => sleep(100))
await act(() => advanceTimers(100))
expect(fetcherWithToken).toBeCalledTimes(2)
expect(fetcherWithToken).toHaveBeenLastCalledWith('0')
await act(() => sleep(200))
await act(() => advanceTimers(200))

// second refresh start
await act(() => sleep(100))
await act(() => advanceTimers(100))
expect(fetcherWithToken).toBeCalledTimes(3)
expect(fetcherWithToken).toHaveBeenLastCalledWith('0')
// change the key during revalidation
// The second refresh will not start a new timer
fireEvent.click(container.firstElementChild)
fireEvent.click(screen.getByText('click me 0'))

// first refresh with new key 1
await act(() => sleep(100))
await act(() => advanceTimers(100))
expect(fetcherWithToken).toBeCalledTimes(4)
expect(fetcherWithToken).toHaveBeenLastCalledWith('1')
await act(() => sleep(210))
await act(() => advanceTimers(200))

// second refresh with new key 1
await act(() => advanceTimers(100))
expect(fetcherWithToken).toBeCalledTimes(5)
expect(fetcherWithToken).toHaveBeenLastCalledWith('1')
})
Expand All @@ -320,38 +319,40 @@ describe('useSWR - refresh', () => {
>{`click me ${data}`}</button>
)
}
const { container } = render(<Page />)

render(<Page />)

// initial revalidate
await act(() => sleep(100))
await act(() => advanceTimers(100))
expect(fetcherWithToken).toBeCalledTimes(1)
expect(onSuccess).toBeCalledTimes(1)
expect(onSuccess).toHaveLastReturnedWith(`0-hash 0-hash`)
// first refresh
await act(() => sleep(50))
await act(() => advanceTimers(50))
expect(fetcherWithToken).toBeCalledTimes(2)
expect(fetcherWithToken).toHaveBeenLastCalledWith('0-hash')
await act(() => sleep(100))
await act(() => advanceTimers(100))
expect(onSuccess).toBeCalledTimes(2)
expect(onSuccess).toHaveLastReturnedWith(`0-hash 0-hash`)

// second refresh start
await act(() => sleep(50))
await act(() => advanceTimers(50))
expect(fetcherWithToken).toBeCalledTimes(3)
expect(fetcherWithToken).toHaveBeenLastCalledWith('0-hash')
// change the key during revalidation
// The second refresh will not start a new timer
fireEvent.click(container.firstElementChild)
fireEvent.click(screen.getByText('click me 0-hash'))

// first refresh with new key 1
await act(() => sleep(50))
await act(() => advanceTimers(50))
expect(fetcherWithToken).toBeCalledTimes(4)
expect(fetcherWithToken).toHaveBeenLastCalledWith('1-hash')
await act(() => sleep(110))
await act(() => advanceTimers(100))
expect(onSuccess).toBeCalledTimes(3)
expect(onSuccess).toHaveLastReturnedWith(`1-hash 1-hash`)

// second refresh with new key 1
await act(() => advanceTimers(50))
expect(fetcherWithToken).toBeCalledTimes(5)
expect(fetcherWithToken).toHaveBeenLastCalledWith('1-hash')
})
Expand Down