@zhin.js/core 1.0.16 → 1.0.18

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 (118) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/REFACTORING_COMPLETE.md +178 -0
  3. package/REFACTORING_STATUS.md +263 -0
  4. package/lib/adapter.d.ts +44 -19
  5. package/lib/adapter.d.ts.map +1 -1
  6. package/lib/adapter.js +81 -50
  7. package/lib/adapter.js.map +1 -1
  8. package/lib/bot.d.ts +7 -12
  9. package/lib/bot.d.ts.map +1 -1
  10. package/lib/built/adapter-process.d.ts +36 -0
  11. package/lib/built/adapter-process.d.ts.map +1 -0
  12. package/lib/built/adapter-process.js +77 -0
  13. package/lib/built/adapter-process.js.map +1 -0
  14. package/lib/built/command.d.ts +46 -0
  15. package/lib/built/command.d.ts.map +1 -0
  16. package/lib/built/command.js +54 -0
  17. package/lib/built/command.js.map +1 -0
  18. package/lib/built/component.d.ts +42 -0
  19. package/lib/built/component.d.ts.map +1 -0
  20. package/lib/built/component.js +66 -0
  21. package/lib/built/component.js.map +1 -0
  22. package/lib/built/config.d.ts +31 -0
  23. package/lib/built/config.d.ts.map +1 -0
  24. package/lib/built/config.js +141 -0
  25. package/lib/built/config.js.map +1 -0
  26. package/lib/built/cron.d.ts +53 -0
  27. package/lib/built/cron.d.ts.map +1 -0
  28. package/lib/built/cron.js +79 -0
  29. package/lib/built/cron.js.map +1 -0
  30. package/lib/built/database.d.ts +17 -0
  31. package/lib/built/database.d.ts.map +1 -0
  32. package/lib/built/database.js +28 -0
  33. package/lib/built/database.js.map +1 -0
  34. package/lib/{permissions.d.ts → built/permission.d.ts} +5 -10
  35. package/lib/built/permission.d.ts.map +1 -0
  36. package/lib/{permissions.js → built/permission.js} +11 -10
  37. package/lib/built/permission.js.map +1 -0
  38. package/lib/command.d.ts +18 -7
  39. package/lib/command.d.ts.map +1 -1
  40. package/lib/command.js +36 -15
  41. package/lib/command.js.map +1 -1
  42. package/lib/component.d.ts +1 -1
  43. package/lib/component.d.ts.map +1 -1
  44. package/lib/component.js.map +1 -1
  45. package/lib/cron.d.ts +4 -12
  46. package/lib/cron.d.ts.map +1 -1
  47. package/lib/cron.js +33 -64
  48. package/lib/cron.js.map +1 -1
  49. package/lib/index.d.ts +11 -3
  50. package/lib/index.d.ts.map +1 -1
  51. package/lib/index.js +14 -4
  52. package/lib/index.js.map +1 -1
  53. package/lib/jsx-runtime.d.ts +2 -2
  54. package/lib/jsx.d.ts +2 -3
  55. package/lib/jsx.d.ts.map +1 -1
  56. package/lib/jsx.js.map +1 -1
  57. package/lib/message.d.ts +4 -7
  58. package/lib/message.d.ts.map +1 -1
  59. package/lib/message.js.map +1 -1
  60. package/lib/plugin.d.ts +164 -51
  61. package/lib/plugin.d.ts.map +1 -1
  62. package/lib/plugin.js +520 -137
  63. package/lib/plugin.js.map +1 -1
  64. package/lib/prompt.d.ts +1 -1
  65. package/lib/prompt.d.ts.map +1 -1
  66. package/lib/prompt.js +2 -1
  67. package/lib/prompt.js.map +1 -1
  68. package/lib/types.d.ts +33 -33
  69. package/lib/types.d.ts.map +1 -1
  70. package/lib/utils.d.ts +16 -1
  71. package/lib/utils.d.ts.map +1 -1
  72. package/lib/utils.js +166 -66
  73. package/lib/utils.js.map +1 -1
  74. package/package.json +17 -11
  75. package/src/adapter.ts +131 -80
  76. package/src/bot.ts +8 -13
  77. package/src/built/adapter-process.ts +77 -0
  78. package/src/built/command.ts +102 -0
  79. package/src/built/component.ts +111 -0
  80. package/src/built/config.ts +126 -0
  81. package/src/built/cron.ts +140 -0
  82. package/src/built/database.ts +38 -0
  83. package/src/{permissions.ts → built/permission.ts} +9 -12
  84. package/src/command.ts +48 -20
  85. package/src/component.ts +2 -3
  86. package/src/cron.ts +35 -70
  87. package/src/index.ts +15 -5
  88. package/src/jsx.ts +2 -3
  89. package/src/message.ts +3 -4
  90. package/src/plugin.ts +671 -184
  91. package/src/prompt.ts +4 -3
  92. package/src/types.ts +41 -35
  93. package/src/utils.ts +418 -296
  94. package/test/minimal-bot.ts +31 -0
  95. package/test/stress-test.ts +123 -0
  96. package/tests/command.test.ts +124 -44
  97. package/ASYNC-JSX-SUPPORT.md +0 -173
  98. package/lib/app.d.ts +0 -191
  99. package/lib/app.d.ts.map +0 -1
  100. package/lib/app.js +0 -604
  101. package/lib/app.js.map +0 -1
  102. package/lib/config.d.ts +0 -54
  103. package/lib/config.d.ts.map +0 -1
  104. package/lib/config.js +0 -308
  105. package/lib/config.js.map +0 -1
  106. package/lib/log-transport.d.ts +0 -37
  107. package/lib/log-transport.d.ts.map +0 -1
  108. package/lib/log-transport.js +0 -136
  109. package/lib/log-transport.js.map +0 -1
  110. package/lib/permissions.d.ts.map +0 -1
  111. package/lib/permissions.js.map +0 -1
  112. package/src/app.ts +0 -772
  113. package/src/config.ts +0 -397
  114. package/src/log-transport.ts +0 -163
  115. package/tests/app.test.ts +0 -265
  116. package/tests/permissions.test.ts +0 -358
  117. package/tests/plugin.test.ts +0 -234
  118. package/tests/prompt.test.ts +0 -223
