@musecat/functionkit 1.1.0 → 1.2.1
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/cookie/cookie.md +10 -4
- package/README.md +4 -0
- package/package.json +18 -1
- package/packages/cookie/cookie.shared.ts +10 -2
- package/packages/utils/floatingMotion.ts +1 -3
- 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,192 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
clearAllClientCookies,
|
|
4
|
-
clearClientCookie,
|
|
5
|
-
getClientCookie,
|
|
6
|
-
setClientCookie,
|
|
7
|
-
} from "@/packages/cookie/cookie.shared";
|
|
8
|
-
import { parseClientCookieNames } from "@/packages/cookie/cookieNames.shared";
|
|
9
|
-
|
|
10
|
-
describe("cookie module", () => {
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
// Reset cookies
|
|
13
|
-
Object.defineProperty(document, "cookie", {
|
|
14
|
-
writable: true,
|
|
15
|
-
value: "",
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
// Mock window location
|
|
19
|
-
Object.defineProperty(window, "location", {
|
|
20
|
-
writable: true,
|
|
21
|
-
value: { hostname: "example.com", pathname: "/test/path" },
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
afterEach(() => {
|
|
26
|
-
vi.restoreAllMocks();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
describe("parseClientCookieNames", () => {
|
|
30
|
-
test("parses cookie string correctly", () => {
|
|
31
|
-
const result = parseClientCookieNames("foo=bar; baz=qux; ; empty=");
|
|
32
|
-
expect(result).toEqual(["foo", "baz", "empty"]);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test("returns empty array for empty string", () => {
|
|
36
|
-
expect(parseClientCookieNames("")).toEqual([]);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
describe("getClientCookie", () => {
|
|
41
|
-
test("returns cookie value if it exists", () => {
|
|
42
|
-
document.cookie = "foo=bar; baz=qux";
|
|
43
|
-
expect(getClientCookie("foo")).toBe("bar");
|
|
44
|
-
expect(getClientCookie("baz")).toBe("qux");
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test("skips cookies with empty name when parsing", () => {
|
|
48
|
-
document.cookie = "=value; foo=bar";
|
|
49
|
-
expect(getClientCookie("foo")).toBe("bar");
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("returns undefined if cookie does not exist", () => {
|
|
53
|
-
document.cookie = "foo=bar";
|
|
54
|
-
expect(getClientCookie("missing")).toBeUndefined();
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("handles undefined document", () => {
|
|
58
|
-
const originalDocument = global.document;
|
|
59
|
-
// @ts-expect-error
|
|
60
|
-
delete global.document;
|
|
61
|
-
|
|
62
|
-
expect(getClientCookie("foo")).toBeUndefined();
|
|
63
|
-
|
|
64
|
-
global.document = originalDocument;
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
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
|
-
|
|
100
|
-
test("sets cookie with name and value", () => {
|
|
101
|
-
setClientCookie("foo", "bar");
|
|
102
|
-
expect(document.cookie).toBe("foo=bar; path=/");
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test("sets cookie with days", () => {
|
|
106
|
-
vi.useFakeTimers();
|
|
107
|
-
vi.setSystemTime(new Date("2020-01-01T00:00:00Z"));
|
|
108
|
-
|
|
109
|
-
setClientCookie("foo", "bar", 7);
|
|
110
|
-
|
|
111
|
-
const expectedDate = new Date("2020-01-08T00:00:00Z").toUTCString();
|
|
112
|
-
expect(document.cookie).toBe(`foo=bar; expires=${expectedDate}; path=/`);
|
|
113
|
-
|
|
114
|
-
vi.useRealTimers();
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test("encodes cookie value", () => {
|
|
118
|
-
setClientCookie("foo", "val ue;");
|
|
119
|
-
expect(document.cookie).toBe("foo=val%20ue%3B; path=/");
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
describe("clearClientCookie", () => {
|
|
124
|
-
test("clears cookie with default options", () => {
|
|
125
|
-
document.cookie = "foo=bar";
|
|
126
|
-
clearClientCookie("foo");
|
|
127
|
-
expect(document.cookie).toBe(
|
|
128
|
-
"foo=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=example.com;",
|
|
129
|
-
);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test("clears cookie with custom options", () => {
|
|
133
|
-
const mockDoc = { cookie: "" };
|
|
134
|
-
clearClientCookie("foo", {
|
|
135
|
-
hostname: "custom.com",
|
|
136
|
-
path: "/custom",
|
|
137
|
-
documentRef: mockDoc,
|
|
138
|
-
});
|
|
139
|
-
expect(mockDoc.cookie).toBe(
|
|
140
|
-
"foo=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/custom; domain=custom.com;",
|
|
141
|
-
);
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
describe("clearAllClientCookies", () => {
|
|
146
|
-
test("clears all cookies based on documentRef.cookie", () => {
|
|
147
|
-
const mockDoc = { cookie: "a=1; b=2" };
|
|
148
|
-
const entries = clearAllClientCookies({ documentRef: mockDoc });
|
|
149
|
-
|
|
150
|
-
expect(entries).toEqual(["a", "b"]);
|
|
151
|
-
expect(mockDoc.cookie).toContain(
|
|
152
|
-
"b=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/test/path; domain=com;",
|
|
153
|
-
);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
test("clears cookies using cookieStore if provided", async () => {
|
|
157
|
-
const cookieStore = { delete: vi.fn().mockResolvedValue(undefined) };
|
|
158
|
-
const mockDoc = { cookie: "a=1; b=2" };
|
|
159
|
-
|
|
160
|
-
const entries = clearAllClientCookies({
|
|
161
|
-
documentRef: mockDoc,
|
|
162
|
-
cookieStore,
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
expect(entries).toEqual(["a", "b"]);
|
|
166
|
-
expect(cookieStore.delete).toHaveBeenCalledWith("a");
|
|
167
|
-
expect(cookieStore.delete).toHaveBeenCalledWith("b");
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
test("clears cookies from root path when includeRoot is true", () => {
|
|
171
|
-
const mockDoc = { cookie: "a=1" };
|
|
172
|
-
clearAllClientCookies({ documentRef: mockDoc, includeRoot: true });
|
|
173
|
-
|
|
174
|
-
// The function iterates and updates document.cookie, so it will contain the last assignment.
|
|
175
|
-
// With paths ["/", "/test/path"] and domain "example.com"
|
|
176
|
-
expect(mockDoc.cookie).toContain(
|
|
177
|
-
"a=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/test/path; domain=com;",
|
|
178
|
-
);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
test("uses provided cookieString instead of documentRef.cookie", () => {
|
|
182
|
-
const mockDoc = { cookie: "a=1" };
|
|
183
|
-
const entries = clearAllClientCookies({
|
|
184
|
-
documentRef: mockDoc,
|
|
185
|
-
cookieString: "x=1; y=2",
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
expect(entries).toEqual(["x", "y"]);
|
|
189
|
-
expect(mockDoc.cookie).toContain("y=;");
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
});
|
|
@@ -1,461 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test, vi } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
formatClientDate,
|
|
4
|
-
formatClientDateTime,
|
|
5
|
-
formatClientRelative,
|
|
6
|
-
formatClientTime,
|
|
7
|
-
} from "@/packages/datetime/dateTime.client";
|
|
8
|
-
import {
|
|
9
|
-
formatServerDate,
|
|
10
|
-
formatServerDateTime,
|
|
11
|
-
formatServerRelative,
|
|
12
|
-
formatServerTime,
|
|
13
|
-
} from "@/packages/datetime/dateTime.server";
|
|
14
|
-
import {
|
|
15
|
-
addUtcDays,
|
|
16
|
-
format24HourTime,
|
|
17
|
-
formatDotDate,
|
|
18
|
-
formatKoreanTime,
|
|
19
|
-
formatLongDate,
|
|
20
|
-
formatRelativeText,
|
|
21
|
-
formatRemainingText,
|
|
22
|
-
formatTwelveHourTime,
|
|
23
|
-
formatUtcDateKey,
|
|
24
|
-
getUtcWeekdayIndex,
|
|
25
|
-
normalizeAppLocale,
|
|
26
|
-
parseUtcDateInput,
|
|
27
|
-
toDate,
|
|
28
|
-
toIntlLocale,
|
|
29
|
-
toUtcMidnight,
|
|
30
|
-
} from "@/packages/datetime/dateTime.shared";
|
|
31
|
-
|
|
32
|
-
describe("datetime module", () => {
|
|
33
|
-
describe("shared", () => {
|
|
34
|
-
describe("normalizeAppLocale", () => {
|
|
35
|
-
test("normalizes kr/ko", () => {
|
|
36
|
-
expect(normalizeAppLocale("kr")).toBe("kr");
|
|
37
|
-
expect(normalizeAppLocale("ko")).toBe("kr");
|
|
38
|
-
});
|
|
39
|
-
test("normalizes jp/ja", () => {
|
|
40
|
-
expect(normalizeAppLocale("jp")).toBe("jp");
|
|
41
|
-
expect(normalizeAppLocale("ja")).toBe("jp");
|
|
42
|
-
});
|
|
43
|
-
test("defaults to en", () => {
|
|
44
|
-
expect(normalizeAppLocale("fr")).toBe("en");
|
|
45
|
-
expect(normalizeAppLocale(undefined)).toBe("en");
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
describe("toIntlLocale", () => {
|
|
50
|
-
test("maps to correct intl locales", () => {
|
|
51
|
-
expect(toIntlLocale("kr")).toBe("ko-KR");
|
|
52
|
-
expect(toIntlLocale("jp")).toBe("ja-JP");
|
|
53
|
-
expect(toIntlLocale("en")).toBe("en-US");
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
describe("toDate", () => {
|
|
58
|
-
test("returns Date object for valid inputs", () => {
|
|
59
|
-
expect(toDate(new Date("2020-01-01"))).toBeInstanceOf(Date);
|
|
60
|
-
expect(toDate("2020-01-01")).toBeInstanceOf(Date);
|
|
61
|
-
expect(toDate(1577836800000)).toBeInstanceOf(Date);
|
|
62
|
-
});
|
|
63
|
-
test("returns null for invalid inputs", () => {
|
|
64
|
-
expect(toDate("invalid")).toBeNull();
|
|
65
|
-
expect(toDate(new Date("invalid"))).toBeNull();
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
describe("toUtcMidnight", () => {
|
|
70
|
-
test("converts to UTC midnight", () => {
|
|
71
|
-
const date = new Date("2020-01-01T15:30:00Z");
|
|
72
|
-
const utcDate = toUtcMidnight(date);
|
|
73
|
-
expect(utcDate.toISOString()).toBe("2020-01-01T00:00:00.000Z");
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
describe("parseUtcDateInput", () => {
|
|
78
|
-
test("parses YYYY-MM-DD string to UTC midnight", () => {
|
|
79
|
-
const result = parseUtcDateInput("2020-05-10");
|
|
80
|
-
expect(result?.toISOString()).toBe("2020-05-10T00:00:00.000Z");
|
|
81
|
-
});
|
|
82
|
-
test("parses Date object to UTC midnight", () => {
|
|
83
|
-
const result = parseUtcDateInput(new Date("2020-05-10T15:00:00Z"));
|
|
84
|
-
expect(result?.toISOString()).toBe("2020-05-10T00:00:00.000Z");
|
|
85
|
-
});
|
|
86
|
-
test("uses fallback", () => {
|
|
87
|
-
const result = parseUtcDateInput("invalid", "2020-01-01T00:00:00Z");
|
|
88
|
-
expect(result?.toISOString()).toBe("2020-01-01T00:00:00.000Z");
|
|
89
|
-
});
|
|
90
|
-
test("returns null if both invalid", () => {
|
|
91
|
-
expect(parseUtcDateInput("invalid", "invalid")).toBeNull();
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test("returns null when no fallback provided", () => {
|
|
95
|
-
expect(parseUtcDateInput("invalid")).toBeNull();
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test("handles undefined value", () => {
|
|
99
|
-
expect(parseUtcDateInput(undefined)).toBeNull();
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test("handles null value", () => {
|
|
103
|
-
expect(parseUtcDateInput(null)).toBeNull();
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe("formatUtcDateKey", () => {
|
|
108
|
-
test("formats date to YYYY-MM-DD", () => {
|
|
109
|
-
expect(formatUtcDateKey(new Date("2020-05-05T00:00:00Z"))).toBe("2020-05-05");
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
describe("getUtcWeekdayIndex", () => {
|
|
114
|
-
test("returns correct weekday index", () => {
|
|
115
|
-
expect(getUtcWeekdayIndex(new Date("2020-01-05T00:00:00Z"))).toBe(0); // Sunday
|
|
116
|
-
expect(getUtcWeekdayIndex(new Date("2020-01-06T00:00:00Z"))).toBe(1); // Monday
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
describe("addUtcDays", () => {
|
|
121
|
-
test("adds days to date", () => {
|
|
122
|
-
const result = addUtcDays(new Date("2020-01-01T00:00:00Z"), 5);
|
|
123
|
-
expect(result.toISOString()).toBe("2020-01-06T00:00:00.000Z");
|
|
124
|
-
});
|
|
125
|
-
test("handles negative days", () => {
|
|
126
|
-
const result = addUtcDays(new Date("2020-01-05T00:00:00Z"), -2);
|
|
127
|
-
expect(result.toISOString()).toBe("2020-01-03T00:00:00.000Z");
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
describe("formatters", () => {
|
|
132
|
-
const testDate = new Date("2020-05-10T15:30:45Z"); // UTC time
|
|
133
|
-
|
|
134
|
-
test("formatLongDate", () => {
|
|
135
|
-
expect(formatLongDate(testDate, "kr", "UTC")).toBe("2020년 5월 10일");
|
|
136
|
-
expect(formatLongDate(testDate, "jp", "UTC")).toBe("2020年5月10日");
|
|
137
|
-
expect(formatLongDate(testDate, "en", "UTC")).toContain("2020");
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test("handles missing date/time parts gracefully", () => {
|
|
141
|
-
const spy = vi.spyOn(Intl.DateTimeFormat.prototype, "formatToParts").mockReturnValue([]);
|
|
142
|
-
try {
|
|
143
|
-
expect(formatLongDate(testDate, "kr", "UTC")).toBe("0000년 0월 0일");
|
|
144
|
-
expect(format24HourTime(testDate)).toBe("00:00");
|
|
145
|
-
} finally {
|
|
146
|
-
spy.mockRestore();
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
test("formatDotDate", () => {
|
|
151
|
-
expect(formatDotDate(testDate, "UTC")).toBe("2020. 5. 10.");
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test("format24HourTime", () => {
|
|
155
|
-
expect(format24HourTime(testDate, { timeZone: "UTC" })).toBe("15:30");
|
|
156
|
-
expect(format24HourTime(testDate, { includeSeconds: true, timeZone: "UTC" })).toBe(
|
|
157
|
-
"15:30:45",
|
|
158
|
-
);
|
|
159
|
-
expect(format24HourTime(testDate)).toBe("15:30");
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
test("formatKoreanTime", () => {
|
|
163
|
-
expect(formatKoreanTime(testDate, "UTC")).toBe("오후 3시 30분");
|
|
164
|
-
expect(formatKoreanTime(new Date("2020-05-10T09:15:00Z"), "UTC")).toBe("오전 9시 15분");
|
|
165
|
-
expect(formatKoreanTime(new Date("2020-05-10T00:30:00Z"), "UTC")).toBe("오전 12시 30분");
|
|
166
|
-
expect(formatKoreanTime(new Date("2020-05-10T12:30:00Z"), "UTC")).toBe("오후 12시 30분");
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test("formatTwelveHourTime", () => {
|
|
170
|
-
expect(formatTwelveHourTime(testDate, "kr", "UTC")).toBe("오후 3:30");
|
|
171
|
-
expect(formatTwelveHourTime(new Date("2020-05-10T00:30:00Z"), "kr", "UTC")).toBe("오전 12:30");
|
|
172
|
-
expect(formatTwelveHourTime(new Date("2020-05-10T12:30:00Z"), "kr", "UTC")).toBe("오후 12:30");
|
|
173
|
-
expect(formatTwelveHourTime(testDate, "en", "UTC")).toMatch(/3:30 (PM|pm)/);
|
|
174
|
-
});
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
describe("formatRelativeText", () => {
|
|
178
|
-
test("formatRelativeUnit with singular count (1 second, 1 minute, etc)", () => {
|
|
179
|
-
expect(formatRelativeText(1000, "en")).toEqual({
|
|
180
|
-
text: "1 second ago",
|
|
181
|
-
isRelative: true,
|
|
182
|
-
});
|
|
183
|
-
expect(formatRelativeText(60000, "en")).toEqual({
|
|
184
|
-
text: "1 minute ago",
|
|
185
|
-
isRelative: true,
|
|
186
|
-
});
|
|
187
|
-
expect(formatRelativeText(3600000, "en")).toEqual({
|
|
188
|
-
text: "1 hour ago",
|
|
189
|
-
isRelative: true,
|
|
190
|
-
});
|
|
191
|
-
expect(formatRelativeText(86400000, "en")).toEqual({
|
|
192
|
-
text: "1 day ago",
|
|
193
|
-
isRelative: true,
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
test("formats seconds", () => {
|
|
198
|
-
expect(formatRelativeText(30000, "en")).toEqual({
|
|
199
|
-
text: "30 seconds ago",
|
|
200
|
-
isRelative: true,
|
|
201
|
-
});
|
|
202
|
-
expect(formatRelativeText(30000, "kr")).toEqual({
|
|
203
|
-
text: "30초 전",
|
|
204
|
-
isRelative: true,
|
|
205
|
-
});
|
|
206
|
-
expect(formatRelativeText(30000, "jp")).toEqual({
|
|
207
|
-
text: "30秒前",
|
|
208
|
-
isRelative: true,
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
test("formats minutes", () => {
|
|
212
|
-
expect(formatRelativeText(120000, "en")).toEqual({
|
|
213
|
-
text: "2 minutes ago",
|
|
214
|
-
isRelative: true,
|
|
215
|
-
});
|
|
216
|
-
expect(formatRelativeText(120000, "kr")).toEqual({
|
|
217
|
-
text: "2분 전",
|
|
218
|
-
isRelative: true,
|
|
219
|
-
});
|
|
220
|
-
expect(formatRelativeText(120000, "jp")).toEqual({
|
|
221
|
-
text: "2分前",
|
|
222
|
-
isRelative: true,
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
test("formats hours", () => {
|
|
226
|
-
expect(formatRelativeText(7200000, "en")).toEqual({
|
|
227
|
-
text: "2 hours ago",
|
|
228
|
-
isRelative: true,
|
|
229
|
-
});
|
|
230
|
-
expect(formatRelativeText(7200000, "kr")).toEqual({
|
|
231
|
-
text: "2시간 전",
|
|
232
|
-
isRelative: true,
|
|
233
|
-
});
|
|
234
|
-
expect(formatRelativeText(7200000, "jp")).toEqual({
|
|
235
|
-
text: "2時間前",
|
|
236
|
-
isRelative: true,
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
test("formats days", () => {
|
|
240
|
-
expect(formatRelativeText(172800000, "en")).toEqual({
|
|
241
|
-
text: "2 days ago",
|
|
242
|
-
isRelative: true,
|
|
243
|
-
});
|
|
244
|
-
expect(formatRelativeText(172800000, "kr")).toEqual({
|
|
245
|
-
text: "2일 전",
|
|
246
|
-
isRelative: true,
|
|
247
|
-
});
|
|
248
|
-
expect(formatRelativeText(172800000, "jp")).toEqual({
|
|
249
|
-
text: "2日前",
|
|
250
|
-
isRelative: true,
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
describe("formatRemainingText", () => {
|
|
256
|
-
test("formats remaining time", () => {
|
|
257
|
-
expect(formatRemainingText(0)).toBe("Closed");
|
|
258
|
-
expect(formatRemainingText(0, "kr")).toBe("마감됨");
|
|
259
|
-
expect(formatRemainingText(0, "jp")).toBe("締切終了");
|
|
260
|
-
|
|
261
|
-
expect(formatRemainingText(1000, "en")).toBe("1 second left");
|
|
262
|
-
expect(formatRemainingText(65000, "en")).toBe("1 minute 5 seconds left");
|
|
263
|
-
expect(formatRemainingText(3665000, "kr")).toBe("1시간 1분 남음");
|
|
264
|
-
expect(formatRemainingText(90065000, "jp")).toBe("あと1日1時間");
|
|
265
|
-
});
|
|
266
|
-
test("without suffix", () => {
|
|
267
|
-
expect(formatRemainingText(1000, "en", { includeSuffix: false })).toBe("1 second");
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
test("remaining time less than 1 second defaults to 1 second", () => {
|
|
271
|
-
expect(formatRemainingText(500, "en")).toBe("1 second left");
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
test("formatRemainingText jp locale with no suffix", () => {
|
|
275
|
-
expect(formatRemainingText(3665000, "jp", { includeSuffix: false })).toBe("1時間1分");
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
test("formatRemainingUnit for kr locale covers all units", () => {
|
|
279
|
-
expect(formatRemainingText(1000, "kr")).toBe("1초 남음");
|
|
280
|
-
expect(formatRemainingText(60000, "kr")).toBe("1분 남음");
|
|
281
|
-
expect(formatRemainingText(3600000, "kr")).toBe("1시간 남음");
|
|
282
|
-
expect(formatRemainingText(86400000, "kr")).toBe("1일 남음");
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
test("formatRemainingUnit for jp locale", () => {
|
|
286
|
-
expect(formatRemainingText(1000, "jp")).toBe("あと1秒");
|
|
287
|
-
expect(formatRemainingText(60000, "jp")).toBe("あと1分");
|
|
288
|
-
expect(formatRemainingText(3600000, "jp")).toBe("あと1時間");
|
|
289
|
-
expect(formatRemainingText(86400000, "jp")).toBe("あと1日");
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
test("formatRemainingUnit for en locale covers all units", () => {
|
|
293
|
-
expect(formatRemainingText(1000, "en")).toBe("1 second left");
|
|
294
|
-
expect(formatRemainingText(60000, "en")).toBe("1 minute left");
|
|
295
|
-
expect(formatRemainingText(3600000, "en")).toBe("1 hour left");
|
|
296
|
-
expect(formatRemainingText(86400000, "en")).toBe("1 day left");
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
test("formatLongDate with undefined locale defaults to en", () => {
|
|
300
|
-
expect(formatLongDate(new Date("2020-05-10T15:00:00Z"), undefined)).toContain("2020");
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
test("formatRelativeText with jp locale", () => {
|
|
304
|
-
expect(formatRelativeText(30000, "jp")).toEqual({
|
|
305
|
-
text: "30秒前",
|
|
306
|
-
isRelative: true,
|
|
307
|
-
});
|
|
308
|
-
expect(formatRelativeText(120000, "jp")).toEqual({
|
|
309
|
-
text: "2分前",
|
|
310
|
-
isRelative: true,
|
|
311
|
-
});
|
|
312
|
-
expect(formatRelativeText(7200000, "jp")).toEqual({
|
|
313
|
-
text: "2時間前",
|
|
314
|
-
isRelative: true,
|
|
315
|
-
});
|
|
316
|
-
expect(formatRelativeText(172800000, "jp")).toEqual({
|
|
317
|
-
text: "2日前",
|
|
318
|
-
isRelative: true,
|
|
319
|
-
});
|
|
320
|
-
});
|
|
321
|
-
});
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
describe("client/server wrappers", () => {
|
|
325
|
-
const testDate = new Date("2020-05-10T15:30:45Z");
|
|
326
|
-
|
|
327
|
-
test("formatClientDateTime / formatServerDateTime", () => {
|
|
328
|
-
expect(formatClientDateTime(testDate, { timeZone: "UTC" })).toBe("May 10, 2020 15:30");
|
|
329
|
-
expect(formatClientDateTime(testDate, { locale: "kr", timeZone: "UTC" })).toBe(
|
|
330
|
-
"2020년 5월 10일 15:30",
|
|
331
|
-
);
|
|
332
|
-
expect(formatServerDateTime(testDate, { locale: "kr", timeZone: "UTC" })).toBe(
|
|
333
|
-
"2020년 5월 10일 15:30",
|
|
334
|
-
);
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
test("formatClientTime with various presets", () => {
|
|
338
|
-
expect(formatClientTime(testDate, { preset: "ko", timeZone: "UTC" })).toBe("오후 3시 30분");
|
|
339
|
-
expect(formatClientTime(testDate, { preset: "12h", locale: "en", timeZone: "UTC" })).toMatch(
|
|
340
|
-
/3:30 (PM|pm)/,
|
|
341
|
-
);
|
|
342
|
-
expect(formatClientTime(testDate, { preset: "24h-second", timeZone: "UTC" })).toBe(
|
|
343
|
-
"15:30:45",
|
|
344
|
-
);
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
test("formatServerTime with various presets", () => {
|
|
348
|
-
expect(formatServerTime(testDate, { preset: "ko", timeZone: "UTC" })).toBe("오후 3시 30분");
|
|
349
|
-
expect(formatServerTime(testDate, { preset: "12h", locale: "en", timeZone: "UTC" })).toMatch(
|
|
350
|
-
/3:30 (PM|pm)/,
|
|
351
|
-
);
|
|
352
|
-
expect(formatServerTime(testDate, { preset: "24h-second", timeZone: "UTC" })).toBe(
|
|
353
|
-
"15:30:45",
|
|
354
|
-
);
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
test("formatClientTime returns empty for invalid date", () => {
|
|
358
|
-
expect(formatClientTime("invalid", { preset: "24h-minute" })).toBe("");
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
test("formatClientDateTime returns only date or time when one is empty", () => {
|
|
362
|
-
expect(formatClientDateTime("invalid", {})).toBe("");
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
test("formatClientRelative / formatServerRelative", () => {
|
|
366
|
-
const now = new Date("2020-05-10T15:35:45Z"); // 5 mins later
|
|
367
|
-
expect(formatClientRelative(testDate, { now, locale: "kr" })).toEqual({
|
|
368
|
-
text: "5분 전",
|
|
369
|
-
isRelative: true,
|
|
370
|
-
});
|
|
371
|
-
expect(formatServerRelative(testDate, { now, locale: "kr" })).toEqual({
|
|
372
|
-
text: "5분 전",
|
|
373
|
-
isRelative: true,
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
const farFutureNow = new Date("2020-05-20T15:30:45Z"); // 10 days later
|
|
377
|
-
expect(
|
|
378
|
-
formatClientRelative(testDate, {
|
|
379
|
-
now: farFutureNow,
|
|
380
|
-
locale: "kr",
|
|
381
|
-
fallbackDatePreset: "dot",
|
|
382
|
-
maxRelativeDays: 7,
|
|
383
|
-
}),
|
|
384
|
-
).toEqual({ text: formatDotDate(testDate), isRelative: false });
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
test("formatClientRelative returns empty when given invalid date", () => {
|
|
388
|
-
expect(formatClientRelative("invalid", {})).toEqual({ text: "", isRelative: false });
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
test("formatClientRelative beyond maxRelative without fallbackDatePreset uses dot default", () => {
|
|
392
|
-
const oldDate = new Date("2020-01-01T00:00:00Z");
|
|
393
|
-
const now = new Date("2020-05-10T00:00:00Z");
|
|
394
|
-
const result = formatClientRelative(oldDate, { now, maxRelativeDays: 30, locale: "en" });
|
|
395
|
-
expect(result.isRelative).toBe(false);
|
|
396
|
-
expect(result.text).toContain("2020");
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
test("formatClientRelative returns empty when now is invalid", () => {
|
|
400
|
-
expect(formatClientRelative(testDate, { now: "invalid" })).toEqual({
|
|
401
|
-
text: "",
|
|
402
|
-
isRelative: false,
|
|
403
|
-
});
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
test("formatServerRelative returns empty when given invalid date", () => {
|
|
407
|
-
expect(formatServerRelative("invalid", {})).toEqual({ text: "", isRelative: false });
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
test("formatServerRelative returns empty when now is invalid", () => {
|
|
411
|
-
expect(formatServerRelative(testDate, { now: "invalid" })).toEqual({
|
|
412
|
-
text: "",
|
|
413
|
-
isRelative: false,
|
|
414
|
-
});
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
test("formatServerRelative beyond maxRelativeDays with fallback preset", () => {
|
|
418
|
-
const oldDate = new Date("2020-01-01T00:00:00Z");
|
|
419
|
-
const now = new Date("2020-05-10T00:00:00Z");
|
|
420
|
-
const result = formatServerRelative(oldDate, {
|
|
421
|
-
now,
|
|
422
|
-
maxRelativeDays: 30,
|
|
423
|
-
fallbackDatePreset: "long",
|
|
424
|
-
locale: "en",
|
|
425
|
-
});
|
|
426
|
-
expect(result.isRelative).toBe(false);
|
|
427
|
-
expect(typeof result.text).toBe("string");
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
test("formatServerRelative beyond maxRelative without fallbackDatePreset", () => {
|
|
431
|
-
const oldDate = new Date("2020-01-01T00:00:00Z");
|
|
432
|
-
const now = new Date("2020-05-10T00:00:00Z");
|
|
433
|
-
const result = formatServerRelative(oldDate, { now, maxRelativeDays: 30, locale: "en" });
|
|
434
|
-
expect(result.isRelative).toBe(false);
|
|
435
|
-
expect(result.text).toContain("2020");
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
test("formatServerDate returns empty for invalid date", () => {
|
|
439
|
-
expect(formatServerDate("invalid", { preset: "dot" })).toBe("");
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
test("formatServerDate with dot preset", () => {
|
|
443
|
-
expect(formatServerDate(testDate, { preset: "dot", timeZone: "UTC" })).toBe("2020. 5. 10.");
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
test("formatServerDate with long preset", () => {
|
|
447
|
-
expect(formatServerDate(testDate, { preset: "long", locale: "kr", timeZone: "UTC" })).toBe(
|
|
448
|
-
"2020년 5월 10일",
|
|
449
|
-
);
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
test("formatServerDateTime returns only date when time is empty", () => {
|
|
453
|
-
expect(formatServerDateTime("invalid", {})).toBe("");
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
test("handles invalid inputs gracefully", () => {
|
|
457
|
-
expect(formatClientDate("invalid", { preset: "long" })).toBe("");
|
|
458
|
-
expect(formatServerTime("invalid", { preset: "ko" })).toBe("");
|
|
459
|
-
});
|
|
460
|
-
});
|
|
461
|
-
});
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { renderHook } from "@testing-library/react";
|
|
2
|
-
import { describe, expect, test } from "vitest";
|
|
3
|
-
import { useAvoidKeyboard } from "@/packages/hooks/useAvoidKeyboard";
|
|
4
|
-
|
|
5
|
-
import { useKeyboardHeight } from "@/packages/hooks/useKeyboardHeight";
|
|
6
|
-
|
|
7
|
-
vi.mock("@/packages/hooks/useKeyboardHeight");
|
|
8
|
-
|
|
9
|
-
describe("useAvoidKeyboard", () => {
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
vi.mocked(useKeyboardHeight).mockReturnValue({ keyboardHeight: 0 });
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
test("returns style object", () => {
|
|
15
|
-
const { result } = renderHook(() => useAvoidKeyboard());
|
|
16
|
-
expect(result.current.style).toBeDefined();
|
|
17
|
-
expect(result.current.style.transform).toBeUndefined();
|
|
18
|
-
expect(result.current.style.transition).toContain("transform");
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
test("applies translateY when keyboard is raised", () => {
|
|
22
|
-
vi.mocked(useKeyboardHeight).mockReturnValue({ keyboardHeight: 300 });
|
|
23
|
-
const { result } = renderHook(() => useAvoidKeyboard());
|
|
24
|
-
expect(result.current.style.transform).toBe("translateY(-300px)");
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test("uses safeAreaBottom when keyboardHeight is 0", () => {
|
|
28
|
-
const { result } = renderHook(() => useAvoidKeyboard({ safeAreaBottom: 20 }));
|
|
29
|
-
expect(result.current.style.transform).toBe("translateY(-20px)");
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test("no transform when both keyboardHeight and safeAreaBottom are 0", () => {
|
|
33
|
-
const { result } = renderHook(() => useAvoidKeyboard());
|
|
34
|
-
expect(result.current.style.transform).toBeUndefined();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test("accepts custom transition values", () => {
|
|
38
|
-
const { result } = renderHook(() =>
|
|
39
|
-
useAvoidKeyboard({ transitionDuration: 300, transitionTimingFunction: "linear" }),
|
|
40
|
-
);
|
|
41
|
-
expect(result.current.style.transition).toBe("transform 300ms linear");
|
|
42
|
-
});
|
|
43
|
-
});
|