@zhin.js/core 1.0.38 → 1.0.40
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 +20 -0
- package/lib/ai/index.d.ts +10 -5
- package/lib/ai/index.d.ts.map +1 -1
- package/lib/ai/index.js +7 -4
- package/lib/ai/index.js.map +1 -1
- package/lib/ai/providers/anthropic.d.ts.map +1 -1
- package/lib/ai/providers/anthropic.js +2 -0
- package/lib/ai/providers/anthropic.js.map +1 -1
- package/lib/ai/providers/openai.d.ts.map +1 -1
- package/lib/ai/providers/openai.js +8 -0
- package/lib/ai/providers/openai.js.map +1 -1
- package/lib/ai/types.d.ts +1 -0
- package/lib/ai/types.d.ts.map +1 -1
- package/lib/cron.d.ts +2 -43
- package/lib/cron.d.ts.map +1 -1
- package/lib/cron.js +2 -126
- package/lib/cron.js.map +1 -1
- package/lib/errors.d.ts +3 -146
- package/lib/errors.d.ts.map +1 -1
- package/lib/errors.js +3 -279
- package/lib/errors.js.map +1 -1
- package/lib/feature.d.ts +5 -87
- package/lib/feature.d.ts.map +1 -1
- package/lib/feature.js +4 -105
- package/lib/feature.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/scheduler/index.d.ts +3 -7
- package/lib/scheduler/index.d.ts.map +1 -1
- package/lib/scheduler/index.js +2 -9
- package/lib/scheduler/index.js.map +1 -1
- package/lib/types.d.ts +8 -1
- package/lib/types.d.ts.map +1 -1
- package/lib/utils.d.ts +7 -52
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +9 -325
- package/lib/utils.js.map +1 -1
- package/package.json +6 -4
- package/src/ai/index.ts +15 -9
- package/src/ai/providers/anthropic.ts +1 -0
- package/src/ai/providers/openai.ts +5 -1
- package/src/ai/types.ts +1 -0
- package/src/cron.ts +2 -140
- package/src/errors.ts +15 -334
- package/src/feature.ts +5 -154
- package/src/index.ts +3 -1
- package/src/scheduler/index.ts +8 -17
- package/src/types.ts +10 -2
- package/src/utils.ts +37 -334
- package/tests/cron.test.ts +4 -299
- package/tests/errors.test.ts +17 -307
- package/tests/utils.test.ts +11 -516
- package/tests/feature.test.ts +0 -145
package/tests/utils.test.ts
CHANGED
|
@@ -1,167 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
segment,
|
|
7
|
-
remove,
|
|
8
|
-
isEmpty,
|
|
9
|
-
Time,
|
|
10
|
-
clearEvalCache,
|
|
11
|
-
getEvalCacheStats,
|
|
12
|
-
execute,
|
|
13
|
-
getValueWithRuntime,
|
|
14
|
-
sleep
|
|
15
|
-
} from '../src/utils'
|
|
16
|
-
|
|
17
|
-
describe('Template Security', () => {
|
|
18
|
-
|
|
19
|
-
it('should prevent access to process.env', () => {
|
|
20
|
-
const template = 'Node env: ${process.env.NODE_ENV}'
|
|
21
|
-
const result = compiler(template, {})
|
|
22
|
-
expect(result).toBe('Node env: undefined')
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('should prevent access to global object', () => {
|
|
26
|
-
const template = 'Global: ${global}'
|
|
27
|
-
const result = compiler(template, {})
|
|
28
|
-
expect(result).toBe('Global: undefined')
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('should prevent access to require function', () => {
|
|
32
|
-
const template = 'Require: ${require}'
|
|
33
|
-
const result = compiler(template, {})
|
|
34
|
-
expect(result).toBe('Require: undefined')
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it('should allow access to provided context variables', () => {
|
|
38
|
-
const template = 'Hello ${name}!'
|
|
39
|
-
const result = compiler(template, { name: 'World' })
|
|
40
|
-
expect(result).toBe('Hello World!')
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('should allow complex expressions with safe context', () => {
|
|
44
|
-
const template = 'Result: ${Math.max(1, 2, 3)}'
|
|
45
|
-
const result = compiler(template, {})
|
|
46
|
-
expect(result).toBe('Result: 3')
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('should handle nested object access safely', () => {
|
|
50
|
-
const template = 'User: ${user.name} (${user.age})'
|
|
51
|
-
const result = compiler(template, { user: { name: 'Alice', age: 25 } })
|
|
52
|
-
expect(result).toBe('User: Alice (25)')
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('should allow safe Math expressions', () => {
|
|
56
|
-
const result = evaluate('Math.PI', {})
|
|
57
|
-
expect(result).toBeCloseTo(3.14159)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('should allow access to safe process properties', () => {
|
|
61
|
-
const result = evaluate('process.version', {})
|
|
62
|
-
expect(result).toBe(process.version)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('should block Buffer access', () => {
|
|
66
|
-
const result = evaluate('Buffer', {})
|
|
67
|
-
expect(result).toBeUndefined()
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('should block crypto access', () => {
|
|
71
|
-
const result = evaluate('crypto', {})
|
|
72
|
-
expect(result).toBeUndefined()
|
|
73
|
-
})
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
describe('Template Functionality', () => {
|
|
77
|
-
it('should handle multiple template variables', () => {
|
|
78
|
-
const template = 'Hello ${name}, you are ${age} years old!'
|
|
79
|
-
const result = compiler(template, { name: 'Bob', age: 30 })
|
|
80
|
-
expect(result).toBe('Hello Bob, you are 30 years old!')
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
it('should handle JSON objects in templates', () => {
|
|
84
|
-
const template = 'Config: ${config}'
|
|
85
|
-
const config = { debug: true, port: 3000 }
|
|
86
|
-
const result = compiler(template, { config })
|
|
87
|
-
expect(result).toBe(`Config: ${JSON.stringify(config, null, 2)}`)
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
it('should handle template expressions that fail gracefully', () => {
|
|
91
|
-
const template = 'Result: ${undefined.property}'
|
|
92
|
-
const result = compiler(template, {})
|
|
93
|
-
// Should return template with undefined when evaluation fails
|
|
94
|
-
expect(result).toBe('Result: undefined')
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
it('should handle templates without variables', () => {
|
|
98
|
-
const template = 'Hello World!'
|
|
99
|
-
const result = compiler(template, {})
|
|
100
|
-
expect(result).toBe('Hello World!')
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
it('should handle empty template', () => {
|
|
104
|
-
const template = ''
|
|
105
|
-
const result = compiler(template, {})
|
|
106
|
-
expect(result).toBe('')
|
|
107
|
-
})
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
describe('evaluate and execute', () => {
|
|
111
|
-
beforeEach(() => {
|
|
112
|
-
clearEvalCache()
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
it('should evaluate simple expressions', () => {
|
|
116
|
-
expect(evaluate('1 + 1', {})).toBe(2)
|
|
117
|
-
expect(evaluate('2 * 3', {})).toBe(6)
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('should return undefined for blocked access', () => {
|
|
121
|
-
expect(evaluate('global.something', {})).toBeUndefined()
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
it('should use cache for repeated expressions', () => {
|
|
125
|
-
const expr = '1 + 1'
|
|
126
|
-
const result1 = execute(expr, {})
|
|
127
|
-
const result2 = execute(expr, {})
|
|
128
|
-
expect(result1).toBe(result2)
|
|
129
|
-
expect(getEvalCacheStats().size).toBe(1)
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
it('should limit cache size', () => {
|
|
133
|
-
clearEvalCache()
|
|
134
|
-
// 添加超过 MAX_EVAL_CACHE_SIZE 的表达式
|
|
135
|
-
for (let i = 0; i < 150; i++) {
|
|
136
|
-
execute(`1 + ${i}`, {})
|
|
137
|
-
}
|
|
138
|
-
const stats = getEvalCacheStats()
|
|
139
|
-
expect(stats.size).toBeLessThanOrEqual(stats.maxSize)
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
it('should handle invalid expressions gracefully', () => {
|
|
143
|
-
expect(() => execute('invalid syntax here !!!', {})).toThrow()
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
it('should provide safe process context', () => {
|
|
147
|
-
const result = execute('return process.platform', {})
|
|
148
|
-
expect(result).toBe(process.platform)
|
|
149
|
-
})
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
describe('getValueWithRuntime', () => {
|
|
153
|
-
it('should return value from context', () => {
|
|
154
|
-
expect(getValueWithRuntime('name', { name: 'Alice' })).toBe('Alice')
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
it('should return undefined for blocked access', () => {
|
|
158
|
-
expect(getValueWithRuntime('global', {})).toBeUndefined()
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
it('should handle complex expressions', () => {
|
|
162
|
-
expect(getValueWithRuntime('user.name', { user: { name: 'Bob' } })).toBe('Bob')
|
|
163
|
-
})
|
|
164
|
-
})
|
|
1
|
+
/**
|
|
2
|
+
* Core 特有的工具函数测试(通用工具测试已迁移到 @zhin.js/kernel)
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect } from 'vitest'
|
|
5
|
+
import { compose, segment } from '../src/utils'
|
|
165
6
|
|
|
166
7
|
describe('compose middleware', () => {
|
|
167
8
|
it('should return empty function for empty middlewares', async () => {
|
|
@@ -219,22 +60,10 @@ describe('compose middleware', () => {
|
|
|
219
60
|
})
|
|
220
61
|
|
|
221
62
|
it('should catch and rethrow middleware errors', async () => {
|
|
222
|
-
const middleware = async (
|
|
223
|
-
throw new Error('Middleware error')
|
|
224
|
-
}
|
|
63
|
+
const middleware = async () => { throw new Error('Middleware error') }
|
|
225
64
|
const composed = compose([middleware])
|
|
226
65
|
await expect(composed({} as any, async () => {})).rejects.toThrow('Middleware error')
|
|
227
66
|
})
|
|
228
|
-
|
|
229
|
-
it('should call final next function', async () => {
|
|
230
|
-
let finalCalled = false
|
|
231
|
-
const middleware = async (msg: any, next: any) => {
|
|
232
|
-
await next()
|
|
233
|
-
}
|
|
234
|
-
const composed = compose([middleware])
|
|
235
|
-
await composed({} as any, async () => { finalCalled = true })
|
|
236
|
-
expect(finalCalled).toBe(true)
|
|
237
|
-
})
|
|
238
67
|
})
|
|
239
68
|
|
|
240
69
|
describe('segment utilities', () => {
|
|
@@ -246,11 +75,6 @@ describe('segment utilities', () => {
|
|
|
246
75
|
it('should unescape HTML entities', () => {
|
|
247
76
|
expect(segment.unescape('<div>&"'</div>')).toBe('<div>&"\'</div>')
|
|
248
77
|
})
|
|
249
|
-
|
|
250
|
-
it('should handle non-string values', () => {
|
|
251
|
-
expect(segment.escape(123 as any)).toBe(123)
|
|
252
|
-
expect(segment.unescape(null as any)).toBe(null)
|
|
253
|
-
})
|
|
254
78
|
})
|
|
255
79
|
|
|
256
80
|
describe('text and face', () => {
|
|
@@ -259,9 +83,9 @@ describe('segment utilities', () => {
|
|
|
259
83
|
})
|
|
260
84
|
|
|
261
85
|
it('should create face segment', () => {
|
|
262
|
-
expect(segment.face('smile', '😊')).toEqual({
|
|
263
|
-
type: 'face',
|
|
264
|
-
data: { id: 'smile', text: '😊' }
|
|
86
|
+
expect(segment.face('smile', '😊')).toEqual({
|
|
87
|
+
type: 'face',
|
|
88
|
+
data: { id: 'smile', text: '😊' }
|
|
265
89
|
})
|
|
266
90
|
})
|
|
267
91
|
})
|
|
@@ -272,83 +96,15 @@ describe('segment utilities', () => {
|
|
|
272
96
|
expect(result).toEqual([{ type: 'text', data: { text: 'Hello World' } }])
|
|
273
97
|
})
|
|
274
98
|
|
|
275
|
-
it('should parse self-closing tags
|
|
99
|
+
it('should parse self-closing tags', () => {
|
|
276
100
|
const result = segment.from('<image url="test.jpg" />')
|
|
277
|
-
expect(result.
|
|
278
|
-
// 检查是否包含 image 类型
|
|
279
|
-
const imageElement = result.find(el => el.type === 'image')
|
|
280
|
-
expect(imageElement).toBeDefined()
|
|
281
|
-
})
|
|
282
|
-
|
|
283
|
-
it('should parse paired tags', () => {
|
|
284
|
-
const result = segment.from('<quote>Hello</quote>')
|
|
285
|
-
// 检查结果中是否有 quote 元素
|
|
286
|
-
const quoteElement = result.find(el => el.type === 'quote')
|
|
287
|
-
expect(quoteElement).toBeDefined()
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
it('should parse mixed content', () => {
|
|
291
|
-
const result = segment.from('Text <image url="pic.jpg" /> More text')
|
|
292
|
-
expect(result.length).toBeGreaterThan(0)
|
|
293
|
-
// 应该包含文本和图片元素
|
|
294
|
-
expect(result.some(el => el.type === 'text')).toBe(true)
|
|
295
|
-
})
|
|
296
|
-
|
|
297
|
-
it('should handle attributes with single quotes', () => {
|
|
298
|
-
const result = segment.from("<image url='test.jpg' />")
|
|
299
|
-
const imageElement = result.find(el => el.type === 'image')
|
|
300
|
-
expect(imageElement).toBeDefined()
|
|
301
|
-
})
|
|
302
|
-
|
|
303
|
-
it('should handle multiple attributes', () => {
|
|
304
|
-
const result = segment.from('<image url="test.jpg" width="100" height="200" />')
|
|
305
|
-
const imageElement = result.find(el => el.type === 'image')
|
|
306
|
-
// 如果找到了 image 元素,检查它有数据
|
|
307
|
-
if (imageElement) {
|
|
308
|
-
expect(imageElement.data).toBeDefined()
|
|
309
|
-
// 至少应该有一些属性
|
|
310
|
-
expect(Object.keys(imageElement.data).length).toBeGreaterThanOrEqual(0)
|
|
311
|
-
} else {
|
|
312
|
-
// 如果没找到,至少应该有元素被解析
|
|
313
|
-
expect(result.length).toBeGreaterThan(0)
|
|
314
|
-
}
|
|
315
|
-
})
|
|
316
|
-
|
|
317
|
-
it('should handle nested tags', () => {
|
|
318
|
-
const result = segment.from('<quote><text>Hello</text></quote>')
|
|
319
|
-
// 应该有元素被解析
|
|
320
|
-
expect(result.length).toBeGreaterThan(0)
|
|
321
|
-
})
|
|
322
|
-
|
|
323
|
-
it('should handle array input', () => {
|
|
324
|
-
const result = segment.from(['Hello', ' ', 'World'])
|
|
325
|
-
expect(result.length).toBeGreaterThan(0)
|
|
326
|
-
})
|
|
327
|
-
|
|
328
|
-
it('should handle MessageElement input', () => {
|
|
329
|
-
const input = { type: 'text', data: { text: 'Hello' } }
|
|
330
|
-
const result = segment.from(input)
|
|
331
|
-
expect(result).toEqual([input])
|
|
332
|
-
})
|
|
333
|
-
|
|
334
|
-
it('should parse JSON values in attributes', () => {
|
|
335
|
-
const result = segment.from('<data value="123" />')
|
|
336
|
-
const dataElement = result.find(el => el.type === 'data')
|
|
337
|
-
// 如果找到了 data 元素,检查值是否被解析
|
|
338
|
-
if (dataElement && dataElement.data.value !== undefined) {
|
|
339
|
-
expect(typeof dataElement.data.value).toBe('number')
|
|
340
|
-
}
|
|
101
|
+
expect(result.find(el => el.type === 'image')).toBeDefined()
|
|
341
102
|
})
|
|
342
103
|
|
|
343
104
|
it('should handle malformed templates gracefully', () => {
|
|
344
105
|
const result = segment.from('Hello <unclosed')
|
|
345
106
|
expect(result[0].type).toBe('text')
|
|
346
107
|
})
|
|
347
|
-
|
|
348
|
-
it('should handle templates with escaped characters', () => {
|
|
349
|
-
const result = segment.from('<div>')
|
|
350
|
-
expect(result[0].data.text).toBe('<div>')
|
|
351
|
-
})
|
|
352
108
|
})
|
|
353
109
|
|
|
354
110
|
describe('raw', () => {
|
|
@@ -360,11 +116,6 @@ describe('segment utilities', () => {
|
|
|
360
116
|
expect(segment.raw(content)).toBe('Hello{face}(😊)')
|
|
361
117
|
})
|
|
362
118
|
|
|
363
|
-
it('should handle segments without text', () => {
|
|
364
|
-
const content = [{ type: 'image', data: { url: 'test.jpg' } }]
|
|
365
|
-
expect(segment.raw(content)).toBe('{image}')
|
|
366
|
-
})
|
|
367
|
-
|
|
368
119
|
it('should handle string input', () => {
|
|
369
120
|
expect(segment.raw('Hello')).toBe('Hello')
|
|
370
121
|
})
|
|
@@ -379,262 +130,6 @@ describe('segment utilities', () => {
|
|
|
379
130
|
const result = segment.toString(content)
|
|
380
131
|
expect(result).toContain('Hello')
|
|
381
132
|
expect(result).toContain('<image')
|
|
382
|
-
expect(result).toContain('url=')
|
|
383
133
|
})
|
|
384
|
-
|
|
385
|
-
it('should handle function types', () => {
|
|
386
|
-
const content = [{ type: function MyType() {} as any, data: { value: 1 } }]
|
|
387
|
-
const result = segment.toString(content)
|
|
388
|
-
expect(result).toContain('MyType')
|
|
389
|
-
})
|
|
390
|
-
|
|
391
|
-
it('should escape attribute values', () => {
|
|
392
|
-
const content = [{ type: 'tag', data: { attr: '<script>' } }]
|
|
393
|
-
const result = segment.toString(content)
|
|
394
|
-
expect(result).toContain('<')
|
|
395
|
-
})
|
|
396
|
-
})
|
|
397
|
-
})
|
|
398
|
-
|
|
399
|
-
describe('remove utility', () => {
|
|
400
|
-
it('should remove item by value', () => {
|
|
401
|
-
const list = [1, 2, 3, 4]
|
|
402
|
-
remove(list, 3)
|
|
403
|
-
expect(list).toEqual([1, 2, 4])
|
|
404
|
-
})
|
|
405
|
-
|
|
406
|
-
it('should remove item by predicate', () => {
|
|
407
|
-
const list = [1, 2, 3, 4]
|
|
408
|
-
remove(list, (x) => x > 2)
|
|
409
|
-
expect(list).toEqual([1, 2, 4])
|
|
410
|
-
})
|
|
411
|
-
|
|
412
|
-
it('should do nothing if item not found', () => {
|
|
413
|
-
const list = [1, 2, 3]
|
|
414
|
-
remove(list, 5)
|
|
415
|
-
expect(list).toEqual([1, 2, 3])
|
|
416
|
-
})
|
|
417
|
-
|
|
418
|
-
it('should handle function items', () => {
|
|
419
|
-
const fn1 = () => 1
|
|
420
|
-
const fn2 = () => 2
|
|
421
|
-
const list = [fn1, fn2]
|
|
422
|
-
remove(list, fn1)
|
|
423
|
-
expect(list).toEqual([fn2])
|
|
424
|
-
})
|
|
425
|
-
})
|
|
426
|
-
|
|
427
|
-
describe('isEmpty utility', () => {
|
|
428
|
-
it('should return true for empty array', () => {
|
|
429
|
-
expect(isEmpty([])).toBe(true)
|
|
430
|
-
})
|
|
431
|
-
|
|
432
|
-
it('should return false for non-empty array', () => {
|
|
433
|
-
expect(isEmpty([1, 2])).toBe(false)
|
|
434
|
-
})
|
|
435
|
-
|
|
436
|
-
it('should return true for empty object', () => {
|
|
437
|
-
expect(isEmpty({})).toBe(true)
|
|
438
|
-
})
|
|
439
|
-
|
|
440
|
-
it('should return false for non-empty object', () => {
|
|
441
|
-
expect(isEmpty({ a: 1 })).toBe(false)
|
|
442
|
-
})
|
|
443
|
-
|
|
444
|
-
it('should return true for null', () => {
|
|
445
|
-
expect(isEmpty(null)).toBe(true)
|
|
446
|
-
})
|
|
447
|
-
|
|
448
|
-
it('should return false for non-empty values', () => {
|
|
449
|
-
expect(isEmpty('string')).toBe(false)
|
|
450
|
-
expect(isEmpty(123)).toBe(false)
|
|
451
|
-
})
|
|
452
|
-
})
|
|
453
|
-
|
|
454
|
-
describe('Time utilities', () => {
|
|
455
|
-
describe('constants', () => {
|
|
456
|
-
it('should have correct time constants', () => {
|
|
457
|
-
expect(Time.second).toBe(1000)
|
|
458
|
-
expect(Time.minute).toBe(60000)
|
|
459
|
-
expect(Time.hour).toBe(3600000)
|
|
460
|
-
expect(Time.day).toBe(86400000)
|
|
461
|
-
expect(Time.week).toBe(604800000)
|
|
462
|
-
})
|
|
463
|
-
})
|
|
464
|
-
|
|
465
|
-
describe('timezone', () => {
|
|
466
|
-
it('should get and set timezone offset', () => {
|
|
467
|
-
const original = Time.getTimezoneOffset()
|
|
468
|
-
Time.setTimezoneOffset(480)
|
|
469
|
-
expect(Time.getTimezoneOffset()).toBe(480)
|
|
470
|
-
Time.setTimezoneOffset(original)
|
|
471
|
-
})
|
|
472
|
-
})
|
|
473
|
-
|
|
474
|
-
describe('getDateNumber and fromDateNumber', () => {
|
|
475
|
-
it('should convert date to number and back', () => {
|
|
476
|
-
const date = new Date('2024-01-01T00:00:00Z')
|
|
477
|
-
const num = Time.getDateNumber(date, 0)
|
|
478
|
-
const restored = Time.fromDateNumber(num, 0)
|
|
479
|
-
expect(restored.getUTCDate()).toBe(date.getUTCDate())
|
|
480
|
-
})
|
|
481
|
-
|
|
482
|
-
it('should handle timestamp input', () => {
|
|
483
|
-
const timestamp = Date.now()
|
|
484
|
-
const num = Time.getDateNumber(timestamp)
|
|
485
|
-
expect(typeof num).toBe('number')
|
|
486
|
-
})
|
|
487
|
-
})
|
|
488
|
-
|
|
489
|
-
describe('parseTime', () => {
|
|
490
|
-
it('should parse time strings', () => {
|
|
491
|
-
expect(Time.parseTime('1d')).toBe(Time.day)
|
|
492
|
-
expect(Time.parseTime('2h')).toBe(Time.hour * 2)
|
|
493
|
-
expect(Time.parseTime('30m')).toBe(Time.minute * 30)
|
|
494
|
-
expect(Time.parseTime('45s')).toBe(Time.second * 45)
|
|
495
|
-
})
|
|
496
|
-
|
|
497
|
-
it('should parse combined time strings', () => {
|
|
498
|
-
expect(Time.parseTime('1d2h')).toBe(Time.day + Time.hour * 2)
|
|
499
|
-
expect(Time.parseTime('1w3d')).toBe(Time.week + Time.day * 3)
|
|
500
|
-
})
|
|
501
|
-
|
|
502
|
-
it('should return 0 for invalid strings', () => {
|
|
503
|
-
expect(Time.parseTime('invalid')).toBe(0)
|
|
504
|
-
expect(Time.parseTime('')).toBe(0)
|
|
505
|
-
})
|
|
506
|
-
|
|
507
|
-
it('should handle decimal values', () => {
|
|
508
|
-
expect(Time.parseTime('1.5h')).toBe(Time.hour * 1.5)
|
|
509
|
-
})
|
|
510
|
-
})
|
|
511
|
-
|
|
512
|
-
describe('parseDate', () => {
|
|
513
|
-
it('should parse relative time', () => {
|
|
514
|
-
const now = Date.now()
|
|
515
|
-
const result = Time.parseDate('1h')
|
|
516
|
-
expect(result.getTime()).toBeGreaterThan(now)
|
|
517
|
-
})
|
|
518
|
-
|
|
519
|
-
it('should parse time-only format', () => {
|
|
520
|
-
const result = Time.parseDate('14:30:00')
|
|
521
|
-
expect(result).toBeInstanceOf(Date)
|
|
522
|
-
})
|
|
523
|
-
|
|
524
|
-
it('should parse short date format', () => {
|
|
525
|
-
const result = Time.parseDate('12-25-14:30:00')
|
|
526
|
-
expect(result).toBeInstanceOf(Date)
|
|
527
|
-
})
|
|
528
|
-
|
|
529
|
-
it('should return current date for invalid input', () => {
|
|
530
|
-
const result = Time.parseDate('')
|
|
531
|
-
expect(result).toBeInstanceOf(Date)
|
|
532
|
-
})
|
|
533
|
-
})
|
|
534
|
-
|
|
535
|
-
describe('formatTimeShort', () => {
|
|
536
|
-
it('should format days', () => {
|
|
537
|
-
expect(Time.formatTimeShort(Time.day * 2)).toBe('2d')
|
|
538
|
-
})
|
|
539
|
-
|
|
540
|
-
it('should format hours', () => {
|
|
541
|
-
expect(Time.formatTimeShort(Time.hour * 3)).toBe('3h')
|
|
542
|
-
})
|
|
543
|
-
|
|
544
|
-
it('should format minutes', () => {
|
|
545
|
-
expect(Time.formatTimeShort(Time.minute * 45)).toBe('45m')
|
|
546
|
-
})
|
|
547
|
-
|
|
548
|
-
it('should format seconds', () => {
|
|
549
|
-
expect(Time.formatTimeShort(Time.second * 30)).toBe('30s')
|
|
550
|
-
})
|
|
551
|
-
|
|
552
|
-
it('should format milliseconds', () => {
|
|
553
|
-
expect(Time.formatTimeShort(500)).toBe('500ms')
|
|
554
|
-
})
|
|
555
|
-
|
|
556
|
-
it('should handle negative values', () => {
|
|
557
|
-
expect(Time.formatTimeShort(-Time.hour * 2)).toBe('-2h')
|
|
558
|
-
})
|
|
559
|
-
})
|
|
560
|
-
|
|
561
|
-
describe('formatTime', () => {
|
|
562
|
-
it('should format days with hours', () => {
|
|
563
|
-
const result = Time.formatTime(Time.day + Time.hour * 3)
|
|
564
|
-
expect(result).toContain('天')
|
|
565
|
-
expect(result).toContain('小时')
|
|
566
|
-
})
|
|
567
|
-
|
|
568
|
-
it('should format hours with minutes', () => {
|
|
569
|
-
const result = Time.formatTime(Time.hour * 2 + Time.minute * 30)
|
|
570
|
-
expect(result).toContain('小时')
|
|
571
|
-
expect(result).toContain('分钟')
|
|
572
|
-
})
|
|
573
|
-
|
|
574
|
-
it('should format minutes with seconds', () => {
|
|
575
|
-
const result = Time.formatTime(Time.minute * 5 + Time.second * 30)
|
|
576
|
-
expect(result).toContain('分钟')
|
|
577
|
-
expect(result).toContain('秒')
|
|
578
|
-
})
|
|
579
|
-
|
|
580
|
-
it('should format seconds only', () => {
|
|
581
|
-
const result = Time.formatTime(Time.second * 30)
|
|
582
|
-
expect(result).toContain('秒')
|
|
583
|
-
})
|
|
584
|
-
})
|
|
585
|
-
|
|
586
|
-
describe('template', () => {
|
|
587
|
-
it('should format date template', () => {
|
|
588
|
-
const date = new Date('2024-01-15T14:30:45.123')
|
|
589
|
-
const result = Time.template('yyyy-MM-dd hh:mm:ss', date)
|
|
590
|
-
expect(result).toBe('2024-01-15 14:30:45')
|
|
591
|
-
})
|
|
592
|
-
|
|
593
|
-
it('should handle short year format', () => {
|
|
594
|
-
const date = new Date('2024-01-15')
|
|
595
|
-
const result = Time.template('yy-MM-dd', date)
|
|
596
|
-
expect(result).toBe('24-01-15')
|
|
597
|
-
})
|
|
598
|
-
|
|
599
|
-
it('should handle milliseconds', () => {
|
|
600
|
-
const date = new Date('2024-01-15T14:30:45.123')
|
|
601
|
-
const result = Time.template('SSS', date)
|
|
602
|
-
expect(result).toBe('123')
|
|
603
|
-
})
|
|
604
|
-
})
|
|
605
|
-
|
|
606
|
-
describe('formatTimeInterval', () => {
|
|
607
|
-
it('should format without interval', () => {
|
|
608
|
-
const date = new Date('2024-01-15T14:30:45')
|
|
609
|
-
const result = Time.formatTimeInterval(date)
|
|
610
|
-
expect(result).toContain('2024-01-15')
|
|
611
|
-
})
|
|
612
|
-
|
|
613
|
-
it('should format daily interval', () => {
|
|
614
|
-
const date = new Date('2024-01-15T14:30:00')
|
|
615
|
-
const result = Time.formatTimeInterval(date, Time.day)
|
|
616
|
-
expect(result).toContain('每天')
|
|
617
|
-
})
|
|
618
|
-
|
|
619
|
-
it('should format weekly interval', () => {
|
|
620
|
-
const date = new Date('2024-01-15T14:30:00')
|
|
621
|
-
const result = Time.formatTimeInterval(date, Time.week)
|
|
622
|
-
expect(result).toContain('每周')
|
|
623
|
-
})
|
|
624
|
-
|
|
625
|
-
it('should format custom interval', () => {
|
|
626
|
-
const date = new Date('2024-01-15T14:30:00')
|
|
627
|
-
const result = Time.formatTimeInterval(date, Time.hour * 6)
|
|
628
|
-
expect(result).toContain('每隔')
|
|
629
|
-
})
|
|
630
|
-
})
|
|
631
|
-
})
|
|
632
|
-
|
|
633
|
-
describe('sleep utility', () => {
|
|
634
|
-
it('should sleep for specified time', async () => {
|
|
635
|
-
const start = Date.now()
|
|
636
|
-
await sleep(100)
|
|
637
|
-
const elapsed = Date.now() - start
|
|
638
|
-
expect(elapsed).toBeGreaterThanOrEqual(90) // 允许一些误差
|
|
639
134
|
})
|
|
640
135
|
})
|