Skip to content

Commit 65f9ba5

Browse files
committed
main 🧊 add test device pixel ratio
1 parent e0f7574 commit 65f9ba5

File tree

10 files changed

+156
-135
lines changed

10 files changed

+156
-135
lines changed

‎src/hooks/useBattery/useBattery.test.ts‎

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { act, renderHook, waitFor } from '@testing-library/react';
33
import { useBattery } from './useBattery';
44

55
const events: Record<string, () => void> = {};
6-
const batteryManager = {
6+
const mockBatteryManager = {
77
charging: true,
88
chargingTime: 0,
99
dischargingTime: 5,
@@ -23,7 +23,7 @@ const batteryManager = {
2323
};
2424

2525
beforeEach(() => {
26-
const mockNavigatorGetBattery = vi.fn(() => Promise.resolve(batteryManager));
26+
const mockNavigatorGetBattery = vi.fn(() => Promise.resolve(mockBatteryManager));
2727
Object.assign(navigator, {
2828
getBattery: mockNavigatorGetBattery
2929
});
@@ -59,12 +59,32 @@ it('Should use battery', async () => {
5959
);
6060
});
6161

62+
it('Should correct return for unsupported', async () => {
63+
Object.assign(navigator, {
64+
getBattery: undefined
65+
});
66+
const { result } = renderHook(useBattery);
67+
68+
await waitFor(() =>
69+
expect(result.current).toEqual({
70+
supported: false,
71+
value: {
72+
charging: false,
73+
chargingTime: 0,
74+
dischargingTime: 0,
75+
level: 0,
76+
loading: false
77+
}
78+
})
79+
);
80+
});
81+
6282
it('Should handle levelchange event', async () => {
6383
const { result } = renderHook(useBattery);
6484

6585
act(() => {
66-
batteryManager.level = 2;
67-
batteryManager.dispatchEvent(new Event('levelchange'));
86+
mockBatteryManager.level = 2;
87+
mockBatteryManager.dispatchEvent(new Event('levelchange'));
6888
});
6989

7090
await waitFor(() =>
@@ -85,8 +105,8 @@ it('Should handle chargingchange event', async () => {
85105
const { result } = renderHook(useBattery);
86106

87107
act(() => {
88-
batteryManager.charging = false;
89-
batteryManager.dispatchEvent(new Event('chargingchange'));
108+
mockBatteryManager.charging = false;
109+
mockBatteryManager.dispatchEvent(new Event('chargingchange'));
90110
});
91111

92112
await waitFor(() =>
@@ -107,8 +127,8 @@ it('Should handle chargingtimechange event', async () => {
107127
const { result } = renderHook(useBattery);
108128

109129
act(() => {
110-
batteryManager.chargingTime = 1;
111-
batteryManager.dispatchEvent(new Event('chargingtimechange'));
130+
mockBatteryManager.chargingTime = 1;
131+
mockBatteryManager.dispatchEvent(new Event('chargingtimechange'));
112132
});
113133

114134
await waitFor(() =>
@@ -129,8 +149,8 @@ it('Should handle dischargingtimechange event', async () => {
129149
const { result } = renderHook(useBattery);
130150

131151
act(() => {
132-
batteryManager.dischargingTime = 6;
133-
batteryManager.dispatchEvent(new Event('dischargingtimechange'));
152+
mockBatteryManager.dischargingTime = 6;
153+
mockBatteryManager.dispatchEvent(new Event('dischargingtimechange'));
134154
});
135155

136156
await waitFor(() =>

‎src/hooks/useBattery/useBattery.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export interface UseBatteryValue {
2929

3030
/** The use battery return type */
3131
export interface UseBatteryStateReturn {
32-
/** Battery API support */
32+
/** Whether the battery api is supported*/
3333
supported: boolean;
3434
/** The use battery value type */
3535
value: UseBatteryValue;

‎src/hooks/useBluetooth/useBluetooth.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export interface UseBluetoothReturn {
88
device?: BluetoothDevice;
99
/** The GATT server for connected bluetooth device */
1010
server?: BluetoothRemoteGATTServer;
11-
/** Indicates if bluetooth API is supported by the browser */
11+
/** Whether the bluetooth is supported*/
1212
supported: boolean;
1313
/** Function to request bluetooth device from the user */
1414
requestDevice: () => Promise<void>;

‎src/hooks/useDevicePixelRatio/useDevicePixelRatio.demo.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const Demo = () => {
77
return <p>Device pixel ratio is not supported.</p>;
88
}
99

10-
return <p>Device pixel ratio (try to zoom page in and out): {ratio}</p>;
10+
return <p>Device pixel ratio (try to zoom page in and out): <code>{ratio}</code></p>;
1111
};
1212

1313
export default Demo;
Lines changed: 91 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,98 @@
11
import { act, renderHook } from '@testing-library/react';
22

3+
import { createTrigger } from '@/tests';
4+
35
import { useDevicePixelRatio } from './useDevicePixelRatio';
46

5-
describe('useDevicePixelRatio', () => {
6-
describe('in unsupported environment', () => {
7-
afterEach(() => void vi.unstubAllGlobals());
8-
9-
it('should return supported as false when devicePixelRatio is not present', () => {
10-
vi.stubGlobal('devicePixelRatio', undefined);
11-
const { result } = renderHook(() => useDevicePixelRatio());
12-
expect(result.current.supported).toBeFalsy();
13-
expect(result.current.ratio).toBeNull();
14-
});
15-
16-
it('should return supported as false when matchMedia is not a function', () => {
17-
vi.stubGlobal('matchMedia', undefined);
18-
const { result } = renderHook(() => useDevicePixelRatio());
19-
expect(result.current.supported).toBeFalsy();
20-
expect(result.current.ratio).toBeNull();
21-
});
22-
});
7+
const trigger = createTrigger<() => void, string>();
8+
const mockMediaQueryListAddEventListener = vi.fn();
9+
const mockMediaQueryListRemoveEventListener = vi.fn();
10+
const MockMediaQueryList = class MediaQueryList {
11+
query: string;
12+
13+
constructor(query: string) {
14+
this.query = query;
15+
}
16+
17+
addEventListener = (_type: keyof MediaQueryListEventMap, listener: any) => {
18+
trigger.add(this.query, listener);
19+
mockMediaQueryListAddEventListener();
20+
};
21+
removeEventListener = () => {
22+
trigger.delete(this.query);
23+
mockMediaQueryListRemoveEventListener();
24+
};
25+
26+
matches = false;
27+
onchange = vi.fn();
28+
addListener = vi.fn();
29+
removeListener = vi.fn();
30+
dispatchEvent = vi.fn();
31+
};
32+
33+
beforeEach(() => {
34+
vi.stubGlobal('devicePixelRatio', 1);
35+
vi.stubGlobal(
36+
'matchMedia',
37+
vi.fn<[string], MediaQueryList>().mockImplementation((query) => {
38+
const mockMediaQueryList = new MockMediaQueryList(query);
39+
return { ...mockMediaQueryList, media: query };
40+
})
41+
);
42+
});
43+
44+
afterEach(() => {
45+
void vi.unstubAllGlobals();
46+
mockMediaQueryListAddEventListener.mockClear();
47+
mockMediaQueryListRemoveEventListener.mockClear();
48+
});
49+
50+
it('Should use device pixel ratio', () => {
51+
const { result } = renderHook(useDevicePixelRatio);
52+
53+
expect(result.current.ratio).toEqual(window.devicePixelRatio);
54+
expect(result.current.supported).toBeTruthy();
55+
});
56+
57+
it('Should correct return for unsupported matchMedia', () => {
58+
vi.stubGlobal('matchMedia', undefined);
59+
const { result } = renderHook(useDevicePixelRatio);
60+
61+
expect(result.current.ratio).toEqual(1);
62+
expect(result.current.supported).toBeFalsy();
63+
});
2364

24-
describe('in supported environment', () => {
25-
const mediaQueryListMock = {
26-
matches: false,
27-
onchange: null,
28-
addListener: vi.fn(),
29-
removeListener: vi.fn(),
30-
addEventListener: vi.fn(),
31-
removeEventListener: vi.fn(),
32-
dispatchEvent: vi.fn()
33-
};
34-
35-
beforeEach(() => {
36-
vi.stubGlobal('devicePixelRatio', 2);
37-
vi.stubGlobal(
38-
'matchMedia',
39-
vi
40-
.fn<[string], MediaQueryList>()
41-
.mockImplementation((query) => ({ ...mediaQueryListMock, media: query }))
42-
);
43-
});
44-
45-
afterEach(() => void vi.unstubAllGlobals());
46-
47-
it('should return initial devicePixelRatio and supported', () => {
48-
const { result } = renderHook(() => useDevicePixelRatio());
49-
expect(result.current.supported).toBeTruthy();
50-
expect(result.current.ratio).toEqual(2);
51-
});
52-
53-
it('should return ratio when devicePixelRatio changes', () => {
54-
const { result } = renderHook(() => useDevicePixelRatio());
55-
expect(result.current.ratio).toEqual(2);
56-
57-
const listener = mediaQueryListMock.addEventListener.mock.calls[0][1];
58-
expect(typeof listener).toEqual('function');
59-
60-
Object.defineProperty(window, 'devicePixelRatio', {
61-
value: 3,
62-
configurable: true
63-
});
64-
65-
act(() => {
66-
listener(new MediaQueryListEvent('change'));
67-
});
68-
69-
expect(result.current.ratio).toEqual(3);
70-
});
71-
72-
it('should remove event listener on unmount', () => {
73-
const { unmount } = renderHook(() => useDevicePixelRatio());
74-
75-
expect(mediaQueryListMock.addEventListener).toHaveBeenCalledWith(
76-
'change',
77-
expect.any(Function)
78-
);
79-
80-
unmount();
81-
82-
expect(mediaQueryListMock.removeEventListener).toHaveBeenCalledWith(
83-
'change',
84-
expect.any(Function)
85-
);
86-
});
65+
it('Should correct return for unsupported devicePixelRatio', () => {
66+
vi.stubGlobal('devicePixelRatio', undefined);
67+
const { result } = renderHook(useDevicePixelRatio);
68+
69+
expect(result.current.ratio).toEqual(1);
70+
expect(result.current.supported).toBeFalsy();
71+
});
72+
73+
it('Should handle media query change', () => {
74+
const { result } = renderHook(useDevicePixelRatio);
75+
expect(result.current.ratio).toEqual(1);
76+
77+
Object.defineProperty(window, 'devicePixelRatio', {
78+
value: 3,
79+
configurable: true
8780
});
81+
82+
expect(mockMediaQueryListAddEventListener).toHaveBeenCalledTimes(1);
83+
expect(mockMediaQueryListRemoveEventListener).toHaveBeenCalledTimes(0);
84+
85+
act(() => trigger.callback(`(resolution: 1dppx)`));
86+
87+
expect(mockMediaQueryListAddEventListener).toHaveBeenCalledTimes(2);
88+
expect(mockMediaQueryListRemoveEventListener).toHaveBeenCalledTimes(1);
89+
expect(result.current.ratio).toEqual(3);
90+
});
91+
92+
it('Should disconnect on onmount', () => {
93+
const { unmount } = renderHook(useDevicePixelRatio);
94+
95+
unmount();
96+
97+
expect(mockMediaQueryListRemoveEventListener).toHaveBeenCalledTimes(1);
8898
});
Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { useEffect, useState } from 'react';
22

3+
/** The use device pixel ratio return type */
34
export interface UseDevicePixelRatioReturn {
4-
/** The ratio of the resolution in physical pixels to the resolution in CSS pixels for the current display device. Equals to null if only `supported` = `false`. */
5-
ratio: number | null;
6-
/** Indicates if devicePixelRatio is available */
5+
/** The ratio of the resolution in physical pixels to the resolution in CSS pixels */
6+
ratio: number;
7+
/** Whether the device pixel ratio is supported*/
78
supported: boolean;
89
}
910

@@ -18,24 +19,24 @@ export interface UseDevicePixelRatioReturn {
1819
* const { supported, ratio } = useDevicePixelRatio();
1920
*/
2021
export const useDevicePixelRatio = (): UseDevicePixelRatioReturn => {
21-
const [ratio, setRatio] = useState<number | null>(null);
22-
2322
const supported =
2423
typeof window !== 'undefined' &&
2524
typeof window.matchMedia === 'function' &&
2625
typeof window.devicePixelRatio === 'number';
2726

27+
const [ratio, setRatio] = useState<number>(window.devicePixelRatio ?? 1);
28+
2829
useEffect(() => {
2930
if (!supported) return;
3031

3132
const updatePixelRatio = () => setRatio(window.devicePixelRatio);
3233

33-
updatePixelRatio();
34-
3534
const media = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
3635
media.addEventListener('change', updatePixelRatio);
37-
return () => media.removeEventListener('change', updatePixelRatio);
38-
}, [supported]);
36+
return () => {
37+
media.removeEventListener('change', updatePixelRatio);
38+
};
39+
}, [ratio]);
3940

4041
return { supported, ratio };
4142
};

‎src/hooks/useElementSize/useElementSize.test.ts‎

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,14 @@
11
import { act, renderHook } from '@testing-library/react';
22

3+
import { createTrigger } from '@/tests';
34
import { getElement } from '@/utils/helpers';
45

56
import type { StateRef } from '../useRefState/useRefState';
67
import type { UseElementSizeReturn } from './useElementSize';
78

89
import { useElementSize } from './useElementSize';
910

10-
export function createTrigger<Callback extends (...args: any[]) => void>() {
11-
const observers = new Map();
12-
return {
13-
callback(element: Element, ...args: Partial<Parameters<Callback>>) {
14-
const observe = observers.get(element);
15-
observe(...args);
16-
},
17-
add(element: Element, callback: Callback) {
18-
observers.set(element, callback);
19-
},
20-
delete(element: Element) {
21-
observers.delete(element);
22-
}
23-
};
24-
}
25-
26-
const trigger = createTrigger<ResizeObserverCallback>();
11+
const trigger = createTrigger<ResizeObserverCallback, Element>();
2712
const mockResizeObserverDisconnect = vi.fn();
2813
const mockResizeObserverObserve = vi.fn();
2914
const mockResizeObserver = class ResizeObserver {
@@ -44,7 +29,9 @@ const mockResizeObserver = class ResizeObserver {
4429
};
4530
unobserve = vi.fn();
4631
};
47-
globalThis.ResizeObserver = mockResizeObserver;
32+
33+
beforeEach(() => void vi.stubGlobal('ResizeObserver', mockResizeObserver));
34+
afterEach(() => void vi.unstubAllGlobals());
4835

4936
const targets = [
5037
undefined,

‎tests/createTrigger.ts‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export function createTrigger<Callback extends (...args: any[]) => void, Key>() {
2+
const observers = new Map();
3+
return {
4+
callback(key: Key, ...args: Partial<Parameters<Callback>>) {
5+
const observe = observers.get(key);
6+
observe(...args);
7+
},
8+
add(key: Key, callback: Callback) {
9+
observers.set(key, callback);
10+
},
11+
delete(key: Key) {
12+
observers.delete(key);
13+
}
14+
};
15+
}

0 commit comments

Comments
 (0)