@@ -0,0 +1,31 @@
1
+ import { App } from '../src/app';
2
+ import { LogLevel } from '@zhin.js/logger';
3
+ import { fileURLToPath } from 'url';
4
+ import * as path from 'path';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+
9
+ async function runMinimalBot() {
10
+ const app = new App({
11
+ log_level: LogLevel.INFO,
12
+ plugin_dirs: [path.join(__dirname, 'plugins')],
13
+ plugins: [],
14
+ bots: [],
15
+ debug: true
16
+ });
17
+
18
+ console.log('Starting Minimal Bot...');
19
+ await app.start();
20
+ console.log('Minimal Bot Started');
21
+
22
+ // Simulate a message
23
+ // await app.receiveMessage(...)
24
+
25
+ await app.stop();
26
+ }
27
+
28
+ if (process.argv[1] === __filename) {
29
+ runMinimalBot().catch(console.error);
30
+ }
31
+
@@ -0,0 +1,123 @@
1
+ import { App } from '../src/app';
2
+ import { LogLevel } from '@zhin.js/logger';
3
+ import { fileURLToPath } from 'url';
4
+ import * as path from 'path';
5
+ import * as fs from 'fs';
6
+ // import { Message } from '../src/message'; // Message is a type/namespace, not a class
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ async function runStressTest() {
12
+ const PLUGIN_COUNT = 50;
13
+ const MSG_COUNT = 10000;
14
+ const WORK_DIR = path.join(__dirname, 'stress_plugins');
15
+
16
+ console.log('Starting Core Stress Test');
17
+
18
+ // Setup workspace
19
+ if (fs.existsSync(WORK_DIR)) fs.rmSync(WORK_DIR, { recursive: true, force: true });
20
+ fs.mkdirSync(WORK_DIR, { recursive: true });
21
+
22
+ // Generate plugins
23
+ let loadedCount = 0;
24
+ for (let i = 0; i < PLUGIN_COUNT; i++) {
25
+ fs.writeFileSync(path.join(WORK_DIR, `plugin-${i}.ts`), `
26
+ import { useContext } from '${path.resolve(__dirname, '../src/plugin').replace(/\\/g, '/')}';
27
+
28
+ export function install(ctx) {
29
+ ctx.on('message.receive', (msg) => {
30
+ // console.log('Plugin ${i} received message');
31
+ });
32
+
33
+ ctx.middleware(async (msg, next) => {
34
+ await next();
35
+ });
36
+ }
37
+ `);
38
+ }
39
+
40
+ const CONFIG_FILE = path.join(WORK_DIR, 'zhin.test.yaml');
41
+ // Write config to file to avoid reload overriding it with defaults
42
+ const config = {
43
+ log_level: LogLevel.WARN,
44
+ plugin_dirs: [WORK_DIR],
45
+ plugins: Array.from({ length: PLUGIN_COUNT }, (_, i) => `plugin-${i}`),
46
+ bots: [],
47
+ debug: false
48
+ };
49
+
50
+ // Simple YAML stringify since we can't require 'yaml' easily in ESM without import
51
+ const yamlString = `
52
+ log_level: ${config.log_level}
53
+ debug: ${config.debug}
54
+ plugin_dirs:
55
+ ${config.plugin_dirs.map(d => ` - ${d}`).join('\n')}
56
+ plugins:
57
+ ${config.plugins.map(p => ` - ${p}`).join('\n')}
58
+ bots: []
59
+ `;
60
+
61
+ fs.writeFileSync(CONFIG_FILE, yamlString);
62
+
63
+ const app = new App(CONFIG_FILE);
64
+
65
+ console.log('Starting App...');
66
+ const startBoot = performance.now();
67
+ await app.start();
68
+ console.log(`App started in ${(performance.now() - startBoot).toFixed(2)}ms`);
69
+
70
+ // Verify plugins loaded
71
+ const loadedPlugins = app.hmrManager.dependencyList.length; // App itself + plugins
72
+ console.log(`Loaded Dependencies: ${loadedPlugins}`);
73
+ if (loadedPlugins < PLUGIN_COUNT) {
74
+ console.warn(`WARNING: Only ${loadedPlugins} dependencies loaded (expected >= ${PLUGIN_COUNT})`);
75
+ }
76
+
77
+ const initialMem = process.memoryUsage();
78
+ console.log(`Initial RSS: ${(initialMem.rss / 1024 / 1024).toFixed(2)} MB`);
79
+
80
+ console.log(`Sending ${MSG_COUNT} messages...`);
81
+ const startMsg = performance.now();
82
+
83
+ // Mock message factory
84
+ const createMockMsg = (id: number) => ({
85
+ $id: `msg-${id}`,
86
+ $adapter: 'mock',
87
+ $bot: 'bot1',
88
+ $content: [{ type: 'text', data: { text: 'hello' } }],
89
+ $sender: { id: 'user1' },
90
+ $channel: { id: 'group1', type: 'group' as const },
91
+ $timestamp: Date.now(),
92
+ $raw: 'hello',
93
+ $reply: async () => 'reply-id',
94
+ $recall: async () => {},
95
+ // Custom fields usually added by adapters
96
+ raw_message: 'hello',
97
+ message_type: 'group',
98
+ sender: { id: 'user1' },
99
+ });
100
+
101
+ for (let i = 0; i < MSG_COUNT; i++) {
102
+ const msg = createMockMsg(i);
103
+ await app.receiveMessage(msg as any);
104
+
105
+ if (i % 1000 === 0) {
106
+ const mem = process.memoryUsage();
107
+ process.stdout.write(`\rMsgs: ${i}, RSS: ${(mem.rss / 1024 / 1024).toFixed(2)} MB`);
108
+ }
109
+ }
110
+
111
+ console.log(`\nProcessed ${MSG_COUNT} messages in ${(performance.now() - startMsg).toFixed(2)}ms`);
112
+ console.log(`Avg: ${((performance.now() - startMsg) / MSG_COUNT).toFixed(3)}ms/msg`);
113
+
114
+ const finalMem = process.memoryUsage();
115
+ console.log(`Final RSS: ${(finalMem.rss / 1024 / 1024).toFixed(2)} MB`);
116
+ console.log(`Memory Delta: ${((finalMem.rss - initialMem.rss) / 1024 / 1024).toFixed(2)} MB`);
117
+
118
+ await app.stop();
119
+ fs.rmSync(WORK_DIR, { recursive: true, force: true });
120
+ }
121
+
122
+ runStressTest().catch(console.error);
123
+
@@ -49,55 +49,58 @@ vi.mock('segment-matcher', () => {
49
49
  })
