@h-ai/cli 0.1.0-alpha.53 → 0.1.0-alpha.55

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 (85) hide show
  1. package/README.md +18 -0
  2. package/dist/{chunk-U4PA632G.js → chunk-FVDXJYMO.js} +34 -67
  3. package/dist/chunk-FVDXJYMO.js.map +1 -0
  4. package/dist/cli-config-templates-WGN2I23J.js +5 -0
  5. package/dist/{cli-config-templates-GSXKLGU5.js.map → cli-config-templates-WGN2I23J.js.map} +1 -1
  6. package/dist/index.js +174 -105
  7. package/dist/index.js.map +1 -1
  8. package/dist/{src-QNEJJ3BV.js → src-6ZBWDBUB.js} +127 -87
  9. package/dist/{src-QNEJJ3BV.js.map → src-6ZBWDBUB.js.map} +1 -1
  10. package/package.json +13 -4
  11. package/templates/apps/api/apps/{{projectName}}-contract/package.json.hbs +1 -0
  12. package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -0
  13. package/templates/apps/api/package.json.hbs +1 -0
  14. package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +1 -0
  15. package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +1 -0
  16. package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +1 -0
  17. package/templates/apps/fullstack/package.json.hbs +1 -0
  18. package/templates/apps/fullstack/packages/{{projectName}}-contract/package.json.hbs +1 -0
  19. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +1 -0
  20. package/templates/apps/fullstack/packages/{{projectName}}-shared/package.json.hbs +1 -0
  21. package/templates/base/eslint.config.js.hbs +1 -0
  22. package/templates/base/package.json.hbs +3 -1
  23. package/templates/skills/AGENTS.md +8 -22
  24. package/templates/skills/CLAUDE.md +3 -16
  25. package/templates/skills/bridges/admin/AGENTS.md +3 -16
  26. package/templates/skills/bridges/admin/CLAUDE.md +3 -11
  27. package/templates/skills/bridges/admin/copilot-instructions.md +2 -39
  28. package/templates/skills/bridges/api/AGENTS.md +5 -18
  29. package/templates/skills/bridges/api/CLAUDE.md +3 -11
  30. package/templates/skills/bridges/api/copilot-instructions.md +3 -38
  31. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +24 -59
  32. package/templates/skills/bridges/fullstack/CLAUDE.md.hbs +1 -15
  33. package/templates/skills/bridges/fullstack/copilot-instructions.md.hbs +5 -45
  34. package/templates/skills/bridges/h5/AGENTS.md +4 -17
  35. package/templates/skills/bridges/h5/CLAUDE.md +3 -11
  36. package/templates/skills/bridges/h5/copilot-instructions.md +2 -37
  37. package/templates/skills/bridges/mobile-app/AGENTS.md +5 -18
  38. package/templates/skills/bridges/mobile-app/CLAUDE.md +3 -11
  39. package/templates/skills/bridges/mobile-app/copilot-instructions.md +2 -38
  40. package/templates/skills/bridges/website/AGENTS.md +4 -17
  41. package/templates/skills/bridges/website/CLAUDE.md +3 -11
  42. package/templates/skills/bridges/website/copilot-instructions.md +2 -37
  43. package/templates/skills/copilot-instructions.md +2 -36
  44. package/templates/skills/hai-ai/SKILL.md +35 -389
  45. package/templates/skills/hai-ai/guide.md +383 -0
  46. package/templates/skills/hai-ai/reference.md +6 -2
  47. package/templates/skills/hai-api-client/SKILL.md +6 -6
  48. package/templates/skills/hai-api-contract/SKILL.md +10 -25
  49. package/templates/skills/hai-app-create/SKILL.md +29 -388
  50. package/templates/skills/hai-app-create/reference.md +350 -0
  51. package/templates/skills/hai-app-review/SKILL.md +15 -23
  52. package/templates/skills/hai-app-tests/SKILL.md +29 -494
  53. package/templates/skills/hai-app-tests/reference.md +442 -0
  54. package/templates/skills/hai-audit/SKILL.md +6 -6
  55. package/templates/skills/hai-build/SKILL.md +19 -330
  56. package/templates/skills/hai-build/reference.md +326 -0
  57. package/templates/skills/hai-cache/SKILL.md +28 -31
  58. package/templates/skills/hai-capacitor/SKILL.md +19 -364
  59. package/templates/skills/hai-capacitor/reference.md +352 -0
  60. package/templates/skills/hai-ci/SKILL.md +17 -20
  61. package/templates/skills/hai-core/SKILL.md +25 -564
  62. package/templates/skills/hai-core/reference.md +533 -0
  63. package/templates/skills/hai-crypto/SKILL.md +8 -20
  64. package/templates/skills/hai-datapipe/SKILL.md +9 -23
  65. package/templates/skills/hai-deploy/SKILL.md +18 -24
  66. package/templates/skills/hai-framework-sync/SKILL.md +8 -20
  67. package/templates/skills/hai-iam/SKILL.md +27 -365
  68. package/templates/skills/hai-iam/reference.md +343 -0
  69. package/templates/skills/hai-kit/SKILL.md +6 -6
  70. package/templates/skills/hai-payment/SKILL.md +6 -26
  71. package/templates/skills/hai-pr-review/SKILL.md +6 -22
  72. package/templates/skills/hai-reach/SKILL.md +19 -317
  73. package/templates/skills/hai-reach/reference.md +303 -0
  74. package/templates/skills/hai-reldb/SKILL.md +19 -472
  75. package/templates/skills/hai-reldb/reference.md +452 -0
  76. package/templates/skills/hai-scheduler/SKILL.md +18 -6
  77. package/templates/skills/hai-serv/SKILL.md +27 -759
  78. package/templates/skills/hai-serv/reference.md +736 -0
  79. package/templates/skills/hai-storage/SKILL.md +7 -25
  80. package/templates/skills/hai-ui/SKILL.md +29 -621
  81. package/templates/skills/hai-ui/reference.md +598 -0
  82. package/templates/skills/hai-vecdb/SKILL.md +18 -303
  83. package/templates/skills/hai-vecdb/reference.md +291 -0
  84. package/dist/chunk-U4PA632G.js.map +0 -1
  85. package/dist/cli-config-templates-GSXKLGU5.js +0 -5
