@musecat/functionkit 1.0.2 → 1.1.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/.agents/references/hooks/useIntersectionObserver.md +4 -0
- package/.agents/references/hooks/useInterval.md +4 -0
- package/.agents/references/hooks/usePreservedCallback.md +4 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +31 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +22 -0
- package/.github/workflows/ci.yml +20 -0
- package/.local/state/gh/device-id +1 -0
- package/.prettierrc +8 -0
- package/AGENTS.md +14 -0
- package/CODE_OF_CONDUCT.md +55 -0
- package/CONTRIBUTING.md +39 -0
- package/biome.json +25 -0
- package/index.ts +1 -1
- package/package.json +21 -2
- package/packages/components/ScrolltoTop.tsx +1 -1
- package/packages/components/ViewportPortal.tsx +1 -2
- package/packages/cookie/cookie.shared.ts +7 -33
- package/packages/cookie/cookieNames.shared.ts +9 -0
- package/packages/datetime/dateTime.client.ts +1 -3
- package/packages/datetime/dateTime.server.ts +1 -3
- package/packages/datetime/dateTime.shared.ts +9 -47
- package/packages/hooks/useCheckInvisible.ts +1 -1
- package/packages/hooks/useClientDateTime.ts +3 -16
- package/packages/hooks/useDebounce.ts +2 -7
- package/packages/hooks/useGeolocation.ts +1 -5
- package/packages/hooks/useInterval.ts +1 -2
- package/packages/hooks/useKeyboardListNavigation.ts +10 -38
- package/packages/hooks/useLongPress.ts +13 -24
- package/packages/hooks/useRelativeDateTime.ts +1 -4
- package/packages/hooks/useViewportHeight.ts +1 -2
- package/packages/hooks/useViewportMatch.ts +0 -4
- package/packages/utils/buildContext.tsx +2 -3
- package/packages/utils/floatingMotion.ts +14 -20
- package/packages/utils/subscribeKeyboardHeight.ts +3 -0
- package/tests/components/ScrolltoTop.test.tsx +1 -1
- package/tests/components/SwitchCase.test.tsx +1 -2
- package/tests/components/ViewportPortal.test.tsx +10 -6
- package/tests/cookie/cookie.test.ts +47 -11
- package/tests/datetime/datetime.test.ts +257 -46
- package/tests/hooks/useAvoidKeyboard.test.ts +32 -1
- package/tests/hooks/useCheckInvisible.test.ts +36 -5
- package/tests/hooks/useCheckScroll.test.ts +2 -2
- package/tests/hooks/useClientDateTime.test.ts +2 -4
- package/tests/hooks/useDebounce.test.ts +30 -5
- package/tests/hooks/useDebouncedCallback.test.ts +41 -12
- package/tests/hooks/useDoubleClick.test.ts +58 -7
- package/tests/hooks/useGeolocation.test.ts +155 -4
- package/tests/hooks/useHasMounted.test.ts +1 -1
- package/tests/hooks/useIntersectionObserver.test.ts +70 -5
- package/tests/hooks/useInterval.test.ts +28 -3
- package/tests/hooks/useKeyboardHeight.test.ts +1 -1
- package/tests/hooks/useKeyboardListNavigation.test.ts +340 -18
- package/tests/hooks/useLongPress.test.ts +177 -15
- package/tests/hooks/usePreservedCallback.test.ts +7 -9
- package/tests/hooks/usePreservedReference.test.ts +19 -12
- package/tests/hooks/useRefEffect.test.ts +43 -6
- package/tests/hooks/useRelativeDateTime.test.ts +8 -6
- package/tests/hooks/useTimeout.test.ts +43 -5
- package/tests/hooks/useToggleState.test.ts +14 -6
- package/tests/hooks/useViewportHeight.test.ts +24 -2
- package/tests/hooks/useViewportMatch.test.ts +1 -1
- package/tests/utils/browserStorage.test.ts +68 -8
- package/tests/utils/buildContext.test.tsx +1 -2
- package/tests/utils/checkDevice.test.ts +62 -6
- package/tests/utils/clipboardShare.test.ts +79 -5
- package/tests/utils/floatingMotion.test.ts +60 -9
- package/tests/utils/keyboardTarget.test.ts +42 -1
- package/tests/utils/mergeRefs.test.ts +33 -1
- package/tests/utils/seen.test.ts +16 -7
- package/tests/utils/subscribeKeyboardHeight.test.ts +124 -2
- package/vitest.config.ts +7 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { describe,
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
2
|
import {
|
|
3
|
+
getFloatingHiddenTransform,
|
|
3
4
|
getFloatingMotionPreset,
|
|
4
5
|
getFloatingTransformOrigin,
|
|
5
|
-
getFloatingHiddenTransform,
|
|
6
6
|
} from "@/packages/utils/floatingMotion";
|
|
7
7
|
|
|
8
8
|
describe("getFloatingMotionPreset", () => {
|
|
@@ -47,19 +47,49 @@ describe("getFloatingTransformOrigin", () => {
|
|
|
47
47
|
test("returns correct origin for middle-center", () => {
|
|
48
48
|
expect(getFloatingTransformOrigin("middle-center")).toBe("center center");
|
|
49
49
|
});
|
|
50
|
+
|
|
51
|
+
test("returns correct origin for top-center", () => {
|
|
52
|
+
expect(getFloatingTransformOrigin("top-center")).toBe("bottom center");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("returns correct origin for top-right", () => {
|
|
56
|
+
expect(getFloatingTransformOrigin("top-right")).toBe("bottom right");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("returns correct origin for middle-left", () => {
|
|
60
|
+
expect(getFloatingTransformOrigin("middle-left")).toBe("center left");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("returns correct origin for middle-right", () => {
|
|
64
|
+
expect(getFloatingTransformOrigin("middle-right")).toBe("center right");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("returns correct origin for bottom-left", () => {
|
|
68
|
+
expect(getFloatingTransformOrigin("bottom-left")).toBe("top left");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("returns correct origin for bottom-center", () => {
|
|
72
|
+
expect(getFloatingTransformOrigin("bottom-center")).toBe("top center");
|
|
73
|
+
});
|
|
50
74
|
});
|
|
51
75
|
|
|
52
76
|
describe("getFloatingHiddenTransform", () => {
|
|
53
77
|
test("returns mobile-sheet transform", () => {
|
|
54
|
-
expect(getFloatingHiddenTransform({ mode: "mobile-sheet" })).toBe(
|
|
78
|
+
expect(getFloatingHiddenTransform({ mode: "mobile-sheet" })).toBe(
|
|
79
|
+
"translateY(1.8rem) scale(1)",
|
|
80
|
+
);
|
|
55
81
|
});
|
|
56
82
|
|
|
57
83
|
test("returns modal-center transform", () => {
|
|
58
|
-
expect(getFloatingHiddenTransform({ mode: "modal-center" })).toBe(
|
|
84
|
+
expect(getFloatingHiddenTransform({ mode: "modal-center" })).toBe(
|
|
85
|
+
"translateY(.8rem) scale(.94)",
|
|
86
|
+
);
|
|
59
87
|
});
|
|
60
88
|
|
|
61
89
|
test("returns center-selected transform", () => {
|
|
62
|
-
expect(getFloatingHiddenTransform({ mode: "center-selected" })).toBe(
|
|
90
|
+
expect(getFloatingHiddenTransform({ mode: "center-selected" })).toBe(
|
|
91
|
+
"translateY(.45rem) scale(.99)",
|
|
92
|
+
);
|
|
63
93
|
});
|
|
64
94
|
|
|
65
95
|
test("anchored with no placement", () => {
|
|
@@ -67,18 +97,39 @@ describe("getFloatingHiddenTransform", () => {
|
|
|
67
97
|
});
|
|
68
98
|
|
|
69
99
|
test("anchored with top placement", () => {
|
|
70
|
-
expect(getFloatingHiddenTransform({ mode: "anchored", placement: "top-left" })).toBe(
|
|
100
|
+
expect(getFloatingHiddenTransform({ mode: "anchored", placement: "top-left" })).toBe(
|
|
101
|
+
"translateY(.4rem) scale(.975)",
|
|
102
|
+
);
|
|
71
103
|
});
|
|
72
104
|
|
|
73
105
|
test("anchored with bottom placement", () => {
|
|
74
|
-
expect(getFloatingHiddenTransform({ mode: "anchored", placement: "bottom-center" })).toBe(
|
|
106
|
+
expect(getFloatingHiddenTransform({ mode: "anchored", placement: "bottom-center" })).toBe(
|
|
107
|
+
"translateY(-.4rem) scale(.975)",
|
|
108
|
+
);
|
|
75
109
|
});
|
|
76
110
|
|
|
77
111
|
test("anchored with middle-left placement", () => {
|
|
78
|
-
expect(getFloatingHiddenTransform({ mode: "anchored", placement: "middle-left" })).toBe(
|
|
112
|
+
expect(getFloatingHiddenTransform({ mode: "anchored", placement: "middle-left" })).toBe(
|
|
113
|
+
"translateX(.4rem) scale(.975)",
|
|
114
|
+
);
|
|
79
115
|
});
|
|
80
116
|
|
|
81
117
|
test("anchored with middle-right placement", () => {
|
|
82
|
-
expect(getFloatingHiddenTransform({ mode: "anchored", placement: "middle-right" })).toBe(
|
|
118
|
+
expect(getFloatingHiddenTransform({ mode: "anchored", placement: "middle-right" })).toBe(
|
|
119
|
+
"translateX(-.4rem) scale(.975)",
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("anchored with middle-center placement falls through to default", () => {
|
|
124
|
+
expect(getFloatingHiddenTransform({ mode: "anchored", placement: "middle-center" })).toBe(
|
|
125
|
+
"translateY(.4rem) scale(.975)",
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("anchored with middle placement and undefined col falls through", () => {
|
|
130
|
+
// @ts-expect-error - testing unexpected placement fallthrough
|
|
131
|
+
expect(getFloatingHiddenTransform({ mode: "anchored", placement: "middle" })).toBe(
|
|
132
|
+
"translateY(.4rem) scale(.975)",
|
|
133
|
+
);
|
|
83
134
|
});
|
|
84
135
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe,
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
2
|
import { isEditableKeyboardTarget } from "@/packages/utils/keyboardTarget";
|
|
3
3
|
|
|
4
4
|
describe("isEditableKeyboardTarget", () => {
|
|
@@ -41,4 +41,45 @@ describe("isEditableKeyboardTarget", () => {
|
|
|
41
41
|
const el = document.createElement("div");
|
|
42
42
|
expect(isEditableKeyboardTarget(el)).toBe(false);
|
|
43
43
|
});
|
|
44
|
+
|
|
45
|
+
test("returns true for SELECT element", () => {
|
|
46
|
+
const el = document.createElement("select");
|
|
47
|
+
expect(isEditableKeyboardTarget(el)).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("returns true for email INPUT", () => {
|
|
51
|
+
const el = document.createElement("input");
|
|
52
|
+
el.type = "email";
|
|
53
|
+
expect(isEditableKeyboardTarget(el)).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("returns true for url INPUT", () => {
|
|
57
|
+
const el = document.createElement("input");
|
|
58
|
+
el.type = "url";
|
|
59
|
+
expect(isEditableKeyboardTarget(el)).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("returns true for number INPUT", () => {
|
|
63
|
+
const el = document.createElement("input");
|
|
64
|
+
el.type = "number";
|
|
65
|
+
expect(isEditableKeyboardTarget(el)).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("returns true for password INPUT", () => {
|
|
69
|
+
const el = document.createElement("input");
|
|
70
|
+
el.type = "password";
|
|
71
|
+
expect(isEditableKeyboardTarget(el)).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("returns true for search INPUT", () => {
|
|
75
|
+
const el = document.createElement("input");
|
|
76
|
+
el.type = "search";
|
|
77
|
+
expect(isEditableKeyboardTarget(el)).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("returns true for tel INPUT", () => {
|
|
81
|
+
const el = document.createElement("input");
|
|
82
|
+
el.type = "tel";
|
|
83
|
+
expect(isEditableKeyboardTarget(el)).toBe(true);
|
|
84
|
+
});
|
|
44
85
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe,
|
|
1
|
+
import { describe, expect, test, vi } from "vitest";
|
|
2
2
|
import { mergeRefs } from "@/packages/utils/mergeRefs";
|
|
3
3
|
|
|
4
4
|
describe("mergeRefs", () => {
|
|
@@ -15,4 +15,36 @@ describe("mergeRefs", () => {
|
|
|
15
15
|
expect(ref1).toHaveBeenCalledWith(element);
|
|
16
16
|
expect(ref2.current).toBe(element);
|
|
17
17
|
});
|
|
18
|
+
|
|
19
|
+
test("handles all null refs", () => {
|
|
20
|
+
const merged = mergeRefs(null, undefined, null);
|
|
21
|
+
const element = document.createElement("div");
|
|
22
|
+
expect(() => merged(element as any)).not.toThrow();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("handles empty refs array", () => {
|
|
26
|
+
const merged = mergeRefs();
|
|
27
|
+
const element = document.createElement("div");
|
|
28
|
+
expect(() => merged(element as any)).not.toThrow();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("handles all callback refs", () => {
|
|
32
|
+
const ref1 = vi.fn();
|
|
33
|
+
const ref2 = vi.fn();
|
|
34
|
+
const merged = mergeRefs(ref1, ref2);
|
|
35
|
+
const element = document.createElement("div");
|
|
36
|
+
merged(element as any);
|
|
37
|
+
expect(ref1).toHaveBeenCalledWith(element);
|
|
38
|
+
expect(ref2).toHaveBeenCalledWith(element);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("handles all object refs", () => {
|
|
42
|
+
const ref1 = { current: null };
|
|
43
|
+
const ref2 = { current: null };
|
|
44
|
+
const merged = mergeRefs(ref1, ref2);
|
|
45
|
+
const element = document.createElement("div");
|
|
46
|
+
merged(element as any);
|
|
47
|
+
expect(ref1.current).toBe(element);
|
|
48
|
+
expect(ref2.current).toBe(element);
|
|
49
|
+
});
|
|
18
50
|
});
|
package/tests/utils/seen.test.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
import { describe,
|
|
2
|
-
import {
|
|
3
|
-
parseSeen,
|
|
4
|
-
hasSeenKey,
|
|
5
|
-
buildSeenValue,
|
|
6
|
-
SEEN_STORAGE_KEY,
|
|
7
|
-
} from "@/packages/utils/seen";
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { buildSeenValue, hasSeenKey, parseSeen, SEEN_STORAGE_KEY } from "@/packages/utils/seen";
|
|
8
3
|
|
|
9
4
|
describe("seen", () => {
|
|
10
5
|
test("SEEN_STORAGE_KEY", () => {
|
|
@@ -28,4 +23,18 @@ describe("seen", () => {
|
|
|
28
23
|
const result = buildSeenValue('{"a":true}', "b");
|
|
29
24
|
expect(JSON.parse(result)).toEqual({ a: true, b: true });
|
|
30
25
|
});
|
|
26
|
+
|
|
27
|
+
test("parseSeen empty string returns {}", () => {
|
|
28
|
+
expect(parseSeen("")).toEqual({});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("buildSeenValue with null raw creates fresh object", () => {
|
|
32
|
+
const result = buildSeenValue(null, "key");
|
|
33
|
+
expect(JSON.parse(result)).toEqual({ key: true });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("buildSeenValue overrides existing key to true", () => {
|
|
37
|
+
const result = buildSeenValue('{"key":false}', "key");
|
|
38
|
+
expect(JSON.parse(result)).toEqual({ key: true });
|
|
39
|
+
});
|
|
31
40
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
2
|
import { subscribeKeyboardHeight } from "@/packages/utils/subscribeKeyboardHeight";
|
|
3
3
|
|
|
4
4
|
describe("subscribeKeyboardHeight", () => {
|
|
@@ -24,7 +24,9 @@ describe("subscribeKeyboardHeight", () => {
|
|
|
24
24
|
});
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
afterEach(() => {
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
vi.useRealTimers();
|
|
29
|
+
});
|
|
28
30
|
|
|
29
31
|
test("subscribes to resize events", () => {
|
|
30
32
|
const callback = vi.fn();
|
|
@@ -41,10 +43,130 @@ describe("subscribeKeyboardHeight", () => {
|
|
|
41
43
|
expect(callback).toHaveBeenCalled();
|
|
42
44
|
});
|
|
43
45
|
|
|
46
|
+
test("runs synchronously when throttleMs is 0", () => {
|
|
47
|
+
const callback = vi.fn();
|
|
48
|
+
subscribeKeyboardHeight({ callback, throttleMs: 0, immediate: true });
|
|
49
|
+
expect(callback).toHaveBeenCalled();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("skips resize when visualViewport is null", () => {
|
|
53
|
+
const origVv = window.visualViewport;
|
|
54
|
+
Object.defineProperty(window, "visualViewport", {
|
|
55
|
+
value: undefined,
|
|
56
|
+
writable: true,
|
|
57
|
+
configurable: true,
|
|
58
|
+
});
|
|
59
|
+
const callback = vi.fn();
|
|
60
|
+
subscribeKeyboardHeight({ callback, immediate: true });
|
|
61
|
+
expect(callback).not.toHaveBeenCalled();
|
|
62
|
+
Object.defineProperty(window, "visualViewport", {
|
|
63
|
+
value: origVv,
|
|
64
|
+
writable: true,
|
|
65
|
+
configurable: true,
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("does not call callback when height unchanged", () => {
|
|
70
|
+
const callback = vi.fn();
|
|
71
|
+
subscribeKeyboardHeight({ callback, throttleMs: 0, immediate: true });
|
|
72
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
73
|
+
|
|
74
|
+
// Trigger resize with the same keyboard height
|
|
75
|
+
const handler = visualViewport.addEventListener.mock.calls.find(
|
|
76
|
+
([event]: [string]) => event === "resize",
|
|
77
|
+
)?.[1] as () => void;
|
|
78
|
+
if (handler) handler();
|
|
79
|
+
|
|
80
|
+
// Same diff (innerHeight 800 - visualViewport.height 600 = 200), so notify(200) again
|
|
81
|
+
// But lastHeight is already 200, so height !== lastHeight is false
|
|
82
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("handles no keyboard scenario (diff <= 0)", () => {
|
|
86
|
+
const origInnerHeight = window.innerHeight;
|
|
87
|
+
// First, call with keyboard raised (diff=200) so lastHeight becomes 200
|
|
88
|
+
const callback = vi.fn();
|
|
89
|
+
subscribeKeyboardHeight({ callback, throttleMs: 0, immediate: true });
|
|
90
|
+
expect(callback).toHaveBeenCalledWith(200);
|
|
91
|
+
// Now change innerHeight to match visualViewport.height (no keyboard)
|
|
92
|
+
Object.defineProperty(window, "innerHeight", {
|
|
93
|
+
value: 600,
|
|
94
|
+
writable: true,
|
|
95
|
+
configurable: true,
|
|
96
|
+
});
|
|
97
|
+
// Trigger resize handler
|
|
98
|
+
const resizeHandler = visualViewport.addEventListener.mock.calls.find(
|
|
99
|
+
([event]: [string]) => event === "resize",
|
|
100
|
+
)?.[1] as () => void;
|
|
101
|
+
resizeHandler();
|
|
102
|
+
// diff = 600 - 600 = 0 → notify(0) → 0 !== 200 → callback(0)
|
|
103
|
+
expect(callback).toHaveBeenCalledWith(0);
|
|
104
|
+
Object.defineProperty(window, "innerHeight", {
|
|
105
|
+
value: origInnerHeight,
|
|
106
|
+
writable: true,
|
|
107
|
+
configurable: true,
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("throttles rapid resize events", () => {
|
|
112
|
+
vi.useFakeTimers();
|
|
113
|
+
const callback = vi.fn();
|
|
114
|
+
subscribeKeyboardHeight({ callback, throttleMs: 50 });
|
|
115
|
+
|
|
116
|
+
// Simulate two rapid resize events
|
|
117
|
+
visualViewport.height = 700;
|
|
118
|
+
visualViewport.addEventListener.mock.calls.forEach(([event, handler]: [string, () => void]) => {
|
|
119
|
+
if (event === "resize") handler();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Change height again and fire again
|
|
123
|
+
visualViewport.height = 650;
|
|
124
|
+
visualViewport.addEventListener.mock.calls.forEach(([event, handler]: [string, () => void]) => {
|
|
125
|
+
if (event === "resize") handler();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Only the last height should be reported after throttle
|
|
129
|
+
vi.advanceTimersByTime(50);
|
|
130
|
+
expect(callback).toHaveBeenCalledTimes(1);
|
|
131
|
+
});
|
|
132
|
+
|
|
44
133
|
test("returns unsubscribe function", () => {
|
|
45
134
|
const callback = vi.fn();
|
|
46
135
|
const { unsubscribe } = subscribeKeyboardHeight({ callback });
|
|
47
136
|
unsubscribe();
|
|
48
137
|
expect(visualViewport.removeEventListener).toHaveBeenCalled();
|
|
49
138
|
});
|
|
139
|
+
|
|
140
|
+
test("does not call callback after unsubscribe with throttle > 0", () => {
|
|
141
|
+
vi.useFakeTimers();
|
|
142
|
+
const callback = vi.fn();
|
|
143
|
+
const { unsubscribe } = subscribeKeyboardHeight({ callback, throttleMs: 50 });
|
|
144
|
+
|
|
145
|
+
// Trigger resize, which schedules a setTimeout
|
|
146
|
+
visualViewport.height = 400;
|
|
147
|
+
visualViewport.dispatchEvent?.(new Event("resize"));
|
|
148
|
+
if (visualViewport.addEventListener.mock.calls.length > 0) {
|
|
149
|
+
const handler = visualViewport.addEventListener.mock.calls[0][1];
|
|
150
|
+
handler?.();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
unsubscribe();
|
|
154
|
+
vi.advanceTimersByTime(100);
|
|
155
|
+
expect(callback).not.toHaveBeenCalled();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("does not call callback after unsubscribe with throttle === 0", () => {
|
|
159
|
+
vi.useFakeTimers();
|
|
160
|
+
const callback = vi.fn();
|
|
161
|
+
const { unsubscribe } = subscribeKeyboardHeight({ callback, throttleMs: 0 });
|
|
162
|
+
|
|
163
|
+
unsubscribe();
|
|
164
|
+
|
|
165
|
+
visualViewport.height = 400;
|
|
166
|
+
if (visualViewport.addEventListener.mock.calls.length > 0) {
|
|
167
|
+
const handler = visualViewport.addEventListener.mock.calls[0][1];
|
|
168
|
+
handler?.();
|
|
169
|
+
}
|
|
170
|
+
expect(callback).not.toHaveBeenCalled();
|
|
171
|
+
});
|
|
50
172
|
});
|
package/vitest.config.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
import { defineConfig } from "vitest/config";
|
|
2
|
-
import path from "path";
|
|
3
3
|
|
|
4
4
|
export default defineConfig({
|
|
5
5
|
test: {
|
|
@@ -7,6 +7,12 @@ export default defineConfig({
|
|
|
7
7
|
setupFiles: ["./tests/setup.ts"],
|
|
8
8
|
include: ["tests/**/*.test.{ts,tsx}"],
|
|
9
9
|
globals: true,
|
|
10
|
+
coverage: {
|
|
11
|
+
provider: "v8",
|
|
12
|
+
include: ["packages/**/*.{ts,tsx}"],
|
|
13
|
+
exclude: ["packages/**/*.types.ts"],
|
|
14
|
+
reporter: ["text", "lcov"],
|
|
15
|
+
},
|
|
10
16
|
},
|
|
11
17
|
resolve: {
|
|
12
18
|
alias: {
|