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 = jest.fn(defaultStatesValidator), initialStates = [1, 2], initialValidity = [false] ): [ MultiStateValidator, RenderHookResult]> ] { 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 = 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]); }); });