@taicode/common-base 1.0.2 → 1.1.0
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/README.md +121 -0
- package/package.json +21 -3
- package/source/catch/catch.test.ts +341 -0
- package/source/catch/catch.ts +35 -0
- package/source/catch/index.ts +1 -0
- package/source/color/color.test.ts +144 -0
- package/source/{utils/color/index.ts → color/color.ts} +7 -0
- package/source/color/index.ts +1 -0
- package/source/debounce/debounce.test.ts +179 -0
- package/source/debounce/debounce.ts +42 -0
- package/source/debounce/index.ts +1 -0
- package/source/disposer/disposer.test.ts +257 -0
- package/source/disposer/index.ts +1 -0
- package/source/error/error.test.ts +293 -0
- package/source/error/error.ts +38 -0
- package/source/error/index.ts +1 -0
- package/source/event-emitter/event-emitter.test.ts +247 -0
- package/source/{utils/event-emitter/index.ts → event-emitter/event-emitter.ts} +2 -0
- package/source/event-emitter/index.ts +1 -0
- package/source/index.ts +19 -0
- package/source/logger/index.ts +43 -0
- package/source/logger/logger.test.ts +36 -0
- package/source/number/index.ts +1 -0
- package/source/number/number.test.ts +60 -0
- package/source/object/index.ts +1 -0
- package/source/{utils/object/index.test.ts → object/object.test.ts} +8 -8
- package/source/string/index.ts +1 -0
- package/source/string/string.test.ts +98 -0
- package/source/throttle/index.ts +1 -0
- package/source/throttle/throttle.test.ts +228 -0
- package/source/throttle/throttle.ts +76 -0
- package/test-exports.js +6 -0
- package/tsconfig.json +16 -21
- package/vitest.config.ts +8 -0
- package/source/decorators/debounce.ts +0 -25
- package/source/utils/poller/index.test.ts +0 -62
- package/source/utils/poller/index.ts +0 -57
- package/source/utils/promise/index.test.ts +0 -38
- package/source/utils/promise/index.ts +0 -45
- /package/source/{utils/disposer → disposer}/disposer.ts +0 -0
- /package/source/{utils/number/index.ts → number/number.ts} +0 -0
- /package/source/{utils/object/index.ts → object/object.ts} +0 -0
- /package/source/{utils/string/index.ts → string/string.ts} +0 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { UserError, SystemError, UnknownError } from './error'
|
|
3
|
+
|
|
4
|
+
describe('UserError', () => {
|
|
5
|
+
describe('constructor', () => {
|
|
6
|
+
it('应该正确创建UserError实例', () => {
|
|
7
|
+
const type = 'VALIDATION_ERROR'
|
|
8
|
+
const message = '输入验证失败'
|
|
9
|
+
const error = new UserError(type, message)
|
|
10
|
+
|
|
11
|
+
expect(error).toBeInstanceOf(Error)
|
|
12
|
+
expect(error).toBeInstanceOf(UserError)
|
|
13
|
+
expect(error.name).toBe('UserError')
|
|
14
|
+
expect(error.type).toBe(type)
|
|
15
|
+
expect(error.message).toBe(message)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('应该支持ErrorOptions参数', () => {
|
|
19
|
+
const type = 'VALIDATION_ERROR'
|
|
20
|
+
const message = '输入验证失败'
|
|
21
|
+
const cause = new Error('原始错误')
|
|
22
|
+
const error = new UserError(type, message, { cause })
|
|
23
|
+
|
|
24
|
+
expect(error.cause).toBe(cause)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('应该正确设置错误堆栈信息', () => {
|
|
28
|
+
const error = new UserError('TEST_ERROR', '测试错误')
|
|
29
|
+
expect(error.stack).toBeDefined()
|
|
30
|
+
expect(error.stack).toContain('UserError')
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe('is', () => {
|
|
35
|
+
it('应该正确识别UserError实例', () => {
|
|
36
|
+
const error = new UserError('TEST_ERROR', '测试错误')
|
|
37
|
+
expect(UserError.is(error)).toBe(true)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('应该正确识别非UserError实例', () => {
|
|
41
|
+
const regularError = new Error('普通错误')
|
|
42
|
+
const systemError = new SystemError('SYSTEM_ERROR', '系统错误')
|
|
43
|
+
const unknownError = new UnknownError('未知错误')
|
|
44
|
+
|
|
45
|
+
expect(UserError.is(regularError)).toBe(false)
|
|
46
|
+
expect(UserError.is(systemError)).toBe(false)
|
|
47
|
+
expect(UserError.is(unknownError)).toBe(false)
|
|
48
|
+
expect(UserError.is(null)).toBe(false)
|
|
49
|
+
expect(UserError.is(undefined)).toBe(false)
|
|
50
|
+
expect(UserError.is('字符串')).toBe(false)
|
|
51
|
+
expect(UserError.is(123)).toBe(false)
|
|
52
|
+
expect(UserError.is({})).toBe(false)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('应该支持类型守卫功能', () => {
|
|
56
|
+
const error: unknown = new UserError('TEST_ERROR', '测试错误')
|
|
57
|
+
|
|
58
|
+
if (UserError.is(error)) {
|
|
59
|
+
// 在类型守卫内部,error的类型应该被正确推断
|
|
60
|
+
expect(error.type).toBe('TEST_ERROR')
|
|
61
|
+
expect(error.message).toBe('测试错误')
|
|
62
|
+
expect(error.name).toBe('UserError')
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe('继承行为', () => {
|
|
68
|
+
it('应该正确继承Error的所有属性和方法', () => {
|
|
69
|
+
const error = new UserError('TEST_ERROR', '测试错误')
|
|
70
|
+
|
|
71
|
+
expect(error.toString()).toContain('测试错误')
|
|
72
|
+
expect(error.toString()).toContain('UserError')
|
|
73
|
+
expect(error instanceof Error).toBe(true)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('应该支持Error的标准用法', () => {
|
|
77
|
+
const error = new UserError('TEST_ERROR', '测试错误')
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
throw error
|
|
81
|
+
} catch (caught) {
|
|
82
|
+
expect(caught).toBe(error)
|
|
83
|
+
expect(caught).toBeInstanceOf(UserError)
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
describe('SystemError', () => {
|
|
90
|
+
describe('constructor', () => {
|
|
91
|
+
it('应该正确创建SystemError实例', () => {
|
|
92
|
+
const type = 'DATABASE_ERROR'
|
|
93
|
+
const message = '数据库连接失败'
|
|
94
|
+
const error = new SystemError(type, message)
|
|
95
|
+
|
|
96
|
+
expect(error).toBeInstanceOf(Error)
|
|
97
|
+
expect(error).toBeInstanceOf(SystemError)
|
|
98
|
+
expect(error.name).toBe('SystemError')
|
|
99
|
+
expect(error.type).toBe(type)
|
|
100
|
+
expect(error.message).toBe(message)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('应该支持ErrorOptions参数', () => {
|
|
104
|
+
const type = 'NETWORK_ERROR'
|
|
105
|
+
const message = '网络请求失败'
|
|
106
|
+
const cause = new Error('连接超时')
|
|
107
|
+
const error = new SystemError(type, message, { cause })
|
|
108
|
+
|
|
109
|
+
expect(error.cause).toBe(cause)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('应该正确设置错误堆栈信息', () => {
|
|
113
|
+
const error = new SystemError('FILE_ERROR', '文件读取失败')
|
|
114
|
+
expect(error.stack).toBeDefined()
|
|
115
|
+
expect(error.stack).toContain('SystemError')
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
describe('is', () => {
|
|
120
|
+
it('应该正确识别SystemError实例', () => {
|
|
121
|
+
const error = new SystemError('TEST_ERROR', '测试错误')
|
|
122
|
+
expect(SystemError.is(error)).toBe(true)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('应该正确识别非SystemError实例', () => {
|
|
126
|
+
const regularError = new Error('普通错误')
|
|
127
|
+
const userError = new UserError('USER_ERROR', '用户错误')
|
|
128
|
+
const unknownError = new UnknownError('未知错误')
|
|
129
|
+
|
|
130
|
+
expect(SystemError.is(regularError)).toBe(false)
|
|
131
|
+
expect(SystemError.is(userError)).toBe(false)
|
|
132
|
+
expect(SystemError.is(unknownError)).toBe(false)
|
|
133
|
+
expect(SystemError.is(null)).toBe(false)
|
|
134
|
+
expect(SystemError.is(undefined)).toBe(false)
|
|
135
|
+
expect(SystemError.is('字符串')).toBe(false)
|
|
136
|
+
expect(SystemError.is(123)).toBe(false)
|
|
137
|
+
expect(SystemError.is({})).toBe(false)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('应该支持类型守卫功能', () => {
|
|
141
|
+
const error: unknown = new SystemError('DATABASE_ERROR', '数据库错误')
|
|
142
|
+
|
|
143
|
+
if (SystemError.is(error)) {
|
|
144
|
+
// 在类型守卫内部,error的类型应该被正确推断
|
|
145
|
+
expect(error.type).toBe('DATABASE_ERROR')
|
|
146
|
+
expect(error.message).toBe('数据库错误')
|
|
147
|
+
expect(error.name).toBe('SystemError')
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
describe('继承行为', () => {
|
|
153
|
+
it('应该正确继承Error的所有属性和方法', () => {
|
|
154
|
+
const error = new SystemError('TEST_ERROR', '测试错误')
|
|
155
|
+
|
|
156
|
+
expect(error.toString()).toContain('测试错误')
|
|
157
|
+
expect(error.toString()).toContain('SystemError')
|
|
158
|
+
expect(error instanceof Error).toBe(true)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('应该支持Error的标准用法', () => {
|
|
162
|
+
const error = new SystemError('TEST_ERROR', '测试错误')
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
throw error
|
|
166
|
+
} catch (caught) {
|
|
167
|
+
expect(caught).toBe(error)
|
|
168
|
+
expect(caught).toBeInstanceOf(SystemError)
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
describe('UnknownError', () => {
|
|
175
|
+
describe('constructor', () => {
|
|
176
|
+
it('应该正确创建UnknownError实例', () => {
|
|
177
|
+
const message = '发生了未知错误'
|
|
178
|
+
const error = new UnknownError(message)
|
|
179
|
+
|
|
180
|
+
expect(error).toBeInstanceOf(Error)
|
|
181
|
+
expect(error).toBeInstanceOf(UnknownError)
|
|
182
|
+
expect(error.name).toBe('UnknownError')
|
|
183
|
+
expect(error.message).toBe(message)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('应该支持ErrorOptions参数', () => {
|
|
187
|
+
const message = '未知错误'
|
|
188
|
+
const cause = new Error('原始未知错误')
|
|
189
|
+
const error = new UnknownError(message, { cause })
|
|
190
|
+
|
|
191
|
+
expect(error.cause).toBe(cause)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('应该正确设置错误堆栈信息', () => {
|
|
195
|
+
const error = new UnknownError('测试未知错误')
|
|
196
|
+
expect(error.stack).toBeDefined()
|
|
197
|
+
expect(error.stack).toContain('UnknownError')
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
describe('is', () => {
|
|
202
|
+
it('应该正确识别UnknownError实例', () => {
|
|
203
|
+
const error = new UnknownError('测试错误')
|
|
204
|
+
expect(UnknownError.is(error)).toBe(true)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('应该正确识别非UnknownError实例', () => {
|
|
208
|
+
const regularError = new Error('普通错误')
|
|
209
|
+
const userError = new UserError('USER_ERROR', '用户错误')
|
|
210
|
+
const systemError = new SystemError('SYSTEM_ERROR', '系统错误')
|
|
211
|
+
|
|
212
|
+
expect(UnknownError.is(regularError)).toBe(false)
|
|
213
|
+
expect(UnknownError.is(userError)).toBe(false)
|
|
214
|
+
expect(UnknownError.is(systemError)).toBe(false)
|
|
215
|
+
expect(UnknownError.is(null)).toBe(false)
|
|
216
|
+
expect(UnknownError.is(undefined)).toBe(false)
|
|
217
|
+
expect(UnknownError.is('字符串')).toBe(false)
|
|
218
|
+
expect(UnknownError.is(123)).toBe(false)
|
|
219
|
+
expect(UnknownError.is({})).toBe(false)
|
|
220
|
+
})
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
describe('继承行为', () => {
|
|
224
|
+
it('应该正确继承Error的所有属性和方法', () => {
|
|
225
|
+
const error = new UnknownError('测试错误')
|
|
226
|
+
|
|
227
|
+
expect(error.toString()).toContain('测试错误')
|
|
228
|
+
expect(error instanceof Error).toBe(true)
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('应该支持Error的标准用法', () => {
|
|
232
|
+
const error = new UnknownError('测试错误')
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
throw error
|
|
236
|
+
} catch (caught) {
|
|
237
|
+
expect(caught).toBe(error)
|
|
238
|
+
expect(caught).toBeInstanceOf(UnknownError)
|
|
239
|
+
}
|
|
240
|
+
})
|
|
241
|
+
})
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
describe('错误类型区分', () => {
|
|
245
|
+
it('三种错误类型应该是不同的类', () => {
|
|
246
|
+
const userError = new UserError('USER_ERROR', '用户错误')
|
|
247
|
+
const systemError = new SystemError('SYSTEM_ERROR', '系统错误')
|
|
248
|
+
const unknownError = new UnknownError('未知错误')
|
|
249
|
+
|
|
250
|
+
// 每种错误只能被自己的类型检查函数识别
|
|
251
|
+
expect(UserError.is(userError)).toBe(true)
|
|
252
|
+
expect(SystemError.is(userError)).toBe(false)
|
|
253
|
+
expect(UnknownError.is(userError)).toBe(false)
|
|
254
|
+
|
|
255
|
+
expect(UserError.is(systemError)).toBe(false)
|
|
256
|
+
expect(SystemError.is(systemError)).toBe(true)
|
|
257
|
+
expect(UnknownError.is(systemError)).toBe(false)
|
|
258
|
+
|
|
259
|
+
expect(UserError.is(unknownError)).toBe(false)
|
|
260
|
+
expect(SystemError.is(unknownError)).toBe(false)
|
|
261
|
+
expect(UnknownError.is(unknownError)).toBe(true)
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
it('所有错误类型都应该是Error的实例', () => {
|
|
265
|
+
const userError = new UserError('USER_ERROR', '用户错误')
|
|
266
|
+
const systemError = new SystemError('SYSTEM_ERROR', '系统错误')
|
|
267
|
+
const unknownError = new UnknownError('未知错误')
|
|
268
|
+
|
|
269
|
+
expect(userError instanceof Error).toBe(true)
|
|
270
|
+
expect(systemError instanceof Error).toBe(true)
|
|
271
|
+
expect(unknownError instanceof Error).toBe(true)
|
|
272
|
+
})
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
describe('泛型类型支持', () => {
|
|
276
|
+
it('UserError应该支持字符串字面量类型', () => {
|
|
277
|
+
type ValidationErrorType = 'REQUIRED_FIELD' | 'INVALID_FORMAT' | 'OUT_OF_RANGE'
|
|
278
|
+
|
|
279
|
+
const error = new UserError<ValidationErrorType>('REQUIRED_FIELD', '字段必填')
|
|
280
|
+
|
|
281
|
+
expect(error.type).toBe('REQUIRED_FIELD')
|
|
282
|
+
expect(UserError.is<ValidationErrorType>(error)).toBe(true)
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it('SystemError应该支持字符串字面量类型', () => {
|
|
286
|
+
type SystemErrorType = 'DATABASE_ERROR' | 'NETWORK_ERROR' | 'FILE_ERROR'
|
|
287
|
+
|
|
288
|
+
const error = new SystemError<SystemErrorType>('DATABASE_ERROR', '数据库连接失败')
|
|
289
|
+
|
|
290
|
+
expect(error.type).toBe('DATABASE_ERROR')
|
|
291
|
+
expect(SystemError.is<SystemErrorType>(error)).toBe(true)
|
|
292
|
+
})
|
|
293
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
interface ErrorOptions {
|
|
2
|
+
cause?: unknown
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
/** 用户可见的错误 */
|
|
6
|
+
export class UserError<T extends string> extends Error {
|
|
7
|
+
constructor(public type: T, public message: string, options?: ErrorOptions) {
|
|
8
|
+
super(message, options)
|
|
9
|
+
this.name = 'UserError'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static is<T extends string>(error: unknown): error is UserError<T> {
|
|
13
|
+
return error instanceof UserError
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** 系统错误 */
|
|
18
|
+
export class SystemError<T extends string> extends Error {
|
|
19
|
+
constructor(public type: T, public message: string, options?: ErrorOptions) {
|
|
20
|
+
super(message, options)
|
|
21
|
+
this.name = 'SystemError'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static is<T extends string>(error: unknown): error is SystemError<T> {
|
|
25
|
+
return error instanceof SystemError
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class UnknownError extends Error {
|
|
30
|
+
constructor(message: string, options?: ErrorOptions) {
|
|
31
|
+
super(message, options)
|
|
32
|
+
this.name = 'UnknownError'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static is(error: unknown): boolean {
|
|
36
|
+
return error instanceof UnknownError
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { UserError, SystemError, UnknownError } from './error'
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
+
import { EventEmitter } from './event-emitter'
|
|
3
|
+
|
|
4
|
+
// 定义测试用的事件类型
|
|
5
|
+
interface TestEvents {
|
|
6
|
+
'user-login': { userId: string; timestamp: number }
|
|
7
|
+
'user-logout': { userId: string }
|
|
8
|
+
'data-update': string
|
|
9
|
+
'error': Error
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
describe('EventEmitter', () => {
|
|
13
|
+
let emitter: EventEmitter<TestEvents>
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
emitter = new EventEmitter<TestEvents>()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe('on 方法', () => {
|
|
20
|
+
it('应该注册监听器并返回一个关闭函数', () => {
|
|
21
|
+
const listener = vi.fn()
|
|
22
|
+
const off = emitter.on('data-update', listener)
|
|
23
|
+
|
|
24
|
+
expect(typeof off).toBe('function')
|
|
25
|
+
|
|
26
|
+
emitter.emit('data-update', 'test data')
|
|
27
|
+
expect(listener).toHaveBeenCalledWith('test data')
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('应该为同一事件注册多个监听器', () => {
|
|
31
|
+
const listener1 = vi.fn()
|
|
32
|
+
const listener2 = vi.fn()
|
|
33
|
+
|
|
34
|
+
emitter.on('data-update', listener1)
|
|
35
|
+
emitter.on('data-update', listener2)
|
|
36
|
+
|
|
37
|
+
emitter.emit('data-update', 'test data')
|
|
38
|
+
|
|
39
|
+
expect(listener1).toHaveBeenCalledWith('test data')
|
|
40
|
+
expect(listener2).toHaveBeenCalledWith('test data')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('应该允许同一监听器被多次注册', () => {
|
|
44
|
+
const listener = vi.fn()
|
|
45
|
+
|
|
46
|
+
emitter.on('data-update', listener)
|
|
47
|
+
emitter.on('data-update', listener)
|
|
48
|
+
|
|
49
|
+
emitter.emit('data-update', 'test data')
|
|
50
|
+
|
|
51
|
+
expect(listener).toHaveBeenCalledTimes(2)
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
describe('off 方法', () => {
|
|
56
|
+
it('应该移除特定的监听器', () => {
|
|
57
|
+
const listener1 = vi.fn()
|
|
58
|
+
const listener2 = vi.fn()
|
|
59
|
+
|
|
60
|
+
emitter.on('data-update', listener1)
|
|
61
|
+
emitter.on('data-update', listener2)
|
|
62
|
+
|
|
63
|
+
emitter.off('data-update', listener1)
|
|
64
|
+
emitter.emit('data-update', 'test data')
|
|
65
|
+
|
|
66
|
+
expect(listener1).not.toHaveBeenCalled()
|
|
67
|
+
expect(listener2).toHaveBeenCalledWith('test data')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('应该在没有提供特定监听器时移除所有监听器', () => {
|
|
71
|
+
const listener1 = vi.fn()
|
|
72
|
+
const listener2 = vi.fn()
|
|
73
|
+
|
|
74
|
+
emitter.on('data-update', listener1)
|
|
75
|
+
emitter.on('data-update', listener2)
|
|
76
|
+
|
|
77
|
+
emitter.off('data-update')
|
|
78
|
+
emitter.emit('data-update', 'test data')
|
|
79
|
+
|
|
80
|
+
expect(listener1).not.toHaveBeenCalled()
|
|
81
|
+
expect(listener2).not.toHaveBeenCalled()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('应该优雅地处理移除不存在的监听器', () => {
|
|
85
|
+
const listener = vi.fn()
|
|
86
|
+
|
|
87
|
+
// 不应该抛出错误
|
|
88
|
+
expect(() => {
|
|
89
|
+
emitter.off('data-update', listener)
|
|
90
|
+
}).not.toThrow()
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('应该与 on 方法返回的 off 函数配合使用', () => {
|
|
94
|
+
const listener = vi.fn()
|
|
95
|
+
const off = emitter.on('data-update', listener)
|
|
96
|
+
|
|
97
|
+
off()
|
|
98
|
+
emitter.emit('data-update', 'test data')
|
|
99
|
+
|
|
100
|
+
expect(listener).not.toHaveBeenCalled()
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
describe('emit 方法', () => {
|
|
105
|
+
it('应该用正确的数据调用所有注册的监听器', () => {
|
|
106
|
+
const listener1 = vi.fn()
|
|
107
|
+
const listener2 = vi.fn()
|
|
108
|
+
const testData = { userId: 'user123', timestamp: Date.now() }
|
|
109
|
+
|
|
110
|
+
emitter.on('user-login', listener1)
|
|
111
|
+
emitter.on('user-login', listener2)
|
|
112
|
+
|
|
113
|
+
emitter.emit('user-login', testData)
|
|
114
|
+
|
|
115
|
+
expect(listener1).toHaveBeenCalledWith(testData)
|
|
116
|
+
expect(listener2).toHaveBeenCalledWith(testData)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('应该优雅地处理没有监听器的事件', () => {
|
|
120
|
+
expect(() => {
|
|
121
|
+
emitter.emit('data-update', 'test data')
|
|
122
|
+
}).not.toThrow()
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('应该处理抛出错误的监听器', () => {
|
|
126
|
+
const goodListener = vi.fn()
|
|
127
|
+
const badListener = vi.fn(() => {
|
|
128
|
+
throw new Error('Listener error')
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
emitter.on('data-update', goodListener)
|
|
132
|
+
emitter.on('data-update', badListener)
|
|
133
|
+
|
|
134
|
+
expect(() => {
|
|
135
|
+
emitter.emit('data-update', 'test data')
|
|
136
|
+
}).toThrow('Listener error')
|
|
137
|
+
|
|
138
|
+
expect(goodListener).toHaveBeenCalledWith('test data')
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('应该创建监听器数组的副本以避免并发修改问题', () => {
|
|
142
|
+
const listeners: Array<() => void> = []
|
|
143
|
+
|
|
144
|
+
// 创建一个监听器,它会在执行时移除自己
|
|
145
|
+
const selfRemovingListener = () => {
|
|
146
|
+
emitter.off('data-update', selfRemovingListener)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 创建其他监听器
|
|
150
|
+
const otherListener = vi.fn()
|
|
151
|
+
|
|
152
|
+
emitter.on('data-update', selfRemovingListener)
|
|
153
|
+
emitter.on('data-update', otherListener)
|
|
154
|
+
|
|
155
|
+
// 发射事件,这应该不会导致问题
|
|
156
|
+
expect(() => {
|
|
157
|
+
emitter.emit('data-update', 'test data')
|
|
158
|
+
}).not.toThrow()
|
|
159
|
+
|
|
160
|
+
expect(otherListener).toHaveBeenCalledWith('test data')
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
describe('once 方法', () => {
|
|
165
|
+
it('应该注册一个只触发一次的监听器', () => {
|
|
166
|
+
const listener = vi.fn()
|
|
167
|
+
|
|
168
|
+
emitter.once('data-update', listener)
|
|
169
|
+
|
|
170
|
+
emitter.emit('data-update', 'first call')
|
|
171
|
+
emitter.emit('data-update', 'second call')
|
|
172
|
+
|
|
173
|
+
expect(listener).toHaveBeenCalledTimes(1)
|
|
174
|
+
expect(listener).toHaveBeenCalledWith('first call')
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('应该处理复杂的事件数据', () => {
|
|
178
|
+
const listener = vi.fn()
|
|
179
|
+
const testData = { userId: 'user123', timestamp: Date.now() }
|
|
180
|
+
|
|
181
|
+
emitter.once('user-login', listener)
|
|
182
|
+
|
|
183
|
+
emitter.emit('user-login', testData)
|
|
184
|
+
emitter.emit('user-login', { userId: 'user456', timestamp: Date.now() })
|
|
185
|
+
|
|
186
|
+
expect(listener).toHaveBeenCalledTimes(1)
|
|
187
|
+
expect(listener).toHaveBeenCalledWith(testData)
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
describe('cleanup 方法', () => {
|
|
192
|
+
it('应该移除所有监听器', () => {
|
|
193
|
+
const listener1 = vi.fn()
|
|
194
|
+
const listener2 = vi.fn()
|
|
195
|
+
|
|
196
|
+
emitter.on('data-update', listener1)
|
|
197
|
+
emitter.on('user-login', listener2)
|
|
198
|
+
|
|
199
|
+
emitter.cleanup()
|
|
200
|
+
|
|
201
|
+
emitter.emit('data-update', 'test data')
|
|
202
|
+
emitter.emit('user-login', { userId: 'user123', timestamp: Date.now() })
|
|
203
|
+
|
|
204
|
+
expect(listener1).not.toHaveBeenCalled()
|
|
205
|
+
expect(listener2).not.toHaveBeenCalled()
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
describe('方法绑定', () => {
|
|
210
|
+
it('应该将方法正确绑定到实例', () => {
|
|
211
|
+
const { on, off, once, emit, cleanup } = emitter
|
|
212
|
+
|
|
213
|
+
const listener = vi.fn()
|
|
214
|
+
|
|
215
|
+
// 这些方法应该可以独立调用而不丢失上下文
|
|
216
|
+
expect(() => {
|
|
217
|
+
on('data-update', listener)
|
|
218
|
+
emit('data-update', 'test data')
|
|
219
|
+
off('data-update', listener)
|
|
220
|
+
once('data-update', listener)
|
|
221
|
+
cleanup()
|
|
222
|
+
}).not.toThrow()
|
|
223
|
+
|
|
224
|
+
expect(listener).toHaveBeenCalledWith('test data')
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
describe('类型安全', () => {
|
|
229
|
+
it('应该处理不同的事件类型', () => {
|
|
230
|
+
const loginListener = vi.fn()
|
|
231
|
+
const logoutListener = vi.fn()
|
|
232
|
+
const errorListener = vi.fn()
|
|
233
|
+
|
|
234
|
+
emitter.on('user-login', loginListener)
|
|
235
|
+
emitter.on('user-logout', logoutListener)
|
|
236
|
+
emitter.on('error', errorListener)
|
|
237
|
+
|
|
238
|
+
emitter.emit('user-login', { userId: 'user123', timestamp: Date.now() })
|
|
239
|
+
emitter.emit('user-logout', { userId: 'user123' })
|
|
240
|
+
emitter.emit('error', new Error('Test error'))
|
|
241
|
+
|
|
242
|
+
expect(loginListener).toHaveBeenCalledWith({ userId: 'user123', timestamp: expect.any(Number) })
|
|
243
|
+
expect(logoutListener).toHaveBeenCalledWith({ userId: 'user123' })
|
|
244
|
+
expect(errorListener).toHaveBeenCalledWith(expect.any(Error))
|
|
245
|
+
})
|
|
246
|
+
})
|
|
247
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './event-emitter'
|
package/source/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// === 资源管理 ===
|
|
2
|
+
export * from './disposer'
|
|
3
|
+
|
|
4
|
+
// === 事件系统 ===
|
|
5
|
+
export * from './event-emitter'
|
|
6
|
+
|
|
7
|
+
// === 日志系统 ===
|
|
8
|
+
export * from './logger'
|
|
9
|
+
|
|
10
|
+
// === 基础工具 ===
|
|
11
|
+
export * from './string'
|
|
12
|
+
export * from './color'
|
|
13
|
+
export * from './object'
|
|
14
|
+
export * from './number'
|
|
15
|
+
export * from './catch'
|
|
16
|
+
|
|
17
|
+
// === 函数增强 ===
|
|
18
|
+
export * from './debounce'
|
|
19
|
+
export * from './throttle'
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// 基础 Logger 实现,支持简单的日志输出和扩展能力
|
|
2
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
3
|
+
|
|
4
|
+
export interface LoggerOptions {
|
|
5
|
+
level?: LogLevel;
|
|
6
|
+
handler?: (level: LogLevel, ...args: any[]) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class Logger {
|
|
10
|
+
private level: LogLevel;
|
|
11
|
+
private handler: (level: LogLevel, ...args: any[]) => void;
|
|
12
|
+
|
|
13
|
+
constructor(options: LoggerOptions = {}) {
|
|
14
|
+
this.level = options.level || 'info';
|
|
15
|
+
this.handler = options.handler || this.defaultHandler;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
private defaultHandler(level: LogLevel, ...args: any[]) {
|
|
19
|
+
const prefix = `[${level.toUpperCase()}]`;
|
|
20
|
+
// eslint-disable-next-line no-console
|
|
21
|
+
(console[level] || console.log)(prefix, ...args);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private shouldLog(level: LogLevel): boolean {
|
|
25
|
+
const order: LogLevel[] = ['debug', 'info', 'warn', 'error'];
|
|
26
|
+
return order.indexOf(level) >= order.indexOf(this.level);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
debug(...args: any[]) {
|
|
30
|
+
if (this.shouldLog('debug')) this.handler('debug', ...args);
|
|
31
|
+
}
|
|
32
|
+
info(...args: any[]) {
|
|
33
|
+
if (this.shouldLog('info')) this.handler('info', ...args);
|
|
34
|
+
}
|
|
35
|
+
warn(...args: any[]) {
|
|
36
|
+
if (this.shouldLog('warn')) this.handler('warn', ...args);
|
|
37
|
+
}
|
|
38
|
+
error(...args: any[]) {
|
|
39
|
+
if (this.shouldLog('error')) this.handler('error', ...args);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const logger = new Logger();
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { Logger } from './index';
|
|
3
|
+
|
|
4
|
+
describe('Logger', () => {
|
|
5
|
+
it('应该默认记录 info 及以上级别', () => {
|
|
6
|
+
const handler = vi.fn();
|
|
7
|
+
const logger = new Logger({ handler });
|
|
8
|
+
logger.debug('debug');
|
|
9
|
+
logger.info('info');
|
|
10
|
+
logger.warn('warn');
|
|
11
|
+
logger.error('error');
|
|
12
|
+
expect(handler).not.toHaveBeenCalledWith('debug', 'debug');
|
|
13
|
+
expect(handler).toHaveBeenCalledWith('info', 'info');
|
|
14
|
+
expect(handler).toHaveBeenCalledWith('warn', 'warn');
|
|
15
|
+
expect(handler).toHaveBeenCalledWith('error', 'error');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('应该遵守日志级别', () => {
|
|
19
|
+
const handler = vi.fn();
|
|
20
|
+
const logger = new Logger({ level: 'warn', handler });
|
|
21
|
+
logger.info('info');
|
|
22
|
+
logger.warn('warn');
|
|
23
|
+
logger.error('error');
|
|
24
|
+
expect(handler).not.toHaveBeenCalledWith('info', 'info');
|
|
25
|
+
expect(handler).toHaveBeenCalledWith('warn', 'warn');
|
|
26
|
+
expect(handler).toHaveBeenCalledWith('error', 'error');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('应该允许自定义处理器', () => {
|
|
30
|
+
const logs: any[] = [];
|
|
31
|
+
const handler = (level: string, ...args: any[]) => logs.push([level, ...args]);
|
|
32
|
+
const logger = new Logger({ handler });
|
|
33
|
+
logger.error('err', 123);
|
|
34
|
+
expect(logs).toContainEqual(['error', 'err', 123]);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './number'
|