@cloudcome/utils-core 0.0.0 → 1.1.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.
Files changed (107) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/LICENSE +21 -0
  3. package/package.json +277 -6
  4. package/src/array.ts +312 -0
  5. package/src/async.ts +379 -0
  6. package/src/base64.ts +20 -0
  7. package/src/cache.ts +146 -0
  8. package/src/color/contrast.ts +20 -0
  9. package/src/color/distance.ts +28 -0
  10. package/src/color/helpers.ts +23 -0
  11. package/src/color/hex-hsl.ts +11 -0
  12. package/src/color/hex-hsv.ts +28 -0
  13. package/src/color/hex-hwb.ts +31 -0
  14. package/src/color/hex-rgb.ts +39 -0
  15. package/src/color/hsl-lighten.ts +15 -0
  16. package/src/color/hsv-brighten.ts +17 -0
  17. package/src/color/luminance.ts +17 -0
  18. package/src/color/mix.ts +26 -0
  19. package/src/color/rgb-hsl.ts +53 -0
  20. package/src/color/rgb-hsv.ts +52 -0
  21. package/src/color/rgb-hwb.ts +56 -0
  22. package/src/color/rgb-lab.ts +33 -0
  23. package/src/color/rgb-whiter.ts +22 -0
  24. package/src/color/rgb-xyz.ts +62 -0
  25. package/src/color/types.ts +65 -0
  26. package/src/color/xyz-lab.ts +54 -0
  27. package/src/color.ts +19 -0
  28. package/src/crypto/md5.mjs +357 -0
  29. package/src/crypto/sha1.mjs +300 -0
  30. package/src/crypto/sha256.mjs +310 -0
  31. package/src/crypto/sha512.mjs +459 -0
  32. package/src/crypto.ts +60 -0
  33. package/src/date/const.ts +6 -0
  34. package/src/date/core.ts +162 -0
  35. package/src/date/days.ts +51 -0
  36. package/src/date/is.ts +186 -0
  37. package/src/date/relative.ts +92 -0
  38. package/src/date/start-end.ts +246 -0
  39. package/src/date/timezone.ts +220 -0
  40. package/src/date/weeks.ts +100 -0
  41. package/src/date.ts +8 -0
  42. package/src/dict.ts +1 -0
  43. package/src/dts/global.d.ts +27 -0
  44. package/src/easing.ts +166 -0
  45. package/src/emitter.ts +117 -0
  46. package/src/enum.ts +171 -0
  47. package/src/env.ts +62 -0
  48. package/src/error.ts +31 -0
  49. package/src/exception.ts +68 -0
  50. package/src/fn.ts +197 -0
  51. package/src/index.ts +1 -0
  52. package/src/number.ts +236 -0
  53. package/src/object/each.ts +56 -0
  54. package/src/object/get-set.ts +273 -0
  55. package/src/object/is.ts +128 -0
  56. package/src/object/merge.ts +180 -0
  57. package/src/object/process.ts +80 -0
  58. package/src/object.ts +5 -0
  59. package/src/path.ts +188 -0
  60. package/src/promise.ts +111 -0
  61. package/src/qs.ts +119 -0
  62. package/src/regexp.ts +156 -0
  63. package/src/string.ts +146 -0
  64. package/src/time/from.ts +57 -0
  65. package/src/time/to.ts +106 -0
  66. package/src/time.ts +2 -0
  67. package/src/timer.ts +226 -0
  68. package/src/tree.ts +394 -0
  69. package/src/type.ts +197 -0
  70. package/src/types.ts +78 -0
  71. package/src/unique.ts +77 -0
  72. package/src/url.ts +93 -0
  73. package/src/version.ts +71 -0
  74. package/test/array.test.ts +332 -0
  75. package/test/async-real.test.ts +39 -0
  76. package/test/async.test.ts +375 -0
  77. package/test/base64.test.ts +32 -0
  78. package/test/cache.test.ts +83 -0
  79. package/test/color.test.ts +163 -0
  80. package/test/crypto.test.ts +34 -0
  81. package/test/date-tz.test.ts +206 -0
  82. package/test/date.test.ts +353 -0
  83. package/test/easing.test.ts +33 -0
  84. package/test/emitter.test.ts +71 -0
  85. package/test/enum.test.ts +113 -0
  86. package/test/env.test.ts +69 -0
  87. package/test/error.test.ts +58 -0
  88. package/test/exception.test.ts +43 -0
  89. package/test/fn.test.ts +263 -0
  90. package/test/helpers.ts +23 -0
  91. package/test/index.test.ts +6 -0
  92. package/test/number.test.ts +213 -0
  93. package/test/object.test.ts +309 -0
  94. package/test/path.test.ts +156 -0
  95. package/test/promise.test.ts +199 -0
  96. package/test/qs.test.ts +79 -0
  97. package/test/regexp.test.ts +97 -0
  98. package/test/string.test.ts +150 -0
  99. package/test/time.test.ts +214 -0
  100. package/test/timer.test.ts +114 -0
  101. package/test/tree.test.ts +348 -0
  102. package/test/type.test.ts +226 -0
  103. package/test/unique.test.ts +71 -0
  104. package/test/url.test.ts +136 -0
  105. package/test/version.test.ts +52 -0
  106. package/tsconfig.json +31 -0
  107. package/vite.config.mts +114 -0
