@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.
- package/CHANGELOG.md +52 -0
- package/LICENSE +21 -0
- package/package.json +277 -6
- package/src/array.ts +312 -0
- package/src/async.ts +379 -0
- package/src/base64.ts +20 -0
- package/src/cache.ts +146 -0
- package/src/color/contrast.ts +20 -0
- package/src/color/distance.ts +28 -0
- package/src/color/helpers.ts +23 -0
- package/src/color/hex-hsl.ts +11 -0
- package/src/color/hex-hsv.ts +28 -0
- package/src/color/hex-hwb.ts +31 -0
- package/src/color/hex-rgb.ts +39 -0
- package/src/color/hsl-lighten.ts +15 -0
- package/src/color/hsv-brighten.ts +17 -0
- package/src/color/luminance.ts +17 -0
- package/src/color/mix.ts +26 -0
- package/src/color/rgb-hsl.ts +53 -0
- package/src/color/rgb-hsv.ts +52 -0
- package/src/color/rgb-hwb.ts +56 -0
- package/src/color/rgb-lab.ts +33 -0
- package/src/color/rgb-whiter.ts +22 -0
- package/src/color/rgb-xyz.ts +62 -0
- package/src/color/types.ts +65 -0
- package/src/color/xyz-lab.ts +54 -0
- package/src/color.ts +19 -0
- package/src/crypto/md5.mjs +357 -0
- package/src/crypto/sha1.mjs +300 -0
- package/src/crypto/sha256.mjs +310 -0
- package/src/crypto/sha512.mjs +459 -0
- package/src/crypto.ts +60 -0
- package/src/date/const.ts +6 -0
- package/src/date/core.ts +162 -0
- package/src/date/days.ts +51 -0
- package/src/date/is.ts +186 -0
- package/src/date/relative.ts +92 -0
- package/src/date/start-end.ts +246 -0
- package/src/date/timezone.ts +220 -0
- package/src/date/weeks.ts +100 -0
- package/src/date.ts +8 -0
- package/src/dict.ts +1 -0
- package/src/dts/global.d.ts +27 -0
- package/src/easing.ts +166 -0
- package/src/emitter.ts +117 -0
- package/src/enum.ts +171 -0
- package/src/env.ts +62 -0
- package/src/error.ts +31 -0
- package/src/exception.ts +68 -0
- package/src/fn.ts +197 -0
- package/src/index.ts +1 -0
- package/src/number.ts +236 -0
- package/src/object/each.ts +56 -0
- package/src/object/get-set.ts +273 -0
- package/src/object/is.ts +128 -0
- package/src/object/merge.ts +180 -0
- package/src/object/process.ts +80 -0
- package/src/object.ts +5 -0
- package/src/path.ts +188 -0
- package/src/promise.ts +111 -0
- package/src/qs.ts +119 -0
- package/src/regexp.ts +156 -0
- package/src/string.ts +146 -0
- package/src/time/from.ts +57 -0
- package/src/time/to.ts +106 -0
- package/src/time.ts +2 -0
- package/src/timer.ts +226 -0
- package/src/tree.ts +394 -0
- package/src/type.ts +197 -0
- package/src/types.ts +78 -0
- package/src/unique.ts +77 -0
- package/src/url.ts +93 -0
- package/src/version.ts +71 -0
- package/test/array.test.ts +332 -0
- package/test/async-real.test.ts +39 -0
- package/test/async.test.ts +375 -0
- package/test/base64.test.ts +32 -0
- package/test/cache.test.ts +83 -0
- package/test/color.test.ts +163 -0
- package/test/crypto.test.ts +34 -0
- package/test/date-tz.test.ts +206 -0
- package/test/date.test.ts +353 -0
- package/test/easing.test.ts +33 -0
- package/test/emitter.test.ts +71 -0
- package/test/enum.test.ts +113 -0
- package/test/env.test.ts +69 -0
- package/test/error.test.ts +58 -0
- package/test/exception.test.ts +43 -0
- package/test/fn.test.ts +263 -0
- package/test/helpers.ts +23 -0
- package/test/index.test.ts +6 -0
- package/test/number.test.ts +213 -0
- package/test/object.test.ts +309 -0
- package/test/path.test.ts +156 -0
- package/test/promise.test.ts +199 -0
- package/test/qs.test.ts +79 -0
- package/test/regexp.test.ts +97 -0
- package/test/string.test.ts +150 -0
- package/test/time.test.ts +214 -0
- package/test/timer.test.ts +114 -0
- package/test/tree.test.ts +348 -0
- package/test/type.test.ts +226 -0
- package/test/unique.test.ts +71 -0
- package/test/url.test.ts +136 -0
- package/test/version.test.ts +52 -0
- package/tsconfig.json +31 -0
- package/vite.config.mts +114 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createMinDelayPromise,
|
|
3
|
+
isPromiseLike,
|
|
4
|
+
promiseDelay,
|
|
5
|
+
promiseShared,
|
|
6
|
+
promiseTimeout,
|
|
7
|
+
promiseWhen,
|
|
8
|
+
} from '@/promise';
|
|
9
|
+
import { describe, expect, it } from 'vitest';
|
|
10
|
+
|
|
11
|
+
describe('promiseDelay', () => {
|
|
12
|
+
it('应在指定时间后解决 Promise', async () => {
|
|
13
|
+
const startTime = Date.now();
|
|
14
|
+
await promiseDelay(100);
|
|
15
|
+
const endTime = Date.now();
|
|
16
|
+
expect(endTime - startTime).toBeGreaterThanOrEqual(0);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('如果 ms 为 0,应立即解决 Promise', async () => {
|
|
20
|
+
const startTime = Date.now();
|
|
21
|
+
await promiseDelay(0);
|
|
22
|
+
const endTime = Date.now();
|
|
23
|
+
expect(endTime - startTime).toBeLessThan(10);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('应在调用 abort 后解决 Promise', async () => {
|
|
27
|
+
const ctrl = new AbortController();
|
|
28
|
+
const startTime = Date.now();
|
|
29
|
+
const promise = promiseDelay(1000, ctrl);
|
|
30
|
+
ctrl.abort();
|
|
31
|
+
await promise;
|
|
32
|
+
const endTime = Date.now();
|
|
33
|
+
expect(endTime - startTime).toBeLessThan(1010);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('promiseTimeout', () => {
|
|
38
|
+
it('如果 Promise 在指定时间内解决,应返回其结果', async () => {
|
|
39
|
+
const result = await promiseTimeout(Promise.resolve('success'), 100);
|
|
40
|
+
expect(result).toBe('success');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('如果 Promise 在指定时间内未解决,应抛出 "timeout" 错误', async () => {
|
|
44
|
+
await expect(promiseTimeout(promiseDelay(100), 0)).rejects.toThrow('timeout');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('如果 Promise 在指定时间内恰好解决,应返回其结果', async () => {
|
|
48
|
+
const result = await promiseTimeout(Promise.resolve('success'), 10);
|
|
49
|
+
expect(result).toBe('success');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('promiseWhen', () => {
|
|
54
|
+
it('如果条件初始为真,应立即解决 Promise', async () => {
|
|
55
|
+
const startTime = Date.now();
|
|
56
|
+
await promiseWhen(() => true, 100);
|
|
57
|
+
const endTime = Date.now();
|
|
58
|
+
expect(endTime - startTime).toBeLessThan(10);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('应在条件变为真后解决 Promise', async () => {
|
|
62
|
+
let conditionMet = false;
|
|
63
|
+
setTimeout(() => {
|
|
64
|
+
conditionMet = true;
|
|
65
|
+
}, 100);
|
|
66
|
+
const startTime = Date.now();
|
|
67
|
+
await promiseWhen(() => conditionMet, 10);
|
|
68
|
+
const endTime = Date.now();
|
|
69
|
+
expect(endTime - startTime).toBeGreaterThanOrEqual(100);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('应使用较小的时间间隔检查条件', async () => {
|
|
73
|
+
let conditionMet = false;
|
|
74
|
+
setTimeout(() => {
|
|
75
|
+
conditionMet = true;
|
|
76
|
+
}, 50);
|
|
77
|
+
const startTime = Date.now();
|
|
78
|
+
await promiseWhen(() => conditionMet, 10);
|
|
79
|
+
const endTime = Date.now();
|
|
80
|
+
expect(endTime - startTime).toBeGreaterThanOrEqual(50);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('isPromiseLike', () => {
|
|
85
|
+
it('应正确判断 Promise 类型', async () => {
|
|
86
|
+
try {
|
|
87
|
+
const p1 = Promise.resolve();
|
|
88
|
+
expect(isPromiseLike(p1)).toBe(true);
|
|
89
|
+
await p1;
|
|
90
|
+
|
|
91
|
+
const p2 = Promise.reject();
|
|
92
|
+
expect(isPromiseLike(p2)).toBe(true);
|
|
93
|
+
// 这里需要执行掉,否会打印未捕获的 promise 错误
|
|
94
|
+
await p2;
|
|
95
|
+
|
|
96
|
+
const p3 = new Promise<void>((r) => r());
|
|
97
|
+
expect(isPromiseLike(p3)).toBe(true);
|
|
98
|
+
await p3;
|
|
99
|
+
} catch (cause) {
|
|
100
|
+
//
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('应正确判断 Promise 类似对象', () => {
|
|
105
|
+
// biome-ignore lint/suspicious/noThenProperty: <explanation>
|
|
106
|
+
expect(isPromiseLike({ then: () => {} })).toBe(true);
|
|
107
|
+
// biome-ignore lint/suspicious/noThenProperty: <explanation>
|
|
108
|
+
expect(isPromiseLike({ then: 'not a function' })).toBe(false);
|
|
109
|
+
expect(isPromiseLike({})).toBe(false);
|
|
110
|
+
expect(isPromiseLike(null)).toBe(false);
|
|
111
|
+
expect(isPromiseLike(undefined)).toBe(false);
|
|
112
|
+
expect(isPromiseLike('string')).toBe(false);
|
|
113
|
+
expect(isPromiseLike(42)).toBe(false);
|
|
114
|
+
expect(isPromiseLike(true)).toBe(false);
|
|
115
|
+
expect(isPromiseLike(Symbol('sym'))).toBe(false);
|
|
116
|
+
expect(isPromiseLike(BigInt(123))).toBe(false);
|
|
117
|
+
expect(isPromiseLike(Number.NaN)).toBe(false);
|
|
118
|
+
expect(isPromiseLike(new Error('error'))).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('sharedPromise', () => {
|
|
123
|
+
it('应共享原始 Promise 的成功状态', async () => {
|
|
124
|
+
const value = Math.random();
|
|
125
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
126
|
+
const { promise: status, resolve: done } = Promise.withResolvers<void>();
|
|
127
|
+
const shared1 = promiseShared(promise);
|
|
128
|
+
|
|
129
|
+
setTimeout(async () => {
|
|
130
|
+
// 在完成之前共享
|
|
131
|
+
resolve(value);
|
|
132
|
+
|
|
133
|
+
// 在完成之后共享
|
|
134
|
+
await expect(promiseShared(promise)).resolves.toBe(value);
|
|
135
|
+
done();
|
|
136
|
+
}, 10);
|
|
137
|
+
|
|
138
|
+
await expect(shared1).resolves.toBe(value);
|
|
139
|
+
await expect(promise).resolves.toBe(value);
|
|
140
|
+
|
|
141
|
+
await expect(status).resolves.toBe(undefined);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('应共享原始 Promise 的拒绝状态', async () => {
|
|
145
|
+
const value = Math.random();
|
|
146
|
+
const { promise, reject } = Promise.withResolvers();
|
|
147
|
+
const { promise: status, resolve: done } = Promise.withResolvers<void>();
|
|
148
|
+
const shared1 = promiseShared(promise);
|
|
149
|
+
|
|
150
|
+
setTimeout(async () => {
|
|
151
|
+
// 在完成之前共享
|
|
152
|
+
reject(value);
|
|
153
|
+
|
|
154
|
+
// 在完成之后共享
|
|
155
|
+
await expect(promiseShared(promise)).rejects.toBe(value);
|
|
156
|
+
done();
|
|
157
|
+
}, 10);
|
|
158
|
+
|
|
159
|
+
await expect(shared1).rejects.toBe(value);
|
|
160
|
+
await expect(promise).rejects.toBe(value);
|
|
161
|
+
|
|
162
|
+
await expect(status).resolves.toBe(undefined);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('createMinDelayPromise', () => {
|
|
167
|
+
it('当实际执行时间小于最小等待时间时,应等待剩余时间', async () => {
|
|
168
|
+
const minWait = 100;
|
|
169
|
+
const end = createMinDelayPromise(minWait);
|
|
170
|
+
const startTime = Date.now();
|
|
171
|
+
await promiseDelay(50); // 模拟操作耗时
|
|
172
|
+
await end();
|
|
173
|
+
const endTime = Date.now();
|
|
174
|
+
expect(endTime - startTime).toBeGreaterThanOrEqual(minWait - 10);
|
|
175
|
+
expect(endTime - startTime).toBeLessThanOrEqual(minWait + 10);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('当实际执行时间大于最小等待时间时,应立即返回', async () => {
|
|
179
|
+
const minWait = 50;
|
|
180
|
+
const end = createMinDelayPromise(minWait);
|
|
181
|
+
const startTime = Date.now();
|
|
182
|
+
await promiseDelay(100); // 模拟操作耗时
|
|
183
|
+
await end();
|
|
184
|
+
const endTime = Date.now();
|
|
185
|
+
// 实际情况下,这个时间是接近 100,可能是 99,也有可能是 101
|
|
186
|
+
expect(endTime - startTime).toBeGreaterThanOrEqual(90);
|
|
187
|
+
expect(endTime - startTime).toBeLessThanOrEqual(110);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('当最小等待时间为 0 时,应立即返回', async () => {
|
|
191
|
+
const minWait = 0;
|
|
192
|
+
const end = createMinDelayPromise(minWait);
|
|
193
|
+
const startTime = Date.now();
|
|
194
|
+
await end();
|
|
195
|
+
const endTime = Date.now();
|
|
196
|
+
expect(endTime - startTime).toBeLessThanOrEqual(10);
|
|
197
|
+
expect(endTime - startTime).toBeGreaterThanOrEqual(0);
|
|
198
|
+
});
|
|
199
|
+
});
|
package/test/qs.test.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { dateFormat } from '@/date';
|
|
2
|
+
import { type QSReader, type QSWriter, qsParse, qsStringify } from '@/qs';
|
|
3
|
+
import { isArray, isBoolean, isDate, isFunction, isNull, isNumber, isString, isUndefined } from '@/type';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
|
|
6
|
+
describe('qsParse', () => {
|
|
7
|
+
it('默认 reader', () => {
|
|
8
|
+
expect(qsParse('?a=1')).toEqual({ a: '1' });
|
|
9
|
+
expect(qsParse('?a=1&b=2')).toEqual({ a: '1', b: '2' });
|
|
10
|
+
expect(qsParse('?a=1&b=1&b=2')).toEqual({ a: '1', b: ['1', '2'] });
|
|
11
|
+
expect(qsParse('?a=1&a=2&b=1')).toEqual({ a: ['1', '2'], b: '1' });
|
|
12
|
+
expect(qsParse('?a=1&a=2&a=3&a=4')).toEqual({ a: ['1', '2', '3', '4'] });
|
|
13
|
+
expect(qsParse('xx?a=1&a=2&a=3&a=4')).toEqual({ a: ['1', '2', '3', '4'] });
|
|
14
|
+
expect(qsParse('xx??a=1&a=2&a=3&a=4')).toEqual({ a: ['1', '2', '3', '4'] });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('自定义 reader', () => {
|
|
18
|
+
const qsReader: QSReader<Record<string, string | number | boolean | Array<string | number | boolean>>> = (
|
|
19
|
+
value,
|
|
20
|
+
key,
|
|
21
|
+
qsObject,
|
|
22
|
+
) => {
|
|
23
|
+
if (value === '1') {
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (value === 'true') {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (value.includes(',')) {
|
|
32
|
+
if (Object.hasOwn(qsObject, key)) {
|
|
33
|
+
if (!isArray(qsObject[key])) qsObject[key] = [qsObject[key]];
|
|
34
|
+
} else {
|
|
35
|
+
qsObject[key] = [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
qsObject[key].push(...value.split(','));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return value;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
expect(qsParse('?a=1', qsReader)).toEqual({ a: 1 });
|
|
46
|
+
expect(qsParse('?a=1&b=2', qsReader)).toEqual({ a: 1, b: '2' });
|
|
47
|
+
expect(qsParse('?a=1&b=2&a=3', qsReader)).toEqual({ a: [1, '3'], b: '2' });
|
|
48
|
+
expect(qsParse('?a=1&b=2&a=3&b=4,5', qsReader)).toEqual({ a: [1, '3'], b: ['2', '4', '5'] });
|
|
49
|
+
expect(qsParse('?a=1&b=2&a=3&c=4,5', qsReader)).toEqual({ a: [1, '3'], b: '2', c: ['4', '5'] });
|
|
50
|
+
expect(qsParse('?x=true', qsReader)).toEqual({ x: true });
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('qsStringify', () => {
|
|
55
|
+
it('qsStringify 默认 writer', () => {
|
|
56
|
+
const i = new Date(2020, 0, 1, 0, 0, 0, 0);
|
|
57
|
+
const query = { a: 1, b: [2, 3], c: '4', d: undefined, e: null, f: true, g: false, i };
|
|
58
|
+
const string = 'a=1&b=2&b=3&c=4&f=true&g=false&i=2019-12-31T16%3A00%3A00.000Z';
|
|
59
|
+
expect(qsStringify(query)).toBe(string);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('qsStringify 复写 writer', () => {
|
|
63
|
+
const qsWriter: QSWriter = (value, key, qsObject) => {
|
|
64
|
+
if (isString(value)) return `string-${value}`;
|
|
65
|
+
if (isNumber(value)) return `number-${value}`;
|
|
66
|
+
if (isBoolean(value)) return `boolean-${value ? 'true' : 'false'}`;
|
|
67
|
+
if (isUndefined(value)) return 'undefined';
|
|
68
|
+
if (isNull(value)) return 'null';
|
|
69
|
+
if (isDate(value)) return `date-${dateFormat(value, 'YYYY-MM-DD HH:mm:ss')}`;
|
|
70
|
+
return null;
|
|
71
|
+
};
|
|
72
|
+
const i = new Date(2020, 0, 1, 0, 0, 0, 0);
|
|
73
|
+
const query = { a: 1, b: [2, 3], c: '4', d: undefined, e: null, f: true, g: false, i };
|
|
74
|
+
const string =
|
|
75
|
+
'a=number-1&b=number-2&b=number-3&c=string-4&d=undefined&e=null&' +
|
|
76
|
+
'f=boolean-true&g=boolean-false&i=date-2020-01-01+00%3A00%3A00';
|
|
77
|
+
expect(qsStringify(query, qsWriter)).toBe(string);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isDigit,
|
|
3
|
+
isEmail,
|
|
4
|
+
isFloat,
|
|
5
|
+
isIDNo,
|
|
6
|
+
isIPV4,
|
|
7
|
+
isInteger,
|
|
8
|
+
isNumerical,
|
|
9
|
+
isPhone,
|
|
10
|
+
isURL,
|
|
11
|
+
regexpEscape,
|
|
12
|
+
} from '@/regexp';
|
|
13
|
+
import { expect, test } from 'vitest';
|
|
14
|
+
|
|
15
|
+
test('reEscape', () => {
|
|
16
|
+
const str = 'a*';
|
|
17
|
+
const re = new RegExp(regexpEscape(str), 'i');
|
|
18
|
+
|
|
19
|
+
expect(re.source).toEqual('a\\*');
|
|
20
|
+
expect(re.test('a*')).toEqual(true);
|
|
21
|
+
expect(re.test('A*')).toEqual(true);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('isURL', () => {
|
|
25
|
+
expect(isURL('')).toBe(false);
|
|
26
|
+
expect(isURL('http://aba.com')).toBe(true);
|
|
27
|
+
expect(isURL("http://aba.com:8080/'1@!.+%a?a(x)")).toBe(true);
|
|
28
|
+
expect(isURL("http://192.168.0.1/'1@!.+%a?a(x)")).toBe(true);
|
|
29
|
+
expect(isURL("http://192.168.0.1:8080/'1@!.+%a?a(x)")).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('isEmail', () => {
|
|
33
|
+
expect(isEmail('')).toBe(false);
|
|
34
|
+
expect(isEmail('http://aba.com')).toBe(false);
|
|
35
|
+
expect(isEmail('http@aba.com')).toBe(true);
|
|
36
|
+
expect(isEmail('ht.tp@aba.com')).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('isPhone', () => {
|
|
40
|
+
expect(isPhone('')).toBe(false);
|
|
41
|
+
expect(isPhone('19912345678')).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('isIPV4', () => {
|
|
45
|
+
expect(isIPV4('')).toBe(false);
|
|
46
|
+
expect(isIPV4('11.11.111.11')).toBe(true);
|
|
47
|
+
expect(isIPV4('222.233.455.11')).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('isIdNo', () => {
|
|
51
|
+
// http://id.8684.cn/
|
|
52
|
+
expect(isIDNo('350213197706189461')).toBe(true);
|
|
53
|
+
expect(isIDNo('350213197706189462')).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('isInteger', () => {
|
|
57
|
+
expect(isInteger('1')).toBe(true);
|
|
58
|
+
expect(isInteger('-1')).toBe(true);
|
|
59
|
+
expect(isInteger('12300')).toBe(true);
|
|
60
|
+
expect(isInteger('-12300')).toBe(true);
|
|
61
|
+
expect(isInteger('0')).toBe(true);
|
|
62
|
+
expect(isInteger('0.0')).toBe(false);
|
|
63
|
+
expect(isInteger('0.1')).toBe(false);
|
|
64
|
+
expect(isInteger('-0')).toBe(false);
|
|
65
|
+
expect(isInteger('-0123')).toBe(false);
|
|
66
|
+
expect(isInteger('0123')).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('isFloat', () => {
|
|
70
|
+
expect(isFloat('0')).toBe(false);
|
|
71
|
+
expect(isFloat('0.1')).toBe(true);
|
|
72
|
+
expect(isFloat('1.1')).toBe(true);
|
|
73
|
+
expect(isFloat('1.01')).toBe(true);
|
|
74
|
+
expect(isFloat('10.01')).toBe(true);
|
|
75
|
+
expect(isFloat('-10.01')).toBe(true);
|
|
76
|
+
expect(isFloat('010.01')).toBe(false);
|
|
77
|
+
expect(isFloat('10.010')).toBe(true);
|
|
78
|
+
expect(isFloat('10.0')).toBe(true);
|
|
79
|
+
expect(isFloat('10.')).toBe(false);
|
|
80
|
+
expect(isFloat('10')).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('.isNumrical', () => {
|
|
84
|
+
expect(isNumerical('0')).toBe(true);
|
|
85
|
+
expect(isNumerical('0.0')).toBe(true);
|
|
86
|
+
expect(isNumerical('0.1')).toBe(true);
|
|
87
|
+
expect(isNumerical('1.1')).toBe(true);
|
|
88
|
+
expect(isNumerical('-1.1')).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('isDigit', () => {
|
|
92
|
+
expect(isDigit('1')).toBe(true);
|
|
93
|
+
expect(isDigit('12')).toBe(true);
|
|
94
|
+
expect(isDigit('012')).toBe(true);
|
|
95
|
+
expect(isDigit('+012')).toBe(false);
|
|
96
|
+
expect(isDigit('-012')).toBe(false);
|
|
97
|
+
});
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { randomString, randomUUID4, stringCamelCase, stringFormat, stringKebabCase, stringify } from '../src/string';
|
|
3
|
+
|
|
4
|
+
describe('stringCamelCase', () => {
|
|
5
|
+
it('应将字符串转换为驼峰命名', () => {
|
|
6
|
+
expect(stringCamelCase('hello world')).toBe('helloWorld');
|
|
7
|
+
expect(stringCamelCase('hello_world')).toBe('helloWorld');
|
|
8
|
+
expect(stringCamelCase('hello-world')).toBe('helloWorld');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('如果 bigger 为 true,应将首字母大写', () => {
|
|
12
|
+
expect(stringCamelCase('hello world', true)).toBe('HelloWorld');
|
|
13
|
+
expect(stringCamelCase('hello_world', true)).toBe('HelloWorld');
|
|
14
|
+
expect(stringCamelCase('hello-world', true)).toBe('HelloWorld');
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('stringKebabCase', () => {
|
|
19
|
+
it('应将字符串转换为默认分隔符的短横线命名', () => {
|
|
20
|
+
expect(stringKebabCase('helloWorld')).toBe('hello-world');
|
|
21
|
+
expect(stringKebabCase('HelloWorld')).toBe('-hello-world');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('应将字符串转换为自定义分隔符的短横线命名', () => {
|
|
25
|
+
expect(stringKebabCase('helloWorld', '_')).toBe('hello_world');
|
|
26
|
+
expect(stringKebabCase('HelloWorld', '_')).toBe('_hello_world');
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('randomString', () => {
|
|
31
|
+
it('应生成指定长度的随机字符串', () => {
|
|
32
|
+
const result = randomString(10);
|
|
33
|
+
expect(result.length).toBe(10);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('应使用自定义字符字典生成随机字符串', () => {
|
|
37
|
+
const dict = 'ABCDEF';
|
|
38
|
+
const result = randomString(8, dict);
|
|
39
|
+
expect(result).toHaveLength(8);
|
|
40
|
+
for (const char of result) {
|
|
41
|
+
expect(dict).toContain(char);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('stringFormat', () => {
|
|
47
|
+
it('应支持索引方式格式化字符串', () => {
|
|
48
|
+
const result = stringFormat('你好 {0}!我的名字是 {1}。', '张三', '李四');
|
|
49
|
+
expect(result).toBe('你好 张三!我的名字是 李四。');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('应支持对象方式格式化字符串', () => {
|
|
53
|
+
const result = stringFormat('{greet}!我的名字是 {name}。', { greet: '你好', name: '王五' });
|
|
54
|
+
expect(result).toBe('你好!我的名字是 王五。');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('应支持带回退值的对象方式格式化字符串', () => {
|
|
58
|
+
const result = stringFormat('{greet}!我的名字是 {name}。', { greet: '你好' }, '未知');
|
|
59
|
+
expect(result).toBe('你好!我的名字是 未知。');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('应支持带回退函数的对象方式格式化字符串', () => {
|
|
63
|
+
const result = stringFormat('{greet}!我的名字是 {name}。', { greet: '你好' }, (key) => `默认${key}`);
|
|
64
|
+
expect(result).toBe('你好!我的名字是 默认name。');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('应处理未找到的键值对', () => {
|
|
68
|
+
const result = stringFormat('{greet}!我的名字是 {name}。', { greet: '你好' });
|
|
69
|
+
expect(result).toBe('你好!我的名字是 name。');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('应处理空字符串', () => {
|
|
73
|
+
const result = stringFormat('', { greet: '你好' });
|
|
74
|
+
expect(result).toBe('');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('应处理未传递参数的情况', () => {
|
|
78
|
+
const result = stringFormat('{greet}!我的名字是 {name}。');
|
|
79
|
+
expect(result).toBe('greet!我的名字是 name。');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('边界,值为 falsy 值', () => {
|
|
83
|
+
const result = stringFormat('{days}天{hours}时{minutes}分{seconds}秒', {
|
|
84
|
+
days: 0,
|
|
85
|
+
hours: 1,
|
|
86
|
+
minutes: 2,
|
|
87
|
+
seconds: 3,
|
|
88
|
+
});
|
|
89
|
+
expect(result).toBe('0天1时2分3秒');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('randomUUID4', () => {
|
|
94
|
+
it('验证长度', () => {
|
|
95
|
+
for (let i = 0; i < 10; i++) {
|
|
96
|
+
const uuid = randomUUID4();
|
|
97
|
+
|
|
98
|
+
// 验证长度
|
|
99
|
+
expect(uuid.length).toBe(36);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('验证分隔符位置', () => {
|
|
104
|
+
for (let i = 0; i < 10; i++) {
|
|
105
|
+
const uuid = randomUUID4();
|
|
106
|
+
|
|
107
|
+
// 验证分隔符位置
|
|
108
|
+
expect(uuid[8]).toBe('-');
|
|
109
|
+
expect(uuid[13]).toBe('-');
|
|
110
|
+
expect(uuid[18]).toBe('-');
|
|
111
|
+
expect(uuid[23]).toBe('-');
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('验证版本号为 4', () => {
|
|
116
|
+
for (let i = 0; i < 10; i++) {
|
|
117
|
+
const uuid = randomUUID4();
|
|
118
|
+
|
|
119
|
+
// 验证版本号为 4
|
|
120
|
+
expect(uuid[14]).toBe('4');
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('验证变体', () => {
|
|
125
|
+
for (let i = 0; i < 10; i++) {
|
|
126
|
+
const uuid = randomUUID4();
|
|
127
|
+
|
|
128
|
+
// 验证变体符合 RFC 4122 (第19位为 '8', '9', 'a', 或 'b')
|
|
129
|
+
const variantChar = uuid[19];
|
|
130
|
+
expect(['8', '9', 'a', 'b']).toContain(variantChar);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('stringify', () => {
|
|
136
|
+
it('应将 null 转换为空字符串', () => {
|
|
137
|
+
expect(stringify(null)).toBe('');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('应将 undefined 转换为空字符串', () => {
|
|
141
|
+
expect(stringify(undefined)).toBe('');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('应将其他值转换为字符串', () => {
|
|
145
|
+
expect(stringify(123)).toBe('123');
|
|
146
|
+
expect(stringify(true)).toBe('true');
|
|
147
|
+
expect(stringify({})).toBe('[object Object]');
|
|
148
|
+
expect(stringify('hello')).toBe('hello');
|
|
149
|
+
});
|
|
150
|
+
});
|