Skip to content

Commit aa9c1aa

Browse files
committed
Add unit tests for the hooks in 'src/hooks'
1 parent 766d1d7 commit aa9c1aa

File tree

8 files changed

+277
-40
lines changed

8 files changed

+277
-40
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { renderHook } from '@testing-library/react-hooks';
2+
import useAppendDomNode from '../useAppendDomNode';
3+
4+
describe('useAppendDomNode', () => {
5+
const ids = ['test-id-0', 'test-id-1'];
6+
const rootId = 'root';
7+
const rootSelector = `#${rootId}`;
8+
9+
beforeEach(() => {
10+
jest.clearAllMocks();
11+
document.body.innerHTML = `
12+
<div id=${rootId}>
13+
</div>
14+
`;
15+
});
16+
17+
it('renders correctly', () => {
18+
const { result } = renderHook(() => useAppendDomNode(ids, rootSelector));
19+
20+
expect(document.getElementById(rootId).children.length).toBe(ids.length);
21+
ids.forEach((_, i) => {
22+
expect(document.getElementById(rootId).children[i].id).toBe(ids[i]);
23+
});
24+
});
25+
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { renderHook } from '@testing-library/react-hooks';
2+
import { useAsyncRequest } from '../useAsyncRequest';
3+
4+
describe('useAsyncRequest', () => {
5+
beforeEach(() => {
6+
jest.clearAllMocks();
7+
});
8+
9+
it('handle request with no response correctly', async () => {
10+
const mockPromise = Promise.resolve();
11+
const mockRequest = jest.fn().mockReturnValue(mockPromise);
12+
13+
const { result } = renderHook(() => useAsyncRequest(mockRequest));
14+
15+
await mockPromise;
16+
17+
expect(result.current.loading).toBe(false);
18+
});
19+
20+
it('handle request with response correctly', async () => {
21+
const mockResponse = { code: 'ok' };
22+
const mockPromise = Promise.resolve(mockResponse);
23+
const mockRequest = jest.fn().mockReturnValue(mockPromise);
24+
25+
const { result } = renderHook(() => useAsyncRequest(mockRequest));
26+
27+
await mockPromise;
28+
29+
expect(result.current.response).toBe(mockResponse);
30+
expect(result.current.loading).toBe(false);
31+
});
32+
33+
it('cancel request correctly', async () => {
34+
const mockCancel = jest.fn();
35+
const mockRequest = { cancel: mockCancel };
36+
37+
const { unmount } = renderHook(() => useAsyncRequest(mockRequest));
38+
39+
unmount();
40+
41+
expect(mockCancel).toBeCalled();
42+
});
43+
44+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { renderHook } from '@testing-library/react-hooks';
2+
import { useDebounce } from '../useDebounce';
3+
4+
describe('useAsyncRequest', () => {
5+
beforeEach(() => {
6+
jest.clearAllMocks();
7+
});
8+
9+
it('handle useDebounce correctly', async () => {
10+
const mockFunction = jest.fn();
11+
const { result } = renderHook(() => useDebounce(mockFunction, 1000));
12+
13+
const debounceFunction = result.current;
14+
15+
debounceFunction();
16+
debounceFunction();
17+
debounceFunction();
18+
debounceFunction();
19+
debounceFunction();
20+
21+
await new Promise(resolve => setTimeout(resolve, 1000));
22+
23+
expect(mockFunction).toBeCalledTimes(1);
24+
});
25+
26+
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import React from 'react';
2+
import { renderHook } from '@testing-library/react-hooks';
3+
import useLongPress from '../useLongPress';
4+
import { screen, fireEvent, render, waitFor } from '@testing-library/react';
5+
6+
describe('useLongPress', () => {
7+
8+
beforeEach(() => {
9+
jest.clearAllMocks();
10+
});
11+
12+
it('handle long press correctly', async () => {
13+
const mockOnLongPress = jest.fn();
14+
const mockOnClick = jest.fn();
15+
16+
const { result } = renderHook(() => useLongPress({
17+
onLongPress: mockOnLongPress,
18+
onClick: mockOnClick,
19+
}));
20+
const { onTouchStart, onTouchEnd } = result.current;
21+
22+
const targetComponent = <div id="target" onTouchStart={onTouchStart} onTouchEnd={onTouchEnd}>touch this</div>;
23+
render(targetComponent);
24+
25+
const element = screen.getByText('touch this');
26+
fireEvent.touchStart(element);
27+
await new Promise(resolve => setTimeout(resolve, 1000));
28+
fireEvent.touchEnd(element);
29+
30+
await waitFor(() => {
31+
expect(mockOnLongPress).toHaveBeenCalled();
32+
});
33+
});
34+
35+
it('cancel long press if touch is too short', async () => {
36+
const mockOnLongPress = jest.fn();
37+
const mockOnClick = jest.fn();
38+
39+
const { result } = renderHook(() => useLongPress({
40+
onLongPress: mockOnLongPress,
41+
onClick: mockOnClick,
42+
}));
43+
const { onTouchStart, onTouchEnd } = result.current;
44+
45+
const targetComponent = <div id="target" onTouchStart={onTouchStart} onTouchEnd={onTouchEnd}>touch this</div>;
46+
render(targetComponent);
47+
48+
const element = screen.getByText('touch this');
49+
fireEvent.touchStart(element);
50+
await new Promise(resolve => setTimeout(resolve, 100));
51+
fireEvent.touchEnd(element);
52+
53+
await waitFor(() => {
54+
expect(mockOnClick).toHaveBeenCalled();
55+
expect(mockOnLongPress).not.toHaveBeenCalled();
56+
});
57+
});
58+
59+
});
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React from 'react';
2+
import { renderHook } from '@testing-library/react-hooks';
3+
import { screen, fireEvent, render, waitFor } from '@testing-library/react';
4+
import useMouseHover from '../useMouseHover';
5+
6+
describe('useMouseHover', () => {
7+
8+
beforeEach(() => {
9+
jest.clearAllMocks();
10+
});
11+
12+
it('handle mouse over and out correctly', async () => {
13+
const mockSetHover = jest.fn();
14+
15+
const targetComponent = <div id="target">hover</div>;
16+
render(targetComponent);
17+
18+
const hoverElement = screen.getByText('hover');
19+
const ref = {
20+
current: hoverElement,
21+
};
22+
23+
renderHook(() => useMouseHover({
24+
ref,
25+
setHover: mockSetHover,
26+
}));
27+
28+
fireEvent.mouseEnter(hoverElement);
29+
fireEvent.mouseLeave(hoverElement);
30+
31+
await waitFor(() => {
32+
expect(mockSetHover).toHaveBeenCalledTimes(2);
33+
expect(mockSetHover).toHaveBeenCalledWith(true);
34+
expect(mockSetHover).toHaveBeenCalledWith(false);
35+
});
36+
});
37+
38+
});
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from 'react';
2+
import { renderHook } from '@testing-library/react-hooks';
3+
import { screen, fireEvent, render, waitFor } from '@testing-library/react';
4+
import useOutsideAlerter from '../useOutsideAlerter';
5+
6+
describe('useOutsideAlerter', () => {
7+
8+
beforeEach(() => {
9+
jest.clearAllMocks();
10+
});
11+
12+
it('handle click outside correctly', async () => {
13+
const mockClickOutside = jest.fn();
14+
15+
const targetComponent = <div id="target">inside</div>;
16+
render(targetComponent);
17+
18+
const insideElement = screen.getByText('inside');
19+
const ref = {
20+
current: insideElement,
21+
};
22+
23+
renderHook(() => useOutsideAlerter({
24+
ref,
25+
callback: mockClickOutside,
26+
}));
27+
28+
fireEvent.mouseDown(insideElement);
29+
30+
await waitFor(() => {
31+
expect(mockClickOutside).toHaveBeenCalledTimes(1);
32+
});
33+
});
34+
35+
});
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { renderHook } from '@testing-library/react-hooks';
2+
import { useThrottleCallback } from '../useThrottleCallback';
3+
4+
describe('useThrottleCallback', () => {
5+
6+
beforeEach(() => {
7+
jest.clearAllMocks();
8+
});
9+
10+
it('handle throttle callback correctly when leading is true', async () => {
11+
const mockCallback = jest.fn();
12+
13+
const { result: { current: throttleCallback } } = renderHook(() => useThrottleCallback(mockCallback, 1000, { leading: true }));
14+
15+
throttleCallback();
16+
throttleCallback();
17+
throttleCallback();
18+
throttleCallback();
19+
throttleCallback();
20+
21+
await new Promise(resolve => setTimeout(resolve, 100));
22+
expect(mockCallback).toHaveBeenCalledTimes(1);
23+
24+
await new Promise(resolve => setTimeout(resolve, 1000));
25+
expect(mockCallback).toHaveBeenCalledTimes(1);
26+
27+
});
28+
29+
it('handle throttle callback correctly when trailing is true', async () => {
30+
const mockCallback = jest.fn();
31+
32+
const { result: { current: throttleCallback } } = renderHook(() => useThrottleCallback(mockCallback, 1000, { trailing: true }));
33+
34+
throttleCallback();
35+
throttleCallback();
36+
throttleCallback();
37+
throttleCallback();
38+
throttleCallback();
39+
40+
await new Promise(resolve => setTimeout(resolve, 100));
41+
expect(mockCallback).toHaveBeenCalledTimes(0);
42+
43+
await new Promise(resolve => setTimeout(resolve, 1000));
44+
expect(mockCallback).toHaveBeenCalledTimes(1);
45+
46+
await new Promise(resolve => setTimeout(resolve, 1000));
47+
expect(mockCallback).toHaveBeenCalledTimes(1);
48+
});
49+
50+
});

src/hooks/useThrottleCallback.ts

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -46,43 +46,3 @@ export function useThrottleCallback<T extends(...args: any[]) => void>(
4646
timer.current = setTimeout(invoke, delay);
4747
}) as T;
4848
}
49-
50-
/**
51-
* Note: `leading` has higher priority rather than `trailing`
52-
* */
53-
export function throttle<T extends(...args: any[]) => void>(
54-
callback: T,
55-
delay: number,
56-
options: { leading?: boolean; trailing?: boolean } = {
57-
leading: true,
58-
trailing: false,
59-
},
60-
) {
61-
let timer: ReturnType<typeof setTimeout> | null = null;
62-
let trailingArgs: null | any[] = null;
63-
64-
return ((...args: any[]) => {
65-
if (timer) {
66-
trailingArgs = args;
67-
return;
68-
}
69-
70-
if (options.leading) {
71-
callback(...args);
72-
} else {
73-
trailingArgs = args;
74-
}
75-
76-
const invoke = () => {
77-
if (options.trailing && trailingArgs) {
78-
callback(...trailingArgs);
79-
trailingArgs = null;
80-
timer = setTimeout(invoke, delay);
81-
} else {
82-
timer = null;
83-
}
84-
};
85-
86-
timer = setTimeout(invoke, delay);
87-
}) as T;
88-
}

0 commit comments

Comments
 (0)