@cloudcome/utils-core 1.1.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 (292) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/LICENSE +21 -0
  3. package/package.json +279 -1
  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/{dist/color/contrast.d.ts → src/color/contrast.ts} +12 -2
  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/{dist/color/hex-hsv.d.ts → src/color/hex-hsv.ts} +11 -3
  13. package/{dist/color/hex-hwb.d.ts → src/color/hex-hwb.ts} +11 -3
  14. package/src/color/hex-rgb.ts +39 -0
  15. package/{dist/color/hsl-lighten.d.ts → src/color/hsl-lighten.ts} +7 -2
  16. package/{dist/color/hsv-brighten.d.ts → src/color/hsv-brighten.ts} +7 -2
  17. package/{dist/color/luminance.d.ts → src/color/luminance.ts} +9 -2
  18. package/{dist/color/mix.d.ts → src/color/mix.ts} +10 -2
  19. package/src/color/rgb-hsl.ts +53 -0
  20. package/{dist/color/rgb-hsv.d.ts → src/color/rgb-hsv.ts} +30 -3
  21. package/src/color/rgb-hwb.ts +56 -0
  22. package/{dist/color/rgb-lab.d.ts → src/color/rgb-lab.ts} +11 -3
  23. package/src/color/rgb-whiter.ts +22 -0
  24. package/src/color/rgb-xyz.ts +62 -0
  25. package/{dist/color/types.d.ts → src/color/types.ts} +12 -30
  26. package/{dist/color/xyz-lab.d.ts → src/color/xyz-lab.ts} +32 -3
  27. package/src/crypto/md5.mjs +357 -0
  28. package/src/crypto/sha1.mjs +300 -0
  29. package/src/crypto/sha256.mjs +310 -0
  30. package/src/crypto/sha512.mjs +459 -0
  31. package/{dist/crypto.d.ts → src/crypto.ts} +20 -4
  32. package/src/date/const.ts +6 -0
  33. package/src/date/core.ts +162 -0
  34. package/src/date/days.ts +51 -0
  35. package/{dist/date/is.d.ts → src/date/is.ts} +102 -8
  36. package/src/date/relative.ts +92 -0
  37. package/src/date/start-end.ts +246 -0
  38. package/src/date/timezone.ts +220 -0
  39. package/src/date/weeks.ts +100 -0
  40. package/src/dts/global.d.ts +27 -0
  41. package/src/easing.ts +166 -0
  42. package/src/emitter.ts +117 -0
  43. package/src/enum.ts +171 -0
  44. package/{dist/env.d.ts → src/env.ts} +30 -6
  45. package/{dist/error.d.ts → src/error.ts} +12 -3
  46. package/src/exception.ts +68 -0
  47. package/src/fn.ts +197 -0
  48. package/src/index.ts +1 -0
  49. package/src/number.ts +236 -0
  50. package/{dist/object/each.d.ts → src/object/each.ts} +23 -3
  51. package/src/object/get-set.ts +273 -0
  52. package/src/object/is.ts +128 -0
  53. package/src/object/merge.ts +180 -0
  54. package/{dist/object/process.d.ts → src/object/process.ts} +38 -4
  55. package/src/path.ts +188 -0
  56. package/{dist/promise.d.ts → src/promise.ts} +67 -6
  57. package/{dist/qs.d.ts → src/qs.ts} +60 -3
  58. package/src/regexp.ts +156 -0
  59. package/src/string.ts +146 -0
  60. package/src/time/from.ts +57 -0
  61. package/src/time/to.ts +106 -0
  62. package/{dist/timer.mjs → src/timer.ts} +124 -17
  63. package/{dist/tree.d.ts → src/tree.ts} +225 -41
  64. package/{dist/type.d.ts → src/type.ts} +96 -20
  65. package/{dist/types.d.ts → src/types.ts} +33 -12
  66. package/src/unique.ts +77 -0
  67. package/src/url.ts +93 -0
  68. package/src/version.ts +71 -0
  69. package/test/array.test.ts +332 -0
  70. package/test/async-real.test.ts +39 -0
  71. package/test/async.test.ts +375 -0
  72. package/test/base64.test.ts +32 -0
  73. package/test/cache.test.ts +83 -0
  74. package/test/color.test.ts +163 -0
  75. package/test/crypto.test.ts +34 -0
  76. package/test/date-tz.test.ts +206 -0
  77. package/test/date.test.ts +353 -0
  78. package/test/easing.test.ts +33 -0
  79. package/test/emitter.test.ts +71 -0
  80. package/test/enum.test.ts +113 -0
  81. package/test/env.test.ts +69 -0
  82. package/test/error.test.ts +58 -0
  83. package/test/exception.test.ts +43 -0
  84. package/test/fn.test.ts +263 -0
  85. package/test/helpers.ts +23 -0
  86. package/test/index.test.ts +6 -0
  87. package/test/number.test.ts +213 -0
  88. package/test/object.test.ts +309 -0
  89. package/test/path.test.ts +156 -0
  90. package/test/promise.test.ts +199 -0
  91. package/test/qs.test.ts +79 -0
  92. package/test/regexp.test.ts +97 -0
  93. package/test/string.test.ts +150 -0
  94. package/test/time.test.ts +214 -0
  95. package/test/timer.test.ts +114 -0
  96. package/test/tree.test.ts +348 -0
  97. package/test/type.test.ts +226 -0
  98. package/test/unique.test.ts +71 -0
  99. package/test/url.test.ts +136 -0
  100. package/test/version.test.ts +52 -0
  101. package/tsconfig.json +31 -0
  102. package/vite.config.mts +114 -0
  103. package/dist/array.cjs +0 -129
  104. package/dist/array.cjs.map +0 -1
  105. package/dist/array.d.ts +0 -171
  106. package/dist/array.mjs +0 -129
  107. package/dist/array.mjs.map +0 -1
  108. package/dist/async.cjs +0 -219
  109. package/dist/async.cjs.map +0 -1
  110. package/dist/async.d.ts +0 -137
  111. package/dist/async.mjs +0 -219
  112. package/dist/async.mjs.map +0 -1
  113. package/dist/base64.cjs +0 -16
  114. package/dist/base64.cjs.map +0 -1
  115. package/dist/base64.d.ts +0 -7
  116. package/dist/base64.mjs +0 -16
  117. package/dist/base64.mjs.map +0 -1
  118. package/dist/cache.cjs +0 -79
  119. package/dist/cache.cjs.map +0 -1
  120. package/dist/cache.d.ts +0 -90
  121. package/dist/cache.mjs +0 -79
  122. package/dist/cache.mjs.map +0 -1
  123. package/dist/color/distance.d.ts +0 -8
  124. package/dist/color/helpers.d.ts +0 -2
  125. package/dist/color/hex-hsl.d.ts +0 -3
  126. package/dist/color/hex-rgb.d.ts +0 -18
  127. package/dist/color/rgb-hsl.d.ts +0 -23
  128. package/dist/color/rgb-hwb.d.ts +0 -29
  129. package/dist/color/rgb-whiter.d.ts +0 -12
  130. package/dist/color/rgb-xyz.d.ts +0 -22
  131. package/dist/color.cjs +0 -250
  132. package/dist/color.cjs.map +0 -1
  133. package/dist/color.mjs +0 -250
  134. package/dist/color.mjs.map +0 -1
  135. package/dist/const.cjs +0 -14
  136. package/dist/const.cjs.map +0 -1
  137. package/dist/const.mjs +0 -15
  138. package/dist/const.mjs.map +0 -1
  139. package/dist/core.cjs +0 -250
  140. package/dist/core.cjs.map +0 -1
  141. package/dist/core.mjs +0 -251
  142. package/dist/core.mjs.map +0 -1
  143. package/dist/crypto/md5.d.mts +0 -1
  144. package/dist/crypto/sha1.d.mts +0 -1
  145. package/dist/crypto/sha256.d.mts +0 -1
  146. package/dist/crypto/sha512.d.mts +0 -1
  147. package/dist/crypto.cjs +0 -812
  148. package/dist/crypto.cjs.map +0 -1
  149. package/dist/crypto.mjs +0 -812
  150. package/dist/crypto.mjs.map +0 -1
  151. package/dist/date/const.d.ts +0 -6
  152. package/dist/date/core.d.ts +0 -52
  153. package/dist/date/days.d.ts +0 -23
  154. package/dist/date/relative.d.ts +0 -44
  155. package/dist/date/start-end.d.ts +0 -73
  156. package/dist/date/timezone.d.ts +0 -67
  157. package/dist/date/weeks.d.ts +0 -72
  158. package/dist/date.cjs +0 -239
  159. package/dist/date.cjs.map +0 -1
  160. package/dist/date.mjs +0 -241
  161. package/dist/date.mjs.map +0 -1
  162. package/dist/dict.cjs +0 -2
  163. package/dist/dict.cjs.map +0 -1
  164. package/dist/dict.mjs +0 -2
  165. package/dist/dict.mjs.map +0 -1
  166. package/dist/each.cjs +0 -18
  167. package/dist/each.cjs.map +0 -1
  168. package/dist/each.mjs +0 -19
  169. package/dist/each.mjs.map +0 -1
  170. package/dist/easing.cjs +0 -151
  171. package/dist/easing.cjs.map +0 -1
  172. package/dist/easing.d.ts +0 -46
  173. package/dist/easing.mjs +0 -151
  174. package/dist/easing.mjs.map +0 -1
  175. package/dist/emitter.cjs +0 -94
  176. package/dist/emitter.cjs.map +0 -1
  177. package/dist/emitter.d.ts +0 -68
  178. package/dist/emitter.mjs +0 -94
  179. package/dist/emitter.mjs.map +0 -1
  180. package/dist/enum.cjs +0 -58
  181. package/dist/enum.cjs.map +0 -1
  182. package/dist/enum.d.ts +0 -68
  183. package/dist/enum.mjs +0 -58
  184. package/dist/enum.mjs.map +0 -1
  185. package/dist/env.cjs +0 -28
  186. package/dist/env.cjs.map +0 -1
  187. package/dist/env.mjs +0 -28
  188. package/dist/env.mjs.map +0 -1
  189. package/dist/error.cjs +0 -12
  190. package/dist/error.cjs.map +0 -1
  191. package/dist/error.mjs +0 -12
  192. package/dist/error.mjs.map +0 -1
  193. package/dist/exception.cjs +0 -22
  194. package/dist/exception.cjs.map +0 -1
  195. package/dist/exception.d.ts +0 -31
  196. package/dist/exception.mjs +0 -22
  197. package/dist/exception.mjs.map +0 -1
  198. package/dist/fn.cjs +0 -76
  199. package/dist/fn.cjs.map +0 -1
  200. package/dist/fn.d.ts +0 -102
  201. package/dist/fn.mjs +0 -76
  202. package/dist/fn.mjs.map +0 -1
  203. package/dist/index.cjs +0 -5
  204. package/dist/index.cjs.map +0 -1
  205. package/dist/index.d.ts +0 -1
  206. package/dist/index.mjs +0 -5
  207. package/dist/index.mjs.map +0 -1
  208. package/dist/merge.cjs +0 -87
  209. package/dist/merge.cjs.map +0 -1
  210. package/dist/merge.mjs +0 -88
  211. package/dist/merge.mjs.map +0 -1
  212. package/dist/number.cjs +0 -11
  213. package/dist/number.cjs.map +0 -1
  214. package/dist/number.d.ts +0 -137
  215. package/dist/number.mjs +0 -11
  216. package/dist/number.mjs.map +0 -1
  217. package/dist/object/get-set.d.ts +0 -111
  218. package/dist/object/is.d.ts +0 -32
  219. package/dist/object/merge.d.ts +0 -72
  220. package/dist/object.cjs +0 -130
  221. package/dist/object.cjs.map +0 -1
  222. package/dist/object.mjs +0 -130
  223. package/dist/object.mjs.map +0 -1
  224. package/dist/path.cjs +0 -77
  225. package/dist/path.cjs.map +0 -1
  226. package/dist/path.d.ts +0 -82
  227. package/dist/path.mjs +0 -77
  228. package/dist/path.mjs.map +0 -1
  229. package/dist/promise.cjs +0 -62
  230. package/dist/promise.cjs.map +0 -1
  231. package/dist/promise.mjs +0 -62
  232. package/dist/promise.mjs.map +0 -1
  233. package/dist/qs.cjs +0 -47
  234. package/dist/qs.cjs.map +0 -1
  235. package/dist/qs.mjs +0 -47
  236. package/dist/qs.mjs.map +0 -1
  237. package/dist/regexp.cjs +0 -66
  238. package/dist/regexp.cjs.map +0 -1
  239. package/dist/regexp.d.ts +0 -65
  240. package/dist/regexp.mjs +0 -66
  241. package/dist/regexp.mjs.map +0 -1
  242. package/dist/string.cjs +0 -16
  243. package/dist/string.cjs.map +0 -1
  244. package/dist/string.d.ts +0 -80
  245. package/dist/string.mjs +0 -16
  246. package/dist/string.mjs.map +0 -1
  247. package/dist/string2.cjs +0 -147
  248. package/dist/string2.cjs.map +0 -1
  249. package/dist/string2.mjs +0 -148
  250. package/dist/string2.mjs.map +0 -1
  251. package/dist/time/from.d.ts +0 -14
  252. package/dist/time/to.d.ts +0 -38
  253. package/dist/time.cjs +0 -82
  254. package/dist/time.cjs.map +0 -1
  255. package/dist/time.mjs +0 -82
  256. package/dist/time.mjs.map +0 -1
  257. package/dist/timer.cjs +0 -119
  258. package/dist/timer.cjs.map +0 -1
  259. package/dist/timer.d.ts +0 -96
  260. package/dist/timer.mjs.map +0 -1
  261. package/dist/tree.cjs +0 -125
  262. package/dist/tree.cjs.map +0 -1
  263. package/dist/tree.mjs +0 -125
  264. package/dist/tree.mjs.map +0 -1
  265. package/dist/type.cjs +0 -78
  266. package/dist/type.cjs.map +0 -1
  267. package/dist/type.mjs +0 -78
  268. package/dist/type.mjs.map +0 -1
  269. package/dist/types.cjs +0 -2
  270. package/dist/types.cjs.map +0 -1
  271. package/dist/types.mjs +0 -2
  272. package/dist/types.mjs.map +0 -1
  273. package/dist/unique.cjs +0 -46
  274. package/dist/unique.cjs.map +0 -1
  275. package/dist/unique.d.ts +0 -22
  276. package/dist/unique.mjs +0 -46
  277. package/dist/unique.mjs.map +0 -1
  278. package/dist/url.cjs +0 -37
  279. package/dist/url.cjs.map +0 -1
  280. package/dist/url.d.ts +0 -53
  281. package/dist/url.mjs +0 -37
  282. package/dist/url.mjs.map +0 -1
  283. package/dist/version.cjs +0 -33
  284. package/dist/version.cjs.map +0 -1
  285. package/dist/version.d.ts +0 -32
  286. package/dist/version.mjs +0 -33
  287. package/dist/version.mjs.map +0 -1
  288. /package/{dist/color.d.ts → src/color.ts} +0 -0
  289. /package/{dist/date.d.ts → src/date.ts} +0 -0
  290. /package/{dist/dict.d.ts → src/dict.ts} +0 -0
  291. /package/{dist/object.d.ts → src/object.ts} +0 -0
  292. /package/{dist/time.d.ts → src/time.ts} +0 -0