@@ -0,0 +1,113 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { declareEnum } from '../src/enum';
3
+
4
+ describe('枚举定义测试', () => {
5
+ it('编译时测试', () => {
6
+ const Status = declareEnum<{ label: string; level: number }>().define({
7
+ Pending: { value: 0, label: '待处理', level: 11 },
8
+ Approved: { value: 1, label: '已批准', level: 22 },
9
+ });
10
+
11
+ assertType<0>(Status.Pending);
12
+ assertType<1>(Status.Approved);
13
+
14
+ assertType<{ key: 'Pending'; readonly value: 0; readonly label: '待处理'; level: 11 }>(Status.$Pending);
15
+ assertType<{ key: 'Approved'; readonly value: 1; readonly label: '已批准'; level: 22 }>(Status.$Approved);
16
+
17
+ assertType<{
18
+ Pending: { readonly value: 0; readonly label: '待处理'; level: 11 };
19
+ Approved: { readonly value: 1; readonly label: '已批准'; level: 22 };
20
+ }>(Status.definition);
21
+ assertType<{ label: string; key: 'Pending' | 'Approved' }[]>(Status.descriptions);
22
+
23
+ assertType<['Pending', 'Approved']>(Status.keys);
24
+ assertType<2>(Status.length);
25
+ assertType<[0, 1]>(Status.values);
26
+
27
+ assertType<{
28
+ readonly Pending: 0;
29
+ readonly Approved: 1;
30
+ }>(Status.kvRecord);
31
+ assertType<{
32
+ readonly 0: 'Pending';
33
+ readonly 1: 'Approved';
34
+ }>(Status.vkRecord);
35
+
36
+ const klr = Status.toKeyRecord('label');
37
+ assertType<Record<'Pending' | 'Approved', string>>(klr);
38
+ const vlr = Status.toValueRecord('level');
39
+ assertType<Record<0 | 1, number>>(vlr);
40
+ });
41
+
42
+ it('运行时测试', () => {
43
+ const Status = declareEnum<{ label: string; level: number }>().define({
44
+ Pending: { value: 0, label: '待处理', level: 11 },
45
+ Approved: { value: 1, label: '已批准', level: 22 },
46
+ });
47
+
48
+ expect(Status.Pending).toBe(0);
49
+ expect(Status.Approved).toBe(1);
50
+
51
+ expect(Status.$Pending.value).toBe(0);
52
+ expect(Status.$Approved.value).toBe(1);
53
+
54
+ expect(Status.$Pending.key).toBe('Pending');
55
+ expect(Status.$Approved.key).toBe('Approved');
56
+
57
+ expect(Status.$Pending.label).toBe('待处理');
58
+ expect(Status.$Pending.level).toBe(11);
59
+ expect(Status.$Approved.label).toBe('已批准');
60
+ expect(Status.$Approved.level).toBe(22);
61
+
62
+ expect(Status.keys).toEqual(['Pending', 'Approved']);
63
+ expect(Status.values).toEqual([0, 1]);
64
+ expect(Status.length).toEqual(2);
65
+
66
+ expect(Status.definition).toEqual({
67
+ Pending: { value: 0, label: '待处理', level: 11 },
68
+ Approved: { value: 1, label: '已批准', level: 22 },
69
+ });
70
+ expect(Status.descriptions).toEqual([
71
+ { key: 'Pending', value: 0, label: '待处理', level: 11 },
72
+ { key: 'Approved', value: 1, label: '已批准', level: 22 },
73
+ ]);
74
+
75
+ expect(Status.kvRecord).toEqual({
76
+ Pending: 0,
77
+ Approved: 1,
78
+ });
79
+ expect(Status.vkRecord).toEqual({
80
+ 0: 'Pending',
81
+ 1: 'Approved',
82
+ });
83
+
84
+ expect(Status.toKeyRecord('label')).toEqual({
85
+ Pending: '待处理',
86
+ Approved: '已批准',
87
+ });
88
+ expect(Status.toValueRecord('level')).toEqual({
89
+ 0: 11,
90
+ 1: 22,
91
+ });
92
+ });
93
+
94
+ it('枚举键名大写开头', () => {
95
+ // 测试小写键名应该报错
96
+ expect(() => {
97
+ declareEnum().define({
98
+ // @ts-expect-error 错误:枚举键名 active 必须以大写字母开头
99
+ active: { value: 1 },
100
+ });
101
+ }).toThrowError('错误:枚举键名 active 必须以大写字母开头');
102
+ });
103
+
104
+ it('枚举键名非 $ 开头', () => {
105
+ // 测试小写键名应该报错
106
+ expect(() => {
107
+ declareEnum().define({
108
+ // @ts-expect-error 错误:枚举键名 $active 不能以 $ 符号开头
109
+ $active: { value: 1 },
110
+ });
111
+ }).toThrowError('错误:枚举键名 $active 不能以 $ 符号开头');
112
+ });
113
+ });
@@ -0,0 +1,69 @@
1
+ import { isBrowser, isLinux, isMacOS, isNode, isWindows, isWorker } from '@/env';
2
+ import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
3
+
4
+ beforeAll(() => {
5
+ // @ts-ignore
6
+ global.TEST_MOCK = {
7
+ IS_BROWSER: false,
8
+ IS_NODE: false,
9
+ };
10
+ });
11
+
12
+ beforeEach(() => {
13
+ TEST_MOCK.IS_NODE = false;
14
+ TEST_MOCK.IS_NODE = false;
15
+ });
16
+
17
+ it('env', () => {
18
+ expect(isBrowser()).toBe(false);
19
+ expect(isNode()).toBe(false);
20
+ expect(isWorker()).toBe(false);
21
+ });
22
+
23
+ describe('isMacOS', () => {
24
+ it('应在 macOS 浏览器环境中返回 true', () => {
25
+ TEST_MOCK.IS_BROWSER = true;
26
+ // @ts-ignore
27
+ global.navigator = { platform: 'MacIntel' };
28
+ expect(isMacOS()).toBe(true);
29
+ });
30
+
31
+ it('应在 macOS Node.js 环境中返回 true', () => {
32
+ TEST_MOCK.IS_NODE = true;
33
+ // @ts-ignore
34
+ global.process = { platform: 'darwin' };
35
+ expect(isMacOS()).toBe(true);
36
+ });
37
+ });
38
+
39
+ describe('isLinux', () => {
40
+ it('应在 Linux 浏览器环境中返回 true', () => {
41
+ TEST_MOCK.IS_BROWSER = true;
42
+ // @ts-ignore
43
+ global.navigator = { platform: 'Linux x86_64' };
44
+ expect(isLinux()).toBe(true);
45
+ });
46
+
47
+ it('应在 Linux Node.js 环境中返回 true', () => {
48
+ TEST_MOCK.IS_NODE = true;
49
+ // @ts-ignore
50
+ global.process = { platform: 'linux' };
51
+ expect(isLinux()).toBe(true);
52
+ });
53
+ });
54
+
55
+ describe('isWindows', () => {
56
+ it('应在 Windows 浏览器环境中返回 true', () => {
57
+ TEST_MOCK.IS_BROWSER = true;
58
+ // @ts-ignore
59
+ global.navigator = { platform: 'Win32' };
60
+ expect(isWindows()).toBe(true);
61
+ });
62
+
63
+ it('应在 Windows Node.js 环境中返回 true', () => {
64
+ TEST_MOCK.IS_NODE = true;
65
+ // @ts-ignore
66
+ global.process = { platform: 'win32' };
67
+ expect(isWindows()).toBe(true);
68
+ });
69
+ });
@@ -0,0 +1,58 @@
1
+ import { errorAssign, errorNormalize } from '@/error';
2
+ import { isError } from '@/type';
3
+
4
+ function shouldBeString(input: string) {
5
+ return true;
6
+ }
7
+
8
+ function shouldOptionalString(input?: string) {
9
+ return true;
10
+ }
11
+
12
+ function shouldBeUnknown(input: unknown) {
13
+ return true;
14
+ }
15
+
16
+ test('errorNormalize', () => {
17
+ const err1 = errorNormalize('abc');
18
+ expect(isError(err1)).toBe(true);
19
+
20
+ const err2 = errorNormalize(new Error('abc'));
21
+ expect(isError(err2)).toBe(true);
22
+
23
+ const err3 = new Error('abc');
24
+ expect(errorNormalize(err3)).toBe(err3);
25
+
26
+ let err4 = null;
27
+ try {
28
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
29
+ // @ts-ignore
30
+ 1();
31
+ } catch (cause) {
32
+ err4 = errorNormalize(cause);
33
+ }
34
+
35
+ if (err4) {
36
+ shouldBeString(err4.name);
37
+ shouldBeString(err4.message);
38
+ shouldOptionalString(err4.stack);
39
+ shouldBeUnknown(err4.cause);
40
+ }
41
+
42
+ expect(isError(err4)).toBe(true);
43
+
44
+ const err5 = errorNormalize(errorAssign(new Error(), { aaa: '' }));
45
+ shouldBeString(err5.name);
46
+ shouldBeString(err5.message);
47
+ shouldOptionalString(err5.stack);
48
+ shouldBeUnknown(err5.cause);
49
+ shouldBeString(err5.aaa);
50
+ expect(isError(err5)).toBe(true);
51
+ });
52
+
53
+ test('errorAssign', () => {
54
+ const data = { xyz: 1, abc: new Date() };
55
+ const error = errorAssign(new Error('abc'), data);
56
+ expect(error.xyz).toBe(data.xyz);
57
+ expect(error.abc).toBe(data.abc);
58
+ });
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { buildException } from '../src/exception';
3
+
4
+ describe('异常构建工具', () => {
5
+ it('应该创建具有正确名称的自定义错误类', () => {
6
+ const TestError = buildException('TestError');
7
+ const error = new TestError('测试消息');
8
+
9
+ expect(error.name).toBe('TestError');
10
+ expect(error).toBeInstanceOf(Error);
11
+ });
12
+
13
+ it('应该使用默认格式格式化错误消息', () => {
14
+ const TestError = buildException('TestError');
15
+ const error = new TestError('测试消息');
16
+
17
+ expect(error.message).toBe('[TestError] 测试消息');
18
+ });
19
+
20
+ it('应该支持自定义消息格式', () => {
21
+ const customFormat = (name: string, message: string) => `${name}::${message}`;
22
+ const TestError = buildException('TestError', { format: customFormat });
23
+ const error = new TestError('测试消息');
24
+
25
+ expect(error.message).toBe('TestError::测试消息');
26
+ });
27
+
28
+ it('应该合并额外属性到错误实例', () => {
29
+ const TestError = buildException<{ code: number; details: string }>('TestError');
30
+ const error = new TestError('测试消息', { code: 404, details: '未找到' });
31
+
32
+ expect(error.code).toBe(404);
33
+ expect(error.details).toBe('未找到');
34
+ });
35
+
36
+ it('应该保留完整的堆栈跟踪', () => {
37
+ const TestError = buildException('TestError');
38
+ const error = new TestError('测试消息');
39
+
40
+ expect(error.stack).toBeDefined();
41
+ expect(error.stack).toContain('TestError: [TestError] 测试消息');
42
+ });
43
+ });
@@ -0,0 +1,263 @@
1
+ import { fnDebounce, fnOnce, fnThrottle } from '@/fn';
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ beforeEach(() => {
5
+ vi.useFakeTimers();
6
+ });
7
+
8
+ afterEach(() => {
9
+ vi.restoreAllMocks();
10
+ });
11
+
12
+ describe('fnDebounce', () => {
13
+ it('应正确防抖函数', async () => {
14
+ const mockFn = vi.fn();
15
+ const debouncedFn = fnDebounce(mockFn, 100);
16
+
17
+ debouncedFn();
18
+ debouncedFn();
19
+ debouncedFn();
20
+
21
+ setTimeout(() => {
22
+ expect(mockFn).toHaveBeenCalledTimes(1);
23
+ }, 150);
24
+
25
+ await vi.runAllTimersAsync();
26
+ });
27
+
28
+ it('应支持 leading 选项', async () => {
29
+ const mockFn = vi.fn();
30
+ const debouncedFn = fnDebounce(mockFn, { wait: 100, leading: true });
31
+
32
+ debouncedFn();
33
+ expect(mockFn).toHaveBeenCalledTimes(1);
34
+
35
+ debouncedFn();
36
+ debouncedFn();
37
+
38
+ setTimeout(() => {
39
+ expect(mockFn).toHaveBeenCalledTimes(2);
40
+ }, 150);
41
+
42
+ await vi.runAllTimersAsync();
43
+ });
44
+
45
+ it('应支持 cancel 方法', async () => {
46
+ const mockFn = vi.fn();
47
+ const debouncedFn = fnDebounce(mockFn, 100);
48
+
49
+ debouncedFn();
50
+ debouncedFn.cancel();
51
+
52
+ setTimeout(() => {
53
+ expect(mockFn).not.toHaveBeenCalled();
54
+ }, 150);
55
+
56
+ await vi.runAllTimersAsync();
57
+ });
58
+
59
+ it('应支持多次调用 cancel 方法', async () => {
60
+ const mockFn = vi.fn();
61
+ const debouncedFn = fnDebounce(mockFn, 100);
62
+
63
+ debouncedFn();
64
+ debouncedFn.cancel();
65
+ debouncedFn.cancel();
66
+
67
+ setTimeout(() => {
68
+ expect(mockFn).not.toHaveBeenCalled();
69
+ }, 150);
70
+
71
+ await vi.runAllTimersAsync();
72
+ });
73
+
74
+ it('应支持多次调用 debounced 函数', async () => {
75
+ const mockFn = vi.fn();
76
+ const debouncedFn = fnDebounce(mockFn, 100);
77
+
78
+ debouncedFn();
79
+ setTimeout(() => {
80
+ debouncedFn();
81
+ }, 50);
82
+ setTimeout(() => {
83
+ debouncedFn();
84
+ }, 100);
85
+ setTimeout(() => {
86
+ debouncedFn();
87
+ }, 150);
88
+ setTimeout(() => {
89
+ // 等待最后一次计时结束
90
+ }, 150);
91
+
92
+ await vi.runAllTimersAsync();
93
+ expect(mockFn).toHaveBeenCalledTimes(1);
94
+ });
95
+ });
96
+
97
+ describe('fnThrottle', () => {
98
+ it('应正确节流函数调用', async () => {
99
+ const mockFn = vi.fn();
100
+ const throttledFn = fnThrottle(mockFn, 100);
101
+
102
+ throttledFn();
103
+ throttledFn();
104
+ throttledFn();
105
+
106
+ setTimeout(() => {
107
+ throttledFn();
108
+ expect(mockFn).toHaveBeenCalledTimes(0);
109
+ }, 50);
110
+
111
+ setTimeout(() => {
112
+ throttledFn();
113
+ expect(mockFn).toHaveBeenCalledTimes(1);
114
+ }, 100);
115
+
116
+ setTimeout(() => {
117
+ throttledFn();
118
+ expect(mockFn).toHaveBeenCalledTimes(1);
119
+ }, 150);
120
+
121
+ setTimeout(() => {
122
+ throttledFn();
123
+ expect(mockFn).toHaveBeenCalledTimes(2);
124
+ }, 200);
125
+
126
+ await vi.runAllTimersAsync();
127
+ expect(mockFn).toHaveBeenCalledTimes(2);
128
+ });
129
+
130
+ it('应支持 leading 选项', async () => {
131
+ const mockFn = vi.fn();
132
+ const throttledFn = fnThrottle(mockFn, { wait: 100, leading: true });
133
+
134
+ throttledFn();
135
+ expect(mockFn).toHaveBeenCalledTimes(1);
136
+
137
+ throttledFn();
138
+ throttledFn();
139
+
140
+ setTimeout(() => {
141
+ throttledFn();
142
+ expect(mockFn).toHaveBeenCalledTimes(2);
143
+ }, 100);
144
+
145
+ await vi.runAllTimersAsync();
146
+ expect(mockFn).toHaveBeenCalledTimes(2);
147
+ });
148
+
149
+ it('应支持 trailing 选项', async () => {
150
+ const mockFn = vi.fn();
151
+ const throttledFn = fnThrottle(mockFn, { wait: 100, trailing: true });
152
+
153
+ throttledFn();
154
+ throttledFn();
155
+ throttledFn();
156
+
157
+ setTimeout(() => {
158
+ expect(mockFn).toHaveBeenCalledTimes(0);
159
+ }, 50);
160
+
161
+ setTimeout(() => {
162
+ expect(mockFn).toHaveBeenCalledTimes(1);
163
+ }, 100);
164
+
165
+ await vi.runAllTimersAsync();
166
+ expect(mockFn).toHaveBeenCalledTimes(1);
167
+ });
168
+
169
+ it('应支持 trailing 选项', async () => {
170
+ const mockFn = vi.fn();
171
+ const throttledFn = fnThrottle(mockFn, { wait: 100, trailing: true });
172
+
173
+ throttledFn();
174
+ throttledFn();
175
+ throttledFn();
176
+
177
+ setTimeout(() => {
178
+ expect(mockFn).toHaveBeenCalledTimes(0);
179
+ }, 50);
180
+
181
+ setTimeout(() => {
182
+ expect(mockFn).toHaveBeenCalledTimes(1);
183
+ }, 100);
184
+
185
+ await vi.runAllTimersAsync();
186
+ expect(mockFn).toHaveBeenCalledTimes(1);
187
+ });
188
+
189
+ it('应支持 leading + trailing 选项', async () => {
190
+ const mockFn = vi.fn();
191
+ const throttledFn = fnThrottle(mockFn, { wait: 100, leading: true, trailing: true });
192
+
193
+ throttledFn();
194
+ expect(mockFn).toHaveBeenCalledTimes(1);
195
+
196
+ throttledFn();
197
+ throttledFn();
198
+
199
+ setTimeout(() => {
200
+ expect(mockFn).toHaveBeenCalledTimes(1);
201
+ }, 50);
202
+
203
+ setTimeout(() => {
204
+ expect(mockFn).toHaveBeenCalledTimes(2);
205
+ }, 100);
206
+
207
+ await vi.runAllTimersAsync();
208
+ expect(mockFn).toHaveBeenCalledTimes(2);
209
+ });
210
+
211
+ it('应取消节流函数调用', async () => {
212
+ const mockFn = vi.fn();
213
+ const throttledFn = fnThrottle(mockFn, 100);
214
+
215
+ throttledFn();
216
+ throttledFn.cancel();
217
+
218
+ setTimeout(() => {
219
+ throttledFn();
220
+ }, 100);
221
+
222
+ await vi.runAllTimersAsync();
223
+ expect(mockFn).toHaveBeenCalledTimes(0);
224
+ });
225
+ });
226
+
227
+ describe('fnOnce', () => {
228
+ it('应确保函数只被调用一次', () => {
229
+ const mockFn = vi.fn();
230
+ const onceFn = fnOnce(mockFn);
231
+
232
+ onceFn();
233
+ onceFn();
234
+ onceFn();
235
+
236
+ expect(mockFn).toHaveBeenCalledTimes(1);
237
+ });
238
+
239
+ it('应支持多次调用,但只执行一次', () => {
240
+ const mockFn = vi.fn();
241
+ const onceFn = fnOnce(mockFn);
242
+
243
+ onceFn();
244
+ expect(mockFn).toHaveBeenCalledTimes(1);
245
+
246
+ onceFn();
247
+ expect(mockFn).toHaveBeenCalledTimes(1);
248
+
249
+ onceFn();
250
+ expect(mockFn).toHaveBeenCalledTimes(1);
251
+ });
252
+
253
+ it('应支持传递参数', () => {
254
+ const mockFn = vi.fn();
255
+ const onceFn = fnOnce(mockFn);
256
+
257
+ onceFn('arg1', 'arg2');
258
+ onceFn('arg3', 'arg4');
259
+
260
+ expect(mockFn).toHaveBeenCalledTimes(1);
261
+ expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2');
262
+ });
263
+ });
@@ -0,0 +1,23 @@
1
+ export function createAfn<T, R>(options: {
2
+ delay: number;
3
+ result?: T;
4
+ reason?: R;
5
+ onResolved?: (result: T) => void;
6
+ onRejected?: (reason: R) => void;
7
+ onFinally?: () => void;
8
+ }) {
9
+ return () =>
10
+ new Promise((resolve, reject) => {
11
+ setTimeout(() => {
12
+ if (options.reason) {
13
+ options.onRejected?.(options.reason);
14
+ reject(options.reason);
15
+ } else {
16
+ options.onResolved?.(options.result as T);
17
+ resolve(options.result);
18
+ }
19
+
20
+ options.onFinally?.();
21
+ }, options.delay);
22
+ });
23
+ }
@@ -0,0 +1,6 @@
1
+ import { VERSION } from '@/index';
2
+ import { expect, it } from 'vitest';
3
+
4
+ it('version', () => {
5
+ expect(VERSION).toEqual(PKG_VERSION);
6
+ });