50
50
 
51
51
  // Mock App with permissions
52
+ const mockPermissionService = {
53
+ check: vi.fn(async (perm: string, message: any) => {
54
+ if (perm === 'adapter(discord)') {
55
+ return message.$adapter === 'discord'
56
+ }
57
+ if (perm === 'adapter(telegram)') {
58
+ return message.$adapter === 'telegram'
59
+ }
60
+ if (perm === 'adapter(email)') {
61
+ return message.$adapter === 'email'
62
+ }
63
+ if (perm === 'adapter(test)') {
64
+ return message.$adapter === 'test'
65
+ }
66
+ return true
67
+ })
68
+ }
69
+
52
70
  const mockApp = {
53
- permissions: {
54
- get: vi.fn((permission: string) => {
55
- // Mock permit checker
56
- return {
57
- check: vi.fn(async (perm: string, message: any) => {
58
- if (permission === 'adapter(discord)') {
59
- return message.$adapter === 'discord'
60
- }
61
- if (permission === 'adapter(telegram)') {
62
- return message.$adapter === 'telegram'
63
- }
64
- if (permission === 'adapter(email)') {
65
- return message.$adapter === 'email'
66
- }
67
- if (permission === 'adapter(test)') {
68
- return message.$adapter === 'test'
69
- }
70
- return true
71
- })
72
- }
73
- })
74
- }
71
+ contextIsReady: vi.fn((name: string) => name === 'permission'),
72
+ inject: vi.fn((name: string) => {
73
+ if (name === 'permission') return mockPermissionService
74
+ return null
75
+ })
75
76
  } as any
