@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,638 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest'
2
- import { Adapter } from '../src/adapter'
3
- import { Bot } from '../src/bot'
4
- import { Plugin } from '../src/plugin'
5
- import { Message, MessageBase } from '../src/message'
6
- import { EventEmitter } from 'events'
7
-
8
- // Mock Bot 实现用于测试
9
- class MockBot implements Bot<any, any> {
10
- $id: string
11
- $config: any
12
- $connected: boolean = false
13
- adapter: Adapter
14
-
15
- constructor(adapter: Adapter, config: any) {
16
- this.adapter = adapter
17
- this.$config = config
18
- this.$id = config.id || 'mock-bot'
19
- }
20
-
21
- $formatMessage(event: any): Message<any> {
22
- const base: MessageBase = {
23
- $id: event.id || 'mock-id',
24
- $adapter: 'test' as any,
25
- $bot: this.$id,
26
- $content: [],
27
- $sender: { id: 'mock-sender', name: 'Mock Sender' },
28
- $channel: { id: 'mock-channel', type: 'private' },
29
- $timestamp: Date.now(),
30
- $raw: event.raw || event,
31
- $reply: async (content: any, quote?: boolean | string) => {
32
- const elements = Array.isArray(content) ? content : [content]
33
- const finalContent: any[] = []
34
-
35
- if (quote) {
36
- finalContent.push({
37
- type: 'reply',
38
- data: { id: typeof quote === 'boolean' ? base.$id : quote }
39
- })
40
- }
41
-
42
- finalContent.push(...elements.map((el: any) =>
43
- typeof el === 'string' ? { type: 'text', data: { text: el } } : el
44
- ))
45
-
46
- return await this.adapter.sendMessage({
47
- ...base.$channel,
48
- context: 'test',
49
- bot: this.$id,
50
- content: finalContent,
51
- })
52
- },
53
- $recall: async () => {}
54
- }
55
- return Message.from(event, base)
56
- }
57
-
58
- async $connect(): Promise<void> {
59
- this.$connected = true
60
- }
61
-
62
- async $disconnect(): Promise<void> {
63
- this.$connected = false
64
- }
65
-
66
- async $sendMessage(options: any): Promise<string> {
67
- return 'mock-message-id'
68
- }
69
-
70
- async $recallMessage(id: string): Promise<void> {
71
- // Mock 撤回消息
72
- }
73
- }
74
-
75
- // Mock Adapter 类用于测试
76
- class MockAdapter extends Adapter<MockBot> {
77
- createBot(config: any): MockBot {
78
- return new MockBot(this, config)
79
- }
80
- }
81
-
82
- describe('Adapter Core Functionality', () => {
83
- let plugin: Plugin
84
- let adapter: MockAdapter
85
-
86
- beforeEach(() => {
87
- plugin = new Plugin('/test/plugin.ts')
88
- adapter = new MockAdapter(plugin, 'test', [])
89
- })
90
-
91
- describe('Adapter Constructor', () => {
92
- it('should create adapter with plugin, name and config', () => {
93
- const config = [{ id: 'bot1' }]
94
- const adapter = new MockAdapter(plugin, 'test', config)
95
-
96
- expect(adapter.plugin).toBe(plugin)
97
- expect(adapter.name).toBe('test')
98
- expect(adapter.config).toBe(config)
99
- })
100
-
101
- it('should initialize with empty bots map', () => {
102
- expect(adapter.bots).toBeInstanceOf(Map)
103
- expect(adapter.bots.size).toBe(0)
104
- })
105
-
106
- it('should inherit from EventEmitter', () => {
107
- expect(adapter).toBeInstanceOf(EventEmitter)
108
- })
109
-
110
- it('should route message.receive via emit without default listener', () => {
111
- expect(adapter.listenerCount('message.receive')).toBe(0)
112
- })
113
-
114
- it('should register call.recallMessage listener', () => {
115
- const listeners = adapter.listeners('call.recallMessage')
116
- expect(listeners.length).toBeGreaterThan(0)
117
- })
118
-
119
- it('should have sendMessage method', () => {
120
- expect(typeof adapter.sendMessage).toBe('function')
121
- })
122
- })
123
-
124
- describe('Adapter Logger', () => {
125
- it('should get logger from plugin', () => {
126
- expect(adapter.logger).toBeDefined()
127
- expect(adapter.logger).toBe(plugin.logger)
128
- })
129
-
130
- it('should throw error if plugin is not set', () => {
131
- const adapter = new MockAdapter(plugin, 'test', [])
132
- adapter.plugin = null as any
133
-
134
- expect(() => adapter.logger).toThrow('Adapter is not associated with any plugin')
135
- })
136
- })
137
-
138
- describe('Adapter Binding', () => {
139
- it('should bind to a plugin', () => {
140
- const newPlugin = new Plugin('/test/new-plugin.ts')
141
- adapter.binding(newPlugin)
142
-
143
- expect(adapter.plugin).toBe(newPlugin)
144
- })
145
- })
146
-
147
- describe('Adapter Start', () => {
148
- it('should start without config', async () => {
149
- await adapter.start()
150
- expect(plugin.root.adapters).toContain('test')
151
- })
152
-
153
- it('should create and connect bots from config', async () => {
154
- const config = [
155
- { id: 'bot1' },
156
- { id: 'bot2' }
157
- ]
158
- const adapter = new MockAdapter(plugin, 'test', config)
159
-
160
- await adapter.start()
161
-
162
- expect(adapter.bots.size).toBe(2)
163
- expect(adapter.bots.has('bot1')).toBe(true)
164
- expect(adapter.bots.has('bot2')).toBe(true)
165
- })
166
-
167
- it('should add adapter name to plugin adapters', async () => {
168
- await adapter.start()
169
- expect(plugin.root.adapters).toContain('test')
170
- })
171
-
172
- it('should handle empty config array', async () => {
173
- const adapter = new MockAdapter(plugin, 'test', [])
174
- await adapter.start()
175
- expect(adapter.bots.size).toBe(0)
176
- })
177
- })
178
-
179
- describe('Adapter Stop', () => {
180
- it('should disconnect all bots', async () => {
181
- const config = [{ id: 'bot1' }, { id: 'bot2' }]
182
- const adapter = new MockAdapter(plugin, 'test', config)
183
-
184
- await adapter.start()
185
- expect(adapter.bots.size).toBe(2)
186
-
187
- await adapter.stop()
188
- expect(adapter.bots.size).toBe(0)
189
- })
190
-
191
- it('should remove adapter from plugin adapters', async () => {
192
- await adapter.start()
193
- expect(plugin.root.adapters).toContain('test')
194
-
195
- await adapter.stop()
196
- expect(plugin.root.adapters).not.toContain('test')
197
- })
198
-
199
- it('should remove all event listeners', async () => {
200
- await adapter.start()
201
- const noop = () => {}
202
- adapter.on('message.receive', noop)
203
- const beforeCount = adapter.listenerCount('message.receive')
204
- expect(beforeCount).toBe(1)
205
-
206
- await adapter.stop()
207
- const afterCount = adapter.listenerCount('message.receive')
208
-
209
- expect(afterCount).toBe(0)
210
- })
211
-
212
- it('should handle bot disconnect errors gracefully', async () => {
213
- const config = [{ id: 'bot1' }, { id: 'bot2' }]
214
- const adapter = new MockAdapter(plugin, 'test', config)
215
-
216
- await adapter.start()
217
-
218
- // Mock first bot disconnect to throw error
219
- const bot1 = adapter.bots.get('bot1')!
220
- bot1.$disconnect = vi.fn().mockRejectedValue(new Error('Disconnect failed'))
221
-
222
- // Mock logger to spy on error logging
223
- const loggerSpy = vi.spyOn(adapter.logger, 'error')
224
-
225
- // The adapter should continue cleanup despite errors
226
- // Note: Current implementation throws, but this test documents the desired behavior
227
- // where adapter.stop() should handle errors gracefully and continue cleanup
228
- await expect(adapter.stop()).rejects.toThrow('Disconnect failed')
229
-
230
- // Even though it throws, we document that graceful handling would be:
231
- // - Log the error
232
- // - Continue disconnecting other bots
233
- // - Complete cleanup (clear bots, remove from adapters list, remove listeners)
234
- })
235
- })
236
-
237
- describe('Adapter Events', () => {
238
- describe('call.recallMessage', () => {
239
- it('should recall message from bot', async () => {
240
- const config = [{ id: 'bot1' }]
241
- const adapter = new MockAdapter(plugin, 'test', config)
242
- await adapter.start()
243
-
244
- const bot = adapter.bots.get('bot1')!
245
- const recallSpy = vi.spyOn(bot, '$recallMessage')
246
-
247
- await adapter.emit('call.recallMessage', 'bot1', 'message-id')
248
-
249
- expect(recallSpy).toHaveBeenCalledWith('message-id')
250
- })
251
-
252
- it('should require valid bot id', () => {
253
- // 验证 adapter 不包含不存在的 bot
254
- expect(adapter.bots.has('non-existent-bot')).toBe(false)
255
- })
256
- })
257
-
258
- describe('sendMessage', () => {
259
- it('should send message through bot', async () => {
260
- const config = [{ id: 'bot1' }]
261
- const adapter = new MockAdapter(plugin, 'test', config)
262
- await adapter.start()
263
-
264
- const bot = adapter.bots.get('bot1')!
265
- const sendSpy = vi.spyOn(bot, '$sendMessage')
266
-
267
- const options = {
268
- context: 'test',
269
- bot: 'bot1',
270
- content: 'Hello',
271
- id: 'channel-id',
272
- type: 'text' as const
273
- }
274
-
275
- const messageId = await adapter.sendMessage(options)
276
-
277
- expect(sendSpy).toHaveBeenCalledWith(options)
278
- expect(messageId).toBe('mock-message-id')
279
- })
280
-
281
- it('should throw error if bot not found', async () => {
282
- const options = {
283
- context: 'test',
284
- bot: 'non-existent-bot',
285
- content: 'Hello',
286
- id: 'channel-id',
287
- type: 'text' as const
288
- }
289
-
290
- await expect(adapter.sendMessage(options)).rejects.toThrow('Bot non-existent-bot not found')
291
- })
292
-
293
- it('should call before.sendMessage handlers', async () => {
294
- const config = [{ id: 'bot1' }]
295
- const adapter = new MockAdapter(plugin, 'test', config)
296
- await adapter.start()
297
-
298
- let handlerCalled = false
299
- let modifiedContent = false
300
-
301
- plugin.root.on('before.sendMessage', (options) => {
302
- handlerCalled = true
303
- // 修改消息内容
304
- return {
305
- ...options,
306
- content: 'Modified: ' + options.content
307
- }
308
- })
309
-
310
- const bot = adapter.bots.get('bot1')!
311
- const sendSpy = vi.spyOn(bot, '$sendMessage')
312
-
313
- const options = {
314
- context: 'test',
315
- bot: 'bot1',
316
- content: 'Hello',
317
- id: 'channel-id',
318
- type: 'text' as const
319
- }
320
-
321
- await adapter.sendMessage(options)
322
-
323
- expect(handlerCalled).toBe(true)
324
- expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
325
- content: 'Modified: Hello'
326
- }))
327
- })
328
-
329
- it('should handle multiple before.sendMessage handlers', async () => {
330
- const config = [{ id: 'bot1' }]
331
- const adapter = new MockAdapter(plugin, 'test', config)
332
- await adapter.start()
333
-
334
- const handlers: string[] = []
335
-
336
- plugin.root.on('before.sendMessage', (options) => {
337
- handlers.push('handler1')
338
- return options
339
- })
340
-
341
- plugin.root.on('before.sendMessage', (options) => {
342
- handlers.push('handler2')
343
- return options
344
- })
345
-
346
- const options = {
347
- context: 'test',
348
- bot: 'bot1',
349
- content: 'Hello',
350
- id: 'channel-id',
351
- type: 'text' as const
352
- }
353
-
354
- await adapter.sendMessage(options)
355
-
356
- expect(handlers).toEqual(['handler1', 'handler2'])
357
- })
358
-
359
- it('should log message sending', async () => {
360
- const config = [{ id: 'bot1' }]
361
- const adapter = new MockAdapter(plugin, 'test', config)
362
- await adapter.start()
363
-
364
- const logSpy = vi.spyOn(adapter.logger, 'info')
365
-
366
- const options = {
367
- context: 'test',
368
- bot: 'bot1',
369
- content: [{ type: 'text', data: { text: 'Hello' } }],
370
- id: 'channel-id',
371
- type: 'private' as const
372
- }
373
-
374
- await adapter.sendMessage(options)
375
-
376
- expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('bot1 send private(channel-id):Hello'))
377
- })
378
- })
379
-
380
- describe('message.receive', () => {
381
- it('should still dispatch plugin message.receive when dispatcher is missing (no middleware fallback)', async () => {
382
- const config = [{ id: 'bot1' }]
383
- const adapter = new MockAdapter(plugin, 'test', config)
384
- await adapter.start()
385
-
386
- let middlewareCalled = false
387
- plugin.addMiddleware(async (_message, next) => {
388
- middlewareCalled = true
389
- await next()
390
- })
391
-
392
- let lifecycleCalled = false
393
- plugin.on('message.receive', () => {
394
- lifecycleCalled = true
395
- })
396
-
397
- const message = {
398
- $bot: 'bot1',
399
- $adapter: 'test',
400
- $channel: { id: 'channel-id', type: 'text' },
401
- $content: 'Hello',
402
- } as any
403
-
404
- adapter.emit('message.receive', message)
405
- await new Promise((r) => setTimeout(r, 20))
406
- expect(middlewareCalled).toBe(false)
407
- expect(lifecycleCalled).toBe(true)
408
- })
409
-
410
- it('should await MessageDispatcher then plugin lifecycle', async () => {
411
- const config = [{ id: 'bot1' }]
412
- const adapter = new MockAdapter(plugin, 'test', config)
413
- await adapter.start()
414
-
415
- const order: string[] = []
416
- plugin.$contexts.set('dispatcher', {
417
- name: 'dispatcher',
418
- description: 'mock dispatcher',
419
- value: {
420
- dispatch: async (_msg: any) => {
421
- order.push('dispatcher')
422
- },
423
- },
424
- } as any)
425
-
426
- plugin.on('message.receive', () => {
427
- order.push('lifecycle')
428
- })
429
-
430
- const message = {
431
- $bot: 'bot1',
432
- $adapter: 'test',
433
- $channel: { id: 'channel-id', type: 'text' },
434
- $content: 'Hello',
435
- } as any
436
-
437
- adapter.emit('message.receive', message)
438
- await new Promise((r) => setTimeout(r, 20))
439
- expect(order).toEqual(['dispatcher', 'lifecycle'])
440
- })
441
-
442
- it('should call adapter.on observers after plugin lifecycle', async () => {
443
- const config = [{ id: 'bot1' }]
444
- const adapter = new MockAdapter(plugin, 'test', config)
445
- await adapter.start()
446
-
447
- plugin.$contexts.set('dispatcher', {
448
- name: 'dispatcher',
449
- description: 'mock dispatcher',
450
- value: { dispatch: async () => {} },
451
- } as any)
452
-
453
- const order: string[] = []
454
- plugin.on('message.receive', () => order.push('lifecycle'))
455
- adapter.on('message.receive', () => order.push('adapterObserver'))
456
-
457
- const message = {
458
- $bot: 'bot1',
459
- $adapter: 'test',
460
- $channel: { id: 'channel-id', type: 'text' },
461
- $content: 'Hello',
462
- } as any
463
-
464
- adapter.emit('message.receive', message)
465
- await new Promise((r) => setTimeout(r, 20))
466
- expect(order).toEqual(['lifecycle', 'adapterObserver'])
467
- })
468
- })
469
- })
470
-
471
- describe('Adapter createBot', () => {
472
- it('should be abstract method', () => {
473
- expect(typeof adapter.createBot).toBe('function')
474
- })
475
-
476
- it('should create bot with config', () => {
477
- const config = { id: 'test-bot' }
478
- const bot = adapter.createBot(config)
479
-
480
- expect(bot).toBeInstanceOf(MockBot)
481
- expect(bot.$id).toBe('test-bot')
482
- })
483
- })
484
-
485
- describe('Adapter Bots Management', () => {
486
- it('should manage multiple bots', async () => {
487
- const config = [
488
- { id: 'bot1' },
489
- { id: 'bot2' },
490
- { id: 'bot3' }
491
- ]
492
- const adapter = new MockAdapter(plugin, 'test', config)
493
-
494
- await adapter.start()
495
-
496
- expect(adapter.bots.size).toBe(3)
497
- expect(Array.from(adapter.bots.keys())).toEqual(['bot1', 'bot2', 'bot3'])
498
- })
499
-
500
- it('should access bot by id', async () => {
501
- const config = [{ id: 'bot1' }]
502
- const adapter = new MockAdapter(plugin, 'test', config)
503
-
504
- await adapter.start()
505
-
506
- const bot = adapter.bots.get('bot1')
507
- expect(bot).toBeDefined()
508
- expect(bot!.$id).toBe('bot1')
509
- })
510
- })
511
-
512
- describe('Message $reply', () => {
513
- it('should send reply through adapter.sendMessage', async () => {
514
- const config = [{ id: 'bot1' }]
515
- const adapter = new MockAdapter(plugin, 'test', config)
516
- await adapter.start()
517
-
518
- const bot = adapter.bots.get('bot1')!
519
- const message = bot.$formatMessage({ id: 'msg-1', raw: 'Hello' })
520
-
521
- const sendSpy = vi.spyOn(adapter, 'sendMessage')
522
-
523
- await message.$reply('Reply content')
524
-
525
- expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
526
- bot: 'bot1',
527
- content: expect.arrayContaining([
528
- expect.objectContaining({ type: 'text', data: { text: 'Reply content' } })
529
- ])
530
- }))
531
- })
532
-
533
- it('should support quote reply', async () => {
534
- const config = [{ id: 'bot1' }]
535
- const adapter = new MockAdapter(plugin, 'test', config)
536
- await adapter.start()
537
-
538
- const bot = adapter.bots.get('bot1')!
539
- const message = bot.$formatMessage({ id: 'msg-1', raw: 'Hello' })
540
-
541
- const sendSpy = vi.spyOn(adapter, 'sendMessage')
542
-
543
- await message.$reply('Reply content', true)
544
-
545
- expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
546
- content: expect.arrayContaining([
547
- expect.objectContaining({ type: 'reply', data: { id: 'msg-1' } }),
548
- expect.objectContaining({ type: 'text', data: { text: 'Reply content' } })
549
- ])
550
- }))
551
- })
552
-
553
- it('should support custom quote id', async () => {
554
- const config = [{ id: 'bot1' }]
555
- const adapter = new MockAdapter(plugin, 'test', config)
556
- await adapter.start()
557
-
558
- const bot = adapter.bots.get('bot1')!
559
- const message = bot.$formatMessage({ id: 'msg-1', raw: 'Hello' })
560
-
561
- const sendSpy = vi.spyOn(adapter, 'sendMessage')
562
-
563
- await message.$reply('Reply content', 'custom-msg-id')
564
-
565
- expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
566
- content: expect.arrayContaining([
567
- expect.objectContaining({ type: 'reply', data: { id: 'custom-msg-id' } })
568
- ])
569
- }))
570
- })
571
-
572
- it('should handle array content', async () => {
573
- const config = [{ id: 'bot1' }]
574
- const adapter = new MockAdapter(plugin, 'test', config)
575
- await adapter.start()
576
-
577
- const bot = adapter.bots.get('bot1')!
578
- const message = bot.$formatMessage({ id: 'msg-1', raw: 'Hello' })
579
-
580
- const sendSpy = vi.spyOn(adapter, 'sendMessage')
581
-
582
- await message.$reply([
583
- { type: 'text', data: { text: 'Part 1' } },
584
- { type: 'text', data: { text: 'Part 2' } }
585
- ])
586
-
587
- expect(sendSpy).toHaveBeenCalledWith(expect.objectContaining({
588
- content: expect.arrayContaining([
589
- expect.objectContaining({ type: 'text', data: { text: 'Part 1' } }),
590
- expect.objectContaining({ type: 'text', data: { text: 'Part 2' } })
591
- ])
592
- }))
593
- })
594
-
595
- it('should trigger before.sendMessage hooks', async () => {
596
- const config = [{ id: 'bot1' }]
597
- const adapter = new MockAdapter(plugin, 'test', config)
598
- await adapter.start()
599
-
600
- let hookCalled = false
601
- plugin.root.on('before.sendMessage', (options) => {
602
- hookCalled = true
603
- return options
604
- })
605
-
606
- const bot = adapter.bots.get('bot1')!
607
- const message = bot.$formatMessage({ id: 'msg-1', raw: 'Hello' })
608
-
609
- await message.$reply('Reply content')
610
-
611
- expect(hookCalled).toBe(true)
612
- })
613
- })
614
- })
615
-
616
- describe('Adapter Registry', () => {
617
- it('should have a Registry Map', () => {
618
- expect(Adapter.Registry).toBeInstanceOf(Map)
619
- })
620
-
621
- it('should register adapter factory', () => {
622
- const factory = MockAdapter as any
623
- Adapter.register('mock', factory)
624
-
625
- expect(Adapter.Registry.has('mock')).toBe(true)
626
- expect(Adapter.Registry.get('mock')).toBe(factory)
627
- })
628
-
629
- it('should allow multiple adapter registrations', () => {
630
- const factory1 = MockAdapter as any
631
- const factory2 = MockAdapter as any
632
-
633
- Adapter.register('adapter1', factory1)
634
- Adapter.register('adapter2', factory2)
635
-
636
- expect(Adapter.Registry.size).toBeGreaterThanOrEqual(2)
637
- })
638
- })