diff --git a/.prettierrc b/.prettierrc index b2095be8..f79acadc 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,6 @@ { "semi": false, - "singleQuote": true + "singleQuote": true, + "trailingComma": "none", + "arrowParens": "avoid" } diff --git a/README.md b/README.md index dac3beb5..b49ad1be 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ Returns: Allows to provide custom instances of cache and axios. -- `cache` An instance of [lru-cache](https://github.com/isaacs/node-lru-cache) +- `cache` An instance of [lru-cache](https://github.com/isaacs/node-lru-cache), or `false` to disable the cache - `axios` An instance of [axios](https://github.com/axios/axios#creating-an-instance) ### serializeCache() @@ -114,7 +114,7 @@ Populates the cache with serialized data generated by `serializeCache`. Creates an instance of the `useAxios` hook configured with the supplied cache and axios instance. -- `cache` An instance of [lru-cache](https://github.com/isaacs/node-lru-cache) +- `cache` An instance of [lru-cache](https://github.com/isaacs/node-lru-cache), or `false` to disable the cache - `axios` An instance of [axios](https://github.com/axios/axios#creating-an-instance) Returns: @@ -137,10 +137,11 @@ Unless provided via the `configure` function, `axios-hooks` uses as defaults: - `axios` - the default `axios` package export - `cache` - a new instance of the default `lru-cache` package export, with no arguments -These defaults may not suit your needs: +These defaults may not suit your needs, for example: - you may want a common base url for axios requests - the default (Infinite) cache size may not be a sensible default +- you want to disable caching altogether In such cases you can use the `configure` function to provide your custom implementation of both. diff --git a/package.json b/package.json index 693b5b5e..854f6ec2 100644 --- a/package.json +++ b/package.json @@ -88,8 +88,7 @@ }, "lint-staged": { "src/**/*.{js,md}": [ - "prettier --write", - "git add" + "eslint --fix" ] }, "config": { diff --git a/src/index.d.ts b/src/index.d.ts index 8dc3fb39..24125278 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -26,7 +26,7 @@ export interface RefetchOptions { export interface ConfigureOptions { axios?: AxiosInstance | AxiosStatic | any - cache?: LRUCache + cache?: LRUCache | false } export interface UseAxios { diff --git a/src/index.js b/src/index.js index 3d24a258..c4b66a3c 100644 --- a/src/index.js +++ b/src/index.js @@ -32,18 +32,17 @@ export function makeUseAxios(configurationOptions) { axiosInstance = StaticAxios } - resetConfigure() - function configure(options = {}) { - if (options.axios) { + if (options.axios !== undefined) { axiosInstance = options.axios } - if (options.cache) { + if (options.cache !== undefined) { cache = options.cache } } + resetConfigure() configure(configurationOptions) function loadCache(data) { @@ -140,7 +139,7 @@ export function makeUseAxios(configurationOptions) { } function executeRequest(config, options, dispatch) { - if (options.useCache) { + if (cache && options.useCache) { return executeRequestWithCache(config, dispatch) } diff --git a/test/index.test.js b/test/index.test.js index e794c576..4aae8d44 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -1,516 +1,544 @@ -import { renderHook, act } from '@testing-library/react-hooks' -import axios from 'axios' - -import useAxiosStatic, { - configure as configureStatic, - resetConfigure as resetConfigureStatic, - 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.length).toBe(0) -}) - -describe('useAxiosStatic', () => { - standardTests(useAxiosStatic, configureStatic, resetConfigureStatic) -}) - -describe('makeUseAxios', () => { - it('should be a function', () => { - expect(makeUseAxios).toBeInstanceOf(Function) - }) - - it('should not throw', () => { - expect(makeUseAxios({})).toBeTruthy() - }) - - describe('standard tests', () => { - const useAxios = makeUseAxios({}) - - standardTests(useAxios, useAxios.configure, useAxios.resetConfigure) - - describe('with custom configuration', () => { - const useAxios = makeUseAxios({ axios }) - - standardTests(useAxios, useAxios.configure, useAxios.resetConfigure) - }) - }) - - describe('custom tests', () => { - it('should use the provided axios instance', () => { - const mockAxios = jest.fn().mockResolvedValueOnce({ data: 'whatever' }) - - const useAxios = makeUseAxios({ axios: mockAxios }) - - const { waitForNextUpdate } = renderHook(() => useAxios('')) - - expect(mockAxios).toHaveBeenCalled() - - return waitForNextUpdate() - }) - }) -}) - -function standardTests(useAxios, configure, resetConfigure) { - describe('basic functionality', () => { - it('should set loading to true', async () => { - axios.mockResolvedValueOnce({ data: 'whatever' }) - - const { result, waitForNextUpdate } = renderHook(() => useAxios('')) - - expect(result.current[0].loading).toBe(true) - - await waitForNextUpdate() - }) - - it('should set loading to false when request completes and returns data', async () => { - axios.mockResolvedValueOnce({ data: 'whatever' }) - - const { result, waitForNextUpdate } = renderHook(() => useAxios('')) - - await waitForNextUpdate() - - expect(result.current[0].loading).toBe(false) - expect(result.current[0].data).toBe('whatever') - }) - - it('should set the response', async () => { - const response = { data: 'whatever' } - - axios.mockResolvedValueOnce(response) - - const { result, waitForNextUpdate } = renderHook(() => useAxios('')) - - await waitForNextUpdate() - - expect(result.current[0].loading).toBe(false) - expect(result.current[0].response).toBe(response) - }) - - it('should reset error when request completes and returns data', async () => { - const error = new Error('boom') - - axios.mockRejectedValueOnce(error) - - const { result, waitForNextUpdate } = renderHook(() => useAxios('')) - - 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 } = renderHook(() => useAxios('')) - - 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 } = renderHook(() => useAxios('')) - - 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 } = renderHook(() => - useAxios('') - ) - - const firstRefetch = result.current[1] - - rerender() - - expect(result.current[1]).toBe(firstRefetch) - - await waitForNextUpdate() - }) - }) - - describe('request cancellation', () => { - describe('effect-generated requests', () => { - it('should provide the cancel token to axios', async () => { - axios.mockResolvedValueOnce({ data: 'whatever' }) - - const { waitForNextUpdate } = renderHook(() => useAxios('')) - - 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 } = renderHook(() => useAxios('')) - - await waitForNextUpdate() - - unmount() - - expect(cancel).toHaveBeenCalled() - }) - - it('should cancel the outstanding request when the component refetches due to a rerender', async () => { - axios.mockResolvedValue({ data: 'whatever' }) - - const { waitForNextUpdate, rerender } = renderHook( - props => useAxios(props), - { initialProps: 'initial config' } - ) - - await waitForNextUpdate() - - rerender('new config') - - expect(cancel).toHaveBeenCalled() - - await waitForNextUpdate() - }) - - it('should not cancel the outstanding request when the component rerenders with same config', async () => { - axios.mockResolvedValue({ data: 'whatever' }) - - const { waitForNextUpdate, rerender } = renderHook( - props => useAxios(props), - { initialProps: 'initial config' } - ) - - await waitForNextUpdate() - - rerender() - - expect(cancel).not.toHaveBeenCalled() - }) - - 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, wait } = renderHook(() => useAxios('')) - - // if we cancel we won't dispatch the error, hence there's no state update - // to wait for. yet, if we don't try to wait, we won't know if we're handling - // the error properly because the return value will not have the error until a - // state update happens. it would be great to have a better way to test this - await wait( - () => { - expect(result.current[0].error).toBeNull() - }, - { timeout: 1000, suppressErrors: false } - ) - }) - }) - - describe('manual refetches', () => { - it('should provide the cancel token to axios', async () => { - const { result, waitForNextUpdate } = renderHook(() => - useAxios('', { 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 } = renderHook(() => - useAxios('', { 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 } = renderHook( - config => useAxios(config), - { initialProps: '' } - ) - - act(() => { - result.current[1]() - }) - - await waitForNextUpdate() - - rerender('new config') - - expect(cancel).toHaveBeenCalled() - - await waitForNextUpdate() - }) - }) - }) - - 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 - } = renderHook(() => useAxios('')) - - act(() => { - expect(refetch()).resolves.toEqual(response) - }) - - await waitForNextUpdate() - }) - - it('should resolve to the response when using cache', async () => { - const response = { data: 'whatever' } - - axios.mockResolvedValue(response) - - const { - result: { - current: [, refetch] - }, - waitForNextUpdate - } = renderHook(() => useAxios('')) - - act(() => { - expect(refetch({}, { useCache: true })).resolves.toEqual(response) - }) - - await waitForNextUpdate() - }) - }) - - 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 - } = renderHook(() => useAxios('')) - - act(() => { - expect(refetch()).rejects.toEqual(error) - }) - - await waitForNextUpdate() - }) - - it('should reject with the error when using cache', async () => { - const error = new Error('boom') - - axios.mockRejectedValue(error) - - const { - result: { - current: [, refetch] - }, - waitForNextUpdate - } = renderHook(() => useAxios('')) - - act(() => { - expect(refetch({}, { useCache: true })).rejects.toEqual(error) - }) - - await waitForNextUpdate() - }) - }) - }) - - describe('manual option', () => { - it('should set loading to false', async () => { - const { result } = renderHook(() => useAxios('', { manual: true })) - - expect(result.current[0].loading).toBe(false) - }) - - it('should not execute request', () => { - renderHook(() => useAxios('', { manual: true })) - - expect(axios).not.toHaveBeenCalled() - }) - - it('should execute request manually and skip cache by default', async () => { - const { result, waitForNextUpdate } = renderHook(() => - useAxios('', { 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) - expect(axios).toHaveBeenCalledWith( - expect.not.objectContaining({ adapter: expect.any(Function) }) - ) - }) - - it('should allow using the cache', async () => { - const { result, waitForNextUpdate } = renderHook(() => - useAxios('', { manual: true }) - ) - - axios.mockResolvedValueOnce({ data: 'whatever' }) - - act(() => { - result.current[1]({}, { useCache: true }) - }) - - await waitForNextUpdate() - - expect(axios).toHaveBeenCalledTimes(1) - expect(axios).toHaveBeenCalledWith( - expect.objectContaining({ adapter: expect.any(Function) }) - ) - }) - }) - - describe('useCache option', () => { - it('should use cache by default', async () => { - axios.mockResolvedValueOnce({ data: 'whatever' }) - - const { waitForNextUpdate } = renderHook(() => useAxios('')) - - await waitForNextUpdate() - - expect(axios).toHaveBeenCalledTimes(1) - expect(axios).toHaveBeenCalledWith( - expect.objectContaining({ adapter: expect.any(Function) }) - ) - }) - - it('should allow disabling cache', async () => { - axios.mockResolvedValueOnce({ data: 'whatever' }) - - const { waitForNextUpdate } = renderHook(() => - useAxios('', { useCache: false }) - ) - - await waitForNextUpdate() - - expect(axios).toHaveBeenCalledTimes(1) - expect(axios).toHaveBeenCalledWith( - expect.not.objectContaining({ adapter: expect.any(Function) }) - ) - }) - }) - - describe('configure', () => { - afterEach(() => resetConfigure()) - - it('should provide a custom implementation of axios', () => { - const mockAxios = jest.fn().mockReturnValueOnce({ data: 'whatever' }) - - configure({ axios: mockAxios }) - - const { waitForNextUpdate } = renderHook(() => useAxios('')) - - expect(mockAxios).toHaveBeenCalled() - - return waitForNextUpdate() - }) - }) -} +import { renderHook, act } from '@testing-library/react-hooks' +import axios from 'axios' + +import useAxiosStatic, { + configure as configureStatic, + resetConfigure as resetConfigureStatic, + 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.length).toBe(0) +}) + +describe('useAxiosStatic', () => { + standardTests(useAxiosStatic, configureStatic, resetConfigureStatic) +}) + +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 useAxios = makeUseAxios({ axios: mockAxios }) + + const { waitForNextUpdate } = renderHook(() => useAxios('')) + + expect(mockAxios).toHaveBeenCalled() + + return waitForNextUpdate() + }) + + it('should allow to disable cache', async () => { + const useAxios = makeUseAxios({ cache: false }) + + axios.mockResolvedValueOnce({ data: 'whatever' }) + + const { waitForNextUpdate } = renderHook(() => useAxios('')) + + await waitForNextUpdate() + + expect(axios).toHaveBeenCalledTimes(1) + expect(axios).toHaveBeenCalledWith( + expect.not.objectContaining({ adapter: expect.any(Function) }) + ) + }) + + describe('standard tests', () => { + const useAxios = makeUseAxios({}) + + standardTests(useAxios, useAxios.configure, useAxios.resetConfigure) + + describe('with custom configuration', () => { + const useAxios = makeUseAxios({ axios }) + + standardTests(useAxios, useAxios.configure, useAxios.resetConfigure) + }) + }) +}) + +function standardTests(useAxios, configure, resetConfigure) { + describe('basic functionality', () => { + it('should set loading to true', async () => { + axios.mockResolvedValueOnce({ data: 'whatever' }) + + const { result, waitForNextUpdate } = renderHook(() => useAxios('')) + + expect(result.current[0].loading).toBe(true) + + await waitForNextUpdate() + }) + + it('should set loading to false when request completes and returns data', async () => { + axios.mockResolvedValueOnce({ data: 'whatever' }) + + const { result, waitForNextUpdate } = renderHook(() => useAxios('')) + + await waitForNextUpdate() + + expect(result.current[0].loading).toBe(false) + expect(result.current[0].data).toBe('whatever') + }) + + it('should set the response', async () => { + const response = { data: 'whatever' } + + axios.mockResolvedValueOnce(response) + + const { result, waitForNextUpdate } = renderHook(() => useAxios('')) + + await waitForNextUpdate() + + expect(result.current[0].loading).toBe(false) + expect(result.current[0].response).toBe(response) + }) + + it('should reset error when request completes and returns data', async () => { + const error = new Error('boom') + + axios.mockRejectedValueOnce(error) + + const { result, waitForNextUpdate } = renderHook(() => useAxios('')) + + 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 } = renderHook(() => useAxios('')) + + 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 } = renderHook(() => useAxios('')) + + 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 } = renderHook(() => + useAxios('') + ) + + const firstRefetch = result.current[1] + + rerender() + + expect(result.current[1]).toBe(firstRefetch) + + await waitForNextUpdate() + }) + }) + + describe('request cancellation', () => { + describe('effect-generated requests', () => { + it('should provide the cancel token to axios', async () => { + axios.mockResolvedValueOnce({ data: 'whatever' }) + + const { waitForNextUpdate } = renderHook(() => useAxios('')) + + 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 } = renderHook(() => useAxios('')) + + await waitForNextUpdate() + + unmount() + + expect(cancel).toHaveBeenCalled() + }) + + it('should cancel the outstanding request when the component refetches due to a rerender', async () => { + axios.mockResolvedValue({ data: 'whatever' }) + + const { waitForNextUpdate, rerender } = renderHook( + props => useAxios(props), + { initialProps: 'initial config' } + ) + + await waitForNextUpdate() + + rerender('new config') + + expect(cancel).toHaveBeenCalled() + + await waitForNextUpdate() + }) + + it('should not cancel the outstanding request when the component rerenders with same config', async () => { + axios.mockResolvedValue({ data: 'whatever' }) + + const { waitForNextUpdate, rerender } = renderHook( + props => useAxios(props), + { initialProps: 'initial config' } + ) + + await waitForNextUpdate() + + rerender() + + expect(cancel).not.toHaveBeenCalled() + }) + + 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, wait } = renderHook(() => useAxios('')) + + // if we cancel we won't dispatch the error, hence there's no state update + // to wait for. yet, if we don't try to wait, we won't know if we're handling + // the error properly because the return value will not have the error until a + // state update happens. it would be great to have a better way to test this + await wait( + () => { + expect(result.current[0].error).toBeNull() + }, + { timeout: 1000, suppressErrors: false } + ) + }) + }) + + describe('manual refetches', () => { + it('should provide the cancel token to axios', async () => { + const { result, waitForNextUpdate } = renderHook(() => + useAxios('', { 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 } = renderHook(() => + useAxios('', { 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 } = renderHook( + config => useAxios(config), + { initialProps: '' } + ) + + act(() => { + result.current[1]() + }) + + await waitForNextUpdate() + + rerender('new config') + + expect(cancel).toHaveBeenCalled() + + await waitForNextUpdate() + }) + }) + }) + + 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 + } = renderHook(() => useAxios('')) + + act(() => { + expect(refetch()).resolves.toEqual(response) + }) + + await waitForNextUpdate() + }) + + it('should resolve to the response when using cache', async () => { + const response = { data: 'whatever' } + + axios.mockResolvedValue(response) + + const { + result: { + current: [, refetch] + }, + waitForNextUpdate + } = renderHook(() => useAxios('')) + + act(() => { + expect(refetch({}, { useCache: true })).resolves.toEqual(response) + }) + + await waitForNextUpdate() + }) + }) + + 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 + } = renderHook(() => useAxios('')) + + act(() => { + expect(refetch()).rejects.toEqual(error) + }) + + await waitForNextUpdate() + }) + + it('should reject with the error when using cache', async () => { + const error = new Error('boom') + + axios.mockRejectedValue(error) + + const { + result: { + current: [, refetch] + }, + waitForNextUpdate + } = renderHook(() => useAxios('')) + + act(() => { + expect(refetch({}, { useCache: true })).rejects.toEqual(error) + }) + + await waitForNextUpdate() + }) + }) + }) + + describe('manual option', () => { + it('should set loading to false', async () => { + const { result } = renderHook(() => useAxios('', { manual: true })) + + expect(result.current[0].loading).toBe(false) + }) + + it('should not execute request', () => { + renderHook(() => useAxios('', { manual: true })) + + expect(axios).not.toHaveBeenCalled() + }) + + it('should execute request manually and skip cache by default', async () => { + const { result, waitForNextUpdate } = renderHook(() => + useAxios('', { 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) + expect(axios).toHaveBeenCalledWith( + expect.not.objectContaining({ adapter: expect.any(Function) }) + ) + }) + + it('should allow using the cache', async () => { + const { result, waitForNextUpdate } = renderHook(() => + useAxios('', { manual: true }) + ) + + axios.mockResolvedValueOnce({ data: 'whatever' }) + + act(() => { + result.current[1]({}, { useCache: true }) + }) + + await waitForNextUpdate() + + expect(axios).toHaveBeenCalledTimes(1) + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ adapter: expect.any(Function) }) + ) + }) + }) + + describe('useCache option', () => { + it('should use cache by default', async () => { + axios.mockResolvedValueOnce({ data: 'whatever' }) + + const { waitForNextUpdate } = renderHook(() => useAxios('')) + + await waitForNextUpdate() + + expect(axios).toHaveBeenCalledTimes(1) + expect(axios).toHaveBeenCalledWith( + expect.objectContaining({ adapter: expect.any(Function) }) + ) + }) + + it('should allow disabling cache', async () => { + axios.mockResolvedValueOnce({ data: 'whatever' }) + + const { waitForNextUpdate } = renderHook(() => + useAxios('', { useCache: false }) + ) + + await waitForNextUpdate() + + expect(axios).toHaveBeenCalledTimes(1) + expect(axios).toHaveBeenCalledWith( + expect.not.objectContaining({ adapter: expect.any(Function) }) + ) + }) + }) + + describe('configure', () => { + afterEach(() => resetConfigure()) + + it('should provide a custom implementation of axios', () => { + const mockAxios = jest.fn().mockReturnValueOnce({ data: 'whatever' }) + + configure({ axios: mockAxios }) + + const { waitForNextUpdate } = renderHook(() => useAxios('')) + + expect(mockAxios).toHaveBeenCalled() + + return waitForNextUpdate() + }) + + it('should allow to disable cache', async () => { + configure({ cache: false }) + + axios.mockResolvedValueOnce({ data: 'whatever' }) + + const { waitForNextUpdate } = renderHook(() => useAxios('')) + + await waitForNextUpdate() + + expect(axios).toHaveBeenCalledTimes(1) + expect(axios).toHaveBeenCalledWith( + expect.not.objectContaining({ adapter: expect.any(Function) }) + ) + }) + }) +} diff --git a/tsconfig.json b/tsconfig.json index 8cba1f23..c782a76c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { "allowJs": true, "esModuleInterop": true, - "jsx": "react" + "jsx": "react", + "outDir": "$$dummy$$" } }