@zhin.js/core 1.1.0 → 1.1.3

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 (122) hide show
  1. package/lib/adapter.d.ts +1 -26
  2. package/lib/adapter.d.ts.map +1 -1
  3. package/lib/adapter.js +20 -117
  4. package/lib/adapter.js.map +1 -1
  5. package/lib/built/adapter-process.d.ts +0 -4
  6. package/lib/built/adapter-process.d.ts.map +1 -1
  7. package/lib/built/adapter-process.js +0 -95
  8. package/lib/built/adapter-process.js.map +1 -1
  9. package/lib/built/agent-preset.d.ts +2 -0
  10. package/lib/built/agent-preset.d.ts.map +1 -1
  11. package/lib/built/agent-preset.js +4 -0
  12. package/lib/built/agent-preset.js.map +1 -1
  13. package/lib/built/command.d.ts +4 -0
  14. package/lib/built/command.d.ts.map +1 -1
  15. package/lib/built/command.js +6 -0
  16. package/lib/built/command.js.map +1 -1
  17. package/lib/built/component.d.ts.map +1 -1
  18. package/lib/built/component.js +1 -0
  19. package/lib/built/component.js.map +1 -1
  20. package/lib/built/dispatcher.d.ts.map +1 -1
  21. package/lib/built/dispatcher.js +0 -13
  22. package/lib/built/dispatcher.js.map +1 -1
  23. package/lib/built/message-filter.d.ts +2 -0
  24. package/lib/built/message-filter.d.ts.map +1 -1
  25. package/lib/built/message-filter.js +5 -0
  26. package/lib/built/message-filter.js.map +1 -1
  27. package/lib/built/skill.d.ts +11 -0
  28. package/lib/built/skill.d.ts.map +1 -1
  29. package/lib/built/skill.js +14 -0
  30. package/lib/built/skill.js.map +1 -1
  31. package/lib/built/tool.d.ts +11 -44
  32. package/lib/built/tool.d.ts.map +1 -1
  33. package/lib/built/tool.js +14 -353
  34. package/lib/built/tool.js.map +1 -1
  35. package/lib/plugin.d.ts +1 -25
  36. package/lib/plugin.d.ts.map +1 -1
  37. package/lib/plugin.js +1 -77
  38. package/lib/plugin.js.map +1 -1
  39. package/lib/types.d.ts +0 -25
  40. package/lib/types.d.ts.map +1 -1
  41. package/package.json +10 -7
  42. package/CHANGELOG.md +0 -561
  43. package/REFACTORING_COMPLETE.md +0 -178
  44. package/REFACTORING_STATUS.md +0 -263
  45. package/src/adapter.ts +0 -275
  46. package/src/ai/index.ts +0 -55
  47. package/src/ai/providers/anthropic.ts +0 -379
  48. package/src/ai/providers/base.ts +0 -175
  49. package/src/ai/providers/index.ts +0 -13
  50. package/src/ai/providers/ollama.ts +0 -302
  51. package/src/ai/providers/openai.ts +0 -174
  52. package/src/ai/types.ts +0 -348
  53. package/src/bot.ts +0 -37
  54. package/src/built/adapter-process.ts +0 -177
  55. package/src/built/agent-preset.ts +0 -136
  56. package/src/built/ai-trigger.ts +0 -259
  57. package/src/built/command.ts +0 -108
  58. package/src/built/common-adapter-tools.ts +0 -242
  59. package/src/built/component.ts +0 -130
  60. package/src/built/config.ts +0 -335
  61. package/src/built/cron.ts +0 -156
  62. package/src/built/database.ts +0 -134
  63. package/src/built/dispatcher.ts +0 -496
  64. package/src/built/login-assist.ts +0 -131
  65. package/src/built/message-filter.ts +0 -390
  66. package/src/built/permission.ts +0 -151
  67. package/src/built/schema-feature.ts +0 -190
  68. package/src/built/skill.ts +0 -221
  69. package/src/built/tool.ts +0 -948
  70. package/src/command.ts +0 -87
  71. package/src/component.ts +0 -565
  72. package/src/cron.ts +0 -4
  73. package/src/errors.ts +0 -46
  74. package/src/feature.ts +0 -7
  75. package/src/index.ts +0 -53
  76. package/src/jsx-dev-runtime.ts +0 -2
  77. package/src/jsx-runtime.ts +0 -12
  78. package/src/jsx.ts +0 -135
  79. package/src/message.ts +0 -48
  80. package/src/models/system-log.ts +0 -20
  81. package/src/models/user.ts +0 -15
  82. package/src/notice.ts +0 -98
  83. package/src/plugin.ts +0 -896
  84. package/src/prompt.ts +0 -293
  85. package/src/request.ts +0 -95
  86. package/src/scheduler/index.ts +0 -19
  87. package/src/scheduler/scheduler.ts +0 -372
  88. package/src/scheduler/types.ts +0 -74
  89. package/src/tool-zod.ts +0 -115
  90. package/src/types-generator.ts +0 -78
  91. package/src/types.ts +0 -505
  92. package/src/utils.ts +0 -227
  93. package/tests/adapter.test.ts +0 -638
  94. package/tests/ai/ai-trigger.test.ts +0 -368
  95. package/tests/ai/providers.integration.test.ts +0 -227
  96. package/tests/ai/setup.ts +0 -308
  97. package/tests/ai/tool.test.ts +0 -800
  98. package/tests/bot.test.ts +0 -151
  99. package/tests/command.test.ts +0 -737
  100. package/tests/component-new.test.ts +0 -361
  101. package/tests/config.test.ts +0 -372
  102. package/tests/cron.test.ts +0 -82
  103. package/tests/dispatcher.test.ts +0 -293
  104. package/tests/errors.test.ts +0 -21
  105. package/tests/expression-evaluation.test.ts +0 -258
  106. package/tests/features-builtin.test.ts +0 -191
  107. package/tests/jsx-runtime.test.ts +0 -45
  108. package/tests/jsx.test.ts +0 -319
  109. package/tests/message-filter.test.ts +0 -566
  110. package/tests/message.test.ts +0 -402
  111. package/tests/notice.test.ts +0 -198
  112. package/tests/plugin.test.ts +0 -779
  113. package/tests/prompt.test.ts +0 -78
  114. package/tests/redos-protection.test.ts +0 -198
  115. package/tests/request.test.ts +0 -221
  116. package/tests/schema.test.ts +0 -248
  117. package/tests/skill-feature.test.ts +0 -179
  118. package/tests/test-utils.ts +0 -59
  119. package/tests/tool-feature.test.ts +0 -254
  120. package/tests/types.test.ts +0 -162
  121. package/tests/utils.test.ts +0 -135
  122. package/tsconfig.json +0 -24
