openmatrix 0.2.23 → 0.2.24
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.
- package/dist/test/generator.js +244 -20
- package/package.json +1 -1
- package/skills/test.md +34 -5
package/dist/test/generator.js
CHANGED
|
@@ -163,31 +163,16 @@ function generateImports(exports, importPath, usesTypeScript) {
|
|
|
163
163
|
: `const { ${exports.join(', ')} } = require('../../${importPath}');`;
|
|
164
164
|
}
|
|
165
165
|
/**
|
|
166
|
-
* 生成测试块
|
|
166
|
+
* 生成测试块 - 根据文件类型生成有意义的业务测试
|
|
167
167
|
*/
|
|
168
168
|
function generateTestBlock(exportName, framework, usesDescribeIt, fileType) {
|
|
169
169
|
const useDescribe = usesDescribeIt ?? true;
|
|
170
|
-
const describeOrTest = usesDescribeIt ? 'describe' : 'test';
|
|
171
170
|
if (useDescribe) {
|
|
171
|
+
// 根据文件类型生成不同的测试场景
|
|
172
|
+
const testCases = generateBusinessTestCases(exportName, fileType);
|
|
173
|
+
const testBlocks = testCases.map(tc => generateTestCaseBlock(tc, exportName));
|
|
172
174
|
return `describe('${getDescribeTitle(exportName, fileType)}', () => {
|
|
173
|
-
|
|
174
|
-
// Arrange
|
|
175
|
-
const input = {};
|
|
176
|
-
|
|
177
|
-
// Act
|
|
178
|
-
const result = ${exportName}(input);
|
|
179
|
-
|
|
180
|
-
// Assert
|
|
181
|
-
expect(result).toBeDefined();
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it('should handle edge cases', () => {
|
|
185
|
-
// Arrange
|
|
186
|
-
const input = null;
|
|
187
|
-
|
|
188
|
-
// Act & Assert
|
|
189
|
-
expect(() => ${exportName}(input)).not.toThrow();
|
|
190
|
-
});
|
|
175
|
+
${testBlocks.join('\n\n')}
|
|
191
176
|
});`;
|
|
192
177
|
}
|
|
193
178
|
else {
|
|
@@ -197,6 +182,245 @@ function generateTestBlock(exportName, framework, usesDescribeIt, fileType) {
|
|
|
197
182
|
});`;
|
|
198
183
|
}
|
|
199
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* 根据文件类型生成业务测试用例
|
|
187
|
+
*/
|
|
188
|
+
function generateBusinessTestCases(exportName, fileType) {
|
|
189
|
+
switch (fileType) {
|
|
190
|
+
case 'service':
|
|
191
|
+
return [
|
|
192
|
+
{
|
|
193
|
+
name: 'should return valid result on successful execution',
|
|
194
|
+
arrange: `// Arrange - 准备有效的输入参数
|
|
195
|
+
const params = { id: 'test-id', data: {} };
|
|
196
|
+
const mockDependencies = {};`,
|
|
197
|
+
act: `// Act - 执行服务方法
|
|
198
|
+
const result = await ${exportName}(params);`,
|
|
199
|
+
assert: [
|
|
200
|
+
'expect(result).toBeDefined();',
|
|
201
|
+
'expect(result).toHaveProperty(\'data\');',
|
|
202
|
+
'expect(result.error).toBeUndefined();'
|
|
203
|
+
]
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: 'should handle invalid input gracefully',
|
|
207
|
+
arrange: `// Arrange - 准备无效输入
|
|
208
|
+
const invalidParams = { id: null, data: undefined };`,
|
|
209
|
+
act: `// Act - 使用无效参数调用
|
|
210
|
+
const result = await ${exportName}(invalidParams);`,
|
|
211
|
+
assert: [
|
|
212
|
+
'expect(result).toBeDefined();',
|
|
213
|
+
'expect(result).toHaveProperty(\'error\');'
|
|
214
|
+
]
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
name: 'should handle empty parameters',
|
|
218
|
+
arrange: `// Arrange - 空参数测试`,
|
|
219
|
+
act: `// Act & Assert - 验证空参数处理
|
|
220
|
+
const result = await ${exportName}({});`,
|
|
221
|
+
assert: [
|
|
222
|
+
'expect(result).toBeDefined();',
|
|
223
|
+
'// 应该返回默认值或错误信息,而非崩溃'
|
|
224
|
+
]
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
name: 'should handle missing required fields',
|
|
228
|
+
arrange: `// Arrange - 缺少必填字段`,
|
|
229
|
+
act: `// Act`,
|
|
230
|
+
assert: [
|
|
231
|
+
`expect(() => ${exportName}(null)).not.toThrow();`,
|
|
232
|
+
'expect(() => ${exportName}(undefined)).not.toThrow();'
|
|
233
|
+
]
|
|
234
|
+
}
|
|
235
|
+
];
|
|
236
|
+
case 'util':
|
|
237
|
+
return [
|
|
238
|
+
{
|
|
239
|
+
name: 'should process valid input correctly',
|
|
240
|
+
arrange: `// Arrange - 准备有效输入
|
|
241
|
+
const input = 'test-value';
|
|
242
|
+
const expected = 'expected-result';`,
|
|
243
|
+
act: `// Act
|
|
244
|
+
const result = ${exportName}(input);`,
|
|
245
|
+
assert: [
|
|
246
|
+
'expect(result).toBeDefined();',
|
|
247
|
+
'expect(typeof result).toBe(\'string\');'
|
|
248
|
+
]
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: 'should handle null input',
|
|
252
|
+
arrange: `// Arrange - null 输入`,
|
|
253
|
+
act: `// Act & Assert`,
|
|
254
|
+
assert: [
|
|
255
|
+
`expect(() => ${exportName}(null)).not.toThrow();`,
|
|
256
|
+
'const result = ${exportName}(null);',
|
|
257
|
+
'expect(result).toBeDefined();'
|
|
258
|
+
]
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: 'should handle empty string input',
|
|
262
|
+
arrange: `// Arrange - 空字符串输入`,
|
|
263
|
+
act: `// Act
|
|
264
|
+
const result = ${exportName}('');`,
|
|
265
|
+
assert: [
|
|
266
|
+
'expect(result).toBeDefined();',
|
|
267
|
+
'// 验证空字符串的预期行为'
|
|
268
|
+
]
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
name: 'should handle various input types',
|
|
272
|
+
arrange: `// Arrange - 多种输入类型测试`,
|
|
273
|
+
act: `// Act - 测试不同类型`,
|
|
274
|
+
assert: [
|
|
275
|
+
'const numResult = ${exportName}(123);',
|
|
276
|
+
'const boolResult = ${exportName}(true);',
|
|
277
|
+
'const objResult = ${exportName}({ key: \'value\' });',
|
|
278
|
+
'expect(numResult).toBeDefined();',
|
|
279
|
+
'expect(boolResult).toBeDefined();',
|
|
280
|
+
'expect(objResult).toBeDefined();'
|
|
281
|
+
]
|
|
282
|
+
}
|
|
283
|
+
];
|
|
284
|
+
case 'component':
|
|
285
|
+
return [
|
|
286
|
+
{
|
|
287
|
+
name: 'should render without crashing',
|
|
288
|
+
arrange: `// Arrange - 准备组件 props
|
|
289
|
+
const props = { title: \'Test Title\', onClick: vi.fn() };`,
|
|
290
|
+
act: `// Act - 渲染组件
|
|
291
|
+
const { container } = render(<${exportName} {...props} />);`,
|
|
292
|
+
assert: [
|
|
293
|
+
'expect(container).toBeDefined();',
|
|
294
|
+
'expect(container.firstChild).toBeInTheDocument();'
|
|
295
|
+
]
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: 'should handle user interaction',
|
|
299
|
+
arrange: `// Arrange - 准备交互测试
|
|
300
|
+
const handleClick = vi.fn();
|
|
301
|
+
const props = { onClick: handleClick };`,
|
|
302
|
+
act: `// Act - 模拟用户点击
|
|
303
|
+
const { getByRole } = render(<${exportName} {...props} />);
|
|
304
|
+
fireEvent.click(getByRole(\'button\'));`,
|
|
305
|
+
assert: [
|
|
306
|
+
'expect(handleClick).toHaveBeenCalledTimes(1);'
|
|
307
|
+
]
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
name: 'should display correct content with props',
|
|
311
|
+
arrange: `// Arrange - 准备显示内容测试
|
|
312
|
+
const testContent = \'Hello World\';
|
|
313
|
+
const props = { children: testContent };`,
|
|
314
|
+
act: `// Act - 渲染并检查内容
|
|
315
|
+
const { getByText } = render(<${exportName} {...props} />);`,
|
|
316
|
+
assert: [
|
|
317
|
+
'expect(getByText(testContent)).toBeInTheDocument();'
|
|
318
|
+
]
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
name: 'should handle missing optional props',
|
|
322
|
+
arrange: `// Arrange - 无可选 props`,
|
|
323
|
+
act: `// Act - 仅传递必需 props
|
|
324
|
+
const { container } = render(<${exportName} />);`,
|
|
325
|
+
assert: [
|
|
326
|
+
'expect(container).toBeDefined();',
|
|
327
|
+
'// 组件应使用默认值正常渲染'
|
|
328
|
+
]
|
|
329
|
+
}
|
|
330
|
+
];
|
|
331
|
+
case 'api':
|
|
332
|
+
return [
|
|
333
|
+
{
|
|
334
|
+
name: 'should return success response for valid request',
|
|
335
|
+
arrange: `// Arrange - 准备有效的 API 请求参数
|
|
336
|
+
const request = { method: \'GET\', path: \'/api/test\' };`,
|
|
337
|
+
act: `// Act - 执行 API 调用
|
|
338
|
+
const response = await ${exportName}(request);`,
|
|
339
|
+
assert: [
|
|
340
|
+
'expect(response).toBeDefined();',
|
|
341
|
+
'expect(response.status).toBe(200);',
|
|
342
|
+
'expect(response.data).toBeDefined();'
|
|
343
|
+
]
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
name: 'should handle 404 not found',
|
|
347
|
+
arrange: `// Arrange - 准备不存在的资源请求
|
|
348
|
+
const request = { method: \'GET\', path: \'/api/nonexistent\' };`,
|
|
349
|
+
act: `// Act - 请求不存在的资源
|
|
350
|
+
const response = await ${exportName}(request);`,
|
|
351
|
+
assert: [
|
|
352
|
+
'expect(response.status).toBe(404);',
|
|
353
|
+
'expect(response.error).toBeDefined();'
|
|
354
|
+
]
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
name: 'should handle invalid request body',
|
|
358
|
+
arrange: `// Arrange - 准备无效的请求体
|
|
359
|
+
const request = { method: \'POST\', path: \'/api/test\', body: null };`,
|
|
360
|
+
act: `// Act - 发送无效请求
|
|
361
|
+
const response = await ${exportName}(request);`,
|
|
362
|
+
assert: [
|
|
363
|
+
'expect(response.status).toBe(400);',
|
|
364
|
+
'expect(response.error).toContain(\'invalid\');'
|
|
365
|
+
]
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
name: 'should handle network errors gracefully',
|
|
369
|
+
arrange: `// Arrange - 模拟网络错误
|
|
370
|
+
const request = { method: \'GET\', path: \'/api/error\' };
|
|
371
|
+
// Mock network failure`,
|
|
372
|
+
act: `// Act - 处理错误响应`,
|
|
373
|
+
assert: [
|
|
374
|
+
`await expect(${exportName}(request)).resolves.toBeDefined();`,
|
|
375
|
+
'// 应该返回错误对象而非抛出异常'
|
|
376
|
+
]
|
|
377
|
+
}
|
|
378
|
+
];
|
|
379
|
+
default:
|
|
380
|
+
// 通用模块测试
|
|
381
|
+
return [
|
|
382
|
+
{
|
|
383
|
+
name: 'should return defined result for valid input',
|
|
384
|
+
arrange: `// Arrange - 准备有效输入
|
|
385
|
+
const input = {};`,
|
|
386
|
+
act: `// Act - 执行函数
|
|
387
|
+
const result = ${exportName}(input);`,
|
|
388
|
+
assert: [
|
|
389
|
+
'expect(result).toBeDefined();'
|
|
390
|
+
]
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
name: 'should handle null input',
|
|
394
|
+
arrange: `// Arrange - null 输入`,
|
|
395
|
+
act: `// Act`,
|
|
396
|
+
assert: [
|
|
397
|
+
`expect(() => ${exportName}(null)).not.toThrow();`
|
|
398
|
+
]
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
name: 'should handle undefined input',
|
|
402
|
+
arrange: `// Arrange - undefined 输入`,
|
|
403
|
+
act: `// Act`,
|
|
404
|
+
assert: [
|
|
405
|
+
`expect(() => ${exportName}(undefined)).not.toThrow();`
|
|
406
|
+
]
|
|
407
|
+
}
|
|
408
|
+
];
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* 生成单个测试用例块
|
|
413
|
+
*/
|
|
414
|
+
function generateTestCaseBlock(tc, exportName) {
|
|
415
|
+
const indent = ' ';
|
|
416
|
+
const actIndent = tc.act.includes('\n') ? indent : indent + ' ';
|
|
417
|
+
const assertLines = tc.assert.map(a => `${indent} ${a}`).join('\n');
|
|
418
|
+
return `${indent}it('${tc.name}', () => {
|
|
419
|
+
${indent} ${tc.arrange}
|
|
420
|
+
${actIndent}${tc.act}
|
|
421
|
+
${assertLines}
|
|
422
|
+
${indent}});`;
|
|
423
|
+
}
|
|
200
424
|
/**
|
|
201
425
|
* 获取 describe 标题
|
|
202
426
|
*/
|
package/package.json
CHANGED
package/skills/test.md
CHANGED
|
@@ -392,6 +392,35 @@ ${uiComponents.map(c => `
|
|
|
392
392
|
|
|
393
393
|
## Step 7: 自动验证测试(循环机制)
|
|
394
394
|
|
|
395
|
+
<LOOP_ENFORCEMENT>
|
|
396
|
+
**此步骤是验证循环,必须执行直到验证通过或达到最大重试次数。**
|
|
397
|
+
|
|
398
|
+
❌ **禁止在验证失败时停止** — 即使 Agent 返回了大段输出,也必须继续循环
|
|
399
|
+
❌ **禁止询问"是否继续验证"** — 自动执行验证,无需用户确认
|
|
400
|
+
❌ **禁止输出"让我知道是否..."后停止** — 继续执行验证循环
|
|
401
|
+
❌ **禁止因为上下文压缩而忘记重试次数** — 使用 TodoWrite 持久化 retryCount
|
|
402
|
+
|
|
403
|
+
**retryCount 持久化机制(防止上下文压缩丢失状态):**
|
|
404
|
+
|
|
405
|
+
验证循环开始时,使用 TodoWrite 记录状态:
|
|
406
|
+
```
|
|
407
|
+
TodoWrite([
|
|
408
|
+
{ content: "验证测试(第 N 次)", activeForm: "正在验证测试(第 N 次)", status: "in_progress" }
|
|
409
|
+
])
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
每次验证失败后:
|
|
413
|
+
1. 更新 TodoWrite 的 retryCount(N → N+1)
|
|
414
|
+
2. 检查是否达到最大次数(>= 3)
|
|
415
|
+
3. 未达到 → 自动回到 Step 6 重新生成
|
|
416
|
+
4. 已达到 → 暂停并报告问题
|
|
417
|
+
|
|
418
|
+
**循环铁律:**
|
|
419
|
+
- retryCount 从 TodoWrite 读取,不依赖上下文记忆
|
|
420
|
+
- 验证失败必须自动循环,不得跳过
|
|
421
|
+
- 最大重试 3 次,超过必须暂停
|
|
422
|
+
</LOOP_ENFORCEMENT>
|
|
423
|
+
|
|
395
424
|
**⛔ 自动验证流程 - 无需用户手动确认**
|
|
396
425
|
|
|
397
426
|
### 7.1 执行验证命令(自动判断结果)
|
|
@@ -414,15 +443,15 @@ fi
|
|
|
414
443
|
**验证失败时自动处理:**
|
|
415
444
|
|
|
416
445
|
```
|
|
417
|
-
验证失败 →
|
|
446
|
+
验证失败 → TodoWrite 更新 retryCount → 检查重试次数 →
|
|
418
447
|
├─ < 3 次 → 自动回到 Step 6 重新生成(带失败信息)
|
|
419
448
|
└─ >= 3 次 → 暂停,报告问题,可能是测试框架配置问题
|
|
420
449
|
```
|
|
421
450
|
|
|
422
|
-
|
|
423
|
-
-
|
|
424
|
-
-
|
|
425
|
-
-
|
|
451
|
+
**重试计数器持久化:**
|
|
452
|
+
- 使用 TodoWrite 记录 retryCount
|
|
453
|
+
- 每次验证失败后更新 TodoWrite(retryCount + 1)
|
|
454
|
+
- 验证通过后更新 TodoWrite 为 completed
|
|
426
455
|
|
|
427
456
|
### 7.3 自动循环回生成步骤
|
|
428
457
|
|