From c43294fead6b9b2e9acd9b6764ee702a4da6c3d5 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Sat, 7 Mar 2020 06:29:12 -0800 Subject: [PATCH] feat: useMutableCallback (#156) * Add useMutableCallback hook * Apply lint on tests * Replace testHook with runHooks --- packages/fuselage-hooks/.jest/helpers.js | 46 +++++++---- packages/fuselage-hooks/README.md | 14 +++- packages/fuselage-hooks/package.json | 2 +- packages/fuselage-hooks/src/index.js | 1 + .../fuselage-hooks/src/useMutableCallback.js | 16 ++++ .../fuselage-hooks/tests/useClassName.spec.js | 16 ++-- .../tests/useDebouncedCallback.spec.js | 63 +++------------ .../tests/useDebouncedUpdates.spec.js | 76 +++++++------------ .../tests/useExclusiveBooleanProps.spec.js | 12 +-- .../tests/useMediaQuery.spec.js | 26 +++---- .../tests/useMergedRefs.spec.js | 58 +++++--------- .../tests/useMutableCallback.spec.js | 34 +++++++++ .../fuselage-hooks/tests/useToggle.spec.js | 53 ++++++------- .../fuselage-hooks/tests/useUniqueId.spec.js | 20 ++--- 14 files changed, 211 insertions(+), 226 deletions(-) create mode 100644 packages/fuselage-hooks/src/useMutableCallback.js create mode 100644 packages/fuselage-hooks/tests/useMutableCallback.spec.js diff --git a/packages/fuselage-hooks/.jest/helpers.js b/packages/fuselage-hooks/.jest/helpers.js index fa4ca27bbc..a4f9b42c3e 100644 --- a/packages/fuselage-hooks/.jest/helpers.js +++ b/packages/fuselage-hooks/.jest/helpers.js @@ -1,12 +1,20 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; +import React, { useReducer, Component, createElement } from 'react'; +import ReactDOM, { render, unmountComponentAtNode } from 'react-dom'; import { act } from 'react-dom/test-utils'; -export const testHook = (callback, ...acts) => { +export const runHooks = (fn, mutations = []) => { let returnedValue; + let forceUpdate; + + function FunctionalComponent() { + [, forceUpdate] = useReducer((state) => !state, false); + returnedValue = fn(); + return null; + } + let errorThrown; - class ErrorBoundary extends React.Component { + class ComponentWithErrorBoundary extends Component { state = { errored: false } static getDerivedStateFromError = () => ({ errored: true }) @@ -15,29 +23,35 @@ export const testHook = (callback, ...acts) => { errorThrown = error; } - render = () => (this.state.errored ? null : <>{this.props.children}) - } - - function TestComponent() { - returnedValue = callback(); - return null; + render = () => (this.state.errored ? null : createElement(FunctionalComponent)) } const spy = jest.spyOn(console, 'error'); spy.mockImplementation(() => {}); const div = document.createElement('div'); - ReactDOM.render( - - , div); + render(createElement(ComponentWithErrorBoundary), div); + + const values = [returnedValue]; + + for (const mutation of mutations) { + act(() => { + forceUpdate(); - acts.forEach((fn) => act(fn.bind(null, returnedValue))); + if (mutation === true) { + return; + } + + mutation(returnedValue); + }); + values.push(returnedValue); + } - ReactDOM.unmountComponentAtNode(div); + unmountComponentAtNode(div); if (errorThrown) { throw errorThrown; } - return returnedValue; + return values; }; diff --git a/packages/fuselage-hooks/README.md b/packages/fuselage-hooks/README.md index 4e0beae137..9fd44f5047 100644 --- a/packages/fuselage-hooks/README.md +++ b/packages/fuselage-hooks/README.md @@ -43,8 +43,10 @@ yarn test - [Parameters](#parameters-6) - [useMergedRefs](#usemergedrefs) - [Parameters](#parameters-7) -- [useToggle](#usetoggle) +- [useMutableCallback](#usemutablecallback) - [Parameters](#parameters-8) +- [useToggle](#usetoggle) + - [Parameters](#parameters-9) ### useClassName @@ -141,6 +143,16 @@ while receiving a forwared ref. Returns **any** a merged callback ref +### useMutableCallback + +Hook to create a stable callback from a mutable one. + +#### Parameters + +- `fn` **function (): any** the mutable callback + +Returns **any** a stable callback + ### useToggle Hook to create a toggleable boolean state. diff --git a/packages/fuselage-hooks/package.json b/packages/fuselage-hooks/package.json index 34601d21c4..408ffddfd7 100644 --- a/packages/fuselage-hooks/package.json +++ b/packages/fuselage-hooks/package.json @@ -29,7 +29,7 @@ "start": "rollup -c -w", "build": "rollup -c", "test": "jest", - "lint": "eslint src", + "lint": "eslint src tests", "lint-staged": "lint-staged", "docs": "documentation readme src/index.js --section='API Reference' --readme-file README.md" }, diff --git a/packages/fuselage-hooks/src/index.js b/packages/fuselage-hooks/src/index.js index d5f444bbb1..1c1d0bd054 100644 --- a/packages/fuselage-hooks/src/index.js +++ b/packages/fuselage-hooks/src/index.js @@ -6,5 +6,6 @@ export * from './useDebouncedCallback'; export * from './useExclusiveBooleanProps'; export * from './useMediaQuery'; export * from './useMergedRefs'; +export * from './useMutableCallback'; export * from './useToggle'; export * from './useUniqueId'; diff --git a/packages/fuselage-hooks/src/useMutableCallback.js b/packages/fuselage-hooks/src/useMutableCallback.js new file mode 100644 index 0000000000..06e5013ea7 --- /dev/null +++ b/packages/fuselage-hooks/src/useMutableCallback.js @@ -0,0 +1,16 @@ +// @flow + +import { useCallback, useRef } from 'react'; + +/** + * Hook to create a stable callback from a mutable one. + * + * @param fn the mutable callback + * @return a stable callback + */ +export const useMutableCallback = (fn: (...args : any[]) => any) => { + const fnRef = useRef(fn); + fnRef.current = fn; + + return useCallback((...args: any[]) => fnRef.current && (0, fnRef.current)(...args), []); +}; diff --git a/packages/fuselage-hooks/tests/useClassName.spec.js b/packages/fuselage-hooks/tests/useClassName.spec.js index 33aae94aa6..675b574971 100644 --- a/packages/fuselage-hooks/tests/useClassName.spec.js +++ b/packages/fuselage-hooks/tests/useClassName.spec.js @@ -1,42 +1,42 @@ -import { testHook } from '../.jest/helpers'; +import { runHooks } from '../.jest/helpers'; import { useClassName } from '../src'; describe('useClassName hook', () => { const componentClassName = 'component'; it('accepts only the component className', () => { - const newClassName = testHook(() => useClassName(componentClassName)); + const [newClassName] = runHooks(() => useClassName(componentClassName)); expect(newClassName).toEqual(componentClassName); }); it('composes with a true-valued boolean modifier', () => { - const newClassName = testHook(() => useClassName(componentClassName, { a: true })); + const [newClassName] = runHooks(() => useClassName(componentClassName, { a: true })); expect(newClassName).toEqual(`${ componentClassName } ${ componentClassName }--a`); }); it('does not compose with a false-valued boolean modifier', () => { - const newClassName = testHook(() => useClassName(componentClassName, { a: false })); + const [newClassName] = runHooks(() => useClassName(componentClassName, { a: false })); expect(newClassName).toEqual(componentClassName); }); it('composes with a non-boolean modifier', () => { - const newClassName = testHook(() => useClassName(componentClassName, { a: 'b' })); + const [newClassName] = runHooks(() => useClassName(componentClassName, { a: 'b' })); expect(newClassName).toEqual(`${ componentClassName } ${ componentClassName }--a-b`); }); it('appends an arbitrary amount of additional classNames', () => { const classNames = new Array(5).fill(undefined).map((i) => `class-${ i }`); - const newClassName = testHook(() => useClassName(componentClassName, {}, ...classNames)); + const [newClassName] = runHooks(() => useClassName(componentClassName, {}, ...classNames)); expect(newClassName).toEqual(`${ componentClassName } ${ classNames.join(' ') }`); }); it('formats a modifier name from camelCase to kebab-case', () => { - const newClassName = testHook(() => useClassName(componentClassName, { camelCaseModifier: true })); + const [newClassName] = runHooks(() => useClassName(componentClassName, { camelCaseModifier: true })); expect(newClassName).toEqual(`${ componentClassName } ${ componentClassName }--camel-case-modifier`); }); it('formats a modifier value from camelCase to kebab-case', () => { - const newClassName = testHook(() => useClassName(componentClassName, { a: 'camelCaseValue' })); + const [newClassName] = runHooks(() => useClassName(componentClassName, { a: 'camelCaseValue' })); expect(newClassName).toEqual(`${ componentClassName } ${ componentClassName }--a-camel-case-value`); }); }); diff --git a/packages/fuselage-hooks/tests/useDebouncedCallback.spec.js b/packages/fuselage-hooks/tests/useDebouncedCallback.spec.js index 718d8b97b1..ff4a5a413d 100644 --- a/packages/fuselage-hooks/tests/useDebouncedCallback.spec.js +++ b/packages/fuselage-hooks/tests/useDebouncedCallback.spec.js @@ -1,6 +1,4 @@ -import { useState } from 'react'; - -import { testHook } from '../.jest/helpers'; +import { runHooks } from '../.jest/helpers'; import { useDebouncedCallback } from '../src'; describe('useDebouncedCallback hook', () => { @@ -13,7 +11,7 @@ describe('useDebouncedCallback hook', () => { }); it('returns a debounced callback', () => { - const debouncedCallback = testHook(() => useDebouncedCallback(fn, delay)); + const [debouncedCallback] = runHooks(() => useDebouncedCallback(fn, delay)); expect(debouncedCallback).toBeInstanceOf(Function); expect(debouncedCallback.flush).toBeInstanceOf(Function); expect(debouncedCallback.cancel).toBeInstanceOf(Function); @@ -24,69 +22,30 @@ describe('useDebouncedCallback hook', () => { }); it('returns the same callback if deps don\'t change', () => { - let callbackA; - let callbackB; - let setDummy; - - testHook( - () => { - [, setDummy] = useState(0); - return useDebouncedCallback(fn, delay, []); - }, - (returnedValue) => { - callbackA = returnedValue; - setDummy((dep) => dep + 1); - }, - (returnedValue) => { - callbackB = returnedValue; - } - ); - + const [callbackA, callbackB] = runHooks(() => useDebouncedCallback(fn, delay, []), [true]); expect(callbackA).toBe(callbackB); }); it('returns another callback if deps change', () => { - let callbackA; - let callbackB; - let dep; - let setDep; + let dep = Symbol(); - testHook( + const [callbackA, , callbackB] = runHooks(() => useDebouncedCallback(fn, delay, [dep]), [ () => { - [dep, setDep] = useState(0); - return useDebouncedCallback(fn, delay, [dep]); + dep = Symbol(); }, - (returnedValue) => { - callbackA = returnedValue; - setDep((dep) => dep + 1); - }, - (returnedValue) => { - callbackB = returnedValue; - } - ); + ]); expect(callbackA).not.toBe(callbackB); }); it('returns another callback if delay change', () => { - let callbackA; - let callbackB; - let delay; - let setDelay; + let delay = 0; - testHook( + const [callbackA, callbackB] = runHooks(() => useDebouncedCallback(fn, delay, []), [ () => { - [delay, setDelay] = useState(0); - return useDebouncedCallback(fn, delay, []); - }, - (returnedValue) => { - callbackA = returnedValue; - setDelay((delay) => delay + 1); + delay = 1; }, - (returnedValue) => { - callbackB = returnedValue; - } - ); + ]); expect(callbackA).not.toBe(callbackB); }); diff --git a/packages/fuselage-hooks/tests/useDebouncedUpdates.spec.js b/packages/fuselage-hooks/tests/useDebouncedUpdates.spec.js index b825f396f4..6864d112d1 100644 --- a/packages/fuselage-hooks/tests/useDebouncedUpdates.spec.js +++ b/packages/fuselage-hooks/tests/useDebouncedUpdates.spec.js @@ -1,6 +1,6 @@ import { useState } from 'react'; -import { testHook } from '../.jest/helpers'; +import { runHooks } from '../.jest/helpers'; import { useDebouncedUpdates, useDebouncedReducer, useDebouncedState } from '../src'; describe('useDebouncedUpdates hook', () => { @@ -11,23 +11,10 @@ describe('useDebouncedUpdates hook', () => { }); it('returns a debounced state updater', () => { - let valueA; - let valueB; - let valueC; - const [, debouncedSetValue] = testHook( - () => useDebouncedUpdates(useState(0), delay), - ([value, debouncedSetValue]) => { - valueA = value; - debouncedSetValue((value) => value + 1); - }, - ([value]) => { - valueB = value; - jest.runAllTimers(); - }, - ([value]) => { - valueC = value; - } - ); + const [[valueA, debouncedSetValue], [valueB], [valueC]] = runHooks(() => useDebouncedUpdates(useState(0), delay), [ + ([, setValue]) => setValue((value) => value + 1), + () => jest.runAllTimers(), + ]); expect(debouncedSetValue).toBeInstanceOf(Function); expect(debouncedSetValue.flush).toBeInstanceOf(Function); @@ -39,55 +26,46 @@ describe('useDebouncedUpdates hook', () => { describe('useDebouncedReducer hook', () => { it('is a debounced state updater', () => { - const initialState = {}; - const newState = {}; + const initialState = Symbol(); + const newState = Symbol(); const reducer = jest.fn(() => newState); const initializerArg = initialState; const initializer = jest.fn((state) => state); - let stateA; - let stateB; - testHook( - () => useDebouncedReducer(reducer, initializerArg, initializer, delay), - ([, dispatch]) => { - dispatch(); - }, - ([state]) => { - stateA = state; - jest.runAllTimers(); - }, - ([state]) => { - stateB = state; - } - ); + + const [ + [stateA], [stateB], [stateC], + ] = runHooks(() => useDebouncedReducer(reducer, initializerArg, initializer, delay), [ + ([, dispatch]) => dispatch(), + () => jest.runAllTimers(), + ]); expect(reducer).toHaveBeenCalledWith(initialState, undefined); expect(initializer).toHaveBeenCalledWith(initializerArg); expect(stateA).toBe(initialState); - expect(stateB).toBe(newState); + expect(stateB).toBe(initialState); + expect(stateC).toBe(newState); }); }); describe('useDebouncedState hook', () => { it('is a debounced state updater', () => { - const initialValue = {}; - const newValue = {}; - let valueA; - let valueB; - testHook( - () => useDebouncedState(initialValue, delay), + const initialValue = Symbol(); + const newValue = Symbol(); + + const [ + [valueA], [valueB], [valueC], + ] = runHooks(() => useDebouncedState(initialValue, delay), [ ([, setValue]) => { setValue(newValue); }, - ([state]) => { - valueA = state; + () => { jest.runAllTimers(); }, - ([state]) => { - valueB = state; - } - ); + ]); + expect(valueA).toBe(initialValue); - expect(valueB).toBe(newValue); + expect(valueB).toBe(initialValue); + expect(valueC).toBe(newValue); }); }); }); diff --git a/packages/fuselage-hooks/tests/useExclusiveBooleanProps.spec.js b/packages/fuselage-hooks/tests/useExclusiveBooleanProps.spec.js index 4ad762dbd5..0600bd4a1d 100644 --- a/packages/fuselage-hooks/tests/useExclusiveBooleanProps.spec.js +++ b/packages/fuselage-hooks/tests/useExclusiveBooleanProps.spec.js @@ -1,26 +1,26 @@ -import { testHook } from '../.jest/helpers'; +import { runHooks } from '../.jest/helpers'; import { useExclusiveBooleanProps } from '../src'; describe('useExclusiveBooleanProps hook', () => { it('allows an empty set of props', () => { - testHook(() => useExclusiveBooleanProps({})); + runHooks(() => useExclusiveBooleanProps({})); }); it('allows only false-valued props', () => { - testHook(() => useExclusiveBooleanProps({ a: false, b: false, c: false })); + runHooks(() => useExclusiveBooleanProps({ a: false, b: false, c: false })); }); it('allows one true-valued prop', () => { - testHook(() => useExclusiveBooleanProps({ a: true })); + runHooks(() => useExclusiveBooleanProps({ a: true })); }); it('allows one true-valued prop among false-valued ones', () => { - testHook(() => useExclusiveBooleanProps({ a: true, b: false, c: false })); + runHooks(() => useExclusiveBooleanProps({ a: true, b: false, c: false })); }); it('denies two true-valued props', () => { expect(() => { - testHook(() => useExclusiveBooleanProps({ a: true, b: true })); + runHooks(() => useExclusiveBooleanProps({ a: true, b: true })); }).toThrow(); }); }); diff --git a/packages/fuselage-hooks/tests/useMediaQuery.spec.js b/packages/fuselage-hooks/tests/useMediaQuery.spec.js index e74e0e778a..2e46993a78 100644 --- a/packages/fuselage-hooks/tests/useMediaQuery.spec.js +++ b/packages/fuselage-hooks/tests/useMediaQuery.spec.js @@ -1,4 +1,4 @@ -import { testHook } from '../.jest/helpers'; +import { runHooks } from '../.jest/helpers'; import { useMediaQuery } from '../src'; describe('useMediaQuery hook', () => { @@ -23,44 +23,40 @@ describe('useMediaQuery hook', () => { }); it('does not register a undefined media query', () => { - testHook(() => useMediaQuery()); + runHooks(() => useMediaQuery()); expect(window.matchMedia).not.toHaveBeenCalled(); }); it('does register a defined media query', () => { - testHook(() => useMediaQuery('(max-width: 1024)')); + runHooks(() => useMediaQuery('(max-width: 1024)')); expect(window.matchMedia).toHaveBeenCalledWith('(max-width: 1024)'); }); it('returns false if no query is given', () => { - const value = testHook(() => useMediaQuery()); + const [value] = runHooks(() => useMediaQuery()); expect(value).toBe(false); }); it('returns false if the media query does not match', () => { - const value = testHook(() => useMediaQuery('(max-width: 1024)')); + const [value] = runHooks(() => useMediaQuery('(max-width: 1024)')); expect(value).toBe(false); }); it('returns true if the media query does match', () => { mql.matches = true; - const value = testHook(() => useMediaQuery('(max-width: 1024)')); + const [value] = runHooks(() => useMediaQuery('(max-width: 1024)')); expect(value).toBe(true); }); it('mutates its value to true if the media query matches', () => { - testHook( - () => useMediaQuery('(max-width: 1024)'), - (matches) => { - expect(matches).toBe(false); - }, + const [matchesA, matchesB] = runHooks(() => useMediaQuery('(max-width: 1024)'), [ () => { mql.matches = true; mql.onchange(); }, - (matches) => { - expect(matches).toBe(true); - } - ); + ]); + + expect(matchesA).toBe(false); + expect(matchesB).toBe(true); }); }); diff --git a/packages/fuselage-hooks/tests/useMergedRefs.spec.js b/packages/fuselage-hooks/tests/useMergedRefs.spec.js index fef2ec2e77..4db8d5a1c1 100644 --- a/packages/fuselage-hooks/tests/useMergedRefs.spec.js +++ b/packages/fuselage-hooks/tests/useMergedRefs.spec.js @@ -1,72 +1,56 @@ import React from 'react'; -import { testHook } from '../.jest/helpers'; +import { runHooks } from '../.jest/helpers'; import { useMergedRefs } from '../src'; describe('useMergedRefs hook', () => { it('returns a callback ref', () => { - const mergedRef = testHook(() => useMergedRefs()); + const [mergedRef] = runHooks(() => useMergedRefs()); expect(mergedRef).toStrictEqual(expect.any(Function)); }); it('works without any arguments', () => { - const value = {}; - const mergedRef = testHook( - () => jest.fn(useMergedRefs()), - (mergedRef) => { - mergedRef(value); - }, - ); + const [mergedRef] = runHooks(() => jest.fn(useMergedRefs())); + + const value = Symbol(); + mergedRef(value); expect(mergedRef).toHaveBeenCalledWith(value); }); it('works with one ref', () => { const ref = React.createRef(); - const value = {}; - testHook( - () => useMergedRefs(ref), - (mergedRef) => { - mergedRef(value); - }, - ); + const [mergedRef] = runHooks(() => useMergedRefs(ref)); + + const value = Symbol(); + mergedRef(value); expect(ref.current).toBe(value); }); it('works with many refs', () => { const refs = new Array(10).fill(undefined).map(() => React.createRef()); - const value = {}; - testHook( - () => useMergedRefs(...refs), - (mergedRef) => { - mergedRef(value); - }, - ); + const [mergedRef] = runHooks(() => useMergedRefs(...refs)); + + const value = Symbol(); + mergedRef(value); refs.forEach((ref) => expect(ref.current).toBe(value)); }); it('works with callback ref', () => { const callbackRef = jest.fn(); - const value = {}; - testHook( - () => useMergedRefs(callbackRef), - (mergedRef) => { - mergedRef(value); - }, - ); + const [mergedRef] = runHooks(() => useMergedRefs(callbackRef)); + + const value = Symbol(); + mergedRef(value); expect(callbackRef).toHaveBeenCalledWith(value); }); it('works with refs and callback refs', () => { const refs = new Array(5).fill(undefined).map(() => React.createRef()); const callbackRefs = new Array(5).fill(undefined).map(() => jest.fn()); + const [mergedRef] = runHooks(() => useMergedRefs(...refs, ...callbackRefs)); - const value = {}; - testHook( - () => useMergedRefs(...refs, ...callbackRefs), - (mergedRef) => { - mergedRef(value); - }, - ); + const value = Symbol(); + mergedRef(value); refs.forEach((ref) => expect(ref.current).toBe(value)); callbackRefs.forEach((callbackRef) => expect(callbackRef).toHaveBeenCalledWith(value)); }); diff --git a/packages/fuselage-hooks/tests/useMutableCallback.spec.js b/packages/fuselage-hooks/tests/useMutableCallback.spec.js new file mode 100644 index 0000000000..ea097c72ea --- /dev/null +++ b/packages/fuselage-hooks/tests/useMutableCallback.spec.js @@ -0,0 +1,34 @@ +import { useRef } from 'react'; + +import { runHooks } from '../.jest/helpers'; +import { useMutableCallback } from '../src'; + +describe('useMutableCallback hook', () => { + it('returns a stable callback', () => { + const fn = jest.fn(); + const [stableCallbackA, stableCallbackB] = runHooks(() => useMutableCallback(fn), [true]); + expect(stableCallbackA).toBe(stableCallbackB); + }); + + it('returns a callback that invokes the mutable one', () => { + const fn = jest.fn(); + const [stableCallback] = runHooks(() => useMutableCallback(fn)); + stableCallback(); + expect(fn).toHaveBeenCalledTimes(1); + }); + + it('handles mutations in callback', () => { + const firstCallback = jest.fn(); + const secondCallback = jest.fn(); + + runHooks(() => { + const ref = useRef(firstCallback); + const stableCallback = useMutableCallback(ref.current); + ref.current = secondCallback; + return stableCallback; + }, [true, (stableCallback) => stableCallback()]); + + expect(firstCallback).toHaveBeenCalledTimes(0); + expect(secondCallback).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/fuselage-hooks/tests/useToggle.spec.js b/packages/fuselage-hooks/tests/useToggle.spec.js index 96b0f350bf..8bd976ae28 100644 --- a/packages/fuselage-hooks/tests/useToggle.spec.js +++ b/packages/fuselage-hooks/tests/useToggle.spec.js @@ -1,77 +1,78 @@ -import { testHook } from '../.jest/helpers'; +import { runHooks } from '../.jest/helpers'; import { useToggle } from '../src'; describe('useToggle hook', () => { it('has false value when an initial value is undefined', () => { - const [value] = testHook(() => useToggle()); + const [[value]] = runHooks(() => useToggle()); expect(value).toBe(false); }); it('has false value when an initial value is false', () => { - const [value] = testHook(() => useToggle(false)); + const [[value]] = runHooks(() => useToggle(false)); expect(value).toBe(false); }); it('has false value when an initial value is falsy', () => { - const [value] = testHook(() => useToggle(0)); + const [[value]] = runHooks(() => useToggle(0)); expect(value).toBe(false); }); it('has false value when an initial value is a function returning false', () => { - const [value] = testHook(() => useToggle(() => false)); + const [[value]] = runHooks(() => useToggle(() => false)); expect(value).toBe(false); }); it('has true value when an initial value is true', () => { - const [value] = testHook(() => useToggle(true)); + const [[value]] = runHooks(() => useToggle(true)); expect(value).toBe(true); }); it('has true value when an initial value is truthy', () => { - const [value] = testHook(() => useToggle(1)); + const [[value]] = runHooks(() => useToggle(1)); expect(value).toBe(true); }); it('has true value when an initial value is a function returning true', () => { - const [value] = testHook(() => useToggle(() => true)); + const [[value]] = runHooks(() => useToggle(() => true)); expect(value).toBe(true); }); it('toggles false to true when toggleValue is called', () => { - const [value] = testHook(() => useToggle(false), ([, toggleValue]) => { - toggleValue(); - }); + const [, [value]] = runHooks(() => useToggle(false), [ + ([, toggleValue]) => toggleValue(), + ]); expect(value).toBe(true); }); it('toggles false to true when toggleValue is called twice in the same render cycle', () => { - const [value] = testHook(() => useToggle(false), ([, toggleValue]) => { - toggleValue(); - toggleValue(); - }); + const [, [value]] = runHooks(() => useToggle(false), [ + ([, toggleValue]) => { + toggleValue(); + toggleValue(); + }, + ]); expect(value).toBe(true); }); it('toggles false to false when toggleValue is called at two render cycles', () => { - const [value] = testHook(() => useToggle(false), ([, toggleValue]) => { - toggleValue(); - }, ([, toggleValue]) => { - toggleValue(); - }); + const [,, [value]] = runHooks(() => useToggle(false), [ + ([, toggleValue]) => toggleValue(), + ([, toggleValue]) => toggleValue(), + ]); expect(value).toBe(false); }); it('forces false argument on toggleValue', () => { - const [value] = testHook(() => useToggle(false), ([, toggleValue]) => { - toggleValue(false); - }); + const [, [value]] = runHooks(() => useToggle(false), [ + ([, toggleValue]) => toggleValue(false), + ]); expect(value).toBe(false); }); it('forces true argument on toggleValue', () => { - const [value] = testHook(() => useToggle(true), ([, toggleValue]) => { - toggleValue(true); - }); + const [, [value]] = runHooks(() => useToggle(true), [ + ([, toggleValue]) => toggleValue(true), + ]); expect(value).toBe(true); }); }); diff --git a/packages/fuselage-hooks/tests/useUniqueId.spec.js b/packages/fuselage-hooks/tests/useUniqueId.spec.js index e4acca9147..8a19aedac3 100644 --- a/packages/fuselage-hooks/tests/useUniqueId.spec.js +++ b/packages/fuselage-hooks/tests/useUniqueId.spec.js @@ -1,32 +1,22 @@ -import { testHook } from '../.jest/helpers'; +import { runHooks } from '../.jest/helpers'; import { useUniqueId } from '../src'; describe('useUniqueId hook', () => { it('returns a string', () => { - const uniqueId = testHook(() => useUniqueId()); + const [uniqueId] = runHooks(() => useUniqueId()); expect(uniqueId).toStrictEqual(expect.any(String)); }); it('returns a unique ID', () => { - const uniqueA = testHook(() => useUniqueId()); - const uniqueB = testHook(() => useUniqueId()); + const uniqueA = runHooks(() => useUniqueId()); + const uniqueB = runHooks(() => useUniqueId()); expect(uniqueA).not.toBe(uniqueB); }); it('returns the same ID on each render cycle', () => { - let uniqueA; - let uniqueB; - testHook( - () => useUniqueId(), - (uniqueId) => { - uniqueA = uniqueId; - }, - (uniqueId) => { - uniqueB = uniqueId; - }, - ); + const [uniqueA, uniqueB] = runHooks(() => useUniqueId(), [true]); expect(uniqueA).toBe(uniqueB); });