@@ -1,779 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest'
2
- import { Plugin, usePlugin, getPlugin, storage, defineContext } from '../src/plugin'
3
- import { EventEmitter } from 'events'
4
-
5
- describe('Plugin Core Functionality', () => {
6
- describe('Plugin Constructor', () => {
7
- it('should create a plugin with file path', () => {
8
- const plugin = new Plugin('/test/plugin.ts')
9
- expect(plugin.filePath).toBe('/test/plugin.ts')
10
- expect(plugin.started).toBe(false)
11
- expect(plugin.children).toEqual([])
12
- })
13
-
14
- it('should create a plugin without file path', () => {
15
- const plugin = new Plugin()
16
- expect(plugin.filePath).toBe('')
17
- })
18
-
19
- it('should add plugin to parent children', () => {
20
- const parent = new Plugin('/test/parent.ts')
21
- const child = new Plugin('/test/child.ts', parent)
22
- expect(parent.children).toContain(child)
23
- expect(child.parent).toBe(parent)
24
- })
25
-
26
- it('should not duplicate child in parent', () => {
27
- const parent = new Plugin('/test/parent.ts')
28
- const child = new Plugin('/test/child.ts', parent)
29
- // 尝试再次添加
30
- if (!parent.children.includes(child)) {
31
- parent.children.push(child)
32
- }
33
- expect(parent.children.filter(c => c === child).length).toBe(1)
34
- })
35
-
36
- it('should inherit from EventEmitter', () => {
37
- const plugin = new Plugin('/test/plugin.ts')
38
- expect(plugin).toBeInstanceOf(EventEmitter)
39
- })
40
-
41
- it('should set max listeners to 50', () => {
42
- const plugin = new Plugin('/test/plugin.ts')
43
- expect(plugin.getMaxListeners()).toBe(50)
44
- })
45
- })
46
-
47
- describe('Plugin Name', () => {
48
- it('should extract plugin name from file path', () => {
49
- const plugin = new Plugin('/path/to/my-plugin/src/index.ts')
50
- expect(plugin.name).toContain('my-plugin')
51
- })
52
-
53
- it('should handle node_modules path', () => {
54
- const plugin = new Plugin('/path/node_modules/@scope/package/index.js')
55
- const name = plugin.name
56
- expect(name.length).toBeGreaterThan(0)
57
- })
58
-
59
- it('should cache plugin name', () => {
60
- const plugin = new Plugin('/test/plugin.ts')
61
- const name1 = plugin.name
62
- const name2 = plugin.name
63
- expect(name1).toBe(name2)
64
- })
65
-
66
- it('should remove file extensions', () => {
67
- const plugin1 = new Plugin('/test/plugin.ts')
68
- const plugin2 = new Plugin('/test/plugin.js')
69
- expect(plugin1.name).not.toContain('.ts')
70
- expect(plugin2.name).not.toContain('.js')
71
- })
72
- })
73
-
74
- describe('Plugin Root', () => {
75
- it('should return self as root if no parent', () => {
76
- const plugin = new Plugin('/test/plugin.ts')
77
- expect(plugin.root).toBe(plugin)
78
- })
79
-
80
- it('should return top-level parent as root', () => {
81
- const grandparent = new Plugin('/test/grandparent.ts')
82
- const parent = new Plugin('/test/parent.ts', grandparent)
83
- const child = new Plugin('/test/child.ts', parent)
84
-
85
- expect(child.root).toBe(grandparent)
86
- expect(parent.root).toBe(grandparent)
87
- expect(grandparent.root).toBe(grandparent)
88
- })
89
- })
90
-
91
- describe('Plugin Middleware', () => {
92
- it('should add middleware', () => {
93
- const plugin = new Plugin('/test/plugin.ts')
94
- const middleware = vi.fn(async (msg: any, next: any) => await next())
95
-
96
- const dispose = plugin.addMiddleware(middleware)
97
- expect(typeof dispose).toBe('function')
98
- })
99
-
100
- it('should remove middleware on dispose', () => {
101
- const plugin = new Plugin('/test/plugin.ts')
102
- const middleware = vi.fn(async (msg: any, next: any) => await next())
103
-
104
- const dispose = plugin.addMiddleware(middleware)
105
- dispose()
106
-
107
- // 验证 dispose 被调用后中间件被移除
108
- expect(dispose).toBeDefined()
109
- })
110
-
111
- it('should compose multiple middlewares', async () => {
112
- const plugin = new Plugin('/test/plugin.ts')
113
- const order: number[] = []
114
-
115
- plugin.addMiddleware(async (msg: any, next: any) => {
116
- order.push(1)
117
- await next()
118
- order.push(4)
119
- })
120
-
121
- plugin.addMiddleware(async (msg: any, next: any) => {
122
- order.push(2)
123
- await next()
124
- order.push(3)
125
- })
126
-
127
- const composed = plugin.middleware
128
- await composed({} as any, async () => {})
129
-
130
- expect(order.length).toBeGreaterThan(0)
131
- })
132
- })
133
-
134
- describe('Plugin Contexts', () => {
135
- it('should initialize with empty contexts', () => {
136
- const plugin = new Plugin('/test/plugin.ts')
137
- expect(plugin.$contexts).toBeInstanceOf(Map)
138
- expect(plugin.$contexts.size).toBe(0)
139
- })
140
-
141
- it('should get contexts including children', () => {
142
- const parent = new Plugin('/test/parent.ts')
143
- new Plugin('/test/child.ts', parent)
144
-
145
- const contexts = parent.contexts
146
- expect(contexts).toBeInstanceOf(Map)
147
- })
148
- })
149
-
150
- describe('Plugin Lifecycle', () => {
151
- it('should start with started = false', () => {
152
- const plugin = new Plugin('/test/plugin.ts')
153
- expect(plugin.started).toBe(false)
154
- })
155
-
156
- it('should emit events', async () => {
157
- const plugin = new Plugin('/test/plugin.ts')
158
-
159
- const promise = new Promise<void>((resolve) => {
160
- plugin.on('mounted', () => {
161
- resolve()
162
- })
163
- })
164
-
165
- plugin.emit('mounted', plugin)
166
- await promise
167
- })
168
- })
169
-
170
- describe('Plugin Adapters', () => {
171
- it('should initialize with empty adapters array', () => {
172
- const plugin = new Plugin('/test/plugin.ts')
173
- expect(plugin.adapters).toEqual([])
174
- expect(Array.isArray(plugin.adapters)).toBe(true)
175
- })
176
- })
177
-
178
- describe('Plugin File Info', () => {
179
- it('should store file path', () => {
180
- const filePath = '/test/my-plugin.ts'
181
- const plugin = new Plugin(filePath)
182
- expect(plugin.filePath).toBe(filePath)
183
- })
184
-
185
- it('should initialize file hash as empty string', () => {
186
- const plugin = new Plugin('/test/plugin.ts')
187
- expect(plugin.fileHash).toBe('')
188
- })
189
-
190
- it('should remove timestamp query from file path', () => {
191
- const plugin = new Plugin('/test/plugin.ts?t=1234567890')
192
- expect(plugin.filePath).toBe('/test/plugin.ts')
193
- })
194
- })
195
-
196
- describe('Plugin Children Management', () => {
197
- it('should manage multiple children', () => {
198
- const parent = new Plugin('/test/parent.ts')
199
- const child1 = new Plugin('/test/child1.ts', parent)
200
- const child2 = new Plugin('/test/child2.ts', parent)
201
-
202
- expect(parent.children).toHaveLength(2)
203
- expect(parent.children).toContain(child1)
204
- expect(parent.children).toContain(child2)
205
- })
206
-
207
- it('should allow nested plugin hierarchy', () => {
208
- const root = new Plugin('/test/root.ts')
209
- const level1 = new Plugin('/test/level1.ts', root)
210
- const level2 = new Plugin('/test/level2.ts', level1)
211
- const level3 = new Plugin('/test/level3.ts', level2)
212
-
213
- expect(level3.root).toBe(root)
214
- expect(level2.parent).toBe(level1)
215
- expect(level1.children).toContain(level2)
216
- })
217
- })
218
- })
219
-
220
- describe('Plugin AsyncLocalStorage', () => {
221
- beforeEach(() => {
222
- // 清理 storage
223
- storage.disable()
224
- })
225
-
226
- describe('usePlugin', () => {
227
- it('should create and store plugin in AsyncLocalStorage', () => {
228
- storage.run(undefined, () => {
229
- const plugin = usePlugin()
230
- expect(plugin).toBeInstanceOf(Plugin)
231
- expect(storage.getStore()).toBe(plugin)
232
- })
233
- })
234
-
235
- it('should return same instance when called twice from same file', () => {
236
- storage.run(undefined, () => {
237
- const first = usePlugin()
238
- const second = usePlugin()
239
-
240
- expect(second).toBe(first)
241
- })
242
- })
243
-
244
- it('should handle nested contexts correctly', () => {
245
- storage.run(undefined, () => {
246
- const parent = usePlugin()
247
-
248
- storage.run(undefined, () => {
249
- const nested = usePlugin()
250
- // 嵌套上下文应该创建新的独立插件
251
- expect(nested).toBeInstanceOf(Plugin)
252
- expect(nested).not.toBe(parent)
253
- expect(storage.getStore()).toBe(nested)
254
- })
255
-
256
- // 返回外层上下文后,应该恢复原来的插件
257
- expect(storage.getStore()).toBe(parent)
258
- })
259
- })
260
-
261
- it('should handle storage disabled during execution', () => {
262
- storage.run(undefined, () => {
263
- const plugin = usePlugin()
264
- expect(plugin).toBeInstanceOf(Plugin)
265
-
266
- // 禁用 storage
267
- storage.disable()
268
-
269
- // 再次调用应该创建新插件
270
- const newPlugin = usePlugin()
271
- expect(newPlugin).toBeInstanceOf(Plugin)
272
- // 注意:禁用后 storage 可能仍然在当前 run 上下文中有值
273
- // 只需要验证 usePlugin 仍然能正常工作即可
274
- })
275
- })
276
-
277
- it('should handle errors in nested contexts', () => {
278
- storage.run(undefined, () => {
279
- const parent = usePlugin()
280
-
281
- expect(() => {
282
- storage.run(undefined, () => {
283
- usePlugin()
284
- throw new Error('Test error')
285
- })
286
- }).toThrow('Test error')
287
-
288
- // 错误后,外层上下文应该保持不变
289
- expect(storage.getStore()).toBe(parent)
290
- })
291
- })
292
- })
293
-
294
- describe('getPlugin', () => {
295
- it('should throw error when called outside plugin context', () => {
296
- storage.run(undefined, () => {
297
- expect(() => getPlugin()).toThrow('must be called within a plugin context')
298
- })
299
- })
300
-
301
- it('should return current plugin from storage', () => {
302
- const plugin = new Plugin('/test/plugin.ts')
303
- storage.run(plugin, () => {
304
- const retrieved = getPlugin()
305
- expect(retrieved).toBe(plugin)
306
- })
307
- })
308
- })
309
-
310
- describe('storage', () => {
311
- it('should be an instance of AsyncLocalStorage', () => {
312
- expect(storage).toBeDefined()
313
- expect(typeof storage.run).toBe('function')
314
- expect(typeof storage.getStore).toBe('function')
315
- })
316
- })
317
- })
318
-
319
- describe('Plugin Logger', () => {
320
- it('should have a logger instance', () => {
321
- const plugin = new Plugin('/test/plugin.ts')
322
- expect(plugin.logger).toBeDefined()
323
- expect(typeof plugin.logger.info).toBe('function')
324
- expect(typeof plugin.logger.error).toBe('function')
325
- })
326
-
327
- it('should use plugin name in logger', () => {
328
- const plugin = new Plugin('/test/my-plugin/index.ts')
329
- expect(plugin.logger).toBeDefined()
330
- })
331
- })
332
-
333
- describe('Plugin Disposables', () => {
334
- it('should track disposable functions', () => {
335
- const plugin = new Plugin('/test/plugin.ts')
336
- const middleware = vi.fn(async (msg: any, next: any) => await next())
337
-
338
- const dispose = plugin.addMiddleware(middleware)
339
-
340
- // 验证 dispose 函数存在
341
- expect(typeof dispose).toBe('function')
342
-
343
- // 调用 dispose
344
- dispose()
345
-
346
- // 再次调用应该是安全的
347
- dispose()
348
- })
349
- })
350
-
351
- describe('Plugin Lifecycle Methods', () => {
352
- describe('start', () => {
353
- it('should set started to true', async () => {
354
- const plugin = new Plugin('/test/plugin.ts')
355
- expect(plugin.started).toBe(false)
356
-
357
- await plugin.start()
358
- expect(plugin.started).toBe(true)
359
- })
360
-
361
- it('should not start twice', async () => {
362
- const plugin = new Plugin('/test/plugin.ts')
363
- await plugin.start()
364
- await plugin.start() // 第二次调用应该被忽略
365
- expect(plugin.started).toBe(true)
366
- })
367
-
368
- it('should start children plugins', async () => {
369
- const parent = new Plugin('/test/parent.ts')
370
- const child = new Plugin('/test/child.ts', parent)
371
-
372
- await parent.start()
373
- expect(parent.started).toBe(true)
374
- expect(child.started).toBe(true)
375
- })
376
-
377
- it('should emit mounted event', async () => {
378
- const plugin = new Plugin('/test/plugin.ts')
379
- let emitted = false
380
-
381
- plugin.on('mounted', () => {
382
- emitted = true
383
- })
384
-
385
- await plugin.start()
386
- expect(emitted).toBe(true)
387
- })
388
- })
389
-
390
- describe('stop', () => {
391
- it('should set started to false', async () => {
392
- const plugin = new Plugin('/test/plugin.ts')
393
- await plugin.start()
394
-
395
- await plugin.stop()
396
- expect(plugin.started).toBe(false)
397
- })
398
-
399
- it('should stop children plugins', async () => {
400
- const parent = new Plugin('/test/parent.ts')
401
- const child = new Plugin('/test/child.ts', parent)
402
-
403
- await parent.start()
404
- await parent.stop()
405
-
406
- expect(parent.started).toBe(false)
407
- expect(child.started).toBe(false)
408
- })
409
-
410
- it('should clear children array', async () => {
411
- const parent = new Plugin('/test/parent.ts')
412
- new Plugin('/test/child.ts', parent)
413
-
414
- await parent.start()
415
- await parent.stop()
416
-
417
- expect(parent.children).toEqual([])
418
- })
419
-
420
- it('should clear contexts', async () => {
421
- const plugin = new Plugin('/test/plugin.ts')
422
- plugin.$contexts.set('test', { name: 'test', description: 'test' } as any)
423
-
424
- await plugin.start()
425
- await plugin.stop()
426
- expect(plugin.$contexts.size).toBe(0)
427
- })
428
-
429
- it('should emit dispose event', async () => {
430
- const plugin = new Plugin('/test/plugin.ts')
431
- let emitted = false
432
-
433
- plugin.on('dispose', () => {
434
- emitted = true
435
- })
436
-
437
- await plugin.start()
438
- await plugin.stop()
439
- expect(emitted).toBe(true)
440
- })
441
-
442
- it('should call disposables', async () => {
443
- const plugin = new Plugin('/test/plugin.ts')
444
- let called = false
445
-
446
- plugin.onDispose(() => {
447
- called = true
448
- })
449
-
450
- await plugin.start()
451
- await plugin.stop()
452
- expect(called).toBe(true)
453
- })
454
-
455
- it('should not stop if not started', async () => {
456
- const plugin = new Plugin('/test/plugin.ts')
457
- await plugin.stop() // 应该直接返回
458
- expect(plugin.started).toBe(false)
459
- })
460
- })
461
-
462
- describe('onMounted', () => {
463
- it('should register mounted callback', async () => {
464
- const plugin = new Plugin('/test/plugin.ts')
465
- let called = false
466
-
467
- plugin.onMounted(() => {
468
- called = true
469
- })
470
-
471
- await plugin.start()
472
- expect(called).toBe(true)
473
- })
474
- })
475
-
476
- describe('onDispose', () => {
477
- it('should register dispose callback', async () => {
478
- const plugin = new Plugin('/test/plugin.ts')
479
- let called = false
480
-
481
- const unregister = plugin.onDispose(() => {
482
- called = true
483
- })
484
-
485
- await plugin.start()
486
- await plugin.stop()
487
- expect(called).toBe(true)
488
- expect(typeof unregister).toBe('function')
489
- })
490
-
491
- it('should allow unregistering callback', async () => {
492
- const plugin = new Plugin('/test/plugin.ts')
493
- let called = false
494
-
495
- const unregister = plugin.onDispose(() => {
496
- called = true
497
- })
498
-
499
- unregister() // 取消注册
500
- await plugin.start()
501
- await plugin.stop()
502
- expect(called).toBe(false)
503
- })
504
- })
505
- })
506
-
507
- describe('Plugin Event Broadcasting', () => {
508
- describe('dispatch', () => {
509
- it('should dispatch to parent', async () => {
510
- const parent = new Plugin('/test/parent.ts')
511
- const child = new Plugin('/test/child.ts', parent)
512
-
513
- let received = false
514
- parent.on('mounted', () => {
515
- received = true
516
- })
517
-
518
- await child.dispatch('mounted')
519
- expect(received).toBe(true)
520
- })
521
-
522
- it('should broadcast if no parent', async () => {
523
- const plugin = new Plugin('/test/plugin.ts')
524
- let received = false
525
-
526
- plugin.on('mounted', () => {
527
- received = true
528
- })
529
-
530
- await plugin.dispatch('mounted')
531
- expect(received).toBe(true)
532
- })
533
- })
534
-
535
- describe('broadcast', () => {
536
- it('should broadcast to children', async () => {
537
- const parent = new Plugin('/test/parent.ts')
538
- const child = new Plugin('/test/child.ts', parent)
539
-
540
- let childReceived = false
541
- child.on('mounted', () => {
542
- childReceived = true
543
- })
544
-
545
- await parent.broadcast('mounted')
546
- expect(childReceived).toBe(true)
547
- })
548
-
549
- it('should call own listeners', async () => {
550
- const plugin = new Plugin('/test/plugin.ts')
551
- let called = false
552
-
553
- plugin.on('mounted', () => {
554
- called = true
555
- })
556
-
557
- await plugin.broadcast('mounted')
558
- expect(called).toBe(true)
559
- })
560
- })
561
- })
562
-
563
- describe('Plugin Context mounted Behavior', () => {
564
- it('should always call mounted callback even when value is preset', async () => {
565
- const plugin = new Plugin('/test/plugin.ts')
566
- let mountedCalled = false
567
-
568
- plugin.provide({
569
- name: 'test-ctx' as any,
570
- description: 'Test context with both value and mounted',
571
- value: { original: true },
572
- mounted(_p: any) {
573
- mountedCalled = true
574
- return { fromMounted: true }
575
- },
576
- } as any)
577
-
578
- await plugin.start()
579
- expect(mountedCalled).toBe(true)
580
- })
581
-
582
- it('should NOT overwrite preset value with mounted return', async () => {
583
- const plugin = new Plugin('/test/plugin.ts')
584
- const presetValue = { original: true }
585
-
586
- plugin.provide({
587
- name: 'test-ctx' as any,
588
- description: 'Test context with both value and mounted',
589
- value: presetValue,
590
- mounted(_p: any) {
591
- return { fromMounted: true }
592
- },
593
- } as any)
594
-
595
- await plugin.start()
596
- const injected = plugin.inject('test-ctx' as any)
597
- expect(injected).toBe(presetValue)
598
- expect(injected).toEqual({ original: true })
599
- })
600
-
601
- it('should assign mounted return value when context.value is not set', async () => {
602
- const plugin = new Plugin('/test/plugin.ts')
603
- const mountedValue = { fromMounted: true }
604
-
605
- plugin.provide({
606
- name: 'test-ctx' as any,
607
- description: 'Test context with mounted only',
608
- mounted(_p: any) {
609
- return mountedValue
610
- },
611
- } as any)
612
-
613
- await plugin.start()
614
- const injected = plugin.inject('test-ctx' as any)
615
- expect(injected).toBe(mountedValue)
616
- })
617
-
618
- it('mounted side effects should run even when value is preset', async () => {
619
- const plugin = new Plugin('/test/plugin.ts')
620
- let sideEffectCounter = 0
621
-
622
- plugin.provide({
623
- name: 'test-ctx' as any,
624
- description: 'Test mounted side effects',
625
- value: { data: 'preset' },
626
- mounted(_p: any) {
627
- sideEffectCounter++
628
- return { data: 'mounted' }
629
- },
630
- } as any)
631
-
632
- await plugin.start()
633
- expect(sideEffectCounter).toBe(1)
634
- // value should remain preset
635
- expect(plugin.inject('test-ctx' as any)).toEqual({ data: 'preset' })
636
- })
637
- })
638
-
639
- describe('Plugin Context Management', () => {
640
- describe('provide', () => {
641
- it('should register context', () => {
642
- const plugin = new Plugin('/test/plugin.ts')
643
- const context = {
644
- name: 'test',
645
- description: 'Test context',
646
- value: { test: true }
647
- } as any
648
-
649
- plugin.provide(context)
650
- expect(plugin.$contexts.has('test')).toBe(true)
651
- })
652
-
653
- it('should return plugin instance for chaining', () => {
654
- const plugin = new Plugin('/test/plugin.ts')
655
- const context = {
656
- name: 'test',
657
- description: 'Test context'
658
- } as any
659
-
660
- const result = plugin.provide(context)
661
- expect(result).toBe(plugin)
662
- })
663
- })
664
-
665
- describe('inject', () => {
666
- it('should inject context value', () => {
667
- const plugin = new Plugin('/test/plugin.ts')
668
- const context = {
669
- name: 'test',
670
- description: 'Test context',
671
- value: { data: 'test-value' }
672
- } as any
673
-
674
- plugin.$contexts.set('test', context)
675
- const injected = plugin.inject('test' as any)
676
- expect(injected).toEqual({ data: 'test-value' })
677
- })
678
-
679
- it('should return undefined for non-existent context', () => {
680
- const plugin = new Plugin('/test/plugin.ts')
681
- const injected = plugin.inject('non-existent' as any)
682
- expect(injected).toBeUndefined()
683
- })
684
- })
685
-
686
- describe('contextIsReady', () => {
687
- it('should return true if context exists', () => {
688
- const plugin = new Plugin('/test/plugin.ts')
689
- const context = {
690
- name: 'test',
691
- description: 'Test context',
692
- value: { test: true }
693
- } as any
694
-
695
- plugin.$contexts.set('test', context)
696
- expect(plugin.contextIsReady('test' as any)).toBe(true)
697
- })
698
-
699
- it('should return false if context does not exist', () => {
700
- const plugin = new Plugin('/test/plugin.ts')
701
- expect(plugin.contextIsReady('non-existent' as any)).toBe(false)
702
- })
703
- })
704
- })
705
-
706
- describe('Plugin Features', () => {
707
- it('should return empty features by default', () => {
708
- const plugin = new Plugin('/test/plugin.ts')
709
- const features = plugin.getFeatures()
710
-
711
- expect(features).toEqual([])
712
- })
713
-
714
- it('should include middleware in getFeatures', () => {
715
- const plugin = new Plugin('/test/plugin.ts')
716
- plugin.addMiddleware(async (msg: any, next: any) => await next())
717
-
718
- const features = plugin.getFeatures()
719
- const middlewareFeature = features.find(f => f.name === 'middleware')
720
- expect(middlewareFeature).toBeDefined()
721
- expect(middlewareFeature!.count).toBeGreaterThan(0)
722
- })
723
- })
724
-
725
- describe('Plugin Info', () => {
726
- it('should return plugin info', () => {
727
- const plugin = new Plugin('/test/my-plugin.ts')
728
- const info = plugin.info()
729
-
730
- expect(info).toHaveProperty(plugin.name)
731
- expect(info[plugin.name]).toHaveProperty('features')
732
- expect(info[plugin.name]).toHaveProperty('children')
733
- })
734
-
735
- it('should include children info', () => {
736
- const parent = new Plugin('/test/parent.ts')
737
- new Plugin('/test/child.ts', parent)
738
-
739
- const info = parent.info()
740
- expect(info[parent.name].children).toHaveLength(1)
741
- })
742
- })
743
-
744
- describe('Plugin Method Binding', () => {
745
- it('should bind core methods', () => {
746
- const plugin = new Plugin('/test/plugin.ts')
747
-
748
- // 解构后方法仍然可用
749
- const { start, stop, provide } = plugin
750
-
751
- expect(typeof start).toBe('function')
752
- expect(typeof stop).toBe('function')
753
- expect(typeof provide).toBe('function')
754
- })
755
-
756
- it('should not bind methods twice', () => {
757
- const plugin = new Plugin('/test/plugin.ts')
758
- plugin.$bindMethods()
759
- plugin.$bindMethods() // 第二次调用应该被忽略
760
-
761
- expect(plugin.started).toBe(false)
762
- })
763
- })
764
-
765
- describe('Plugin Static Methods and Utilities', () => {
766
- it('should export defineContext function', () => {
767
- expect(typeof defineContext).toBe('function')
768
- })
769
-
770
- it('defineContext should return the options as-is', () => {
771
- const context = {
772
- name: 'test' as const,
773
- description: 'Test context',
774
- value: 'test-value'
775
- }
776
- const result = defineContext(context)
777
- expect(result).toEqual(context)
778
- })
779
- })