react-use/tests/useMultiStateValidator.test.ts
2021-08-31 22:22:38 +01:00

131 lines
4.0 KiB
TypeScript

import { act, renderHook, RenderHookResult } from '@testing-library/react-hooks';
import { useState } from 'react';
import { MultiStateValidator, useMultiStateValidator } from '../src/useMultiStateValidator';
import { UseStateValidatorReturn, ValidityState } from '../src/useStateValidator';
interface Mock extends jest.Mock {}
describe('useMultiStateValidator', () => {
it('should be defined', () => {
expect(useMultiStateValidator).toBeDefined();
});
const defaultStatesValidator = (states: number[]) => [states.every((num) => !(num % 2))];
function getHook(
fn: MultiStateValidator<any, number[]> = jest.fn(defaultStatesValidator),
initialStates = [1, 2],
initialValidity = [false]
): [
MultiStateValidator<any, number[]>,
RenderHookResult<any, [Function, UseStateValidatorReturn<ValidityState>]>
] {
return [
fn,
renderHook(
({ initStates, validator, initValidity }) => {
const [states, setStates] = useState(initStates);
return [setStates, useMultiStateValidator(states, validator, initValidity)];
},
{
initialProps: {
initStates: initialStates,
initValidity: initialValidity,
validator: fn,
},
}
),
];
}
it('should return an array of two elements', () => {
const [, hook] = getHook();
const res = hook.result.current[1];
expect(Array.isArray(res)).toBe(true);
expect(res.length).toBe(2);
});
it('should call validator on init', () => {
const [spy] = getHook();
expect(spy).toHaveBeenCalledTimes(1);
});
it('should call validator on any of states changed', () => {
const [spy, hook] = getHook();
expect(spy).toHaveBeenCalledTimes(1);
act(() => hook.result.current[0]([4, 2]));
expect(spy).toHaveBeenCalledTimes(2);
});
it("should NOT call validator on it's change", () => {
const [spy, hook] = getHook();
const newValidator: MultiStateValidator<any, number[]> = jest.fn((states) => [
states!.every((num) => !!(num % 2)),
]);
expect(spy).toHaveBeenCalledTimes(1);
hook.rerender({ validator: newValidator });
expect(spy).toHaveBeenCalledTimes(1);
});
it('should throw if states is not an object', () => {
expect(() => {
// @ts-ignore
const [, hook] = getHook(defaultStatesValidator, 123);
if (hook.result.error) {
throw hook.result.error;
}
}).toThrowError('states expected to be an object or array, got number');
});
it('first returned element should represent current validity state', () => {
const [, hook] = getHook();
let [setState, [validity]] = hook.result.current;
expect(validity).toEqual([false]);
act(() => setState([4, 2]));
[setState, [validity]] = hook.result.current;
expect(validity).toEqual([true]);
act(() => setState([4, 5]));
[setState, [validity]] = hook.result.current;
expect(validity).toEqual([false]);
});
it('second returned element should re-call validation', () => {
const [spy, hook] = getHook();
const [, [, revalidate]] = hook.result.current;
expect(spy).toHaveBeenCalledTimes(1);
act(() => revalidate());
expect(spy).toHaveBeenCalledTimes(2);
});
it('validator should receive states as a firs argument', () => {
const [spy, hook] = getHook();
const [setState] = hook.result.current;
expect((spy as Mock).mock.calls[0].length).toBe(1);
expect((spy as Mock).mock.calls[0][0]).toEqual([1, 2]);
act(() => setState([4, 6]));
expect((spy as Mock).mock.calls[1][0]).toEqual([4, 6]);
});
it('if validator expects 2nd parameters it should pass a validity setter there', () => {
const spy = jest.fn((states: number[], done) => {
done([states.every((num) => !!(num % 2))]);
}) as unknown as MultiStateValidator<[boolean], number[]>;
const [, hook] = getHook(spy, [1, 3]);
const [, [validity]] = hook.result.current;
expect((spy as Mock).mock.calls[0].length).toBe(2);
expect((spy as Mock).mock.calls[0][0]).toEqual([1, 3]);
expect(validity).toEqual([true]);
});
});