@zhin.js/core 1.0.57 → 1.1.2

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 (126) 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/ai/index.d.ts +2 -0
  6. package/lib/ai/index.d.ts.map +1 -1
  7. package/lib/ai/index.js +1 -0
  8. package/lib/ai/index.js.map +1 -1
  9. package/lib/built/adapter-process.d.ts +0 -4
  10. package/lib/built/adapter-process.d.ts.map +1 -1
  11. package/lib/built/adapter-process.js +0 -95
  12. package/lib/built/adapter-process.js.map +1 -1
  13. package/lib/built/agent-preset.d.ts +2 -0
  14. package/lib/built/agent-preset.d.ts.map +1 -1
  15. package/lib/built/agent-preset.js +4 -0
  16. package/lib/built/agent-preset.js.map +1 -1
  17. package/lib/built/command.d.ts +4 -0
  18. package/lib/built/command.d.ts.map +1 -1
  19. package/lib/built/command.js +6 -0
  20. package/lib/built/command.js.map +1 -1
  21. package/lib/built/component.d.ts.map +1 -1
  22. package/lib/built/component.js +1 -0
  23. package/lib/built/component.js.map +1 -1
  24. package/lib/built/dispatcher.d.ts.map +1 -1
  25. package/lib/built/dispatcher.js +0 -13
  26. package/lib/built/dispatcher.js.map +1 -1
  27. package/lib/built/message-filter.d.ts +2 -0
  28. package/lib/built/message-filter.d.ts.map +1 -1
  29. package/lib/built/message-filter.js +5 -0
  30. package/lib/built/message-filter.js.map +1 -1
  31. package/lib/built/skill.d.ts +11 -0
  32. package/lib/built/skill.d.ts.map +1 -1
  33. package/lib/built/skill.js +14 -0
  34. package/lib/built/skill.js.map +1 -1
  35. package/lib/built/tool.d.ts +11 -44
  36. package/lib/built/tool.d.ts.map +1 -1
  37. package/lib/built/tool.js +14 -353
  38. package/lib/built/tool.js.map +1 -1
  39. package/lib/plugin.d.ts +1 -25
  40. package/lib/plugin.d.ts.map +1 -1
  41. package/lib/plugin.js +1 -77
  42. package/lib/plugin.js.map +1 -1
  43. package/lib/types.d.ts +0 -25
  44. package/lib/types.d.ts.map +1 -1
  45. package/package.json +10 -7
  46. package/CHANGELOG.md +0 -538
  47. package/REFACTORING_COMPLETE.md +0 -178
  48. package/REFACTORING_STATUS.md +0 -263
  49. package/src/adapter.ts +0 -275
  50. package/src/ai/index.ts +0 -52
  51. package/src/ai/providers/anthropic.ts +0 -379
  52. package/src/ai/providers/base.ts +0 -175
  53. package/src/ai/providers/index.ts +0 -13
  54. package/src/ai/providers/ollama.ts +0 -302
  55. package/src/ai/providers/openai.ts +0 -174
  56. package/src/ai/types.ts +0 -348
  57. package/src/bot.ts +0 -37
  58. package/src/built/adapter-process.ts +0 -177
  59. package/src/built/agent-preset.ts +0 -136
  60. package/src/built/ai-trigger.ts +0 -259
  61. package/src/built/command.ts +0 -108
  62. package/src/built/common-adapter-tools.ts +0 -242
  63. package/src/built/component.ts +0 -130
  64. package/src/built/config.ts +0 -335
  65. package/src/built/cron.ts +0 -156
  66. package/src/built/database.ts +0 -134
  67. package/src/built/dispatcher.ts +0 -496
  68. package/src/built/login-assist.ts +0 -131
  69. package/src/built/message-filter.ts +0 -390
  70. package/src/built/permission.ts +0 -151
  71. package/src/built/schema-feature.ts +0 -190
  72. package/src/built/skill.ts +0 -221
  73. package/src/built/tool.ts +0 -948
  74. package/src/command.ts +0 -87
  75. package/src/component.ts +0 -565
  76. package/src/cron.ts +0 -4
  77. package/src/errors.ts +0 -46
  78. package/src/feature.ts +0 -7
  79. package/src/index.ts +0 -53
  80. package/src/jsx-dev-runtime.ts +0 -2
  81. package/src/jsx-runtime.ts +0 -12
  82. package/src/jsx.ts +0 -135
  83. package/src/message.ts +0 -48
  84. package/src/models/system-log.ts +0 -20
  85. package/src/models/user.ts +0 -15
  86. package/src/notice.ts +0 -98
  87. package/src/plugin.ts +0 -896
  88. package/src/prompt.ts +0 -293
  89. package/src/request.ts +0 -95
  90. package/src/scheduler/index.ts +0 -19
  91. package/src/scheduler/scheduler.ts +0 -372
  92. package/src/scheduler/types.ts +0 -74
  93. package/src/tool-zod.ts +0 -115
  94. package/src/types-generator.ts +0 -78
  95. package/src/types.ts +0 -505
  96. package/src/utils.ts +0 -227
  97. package/tests/adapter.test.ts +0 -638
  98. package/tests/ai/ai-trigger.test.ts +0 -368
  99. package/tests/ai/providers.integration.test.ts +0 -227
  100. package/tests/ai/setup.ts +0 -308
  101. package/tests/ai/tool.test.ts +0 -800
  102. package/tests/bot.test.ts +0 -151
  103. package/tests/command.test.ts +0 -737
  104. package/tests/component-new.test.ts +0 -361
  105. package/tests/config.test.ts +0 -372
  106. package/tests/cron.test.ts +0 -82
  107. package/tests/dispatcher.test.ts +0 -293
  108. package/tests/errors.test.ts +0 -21
  109. package/tests/expression-evaluation.test.ts +0 -258
  110. package/tests/features-builtin.test.ts +0 -191
  111. package/tests/jsx-runtime.test.ts +0 -45
  112. package/tests/jsx.test.ts +0 -319
  113. package/tests/message-filter.test.ts +0 -566
  114. package/tests/message.test.ts +0 -402
  115. package/tests/notice.test.ts +0 -198
  116. package/tests/plugin.test.ts +0 -779
  117. package/tests/prompt.test.ts +0 -78
  118. package/tests/redos-protection.test.ts +0 -198
  119. package/tests/request.test.ts +0 -221
  120. package/tests/schema.test.ts +0 -248
  121. package/tests/skill-feature.test.ts +0 -179
  122. package/tests/test-utils.ts +0 -59
  123. package/tests/tool-feature.test.ts +0 -254
  124. package/tests/types.test.ts +0 -162
  125. package/tests/utils.test.ts +0 -135
  126. 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
- })