Skip to content

Commit 3ec612c

Browse files
author
babin
committed
main 🧊 add use element size test
1 parent 143c9c2 commit 3ec612c

23 files changed

+221
-65
lines changed

‎src/hooks/useAsync/useAsync.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ export interface UseAsyncReturn<Data> {
2020
* @category Utilities
2121
*
2222
* @param {() => Promise<Data>} callback - The async callback
23-
* @param {DependencyList} [deps] - The dependencies of the callback
23+
* @param {DependencyList} deps - The dependencies of the callback
2424
* @returns {UseAsyncReturn<Data>} - The state of the async callback
2525
*
2626
* @example
2727
* const { data, isLoading, isError, error } = useAsync(() => fetch('url'), [deps]);
2828
*/
2929
export const useAsync = <Data>(
3030
callback: () => Promise<Data>,
31-
deps?: DependencyList
31+
deps: DependencyList
3232
): UseAsyncReturn<Data> => {
3333
const [isLoading, setIsLoading] = useState(false);
3434
const [isError, setIsError] = useState(false);

‎src/hooks/useBoolean/useBoolean.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ it('Should use counter', () => {
77
const [on, toggle] = result.current;
88

99
expect(on).toBeFalsy();
10-
expect(typeof toggle).toBe('function');
10+
expect(toggle).toBeTypeOf('function');
1111
});
1212

1313
it('Should set initial value', () => {

‎src/hooks/useClickOutside/useClickOutside.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useClickOutside } from './useClickOutside';
55
it('Should use click outside', () => {
66
const { result } = renderHook(() => useClickOutside(vi.fn()));
77

8-
expect(typeof result.current).toBe('function');
8+
expect(result.current).toBeTypeOf('function');
99
});
1010

1111
it('Should call callback when ref connected to the document', () => {

‎src/hooks/useClipboard/useClipboard.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ it('Should use copy to clipboard', () => {
2424

2525
expect(result.current.value).toBeNull();
2626
expect(result.current.supported).toBeTruthy();
27-
expect(typeof result.current.copy).toBe('function');
27+
expect(result.current.copy).toBeTypeOf('function');
2828
});
2929

3030
it('Should copy value to clipboard', async () => {

‎src/hooks/useCounter/useCounter.test.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ it('Should use counter', () => {
66
const { result } = renderHook(useCounter);
77

88
expect(result.current.value).toBe(0);
9-
expect(typeof result.current.inc).toBe('function');
10-
expect(typeof result.current.dec).toBe('function');
11-
expect(typeof result.current.reset).toBe('function');
12-
expect(typeof result.current.set).toBe('function');
9+
expect(result.current.inc).toBeTypeOf('function');
10+
expect(result.current.dec).toBeTypeOf('function');
11+
expect(result.current.reset).toBeTypeOf('function');
12+
expect(result.current.set).toBeTypeOf('function');
1313
});
1414

1515
it('Should set initial value', () => {

‎src/hooks/useDefault/useDefault.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ it('Should use default', () => {
77
const [state, setState] = result.current;
88

99
expect(state).toBe(5);
10-
expect(typeof setState).toBe('function');
10+
expect(setState).toBeTypeOf('function');
1111
});
1212

1313
it('Should return initial value', () => {

‎src/hooks/useDisclosure/useDisclosure.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ it('Should use counter', () => {
66
const { result } = renderHook(useDisclosure);
77

88
expect(result.current.opened).toBeFalsy();
9-
expect(typeof result.current.open).toBe('function');
10-
expect(typeof result.current.close).toBe('function');
11-
expect(typeof result.current.toggle).toBe('function');
9+
expect(result.current.open).toBeTypeOf('function');
10+
expect(result.current.close).toBeTypeOf('function');
11+
expect(result.current.toggle).toBeTypeOf('function');
1212
});
1313

1414
it('Should set initial value', () => {

‎src/hooks/useDocumentTitle/useDocumentTitle.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ it('Should use document title', () => {
1111
const [title, setTitle] = result.current;
1212

1313
expect(title).toBe('default title');
14-
expect(typeof setTitle).toBe('function');
14+
expect(setTitle).toBeTypeOf('function');
1515
});
1616

1717
it('Should be set initial title', () => {

‎src/hooks/useElementSize/useElementSize.demo.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const Demo = () => {
88
<p>Resize the box to see changes</p>
99
<textarea
1010
ref={elementSize.ref}
11+
className='h-[200px] w-[200px]'
1112
style={{ resize: 'both' }}
1213
value={`width: ${elementSize.value.width}\nheight: ${elementSize.value.height}`}
1314
/>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { act, renderHook } from '@testing-library/react';
2+
3+
import type { HookRef } from '@/utils/helpers';
4+
5+
import { getElement } from '@/utils/helpers';
6+
7+
import type { UseElementSizeReturn } from './useElementSize';
8+
9+
import { useElementSize } from './useElementSize';
10+
11+
export function createTrigger<Callback extends (...args: any[]) => void>() {
12+
const observers = new Map();
13+
return {
14+
callback(target: Element, ...args: Partial<Parameters<Callback>>) {
15+
const observe = observers.get(target);
16+
observe(...args);
17+
},
18+
add(element: Element, callback: Callback) {
19+
observers.set(element, callback);
20+
},
21+
delete(element: Element) {
22+
observers.delete(element);
23+
}
24+
};
25+
}
26+
27+
const trigger = createTrigger<ResizeObserverCallback>();
28+
const mockResizeObserverDisconnect = vi.fn();
29+
const mockResizeObserverObserve = vi.fn();
30+
const mockResizeObserver = class ResizeObserver {
31+
callback: ResizeObserverCallback;
32+
element: Element | undefined;
33+
34+
constructor(callback: ResizeObserverCallback) {
35+
this.callback = callback;
36+
}
37+
38+
observe = (element: Element) => {
39+
trigger.add(element, this.callback);
40+
mockResizeObserverObserve();
41+
};
42+
disconnect = () => {
43+
if (this.element) trigger.delete(this.element);
44+
mockResizeObserverDisconnect();
45+
};
46+
unobserve = vi.fn();
47+
};
48+
globalThis.ResizeObserver = mockResizeObserver;
49+
50+
const targets = [
51+
undefined,
52+
'#target',
53+
document.getElementById('target'),
54+
{ current: document.getElementById('target') }
55+
];
56+
57+
targets.forEach((target) => {
58+
beforeEach(() => {
59+
mockResizeObserverObserve.mockClear();
60+
mockResizeObserverDisconnect.mockClear();
61+
});
62+
63+
describe(`${target}`, () => {
64+
it('Should use element size', () => {
65+
const { result } = renderHook(() => {
66+
if (target)
67+
return useElementSize(target) as {
68+
ref: HookRef<HTMLDivElement>;
69+
} & UseElementSizeReturn;
70+
return useElementSize<HTMLDivElement>();
71+
});
72+
expect(result.current.value).toStrictEqual({ width: 0, height: 0 });
73+
if (!target) expect(result.current.ref).toBeTypeOf('function');
74+
});
75+
76+
it('Should set intitial value', () => {
77+
const { result } = renderHook(() => {
78+
if (target) return useElementSize(target, { width: 200, height: 200 });
79+
return useElementSize<HTMLDivElement>({ width: 200, height: 200 });
80+
});
81+
82+
expect(result.current.value).toStrictEqual({ width: 200, height: 200 });
83+
});
84+
85+
it('Should change value after resize', () => {
86+
const { result } = renderHook(() => {
87+
if (target)
88+
return useElementSize(target) as {
89+
ref: HookRef<HTMLDivElement>;
90+
} & UseElementSizeReturn;
91+
return useElementSize<HTMLDivElement>();
92+
});
93+
94+
expect(result.current.value).toStrictEqual({ width: 0, height: 0 });
95+
96+
if (!target)
97+
act(() => result.current.ref(document.getElementById('target')! as HTMLDivElement));
98+
99+
act(() => {
100+
const element = (target ? getElement(target) : result.current.ref.current) as Element;
101+
if (!element) return;
102+
103+
trigger.callback(element, [
104+
{ contentRect: { width: 200, height: 200 } }
105+
] as unknown as ResizeObserverEntry[]);
106+
});
107+
108+
expect(mockResizeObserverObserve).toHaveBeenCalledTimes(1);
109+
expect(result.current.value).toStrictEqual({ width: 200, height: 200 });
110+
});
111+
112+
it('Should disconnect on onmount', () => {
113+
const { result, unmount } = renderHook(() => {
114+
if (target)
115+
return useElementSize(target) as {
116+
ref: HookRef<HTMLDivElement>;
117+
} & UseElementSizeReturn;
118+
return useElementSize<HTMLDivElement>();
119+
});
120+
121+
if (!target)
122+
act(() => result.current.ref(document.getElementById('target')! as HTMLDivElement));
123+
124+
unmount();
125+
126+
expect(mockResizeObserverDisconnect).toHaveBeenCalledTimes(1);
127+
});
128+
});
129+
});

‎src/hooks/useElementSize/useElementSize.ts

+22-24
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import type { RefObject } from 'react';
22

33
import { useEffect, useState } from 'react';
44

5-
import { getElement } from '@/utils/helpers';
5+
import type { HookRef } from '@/utils/helpers';
6+
7+
import { createHookRef, getElement, isTarget } from '@/utils/helpers';
68

79
//* The element size value type */
810
export interface UseElementSizeValue {
@@ -11,12 +13,9 @@ export interface UseElementSizeValue {
1113
}
1214

1315
/** The use element size target element type */
14-
export type UseElementSizeTarget =
15-
| (() => Element)
16-
| string
17-
| Element
18-
| RefObject<Element | null | undefined>;
16+
export type UseElementSizeTarget = string | Element | RefObject<Element | null | undefined>;
1917

18+
/** The use element size return type */
2019
export interface UseElementSizeReturn {
2120
value: UseElementSizeValue;
2221
}
@@ -30,7 +29,7 @@ export interface UseElementSize {
3029
<Target extends UseElementSizeTarget>(
3130
initialValue?: UseElementSizeValue,
3231
target?: never
33-
): { ref: (node: Target) => void } & UseElementSizeReturn;
32+
): { ref: HookRef<Target> } & UseElementSizeReturn;
3433
}
3534

3635
/**
@@ -39,29 +38,24 @@ export interface UseElementSize {
3938
* @category Elements
4039
*
4140
* @overload
42-
* @template Target The target element type.
43-
* @param {UseElementSizeTarget} target The target element to observe.
44-
* @param {UseElementSizeValue} [initialValue = { width: 0, height: 0 }]
45-
* @returns {UseElementSizeReturn} An object containing the current width and height of the element.
41+
* @template Target The target element type
42+
* @param {UseElementSizeTarget} target The target element to observe
43+
* @param {UseElementSizeValue} [initialValue] The initial size of the element.
44+
* @returns {UseElementSizeReturn} An object containing the current width and height of the element
4645
*
4746
* @example
48-
* const { value } = useElementSize(elementRef);
47+
* const { value } = useElementSize(ref);
4948
*
5049
* @overload
51-
* @param {UseElementSizeValue} [initialValue = { width: 0, height: 0 }] The initial size of the element.
52-
* @returns { { ref: (node: Target) => void } & UseElementSizeReturn } An object containing the current width and height of the element.
50+
* @param {UseElementSizeValue} [initialValue] The initial size of the element
51+
* @returns { { ref: (node: Target) => void } & UseElementSizeReturn } An object containing the current width and height of the element
5352
*
5453
* @example
55-
* const { ref, value } = useElementSize({ width: 100, height: 100 });
54+
* const { ref, value } = useElementSize();
5655
*/
5756
export const useElementSize = ((...params: any[]) => {
58-
const target = (
59-
params[0] && typeof params[0].width !== 'number' && typeof params[0].height !== 'number'
60-
? params[0]
61-
: undefined
62-
) as UseElementSizeTarget | undefined;
63-
const initialValue = (target ? params[1] : params[0]) as UseElementSizeValue | undefined;
64-
57+
const target = (isTarget(params[0]) ? params[0] : undefined) as UseElementSizeTarget | undefined;
58+
const initialValue = (target ? params[1] : params[0]) as UseElementSizeTarget | undefined;
6559
const [size, setSize] = useState(initialValue ?? { width: 0, height: 0 });
6660
const [internalRef, setInternalRef] = useState<Element>();
6761

@@ -70,8 +64,9 @@ export const useElementSize = ((...params: any[]) => {
7064
const element = (target ? getElement(target) : internalRef) as Element;
7165

7266
if (!element) return;
67+
7368
const observer = new ResizeObserver(([entry]) => {
74-
const { inlineSize: width, blockSize: height } = entry.borderBoxSize[0];
69+
const { width, height } = entry.contentRect;
7570
setSize({ width, height });
7671
});
7772

@@ -83,5 +78,8 @@ export const useElementSize = ((...params: any[]) => {
8378
}, [internalRef, target]);
8479

8580
if (target) return { value: size };
86-
return { ref: setInternalRef, value: size };
81+
return {
82+
ref: createHookRef(internalRef, setInternalRef),
83+
value: size
84+
};
8785
}) as UseElementSize;

‎src/hooks/useFavicon/useFavicon.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ it('Should use favicon', () => {
1111
const { result } = renderHook(useFavicon);
1212

1313
expect(result.current.href).toBe(undefined);
14-
expect(typeof result.current.set).toBe('function');
14+
expect(result.current.set).toBeTypeOf('function');
1515
});
1616

1717
it('Should be set initial favicon', () => {

‎src/hooks/useInterval/useInterval.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ beforeEach(() => {
99
it('Should use interval', () => {
1010
const { result } = renderHook(() => useInterval(vi.fn, 1000));
1111
expect(result.current.active).toBeTruthy();
12-
expect(typeof result.current.pause).toBe('function');
13-
expect(typeof result.current.resume).toBe('function');
12+
expect(result.current.pause).toBeTypeOf('function');
13+
expect(result.current.resume).toBeTypeOf('function');
1414
});
1515

1616
it('Should pause and resume properly', () => {

‎src/hooks/useMap/useMap.test.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ it('Should use map', () => {
88

99
expect(result.current.value).toEqual(new Map());
1010
expect(result.current.size).toBe(0);
11-
expect(typeof result.current.set).toBe('function');
12-
expect(typeof result.current.remove).toBe('function');
13-
expect(typeof result.current.clear).toBe('function');
14-
expect(typeof result.current.reset).toBe('function');
11+
expect(result.current.set).toBeTypeOf('function');
12+
expect(result.current.remove).toBeTypeOf('function');
13+
expect(result.current.clear).toBeTypeOf('function');
14+
expect(result.current.reset).toBeTypeOf('function');
1515
});
1616

1717
it('Should set initial value', () => {

‎src/hooks/useOrientation/useOrientation.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import { useState } from 'react';
2-
3-
import { useIsomorphicLayoutEffect } from '../useIsomorphicLayoutEffect/useIsomorphicLayoutEffect';
1+
import { useEffect, useState } from 'react';
42

53
/** The use orientation return type */
64
export interface UseOrientationReturn {
@@ -26,7 +24,7 @@ export const useOrientation = (): UseOrientationReturn => {
2624
type: OrientationType;
2725
}>({ angle: 0, type: 'landscape-primary' });
2826

29-
useIsomorphicLayoutEffect(() => {
27+
useEffect(() => {
3028
const onChange = () => {
3129
const { angle, type } = window.screen.orientation;
3230
setOrientation({
@@ -35,8 +33,8 @@ export const useOrientation = (): UseOrientationReturn => {
3533
});
3634
};
3735

38-
window.screen.orientation?.addEventListener('change', onChange);
39-
return () => window.screen.orientation?.removeEventListener('change', onChange);
36+
window.screen.orientation.addEventListener('change', onChange);
37+
return () => window.screen.orientation.removeEventListener('change', onChange);
4038
}, []);
4139

4240
return orientation;

0 commit comments

Comments
 (0)