refactor: Add tests to check that typescript definitions are correct

Fixes #23
This commit is contained in:
Simone Busoli 2019-08-05 19:48:20 +02:00
parent 3adb87e440
commit 076f89a267
9 changed files with 521 additions and 842 deletions

2
.gitignore vendored
View File

@ -2,3 +2,5 @@ es
lib
node_modules
npm-debug.log*
src/index.d.ts
*.test.ts

9
axios.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
import { AxiosStatic } from "axios";
declare module "axios" {
interface AxiosStatic {
mockResolvedValue: Function;
mockResolvedValueOnce: Function;
mockRejectedValue: Function;
}
}

31
index.d.ts vendored
View File

@ -1,17 +1,31 @@
import { AxiosRequestConfig, AxiosError, AxiosPromise } from 'axios'
import {
AxiosRequestConfig,
AxiosError,
AxiosPromise,
AxiosStatic,
AxiosInstance,
AxiosResponse
} from "axios";
import LRUCache from "lru-cache";
interface ResponseValues<T> {
data: T
loading: boolean
error?: AxiosError
data: T;
loading: boolean;
error?: AxiosError;
response?: AxiosResponse;
}
interface Options {
manual: boolean
manual?: boolean;
}
interface RefetchOptions {
useCache: boolean
useCache?: boolean;
}
interface ConfigureOptions {
axios?: AxiosInstance | AxiosStatic | any;
cache?: LRUCache<any, any>;
}
export default function useAxios<T = any>(
@ -20,4 +34,7 @@ export default function useAxios<T = any>(
): [
ResponseValues<T>,
(config?: AxiosRequestConfig, options?: RefetchOptions) => void
]
];
export function configure(options: ConfigureOptions): void;
export function resetConfigure(): void;

View File

@ -1,185 +1,17 @@
// For a detailed explanation regarding each configuration property, visit:
// https://jestjs.io/docs/en/configuration.html
const projects = [
{
clearMocks: true,
coverageDirectory: 'coverage',
testMatch: ['**/?(*.)+(spec|test).js?(x)']
},
{
clearMocks: true,
coverageDirectory: 'coverage',
testMatch: ['**/?(*.)+(spec|test).ts?(x)'],
preset: 'ts-jest/presets/js-with-ts'
}
]
module.exports = {
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// Respect "browser" field in package.json when resolving modules
// browser: false,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "C:\\Users\\simone\\AppData\\Local\\Temp\\jest",
// Automatically clear mock calls and instances between every test
clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
// collectCoverage: false,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: null,
// The directory where Jest should output its coverage files
coverageDirectory: 'coverage'
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "\\\\node_modules\\\\"
// ],
// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: null,
// A path to a custom dependency extractor
// dependencyExtractor: null,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// Force coverage collection from ignored files using an array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: null,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: null,
// A set of global variables that need to be available in all test environments
// globals: {},
// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: [
// "node_modules"
// ],
// An array of file extensions your modules use
// moduleFileExtensions: [
// "js",
// "json",
// "jsx",
// "ts",
// "tsx",
// "node"
// ],
// A map from regular expressions to module names that allow to stub out resources with a single module
// moduleNameMapper: {},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
// preset: null,
// Run tests from one or more projects
// projects: null,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state between every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: null,
// Automatically restore mock state between every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: null,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
// setupFilesAfterEnv: [],
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
// testEnvironment: "jest-environment-jsdom",
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
// testMatch: [
// "**/__tests__/**/*.[jt]s?(x)",
// "**/?(*.)+(spec|test).[tj]s?(x)"
// ],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "\\\\node_modules\\\\"
// ],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: null,
// This option allows use of a custom test runner
// testRunner: "jasmine2",
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
// testURL: "http://localhost",
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
// timers: "real",
// A map from regular expressions to paths to transformers
// transform: null,
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// "\\\\node_modules\\\\"
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: null,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
projects
}

819
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,8 @@
"prepare": "npm run clean && npm run build",
"prepublishOnly": "npm run build",
"release": "standard-version",
"test": "jest"
"pretest": "cp ./src/index.test.js ./src/index.test.ts && cp ./index.d.ts ./src",
"test": "jest --no-cache"
},
"dependencies": {
"@babel/runtime": "^7.5.5",
@ -47,6 +48,11 @@
"@babel/plugin-transform-runtime": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@testing-library/react-hooks": "^1.1.0",
"@types/jest": "^24.0.16",
"@types/lru-cache": "^5.1.0",
"@types/node": "^12.6.9",
"@types/react": "^16.8.24",
"@types/react-dom": "^16.8.5",
"axios": "^0.18.0",
"babel-eslint": "^10.0.2",
"cross-env": "^5.2.0",
@ -64,7 +70,9 @@
"react-dom": "^16.8.6",
"react-test-renderer": "^16.8.6",
"rimraf": "^2.6.3",
"standard-version": "^6.0.1"
"standard-version": "^6.0.1",
"ts-jest": "^24.0.2",
"typescript": "^3.5.3"
},
"husky": {
"hooks": {

View File

@ -9,8 +9,15 @@ const actions = {
const ssrPromises = []
let cache = new LRU()
let axiosInstance = DefaultAxios
let cache
let axiosInstance
export function resetConfigure() {
cache = new LRU()
axiosInstance = DefaultAxios
}
resetConfigure()
export function configure(options) {
if (options.axios) {

View File

@ -1,125 +1,152 @@
import { renderHook, act } from '@testing-library/react-hooks'
import axios from 'axios'
import useAxios from './'
jest.mock('axios')
it('should set loading to true', async () => {
const { result } = renderHook(() => useAxios())
expect(result.current[0].loading).toBe(true)
})
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 reset error when request completes and returns data', async () => {
axios.mockResolvedValueOnce({ data: 'whatever' })
const { result, waitForNextUpdate } = renderHook(() => useAxios())
result.current[0].error = new Error()
await waitForNextUpdate()
// Refetch
act(() => {
result.current[1]()
})
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.mockRejectedValue(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)
})
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) })
)
})
})
import { renderHook, act } from '@testing-library/react-hooks'
import axios from 'axios'
import useAxios, { configure, resetConfigure } from './'
jest.mock('axios')
it('should set loading to true', async () => {
const { result } = renderHook(() => useAxios(''))
expect(result.current[0].loading).toBe(true)
})
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 () => {
axios.mockResolvedValueOnce({ data: 'whatever' })
const { result, waitForNextUpdate } = renderHook(() => useAxios(''))
result.current[0].error = { config: {}, name: '', message: '' }
await waitForNextUpdate()
// Refetch
act(() => {
result.current[1]()
})
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.mockRejectedValue(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)
})
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('configure', () => {
afterEach(() => resetConfigure())
it('should provide a custom implementation of axios', () => {
const mockAxios = jest.fn()
configure({ axios: mockAxios })
renderHook(() => useAxios(''))
expect(mockAxios).toHaveBeenCalled()
})
})

6
tsconfig.json Normal file
View File

@ -0,0 +1,6 @@
{
"compilerOptions": {
"allowJs": true,
"esModuleInterop": true
}
}