@@ -1,503 +1,38 @@
1
1
  ---
2
2
  name: hai-app-tests
3
- description: 以 TDD 驱动应用开发:先分析需求、生成单元测试(Vitest)与 E2E 测试(Playwright),确认测试全部失败后再编码实现直至测试通过;当需求涉及应用测试、TDD 流程、覆盖率提升、E2E 测试时使用。
3
+ description: "编写或修复 hai 应用的 Vitest/Playwright 测试,验证业务边界和完整用户流程。"
4
4
  ---
5
5
 
6
- # hai-app-tests — TDD 驱动的应用测试规范
6
+ # hai-app-tests
7
7
 
8
8
  ## 能力契约
9
9
 
10
10
  | 项目 | 契约 |
11
11
  | --- | --- |
12
- | 能力 | 以 TDD 驱动应用开发:先分析需求、生成单元测试(Vitest)与 E2E 测试(Playwright),确认测试全部失败后再编码实现直至测试通过;当需求涉及应用测试、TDD 流程、覆盖率提升、E2E 测试时使用。 |
13
- | 适用场景 | 当任务与 `hai-app-tests` 的能力描述匹配,并且需要遵循本 Skill 的流程和边界时 |
14
- | 输入 | 模块配置、类型化业务参数、依赖初始化状态和目标运行环境 |
15
- | 输出 | 符合模块公共 API 的实现或示例;业务结果使用 HaiResult,并同步必要测试与文档 |
16
- | 限制 | 遵守 init → use → close 生命周期与运行环境边界;不绕过类型、授权、输入校验或敏感信息保护 |
17
-
18
- > 面向 AI 助手的 TDD 测试指南。**测试先行**:先写测试定义行为,再写实现让测试通过。涵盖 Vitest 单元测试与 Playwright E2E 测试。
19
-
20
- ---
21
-
22
- ## 适用场景
23
-
24
- - 以 TDD 方式开发新功能(先写测试 → 确认失败 → 再实现 → 确认通过)
25
- - 为 `$lib/server/services/` 编写服务层单元测试
26
- - 为 API 端点编写 E2E 测试(Playwright)
27
- - 为页面交互编写 UI E2E 测试(Playwright)
28
- - 为工具函数、Schema 校验编写单元测试
29
- - 需要提升测试覆盖率
30
-
31
- ---
32
-
33
- ## TDD 核心工作流(必须严格遵循)
34
-
35
- ### 阶段一:需求分析(Red 前准备)
36
-
37
- 1. **理解需求**:明确功能的输入、输出、边界条件、错误场景
38
- 2. **拆分测试点**:将需求转化为可验证的测试用例清单
39
- 3. **确定测试类型**:
40
- - **单元测试(Vitest)**:服务层、工具函数、Schema 校验、HaiResult 处理
41
- - **E2E 测试(Playwright)**:API 端点、页面交互、认证流程、跨模块集成
42
-
43
- ### 阶段二:Red — 编写失败的测试
44
-
45
- 1. **编写单元测试**:覆盖正常路径、边界路径、权限路径、错误路径
46
- 2. **编写 E2E 测试**:覆盖 API 端点和关键用户交互流程
47
- 3. **运行测试确认全部失败**:
48
- ```bash
49
- pnpm --filter <app-name> test # 单元测试 — 应全部 FAIL
50
- pnpm --filter <app-name> test:e2e # E2E 测试 — 应全部 FAIL
51
- ```
52
- 4. 如果测试意外通过,说明测试没有测到新功能,需要修正测试
53
-
54
- ### 阶段三:Green — 最小实现
55
-
56
- 1. 参照 `hai-app-create` 技能编写实现代码
57
- 2. 每实现一个功能点,立即运行对应测试确认通过
58
- 3. 只写让测试通过的最小代码,不做过度设计
59
- 4. 运行全部测试确认通过:
60
- ```bash
61
- pnpm --filter <app-name> test # 单元测试 — 应全部 PASS
62
- pnpm --filter <app-name> test:e2e # E2E 测试 — 应全部 PASS
63
- ```
64
-
65
- ### 阶段四:Refactor — 重构优化
66
-
67
- 1. 参照 `hai-app-review` 技能审查代码规范
68
- 2. 重构后再次运行所有测试确认仍然通过
69
- 3. 运行质量门禁:`pnpm typecheck && pnpm lint && pnpm test`
70
-
71
- ---
72
-
73
- ## 测试目录约定
74
-
75
- ```
76
- tests/ # 单元测试(Vitest)
77
- services/
78
- user.test.ts # 服务层测试
79
- article.test.ts
80
- schemas/
81
- user-schema.test.ts # Schema 校验测试
82
- utils/
83
- helper.test.ts # 工具函数测试
84
- e2e/ # E2E 测试(Playwright)
85
- helpers.ts # E2E 共用工具函数
86
- health.spec.ts # 健康检查 API
87
- auth-api.spec.ts # 认证 API 测试
88
- auth-ui.spec.ts # 认证页面 UI 测试
89
- users-api.spec.ts # 用户管理 API 测试
90
- users-ui.spec.ts # 用户管理页面 UI 测试
91
- ```
92
-
93
- ---
94
-
95
- ## 核心规则
96
-
97
- 1. **测试先行**:先写测试定义预期行为,再写实现代码。
98
- 2. **统一入口**:通过框架模块公共 API 测试(`reldb.sql`、`iam.authn`),不直接调用内部实现。
99
- 3. **从实际场景出发**:测试用例验证真实业务行为,不做形式覆盖。
100
- 4. **先审查后修复**:测试不通过时先审查业务逻辑是否有问题,不直接修改测试迎合通过。
101
- 5. **Mock 外部依赖**:单元测试中,数据库、缓存、网络请求使用 mock,不直连真实服务。
102
- 6. **E2E 真实环境**:E2E 测试运行在完整应用环境中,不使用 mock。
103
-
104
- ---
105
-
106
- ## 覆盖范围
107
-
108
- 每个功能至少覆盖以下路径(单元测试 + E2E 按需):
109
-
110
- | 路径 | 说明 | 单元测试示例 | E2E 测试示例 |
111
- | -------- | ------------------------ | ------------------ | ----------------------- |
112
- | 正常路径 | 核心功能可用 | 创建用户成功 | POST /api/users → 201 |
113
- | 边界路径 | 非法输入、空值、格式错误 | 空名称、超长字符串 | 空表单提交 → 校验提示 |
114
- | 权限路径 | 未认证、无权限 | 未登录调用服务 | 未登录访问 → 401/重定向 |
115
- | 错误路径 | 服务异常、数据库错误 | DB 失败时的 HaiResult | 服务不可用 → 错误提示页 |
116
-
117
- ---
118
-
119
- ## 单元测试(Vitest)
120
-
121
- ### 服务层测试模板
122
-
123
- ```typescript
124
- import { createArticle, listArticles } from '$lib/server/services/article'
125
- // tests/services/article.test.ts
126
- import { reldb } from '@h-ai/reldb'
127
- import { describe, expect, it, vi } from 'vitest'
128
-
129
- // Mock 依赖模块
130
- vi.mock('@h-ai/reldb', () => ({
131
- db: {
132
- sql: { query: vi.fn(), execute: vi.fn() },
133
- crud: { create: vi.fn(), findById: vi.fn(), paginate: vi.fn() },
134
- },
135
- }))
136
-
137
- vi.mock('@h-ai/core', () => ({
138
- core: { logger: { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() } },
139
- }))
140
-
141
- describe('ArticleService', () => {
142
- // 正常路径
143
- it('should create article successfully', async () => {
144
- const mockArticle = { id: '1', title: 'Test', authorId: 'u1' }
145
- vi.mocked(reldb.crud.create).mockResolvedValue({ success: true, data: mockArticle })
146
-
147
- const result = await createArticle({ title: 'Test', authorId: 'u1' })
148
-
149
- expect(result.success).toBe(true)
150
- if (result.success) {
151
- expect(result.data.title).toBe('Test')
152
- }
153
- })
154
-
155
- // 边界路径
156
- it('should return error for empty title', async () => {
157
- const result = await createArticle({ title: '', authorId: 'u1' })
158
-
159
- expect(result.success).toBe(false)
160
- if (!result.success) {
161
- expect(result.error.code).toBeDefined()
162
- }
163
- })
164
-
165
- // 错误路径
166
- it('should return error when db fails', async () => {
167
- vi.mocked(reldb.crud.create).mockResolvedValue({
168
- success: false,
169
- error: { code: 'DB_ERROR', message: 'Connection lost' },
170
- })
171
-
172
- const result = await createArticle({ title: 'Test', authorId: 'u1' })
173
-
174
- expect(result.success).toBe(false)
175
- })
176
- })
177
- ```
178
-
179
- ### Schema 校验测试模板
180
-
181
- ```typescript
182
- import { CreateUserSchema } from '$lib/server/schemas/user'
183
- // tests/schemas/user-schema.test.ts
184
- import { describe, expect, it } from 'vitest'
185
-
186
- describe('CreateUserSchema', () => {
187
- it('should accept valid input', () => {
188
- const result = CreateUserSchema.safeParse({
189
- username: 'testuser',
190
- email: 'test@example.com',
191
- password: '12345678',
192
- })
193
- expect(result.success).toBe(true)
194
- })
195
-
196
- it('should reject short username', () => {
197
- const result = CreateUserSchema.safeParse({
198
- username: 'ab',
199
- email: 'test@example.com',
200
- password: '12345678',
201
- })
202
- expect(result.success).toBe(false)
203
- })
204
-
205
- it('should reject invalid email', () => {
206
- const result = CreateUserSchema.safeParse({
207
- username: 'testuser',
208
- email: 'not-email',
209
- password: '12345678',
210
- })
211
- expect(result.success).toBe(false)
212
- })
213
- })
214
- ```
215
-
216
- ---
217
-
218
- ## E2E 测试(Playwright)
219
-
220
- ### Playwright 配置
221
-
222
- 项目中应有 `playwright.config.ts`(由 CLI 生成):
223
-
224
- ```typescript
225
- // playwright.config.ts
226
- import process from 'node:process'
227
- import { defineConfig } from '@playwright/test'
228
-
229
- const DEFAULT_BASE_URL = 'http://localhost:4173'
230
- const baseURL = process.env.BASE_URL || DEFAULT_BASE_URL
231
- const previewHost = resolveHostFromBaseUrl(baseURL, 'localhost')
232
- const previewPort = resolvePortFromBaseUrl(baseURL, '4173')
233
-
234
- function resolveHostFromBaseUrl(urlString: string, fallbackHost: string): string {
235
- try {
236
- const url = new URL(urlString)
237
- return url.hostname || fallbackHost
238
- }
239
- catch {
240
- return fallbackHost
241
- }
242
- }
243
-
244
- function resolvePortFromBaseUrl(urlString: string, fallbackPort: string): string {
245
- try {
246
- const url = new URL(urlString)
247
- if (url.port)
248
- return url.port
249
- return url.protocol === 'https:' ? '443' : '80'
250
- }
251
- catch {
252
- return fallbackPort
253
- }
254
- }
255
-
256
- export default defineConfig({
257
- testDir: './e2e',
258
- fullyParallel: false,
259
- forbidOnly: !!process.env.CI,
260
- retries: process.env.CI ? 2 : 0,
261
- workers: 2,
262
- reporter: 'list',
263
- timeout: 30_000,
264
- use: {
265
- baseURL,
266
- channel: 'chrome',
267
- trace: 'on-first-retry',
268
- screenshot: 'only-on-failure',
269
- },
270
- webServer: {
271
- command: `pnpm build && pnpm preview --host ${previewHost} --port ${previewPort} --strictPort`,
272
- env: { HAI_E2E: '1', BASE_URL: baseURL },
273
- url: baseURL,
274
- reuseExistingServer: false,
275
- timeout: 180_000,
276
- },
277
- })
278
- ```
279
-
280
- ### E2E 工具函数模板
281
-
282
- ```typescript
283
- // e2e/helpers.ts
284
- import type { APIRequestContext, Page } from '@playwright/test'
285
-
286
- /** 生成唯一测试用户 */
287
- export function uniqueUser(prefix = 'e2e') {
288
- const safePrefix = (prefix.replace(/\W/g, '') || 'e2e').slice(0, 8)
289
- const entropy = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`
290
- const id = entropy.slice(-10)
291
- const username = `${safePrefix}_${id}`.slice(0, 20)
292
- return {
293
- username,
294
- email: `${safePrefix}_${id}@test.local`,
295
- password: 'Test1234!@',
296
- }
297
- }
298
-
299
- /** 通过 API 注册用户 */
300
- export async function registerViaApi(
301
- request: APIRequestContext,
302
- user: ReturnType<typeof uniqueUser>,
303
- ) {
304
- return request.post('/api/auth/register', {
305
- data: {
306
- username: user.username,
307
- email: user.email,
308
- password: user.password,
309
- confirmPassword: user.password,
310
- },
311
- })
312
- }
313
-
314
- /** 注册并登录,返回已认证的 Page */
315
- export async function registerAndLogin(
316
- page: Page,
317
- request: APIRequestContext,
318
- prefix = 'e2e',
319
- ) {
320
- const user = uniqueUser(prefix)
321
- await registerViaApi(request, user)
322
- await page.goto('/login')
323
- await page.fill('[name="username"]', user.username)
324
- await page.fill('[name="password"]', user.password)
325
- await page.click('button[type="submit"]')
326
- await page.waitForURL('/')
327
- return user
328
- }
329
- ```
330
-
331
- ### API 端点 E2E 测试模板
332
-
333
- ```typescript
334
- // e2e/articles-api.spec.ts
335
- import { expect, test } from '@playwright/test'
336
- import { registerAndLogin } from './helpers'
337
-
338
- test.describe('Articles API', () => {
339
- test('GET /api/articles 未认证返回 401', async ({ request }) => {
340
- const response = await request.get('/api/articles')
341
- expect(response.status()).toBe(401)
342
- })
343
-
344
- test('POST /api/articles 创建文章成功', async ({ page, request }) => {
345
- await registerAndLogin(page, request, 'article')
346
-
347
- const response = await page.request.post('/api/articles', {
348
- data: { title: 'E2E Test Article', content: 'Test content' },
349
- })
350
- expect(response.status()).toBe(201)
351
-
352
- const body = await response.json()
353
- expect(body.success).toBe(true)
354
- expect(body.data.title).toBe('E2E Test Article')
355
- })
356
-
357
- test('POST /api/articles 空标题返回 400', async ({ page, request }) => {
358
- await registerAndLogin(page, request, 'article')
359
-
360
- const response = await page.request.post('/api/articles', {
361
- data: { title: '', content: '' },
362
- })
363
- expect(response.status()).toBe(400)
364
- })
365
-
366
- test('GET /api/articles 认证后获取列表', async ({ page, request }) => {
367
- await registerAndLogin(page, request, 'article')
368
-
369
- const response = await page.request.get('/api/articles')
370
- expect(response.ok()).toBeTruthy()
371
-
372
- const body = await response.json()
373
- expect(body.success).toBe(true)
374
- expect(Array.isArray(body.data)).toBe(true)
375
- })
376
- })
377
- ```
378
-
379
- ### 页面 UI E2E 测试模板
380
-
381
- ```typescript
382
- // e2e/articles-ui.spec.ts
383
- import { expect, test } from '@playwright/test'
384
- import { registerAndLogin } from './helpers'
385
-
386
- test.describe('Articles Page', () => {
387
- test('未登录跳转登录页', async ({ page }) => {
388
- await page.goto('/admin/articles')
389
- await expect(page).toHaveURL(/login/)
390
- })
391
-
392
- test('已登录显示文章列表', async ({ page, request }) => {
393
- await registerAndLogin(page, request, 'artui')
394
- await page.goto('/admin/articles')
395
-
396
- await expect(page.locator('h1, h2').first()).toContainText(/文章|article/i)
397
- })
398
-
399
- test('创建文章表单提交', async ({ page, request }) => {
400
- await registerAndLogin(page, request, 'artui')
401
- await page.goto('/admin/articles/new')
402
-
403
- await page.fill('[name="title"]', 'E2E Article')
404
- await page.fill('[name="content"]', 'E2E content')
405
- await page.click('button[type="submit"]')
406
-
407
- // 验证跳转或成功提示
408
- await expect(page.locator('.alert-success, .toast')).toBeVisible({ timeout: 5000 })
409
- })
410
- })
411
- ```
412
-
413
- ---
414
-
415
- ## TDD 用例设计策略
416
-
417
- ### 从需求到测试用例的转换
418
-
419
- ```
420
- 需求:用户可以创建文章,标题必填且不超过 100 字符,需要登录后才能操作
421
-
422
- ↓ 拆分测试点
423
-
424
- 单元测试(Vitest):
425
- ✅ 创建文章成功返回 HaiResult.ok
426
- ✅ 空标题返回 HaiResult.err + VALIDATION_FAILED
427
- ✅ 超长标题(>100 字符)返回 HaiResult.err
428
- ✅ DB 异常时透传错误
429
-
430
- Schema 测试(Vitest):
431
- ✅ 合法输入通过校验
432
- ✅ 空标题被拒绝
433
- ✅ 超长标题被拒绝
434
- ✅ 缺少必填字段被拒绝
435
-
436
- E2E 测试(Playwright):
437
- ✅ POST /api/articles 未认证 → 401
438
- ✅ POST /api/articles 合法数据 → 201
439
- ✅ POST /api/articles 空标题 → 400
440
- ✅ 页面表单提交成功 → 跳转/提示
441
- ✅ 未登录访问创建页 → 重定向登录
442
- ```
443
-
444
- ---
445
-
446
- ## 断言规范
447
-
448
- - 始终校验 `result.success`
449
- - 失败时校验 `error.code`(不只检查 `success === false`)
450
- - 使用类型缩窄后访问 data 或 error
451
- - E2E 断言使用 Playwright 内置 expect 匹配器
452
-
453
- ```typescript
454
- // ✅ 单元测试断言
455
- const result = await service.create(input)
456
- expect(result.success).toBe(true)
457
- if (result.success) {
458
- expect(result.data.id).toBeDefined()
459
- }
460
-
461
- // ✅ 单元测试错误断言
462
- const result = await service.create(invalidInput)
463
- expect(result.success).toBe(false)
464
- if (!result.success) {
465
- expect(result.error.code).toBe('VALIDATION_FAILED')
466
- }
467
-
468
- // ✅ E2E API 断言
469
- const response = await request.post('/api/articles', { data: input })
470
- expect(response.status()).toBe(201)
471
- const body = await response.json()
472
- expect(body.success).toBe(true)
473
-
474
- // ✅ E2E UI 断言
475
- await expect(page.locator('.alert-success')).toBeVisible()
476
- await expect(page).toHaveURL('/admin/articles')
477
- ```
478
-
479
- ---
480
-
481
- ## 质量门禁
482
-
483
- ```bash
484
- # 运行单元测试
485
- pnpm --filter <app-name> test
486
-
487
- # 运行 E2E 测试
488
- pnpm --filter <app-name> test:e2e
489
-
490
- # 覆盖率
491
- pnpm --filter <app-name> test:coverage
492
-
493
- # 全量门禁
494
- pnpm typecheck && pnpm lint && pnpm test
495
- ```
496
-
497
- ---
498
-
499
- ## 相关 Skills
500
-
501
- - `hai-app-create`:应用功能创建规范(TDD Green 阶段参照)
502
- - `hai-app-review`:应用代码审查规范(TDD Refactor 阶段参照)
503
- - `hai-build`:项目架构总览与 TDD 工作流导航
12
+ | 能力 | 编写或修复 hai 应用的 Vitest/Playwright 测试,验证业务边界和完整用户流程 |
13
+ | 适用场景 | 需求验收、缺陷回归或应用测试改进 |
14
+ | 输入 | 验收条件、被测公共入口、现有测试和运行环境 |
15
+ | 输出 | 可复现用例及明确区分静态、mock、浏览器/外部服务的结果 |
16
+ | 限制 | Red 阶段仅要求新增目标用例因行为缺失而失败,不要求旧测试失败;不用删断言或修改预期掩盖缺陷。 |
17
+
18
+ ## 使用路径
19
+
20
+ - 先选单元/集成/浏览器层,复用测试 fixture,验证用户可观察结果。
21
+ - 新用例已通过时先检查是否已有能力;不能为达成 Red 人为破坏旧测试。
22
+ - 外部服务未运行必须报告;mock/Testcontainers/浏览器/真实付费 Provider 的结果分开表述。
23
+
24
+ ## 按需参考
25
+
26
+ 详细配置、API 签名、错误码和范本在 [reference.md](reference.md)。按下列标题定位所需小节,不默认通读;用例中的业务变量需结合当前应用补齐。
27
+
28
+ - 适用场景
29
+ - TDD 核心工作流(必须严格遵循)
30
+ - 测试目录约定
31
+ - 核心规则
32
+ - 覆盖范围
33
+ - 单元测试(Vitest)
34
+ - E2E 测试(Playwright)
35
+ - TDD 用例设计策略
36
+ - 断言规范
37
+ - 质量门禁
38
+ - 相关 Skills