text: 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()
      })
    })
  }
  