76
77
 
77
78
  // 为多个权限测试创建特殊的 mock app
79
+ const multiPermitPermissionService = {
80
+ check: vi.fn(async (perm: string, message: any) => {
81
+ // 对于多个权限,只要有一个匹配就返回 true
82
+ if (perm === 'adapter(discord)' && message.$adapter === 'discord') {
83
+ return true
84
+ }
85
+ if (perm === 'adapter(telegram)' && message.$adapter === 'telegram') {
86
+ return true
87
+ }
88
+ if (perm === 'adapter(email)' && message.$adapter === 'email') {
89
+ return true
90
+ }
91
+ if (perm === 'adapter(test)' && message.$adapter === 'test') {
92
+ return true
93
+ }
94
+ return false
95
+ })
96
+ }
97
+
78
98
  const multiPermitMockApp = {
79
- permissions: {
80
- get: vi.fn((permission: string) => {
81
- return {
82
- check: vi.fn(async (perm: string, message: any) => {
83
- // 对于多个权限,只要有一个匹配就返回 true
84
- if (permission === 'adapter(discord)' && message.$adapter === 'discord') {
85
- return true
86
- }
87
- if (permission === 'adapter(telegram)' && message.$adapter === 'telegram') {
88
- return true
89
- }
90
- if (permission === 'adapter(email)' && message.$adapter === 'email') {
91
- return true
92
- }
93
- if (permission === 'adapter(test)' && message.$adapter === 'test') {
94
- return true
95
- }
96
- return false
97
- })
98
- }
99
- })
100
- }
99
+ contextIsReady: vi.fn((name: string) => name === 'permission'),
100
+ inject: vi.fn((name: string) => {
101
+ if (name === 'permission') return multiPermitPermissionService
102
+ return null
103
+ })
101
104
  } as any
102
105
 
103
106
  describe('Command系统测试', () => {
@@ -611,6 +614,83 @@ describe('Command系统测试', () => {
611
614
  })
612
615
  })
613
616
 
