@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
|
@@ -10,18 +10,13 @@ type UseKeyboardListNavigationOptions = {
|
|
|
10
10
|
itemCount: number;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
export function useKeyboardListNavigation({
|
|
14
|
-
itemCount,
|
|
15
|
-
}: UseKeyboardListNavigationOptions) {
|
|
13
|
+
export function useKeyboardListNavigation({ itemCount }: UseKeyboardListNavigationOptions) {
|
|
16
14
|
const [activeIndex, setActiveIndex] = useState(-1);
|
|
17
15
|
const itemRefs = useRef<(HTMLElement | null)[]>([]);
|
|
18
16
|
|
|
19
|
-
const setItemRef = useCallback(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
},
|
|
23
|
-
[],
|
|
24
|
-
);
|
|
17
|
+
const setItemRef = useCallback((index: number, element: HTMLElement | null) => {
|
|
18
|
+
itemRefs.current[index] = element;
|
|
19
|
+
}, []);
|
|
25
20
|
|
|
26
21
|
const focusItem = useCallback((index: number) => {
|
|
27
22
|
const target = itemRefs.current[index];
|
|
@@ -59,18 +54,6 @@ export function useKeyboardListNavigation({
|
|
|
59
54
|
|
|
60
55
|
const moveActiveItem = useCallback(
|
|
61
56
|
(key: string) => {
|
|
62
|
-
if (itemCount === 0) {
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (key === "Home") {
|
|
67
|
-
return focusItem(0);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (key === "End") {
|
|
71
|
-
return focusItem(itemCount - 1);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
57
|
if (key === "ArrowDown" || key === "ArrowRight") {
|
|
75
58
|
const nextIndex = activeIndex < 0 ? 0 : (activeIndex + 1) % itemCount;
|
|
76
59
|
return focusItem(nextIndex);
|
|
@@ -78,15 +61,13 @@ export function useKeyboardListNavigation({
|
|
|
78
61
|
|
|
79
62
|
if (key === "ArrowUp" || key === "ArrowLeft") {
|
|
80
63
|
const nextIndex =
|
|
81
|
-
activeIndex < 0
|
|
82
|
-
? itemCount - 1
|
|
83
|
-
: (activeIndex - 1 + itemCount) % itemCount;
|
|
64
|
+
activeIndex < 0 ? itemCount - 1 : (activeIndex - 1 + itemCount) % itemCount;
|
|
84
65
|
return focusItem(nextIndex);
|
|
85
66
|
}
|
|
86
67
|
|
|
87
|
-
return
|
|
68
|
+
return focusBoundaryItem(key);
|
|
88
69
|
},
|
|
89
|
-
[activeIndex, focusItem, itemCount],
|
|
70
|
+
[activeIndex, focusItem, focusBoundaryItem, itemCount],
|
|
90
71
|
);
|
|
91
72
|
|
|
92
73
|
const clickActiveItem = useCallback(() => {
|
|
@@ -99,10 +80,7 @@ export function useKeyboardListNavigation({
|
|
|
99
80
|
}, [activeIndex]);
|
|
100
81
|
|
|
101
82
|
const handleListKeyDown = useCallback(
|
|
102
|
-
(
|
|
103
|
-
event: React.KeyboardEvent<HTMLElement>,
|
|
104
|
-
options?: { container?: HTMLElement | null },
|
|
105
|
-
) => {
|
|
83
|
+
(event: React.KeyboardEvent<HTMLElement>, options?: { container?: HTMLElement | null }) => {
|
|
106
84
|
if (itemCount === 0 || event.defaultPrevented) {
|
|
107
85
|
return;
|
|
108
86
|
}
|
|
@@ -144,15 +122,9 @@ export function useKeyboardListNavigation({
|
|
|
144
122
|
return;
|
|
145
123
|
}
|
|
146
124
|
|
|
147
|
-
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (focusBoundaryItem(event.key)) {
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
125
|
+
moveActiveItem(event.key);
|
|
154
126
|
},
|
|
155
|
-
[clickActiveItem, moveActiveItem,
|
|
127
|
+
[clickActiveItem, moveActiveItem, itemCount],
|
|
156
128
|
);
|
|
157
129
|
|
|
158
130
|
useEffect(() => {
|
|
@@ -14,12 +14,7 @@ export type UseLongPressOptions<E extends HTMLElement> = {
|
|
|
14
14
|
|
|
15
15
|
export function useLongPress<E extends HTMLElement = HTMLElement>(
|
|
16
16
|
onLongPress: (event: MouseEvent<E> | TouchEvent<E>) => void,
|
|
17
|
-
{
|
|
18
|
-
delay = 500,
|
|
19
|
-
moveThreshold,
|
|
20
|
-
onClick,
|
|
21
|
-
onLongPressEnd,
|
|
22
|
-
}: UseLongPressOptions<E> = {},
|
|
17
|
+
{ delay = 500, moveThreshold, onClick, onLongPressEnd }: UseLongPressOptions<E> = {},
|
|
23
18
|
) {
|
|
24
19
|
const timeoutRef = useRef<number | null>(null);
|
|
25
20
|
const isLongPressActiveRef = useRef(false);
|
|
@@ -32,22 +27,18 @@ export function useLongPress<E extends HTMLElement = HTMLElement>(
|
|
|
32
27
|
savedOnClick.current = onClick;
|
|
33
28
|
savedOnLongPressEnd.current = onLongPressEnd;
|
|
34
29
|
|
|
35
|
-
const hasThreshold =
|
|
36
|
-
moveThreshold?.x !== undefined || moveThreshold?.y !== undefined;
|
|
30
|
+
const hasThreshold = moveThreshold?.x !== undefined || moveThreshold?.y !== undefined;
|
|
37
31
|
|
|
38
|
-
const getClientPosition = useCallback(
|
|
39
|
-
(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
},
|
|
49
|
-
[],
|
|
50
|
-
);
|
|
32
|
+
const getClientPosition = useCallback((event: MouseEvent<E> | TouchEvent<E>) => {
|
|
33
|
+
if ("touches" in event.nativeEvent) {
|
|
34
|
+
const touch = event.nativeEvent.touches[0];
|
|
35
|
+
return { x: touch.clientX, y: touch.clientY };
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
x: event.nativeEvent.clientX,
|
|
39
|
+
y: event.nativeEvent.clientY,
|
|
40
|
+
};
|
|
41
|
+
}, []);
|
|
51
42
|
|
|
52
43
|
const isMovedBeyondThreshold = useCallback(
|
|
53
44
|
(event: MouseEvent<E> | TouchEvent<E>) => {
|
|
@@ -113,8 +104,6 @@ export function useLongPress<E extends HTMLElement = HTMLElement>(
|
|
|
113
104
|
onMouseLeave: cancelLongPress,
|
|
114
105
|
onTouchStart: handlePressStart,
|
|
115
106
|
onTouchEnd: handlePressEnd,
|
|
116
|
-
...(hasThreshold
|
|
117
|
-
? { onTouchMove: handlePressMove, onMouseMove: handlePressMove }
|
|
118
|
-
: {}),
|
|
107
|
+
...(hasThreshold ? { onTouchMove: handlePressMove, onMouseMove: handlePressMove } : {}),
|
|
119
108
|
};
|
|
120
109
|
}
|
|
@@ -12,10 +12,7 @@ type UseRelativeDateTimeOptions = {
|
|
|
12
12
|
updateIntervalMs?: number;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
export function useRelativeDateTime(
|
|
16
|
-
value: DateInput,
|
|
17
|
-
options?: UseRelativeDateTimeOptions,
|
|
18
|
-
) {
|
|
15
|
+
export function useRelativeDateTime(value: DateInput, options?: UseRelativeDateTimeOptions) {
|
|
19
16
|
const [ready, setReady] = useState(false);
|
|
20
17
|
const [text, setText] = useState("");
|
|
21
18
|
const [isRelative, setIsRelative] = useState(false);
|
|
@@ -6,8 +6,7 @@ export function useViewportHeight() {
|
|
|
6
6
|
const [height, setHeight] = useState(0);
|
|
7
7
|
|
|
8
8
|
useEffect(() => {
|
|
9
|
-
const update = () =>
|
|
10
|
-
setHeight(window.visualViewport?.height ?? window.innerHeight);
|
|
9
|
+
const update = () => setHeight(window.visualViewport?.height ?? window.innerHeight);
|
|
11
10
|
|
|
12
11
|
update();
|
|
13
12
|
window.visualViewport?.addEventListener("resize", update);
|
|
@@ -6,10 +6,6 @@ export function useViewportMatch(mediaQuery: string): boolean {
|
|
|
6
6
|
const [isMatched, setIsMatched] = useState(false);
|
|
7
7
|
|
|
8
8
|
useEffect(() => {
|
|
9
|
-
if (typeof window === "undefined") {
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
9
|
const mediaQueryList = window.matchMedia(mediaQuery);
|
|
14
10
|
const syncMatchState = () => {
|
|
15
11
|
setIsMatched(mediaQueryList.matches);
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { createContext, type ReactNode, useContext
|
|
3
|
+
import { createContext, type ReactNode, useContext } from "react";
|
|
4
4
|
|
|
5
5
|
export function buildContext<T>(defaultValue: T) {
|
|
6
6
|
const Ctx = createContext<T>(defaultValue);
|
|
7
7
|
|
|
8
8
|
function Provider({ value, children }: { value: T; children: ReactNode }) {
|
|
9
|
-
|
|
10
|
-
return <Ctx.Provider value={stable}>{children}</Ctx.Provider>;
|
|
9
|
+
return <Ctx.Provider value={value}>{children}</Ctx.Provider>;
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
function useValue() {
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
export type FloatingMotionMode =
|
|
2
|
-
| "anchored"
|
|
3
|
-
| "center-selected"
|
|
4
|
-
| "modal-center"
|
|
5
|
-
| "mobile-sheet";
|
|
1
|
+
export type FloatingMotionMode = "anchored" | "center-selected" | "modal-center" | "mobile-sheet";
|
|
6
2
|
|
|
7
3
|
export type FloatingPlacement =
|
|
8
4
|
| "top-left"
|
|
@@ -21,9 +17,7 @@ export interface FloatingMotionPreset {
|
|
|
21
17
|
ease: string;
|
|
22
18
|
}
|
|
23
19
|
|
|
24
|
-
export const getFloatingMotionPreset = (
|
|
25
|
-
mode: FloatingMotionMode,
|
|
26
|
-
): FloatingMotionPreset => {
|
|
20
|
+
export const getFloatingMotionPreset = (mode: FloatingMotionMode): FloatingMotionPreset => {
|
|
27
21
|
if (mode === "mobile-sheet") {
|
|
28
22
|
return {
|
|
29
23
|
enterMs: 360,
|
|
@@ -55,15 +49,20 @@ export const getFloatingMotionPreset = (
|
|
|
55
49
|
};
|
|
56
50
|
};
|
|
57
51
|
|
|
52
|
+
type FloatingRow = "top" | "middle" | "bottom";
|
|
53
|
+
type FloatingCol = "left" | "center" | "right";
|
|
54
|
+
|
|
55
|
+
function parsePlacement(placement: FloatingPlacement): [FloatingRow, FloatingCol] {
|
|
56
|
+
const [row, col] = placement.split("-") as [FloatingRow, FloatingCol];
|
|
57
|
+
return [row, col];
|
|
58
|
+
}
|
|
59
|
+
|
|
58
60
|
export const getFloatingTransformOrigin = (
|
|
59
61
|
placement?: FloatingPlacement,
|
|
60
62
|
): string => {
|
|
61
63
|
if (!placement) return "top center";
|
|
62
64
|
|
|
63
|
-
const [row, col] = placement
|
|
64
|
-
"top" | "middle" | "bottom",
|
|
65
|
-
"left" | "center" | "right",
|
|
66
|
-
];
|
|
65
|
+
const [row, col] = parsePlacement(placement);
|
|
67
66
|
const y = row === "top" ? "bottom" : row === "bottom" ? "top" : "center";
|
|
68
67
|
const x = col === "right" ? "right" : col === "left" ? "left" : "center";
|
|
69
68
|
|
|
@@ -82,18 +81,13 @@ export const getFloatingHiddenTransform = ({
|
|
|
82
81
|
if (mode === "center-selected") return "translateY(.45rem) scale(.99)";
|
|
83
82
|
if (!placement) return "translateY(.4rem) scale(.975)";
|
|
84
83
|
|
|
85
|
-
const [row, col] = placement
|
|
86
|
-
"top" | "middle" | "bottom",
|
|
87
|
-
"left" | "center" | "right",
|
|
88
|
-
];
|
|
84
|
+
const [row, col] = parsePlacement(placement);
|
|
89
85
|
|
|
90
86
|
if (row === "top") return "translateY(.4rem) scale(.975)";
|
|
91
87
|
if (row === "bottom") return "translateY(-.4rem) scale(.975)";
|
|
92
88
|
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
if (col === "right") return "translateX(-.4rem) scale(.975)";
|
|
96
|
-
}
|
|
89
|
+
if (col === "left") return "translateX(.4rem) scale(.975)";
|
|
90
|
+
if (col === "right") return "translateX(-.4rem) scale(.975)";
|
|
97
91
|
|
|
98
92
|
return "translateY(.4rem) scale(.975)";
|
|
99
93
|
};
|
|
@@ -12,6 +12,7 @@ export function subscribeKeyboardHeight({
|
|
|
12
12
|
throttleMs = 16,
|
|
13
13
|
}: SubscribeKeyboardHeightOptions) {
|
|
14
14
|
let ticking = false;
|
|
15
|
+
let disposed = false;
|
|
15
16
|
let lastHeight = 0;
|
|
16
17
|
|
|
17
18
|
const notify = (height: number) => {
|
|
@@ -19,6 +20,7 @@ export function subscribeKeyboardHeight({
|
|
|
19
20
|
ticking = true;
|
|
20
21
|
|
|
21
22
|
const run = () => {
|
|
23
|
+
if (disposed) return;
|
|
22
24
|
if (height !== lastHeight) {
|
|
23
25
|
lastHeight = height;
|
|
24
26
|
callback(height);
|
|
@@ -47,6 +49,7 @@ export function subscribeKeyboardHeight({
|
|
|
47
49
|
|
|
48
50
|
return {
|
|
49
51
|
unsubscribe() {
|
|
52
|
+
disposed = true;
|
|
50
53
|
window.visualViewport?.removeEventListener("resize", handleResize);
|
|
51
54
|
window.visualViewport?.removeEventListener("scroll", handleResize);
|
|
52
55
|
},
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { describe, test, expect } from "vitest";
|
|
2
1
|
import { render } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
3
|
import { SwitchCase } from "@/packages/components/SwitchCase";
|
|
4
|
-
import React from "react";
|
|
5
4
|
|
|
6
5
|
describe("SwitchCase", () => {
|
|
7
6
|
const cases = {
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import { describe, test, expect, beforeEach } from "vitest";
|
|
2
1
|
import { render, screen } from "@testing-library/react";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
getViewportPortalRoot,
|
|
6
|
-
} from "@/packages/components/ViewportPortal";
|
|
7
|
-
import React from "react";
|
|
2
|
+
import { beforeEach, describe, expect, test } from "vitest";
|
|
3
|
+
import { getViewportPortalRoot, ViewportPortal } from "@/packages/components/ViewportPortal";
|
|
8
4
|
|
|
9
5
|
describe("ViewportPortal", () => {
|
|
10
6
|
beforeEach(() => {
|
|
@@ -25,6 +21,14 @@ describe("ViewportPortal", () => {
|
|
|
25
21
|
expect(document.querySelectorAll("#viewport-portal-root").length).toBe(1);
|
|
26
22
|
});
|
|
27
23
|
|
|
24
|
+
test("getViewportPortalRoot returns null when document is undefined (SSR)", () => {
|
|
25
|
+
const origDocument = global.document;
|
|
26
|
+
// @ts-expect-error
|
|
27
|
+
delete global.document;
|
|
28
|
+
expect(getViewportPortalRoot()).toBeNull();
|
|
29
|
+
global.document = origDocument;
|
|
30
|
+
});
|
|
31
|
+
|
|
28
32
|
test("ViewportPortal renders children into the portal root", () => {
|
|
29
33
|
render(
|
|
30
34
|
<ViewportPortal>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
clearAllClientCookies,
|
|
4
|
+
clearClientCookie,
|
|
4
5
|
getClientCookie,
|
|
5
6
|
setClientCookie,
|
|
6
|
-
clearClientCookie,
|
|
7
|
-
clearAllClientCookies,
|
|
8
7
|
} from "@/packages/cookie/cookie.shared";
|
|
8
|
+
import { parseClientCookieNames } from "@/packages/cookie/cookieNames.shared";
|
|
9
9
|
|
|
10
10
|
describe("cookie module", () => {
|
|
11
11
|
beforeEach(() => {
|
|
@@ -44,6 +44,11 @@ describe("cookie module", () => {
|
|
|
44
44
|
expect(getClientCookie("baz")).toBe("qux");
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
+
test("skips cookies with empty name when parsing", () => {
|
|
48
|
+
document.cookie = "=value; foo=bar";
|
|
49
|
+
expect(getClientCookie("foo")).toBe("bar");
|
|
50
|
+
});
|
|
51
|
+
|
|
47
52
|
test("returns undefined if cookie does not exist", () => {
|
|
48
53
|
document.cookie = "foo=bar";
|
|
49
54
|
expect(getClientCookie("missing")).toBeUndefined();
|
|
@@ -51,7 +56,7 @@ describe("cookie module", () => {
|
|
|
51
56
|
|
|
52
57
|
test("handles undefined document", () => {
|
|
53
58
|
const originalDocument = global.document;
|
|
54
|
-
// @ts-
|
|
59
|
+
// @ts-expect-error
|
|
55
60
|
delete global.document;
|
|
56
61
|
|
|
57
62
|
expect(getClientCookie("foo")).toBeUndefined();
|
|
@@ -61,6 +66,37 @@ describe("cookie module", () => {
|
|
|
61
66
|
});
|
|
62
67
|
|
|
63
68
|
describe("setClientCookie", () => {
|
|
69
|
+
test("handles undefined document in setClientCookie", () => {
|
|
70
|
+
const origDocument = global.document;
|
|
71
|
+
// @ts-expect-error
|
|
72
|
+
delete global.document;
|
|
73
|
+
setClientCookie("foo", "bar");
|
|
74
|
+
global.document = origDocument;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("parseClientCookie handles malformed entries", () => {
|
|
78
|
+
const result = parseClientCookieNames("=value; key");
|
|
79
|
+
expect(result).toEqual(["key"]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("clearAllClientCookies handles empty cookie name", () => {
|
|
83
|
+
const mockDoc = { cookie: "=1;;" };
|
|
84
|
+
const entries = clearAllClientCookies({ documentRef: mockDoc as any });
|
|
85
|
+
expect(Array.isArray(entries)).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("clearAllClientCookies skips entries with empty name", () => {
|
|
89
|
+
const mockDoc = { cookie: "a=1;;b=2" };
|
|
90
|
+
const entries = clearAllClientCookies({ documentRef: mockDoc as any });
|
|
91
|
+
// Entries with empty names should be skipped in the loop
|
|
92
|
+
expect(entries).toEqual(["a", "b"]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("setClientCookie with days=0 does not add expires", () => {
|
|
96
|
+
setClientCookie("foo", "bar", 0);
|
|
97
|
+
expect(document.cookie).toBe("foo=bar; path=/");
|
|
98
|
+
});
|
|
99
|
+
|
|
64
100
|
test("sets cookie with name and value", () => {
|
|
65
101
|
setClientCookie("foo", "bar");
|
|
66
102
|
expect(document.cookie).toBe("foo=bar; path=/");
|
|
@@ -98,7 +134,7 @@ describe("cookie module", () => {
|
|
|
98
134
|
clearClientCookie("foo", {
|
|
99
135
|
hostname: "custom.com",
|
|
100
136
|
path: "/custom",
|
|
101
|
-
documentRef: mockDoc
|
|
137
|
+
documentRef: mockDoc,
|
|
102
138
|
});
|
|
103
139
|
expect(mockDoc.cookie).toBe(
|
|
104
140
|
"foo=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/custom; domain=custom.com;",
|
|
@@ -109,7 +145,7 @@ describe("cookie module", () => {
|
|
|
109
145
|
describe("clearAllClientCookies", () => {
|
|
110
146
|
test("clears all cookies based on documentRef.cookie", () => {
|
|
111
147
|
const mockDoc = { cookie: "a=1; b=2" };
|
|
112
|
-
const entries = clearAllClientCookies({ documentRef: mockDoc
|
|
148
|
+
const entries = clearAllClientCookies({ documentRef: mockDoc });
|
|
113
149
|
|
|
114
150
|
expect(entries).toEqual(["a", "b"]);
|
|
115
151
|
expect(mockDoc.cookie).toContain(
|
|
@@ -122,8 +158,8 @@ describe("cookie module", () => {
|
|
|
122
158
|
const mockDoc = { cookie: "a=1; b=2" };
|
|
123
159
|
|
|
124
160
|
const entries = clearAllClientCookies({
|
|
125
|
-
documentRef: mockDoc
|
|
126
|
-
cookieStore
|
|
161
|
+
documentRef: mockDoc,
|
|
162
|
+
cookieStore,
|
|
127
163
|
});
|
|
128
164
|
|
|
129
165
|
expect(entries).toEqual(["a", "b"]);
|
|
@@ -133,7 +169,7 @@ describe("cookie module", () => {
|
|
|
133
169
|
|
|
134
170
|
test("clears cookies from root path when includeRoot is true", () => {
|
|
135
171
|
const mockDoc = { cookie: "a=1" };
|
|
136
|
-
clearAllClientCookies({ documentRef: mockDoc
|
|
172
|
+
clearAllClientCookies({ documentRef: mockDoc, includeRoot: true });
|
|
137
173
|
|
|
138
174
|
// The function iterates and updates document.cookie, so it will contain the last assignment.
|
|
139
175
|
// With paths ["/", "/test/path"] and domain "example.com"
|
|
@@ -145,7 +181,7 @@ describe("cookie module", () => {
|
|
|
145
181
|
test("uses provided cookieString instead of documentRef.cookie", () => {
|
|
146
182
|
const mockDoc = { cookie: "a=1" };
|
|
147
183
|
const entries = clearAllClientCookies({
|
|
148
|
-
documentRef: mockDoc
|
|
184
|
+
documentRef: mockDoc,
|
|
149
185
|
cookieString: "x=1; y=2",
|
|
150
186
|
});
|
|
151
187
|
|