@musecat/functionkit 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -1
- package/packages/cookie/cookie.shared.ts +10 -2
- package/packages/utils/floatingMotion.ts +1 -3
- package/.agents/references/components/ScrolltoTop.md +0 -36
- package/.agents/references/components/SwitchCase.md +0 -47
- package/.agents/references/components/ViewportPortal.md +0 -34
- package/.agents/references/cookie/cookie.md +0 -47
- package/.agents/references/datetime/dateTime.client.md +0 -42
- package/.agents/references/datetime/dateTime.server.md +0 -42
- package/.agents/references/datetime/dateTime.shared.md +0 -86
- package/.agents/references/hooks/useAvoidKeyboard.md +0 -29
- package/.agents/references/hooks/useCheckInvisible.md +0 -36
- package/.agents/references/hooks/useCheckScroll.md +0 -36
- package/.agents/references/hooks/useClientDateTime.md +0 -39
- package/.agents/references/hooks/useDebounce.md +0 -46
- package/.agents/references/hooks/useDebouncedCallback.md +0 -34
- package/.agents/references/hooks/useDoubleClick.md +0 -40
- package/.agents/references/hooks/useGeolocation.md +0 -37
- package/.agents/references/hooks/useHasMounted.md +0 -27
- package/.agents/references/hooks/useIntersectionObserver.md +0 -41
- package/.agents/references/hooks/useInterval.md +0 -44
- package/.agents/references/hooks/useKeyboardHeight.md +0 -33
- package/.agents/references/hooks/useKeyboardListNavigation.md +0 -58
- package/.agents/references/hooks/useLongPress.md +0 -48
- package/.agents/references/hooks/usePreservedCallback.md +0 -37
- package/.agents/references/hooks/usePreservedReference.md +0 -34
- package/.agents/references/hooks/useRefEffect.md +0 -35
- package/.agents/references/hooks/useRelativeDateTime.md +0 -35
- package/.agents/references/hooks/useTimeout.md +0 -42
- package/.agents/references/hooks/useToggleState.md +0 -39
- package/.agents/references/hooks/useViewportHeight.md +0 -26
- package/.agents/references/hooks/useViewportMatch.md +0 -32
- package/.agents/references/utils/browserStorage.md +0 -41
- package/.agents/references/utils/buildContext.md +0 -47
- package/.agents/references/utils/clipboardShare.md +0 -59
- package/.agents/references/utils/floatingMotion.md +0 -67
- package/.agents/references/utils/getDeviceInfo.md +0 -43
- package/.agents/references/utils/isEditableKeyboardTarget.md +0 -35
- package/.agents/references/utils/mergeRefs.md +0 -29
- package/.agents/references/utils/seen.md +0 -37
- package/.agents/references/utils/subscribeKeyboardHeight.md +0 -35
- package/.gitattributes +0 -2
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -31
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -22
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -22
- package/.github/workflows/ci.yml +0 -20
- package/.local/state/gh/device-id +0 -1
- package/.prettierrc +0 -8
- package/AGENTS.md +0 -119
- package/CODE_OF_CONDUCT.md +0 -55
- package/CONTRIBUTING.md +0 -39
- package/biome.json +0 -25
- package/tests/components/ScrolltoTop.test.tsx +0 -15
- package/tests/components/SwitchCase.test.tsx +0 -33
- package/tests/components/ViewportPortal.test.tsx +0 -46
- package/tests/cookie/cookie.test.ts +0 -192
- package/tests/datetime/datetime.test.ts +0 -461
- package/tests/hooks/useAvoidKeyboard.test.ts +0 -43
- package/tests/hooks/useCheckInvisible.test.ts +0 -59
- package/tests/hooks/useCheckScroll.test.ts +0 -23
- package/tests/hooks/useClientDateTime.test.ts +0 -12
- package/tests/hooks/useDebounce.test.ts +0 -96
- package/tests/hooks/useDebouncedCallback.test.ts +0 -63
- package/tests/hooks/useDoubleClick.test.ts +0 -82
- package/tests/hooks/useGeolocation.test.ts +0 -201
- package/tests/hooks/useHasMounted.test.ts +0 -10
- package/tests/hooks/useIntersectionObserver.test.ts +0 -78
- package/tests/hooks/useInterval.test.ts +0 -56
- package/tests/hooks/useKeyboardHeight.test.ts +0 -10
- package/tests/hooks/useKeyboardListNavigation.test.ts +0 -404
- package/tests/hooks/useLongPress.test.ts +0 -214
- package/tests/hooks/usePreservedCallback.test.ts +0 -33
- package/tests/hooks/usePreservedReference.test.ts +0 -42
- package/tests/hooks/useRefEffect.test.ts +0 -67
- package/tests/hooks/useRelativeDateTime.test.ts +0 -21
- package/tests/hooks/useTimeout.test.ts +0 -72
- package/tests/hooks/useToggleState.test.ts +0 -43
- package/tests/hooks/useViewportHeight.test.ts +0 -32
- package/tests/hooks/useViewportMatch.test.ts +0 -35
- package/tests/setup.ts +0 -9
- package/tests/utils/browserStorage.test.ts +0 -114
- package/tests/utils/buildContext.test.tsx +0 -26
- package/tests/utils/checkDevice.test.ts +0 -140
- package/tests/utils/clipboardShare.test.ts +0 -152
- package/tests/utils/floatingMotion.test.ts +0 -135
- package/tests/utils/keyboardTarget.test.ts +0 -85
- package/tests/utils/mergeRefs.test.ts +0 -50
- package/tests/utils/seen.test.ts +0 -40
- package/tests/utils/subscribeKeyboardHeight.test.ts +0 -172
- package/tsconfig.json +0 -18
- package/vitest.config.ts +0 -22
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { act, renderHook } from "@testing-library/react";
|
|
2
|
-
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
3
|
-
import { useDebouncedCallback } from "@/packages/hooks/useDebouncedCallback";
|
|
4
|
-
|
|
5
|
-
describe("useDebouncedCallback", () => {
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
vi.useFakeTimers();
|
|
8
|
-
});
|
|
9
|
-
afterEach(() => {
|
|
10
|
-
vi.useRealTimers();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
test("debounces onChange calls", () => {
|
|
14
|
-
const onChange = vi.fn();
|
|
15
|
-
const { result } = renderHook(() => useDebouncedCallback({ onChange, timeThreshold: 100 }));
|
|
16
|
-
|
|
17
|
-
act(() => {
|
|
18
|
-
result.current(true);
|
|
19
|
-
});
|
|
20
|
-
vi.advanceTimersByTime(100);
|
|
21
|
-
expect(onChange).toHaveBeenCalledWith(true);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test("works with leading: true", () => {
|
|
25
|
-
const onChange = vi.fn();
|
|
26
|
-
const { result } = renderHook(() =>
|
|
27
|
-
useDebouncedCallback({ onChange, timeThreshold: 100, leading: true }),
|
|
28
|
-
);
|
|
29
|
-
act(() => {
|
|
30
|
-
result.current(true);
|
|
31
|
-
});
|
|
32
|
-
expect(onChange).toHaveBeenCalledOnce();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test("works with trailing: false", () => {
|
|
36
|
-
const onChange = vi.fn();
|
|
37
|
-
const { result } = renderHook(() =>
|
|
38
|
-
useDebouncedCallback({ onChange, timeThreshold: 100, trailing: false }),
|
|
39
|
-
);
|
|
40
|
-
act(() => {
|
|
41
|
-
result.current(true);
|
|
42
|
-
});
|
|
43
|
-
vi.advanceTimersByTime(100);
|
|
44
|
-
expect(onChange).not.toHaveBeenCalled();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test("skips duplicate value", () => {
|
|
48
|
-
const onChange = vi.fn();
|
|
49
|
-
const { result } = renderHook(() => useDebouncedCallback({ onChange, timeThreshold: 100 }));
|
|
50
|
-
|
|
51
|
-
act(() => {
|
|
52
|
-
result.current(true);
|
|
53
|
-
});
|
|
54
|
-
vi.advanceTimersByTime(100);
|
|
55
|
-
expect(onChange).toHaveBeenCalledTimes(1);
|
|
56
|
-
|
|
57
|
-
act(() => {
|
|
58
|
-
result.current(true);
|
|
59
|
-
});
|
|
60
|
-
vi.advanceTimersByTime(100);
|
|
61
|
-
expect(onChange).toHaveBeenCalledTimes(1);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { act, renderHook } from "@testing-library/react";
|
|
2
|
-
import { afterEach, describe, expect, test, vi } from "vitest";
|
|
3
|
-
import { useDoubleClick } from "@/packages/hooks/useDoubleClick";
|
|
4
|
-
|
|
5
|
-
describe("useDoubleClick", () => {
|
|
6
|
-
afterEach(() => {
|
|
7
|
-
vi.useRealTimers();
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
test("fires click on single click (event.detail === 1)", () => {
|
|
11
|
-
vi.useFakeTimers();
|
|
12
|
-
const click = vi.fn();
|
|
13
|
-
const doubleClick = vi.fn();
|
|
14
|
-
const { result } = renderHook(() => useDoubleClick({ click, doubleClick }));
|
|
15
|
-
|
|
16
|
-
const event = new MouseEvent("click", {
|
|
17
|
-
detail: 1,
|
|
18
|
-
}) as unknown as React.MouseEvent<HTMLElement>;
|
|
19
|
-
act(() => {
|
|
20
|
-
result.current(event);
|
|
21
|
-
});
|
|
22
|
-
expect(doubleClick).not.toHaveBeenCalled();
|
|
23
|
-
vi.advanceTimersByTime(300);
|
|
24
|
-
expect(click).toHaveBeenCalledOnce();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test("clears pending timeout on unmount", () => {
|
|
28
|
-
vi.useFakeTimers();
|
|
29
|
-
const clearTimeoutSpy = vi.spyOn(window, "clearTimeout");
|
|
30
|
-
const click = vi.fn();
|
|
31
|
-
const doubleClick = vi.fn();
|
|
32
|
-
const { result, unmount } = renderHook(() => useDoubleClick({ click, doubleClick }));
|
|
33
|
-
|
|
34
|
-
const event = new MouseEvent("click", {
|
|
35
|
-
detail: 1,
|
|
36
|
-
}) as unknown as React.MouseEvent<HTMLElement>;
|
|
37
|
-
act(() => {
|
|
38
|
-
result.current(event);
|
|
39
|
-
});
|
|
40
|
-
unmount();
|
|
41
|
-
expect(clearTimeoutSpy).toHaveBeenCalled();
|
|
42
|
-
clearTimeoutSpy.mockRestore();
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test("handles second click before first timeout (race condition)", () => {
|
|
46
|
-
vi.useFakeTimers();
|
|
47
|
-
const click = vi.fn();
|
|
48
|
-
const doubleClick = vi.fn();
|
|
49
|
-
const { result } = renderHook(() => useDoubleClick({ click, doubleClick }));
|
|
50
|
-
|
|
51
|
-
const event1 = new MouseEvent("click", {
|
|
52
|
-
detail: 1,
|
|
53
|
-
}) as unknown as React.MouseEvent<HTMLElement>;
|
|
54
|
-
const event2 = new MouseEvent("click", {
|
|
55
|
-
detail: 1,
|
|
56
|
-
}) as unknown as React.MouseEvent<HTMLElement>;
|
|
57
|
-
|
|
58
|
-
act(() => {
|
|
59
|
-
result.current(event1);
|
|
60
|
-
});
|
|
61
|
-
act(() => {
|
|
62
|
-
result.current(event2);
|
|
63
|
-
});
|
|
64
|
-
vi.advanceTimersByTime(300);
|
|
65
|
-
expect(click).toHaveBeenCalledTimes(1);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("fires doubleClick on double click (event.detail === 2)", () => {
|
|
69
|
-
const click = vi.fn();
|
|
70
|
-
const doubleClick = vi.fn();
|
|
71
|
-
const { result } = renderHook(() => useDoubleClick({ click, doubleClick }));
|
|
72
|
-
|
|
73
|
-
const event = new MouseEvent("click", {
|
|
74
|
-
detail: 2,
|
|
75
|
-
}) as unknown as React.MouseEvent<HTMLElement>;
|
|
76
|
-
act(() => {
|
|
77
|
-
result.current(event);
|
|
78
|
-
});
|
|
79
|
-
expect(doubleClick).toHaveBeenCalledOnce();
|
|
80
|
-
expect(click).not.toHaveBeenCalled();
|
|
81
|
-
});
|
|
82
|
-
});
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
import { act, renderHook } from "@testing-library/react";
|
|
2
|
-
import { beforeEach, describe, expect, type Mock, test, vi } from "vitest";
|
|
3
|
-
import { useGeolocation } from "@/packages/hooks/useGeolocation";
|
|
4
|
-
|
|
5
|
-
describe("useGeolocation", () => {
|
|
6
|
-
let mockGeolocation: {
|
|
7
|
-
getCurrentPosition: Mock;
|
|
8
|
-
watchPosition: Mock;
|
|
9
|
-
clearWatch: Mock;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
mockGeolocation = {
|
|
14
|
-
getCurrentPosition: vi.fn((success) =>
|
|
15
|
-
success({
|
|
16
|
-
coords: {
|
|
17
|
-
latitude: 37.5665,
|
|
18
|
-
longitude: 126.978,
|
|
19
|
-
accuracy: 10,
|
|
20
|
-
altitude: null,
|
|
21
|
-
altitudeAccuracy: null,
|
|
22
|
-
heading: null,
|
|
23
|
-
speed: null,
|
|
24
|
-
},
|
|
25
|
-
timestamp: Date.now(),
|
|
26
|
-
}),
|
|
27
|
-
),
|
|
28
|
-
watchPosition: vi.fn(),
|
|
29
|
-
clearWatch: vi.fn(),
|
|
30
|
-
};
|
|
31
|
-
Object.defineProperty(navigator, "geolocation", {
|
|
32
|
-
value: mockGeolocation,
|
|
33
|
-
writable: true,
|
|
34
|
-
configurable: true,
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("returns default state", () => {
|
|
39
|
-
const { result } = renderHook(() => useGeolocation());
|
|
40
|
-
expect(result.current.loading).toBe(false);
|
|
41
|
-
expect(result.current.error).toBeNull();
|
|
42
|
-
expect(result.current.data).toBeNull();
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test("getCurrentPosition fetches location and updates state", () => {
|
|
46
|
-
const { result } = renderHook(() => useGeolocation());
|
|
47
|
-
act(() => {
|
|
48
|
-
result.current.getCurrentPosition();
|
|
49
|
-
});
|
|
50
|
-
expect(mockGeolocation.getCurrentPosition).toHaveBeenCalledOnce();
|
|
51
|
-
expect(result.current.loading).toBe(false);
|
|
52
|
-
expect(result.current.data?.latitude).toBe(37.5665);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("getCurrentPosition with mount behavior GET", () => {
|
|
56
|
-
renderHook(() => useGeolocation({ mountBehavior: "get" }));
|
|
57
|
-
expect(mockGeolocation.getCurrentPosition).toHaveBeenCalled();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test("mount behavior GET calls getCurrentPosition with geoOptions", () => {
|
|
61
|
-
renderHook(() => useGeolocation({ mountBehavior: "get", enableHighAccuracy: true }));
|
|
62
|
-
expect(mockGeolocation.getCurrentPosition).toHaveBeenCalledWith(
|
|
63
|
-
expect.any(Function),
|
|
64
|
-
expect.any(Function),
|
|
65
|
-
expect.objectContaining({ enableHighAccuracy: true }),
|
|
66
|
-
);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test("clears existing watch before starting new tracking", () => {
|
|
70
|
-
mockGeolocation.watchPosition = vi.fn(() => 789);
|
|
71
|
-
const { result } = renderHook(() => useGeolocation());
|
|
72
|
-
act(() => {
|
|
73
|
-
result.current.startTracking();
|
|
74
|
-
});
|
|
75
|
-
act(() => {
|
|
76
|
-
result.current.startTracking();
|
|
77
|
-
});
|
|
78
|
-
expect(mockGeolocation.clearWatch).toHaveBeenCalledWith(789);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test("startTracking returns early when geolocation unsupported", () => {
|
|
82
|
-
const origGeolocation = navigator.geolocation;
|
|
83
|
-
Object.defineProperty(navigator, "geolocation", {
|
|
84
|
-
value: undefined,
|
|
85
|
-
writable: true,
|
|
86
|
-
configurable: true,
|
|
87
|
-
});
|
|
88
|
-
const { result } = renderHook(() => useGeolocation());
|
|
89
|
-
act(() => {
|
|
90
|
-
result.current.startTracking();
|
|
91
|
-
});
|
|
92
|
-
expect(result.current.error).toBe("Geolocation is not supported by this environment.");
|
|
93
|
-
Object.defineProperty(navigator, "geolocation", {
|
|
94
|
-
value: origGeolocation,
|
|
95
|
-
writable: true,
|
|
96
|
-
configurable: true,
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test("startTracking calls watchPosition", () => {
|
|
101
|
-
const watchSuccess = vi.fn();
|
|
102
|
-
mockGeolocation.watchPosition = vi.fn((success) => {
|
|
103
|
-
watchSuccess.mockImplementation(() =>
|
|
104
|
-
success({
|
|
105
|
-
coords: {
|
|
106
|
-
latitude: 37.5,
|
|
107
|
-
longitude: 127.0,
|
|
108
|
-
accuracy: 5,
|
|
109
|
-
altitude: null,
|
|
110
|
-
altitudeAccuracy: null,
|
|
111
|
-
heading: null,
|
|
112
|
-
speed: null,
|
|
113
|
-
},
|
|
114
|
-
timestamp: Date.now(),
|
|
115
|
-
}),
|
|
116
|
-
);
|
|
117
|
-
});
|
|
118
|
-
const { result } = renderHook(() => useGeolocation());
|
|
119
|
-
act(() => {
|
|
120
|
-
result.current.startTracking();
|
|
121
|
-
});
|
|
122
|
-
expect(mockGeolocation.watchPosition).toHaveBeenCalled();
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test("startTracking with mount behavior WATCH", () => {
|
|
126
|
-
mockGeolocation.watchPosition = vi.fn((success) =>
|
|
127
|
-
success({
|
|
128
|
-
coords: {
|
|
129
|
-
latitude: 37.5,
|
|
130
|
-
longitude: 127.0,
|
|
131
|
-
accuracy: 5,
|
|
132
|
-
altitude: null,
|
|
133
|
-
altitudeAccuracy: null,
|
|
134
|
-
heading: null,
|
|
135
|
-
speed: null,
|
|
136
|
-
},
|
|
137
|
-
timestamp: Date.now(),
|
|
138
|
-
}),
|
|
139
|
-
);
|
|
140
|
-
renderHook(() => useGeolocation({ mountBehavior: "watch" }));
|
|
141
|
-
expect(mockGeolocation.watchPosition).toHaveBeenCalled();
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
test("stopTracking clears watch and sets isTracking to false", () => {
|
|
145
|
-
mockGeolocation.watchPosition = vi.fn(() => 123);
|
|
146
|
-
const { result } = renderHook(() => useGeolocation());
|
|
147
|
-
act(() => {
|
|
148
|
-
result.current.startTracking();
|
|
149
|
-
});
|
|
150
|
-
act(() => {
|
|
151
|
-
result.current.stopTracking();
|
|
152
|
-
});
|
|
153
|
-
expect(mockGeolocation.clearWatch).toHaveBeenCalledWith(123);
|
|
154
|
-
expect(result.current.isTracking).toBe(false);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test("stopTracking does nothing when no watch is active", () => {
|
|
158
|
-
const { result } = renderHook(() => useGeolocation());
|
|
159
|
-
act(() => {
|
|
160
|
-
result.current.stopTracking();
|
|
161
|
-
});
|
|
162
|
-
expect(mockGeolocation.clearWatch).not.toHaveBeenCalled();
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
test("clears watch on unmount when tracking", () => {
|
|
166
|
-
mockGeolocation.watchPosition = vi.fn(() => 456);
|
|
167
|
-
const { unmount } = renderHook(() => useGeolocation({ mountBehavior: "watch" }));
|
|
168
|
-
unmount();
|
|
169
|
-
expect(mockGeolocation.clearWatch).toHaveBeenCalledWith(456);
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
test("handles unsupported geolocation", () => {
|
|
173
|
-
const origGeolocation = navigator.geolocation;
|
|
174
|
-
Object.defineProperty(navigator, "geolocation", {
|
|
175
|
-
value: undefined,
|
|
176
|
-
writable: true,
|
|
177
|
-
configurable: true,
|
|
178
|
-
});
|
|
179
|
-
const { result } = renderHook(() => useGeolocation());
|
|
180
|
-
act(() => {
|
|
181
|
-
result.current.getCurrentPosition();
|
|
182
|
-
});
|
|
183
|
-
expect(result.current.error).toBe("Geolocation is not supported by this environment.");
|
|
184
|
-
Object.defineProperty(navigator, "geolocation", {
|
|
185
|
-
value: origGeolocation,
|
|
186
|
-
writable: true,
|
|
187
|
-
configurable: true,
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
test("handles geolocation error", () => {
|
|
192
|
-
mockGeolocation.getCurrentPosition = vi.fn((_success, error) =>
|
|
193
|
-
error?.({ message: "User denied geolocation" }),
|
|
194
|
-
);
|
|
195
|
-
const { result } = renderHook(() => useGeolocation());
|
|
196
|
-
act(() => {
|
|
197
|
-
result.current.getCurrentPosition();
|
|
198
|
-
});
|
|
199
|
-
expect(result.current.error).toBe("User denied geolocation");
|
|
200
|
-
});
|
|
201
|
-
});
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { renderHook } from "@testing-library/react";
|
|
2
|
-
import { describe, expect, test } from "vitest";
|
|
3
|
-
import { useHasMounted } from "@/packages/hooks/useHasMounted";
|
|
4
|
-
|
|
5
|
-
describe("useHasMounted", () => {
|
|
6
|
-
test("returns true after mount", () => {
|
|
7
|
-
const { result } = renderHook(() => useHasMounted());
|
|
8
|
-
expect(result.current).toBe(true);
|
|
9
|
-
});
|
|
10
|
-
});
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { act, renderHook } from "@testing-library/react";
|
|
2
|
-
import { describe, expect, test, vi } from "vitest";
|
|
3
|
-
import { useIntersectionObserver } from "@/packages/hooks/useIntersectionObserver";
|
|
4
|
-
|
|
5
|
-
describe("useIntersectionObserver", () => {
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
const mockObserve = vi.fn();
|
|
8
|
-
const mockUnobserve = vi.fn();
|
|
9
|
-
class MockIntersectionObserver {
|
|
10
|
-
observe = mockObserve;
|
|
11
|
-
unobserve = mockUnobserve;
|
|
12
|
-
constructor(
|
|
13
|
-
public callback: IntersectionObserverCallback,
|
|
14
|
-
public options?: IntersectionObserverInit,
|
|
15
|
-
) {}
|
|
16
|
-
}
|
|
17
|
-
vi.stubGlobal("IntersectionObserver", MockIntersectionObserver);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test("returns a ref callback", () => {
|
|
21
|
-
const callback = vi.fn();
|
|
22
|
-
const { result } = renderHook(() => useIntersectionObserver(callback, { threshold: 0 }));
|
|
23
|
-
expect(typeof result.current).toBe("function");
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test("observes element when ref is attached", () => {
|
|
27
|
-
const callback = vi.fn();
|
|
28
|
-
const { result } = renderHook(() => useIntersectionObserver(callback, { threshold: 0 }));
|
|
29
|
-
|
|
30
|
-
const element = document.createElement("div");
|
|
31
|
-
act(() => {
|
|
32
|
-
(result.current as (el: HTMLElement | null) => void)(element);
|
|
33
|
-
});
|
|
34
|
-
expect(callback).not.toHaveBeenCalled();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test("triggers observer callback when intersection fires", () => {
|
|
38
|
-
let observerCallback: IntersectionObserverCallback = () => {};
|
|
39
|
-
class MockIO {
|
|
40
|
-
constructor(cb: IntersectionObserverCallback) {
|
|
41
|
-
observerCallback = cb;
|
|
42
|
-
}
|
|
43
|
-
observe = vi.fn();
|
|
44
|
-
unobserve = vi.fn();
|
|
45
|
-
}
|
|
46
|
-
vi.stubGlobal("IntersectionObserver", MockIO);
|
|
47
|
-
|
|
48
|
-
const callback = vi.fn();
|
|
49
|
-
renderHook(() => useIntersectionObserver(callback, { threshold: 0 }));
|
|
50
|
-
|
|
51
|
-
const entry = { isIntersecting: true } as IntersectionObserverEntry;
|
|
52
|
-
observerCallback([entry], {} as IntersectionObserver);
|
|
53
|
-
expect(callback).toHaveBeenCalledWith(entry);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("unobserves element when ref is detached", () => {
|
|
57
|
-
const callback = vi.fn();
|
|
58
|
-
const { result } = renderHook(() => useIntersectionObserver(callback, { threshold: 0 }));
|
|
59
|
-
|
|
60
|
-
const element = document.createElement("div");
|
|
61
|
-
act(() => {
|
|
62
|
-
(result.current as (el: HTMLElement | null) => void)(element);
|
|
63
|
-
});
|
|
64
|
-
act(() => {
|
|
65
|
-
(result.current as (el: HTMLElement | null) => void)(null);
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test("returns noop when IntersectionObserver is undefined", () => {
|
|
70
|
-
const origIO = global.IntersectionObserver;
|
|
71
|
-
// @ts-expect-error
|
|
72
|
-
delete global.IntersectionObserver;
|
|
73
|
-
const callback = vi.fn();
|
|
74
|
-
const { result } = renderHook(() => useIntersectionObserver(callback, { threshold: 0 }));
|
|
75
|
-
expect(typeof result.current).toBe("function");
|
|
76
|
-
global.IntersectionObserver = origIO;
|
|
77
|
-
});
|
|
78
|
-
});
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { renderHook } from "@testing-library/react";
|
|
2
|
-
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
3
|
-
import { useInterval } from "@/packages/hooks/useInterval";
|
|
4
|
-
|
|
5
|
-
describe("useInterval", () => {
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
vi.useFakeTimers();
|
|
8
|
-
});
|
|
9
|
-
afterEach(() => {
|
|
10
|
-
vi.useRealTimers();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
test("calls callback repeatedly at interval", () => {
|
|
14
|
-
const fn = vi.fn();
|
|
15
|
-
renderHook(() => useInterval(fn, 100));
|
|
16
|
-
expect(fn).not.toHaveBeenCalled();
|
|
17
|
-
vi.advanceTimersByTime(100);
|
|
18
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
19
|
-
vi.advanceTimersByTime(100);
|
|
20
|
-
expect(fn).toHaveBeenCalledTimes(2);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test("calls immediately with immediate option", () => {
|
|
24
|
-
const fn = vi.fn();
|
|
25
|
-
renderHook(() => useInterval(fn, { delay: 100, immediate: true }));
|
|
26
|
-
expect(fn).toHaveBeenCalledOnce();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test("does not call immediate when enabled is false", () => {
|
|
30
|
-
const fn = vi.fn();
|
|
31
|
-
renderHook(() => useInterval(fn, { delay: 100, immediate: true, enabled: false }));
|
|
32
|
-
expect(fn).not.toHaveBeenCalled();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test("handles immediate StrictMode double-fire guard", () => {
|
|
36
|
-
const fn = vi.fn();
|
|
37
|
-
const { rerender } = renderHook(
|
|
38
|
-
({ immediate, enabled }: { immediate?: boolean; enabled?: boolean }) =>
|
|
39
|
-
useInterval(fn, { delay: 100, immediate, enabled }),
|
|
40
|
-
{ initialProps: { immediate: true, enabled: true } },
|
|
41
|
-
);
|
|
42
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
43
|
-
// Toggle enabled to trigger effect re-run while immediateCalledRef is still true
|
|
44
|
-
rerender({ immediate: true, enabled: false });
|
|
45
|
-
rerender({ immediate: true, enabled: true });
|
|
46
|
-
// Should not call callback again (ref already set)
|
|
47
|
-
expect(fn).toHaveBeenCalledTimes(1);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("does not call when disabled", () => {
|
|
51
|
-
const fn = vi.fn();
|
|
52
|
-
renderHook(() => useInterval(fn, { delay: 100, enabled: false }));
|
|
53
|
-
vi.advanceTimersByTime(200);
|
|
54
|
-
expect(fn).not.toHaveBeenCalled();
|
|
55
|
-
});
|
|
56
|
-
});
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { renderHook } from "@testing-library/react";
|
|
2
|
-
import { describe, expect, test } from "vitest";
|
|
3
|
-
import { useKeyboardHeight } from "@/packages/hooks/useKeyboardHeight";
|
|
4
|
-
|
|
5
|
-
describe("useKeyboardHeight", () => {
|
|
6
|
-
test("returns default keyboard height of 0", () => {
|
|
7
|
-
const { result } = renderHook(() => useKeyboardHeight());
|
|
8
|
-
expect(result.current.keyboardHeight).toBe(0);
|
|
9
|
-
});
|
|
10
|
-
});
|