4.0 KB139 lines
Blame
1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @jest-environment jsdom
8 */
9
10import '@testing-library/jest-dom';
11import {act, fireEvent, render, screen} from '@testing-library/react';
12import {useState} from 'react';
13import {useDeepMemo, usePrevious, useThrottledEffect} from '../hooks';
14
15describe('useThrottledEffect', () => {
16 afterEach(() => {
17 jest.useRealTimers();
18 });
19 it('debounces multiple calls', () => {
20 jest.useFakeTimers();
21 const myFunc = jest.fn();
22 const onRender = jest.fn();
23 function MyComponent() {
24 const [count, setCount] = useState(0);
25 onRender();
26 useThrottledEffect(
27 () => {
28 myFunc(count);
29 },
30 1000,
31 [],
32 );
33 return <button data-testid="button" onClick={() => setCount(count + 1)} />;
34 }
35
36 render(<MyComponent />);
37 jest.advanceTimersByTime(100);
38 act(() => {
39 fireEvent.click(screen.getByTestId('button'));
40 });
41 jest.advanceTimersByTime(100);
42 act(() => {
43 fireEvent.click(screen.getByTestId('button'));
44 });
45 jest.advanceTimersByTime(2000);
46
47 expect(myFunc).toHaveBeenCalledTimes(1);
48 expect(myFunc).toHaveBeenCalledWith(0);
49
50 expect(onRender).toHaveBeenCalledTimes(3);
51 });
52
53 it('resets via dependencies', () => {
54 jest.useFakeTimers();
55 const myFunc = jest.fn();
56 const onRender = jest.fn();
57 function MyComponent() {
58 const [count, setCount] = useState(0);
59 onRender();
60 useThrottledEffect(
61 () => {
62 myFunc(count);
63 },
64 1000,
65 [count],
66 );
67 return <button data-testid="button" onClick={() => setCount(count + 1)} />;
68 }
69
70 render(<MyComponent />);
71 jest.advanceTimersByTime(100);
72 act(() => {
73 fireEvent.click(screen.getByTestId('button'));
74 });
75 jest.advanceTimersByTime(100);
76 act(() => {
77 fireEvent.click(screen.getByTestId('button'));
78 });
79 jest.advanceTimersByTime(2000);
80
81 expect(myFunc).toHaveBeenCalledTimes(3);
82 expect(myFunc).toHaveBeenCalledWith(0);
83 expect(myFunc).toHaveBeenCalledWith(1);
84 expect(myFunc).toHaveBeenCalledWith(2);
85
86 expect(onRender).toHaveBeenCalledTimes(3);
87 });
88});
89
90describe('useDeepMemo', () => {
91 it('uses deep equality and memoizes', () => {
92 jest.useFakeTimers();
93 const myExpensiveFunc = jest.fn();
94
95 function MyComponent({dep}: {dep: unknown}) {
96 useDeepMemo(myExpensiveFunc, [dep]);
97 return <div />;
98 }
99
100 const {rerender} = render(<MyComponent dep={{foo: 123}} />);
101 expect(myExpensiveFunc).toHaveBeenCalledTimes(1);
102 rerender(<MyComponent dep={{foo: 123}} />);
103 rerender(<MyComponent dep={{foo: 123}} />);
104 rerender(<MyComponent dep={{foo: 123}} />);
105 expect(myExpensiveFunc).toHaveBeenCalledTimes(1);
106 rerender(<MyComponent dep={{foo: 1234}} />);
107 expect(myExpensiveFunc).toHaveBeenCalledTimes(2);
108 rerender(<MyComponent dep={{foo: 1234}} />);
109 rerender(<MyComponent dep={{foo: 123}} />);
110 expect(myExpensiveFunc).toHaveBeenCalledTimes(3);
111 rerender(<MyComponent dep={[1, 2, 3]} />);
112 expect(myExpensiveFunc).toHaveBeenCalledTimes(4);
113 rerender(<MyComponent dep={[1, 2, 3]} />);
114 expect(myExpensiveFunc).toHaveBeenCalledTimes(4);
115 });
116});
117
118describe('usePrevious', () => {
119 it('keeps previous value', () => {
120 function MyComponent({dep}: {dep: number}) {
121 const last = usePrevious(dep);
122 return (
123 <div>
124 {dep}, {last ?? 'undefined'}
125 </div>
126 );
127 }
128
129 const {rerender} = render(<MyComponent dep={1} />);
130 expect(screen.getByText('1, undefined')).toBeInTheDocument();
131 rerender(<MyComponent dep={2} />);
132 expect(screen.getByText('2, 1')).toBeInTheDocument();
133 rerender(<MyComponent dep={2} />); // rerenders still show previous different value
134 expect(screen.getByText('2, 1')).toBeInTheDocument();
135 rerender(<MyComponent dep={3} />);
136 expect(screen.getByText('3, 2')).toBeInTheDocument();
137 });
138});
139