617
+ describe('帮助系统测试', () => {
618
+ it('应该正确设置和获取描述信息', () => {
619
+ const command = new MessageCommand('help')
620
+ .desc('这是命令描述', '可以有多行描述')
621
+
622
+ expect(command.helpInfo.desc).toEqual(['这是命令描述', '可以有多行描述'])
623
+ })
624
+
625
+ it('应该正确设置和获取用法信息', () => {
626
+ const command = new MessageCommand('help')
627
+ .usage('help', 'help <command>')
628
+
629
+ expect(command.helpInfo.usage).toEqual(['help', 'help <command>'])
630
+ })
631
+
632
+ it('应该正确设置和获取示例信息', () => {
633
+ const command = new MessageCommand('help')
634
+ .examples('help', 'help echo', 'help admin')
635
+
636
+ expect(command.helpInfo.examples).toEqual(['help', 'help echo', 'help admin'])
637
+ })
638
+
639
+ it('应该支持链式调用设置帮助信息', () => {
640
+ const command = new MessageCommand('test')
641
+ .desc('测试命令', '用于测试功能')
642
+ .usage('test', 'test <arg>')
643
+ .examples('test', 'test hello')
644
+ .action(() => 'Test response')
645
+
646
+ expect(command.helpInfo.pattern).toBe('test')
647
+ expect(command.helpInfo.desc).toEqual(['测试命令', '用于测试功能'])
648
+ expect(command.helpInfo.usage).toEqual(['test', 'test <arg>'])
649
+ expect(command.helpInfo.examples).toEqual(['test', 'test hello'])
650
+ })
651
+
652
+ it('应该正确生成帮助文本', () => {
653
+ const command = new MessageCommand('greet')
654
+ .desc('打招呼命令')
655
+ .usage('greet <name>')
656
+ .examples('greet Alice')
657
+
658
+ const help = command.help
659
+
660
+ expect(help).toContain('greet')
661
+ expect(help).toContain('打招呼命令')
662
+ expect(help).toContain('greet <name>')
663
+ expect(help).toContain('greet Alice')
664
+ })
665
+
666
+ it('应该处理没有帮助信息的情况', () => {
667
+ const command = new MessageCommand('simple')
668
+
669
+ expect(command.helpInfo.desc).toEqual([])
670
+ expect(command.helpInfo.usage).toEqual([])
671
+ expect(command.helpInfo.examples).toEqual([])
672
+ expect(command.help).toBe('simple')
673
+ })
674
+
675
+ it('应该正确返回 helpInfo 对象结构', () => {
676
+ const command = new MessageCommand('info')
677
+ .desc('信息命令')
678
+ .usage('info')
679
+ .examples('info')
680
+
681
+ const helpInfo = command.helpInfo
682
+
683
+ expect(helpInfo).toHaveProperty('pattern')
684
+ expect(helpInfo).toHaveProperty('desc')
685
+ expect(helpInfo).toHaveProperty('usage')
686
+ expect(helpInfo).toHaveProperty('examples')
687
+ expect(typeof helpInfo.pattern).toBe('string')
688
+ expect(Array.isArray(helpInfo.desc)).toBe(true)
689
+ expect(Array.isArray(helpInfo.usage)).toBe(true)
690
+ expect(Array.isArray(helpInfo.examples)).toBe(true)
691
+ })
692
+ })
693
+
614
694
  describe('权限系统测试', () => {
615
695
  it('应该正确处理权限检查失败', async () => {
616
696
  const command = new MessageCommand('admin')
@@ -1,173 +0,0 @@
1
- # 异步 JSX 组件支持
2
-
3
- ## 概述
4
-
5
- Zhin Core 现在原生支持异步 JSX 组件,允许你像使用普通组件一样使用异步组件,无需额外的类型断言或注释。
6
-
7
- ## 核心改动
8
-
9
- ### 1. 类型系统扩展
10
-
11
- **`packages/core/src/jsx.ts`**:
12
- - 修改 `JSX.Element` 类型为联合类型,支持 `Promise<SendContent>`
13
- - `renderJSX` 函数自动检测并 await Promise 返回值
14
- - 错误时自动捕获并返回错误信息
15
-
16
- **`packages/core/src/message.ts`**:
17
- - `MessageComponent` 类型支持异步组件函数
18
-
19
- ### 2. 运行时支持
20
-
21
- **自动 Promise 处理**:
22
- ```typescript
23
- export async function renderJSX(element: MessageComponent<any>, context?: ComponentContext): Promise<SendContent> {
24
- try {
25
- // ... 组件渲染逻辑
26
- const result = await component(element.data, context || {} as ComponentContext);
27
-
28
- // 如果组件返回 Promise,自动 await
29
- if (result && typeof result === 'object' && 'then' in result) {
30
- return await result;
31
- }
32
-
33
- return result;
34
- } catch (error) {
35
- // 渲染错误时返回错误信息
36
- const errorMessage = error instanceof Error ? error.message : String(error);
37
- return `❌ 组件渲染失败: ${errorMessage}`;
38
- }
39
- }
40
- ```
41
-
42
- **子组件 Promise 处理**:
43
- ```typescript
44
- async function renderChildren(children: JSXChildren, context?: ComponentContext): Promise<SendContent> {
45
- // ...
46
- // 如果子元素是 Promise,自动 await
47
- if (children && typeof children === 'object' && 'then' in children) {
48
- try {
49
- return await children;
50
- } catch (error) {
51
- return `❌ 组件渲染失败: ${errorMessage}`;
52
- }
53
- }
54
- }
55
- ```
56
-
57
- ## 使用方式
58
-
59
- ### 定义异步组件
60
-
61
- ```tsx
62
- import { defineComponent, addComponent } from 'zhin.js';
63
-
64
- const AsyncComponent = defineComponent(async function AsyncComponent({ userId }: { userId: string }) {
65
- // 执行异步操作
66
- const user = await fetchUserFromDatabase(userId);
67
- const profile = await fetchUserProfile(userId);
68
-
69
- return `👤 ${user.name}\n📧 ${profile.email}`;
70
- }, 'AsyncComponent');
71
-
72
- addComponent(AsyncComponent);
73
- ```
74
-
75
- ### 在 JSX 中使用(现在完全类型安全)
76
-
77
- ```tsx
78
- addCommand(
79
- new MessageCommand('用户 <userId:text>')
80
- .action(async (message, result) => {
81
- // ✅ 直接使用 JSX 语法,无需 @ts-expect-error
82
- return <AsyncComponent userId={result.params.userId} />
83
- })
84
- );
85
- ```
86
-
87
- ### 嵌套异步组件
88
-
89
- ```tsx
90
- const UserProfile = defineComponent(async function UserProfile({ userId }: { userId: string }) {
91
- const user = await fetchUser(userId);
92
-
93
- // 嵌套使用其他异步组件
94
- return (
95
- <div>
96
- <h1>{user.name}</h1>
97
- <AsyncComponent userId={userId} />
98
- </div>
99
- );
100
- }, 'UserProfile');
101
- ```
102
-
103
- ## 错误处理
104
-
105
- 异步组件中的错误会自动被捕获并返回友好的错误信息:
106
-
107
- ```tsx
108
- const FailingComponent = defineComponent(async function FailingComponent() {
109
- throw new Error('数据加载失败');
110
- }, 'FailingComponent');
111
-
112
- // 使用时会自动返回: "❌ 组件渲染失败: 数据加载失败"
113
- ```
114
-
115
- ## 性能考虑
116
-
117
- - **自动 await**:框架自动检测 Promise 并等待,无额外开销
118
- - **并行渲染**:多个异步组件可以并行加载(使用 `Promise.all`)
119
- - **错误隔离**:单个组件错误不会影响整体渲染
120
-
121
- ## 迁移指南
122
-
123
- 如果你之前使用了 `@ts-expect-error` 或直接函数调用:
124
-
125
- ```tsx
126
- // ❌ 旧方式(已废弃)
127
- return await ShareMusic({ platform: 'qq', musicId: '123' });
128
-
129
- // ✅ 新方式(推荐)
130
- return <ShareMusic platform="qq" musicId="123" />
131
- ```
132
-
133
- ## TypeScript 类型
134
-
135
- ```typescript
136
- // JSX.Element 现在支持 Promise
137
- declare global {
138
- namespace JSX {
139
- type Element = MessageComponent<any> | Promise<MessageComponent<any>> | Promise<SendContent>
140
- }
141
- }
142
-
143
- // MessageComponent 支持异步函数
144
- export type MessageComponent<T extends object> = {
145
- type: Component<T & {children?: SendContent}> | ((props: T & {children?: SendContent}) => Promise<SendContent>)
146
- data: T
147
- }
148
- ```
149
-
150
- ## 测试
151
-
152
- 确保你的异步组件正确工作:
153
-
154
- ```typescript
155
- import { describe, it, expect } from 'vitest';
156
-
157
- describe('Async Components', () => {
158
- it('should render async component', async () => {
159
- const result = await renderJSX(<AsyncComponent userId="123" />);
160
- expect(result).toBe('👤 User Name\n📧 user@example.com');
161
- });
162
-
163
- it('should handle errors gracefully', async () => {
164
- const result = await renderJSX(<FailingComponent />);
165
- expect(result).toMatch(/❌ 组件渲染失败/);
166
- });
167
- });
168
- ```
169
-
170
- ---
171
-
172
- **版本**: 1.0.15+
173
- **文档更新**: 2025-11-19