@@ -0,0 +1,213 @@
1
+ import { fileSizeAbbr } from '@/number';
2
+ import { numberAbbr, numberConvert, numberFixed, numberFormat, randomNumber } from '@/number';
3
+ import { describe, expect, it } from 'vitest';
4
+
5
+ describe('randomNumber', () => {
6
+ it('应在指定范围内生成随机整数', () => {
7
+ for (let i = 0; i < 100; i++) {
8
+ const result = randomNumber(1, 10);
9
+ expect(result).toBeGreaterThanOrEqual(1);
10
+ expect(result).toBeLessThanOrEqual(10);
11
+ }
12
+ });
13
+
14
+ it('应处理 min 大于 max 的情况', () => {
15
+ for (let i = 0; i < 100; i++) {
16
+ const result = randomNumber(10, 1);
17
+ expect(result).toBeGreaterThanOrEqual(1);
18
+ expect(result).toBeLessThanOrEqual(10);
19
+ }
20
+ });
21
+
22
+ it('应处理边界情况', () => {
23
+ expect(randomNumber(0, 0)).toBe(0);
24
+ expect(randomNumber(5, 5)).toBe(5);
25
+ });
26
+
27
+ it('应处理负数范围', () => {
28
+ for (let i = 0; i < 100; i++) {
29
+ const result = randomNumber(-10, -1);
30
+ expect(result).toBeGreaterThanOrEqual(-10);
31
+ expect(result).toBeLessThanOrEqual(-1);
32
+ }
33
+ });
34
+
35
+ it('应处理负数和正数混合范围', () => {
36
+ for (let i = 0; i < 100; i++) {
37
+ const result = randomNumber(-5, 5);
38
+ expect(result).toBeGreaterThanOrEqual(-5);
39
+ expect(result).toBeLessThanOrEqual(5);
40
+ }
41
+ });
42
+ });
43
+
44
+ describe('numberAbbr', () => {
45
+ it('应正确转换数字为带单位的缩写', () => {
46
+ expect(numberAbbr(1500, ['', 'K', 'M'], { base: 1000 })).toBe('1K');
47
+ expect(numberAbbr(123456, ['B', 'KB', 'MB'], { precision: 1 })).toBe('123.4KB');
48
+ expect(numberAbbr(500, ['B', 'KB'])).toBe('500B');
49
+ });
50
+
51
+ it('应处理单位数组为空的情况', () => {
52
+ expect(() => numberAbbr(1000, [])).toThrow('数字单位组不能为空');
53
+ });
54
+
55
+ it('应处理自定义进制基数', () => {
56
+ expect(numberAbbr(1024, ['B', 'KB', 'MB'], { base: 1024 })).toBe('1KB');
57
+ expect(numberAbbr(1048576, ['B', 'KB', 'MB'], { base: 1024 })).toBe('1MB');
58
+ expect(numberAbbr(1048576, ['', '万', '亿'], { base: 10000 })).toBe('104万');
59
+ expect(numberAbbr(10485769, ['', '万', '亿'], { base: 10000 })).toBe('1048万');
60
+ expect(numberAbbr(10485769012, ['', '万', '亿'], { base: 10000 })).toBe('104亿');
61
+ });
62
+
63
+ it('应处理小数位数', () => {
64
+ expect(numberAbbr(1234, ['', 'K', 'M'], { precision: 2 })).toBe('1.23K');
65
+ expect(numberAbbr(1234567, ['', 'K', 'M'], { precision: 3 })).toBe('1.234M');
66
+ });
67
+
68
+ it('应处理不足基数的情况', () => {
69
+ expect(numberAbbr(999, ['', 'K', 'M'])).toBe('999');
70
+ expect(numberAbbr(999999, ['', 'K', 'M'])).toBe('999K');
71
+ });
72
+ });
73
+
74
+ describe('numberFixed', () => {
75
+ it('应正确执行四舍五入', () => {
76
+ // biome-ignore lint/suspicious/noApproximativeNumericConstant: <explanation>
77
+ expect(numberFixed(3.1415, { precision: 2 })).toBe(3.14);
78
+ expect(numberFixed(3.145, { precision: 2 })).toBe(3.15);
79
+ expect(numberFixed(3.5)).toBe(4);
80
+ });
81
+
82
+ it('应正确执行向上取整', () => {
83
+ expect(numberFixed(3.1, { round: 1 })).toBe(4);
84
+ expect(numberFixed(-3.1, { round: 1 })).toBe(-3);
85
+ });
86
+
87
+ it('应正确执行向下取整', () => {
88
+ expect(numberFixed(3.9, { round: -1 })).toBe(3);
89
+ expect(numberFixed(-3.9, { round: -1 })).toBe(-4);
90
+ });
91
+
92
+ it('应处理负数和小数位', () => {
93
+ // biome-ignore lint/suspicious/noApproximativeNumericConstant: <explanation>
94
+ expect(numberFixed(-3.1415, { precision: 3 })).toBe(-3.141);
95
+ expect(numberFixed(-3.149, { precision: 2, round: -1 })).toBe(-3.15);
96
+ });
97
+
98
+ it('应支持默认参数', () => {
99
+ expect(numberFixed(2.5)).toBe(3);
100
+ expect(numberFixed(2.5, {})).toBe(3);
101
+ });
102
+
103
+ it('应处理精度为0的情况', () => {
104
+ expect(numberFixed(99.9, { precision: 0 })).toBe(100);
105
+ expect(numberFixed(99.4, { precision: 0, round: -1 })).toBe(99);
106
+ });
107
+ });
108
+
109
+ describe('fileSizeAbbr', () => {
110
+ it('应正确转换基础文件大小', () => {
111
+ expect(fileSizeAbbr(1024)).toBe('1KB');
112
+ expect(fileSizeAbbr(1048576)).toBe('1MB');
113
+ expect(fileSizeAbbr(500)).toBe('500B');
114
+ expect(fileSizeAbbr(1073741824)).toBe('1GB');
115
+ });
116
+
117
+ it('应处理自定义小数位', () => {
118
+ expect(fileSizeAbbr(123456, 1)).toBe('120.5KB');
119
+ expect(fileSizeAbbr(1050000, 2)).toBe('1MB');
120
+ });
121
+
122
+ it('应处理不足基数的情况', () => {
123
+ expect(fileSizeAbbr(999)).toBe('999B');
124
+ expect(fileSizeAbbr(1023)).toBe('1023B');
125
+ });
126
+
127
+ it('应处理大单位转换', () => {
128
+ expect(fileSizeAbbr(1099511627776)).toBe('1TB');
129
+ expect(fileSizeAbbr(2199023255552)).toBe('2TB');
130
+ });
131
+
132
+ it('应处理零值', () => {
133
+ expect(fileSizeAbbr(0)).toBe('0B');
134
+ });
135
+
136
+ it('应处理边界情况', () => {
137
+ expect(fileSizeAbbr(1024 * 1024 - 1)).toBe('1023KB');
138
+ expect(fileSizeAbbr(1024 ** 3)).toBe('1GB');
139
+ });
140
+ });
141
+
142
+ describe('numberConvert', () => {
143
+ it('应正确将十进制数转换为默认 62 进制字符串', () => {
144
+ expect(numberConvert(123456789)).toBe('8M0kX');
145
+ });
146
+
147
+ it('应正确将十进制数转换为自定义 16 进制字符串', () => {
148
+ expect(numberConvert(255, '0123456789ABCDEF')).toBe('FF');
149
+ });
150
+
151
+ it('应正确处理大整数', () => {
152
+ expect(numberConvert(9007199254740991n)).toBe('fFgnDxSe7');
153
+ });
154
+
155
+ it('应处理字符字典长度小于 2 的情况', () => {
156
+ expect(() => numberConvert(123, 'A')).toThrow('进制转换字典长度不能小于 2');
157
+ });
158
+
159
+ it('应处理字符字典为空的情况', () => {
160
+ expect(() => numberConvert(123, 'a')).toThrow('进制转换字典长度不能小于 2');
161
+ });
162
+
163
+ it('应处理负数', () => {
164
+ expect(numberConvert(-123)).toBe('-1z');
165
+ });
166
+
167
+ it('应处理零', () => {
168
+ expect(numberConvert(0)).toBe('0');
169
+ });
170
+ });
171
+
172
+ describe('numberFormat', () => {
173
+ it('应支持默认分隔符和步长', () => {
174
+ expect(numberFormat(123456.789)).toBe('123,456.789');
175
+ expect(numberFormat(1000)).toBe('1,000');
176
+ expect(numberFormat(0)).toBe('0');
177
+ expect(numberFormat(-123456)).toBe('-123,456');
178
+ });
179
+
180
+ it('应支持自定义分隔符', () => {
181
+ expect(numberFormat(123456, '_')).toBe('123_456');
182
+ expect(numberFormat(123456.789, '_')).toBe('123_456.789');
183
+ });
184
+
185
+ it('应支持自定义步长', () => {
186
+ expect(numberFormat(123456, 2)).toBe('12,34,56');
187
+ expect(numberFormat(123456.789, 2)).toBe('12,34,56.789');
188
+ expect(numberFormat(100000, 3)).toBe('100,000');
189
+ });
190
+
191
+ it('应支持对象配置', () => {
192
+ expect(numberFormat(123456, { separator: '.', step: 4 })).toBe('12.3456');
193
+ expect(numberFormat(123456.789, { separator: ' ', step: 3 })).toBe('123 456.789');
194
+ });
195
+
196
+ it('应处理小数部分', () => {
197
+ expect(numberFormat(1234.5678)).toBe('1,234.5678');
198
+ expect(numberFormat(0.1234)).toBe('0.1234');
199
+ expect(numberFormat(123456.789, { step: 3 })).toBe('123,456.789');
200
+ });
201
+
202
+ it('应处理特殊数值', () => {
203
+ expect(numberFormat(999)).toBe('999');
204
+ expect(numberFormat(1000)).toBe('1,000');
205
+ expect(numberFormat(1000000)).toBe('1,000,000');
206
+ expect(numberFormat(-123456.789)).toBe('-123,456.789');
207
+ });
208
+
209
+ it('应处理非整数步长参数', () => {
210
+ expect(numberFormat(123456, 4)).toBe('12,3456');
211
+ expect(numberFormat(123456, { step: 4 })).toBe('12,3456');
212
+ });
213
+ });
@@ -0,0 +1,309 @@
1
+ import {
2
+ isEmptyObject,
3
+ isPlainObject,
4
+ objectDefaults,
5
+ objectEach,
6
+ objectEachAsync,
7
+ objectGet,
8
+ objectMap,
9
+ objectMerge,
10
+ objectOmit,
11
+ objectPick,
12
+ objectSet,
13
+ } from '@/object';
14
+ import type { DeepPartial } from '@/types';
15
+ import { describe, expect, it } from 'vitest';
16
+
17
+ describe('objectEach', () => {
18
+ it('应正确遍历对象的每个键值对', () => {
19
+ const obj = { a: 1, b: 2, c: 3 };
20
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
21
+ const results: any[] = [];
22
+ objectEach(obj, (val, key) => {
23
+ results.push([key, val]);
24
+ });
25
+ expect(results).toEqual([
26
+ ['a', 1],
27
+ ['b', 2],
28
+ ['c', 3],
29
+ ]);
30
+ });
31
+
32
+ it('应支持提前终止遍历', () => {
33
+ const obj = { a: 1, b: 2, c: 3 };
34
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
35
+ const results: any[] = [];
36
+ objectEach(obj, (val, key) => {
37
+ results.push([key, val]);
38
+ if (key === 'b') {
39
+ return false;
40
+ }
41
+ });
42
+ expect(results).toEqual([
43
+ ['a', 1],
44
+ ['b', 2],
45
+ ]);
46
+ });
47
+ });
48
+
49
+ describe('objectEachAsync', () => {
50
+ it('应正确异步遍历对象的每个键值对', async () => {
51
+ const obj = { a: 1, b: 2, c: 3 };
52
+ const results: [string, number][] = [];
53
+ await objectEachAsync(obj, async (val, key) => {
54
+ results.push([key, val]);
55
+ });
56
+ expect(results).toEqual([
57
+ ['a', 1],
58
+ ['b', 2],
59
+ ['c', 3],
60
+ ]);
61
+ });
62
+
63
+ it('应支持提前终止异步遍历', async () => {
64
+ const obj = { a: 1, b: 2, c: 3 };
65
+ const results: [string, number][] = [];
66
+ await objectEachAsync(obj, async (val, key) => {
67
+ results.push([key, val]);
68
+ if (key === 'b') {
69
+ return false;
70
+ }
71
+ });
72
+ expect(results).toEqual([
73
+ ['a', 1],
74
+ ['b', 2],
75
+ ]);
76
+ });
77
+ });
78
+
79
+ describe('objectMerge', () => {
80
+ it('应正确合并两个对象', () => {
81
+ const obj1 = { a: 1, b: { x: 10 } };
82
+ const obj2 = { b: { y: 20 }, c: 3 };
83
+ const merged = objectMerge(obj1, obj2);
84
+ expect(merged).toEqual({ a: 1, b: { x: 10, y: 20 }, c: 3 });
85
+ });
86
+
87
+ it('应正确处理循环引用', () => {
88
+ const obj1 = { a: 1 };
89
+ const obj2 = { c: obj1 };
90
+ // { a: 1, b: <{b: obj1}> }
91
+ // @ts-expect-error
92
+ obj1.b = obj2;
93
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
94
+ const merged = objectMerge({}, obj1) as any;
95
+ expect(merged.a).toBe(1);
96
+ expect(merged.b.c).toBe(merged);
97
+ });
98
+
99
+ it('应正确合并多个对象', () => {
100
+ const obj1 = { a: 1 };
101
+ const obj2 = { b: 2 };
102
+ const obj3 = { c: 3 };
103
+ const merged = objectMerge(obj1, obj2, obj3);
104
+ expect(merged).toEqual({ a: 1, b: 2, c: 3 });
105
+ });
106
+
107
+ it('应正确合并数组', () => {
108
+ const obj1 = { a: [1, 2, 3] };
109
+ const obj2 = { a: [3, 4] };
110
+ const merged = objectMerge(obj1, obj2);
111
+ expect(merged).toEqual({ a: [3, 4, 3] });
112
+ });
113
+
114
+ it('不同类型的值应被覆盖,返回数组', () => {
115
+ const obj1 = { a: 1 };
116
+ const obj2 = [1];
117
+ const merged = objectMerge(obj1, obj2);
118
+ expect(merged).toEqual([1]);
119
+ });
120
+
121
+ it('不同类型的值应被覆盖,返回对象', () => {
122
+ const obj1 = { a: 1 };
123
+ const obj2 = [1];
124
+ const merged = objectMerge(obj2, obj1);
125
+ expect(merged).toEqual({ a: 1 });
126
+ });
127
+ });
128
+
129
+ // 新增 objectDefaults 单测
130
+ describe('objectDefaults', () => {
131
+ it('应正确设置默认值', () => {
132
+ type Obj = {
133
+ a: number;
134
+ b: number;
135
+ c: number;
136
+ };
137
+ const obj: Partial<Obj> = { a: 1, b: undefined };
138
+ const defaults = { a: 4, b: 2, c: 3 };
139
+ const result = objectDefaults(obj, defaults);
140
+ expect(result).toEqual({ a: 1, b: 2, c: 3 });
141
+ });
142
+
143
+ it('应支持多个默认对象', () => {
144
+ type Obj = {
145
+ a: number;
146
+ b: number;
147
+ c: number;
148
+ };
149
+ const obj: Partial<Obj> = { a: 1, b: undefined };
150
+ const defaults1 = { a: 4, b: 2, c: 3 };
151
+ const result = objectDefaults(obj, defaults1);
152
+ expect(result).toEqual({ a: 1, b: 2, c: 3 });
153
+ });
154
+
155
+ it('不应覆盖已定义的值', () => {
156
+ const obj = { a: 1, b: 2 };
157
+ const defaults = { a: 5, b: 3, c: 4 };
158
+ const result = objectDefaults(obj, defaults);
159
+ expect(result).toEqual({ a: 1, b: 2, c: 4 });
160
+ });
161
+
162
+ it('应处理嵌套对象', () => {
163
+ type Obj = {
164
+ a: { x: number; y: number; z: number };
165
+ b: { y: number };
166
+ };
167
+ const obj: DeepPartial<Obj> = { a: { x: 1 }, b: undefined };
168
+ const defaults = { a: { x: 4, z: 3 }, b: { y: 2 } };
169
+ const result = objectDefaults(obj, defaults);
170
+ expect(result).toEqual({ a: { x: 1, z: 3 }, b: { y: 2 } });
171
+ });
172
+ });
173
+
174
+ describe('objectPick', () => {
175
+ it('应正确选择指定键的对象', () => {
176
+ const obj = { a: 1, b: 2, c: 3 };
177
+ const result = objectPick(obj, ['a', 'c']);
178
+ expect(result).toEqual({ a: 1, c: 3 });
179
+ });
180
+
181
+ it('应忽略不存在的键', () => {
182
+ const obj = { a: 1, b: 2, c: 3, d: 4 };
183
+ const result = objectPick(obj, ['a', 'd']);
184
+ expect(result).toEqual({ a: 1, d: 4 });
185
+ });
186
+
187
+ it('应返回空对象如果键数组为空', () => {
188
+ const obj = { a: 1, b: 2, c: 3 };
189
+ const result = objectPick(obj, []);
190
+ expect(result).toEqual({});
191
+ });
192
+ });
193
+
194
+ describe('objectOmit', () => {
195
+ it('应正确排除指定键的对象', () => {
196
+ const obj = { a: 1, b: 2, c: 3 };
197
+ const result = objectOmit(obj, ['a', 'c']);
198
+ expect(result).toEqual({ b: 2 });
199
+ });
200
+
201
+ it('应忽略不存在的键', () => {
202
+ const obj = { a: 1, b: 2, c: 3, d: 4 };
203
+ const result = objectOmit(obj, ['a', 'd']);
204
+ expect(result).toEqual({ b: 2, c: 3 });
205
+ });
206
+
207
+ it('应返回原对象如果键数组为空', () => {
208
+ const obj = { a: 1, b: 2, c: 3 };
209
+ const result = objectOmit(obj, []);
210
+ expect(result).toEqual({ a: 1, b: 2, c: 3 });
211
+ });
212
+ });
213
+
214
+ describe('objectMap', () => {
215
+ it('应正确映射对象的每个键值对', () => {
216
+ const obj = { a: 1, b: 2, c: 3 };
217
+ const result = objectMap(obj, (val, key) => val * 2);
218
+ expect(result).toEqual({ a: 2, b: 4, c: 6 });
219
+ });
220
+
221
+ it('应支持将值映射为不同类型的值', () => {
222
+ const obj = { a: 1, b: 2, c: 3 };
223
+ const result = objectMap(obj, (val, key) => String(val * 2));
224
+ expect(result).toEqual({ a: '2', b: '4', c: '6' });
225
+ });
226
+
227
+ it('应返回空对象如果输入对象为空', () => {
228
+ const obj = {};
229
+ const result = objectMap(obj, (val, key) => val);
230
+ expect(result).toEqual({});
231
+ });
232
+ });
233
+
234
+ describe('objectGet', () => {
235
+ it('应正确获取嵌套属性值(字符串路径)', () => {
236
+ const obj = { a: { b: { c: 42 } } };
237
+ const result = objectGet(obj, 'a.b.c');
238
+ expect(result.value).toBe(42);
239
+ });
240
+
241
+ it('应正确获取嵌套属性值(数组路径)', () => {
242
+ const obj = { a: { b: { c: 42 } } };
243
+ const result = objectGet(obj, ['a', 'b', 'c']);
244
+ expect(result.value).toBe(42);
245
+ });
246
+
247
+ it('应返回 undefined 如果路径不存在', () => {
248
+ const obj = { a: { b: { c: 42 } } };
249
+ const result = objectGet(obj, 'a.b.x');
250
+ expect(result.value).toBeUndefined();
251
+ });
252
+ });
253
+
254
+ describe('objectSet', () => {
255
+ it('应正确设置嵌套属性值', () => {
256
+ const obj = { a: { b: { c: 42 } } };
257
+ objectSet(obj, 'a.b.c', 100);
258
+ expect(obj.a.b.c).toBe(100);
259
+ });
260
+
261
+ it('应正确创建未定义的中间节点', () => {
262
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
263
+ const obj: Record<string, any> = {};
264
+ objectSet(obj, 'a.b.c', 42);
265
+ expect(obj.a.b.c).toBe(42);
266
+ });
267
+
268
+ it('应在 beforeSet 返回 false 时阻止设置值', () => {
269
+ const obj = { a: { b: { c: 42 } } };
270
+ objectSet(obj, 'a.b.c', 100, {
271
+ beforeSet: () => false,
272
+ });
273
+ expect(obj.a.b.c).toBe(42);
274
+ });
275
+
276
+ it('应在 undefinedSet 返回自定义值时使用该值', () => {
277
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
278
+ const obj: Record<string, any> = {};
279
+ objectSet(obj, 'a.b.c', 42, {
280
+ undefinedSet: () => ({ custom: 'value' }),
281
+ });
282
+ expect(obj.a.b).toEqual({ custom: 'value', c: 42 });
283
+ });
284
+ });
285
+
286
+ describe('isEmptyObject', () => {
287
+ it('应该正确识别空对象', () => {
288
+ expect(isEmptyObject({})).toBe(true);
289
+ expect(isEmptyObject(Object.create(null))).toBe(true);
290
+ });
291
+
292
+ it('应该识别非空对象', () => {
293
+ expect(isEmptyObject({ a: 1 })).toBe(false);
294
+ expect(isEmptyObject({ [Symbol('key')]: 'value' })).toBe(false);
295
+ });
296
+ });
297
+
298
+ describe('isPlainObject', () => {
299
+ it('应识别普通对象', () => {
300
+ expect(isPlainObject({})).toBe(true);
301
+ expect(isPlainObject(Object.create(null))).toBe(true);
302
+ });
303
+
304
+ it('应排除非纯对象', () => {
305
+ const o = {};
306
+ Object.setPrototypeOf(o, { aa: 1 });
307
+ expect(isPlainObject(o)).toBe(false);
308
+ });
309
+ });
@@ -0,0 +1,156 @@
1
+ import path from 'node:path/posix';
2
+ import { isAbsolutePath, isRelativePath, pathJoin, pathNormalize, pathRelativize, pathResolve } from '@/path';
3
+ import { describe, expect, it } from 'vitest';
4
+
5
+ function testNormalize(value: string) {
6
+ expect(pathNormalize(value)).toBe(path.normalize(value));
7
+ }
8
+
9
+ function testJoin(from: string, ...to: string[]) {
10
+ expect(pathJoin.apply(pathJoin, [from, ...to])).toBe(path.join.apply(path, [from, ...to]));
11
+ }
12
+
13
+ describe('pathNormalize', () => {
14
+ it('应正确标准化路径', () => {
15
+ testNormalize('/path///to///file');
16
+ testNormalize('path/to/file');
17
+ testNormalize('/path/to/./file');
18
+ testNormalize('/path/to/../file');
19
+ testNormalize('/path/to/../../file');
20
+ testNormalize('/path/to/../../../file');
21
+ testNormalize('/path/to/../../../../file');
22
+ testNormalize('/path/to/../../file/');
23
+ testNormalize('/path/to/./file/');
24
+ testNormalize('/path/to/./');
25
+ testNormalize('/path/to/../');
26
+ testNormalize('/path/to/../../');
27
+ testNormalize('/path/to/../../../');
28
+ testNormalize('/path/to/../../../../');
29
+ testNormalize('/path/to/./././file');
30
+ testNormalize('/path/to/.././file');
31
+ testNormalize('/path/to/../.././file');
32
+ testNormalize('/path/to/../../.././file');
33
+ testNormalize('/path/to/../../../.././file');
34
+ testNormalize('/path/to/./../file');
35
+ testNormalize('/path/to/./../../file');
36
+ testNormalize('/path/to/./../../../file');
37
+ testNormalize('/path/to/./../../../../file');
38
+ testNormalize('/path/to/././file');
39
+ testNormalize('/path/to/../././file');
40
+ testNormalize('/path/to/../../././file');
41
+ testNormalize('/path/to/../../../././file');
42
+ testNormalize('/path/to/../../../../././file');
43
+ });
44
+
45
+ it('应正确处理绝对路径和相对路径', () => {
46
+ testNormalize('/path/to/file');
47
+ testNormalize('path/to/file');
48
+ testNormalize('/./path/to/file');
49
+ testNormalize('./path/to/file');
50
+ testNormalize('../path/to/file');
51
+ testNormalize('/path/to/./file');
52
+ testNormalize('path/to/./file');
53
+ testNormalize('/path/to/../file');
54
+ testNormalize('path/to/../file');
55
+ testNormalize('/path/to/../../file');
56
+ testNormalize('path/to/../../file');
57
+ testNormalize('/path/to/../../../file');
58
+ testNormalize('path/to/../../../file');
59
+ testNormalize('/path/to/../../../../file');
60
+ testNormalize('path/to/../../../../file');
61
+ });
62
+ });
63
+
64
+ describe('pathJoin', () => {
65
+ it('应正确合并路径', () => {
66
+ testJoin('/path', 'to', 'file');
67
+ testJoin('path', 'to', 'file');
68
+ testJoin('/path', '/to', 'file');
69
+ testJoin('path', '/to', 'file');
70
+ testJoin('/path', 'to', '/file');
71
+ testJoin('path', 'to', '/file');
72
+ testJoin('/path', '/to', '/file');
73
+ testJoin('path', '/to', '/file');
74
+ testJoin('/path', 'to', 'file/');
75
+ testJoin('path', 'to', 'file/');
76
+ testJoin('/path', 'to/', 'file');
77
+ testJoin('path', 'to/', 'file');
78
+ testJoin('/path', 'to/', 'file/');
79
+ testJoin('path', 'to/', 'file/');
80
+ testJoin('/path', '/to', 'file/');
81
+ testJoin('path', '/to', 'file/');
82
+ testJoin('/path', '/to/', 'file');
83
+ testJoin('path', '/to/', 'file');
84
+ testJoin('/path', '/to/', 'file/');
85
+ testJoin('path', '/to/', 'file/');
86
+ testJoin('/path', 'to', '/file/');
87
+ testJoin('path', 'to', '/file/');
88
+ testJoin('/path', '/to', '/file/');
89
+ testJoin('path', '/to', '/file/');
90
+ });
91
+ });
92
+
93
+ describe('isAbsolutePath', () => {
94
+ it('应正确判断绝对路径', () => {
95
+ expect(isAbsolutePath('/path/to/file')).toBe(true);
96
+ expect(isAbsolutePath('/')).toBe(true);
97
+ expect(isAbsolutePath('/path/../to/file')).toBe(true);
98
+ expect(isAbsolutePath('/path/./to/file')).toBe(true);
99
+ });
100
+
101
+ it('应正确判断非绝对路径', () => {
102
+ expect(isAbsolutePath('path/to/file')).toBe(false);
103
+ expect(isAbsolutePath('./path/to/file')).toBe(false);
104
+ expect(isAbsolutePath('../path/to/file')).toBe(false);
105
+ expect(isAbsolutePath('')).toBe(false);
106
+ });
107
+ });
108
+
109
+ describe('isRelativePath', () => {
110
+ it('应正确判断相对路径', () => {
111
+ expect(isRelativePath('path/to/file')).toBe(true);
112
+ expect(isRelativePath('./path/to/file')).toBe(true);
113
+ expect(isRelativePath('../path/to/file')).toBe(true);
114
+ expect(isRelativePath('')).toBe(true);
115
+ });
116
+
117
+ it('应正确判断非相对路径', () => {
118
+ expect(isRelativePath('/path/to/file')).toBe(false);
119
+ expect(isRelativePath('/')).toBe(false);
120
+ expect(isRelativePath('/path/../to/file')).toBe(false);
121
+ expect(isRelativePath('/path/./to/file')).toBe(false);
122
+ });
123
+ });
124
+
125
+ describe('pathResolve', () => {
126
+ it('应正确解析路径', () => {
127
+ expect(pathResolve('/path', 'to', 'file')).toBe('/path/to/file');
128
+ expect(pathResolve('/path', '/to', 'file')).toBe('/to/file');
129
+ expect(pathResolve('/path', 'to', '/file')).toBe('/file');
130
+ expect(pathResolve('path', 'to', 'file')).toBe('path/to/file');
131
+ expect(pathResolve('path', '/to', 'file')).toBe('/to/file');
132
+ expect(pathResolve('path', 'to', '/file')).toBe('/file');
133
+ expect(pathResolve('/path', 'to/../file')).toBe('/path/file');
134
+ expect(pathResolve('/path', 'to/./file')).toBe('/path/to/file');
135
+ expect(pathResolve('/path', 'to/../../file')).toBe('/file');
136
+ });
137
+ });
138
+
139
+ describe('pathRelativize', () => {
140
+ it('应正确相对化路径', () => {
141
+ // 绝对路径应保持不变
142
+ expect(pathRelativize('/path/to/file')).toBe('/path/to/file');
143
+ expect(pathRelativize('/')).toBe('/');
144
+
145
+ // 已带'./'前缀的相对路径应保持不变
146
+ expect(pathRelativize('./path/to/file')).toBe('./path/to/file');
147
+ expect(pathRelativize('./')).toBe('./');
148
+
149
+ // 不带'./'前缀的相对路径应添加'./'前缀
150
+ expect(pathRelativize('path/to/file')).toBe('./path/to/file');
151
+ expect(pathRelativize('file')).toBe('./file');
152
+
153
+ // 带'../'前缀的相对路径应保持不变
154
+ expect(pathRelativize('../path/to/file')).toBe('../path/to/file');
155
+ });
156
+ });