addons/shared/__tests__/hooks.test.tsxblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 *
b69ab317 * @jest-environment jsdom
b69ab318 */
b69ab319
b69ab3110import '@testing-library/jest-dom';
b69ab3111import {act, fireEvent, render, screen} from '@testing-library/react';
b69ab3112import {useState} from 'react';
b69ab3113import {useDeepMemo, usePrevious, useThrottledEffect} from '../hooks';
b69ab3114
b69ab3115describe('useThrottledEffect', () => {
b69ab3116 afterEach(() => {
b69ab3117 jest.useRealTimers();
b69ab3118 });
b69ab3119 it('debounces multiple calls', () => {
b69ab3120 jest.useFakeTimers();
b69ab3121 const myFunc = jest.fn();
b69ab3122 const onRender = jest.fn();
b69ab3123 function MyComponent() {
b69ab3124 const [count, setCount] = useState(0);
b69ab3125 onRender();
b69ab3126 useThrottledEffect(
b69ab3127 () => {
b69ab3128 myFunc(count);
b69ab3129 },
b69ab3130 1000,
b69ab3131 [],
b69ab3132 );
b69ab3133 return <button data-testid="button" onClick={() => setCount(count + 1)} />;
b69ab3134 }
b69ab3135
b69ab3136 render(<MyComponent />);
b69ab3137 jest.advanceTimersByTime(100);
b69ab3138 act(() => {
b69ab3139 fireEvent.click(screen.getByTestId('button'));
b69ab3140 });
b69ab3141 jest.advanceTimersByTime(100);
b69ab3142 act(() => {
b69ab3143 fireEvent.click(screen.getByTestId('button'));
b69ab3144 });
b69ab3145 jest.advanceTimersByTime(2000);
b69ab3146
b69ab3147 expect(myFunc).toHaveBeenCalledTimes(1);
b69ab3148 expect(myFunc).toHaveBeenCalledWith(0);
b69ab3149
b69ab3150 expect(onRender).toHaveBeenCalledTimes(3);
b69ab3151 });
b69ab3152
b69ab3153 it('resets via dependencies', () => {
b69ab3154 jest.useFakeTimers();
b69ab3155 const myFunc = jest.fn();
b69ab3156 const onRender = jest.fn();
b69ab3157 function MyComponent() {
b69ab3158 const [count, setCount] = useState(0);
b69ab3159 onRender();
b69ab3160 useThrottledEffect(
b69ab3161 () => {
b69ab3162 myFunc(count);
b69ab3163 },
b69ab3164 1000,
b69ab3165 [count],
b69ab3166 );
b69ab3167 return <button data-testid="button" onClick={() => setCount(count + 1)} />;
b69ab3168 }
b69ab3169
b69ab3170 render(<MyComponent />);
b69ab3171 jest.advanceTimersByTime(100);
b69ab3172 act(() => {
b69ab3173 fireEvent.click(screen.getByTestId('button'));
b69ab3174 });
b69ab3175 jest.advanceTimersByTime(100);
b69ab3176 act(() => {
b69ab3177 fireEvent.click(screen.getByTestId('button'));
b69ab3178 });
b69ab3179 jest.advanceTimersByTime(2000);
b69ab3180
b69ab3181 expect(myFunc).toHaveBeenCalledTimes(3);
b69ab3182 expect(myFunc).toHaveBeenCalledWith(0);
b69ab3183 expect(myFunc).toHaveBeenCalledWith(1);
b69ab3184 expect(myFunc).toHaveBeenCalledWith(2);
b69ab3185
b69ab3186 expect(onRender).toHaveBeenCalledTimes(3);
b69ab3187 });
b69ab3188});
b69ab3189
b69ab3190describe('useDeepMemo', () => {
b69ab3191 it('uses deep equality and memoizes', () => {
b69ab3192 jest.useFakeTimers();
b69ab3193 const myExpensiveFunc = jest.fn();
b69ab3194
b69ab3195 function MyComponent({dep}: {dep: unknown}) {
b69ab3196 useDeepMemo(myExpensiveFunc, [dep]);
b69ab3197 return <div />;
b69ab3198 }
b69ab3199
b69ab31100 const {rerender} = render(<MyComponent dep={{foo: 123}} />);
b69ab31101 expect(myExpensiveFunc).toHaveBeenCalledTimes(1);
b69ab31102 rerender(<MyComponent dep={{foo: 123}} />);
b69ab31103 rerender(<MyComponent dep={{foo: 123}} />);
b69ab31104 rerender(<MyComponent dep={{foo: 123}} />);
b69ab31105 expect(myExpensiveFunc).toHaveBeenCalledTimes(1);
b69ab31106 rerender(<MyComponent dep={{foo: 1234}} />);
b69ab31107 expect(myExpensiveFunc).toHaveBeenCalledTimes(2);
b69ab31108 rerender(<MyComponent dep={{foo: 1234}} />);
b69ab31109 rerender(<MyComponent dep={{foo: 123}} />);
b69ab31110 expect(myExpensiveFunc).toHaveBeenCalledTimes(3);
b69ab31111 rerender(<MyComponent dep={[1, 2, 3]} />);
b69ab31112 expect(myExpensiveFunc).toHaveBeenCalledTimes(4);
b69ab31113 rerender(<MyComponent dep={[1, 2, 3]} />);
b69ab31114 expect(myExpensiveFunc).toHaveBeenCalledTimes(4);
b69ab31115 });
b69ab31116});
b69ab31117
b69ab31118describe('usePrevious', () => {
b69ab31119 it('keeps previous value', () => {
b69ab31120 function MyComponent({dep}: {dep: number}) {
b69ab31121 const last = usePrevious(dep);
b69ab31122 return (
b69ab31123 <div>
b69ab31124 {dep}, {last ?? 'undefined'}
b69ab31125 </div>
b69ab31126 );
b69ab31127 }
b69ab31128
b69ab31129 const {rerender} = render(<MyComponent dep={1} />);
b69ab31130 expect(screen.getByText('1, undefined')).toBeInTheDocument();
b69ab31131 rerender(<MyComponent dep={2} />);
b69ab31132 expect(screen.getByText('2, 1')).toBeInTheDocument();
b69ab31133 rerender(<MyComponent dep={2} />); // rerenders still show previous different value
b69ab31134 expect(screen.getByText('2, 1')).toBeInTheDocument();
b69ab31135 rerender(<MyComponent dep={3} />);
b69ab31136 expect(screen.getByText('3, 2')).toBeInTheDocument();
b69ab31137 });
b69ab31138});