@zhin.js/core 1.0.0 → 1.0.1

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 (121) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +21 -0
  3. package/README.md +295 -74
  4. package/lib/adapter.d.ts +39 -0
  5. package/lib/adapter.d.ts.map +1 -0
  6. package/{dist → lib}/adapter.js +20 -2
  7. package/lib/adapter.js.map +1 -0
  8. package/lib/app.d.ts +115 -0
  9. package/lib/app.d.ts.map +1 -0
  10. package/{dist → lib}/app.js +148 -78
  11. package/lib/app.js.map +1 -0
  12. package/lib/bot.d.ts +31 -0
  13. package/lib/bot.d.ts.map +1 -0
  14. package/lib/command.d.ts +32 -0
  15. package/lib/command.d.ts.map +1 -0
  16. package/lib/command.js +46 -0
  17. package/lib/command.js.map +1 -0
  18. package/lib/component.d.ts +107 -0
  19. package/lib/component.d.ts.map +1 -0
  20. package/lib/component.js +273 -0
  21. package/lib/component.js.map +1 -0
  22. package/{dist → lib}/config.d.ts.map +1 -1
  23. package/{dist → lib}/config.js +6 -9
  24. package/lib/config.js.map +1 -0
  25. package/lib/cron.d.ts +81 -0
  26. package/lib/cron.d.ts.map +1 -0
  27. package/lib/cron.js +159 -0
  28. package/lib/cron.js.map +1 -0
  29. package/lib/errors.d.ts +165 -0
  30. package/lib/errors.d.ts.map +1 -0
  31. package/lib/errors.js +306 -0
  32. package/lib/errors.js.map +1 -0
  33. package/lib/index.d.ts +15 -0
  34. package/lib/index.d.ts.map +1 -0
  35. package/lib/index.js +17 -0
  36. package/lib/index.js.map +1 -0
  37. package/lib/message.d.ts +44 -0
  38. package/lib/message.d.ts.map +1 -0
  39. package/lib/message.js +11 -0
  40. package/lib/message.js.map +1 -0
  41. package/lib/plugin.d.ts +50 -0
  42. package/lib/plugin.d.ts.map +1 -0
  43. package/lib/plugin.js +170 -0
  44. package/lib/plugin.js.map +1 -0
  45. package/lib/prompt.d.ts +116 -0
  46. package/lib/prompt.d.ts.map +1 -0
  47. package/lib/prompt.js +240 -0
  48. package/lib/prompt.js.map +1 -0
  49. package/lib/schema.d.ts +83 -0
  50. package/lib/schema.d.ts.map +1 -0
  51. package/lib/schema.js +245 -0
  52. package/lib/schema.js.map +1 -0
  53. package/{dist → lib}/types-generator.d.ts.map +1 -1
  54. package/{dist → lib}/types-generator.js +6 -3
  55. package/lib/types-generator.js.map +1 -0
  56. package/lib/types.d.ts +119 -0
  57. package/lib/types.d.ts.map +1 -0
  58. package/lib/utils.d.ts +52 -0
  59. package/lib/utils.d.ts.map +1 -0
  60. package/lib/utils.js +338 -0
  61. package/lib/utils.js.map +1 -0
  62. package/package.json +15 -9
  63. package/src/adapter.ts +25 -9
  64. package/src/app.ts +363 -258
  65. package/src/bot.ts +29 -8
  66. package/src/command.ts +50 -0
  67. package/src/component.ts +318 -0
  68. package/src/config.ts +9 -12
  69. package/src/cron.ts +176 -0
  70. package/src/errors.ts +365 -0
  71. package/src/index.ts +16 -13
  72. package/src/message.ts +44 -0
  73. package/src/plugin.ts +148 -66
  74. package/src/prompt.ts +290 -0
  75. package/src/schema.ts +273 -0
  76. package/src/types-generator.ts +7 -3
  77. package/src/types.ts +77 -30
  78. package/src/utils.ts +312 -0
  79. package/tests/adapter.test.ts +36 -22
  80. package/tests/app.test.ts +30 -0
  81. package/tests/command.test.ts +545 -0
  82. package/tests/component.test.ts +656 -0
  83. package/tests/config.test.ts +1 -1
  84. package/tests/errors.test.ts +311 -0
  85. package/tests/message.test.ts +402 -0
  86. package/tests/plugin.test.ts +275 -143
  87. package/tests/utils.test.ts +80 -0
  88. package/tsconfig.json +3 -4
  89. package/dist/adapter.d.ts +0 -22
  90. package/dist/adapter.d.ts.map +0 -1
  91. package/dist/adapter.js.map +0 -1
  92. package/dist/app.d.ts +0 -69
  93. package/dist/app.d.ts.map +0 -1
  94. package/dist/app.js.map +0 -1
  95. package/dist/bot.d.ts +0 -9
  96. package/dist/bot.d.ts.map +0 -1
  97. package/dist/config.js.map +0 -1
  98. package/dist/index.d.ts +0 -9
  99. package/dist/index.d.ts.map +0 -1
  100. package/dist/index.js +0 -12
  101. package/dist/index.js.map +0 -1
  102. package/dist/logger.d.ts +0 -3
  103. package/dist/logger.d.ts.map +0 -1
  104. package/dist/logger.js +0 -3
  105. package/dist/logger.js.map +0 -1
  106. package/dist/plugin.d.ts +0 -41
  107. package/dist/plugin.d.ts.map +0 -1
  108. package/dist/plugin.js +0 -95
  109. package/dist/plugin.js.map +0 -1
  110. package/dist/types-generator.js.map +0 -1
  111. package/dist/types.d.ts +0 -69
  112. package/dist/types.d.ts.map +0 -1
  113. package/src/logger.ts +0 -3
  114. package/tests/logger.test.ts +0 -170
  115. package/tsconfig.tsbuildinfo +0 -1
  116. /package/{dist → lib}/bot.js +0 -0
  117. /package/{dist → lib}/bot.js.map +0 -0
  118. /package/{dist → lib}/config.d.ts +0 -0
  119. /package/{dist → lib}/types-generator.d.ts +0 -0
  120. /package/{dist → lib}/types.js +0 -0
  121. /package/{dist → lib}/types.js.map +0 -0
