Skip to content

Commit adb0873

Browse files
authored
Merge pull request #450 from sad1k/test/usePerformanceObserver
🧊 [test]: usePerformanceObserver
2 parents 52d5ac6 + 5cda3d3 commit adb0873

1 file changed

Lines changed: 166 additions & 0 deletions

File tree

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import { act, renderHook } from '@testing-library/react';
2+
import { vi } from 'vitest';
3+
4+
import { renderHookServer } from '@/tests';
5+
6+
import { usePerformanceObserver } from './usePerformanceObserver';
7+
8+
const mockObserve = vi.fn();
9+
const mockDisconnect = vi.fn();
10+
11+
let observerCallback: PerformanceObserverCallback;
12+
13+
const createMockPerformanceEntry = (
14+
name = 'test-entry',
15+
entryType = 'measure',
16+
startTime = 0,
17+
duration = 100
18+
): PerformanceEntry => ({
19+
name,
20+
entryType,
21+
startTime,
22+
duration,
23+
toJSON: () => ({})
24+
});
25+
26+
class MockPerformanceObserver {
27+
constructor(callback: PerformanceObserverCallback) {
28+
observerCallback = callback;
29+
}
30+
31+
observe = (options?: PerformanceObserverInit) => mockObserve(options);
32+
disconnect = () => mockDisconnect();
33+
}
34+
35+
globalThis.PerformanceObserver = MockPerformanceObserver as any;
36+
37+
afterEach(() => {
38+
vi.clearAllMocks();
39+
});
40+
41+
it('Should use performance observer', () => {
42+
const { result } = renderHook(() => usePerformanceObserver({ entryTypes: ['measure'] }));
43+
44+
expect(result.current.supported).toBe(true);
45+
expect(result.current.entries).toEqual([]);
46+
expect(result.current.start).toBeTypeOf('function');
47+
expect(result.current.stop).toBeTypeOf('function');
48+
});
49+
50+
it('Should use performance observer on server side', () => {
51+
const { result } = renderHookServer(() => usePerformanceObserver({ entryTypes: ['measure'] }));
52+
53+
expect(result.current.supported).toBe(false);
54+
expect(result.current.entries).toEqual([]);
55+
expect(result.current.start).toBeTypeOf('function');
56+
expect(result.current.stop).toBeTypeOf('function');
57+
});
58+
59+
it('Should start observing when immediate is true', () => {
60+
renderHook(() => usePerformanceObserver({ entryTypes: ['measure'], immediate: true }));
61+
62+
expect(mockObserve).toHaveBeenCalledOnce();
63+
expect(mockObserve).toHaveBeenCalledWith(expect.objectContaining({ entryTypes: ['measure'] }));
64+
});
65+
66+
it('Should not start observing when immediate is not set', () => {
67+
renderHook(() => usePerformanceObserver({ entryTypes: ['measure'] }));
68+
69+
expect(mockObserve).not.toHaveBeenCalled();
70+
});
71+
72+
it('Should start, stop and restart observer manually', () => {
73+
const { result } = renderHook(() => usePerformanceObserver({ entryTypes: ['measure'] }));
74+
75+
expect(mockObserve).not.toHaveBeenCalled();
76+
77+
act(() => result.current.start());
78+
79+
expect(mockObserve).toHaveBeenCalledOnce();
80+
81+
act(() => result.current.stop());
82+
83+
expect(mockDisconnect).toHaveBeenCalledOnce();
84+
85+
act(() => result.current.start());
86+
87+
expect(mockObserve).toHaveBeenCalledTimes(2);
88+
});
89+
90+
it('Should call callback when performance entries are observed', () => {
91+
const callback = vi.fn();
92+
93+
renderHook(() => usePerformanceObserver({ entryTypes: ['measure'], immediate: true }, callback));
94+
95+
const entry = createMockPerformanceEntry();
96+
const entryList = { getEntries: () => [entry] } as PerformanceObserverEntryList;
97+
98+
act(() => observerCallback(entryList, {} as PerformanceObserver));
99+
100+
expect(callback).toHaveBeenCalledOnce();
101+
expect(callback).toHaveBeenCalledWith(entryList, expect.any(Object));
102+
});
103+
104+
it('Should update entries state when performance entries are observed', () => {
105+
const { result } = renderHook(() =>
106+
usePerformanceObserver({ entryTypes: ['measure'], immediate: true })
107+
);
108+
109+
expect(result.current.entries).toEqual([]);
110+
111+
const entry = createMockPerformanceEntry('paint', 'measure', 0, 50);
112+
const entryList = { getEntries: () => [entry] } as PerformanceObserverEntryList;
113+
114+
act(() => observerCallback(entryList, {} as PerformanceObserver));
115+
116+
expect(result.current.entries).toEqual([entry]);
117+
});
118+
119+
it('Should disconnect observer on unmount', () => {
120+
const { unmount } = renderHook(() =>
121+
usePerformanceObserver({ entryTypes: ['measure'], immediate: true })
122+
);
123+
124+
unmount();
125+
126+
expect(mockDisconnect).toHaveBeenCalledOnce();
127+
});
128+
129+
it('Should not disconnect observer on unmount when not started', () => {
130+
const { unmount } = renderHook(() => usePerformanceObserver({ entryTypes: ['measure'] }));
131+
132+
unmount();
133+
134+
expect(mockDisconnect).not.toHaveBeenCalled();
135+
});
136+
137+
it('Should work without callback', () => {
138+
const { result } = renderHook(() =>
139+
usePerformanceObserver({ entryTypes: ['measure'], immediate: true })
140+
);
141+
142+
const entry = createMockPerformanceEntry();
143+
const entryList = { getEntries: () => [entry] } as PerformanceObserverEntryList;
144+
145+
act(() => observerCallback(entryList, {} as PerformanceObserver));
146+
147+
expect(result.current.entries).toEqual([entry]);
148+
});
149+
150+
it('Should update entries state with multiple entries', () => {
151+
const { result } = renderHook(() =>
152+
usePerformanceObserver({ entryTypes: ['measure'], immediate: true })
153+
);
154+
155+
const entries = [
156+
createMockPerformanceEntry('first-paint', 'paint', 0, 10),
157+
createMockPerformanceEntry('api-call', 'measure', 100, 200),
158+
createMockPerformanceEntry('lcp', 'largest-contentful-paint', 50, 150)
159+
];
160+
const entryList = { getEntries: () => entries } as PerformanceObserverEntryList;
161+
162+
act(() => observerCallback(entryList, {} as PerformanceObserver));
163+
164+
expect(result.current.entries).toEqual(entries);
165+
expect(result.current.entries).toHaveLength(3);
166+
});

0 commit comments

Comments
 (0)