mirror of
https://github.com/simoneb/axios-hooks.git
synced 2025-12-08 21:25:56 +00:00
1163 lines
29 KiB
JavaScript
1163 lines
29 KiB
JavaScript
import React from 'react'
|
|
import axios from 'axios'
|
|
import { render, fireEvent } from '@testing-library/react'
|
|
import { renderHook, act } from '@testing-library/react-hooks'
|
|
|
|
import defaultUseAxios, {
|
|
configure as defaultConfigure,
|
|
resetConfigure as defaultResetConfigure,
|
|
clearCache as defaultClearCache,
|
|
loadCache as defaultLoadCache,
|
|
serializeCache as defaultSerializeCache,
|
|
makeUseAxios
|
|
} from '../src'
|
|
import { mockCancelToken } from './testUtils'
|
|
|
|
jest.mock('axios')
|
|
|
|
let cancel
|
|
let token
|
|
let errors
|
|
|
|
beforeEach(() => {
|
|
;({ cancel, token } = mockCancelToken(axios))
|
|
})
|
|
|
|
beforeAll(() => {
|
|
const error = console.error
|
|
|
|
console.error = (...args) => {
|
|
error.apply(console, args) // keep default behaviour
|
|
errors.push(args)
|
|
}
|
|
})
|
|
|
|
beforeEach(() => {
|
|
errors = []
|
|
})
|
|
|
|
afterEach(() => {
|
|
// assert that no errors were logged during tests
|
|
expect(errors).toEqual([])
|
|
})
|
|
|
|
describe('default useAxios', () => {
|
|
standardTests(
|
|
defaultUseAxios,
|
|
defaultConfigure,
|
|
defaultResetConfigure,
|
|
defaultClearCache,
|
|
defaultLoadCache,
|
|
defaultSerializeCache
|
|
)
|
|
})
|
|
|
|
describe('makeUseAxios', () => {
|
|
it('should be a function', () => {
|
|
expect(makeUseAxios).toBeInstanceOf(Function)
|
|
})
|
|
|
|
it('should not throw', () => {
|
|
expect(makeUseAxios()).toBeTruthy()
|
|
})
|
|
|
|
it('should provide a custom implementation of axios', () => {
|
|
const mockAxios = jest.fn().mockResolvedValueOnce({ data: 'whatever' })
|
|
|
|
const setup = makeSetup(makeUseAxios({ axios: mockAxios }))
|
|
|
|
const { waitForNextUpdate } = setup('')
|
|
|
|
expect(mockAxios).toHaveBeenCalled()
|
|
|
|
return waitForNextUpdate()
|
|
})
|
|
|
|
describe('globally disabled cache', () => {
|
|
let setup
|
|
|
|
beforeEach(() => {
|
|
setup = makeSetup(makeUseAxios({ cache: false }))
|
|
})
|
|
|
|
it('should use local state across rerenders', async () => {
|
|
axios.mockResolvedValueOnce({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, rerender } = setup('')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
rerender()
|
|
|
|
expect(axios).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should hit network across component mounts', async () => {
|
|
axios.mockResolvedValue({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, unmount } = setup('')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
unmount()
|
|
|
|
await setup('').waitForNextUpdate()
|
|
|
|
expect(axios).toHaveBeenCalledTimes(2)
|
|
})
|
|
})
|
|
|
|
describe('default hook options', () => {
|
|
describe('manual', () => {
|
|
it('should override default manual option', () => {
|
|
const setup = makeSetup(
|
|
makeUseAxios({ defaultOptions: { manual: true } })
|
|
)
|
|
|
|
setup('')
|
|
|
|
expect(axios).not.toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
describe('useCache', () => {
|
|
it('should override default useCache option', async () => {
|
|
const setup = makeSetup(
|
|
makeUseAxios({ defaultOptions: { useCache: false } })
|
|
)
|
|
|
|
axios.mockResolvedValue({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, unmount } = setup('')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
unmount()
|
|
|
|
await setup('').waitForNextUpdate()
|
|
|
|
expect(axios).toHaveBeenCalledTimes(2)
|
|
})
|
|
})
|
|
|
|
describe('ssr', () => {
|
|
it('should be able to set ssr option', () => {
|
|
makeSetup(makeUseAxios({ defaultOptions: { ssr: false } }))
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('standard tests', () => {
|
|
const useAxios = makeUseAxios()
|
|
|
|
standardTests(
|
|
useAxios,
|
|
useAxios.configure,
|
|
useAxios.resetConfigure,
|
|
useAxios.clearCache,
|
|
useAxios.loadCache,
|
|
useAxios.serializeCache
|
|
)
|
|
|
|
describe('with custom configuration', () => {
|
|
const useAxios = makeUseAxios({ axios })
|
|
|
|
standardTests(
|
|
useAxios,
|
|
useAxios.configure,
|
|
useAxios.resetConfigure,
|
|
useAxios.clearCache,
|
|
useAxios.loadCache,
|
|
useAxios.serializeCache
|
|
)
|
|
})
|
|
})
|
|
})
|
|
|
|
function makeSetup(useAxios) {
|
|
return (config, options = undefined) =>
|
|
renderHook(
|
|
({ config, options }) => {
|
|
return useAxios(config, options)
|
|
},
|
|
{
|
|
initialProps: { config, options }
|
|
}
|
|
)
|
|
}
|
|
|
|
function standardTests(
|
|
useAxios,
|
|
configure,
|
|
resetConfigure,
|
|
clearCache,
|
|
loadCache,
|
|
serializeCache
|
|
) {
|
|
const setup = makeSetup(useAxios)
|
|
|
|
beforeEach(clearCache)
|
|
|
|
describe('basic functionality', () => {
|
|
it('should set loading to true and error to null before the request resolves', async () => {
|
|
axios.mockResolvedValueOnce({ data: 'whatever' })
|
|
|
|
const { result, waitForNextUpdate } = setup('')
|
|
|
|
expect(result.current[0].loading).toBe(true)
|
|
expect(result.current[0].error).toBe(null)
|
|
|
|
await waitForNextUpdate()
|
|
})
|
|
|
|
it('should set loading to false when request resolves', async () => {
|
|
axios.mockResolvedValueOnce({ data: 'whatever' })
|
|
|
|
const { result, waitForNextUpdate } = setup('')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
expect(result.current[0].loading).toBe(false)
|
|
expect(result.current[0].error).toBe(null)
|
|
expect(result.current[0].data).toBe('whatever')
|
|
})
|
|
|
|
it('should set the response', async () => {
|
|
const response = { data: 'whatever' }
|
|
|
|
axios.mockResolvedValueOnce(response)
|
|
|
|
const { result, waitForNextUpdate } = setup('')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
expect(result.current[0].loading).toBe(false)
|
|
expect(result.current[0].error).toBe(null)
|
|
expect(result.current[0].response).toBe(response)
|
|
})
|
|
|
|
it('should set error when request fails', async () => {
|
|
const error = new Error('boom')
|
|
|
|
axios.mockRejectedValueOnce(error)
|
|
|
|
const { result, waitForNextUpdate } = setup('')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
expect(result.current[0].error).toBe(error)
|
|
})
|
|
|
|
it('should not reset error when component rerenders', async () => {
|
|
const error = new Error('boom')
|
|
|
|
axios.mockRejectedValueOnce(error)
|
|
|
|
const { result, waitForNextUpdate, rerender } = setup('')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
expect(result.current[0].error).toBe(error)
|
|
|
|
axios.mockResolvedValueOnce({ data: 'whatever' })
|
|
|
|
rerender()
|
|
|
|
expect(result.current[0].error).toBe(error)
|
|
})
|
|
|
|
it('should reset error when component remounts', async () => {
|
|
const error = new Error('boom')
|
|
|
|
axios.mockRejectedValueOnce(error)
|
|
|
|
const firstRender = setup('')
|
|
|
|
await firstRender.waitForNextUpdate()
|
|
|
|
expect(firstRender.result.current[0].error).toBe(error)
|
|
|
|
axios.mockResolvedValueOnce({ data: 'whatever' })
|
|
|
|
const secondRender = setup('')
|
|
|
|
await secondRender.waitForNextUpdate()
|
|
|
|
expect(secondRender.result.current[0].error).toBe(null)
|
|
})
|
|
|
|
it('should reset error when refetch succeeds', async () => {
|
|
const error = new Error('boom')
|
|
|
|
axios.mockRejectedValueOnce(error)
|
|
|
|
const { result, waitForNextUpdate } = setup('')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
expect(result.current[0].error).toBe(error)
|
|
|
|
axios.mockResolvedValueOnce({ data: 'whatever' })
|
|
|
|
// Refetch
|
|
act(() => {
|
|
result.current[1]()
|
|
})
|
|
|
|
await waitForNextUpdate()
|
|
|
|
expect(result.current[0].error).toBe(null)
|
|
})
|
|
|
|
it('should set loading to false when request completes and returns error', async () => {
|
|
const error = new Error('boom')
|
|
|
|
axios.mockRejectedValueOnce(error)
|
|
|
|
const { result, waitForNextUpdate } = setup('')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
expect(result.current[0].loading).toBe(false)
|
|
expect(result.current[0].error).toBe(error)
|
|
})
|
|
|
|
it('should refetch', async () => {
|
|
axios.mockResolvedValue({ data: 'whatever' })
|
|
|
|
const { result, waitForNextUpdate } = setup('')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
act(() => {
|
|
result.current[1]()
|
|
})
|
|
|
|
expect(result.current[0].loading).toBe(true)
|
|
expect(axios).toHaveBeenCalledTimes(2)
|
|
|
|
await waitForNextUpdate()
|
|
})
|
|
|
|
it('should return the same reference to the fetch function', async () => {
|
|
axios.mockResolvedValue({ data: 'whatever' })
|
|
|
|
const { result, rerender, waitForNextUpdate } = setup('')
|
|
|
|
const firstRefetch = result.current[1]
|
|
|
|
rerender()
|
|
|
|
expect(result.current[1]).toBe(firstRefetch)
|
|
|
|
await waitForNextUpdate()
|
|
})
|
|
|
|
it('should return the cached response on a new render', async () => {
|
|
const response = { data: 'whatever' }
|
|
|
|
axios.mockResolvedValueOnce(response)
|
|
|
|
await setup('').waitForNextUpdate()
|
|
|
|
const { result } = setup('')
|
|
|
|
expect(result.current[0]).toEqual({
|
|
loading: false,
|
|
error: null,
|
|
data: response.data,
|
|
response
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('request cancellation', () => {
|
|
describe('effect-generated requests', () => {
|
|
it('should provide the cancel token to axios', async () => {
|
|
axios.mockResolvedValueOnce({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate } = setup('')
|
|
|
|
expect(axios).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
cancelToken: token
|
|
})
|
|
)
|
|
|
|
await waitForNextUpdate()
|
|
})
|
|
|
|
it('should cancel the outstanding request when the component unmounts', async () => {
|
|
axios.mockResolvedValueOnce({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, unmount } = setup('')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
unmount()
|
|
|
|
expect(cancel).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should cancel the outstanding request when the cancel method is called', async () => {
|
|
axios.mockResolvedValue({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, result } = setup('')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
result.current[2]()
|
|
|
|
expect(cancel).toHaveBeenCalledTimes(1)
|
|
expect(result.current[0].loading).toBe(false)
|
|
})
|
|
|
|
it('should cancel the outstanding request when the component refetches due to a rerender', async () => {
|
|
axios.mockResolvedValue({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, rerender } = setup('initial config')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
rerender({ config: 'new config', options: {} })
|
|
|
|
expect(cancel).toHaveBeenCalled()
|
|
|
|
await waitForNextUpdate()
|
|
})
|
|
|
|
it('should not cancel the outstanding request when the component rerenders with same string config', async () => {
|
|
axios.mockResolvedValue({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, rerender } = setup('initial config')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
rerender()
|
|
|
|
expect(cancel).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should not cancel the outstanding request when the component rerenders with same object config', async () => {
|
|
axios.mockResolvedValue({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, rerender } = setup({ some: 'config' })
|
|
|
|
await waitForNextUpdate()
|
|
|
|
rerender()
|
|
|
|
expect(cancel).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should not cancel the outstanding request when the component rerenders with equal string config', async () => {
|
|
axios.mockResolvedValue({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, rerender } = setup('initial config', {})
|
|
|
|
await waitForNextUpdate()
|
|
|
|
rerender({ config: 'initial config', options: {} })
|
|
|
|
expect(cancel).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should not cancel the outstanding request when the component rerenders with equal object config', async () => {
|
|
axios.mockResolvedValue({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, rerender } = setup({ some: 'config' }, {})
|
|
|
|
await waitForNextUpdate()
|
|
|
|
rerender({ config: { some: 'config' }, options: {} })
|
|
|
|
expect(cancel).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should cancel the outstanding request when the cancel method is called after the component rerenders with same config', async () => {
|
|
axios.mockResolvedValue({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, rerender, result } = setup('initial config')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
rerender()
|
|
|
|
result.current[2]()
|
|
|
|
expect(cancel).toHaveBeenCalled()
|
|
expect(result.current[0].loading).toBe(false)
|
|
})
|
|
|
|
it('should not dispatch an error when the request is canceled', async () => {
|
|
const cancellation = new Error('canceled')
|
|
|
|
axios.mockRejectedValueOnce(cancellation)
|
|
axios.isCancel = jest
|
|
.fn()
|
|
.mockImplementationOnce(err => err === cancellation)
|
|
|
|
const { result, waitForNextUpdate } = setup('')
|
|
|
|
await waitForNextUpdate()
|
|
expect(result.current[0].error).toBeNull()
|
|
})
|
|
|
|
it('should return previous state after cancel', async () => {
|
|
const response = { data: 'whatever' }
|
|
|
|
const cancellation = new Error('canceled')
|
|
|
|
axios.isCancel = jest
|
|
.fn()
|
|
.mockImplementationOnce(err => err === cancellation)
|
|
|
|
axios
|
|
.mockResolvedValueOnce(response)
|
|
.mockRejectedValueOnce(cancellation)
|
|
|
|
const { result, waitForNextUpdate, rerender } = setup('', {
|
|
manual: true
|
|
})
|
|
|
|
act(() => {
|
|
result.current[1]()
|
|
})
|
|
|
|
await waitForNextUpdate()
|
|
|
|
rerender({ config: 'test', options: { manual: false } })
|
|
|
|
result.current[2]()
|
|
|
|
await waitForNextUpdate()
|
|
|
|
expect(axios).toHaveBeenCalledTimes(2)
|
|
expect(result.current[0].error).toBeNull()
|
|
expect(result.current[0].loading).toBe(false)
|
|
expect(result.current[0].response).toBe(response)
|
|
})
|
|
})
|
|
|
|
describe('manual refetches', () => {
|
|
it('should provide the cancel token to axios', async () => {
|
|
const { result, waitForNextUpdate } = setup('', { manual: true })
|
|
|
|
axios.mockResolvedValueOnce({ data: 'whatever' })
|
|
|
|
act(() => {
|
|
result.current[1]()
|
|
})
|
|
|
|
expect(axios).toHaveBeenCalledTimes(1)
|
|
|
|
expect(axios).toHaveBeenLastCalledWith(
|
|
expect.objectContaining({
|
|
cancelToken: token
|
|
})
|
|
)
|
|
|
|
await waitForNextUpdate()
|
|
})
|
|
|
|
it('should cancel the outstanding manual refetch when the component unmounts', async () => {
|
|
const { result, waitForNextUpdate, unmount } = setup('', {
|
|
manual: true
|
|
})
|
|
|
|
axios.mockResolvedValueOnce({ data: 'whatever' })
|
|
|
|
act(() => {
|
|
result.current[1]()
|
|
})
|
|
|
|
await waitForNextUpdate()
|
|
|
|
unmount()
|
|
|
|
expect(cancel).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should cancel the outstanding manual refetch when the component refetches', async () => {
|
|
axios.mockResolvedValue({ data: 'whatever' })
|
|
|
|
const { result, waitForNextUpdate, rerender } = setup('')
|
|
|
|
act(() => {
|
|
result.current[1]()
|
|
})
|
|
|
|
await waitForNextUpdate()
|
|
|
|
rerender({ config: 'new config', options: {} })
|
|
|
|
expect(cancel).toHaveBeenCalled()
|
|
|
|
await waitForNextUpdate()
|
|
})
|
|
|
|
it('should cancel the outstanding manual refetch when the cancel method is called', async () => {
|
|
axios.mockResolvedValue({ data: 'whatever' })
|
|
|
|
const { result, waitForNextUpdate } = setup('', { manual: true })
|
|
|
|
act(() => {
|
|
result.current[1]()
|
|
})
|
|
|
|
await waitForNextUpdate()
|
|
|
|
result.current[2]()
|
|
|
|
expect(cancel).toHaveBeenCalledTimes(1)
|
|
expect(result.current[0].loading).toBe(false)
|
|
})
|
|
|
|
it('should cancel manual request when the config options change', async () => {
|
|
axios.mockResolvedValue({ data: 'whatever' })
|
|
|
|
const { result, waitForNextUpdate, rerender } = setup('', {
|
|
manual: true
|
|
})
|
|
|
|
act(() => {
|
|
result.current[1]()
|
|
})
|
|
|
|
rerender({ config: 'new url', options: { manual: true } })
|
|
|
|
await waitForNextUpdate()
|
|
|
|
expect(result.current[0].loading).toBe(false)
|
|
expect(cancel).toHaveBeenCalledTimes(1)
|
|
expect(axios).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should throw an error when the request is canceled', async () => {
|
|
const cancellation = new Error('canceled')
|
|
|
|
axios.mockRejectedValueOnce(cancellation)
|
|
axios.isCancel = jest
|
|
.fn()
|
|
.mockImplementationOnce(err => err === cancellation)
|
|
|
|
const { result } = renderHook(() => useAxios('', { manual: true }))
|
|
|
|
expect(() => act(result.current[1])).rejects.toBe(cancellation)
|
|
})
|
|
|
|
it('should return response from cache in hook results', async () => {
|
|
const response = { data: 'whatever' }
|
|
|
|
axios.mockResolvedValueOnce(response)
|
|
|
|
// first component renders and stores results in cache
|
|
await setup('').waitForNextUpdate()
|
|
|
|
const { result } = setup('', { manual: true })
|
|
|
|
// no results on first render as it's a manual request
|
|
expect(result.current[0]).toEqual({ loading: false, error: null })
|
|
|
|
// refetch using cache
|
|
act(() => {
|
|
result.current[1]({}, { useCache: true })
|
|
})
|
|
|
|
expect(result.current[0]).toEqual({
|
|
loading: false,
|
|
error: null,
|
|
response,
|
|
data: response.data
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('refetch', () => {
|
|
describe('when axios resolves', () => {
|
|
it('should resolve to the response by default', async () => {
|
|
const response = { data: 'whatever' }
|
|
|
|
axios.mockResolvedValue(response)
|
|
|
|
const {
|
|
result: {
|
|
current: [, refetch]
|
|
},
|
|
waitForNextUpdate
|
|
} = setup('')
|
|
|
|
act(() => {
|
|
expect(refetch()).resolves.toEqual(response)
|
|
})
|
|
|
|
await waitForNextUpdate()
|
|
|
|
expect(axios).toHaveBeenCalledTimes(2)
|
|
})
|
|
|
|
it('should resolve to the response when using cache', async () => {
|
|
const response = { data: 'whatever' }
|
|
|
|
axios.mockResolvedValue(response)
|
|
|
|
const {
|
|
result: {
|
|
current: [, refetch]
|
|
},
|
|
waitForNextUpdate
|
|
} = setup('')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
act(() => {
|
|
expect(refetch({}, { useCache: true })).resolves.toEqual(response)
|
|
})
|
|
|
|
expect(axios).toHaveBeenCalledTimes(1)
|
|
})
|
|
})
|
|
|
|
describe('when axios rejects', () => {
|
|
it('should reject with the error by default', async () => {
|
|
const error = new Error('boom')
|
|
|
|
axios.mockRejectedValue(error)
|
|
|
|
const {
|
|
result: {
|
|
current: [, refetch]
|
|
},
|
|
waitForNextUpdate
|
|
} = setup('')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
act(() => {
|
|
expect(refetch()).rejects.toEqual(error)
|
|
})
|
|
|
|
await waitForNextUpdate()
|
|
})
|
|
|
|
it('should reject with the error and skip cache even when using cache', async () => {
|
|
const error = new Error('boom')
|
|
|
|
axios.mockRejectedValue(error)
|
|
|
|
const {
|
|
result: {
|
|
current: [, refetch]
|
|
},
|
|
waitForNextUpdate
|
|
} = setup('')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
act(() => {
|
|
expect(refetch({}, { useCache: true })).rejects.toEqual(error)
|
|
})
|
|
|
|
await waitForNextUpdate()
|
|
})
|
|
})
|
|
|
|
describe('configuration override handling', () => {
|
|
it('should override url', async () => {
|
|
const response = { data: 'whatever' }
|
|
|
|
axios.mockResolvedValue(response)
|
|
|
|
const {
|
|
result: {
|
|
current: [, refetch]
|
|
},
|
|
waitForNextUpdate
|
|
} = setup('some url')
|
|
|
|
act(() => {
|
|
expect(refetch('some other url')).resolves.toEqual(response)
|
|
})
|
|
|
|
await waitForNextUpdate()
|
|
|
|
expect(axios).toHaveBeenNthCalledWith(
|
|
1,
|
|
expect.objectContaining({ url: 'some url' })
|
|
)
|
|
expect(axios).toHaveBeenNthCalledWith(
|
|
2,
|
|
expect.objectContaining({ url: 'some other url' })
|
|
)
|
|
})
|
|
|
|
it('should merge with the existing configuration', async () => {
|
|
const response = { data: 'whatever' }
|
|
|
|
axios.mockResolvedValue(response)
|
|
|
|
const {
|
|
result: {
|
|
current: [, refetch]
|
|
},
|
|
waitForNextUpdate
|
|
} = setup('some url')
|
|
|
|
act(() => {
|
|
expect(refetch({ params: { some: 'param' } })).resolves.toEqual(
|
|
response
|
|
)
|
|
})
|
|
|
|
await waitForNextUpdate()
|
|
|
|
expect(axios).toHaveBeenNthCalledWith(
|
|
1,
|
|
expect.objectContaining({ url: 'some url' })
|
|
)
|
|
expect(axios).toHaveBeenNthCalledWith(
|
|
2,
|
|
expect.objectContaining({
|
|
url: 'some url',
|
|
params: { some: 'param' }
|
|
})
|
|
)
|
|
})
|
|
|
|
it('should ignore config override if it is an event', async () => {
|
|
const response = { data: 'whatever' }
|
|
|
|
axios.mockResolvedValue(response)
|
|
|
|
const {
|
|
result: {
|
|
current: [, refetch]
|
|
},
|
|
waitForNextUpdate
|
|
} = setup('some url')
|
|
|
|
const handleClick = jest.fn(e => e.persist())
|
|
|
|
fireEvent.click(
|
|
render(<button onClick={handleClick} />).getByRole('button')
|
|
)
|
|
|
|
const [[clickEvent]] = handleClick.mock.calls
|
|
|
|
act(() => {
|
|
expect(refetch(clickEvent)).resolves.toEqual(response)
|
|
})
|
|
|
|
await waitForNextUpdate()
|
|
|
|
expect(axios).toHaveBeenNthCalledWith(
|
|
1,
|
|
expect.objectContaining({ url: 'some url' })
|
|
)
|
|
expect(axios).toHaveBeenNthCalledWith(
|
|
2,
|
|
expect.objectContaining({ url: 'some url' })
|
|
)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('manual option', () => {
|
|
it('should set loading to false on initial render', async () => {
|
|
const { result } = setup('', { manual: true })
|
|
|
|
expect(result.current[0].loading).toBe(false)
|
|
})
|
|
|
|
it('should not execute request immediately', () => {
|
|
setup('', { manual: true })
|
|
|
|
expect(axios).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should execute request manually and skip cache by default', async () => {
|
|
const { result, waitForNextUpdate } = setup('', { manual: true })
|
|
|
|
axios.mockResolvedValueOnce({ data: 'whatever' })
|
|
|
|
act(() => {
|
|
result.current[1]()
|
|
})
|
|
|
|
expect(result.current[0].loading).toBe(true)
|
|
|
|
await waitForNextUpdate()
|
|
|
|
expect(result.current[0].loading).toBe(false)
|
|
expect(result.current[0].data).toBe('whatever')
|
|
|
|
expect(axios).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should allow using the cache', async () => {
|
|
const { result, waitForNextUpdate } = setup('', { manual: true })
|
|
|
|
const response = { data: 'whatever' }
|
|
axios.mockResolvedValueOnce(response)
|
|
|
|
// first manual request
|
|
act(() => {
|
|
result.current[1]()
|
|
})
|
|
|
|
await waitForNextUpdate()
|
|
|
|
// cache hit
|
|
act(() => {
|
|
result.current[1]({}, { useCache: true })
|
|
})
|
|
|
|
expect(result.current[0]).toEqual({
|
|
loading: false,
|
|
error: null,
|
|
response,
|
|
data: response.data
|
|
})
|
|
|
|
expect(axios).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should not return response even if there is a cached one', async () => {
|
|
axios.mockResolvedValueOnce({ data: 'whatever' })
|
|
|
|
await setup('').waitForNextUpdate()
|
|
|
|
const { result } = setup('', { manual: true })
|
|
|
|
expect(result.current[0]).toEqual({ loading: false, error: null })
|
|
})
|
|
})
|
|
|
|
describe('useCache option', () => {
|
|
it('should use local state across rerenders', async () => {
|
|
axios.mockResolvedValueOnce({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, rerender } = setup('')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
rerender()
|
|
|
|
expect(axios).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should not hit network across component mounts by default', async () => {
|
|
axios.mockResolvedValueOnce({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, unmount } = setup('')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
unmount()
|
|
|
|
setup('')
|
|
|
|
expect(axios).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should use local state across rerenders even when cache disabled', async () => {
|
|
axios.mockResolvedValueOnce({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, rerender } = setup('', { useCache: false })
|
|
|
|
await waitForNextUpdate()
|
|
|
|
rerender()
|
|
|
|
expect(axios).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should hit network across component mounts when cache disabled', async () => {
|
|
axios.mockResolvedValue({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, unmount } = setup('', { useCache: false })
|
|
|
|
await waitForNextUpdate()
|
|
|
|
unmount()
|
|
|
|
await setup('', { useCache: false }).waitForNextUpdate()
|
|
|
|
expect(axios).toHaveBeenCalledTimes(2)
|
|
})
|
|
|
|
it('should store result in cache even when disabled', async () => {
|
|
axios.mockResolvedValue({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, unmount } = setup('', { useCache: false })
|
|
|
|
await waitForNextUpdate()
|
|
|
|
unmount()
|
|
|
|
setup('')
|
|
|
|
expect(axios).toHaveBeenCalledTimes(1)
|
|
})
|
|
})
|
|
|
|
describe('option change detection', () => {
|
|
it('manual:false to manual:true with cache disabled should execute first request only', async () => {
|
|
axios.mockResolvedValue({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, rerender } = setup('', {
|
|
manual: false,
|
|
useCache: false
|
|
})
|
|
|
|
await waitForNextUpdate()
|
|
|
|
rerender({ config: '', options: { manual: true } })
|
|
|
|
expect(axios).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('manual:true to manual:false with cache disabled should execute second request only', async () => {
|
|
axios.mockResolvedValue({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, rerender } = setup('', {
|
|
manual: true,
|
|
useCache: false
|
|
})
|
|
|
|
rerender({ config: '', options: { manual: false } })
|
|
|
|
await waitForNextUpdate()
|
|
|
|
expect(axios).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('useCache:true to useCache:false should execute both requests', async () => {
|
|
axios.mockResolvedValue({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, rerender } = setup('', {
|
|
useCache: true
|
|
})
|
|
|
|
await waitForNextUpdate()
|
|
|
|
rerender({ config: '', options: { useCache: false } })
|
|
|
|
await waitForNextUpdate()
|
|
|
|
expect(axios).toHaveBeenCalledTimes(2)
|
|
})
|
|
|
|
it('useCache:false to useCache:true should execute first request only', async () => {
|
|
axios.mockResolvedValue({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, rerender } = setup('', {
|
|
useCache: false
|
|
})
|
|
|
|
await waitForNextUpdate()
|
|
|
|
rerender({ config: '', options: { useCache: true } })
|
|
|
|
expect(axios).toHaveBeenCalledTimes(1)
|
|
})
|
|
})
|
|
|
|
describe('configure', () => {
|
|
afterEach(() => resetConfigure())
|
|
|
|
it('should provide a custom implementation of axios', () => {
|
|
const mockAxios = jest.fn().mockReturnValueOnce({ data: 'whatever' })
|
|
|
|
configure({ axios: mockAxios })
|
|
|
|
const { waitForNextUpdate } = setup('')
|
|
|
|
expect(mockAxios).toHaveBeenCalled()
|
|
|
|
return waitForNextUpdate()
|
|
})
|
|
|
|
describe('with globally disabled cache', () => {
|
|
it('should use local state across rerenders', async () => {
|
|
configure({ cache: false })
|
|
|
|
axios.mockResolvedValueOnce({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, rerender } = setup('')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
rerender()
|
|
|
|
expect(axios).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should hit network across component mounts', async () => {
|
|
configure({ cache: false })
|
|
|
|
axios.mockResolvedValue({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, unmount } = setup('')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
unmount()
|
|
|
|
await setup('').waitForNextUpdate()
|
|
|
|
expect(axios).toHaveBeenCalledTimes(2)
|
|
})
|
|
})
|
|
|
|
describe('default hook options', () => {
|
|
describe('manual', () => {
|
|
it('should override default manual option', () => {
|
|
configure({ defaultOptions: { manual: true } })
|
|
|
|
setup('')
|
|
|
|
expect(axios).not.toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
describe('useCache', () => {
|
|
it('should override default useCache option', async () => {
|
|
configure({ defaultOptions: { useCache: false } })
|
|
|
|
axios.mockResolvedValue({ data: 'whatever' })
|
|
|
|
const { waitForNextUpdate, unmount } = setup('')
|
|
|
|
await waitForNextUpdate()
|
|
|
|
unmount()
|
|
|
|
await setup('').waitForNextUpdate()
|
|
|
|
expect(axios).toHaveBeenCalledTimes(2)
|
|
})
|
|
})
|
|
|
|
describe('ssr', () => {
|
|
it('should be able to set ssr option', () => {
|
|
configure({ defaultOptions: { ssr: false } })
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('loadCache', () => {
|
|
it('should load cache', () => {
|
|
loadCache({ some: 'data' })
|
|
})
|
|
})
|
|
|
|
describe('serializeCache', () => {
|
|
it('should serialize cache', () => {
|
|
serializeCache()
|
|
})
|
|
})
|
|
}
|