@@ -0,0 +1,656 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest'
2
+ import { Component, defineComponent, CapWithChild, CapWithClose } from '../src/component'
3
+ import { Message } from '../src/message'
4
+
5
+ // Mock utils functions
6
+ vi.mock('../src/utils', () => ({
7
+ getValueWithRuntime: vi.fn((expression, context) => {
8
+ // Simple mock implementation
9
+ if (typeof expression === 'string' && context) {
10
+ // Handle simple variable access
11
+ if (expression in context) {
12
+ return context[expression]
13
+ }
14
+ // Handle object property access like 'user.name'
15
+ if (expression.includes('.')) {
16
+ const [obj, prop] = expression.split('.')
17
+ return context[obj]?.[prop]
18
+ }
19
+ }
20
+ return expression
21
+ }),
22
+ compiler: vi.fn((template, context) => template),
23
+ segment: {
24
+ toString: vi.fn((content) => typeof content === 'string' ? content : JSON.stringify(content)),
25
+ from: vi.fn((content) => content),
26
+ escape: vi.fn((content) => content.replace(/</g, '&lt;').replace(/>/g, '&gt;'))
27
+ }
28
+ }))
29
+
30
+ describe('Component系统测试', () => {
31
+ let mockContext: Component.Context
32
+ let mockMessage: Message
33
+
34
+ beforeEach(() => {
35
+ mockMessage = {
36
+ $id: '1',
37
+ $adapter: 'test',
38
+ $bot: 'test-bot',
39
+ $content: [],
40
+ $sender: { id: 'user1', name: 'User' },
41
+ $reply: vi.fn(),
42
+ $channel: { id: 'channel1', type: 'private' },
43
+ $timestamp: Date.now(),
44
+ $raw: 'test'
45
+ }
46
+
47
+ mockContext = {
48
+ $slots: {},
49
+ $message: mockMessage,
50
+ $root: 'test template',
51
+ parent: {} as any,
52
+ render: vi.fn().mockResolvedValue('rendered content'),
53
+ children: 'default children'
54
+ }
55
+ })
56
+
57
+ describe('Component类基础功能测试', () => {
58
+ it('应该正确创建Component实例', () => {
59
+ const component = new Component({
60
+ name: 'test-component',
61
+ render: (props) => `Hello ${props.name || 'World'}`
62
+ })
63
+
64
+ expect(component).toBeInstanceOf(Component)
65
+ expect(component.name).toBe('test-component')
66
+ expect(component.$props).toEqual([])
67
+ })
68
+
69
+ it('应该正确处理带属性的组件', () => {
70
+ const component = new Component({
71
+ name: 'user-card',
72
+ props: {
73
+ name: String,
74
+ age: Number,
75
+ active: Boolean
76
+ },
77
+ render: (props) => `User: ${props.name}, Age: ${props.age}, Active: ${props.active}`
78
+ })
79
+
80
+ expect(component.$props).toHaveLength(3)
81
+ expect(component.$props[0].name).toBe('name')
82
+ expect(component.$props[0].type).toBe(String)
83
+ expect(component.$props[1].name).toBe('age')
84
+ expect(component.$props[1].type).toBe(Number)
85
+ expect(component.$props[2].name).toBe('active')
86
+ expect(component.$props[2].type).toBe(Boolean)
87
+ })
88
+
89
+ it('应该正确处理带默认值的属性', () => {
90
+ const component = new Component({
91
+ name: 'greeting',
92
+ props: {
93
+ message: {
94
+ type: String,
95
+ default: 'Hello World'
96
+ },
97
+ count: {
98
+ type: Number,
99
+ default: 0
100
+ }
101
+ },
102
+ render: (props) => `${props.message} (${props.count})`
103
+ })
104
+
105
+ expect(component.$props).toHaveLength(2)
106
+ expect(component.$props[0].default).toBe('Hello World')
107
+ expect(component.$props[1].default).toBe(0)
108
+ })
109
+
110
+ it('应该支持动态名称设置', () => {
111
+ const component = new Component({
112
+ name: 'initial-name',
113
+ render: () => 'content'
114
+ })
115
+
116
+ expect(component.name).toBe('initial-name')
117
+
118
+ component.name = 'new-name'
119
+ expect(component.name).toBe('new-name')
120
+ })
121
+ })
122
+
123
+ describe('模板匹配测试', () => {
124
+ let component: Component
125
+
126
+ beforeEach(() => {
127
+ component = new Component({
128
+ name: 'test-comp',
129
+ render: () => 'test'
130
+ })
131
+ })
132
+
133
+ it('应该正确匹配自闭合标签', () => {
134
+ const template = '<test-comp name="test" />'
135
+ expect(component.isClosing(template)).toBe(true)
136
+ expect(component.match(template)).toBe(template)
137
+ })
138
+
139
+ it('应该正确匹配带children的标签', () => {
140
+ const template = '<test-comp name="test">Hello World</test-comp>'
141
+ expect(component.isClosing(template)).toBe(false)
142
+ expect(component.match(template)).toBe(template)
143
+ })
144
+
145
+ it('应该在不匹配时返回undefined', () => {
146
+ const template = '<other-comp name="test" />'
147
+ expect(component.match(template)).toBeUndefined()
148
+ })
149
+
150
+ it('应该正确识别符号常量', () => {
151
+ expect(component[CapWithChild]).toBeInstanceOf(RegExp)
152
+ expect(component[CapWithClose]).toBeInstanceOf(RegExp)
153
+ })
154
+ })
155
+
156
+ describe('属性解析测试', () => {
157
+ let component: Component
158
+
159
+ beforeEach(() => {
160
+ component = new Component({
161
+ name: 'prop-test',
162
+ props: {
163
+ name: String,
164
+ age: {
165
+ type: Number,
166
+ default: 18
167
+ },
168
+ active: {
169
+ type: Boolean,
170
+ default: false
171
+ }
172
+ },
173
+ render: () => 'test'
174
+ })
175
+ })
176
+
177
+ it('应该正确解析属性', () => {
178
+ const template = '<prop-test name="John" age="25" active="true" />'
179
+ const props = component.parseProps(template)
180
+
181
+ expect(props.name).toBe('John')
182
+ expect(props.age).toBe('25') // Note: parseProps returns strings
183
+ expect(props.active).toBe('true')
184
+ })
185
+
186
+ it('应该使用默认值填充缺失的属性', () => {
187
+ const template = '<prop-test name="John" />'
188
+ const props = component.parseProps(template)
189
+
190
+ expect(props.name).toBe('John')
191
+ expect(props.age).toBe(18)
192
+ expect(props.active).toBe(false)
193
+ })
194
+
195
+ it('应该正确处理带引号的属性值', () => {
196
+ const template = `<prop-test name='John Doe' description="A 'test' user" />`
197
+ const props = component.parseProps(template)
198
+
199
+ expect(props.name).toBe('John Doe')
200
+ expect(props.description).toBe("A 'test' user")
201
+ })
202
+
203
+ it('应该处理kebab-case到camelCase的转换', () => {
204
+ const template = '<prop-test user-name="john" max-count="10" />'
205
+ // This test would need the actual render method to see the conversion
206
+ // For now, we just test that parseProps extracts the attributes
207
+ const props = component.parseProps(template)
208
+
209
+ expect(props['user-name']).toBe('john')
210
+ expect(props['max-count']).toBe('10')
211
+ })
212
+ })
213
+
214
+ describe('Children解析测试', () => {
215
+ let component: Component
216
+
217
+ beforeEach(() => {
218
+ component = new Component({
219
+ name: 'child-test',
220
+ render: () => 'test'
221
+ })
222
+ })
223
+
224
+ it('应该正确解析children内容', () => {
225
+ const template = '<child-test>Hello World</child-test>'
226
+ const children = component.parseChildren(template)
227
+
228
+ expect(children).toBe('Hello World')
229
+ })
230
+
231
+ it('应该在自闭合标签中返回空字符串', () => {
232
+ const template = '<child-test />'
233
+ const children = component.parseChildren(template)
234
+
235
+ expect(children).toBe('')
236
+ })
237
+
238
+ it('应该处理复杂的children内容', () => {
239
+ const template = '<child-test><span>Nested content</span></child-test>'
240
+ const children = component.parseChildren(template)
241
+
242
+ // parseChildren实际只提取标签之间的文本内容
243
+ expect(children).toBe('Nested content')
244
+ })
245
+ })
246
+
247
+ describe('渲染功能测试', () => {
248
+ it('应该正确渲染简单组件', async () => {
249
+ const component = new Component({
250
+ name: 'simple',
251
+ render: () => 'Hello World'
252
+ })
253
+
254
+ const template = '<simple />'
255
+ const result = await component.render(template, mockContext)
256
+
257
+ expect(result).toBe('rendered content') // Mocked render function result
258
+ })
259
+
260
+ it('应该正确传递props给渲染函数', async () => {
261
+ const renderSpy = vi.fn().mockReturnValue('rendered')
262
+
263
+ const component = new Component({
264
+ name: 'props-comp',
265
+ props: {
266
+ name: String
267
+ },
268
+ render: renderSpy
269
+ })
270
+
271
+ const template = '<props-comp name="John" />'
272
+ await component.render(template, mockContext)
273
+
274
+ expect(renderSpy).toHaveBeenCalled()
275
+ // The first argument should contain the parsed props
276
+ const [props] = renderSpy.mock.calls[0]
277
+ expect(props.name).toBe('John')
278
+ })
279
+
280
+ it('应该正确处理data函数', async () => {
281
+ const component = new Component({
282
+ name: 'data-comp',
283
+ props: {
284
+ name: String
285
+ },
286
+ data() {
287
+ return {
288
+ computed: `Hello ${this.name}`
289
+ }
290
+ },
291
+ render: (props, context) => context.computed
292
+ })
293
+
294
+ const template = '<data-comp name="World" />'
295
+ const result = await component.render(template, mockContext)
296
+
297
+ expect(result).toBe('rendered content') // Mocked result
298
+ })
299
+ })
300
+
301
+ describe('指令系统测试', () => {
302
+ it('应该正确处理v-if指令', async () => {
303
+ const component = new Component({
304
+ name: 'conditional',
305
+ render: () => 'Content'
306
+ })
307
+
308
+ // Mock getValueWithRuntime to return false for v-if
309
+ const { getValueWithRuntime } = await import('../src/utils')
310
+ vi.mocked(getValueWithRuntime).mockReturnValueOnce(false)
311
+
312
+ const template = '<conditional v-if="false" />'
313
+ const result = await component.render(template, mockContext)
314
+
315
+ expect(result).toBe('') // Should return empty string when v-if is false
316
+ })
317
+
318
+ it('应该正确处理v-for指令', async () => {
319
+ const component = new Component({
320
+ name: 'list-item',
321
+ render: (props) => `Item: ${props.item}`
322
+ })
323
+
324
+ const template = '<list-item v-for="item in items" />'
325
+ const contextWithItems = {
326
+ ...mockContext,
327
+ items: ['A', 'B', 'C']
328
+ }
329
+
330
+ // This test is complex due to the v-for implementation
331
+ // We'll test that the render method is called
332
+ const result = await component.render(template, contextWithItems)
333
+ expect(result).toBeDefined()
334
+ })
335
+ })
336
+
337
+ describe('循环处理测试', () => {
338
+ it('应该正确解析简单循环表达式', () => {
339
+ const result = Component.fixLoop('item in items')
340
+
341
+ expect(result).toEqual({
342
+ name: 'item',
343
+ value: 'items'
344
+ })
345
+ })
346
+
347
+ it('应该正确处理数字范围循环', () => {
348
+ const result = Component.fixLoop('i in 3')
349
+
350
+ expect(result).toEqual({
351
+ name: 'i',
352
+ value: '__loop__',
353
+ __loop__: [0, 1, 2]
354
+ })
355
+ })
356
+
357
+ it('应该正确处理数组字面量循环', () => {
358
+ const result = Component.fixLoop('item in ["a","b","c"]')
359
+
360
+ expect(result).toEqual({
361
+ name: 'item',
362
+ value: '__loop__',
363
+ __loop__: ["a", "b", "c"]
364
+ })
365
+ })
366
+ })
367
+
368
+ describe('静态渲染方法测试', () => {
369
+ it('应该正确处理空组件映射', async () => {
370
+ const componentMap = new Map()
371
+ const options = { content: 'Hello World' }
372
+
373
+ const result = await Component.render(componentMap, options)
374
+
375
+ expect(result).toEqual(options) // Should return original options unchanged
376
+ })
377
+
378
+ it('应该正确渲染带组件的内容', async () => {
379
+ const component = new Component({
380
+ name: 'test-comp',
381
+ render: () => 'Rendered'
382
+ })
383
+
384
+ const componentMap = new Map([['test-comp', component]])
385
+ const options = { content: '<test-comp />' }
386
+
387
+ // Mock segment functions
388
+ const { segment } = await import('../src/utils')
389
+ vi.mocked(segment.toString).mockReturnValue('<test-comp />')
390
+ vi.mocked(segment.from).mockReturnValue('processed content')
391
+
392
+ const result = await Component.render(componentMap, options)
393
+
394
+ expect(result.content).toBe('processed content')
395
+ })
396
+ })
397
+
398
+ describe('内置组件测试', () => {
399
+ describe('Template组件', () => {
400
+ it('应该正确处理模板组件的render函数', () => {
401
+ const props = { '#header': 'true' }
402
+ const contextWithParent = {
403
+ ...mockContext,
404
+ parent: { ...mockContext.parent, $slots: {} }
405
+ }
406
+ const result = Component.Template.$options.render(props, contextWithParent)
407
+
408
+ expect(result).toBe('') // Template components return empty string
409
+ })
410
+
411
+ it('应该正确设置插槽', () => {
412
+ const props = { '#header': 'true', '#footer': 'true' }
413
+ const contextWithParent = {
414
+ ...mockContext,
415
+ parent: { ...mockContext.parent, $slots: {} }
416
+ }
417
+
418
+ Component.Template.$options.render(props, contextWithParent)
419
+
420
+ // Should have set slots in parent context
421
+ expect(Object.keys(contextWithParent.parent.$slots)).toContain('header')
422
+ expect(Object.keys(contextWithParent.parent.$slots)).toContain('footer')
423
+ })
424
+ })
425
+
426
+ describe('Slot组件', () => {
427
+ it('应该正确渲染默认插槽', () => {
428
+ const contextWithSlots = {
429
+ ...mockContext,
430
+ parent: {
431
+ ...mockContext.parent,
432
+ $slots: {
433
+ default: vi.fn().mockReturnValue('Slot Content')
434
+ }
435
+ }
436
+ }
437
+
438
+ const result = Component.Slot.$options.render({}, contextWithSlots)
439
+
440
+ expect(result).toBe('Slot Content')
441
+ })
442
+
443
+ it('应该正确渲染命名插槽', () => {
444
+ const contextWithSlots = {
445
+ ...mockContext,
446
+ parent: {
447
+ ...mockContext.parent,
448
+ $slots: {
449
+ header: vi.fn().mockReturnValue('Header Slot')
450
+ }
451
+ }
452
+ }
453
+
454
+ const result = Component.Slot.$options.render({ name: 'header' }, contextWithSlots)
455
+
456
+ expect(result).toBe('Header Slot')
457
+ })
458
+
459
+ it('应该在插槽不存在时使用children', () => {
460
+ const contextWithoutSlots = {
461
+ ...mockContext,
462
+ parent: { ...mockContext.parent, $slots: {} },
463
+ children: 'Fallback Content'
464
+ }
465
+
466
+ const result = Component.Slot.$options.render({ name: 'missing' }, contextWithoutSlots)
467
+
468
+ expect(result).toBe('Fallback Content')
469
+ })
470
+ })
471
+ })
472
+
473
+ describe('defineComponent工厂函数测试', () => {
474
+ it('应该正确创建函数式组件', () => {
475
+ const renderFn = (props: any) => `Hello ${props.name}`
476
+ const component = defineComponent(renderFn, 'func-comp')
477
+
478
+ expect(component).toBeInstanceOf(Component)
479
+ expect(component.name).toBe('func-comp')
480
+ })
481
+
482
+ it('应该正确创建配置式组件', () => {
483
+ const component = defineComponent({
484
+ name: 'config-comp',
485
+ props: {
486
+ title: String
487
+ },
488
+ render: (props) => props.title
489
+ })
490
+
491
+ expect(component).toBeInstanceOf(Component)
492
+ expect(component.name).toBe('config-comp')
493
+ expect(component.$props).toHaveLength(1)
494
+ })
495
+
496
+ it('应该正确处理重载函数', () => {
497
+ // Test function overload
498
+ const renderFn = () => 'test'
499
+ const funcComponent = defineComponent(renderFn)
500
+ expect(funcComponent).toBeInstanceOf(Component)
501
+
502
+ // Test options overload
503
+ const optionsComponent = defineComponent({
504
+ name: 'options-comp',
505
+ render: () => 'test'
506
+ })
507
+ expect(optionsComponent).toBeInstanceOf(Component)
508
+ expect(optionsComponent.name).toBe('options-comp')
509
+ })
510
+ })
511
+
512
+ describe('复杂场景测试', () => {
513
+ it('应该正确处理嵌套组件', async () => {
514
+ const childComponent = new Component({
515
+ name: 'child',
516
+ render: (props) => `Child: ${props.content}`
517
+ })
518
+
519
+ const parentComponent = new Component({
520
+ name: 'parent',
521
+ render: () => '<child content="nested" />'
522
+ })
523
+
524
+ const componentMap = new Map([
525
+ ['child', childComponent],
526
+ ['parent', parentComponent]
527
+ ])
528
+
529
+ const options = { content: '<parent />' }
530
+ const result = await Component.render(componentMap, options)
531
+
532
+ expect(result).toBeDefined()
533
+ })
534
+
535
+ it('应该正确处理属性绑定', async () => {
536
+ const component = new Component({
537
+ name: 'bind-test',
538
+ render: (props) => `Value: ${props.dynamicValue}`
539
+ })
540
+
541
+ const template = '<bind-test :dynamic-value="someVar" />'
542
+ const contextWithVar = {
543
+ ...mockContext,
544
+ someVar: 'Dynamic Content'
545
+ }
546
+
547
+ const result = await component.render(template, contextWithVar)
548
+ expect(result).toBeDefined()
549
+ })
550
+
551
+ it('应该正确处理上下文传递', async () => {
552
+ const component = new Component({
553
+ name: 'context-test',
554
+ render: (props, context) => {
555
+ expect(context.$message).toBe(mockMessage)
556
+ expect(context.parent).toBeDefined()
557
+ return 'Context OK'
558
+ }
559
+ })
560
+
561
+ const template = '<context-test />'
562
+ await component.render(template, mockContext)
563
+ })
564
+
565
+ it('应该正确处理错误情况', async () => {
566
+ const component = new Component({
567
+ name: 'error-test',
568
+ render: () => {
569
+ throw new Error('Render error')
570
+ }
571
+ })
572
+
573
+ const template = '<error-test />'
574
+
575
+ // The render method should handle errors gracefully
576
+ await expect(component.render(template, mockContext)).rejects.toThrow('Render error')
577
+ })
578
+ })
579
+
580
+ describe('性能和边界测试', () => {
581
+ it('应该处理空模板', async () => {
582
+ const component = new Component({
583
+ name: 'empty-test',
584
+ render: () => ''
585
+ })
586
+
587
+ const result = await component.render('', mockContext)
588
+ expect(result).toBe('rendered content') // Mocked result
589
+ })
590
+
591
+ it('应该处理大量属性', async () => {
592
+ const manyProps: any = {}
593
+ for (let i = 0; i < 100; i++) {
594
+ manyProps[`prop${i}`] = String
595
+ }
596
+
597
+ const component = new Component({
598
+ name: 'many-props',
599
+ props: manyProps,
600
+ render: () => 'OK'
601
+ })
602
+
603
+ expect(component.$props).toHaveLength(100)
604
+ })
605
+
606
+ it('应该处理长模板字符串', async () => {
607
+ const component = new Component({
608
+ name: 'long-template',
609
+ render: () => 'OK'
610
+ })
611
+
612
+ const longContent = 'A'.repeat(10000)
613
+ const template = `<long-template content="${longContent}" />`
614
+
615
+ const result = await component.render(template, mockContext)
616
+ expect(result).toBeDefined()
617
+ })
618
+ })
619
+
620
+ describe('类型系统测试', () => {
621
+ it('应该支持泛型类型约束', () => {
622
+ interface Props {
623
+ title: string
624
+ count: number
625
+ }
626
+
627
+ const component: Component<{}, {}, Props> = new Component({
628
+ name: 'typed-comp',
629
+ render: (props: Props) => `${props.title}: ${props.count}`
630
+ })
631
+
632
+ expect(component).toBeInstanceOf(Component)
633
+ })
634
+
635
+ it('应该正确处理PropConfig类型', () => {
636
+ const component = new Component({
637
+ name: 'prop-config-test',
638
+ props: {
639
+ stringProp: String,
640
+ numberProp: Number,
641
+ booleanProp: Boolean,
642
+ arrayProp: Array,
643
+ objectProp: Object
644
+ },
645
+ render: () => 'test'
646
+ })
647
+
648
+ const propTypes = component.$props.map(p => p.type)
649
+ expect(propTypes).toContain(String)
650
+ expect(propTypes).toContain(Number)
651
+ expect(propTypes).toContain(Boolean)
652
+ expect(propTypes).toContain(Array)
653
+ expect(propTypes).toContain(Object)
654
+ })
655
+ })
656
+ })
@@ -101,7 +101,7 @@ module.exports = {
101
101
  ]
102
102
  }
103
103
  `
104
- const configPath = path.join(testDir, 'zhin.config.js')
104
+ const configPath = path.join(testDir, 'zhin.config.ts')
105
105
  fs.writeFileSync(configPath, config)
106
106
 
107
107
  const [loadedPath, loadedConfig] = await loadConfig({ configPath })