-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: improve useAsync and useAsyncFn
- Loading branch information
Showing
7 changed files
with
334 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,22 @@ | ||
module.exports = { | ||
presets: ['@babel/preset-env', '@babel/preset-react', "@babel/preset-typescript"], | ||
env: { | ||
test: { | ||
plugins: ['dynamic-import-node'] | ||
}, | ||
production: { | ||
plugins: ['@babel/plugin-syntax-dynamic-import'] | ||
} | ||
presets: [ | ||
[ | ||
"@babel/preset-env", | ||
{ | ||
targets: { | ||
node: "current" | ||
} | ||
} | ||
], | ||
"@babel/preset-react", | ||
"@babel/preset-typescript" | ||
], | ||
env: { | ||
test: { | ||
plugins: ['dynamic-import-node'] | ||
}, | ||
production: { | ||
plugins: ['@babel/plugin-syntax-dynamic-import'] | ||
} | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -131,5 +131,9 @@ | |
"tslint --fix -t verbose", | ||
"git add" | ||
] | ||
}, | ||
"volta": { | ||
"node": "10.16.0", | ||
"yarn": "1.16.0" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
import { useCallback } from 'react'; | ||
import { cleanup, renderHook } from 'react-hooks-testing-library'; | ||
import useAsync from '../useAsync'; | ||
|
||
afterEach(cleanup); | ||
|
||
// NOTE: these tests cause console errors. | ||
// maybe we should test in a real environment instead | ||
// of a fake one? | ||
describe('useAsync', () => { | ||
it('should be defined', () => { | ||
expect(useAsync).toBeDefined(); | ||
}); | ||
|
||
describe('a success', () => { | ||
let hook; | ||
let callCount = 0; | ||
|
||
const resolver = async () => { | ||
return new Promise((resolve, reject) => { | ||
callCount++; | ||
|
||
const wait = setTimeout(() => { | ||
clearTimeout(wait); | ||
resolve('yay'); | ||
}, 0); | ||
}); | ||
}; | ||
|
||
beforeEach(() => { | ||
callCount = 0; | ||
hook = renderHook(({ fn }) => useAsync(fn, [fn]), { | ||
initialProps: { | ||
fn: resolver, | ||
}, | ||
}); | ||
}); | ||
|
||
it('initially starts loading', () => { | ||
expect(hook.result.current.loading).toEqual(true); | ||
}); | ||
|
||
it('resolves', async () => { | ||
expect.assertions(4); | ||
|
||
hook.rerender({ fn: resolver }); | ||
await hook.waitForNextUpdate(); | ||
|
||
expect(callCount).toEqual(1); | ||
expect(hook.result.current.loading).toBeFalsy(); | ||
expect(hook.result.current.value).toEqual('yay'); | ||
expect(hook.result.current.error).toEqual(undefined); | ||
}); | ||
}); | ||
|
||
describe('an error', () => { | ||
let hook; | ||
let callCount = 0; | ||
|
||
const rejection = async () => { | ||
return new Promise((resolve, reject) => { | ||
callCount++; | ||
|
||
const wait = setTimeout(() => { | ||
clearTimeout(wait); | ||
reject('yay'); | ||
}, 0); | ||
}); | ||
}; | ||
|
||
beforeEach(() => { | ||
callCount = 0; | ||
hook = renderHook(({ fn }) => useAsync(fn, [fn]), { | ||
initialProps: { | ||
fn: rejection, | ||
}, | ||
}); | ||
}); | ||
|
||
it('initially starts loading', () => { | ||
expect(hook.result.current.loading).toBeTruthy(); | ||
}); | ||
|
||
it('resolves', async () => { | ||
expect.assertions(4); | ||
|
||
hook.rerender({ fn: rejection }); | ||
await hook.waitForNextUpdate(); | ||
|
||
expect(callCount).toEqual(1); | ||
expect(hook.result.current.loading).toBeFalsy(); | ||
expect(hook.result.current.error).toEqual('yay'); | ||
expect(hook.result.current.value).toEqual(undefined); | ||
}); | ||
}); | ||
|
||
describe('re-evaluates when dependencies change', () => { | ||
describe('the fn is a dependency', () => { | ||
let hook; | ||
let callCount = 0; | ||
|
||
const initialFn = async () => { | ||
callCount++; | ||
return 'value'; | ||
}; | ||
|
||
const differentFn = async () => { | ||
callCount++; | ||
return 'new value'; | ||
}; | ||
|
||
beforeEach(() => { | ||
callCount = 0; | ||
hook = renderHook(({ fn }) => useAsync(fn, [fn]), { | ||
initialProps: { fn: initialFn }, | ||
}); | ||
}); | ||
|
||
it('renders the first value', () => { | ||
expect(hook.result.current.value).toEqual('value'); | ||
}); | ||
|
||
it('renders a different value when deps change', async () => { | ||
expect.assertions(3); | ||
|
||
expect(callCount).toEqual(1); | ||
|
||
hook.rerender({ fn: differentFn }); // change the fn to initiate new request | ||
await hook.waitForNextUpdate(); | ||
|
||
expect(callCount).toEqual(2); | ||
expect(hook.result.current.value).toEqual('new value'); | ||
}); | ||
}); | ||
|
||
describe('the additional dependencies list changes', () => { | ||
let callCount = 0; | ||
let hook; | ||
|
||
const staticFunction = async counter => { | ||
callCount++; | ||
return `counter is ${counter} and callCount is ${callCount}`; | ||
}; | ||
|
||
beforeEach(() => { | ||
callCount = 0; | ||
hook = renderHook( | ||
({ fn, counter }) => { | ||
const callback = useCallback(() => fn(counter), [counter]); | ||
return useAsync<string>(callback, [callback]); | ||
}, | ||
{ | ||
initialProps: { | ||
counter: 0, | ||
fn: staticFunction, | ||
}, | ||
} | ||
); | ||
}); | ||
|
||
it('initial renders the first passed pargs', () => { | ||
expect(hook.result.current.value).toEqual('counter is 0 and callCount is 1'); | ||
}); | ||
|
||
it('renders a different value when deps change', async () => { | ||
expect.assertions(1); | ||
|
||
hook.rerender({ fn: staticFunction, counter: 1 }); | ||
await hook.waitForNextUpdate(); | ||
|
||
expect(hook.result.current.value).toEqual('counter is 1 and callCount is 2'); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// NOTE: most behavior that useAsyncFn provides | ||
// is covered be the useAsync tests. | ||
// | ||
// The main difference is that useAsyncFn | ||
// does not automatically invoke the function | ||
// and it can take arguments. | ||
|
||
import { cleanup, renderHook } from 'react-hooks-testing-library'; | ||
import useAsyncFn, { AsyncState } from '../useAsyncFn'; | ||
|
||
afterEach(cleanup); | ||
|
||
type AdderFn = (a: number, b: number) => Promise<number>; | ||
|
||
describe('useAsyncFn', () => { | ||
it('should be defined', () => { | ||
expect(useAsyncFn).toBeDefined(); | ||
}); | ||
|
||
describe('the callback can be awaited and return the value', () => { | ||
let hook; | ||
let callCount = 0; | ||
const adder = async (a: number, b: number): Promise<number> => { | ||
callCount++; | ||
return a + b; | ||
}; | ||
|
||
beforeEach(() => { | ||
// NOTE: renderHook isn't good at inferring array types | ||
hook = renderHook<{ fn: AdderFn }, [AsyncState<number>, AdderFn]>(({ fn }) => useAsyncFn(fn), { | ||
initialProps: { | ||
fn: adder, | ||
}, | ||
}); | ||
}); | ||
|
||
it('awaits the result', async () => { | ||
expect.assertions(3); | ||
|
||
const [s, callback] = hook.result.current; | ||
|
||
const result = await callback(5, 7); | ||
|
||
expect(result).toEqual(12); | ||
|
||
const [state] = hook.result.current; | ||
|
||
expect(state.value).toEqual(12); | ||
expect(result).toEqual(state.value); | ||
}); | ||
}); | ||
|
||
describe('args can be passed to the function', () => { | ||
let hook; | ||
let callCount = 0; | ||
const adder = async (a: number, b: number): Promise<number> => { | ||
callCount++; | ||
return a + b; | ||
}; | ||
|
||
beforeEach(() => { | ||
// NOTE: renderHook isn't good at inferring array types | ||
hook = renderHook<{ fn: AdderFn }, [AsyncState<number>, AdderFn]>(({ fn }) => useAsyncFn(fn), { | ||
initialProps: { | ||
fn: adder, | ||
}, | ||
}); | ||
}); | ||
|
||
it('initially does not have a value', () => { | ||
const [state] = hook.result.current; | ||
|
||
expect(state.value).toEqual(undefined); | ||
expect(state.loading).toEqual(false); | ||
expect(state.error).toEqual(undefined); | ||
expect(callCount).toEqual(0); | ||
}); | ||
|
||
describe('when invoked', () => { | ||
it('resolves a value derived from args', async () => { | ||
expect.assertions(4); | ||
|
||
const [s, callback] = hook.result.current; | ||
|
||
callback(2, 7); | ||
hook.rerender({ fn: adder }); | ||
await hook.waitForNextUpdate(); | ||
|
||
const [state, c] = hook.result.current; | ||
|
||
expect(callCount).toEqual(1); | ||
expect(state.loading).toEqual(false); | ||
expect(state.error).toEqual(undefined); | ||
expect(state.value).toEqual(9); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.