@kkeve/suite 0.0.1 → 0.0.2

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 (152) hide show
  1. package/README.md +53 -0
  2. package/dist/bin/keve.d.ts +13 -0
  3. package/dist/bin/keve.js +164 -0
  4. package/dist/commands/auth-update.d.ts +16 -0
  5. package/dist/commands/auth-update.js +88 -0
  6. package/dist/commands/clean.d.ts +10 -0
  7. package/dist/commands/clean.js +78 -0
  8. package/dist/commands/config.d.ts +17 -0
  9. package/dist/commands/config.js +99 -0
  10. package/dist/commands/init.d.ts +15 -0
  11. package/dist/commands/init.js +292 -0
  12. package/dist/commands/report.d.ts +12 -0
  13. package/dist/commands/report.js +159 -0
  14. package/dist/commands/run.d.ts +29 -0
  15. package/dist/commands/run.js +417 -0
  16. package/dist/commands/test.d.ts +12 -0
  17. package/dist/commands/test.js +102 -0
  18. package/dist/commands/vitest.d.ts +14 -0
  19. package/dist/commands/vitest.js +166 -0
  20. package/dist/config.d.ts +109 -0
  21. package/dist/config.js +291 -0
  22. package/dist/core/action-log-writer.d.ts +47 -0
  23. package/dist/{tools/write-file.js → core/action-log-writer.js} +42 -26
  24. package/dist/core/auth.d.ts +17 -0
  25. package/dist/core/auth.js +224 -0
  26. package/dist/core/cdp-resolver.d.ts +5 -0
  27. package/dist/core/cdp-resolver.js +93 -0
  28. package/dist/core/generateReport/report.d.ts +19 -0
  29. package/dist/core/generateReport/report.js +91 -0
  30. package/dist/core/generateReport/reportData.d.ts +215 -0
  31. package/dist/core/generateReport/reportData.js +735 -0
  32. package/dist/core/global-setup.d.ts +6 -0
  33. package/dist/core/global-setup.js +113 -0
  34. package/dist/core/keve-aspect.d.ts +54 -0
  35. package/dist/core/keve-aspect.js +52 -0
  36. package/dist/core/keve-decorators.d.ts +51 -0
  37. package/dist/core/keve-decorators.js +95 -0
  38. package/dist/core/keve-registry.d.ts +38 -0
  39. package/dist/core/keve-registry.js +25 -0
  40. package/dist/core/keve-report.d.ts +27 -0
  41. package/dist/core/keve-report.js +357 -0
  42. package/dist/core/keve-test.d.ts +36 -0
  43. package/dist/core/keve-test.js +364 -0
  44. package/dist/core/learned-actions.d.ts +24 -0
  45. package/dist/core/learned-actions.js +65 -0
  46. package/dist/core/pw-config.d.ts +19 -0
  47. package/dist/core/pw-config.js +180 -0
  48. package/dist/index.d.ts +8 -23
  49. package/dist/index.js +36 -43
  50. package/dist/page-agent/agent.d.ts +129 -0
  51. package/dist/page-agent/agent.js +585 -0
  52. package/dist/page-agent/hooks.d.ts +13 -0
  53. package/dist/page-agent/hooks.js +218 -0
  54. package/dist/page-agent/tools.d.ts +29 -0
  55. package/dist/page-agent/tools.js +297 -0
  56. package/doc/README.md +124 -0
  57. package/doc/agents/keve-dev-test-agent.md +140 -0
  58. package/doc/agents/keve-test-agent.md +178 -0
  59. package/doc/asserts//346/265/213/350/257/225/345/220/204/351/230/266/346/256/265/350/257/264/346/230/216.png +0 -0
  60. package/doc/keve-agent-demo.html +370 -0
  61. package/doc/skills/keve-bddcase-gen/SKILL.md +332 -0
  62. package/doc/skills/keve-bddcase-gen/references/test-case-rules.md +91 -0
  63. package/doc/skills/keve-bddcase-gen/references/test-cases-example.md +103 -0
  64. package/doc/skills/keve-component-gen/SKILL.md +255 -0
  65. package/doc/skills/keve-component-gen/references/component-mock-strategy.md +74 -0
  66. package/doc/skills/keve-component-gen/references/component-model-template.md +98 -0
  67. package/doc/skills/keve-component-gen/references/component-test-template.md +100 -0
  68. package/doc/skills/keve-component-gen/references/vue-test-utils-guide.md +91 -0
  69. package/doc/skills/keve-e2e-gen/SKILL.md +179 -0
  70. package/doc/skills/keve-e2e-gen/references/code-analysis-guide.md +208 -0
  71. package/doc/skills/keve-e2e-gen/references/decisions.md +36 -0
  72. package/doc/skills/keve-e2e-gen/references/pom-generation-guide.md +140 -0
  73. package/doc/skills/keve-e2e-gen/references/script-generation-guide.md +236 -0
  74. package/doc/skills/keve-e2e-gen/references/test-auditor-guide.md +189 -0
  75. package/doc/skills/keve-e2e-gen/scripts/extract-components.sh +85 -0
  76. package/doc/skills/keve-e2e-gen/scripts/inject-testids.sh +95 -0
  77. package/doc/skills/keve-static-analysis/SKILL.md +362 -0
  78. package/doc/skills/keve-static-analysis/references/codegraph-analysis-guide.md +108 -0
  79. package/doc/skills/keve-static-analysis/references/git-impact-analysis.md +81 -0
  80. package/doc/skills/keve-static-analysis/references/lint-fix-guide.md +69 -0
  81. package/doc/skills/keve-static-analysis/references/runtime-risk-patterns.md +92 -0
  82. package/doc/skills/keve-static-analysis/references/security-scan-guide.md +32 -0
  83. package/doc/skills/keve-static-analysis/references/ts-error-patterns.md +98 -0
  84. package/doc/skills/keve-static-analysis/references/vue-security-guide.md +72 -0
  85. package/doc/skills/keve-test-eval/SKILL.md +78 -0
  86. package/doc/skills/keve-test-eval/references/common-fix-guide.md +166 -0
  87. package/doc/skills/keve-test-eval/references/confidence-data-guide.md +78 -0
  88. package/doc/skills/keve-test-eval/references/fix-loop-guide.md +67 -0
  89. package/doc/skills/keve-unit-gen/SKILL.md +203 -0
  90. package/doc/skills/keve-unit-gen/references/boundary-conditions.md +78 -0
  91. package/doc/skills/keve-unit-gen/references/unit-mock-strategy.md +71 -0
  92. package/doc/skills/keve-unit-gen/references/unit-test-template.md +105 -0
  93. package/package.json +47 -25
  94. package/dist/agent/factory.d.ts +0 -25
  95. package/dist/agent/factory.js +0 -99
  96. package/dist/agent/index.d.ts +0 -3
  97. package/dist/agent/index.js +0 -7
  98. package/dist/agent/loader.d.ts +0 -22
  99. package/dist/agent/loader.js +0 -117
  100. package/dist/agent/types.d.ts +0 -9
  101. package/dist/agent/types.js +0 -2
  102. package/dist/agent-loop/complete-task.d.ts +0 -28
  103. package/dist/agent-loop/complete-task.js +0 -70
  104. package/dist/agent-loop/index.d.ts +0 -16
  105. package/dist/agent-loop/index.js +0 -26
  106. package/dist/agent-loop/loop.d.ts +0 -42
  107. package/dist/agent-loop/loop.js +0 -282
  108. package/dist/agent-loop/runtime.d.ts +0 -40
  109. package/dist/agent-loop/runtime.js +0 -163
  110. package/dist/agent-loop/types.d.ts +0 -161
  111. package/dist/agent-loop/types.js +0 -105
  112. package/dist/context/index.d.ts +0 -8
  113. package/dist/context/index.js +0 -29
  114. package/dist/context/tokenCalculation.d.ts +0 -55
  115. package/dist/context/tokenCalculation.js +0 -95
  116. package/dist/context/truncation.d.ts +0 -67
  117. package/dist/context/truncation.js +0 -142
  118. package/dist/context/types.d.ts +0 -7
  119. package/dist/context/types.js +0 -27
  120. package/dist/llm/OpenAIClient.d.ts +0 -31
  121. package/dist/llm/OpenAIClient.js +0 -455
  122. package/dist/llm/constants.d.ts +0 -2
  123. package/dist/llm/constants.js +0 -6
  124. package/dist/llm/errors.d.ts +0 -25
  125. package/dist/llm/errors.js +0 -51
  126. package/dist/llm/index.d.ts +0 -49
  127. package/dist/llm/index.js +0 -158
  128. package/dist/llm/types.d.ts +0 -124
  129. package/dist/llm/types.js +0 -2
  130. package/dist/llm/utils.d.ts +0 -29
  131. package/dist/llm/utils.js +0 -284
  132. package/dist/skill/frontmatter-parser.d.ts +0 -50
  133. package/dist/skill/frontmatter-parser.js +0 -152
  134. package/dist/skill/index.d.ts +0 -12
  135. package/dist/skill/index.js +0 -17
  136. package/dist/skill/loader.d.ts +0 -96
  137. package/dist/skill/loader.js +0 -256
  138. package/dist/tools/ask-user.d.ts +0 -8
  139. package/dist/tools/ask-user.js +0 -47
  140. package/dist/tools/bash.d.ts +0 -47
  141. package/dist/tools/bash.js +0 -213
  142. package/dist/tools/edit-file.d.ts +0 -9
  143. package/dist/tools/edit-file.js +0 -76
  144. package/dist/tools/glob.d.ts +0 -8
  145. package/dist/tools/glob.js +0 -101
  146. package/dist/tools/grep.d.ts +0 -10
  147. package/dist/tools/grep.js +0 -76
  148. package/dist/tools/index.d.ts +0 -24
  149. package/dist/tools/index.js +0 -47
  150. package/dist/tools/read-files.d.ts +0 -8
  151. package/dist/tools/read-files.js +0 -81
  152. package/dist/tools/write-file.d.ts +0 -8
@@ -0,0 +1,255 @@
1
+ ---
2
+ name: keve-component-gen
3
+ description: 从 Vue 组件源码自动生成组件行为模型和 Vitest 组件测试。分析组件 Props/emit/Slots/条件渲染,生成 Component Model(场景 + 断言)和测试脚本,执行验证后输出覆盖索引。
4
+ allowed-tools: Bash(shell:*) Bash(npx:*) Bash(npm:*) Bash(node:*) Bash(git:*) Bash(pnpm:*) Bash(yarn:*)
5
+ ---
6
+
7
+ # keve-component-gen
8
+
9
+ ## 定位
10
+
11
+ **本技能负责**:从 Vue 组件源码自动生成组件行为模型和组件测试(不负责单元测试、E2E 测试)。
12
+
13
+ 组件测试覆盖测试金字塔中层:Vue 组件的 Props、事件、条件渲染、Slots。
14
+ 运行环境为 Node.js + jsdom,不需要真实浏览器,秒级执行。
15
+
16
+ ***
17
+
18
+ ## 触发条件
19
+
20
+ | 触发方式 | 场景 | 说明 |
21
+ |----------|------|------|
22
+ | `use_skill("keve-component-gen")` | 开发阶段测试流水线 | 由 keve-dev-test-agent 调度 |
23
+ | `/keve-component-gen` 指令 | 用户主动要求生成组件测试 | 手动触发 |
24
+
25
+ ***
26
+
27
+ ## 输入
28
+
29
+ - 项目组件源码(由 Step 1 自动扫描识别,`.vue` SFC 文件)
30
+ - 可选:已有的 `keve_test_spec/component/registry.json`
31
+
32
+ **排除**:
33
+ - 页面级组件(pages/、views/、layouts/ 目录下的组件,由 E2E 覆盖)
34
+ - 纯布局组件(无交互、无 Props、无逻辑)
35
+ - 第三方组件库组件
36
+ - 已在 `coverage.yaml` 中标记 100% 覆盖的组件
37
+
38
+ ***
39
+
40
+ ## 流程
41
+
42
+ ### Step 1: 扫描组件文件
43
+
44
+ **分析项目结构**,自动识别包含 `.vue` SFC 文件的目录,不硬编码路径:
45
+
46
+ ```
47
+ 扫描策略:
48
+ 1. 读取项目 src/ 目录结构
49
+ 2. 识别包含 .vue 文件的目录
50
+ 3. 过滤条件:
51
+ - **只扫描 components/ 目录下的组件**(页面级组件由 E2E 覆盖)
52
+ - 排除: pages/、views/、layouts/ 目录(页面级,依赖路由+API,组件测试 ROI 低)
53
+ - 排除: 纯布局组件(无 script 或 script 只有 name)
54
+ - 排除: 第三方组件库组件(node_modules 下的)
55
+ - 排除: 已在 coverage.yaml 中 coverage=100% 的组件
56
+
57
+ 判断标准:
58
+ - 组件能独立 mount(只依赖 Props + stub 子组件)→ 适合组件测试
59
+ - 组件依赖路由/API/页面级生命周期 → 不适合,由 E2E 覆盖
60
+
61
+ 常见组件目录(仅供参考,不硬编码):
62
+ - src/components/
63
+ - 项目可能有自定义目录名,需逐一分析
64
+ ```
65
+
66
+ ### Step 2: 分析组件 API
67
+
68
+ 对每个组件分析:
69
+
70
+ | 分析维度 | 提取来源 | 用途 |
71
+ |---------|---------|------|
72
+ | Props 定义 | `defineProps` / `props` 选项 | 场景 Props + 默认值 |
73
+ | emit 事件 | `defineEmits` / `emits` 选项 | 断言 emitted |
74
+ | Slots | `$slots` / `<slot>` 标签 | 断言插槽渲染 |
75
+ | 条件渲染 | `v-if` / `v-show` | true/false 分支各 1 test |
76
+ | 计算属性 | `computed` | 断言派生值 |
77
+ | 事件处理器 | `@click` / `@input` 等 | trigger + 断言 |
78
+
79
+ ### Step 3: 生成 Component Model
80
+
81
+ **生成路径**:`keve_test_spec/component/models/{ComponentName}.model.ts`
82
+
83
+ **model.ts 结构**(纯数据,不依赖 Storybook,D14):
84
+
85
+ ```typescript
86
+ // keve_test_spec/component/models/SearchFilter.model.ts
87
+
88
+ import SearchFilter from '@/components/SearchFilter.vue';
89
+
90
+ export const SearchFilterModel = {
91
+ // ─── 组件标识 ───
92
+ name: 'SearchFilter',
93
+ source: 'src/components/SearchFilter.vue',
94
+ component: SearchFilter,
95
+
96
+ // ─── 组件 API ───
97
+ events: ['update:modelValue'],
98
+ slots: [],
99
+
100
+ // ─── 组件场景 ───
101
+ scenarios: [
102
+ {
103
+ id: 'SF-DEFAULT',
104
+ name: '默认状态',
105
+ description: '显示三个选项,当前选中"全部"',
106
+ props: { modelValue: 'all', disabled: false, options: ['全部', '我创建的', '可编辑的'] },
107
+ mountConfig: { globalStubs: ['RouterLink'] },
108
+ assertions: [
109
+ { type: 'visible', target: '三个筛选按钮' },
110
+ { type: 'checked', target: '"全部"按钮' },
111
+ ],
112
+ },
113
+ {
114
+ id: 'SF-DISABLED',
115
+ name: '禁用状态',
116
+ description: '所有按钮 disabled,不响应点击',
117
+ props: { modelValue: 'all', disabled: true, options: ['全部', '我创建的', '可编辑的'] },
118
+ mountConfig: { globalStubs: ['RouterLink'] },
119
+ assertions: [
120
+ { type: 'disabled', target: '所有按钮' },
121
+ { type: 'no-emit', event: 'update:modelValue' },
122
+ ],
123
+ },
124
+ ],
125
+ };
126
+ ```
127
+
128
+ **场景 ID 命名规则**:`{组件缩写}-{场景描述}`(如 `SF-DEFAULT`)
129
+
130
+ ### Step 4: 生成组件测试脚本
131
+
132
+ **生成路径**:`keve_test_spec/component/__test__/{ComponentName}.test.ts`
133
+
134
+ ```typescript
135
+ // keve_test_spec/component/__test__/SearchFilter.test.ts
136
+
137
+ import { describe, it, expect } from 'vitest';
138
+ import { mount } from '@vue/test-utils';
139
+ import SearchFilter from '@/components/SearchFilter.vue';
140
+ import { SearchFilterModel } from '@/keve_test_spec/component/models/SearchFilter.model';
141
+
142
+ function mountFromScenario(scenarioId: string, overrides: Record<string, unknown> = {}) {
143
+ const scenario = SearchFilterModel.scenarios.find(s => s.id === scenarioId)!;
144
+ return mount(SearchFilter, {
145
+ props: { ...scenario.props, ...overrides },
146
+ global: {
147
+ stubs: Object.fromEntries(scenario.mountConfig.globalStubs.map(n => [n, true])),
148
+ },
149
+ });
150
+ }
151
+
152
+ describe('SearchFilter', () => {
153
+ it('SF-DEFAULT: 默认状态', async () => {
154
+ const wrapper = mountFromScenario('SF-DEFAULT');
155
+ expect(wrapper.findAll('button')).toHaveLength(3);
156
+ });
157
+
158
+ it('SF-DISABLED: 禁用状态', async () => {
159
+ const wrapper = mountFromScenario('SF-DISABLED');
160
+ await wrapper.find('button').trigger('click');
161
+ expect(wrapper.emitted('update:modelValue')).toBeUndefined();
162
+ });
163
+ });
164
+ ```
165
+
166
+ ### Step 5: Mock 策略
167
+
168
+ | 场景 | Mock 方式 |
169
+ |------|----------|
170
+ | 子组件 | `global.stubs: { ChildComponent: true }` |
171
+ | RouterLink | `global.stubs: { RouterLink: true }` |
172
+ | Pinia Store | `global.plugins: [createTestingPinia()]` |
173
+ | API 调用 | `vi.mock('@/api/xxx')` |
174
+
175
+ **原则**:使用 `stubChildComponent`(通过 `global.stubs`)避免级联渲染,
176
+ 只在 model 的 `mountConfig.globalStubs` 中声明需要 stub 的组件名。
177
+
178
+ ### Step 6: 执行验证
179
+
180
+ ```bash
181
+ npx vitest run keve_test_spec/component/__test__/SearchFilter.test.ts
182
+ ```
183
+
184
+ - 如果通过 → 继续
185
+ - 如果失败 → AI 分析错误,修复测试(≤3 轮)
186
+ - 超过 3 轮仍失败 → 用 `it.todo` 标记
187
+
188
+ ### Step 7: 生成覆盖索引
189
+
190
+ 生成 `keve_test_spec/component/coverage.yaml`:
191
+
192
+ ```yaml
193
+ generated_at: 2026-06-16T16:08:00
194
+ total_components: 20
195
+ tested_components: 12
196
+ untested_components: 8
197
+
198
+ components:
199
+ - source: src/components/SearchFilter.vue
200
+ model: component/models/SearchFilter.model.ts
201
+ test: component/__test__/SearchFilter.test.ts
202
+ scenarios_total: 2
203
+ scenarios_tested: 2
204
+ coverage: 100%
205
+ missing: []
206
+
207
+ - source: src/components/ToolManagement.vue
208
+ model: null
209
+ test: null
210
+ missing: [全部场景]
211
+ ```
212
+
213
+ ***
214
+
215
+ ## 产出
216
+
217
+ | 产出 | 路径 |
218
+ |------|------|
219
+ | 组件行为模型 | `keve_test_spec/component/models/*.model.ts` |
220
+ | 组件测试脚本 | `keve_test_spec/component/__test__/*.test.ts` |
221
+ | 覆盖索引 | `keve_test_spec/component/coverage.yaml` |
222
+
223
+ ***
224
+
225
+ ## 硬性约束
226
+
227
+ - **不打标**(不需要 `data-e2e-name`,D07)
228
+ - **不生成 POM**(组件测试不需要页面操作模型,D07)
229
+ - **不生成 .stories.ts**(延后,D14)
230
+ - **model.ts 保持纯数据**(不依赖 Storybook,D14)
231
+ - **test.ts 通过 model 的 scenarios 获取 mount 配置**
232
+ - **model 和 test 放在 `keve_test_spec/component/`**(长期资产,D12)
233
+ - **每轮修复后重新执行验证**
234
+ - **失败用例用 `it.todo` 标记**
235
+
236
+ ***
237
+
238
+ ## Key Decisions
239
+
240
+ | ID | 决策 | 说明 |
241
+ |----|------|------|
242
+ | CG01 | 先生成 model 再生成 test | model 是唯一数据源,test 引用 model |
243
+ | CG02 | model.ts 保持纯数据 | 不依赖 Storybook,后续可按需扩展(D14) |
244
+ | CG03 | 不打标、不 POM | jsdom 中直接 mount,不需要浏览器定位(D07) |
245
+ | CG04 | 最多 3 轮修复 | 组件测试比单测复杂,允许更多轮次 |
246
+ | CG05 | 覆盖索引从 model 生成 | 场景信息已含在 model 中,直接提取 |
247
+
248
+ ***
249
+
250
+ ## References
251
+
252
+ - [component-model-template.md](references/component-model-template.md) — model.ts 结构和字段说明
253
+ - [component-test-template.md](references/component-test-template.md) — .test.ts 生成模板
254
+ - [component-mock-strategy.md](references/component-mock-strategy.md) — stub + vi.mock 策略
255
+ - [vue-test-utils-guide.md](references/vue-test-utils-guide.md) — @vue/test-utils API 速查
@@ -0,0 +1,74 @@
1
+ # 组件测试 Mock 策略
2
+
3
+ > AI 生成组件测试时的 stub/mock 指南
4
+
5
+ ---
6
+
7
+ ## 优先级
8
+
9
+ | 优先级 | 方式 | 适用场景 |
10
+ |--------|------|---------|
11
+ | 1 | `global.stubs` | 子组件、RouterLink、RouterView |
12
+ | 2 | `createTestingPinia` | Pinia Store |
13
+ | 3 | `vi.mock` | API 调用、工具函数 |
14
+ | 4 | 直接传 Props | 不需要 mock |
15
+
16
+ ---
17
+
18
+ ## 子组件 Stub
19
+
20
+ ```typescript
21
+ // 方式1:通过 mountConfig.globalStubs(在 model 中声明)
22
+ mount(Component, {
23
+ global: {
24
+ stubs: { RouterLink: true, ChildComponent: true },
25
+ },
26
+ });
27
+
28
+ // 方式2:stub 所有子组件(粗暴,不推荐)
29
+ mount(Component, {
30
+ global: {
31
+ stubs: { stubChildComponent: true },
32
+ },
33
+ });
34
+ ```
35
+
36
+ **原则**:只 stub 有副作用的子组件(API 调用、路由跳转),不 stub 纯展示组件。
37
+
38
+ ---
39
+
40
+ ## Pinia Store Mock
41
+
42
+ ```typescript
43
+ import { createTestingPinia } from '@pinia/testing';
44
+
45
+ mount(Component, {
46
+ global: {
47
+ plugins: [createTestingPinia({ stubActions: false })],
48
+ },
49
+ });
50
+ ```
51
+
52
+ **原则**:默认 `stubActions: false`,保留 Store 逻辑;只在 Store 有 API 调用时 `stubActions: true`。
53
+
54
+ ---
55
+
56
+ ## API Mock
57
+
58
+ ```typescript
59
+ vi.mock('@/api/agent', () => ({
60
+ getAgentList: vi.fn().mockResolvedValue({ list: [], total: 0 }),
61
+ getAgentDetail: vi.fn().mockResolvedValue({ id: 1, name: 'Test Agent' }),
62
+ }));
63
+ ```
64
+
65
+ ---
66
+
67
+ ## 禁止做法
68
+
69
+ | 禁止 | 原因 |
70
+ |------|------|
71
+ | Stub 被测组件本身 | 失去测试意义 |
72
+ | Mock Vue 内置组件 | `Transition`、`Teleport` 不需要 mock |
73
+ | `shallowMount` 替代 `mount` | 会跳过子组件渲染,断言不完整 |
74
+ | 在 `global.stubs` 中 stub 所有 | 过度 stub 导致测试无意义 |
@@ -0,0 +1,98 @@
1
+ # Component Model 模板
2
+
3
+ > AI 生成 component model 时的参考
4
+
5
+ ---
6
+
7
+ ## 基本结构
8
+
9
+ ```typescript
10
+ import ComponentName from '@/components/ComponentName.vue';
11
+
12
+ export const ComponentNameModel = {
13
+ name: 'ComponentName',
14
+ source: 'src/components/ComponentName.vue',
15
+ component: ComponentName,
16
+
17
+ events: ['update:modelValue', 'confirm', 'cancel'],
18
+ slots: ['default', 'footer'],
19
+
20
+ scenarios: [
21
+ {
22
+ id: 'CN-DEFAULT',
23
+ name: '默认状态',
24
+ description: '组件在默认 Props 下的渲染',
25
+ props: { /* default prop values */ },
26
+ mountConfig: {
27
+ globalStubs: ['RouterLink'],
28
+ },
29
+ assertions: [
30
+ { type: 'visible', target: '组件根元素' },
31
+ ],
32
+ },
33
+ ],
34
+ };
35
+ ```
36
+
37
+ ---
38
+
39
+ ## 场景设计规则
40
+
41
+ ### Props 变化场景
42
+
43
+ 每个有意义的 Props 变化 → 独立场景:
44
+
45
+ | Props | 场景 ID | 场景名 |
46
+ |-------|---------|--------|
47
+ | 默认值 | `CN-DEFAULT` | 默认状态 |
48
+ | disabled: true | `CN-DISABLED` | 禁用状态 |
49
+ | loading: true | `CN-LOADING` | 加载中 |
50
+ | empty: [] | `CN-EMPTY` | 空数据 |
51
+
52
+ ### 条件渲染场景
53
+
54
+ 每个 `v-if` → true/false 两个场景:
55
+
56
+ | 条件 | 场景 ID | 场景名 |
57
+ |------|---------|--------|
58
+ | `v-if="detail"` 为 true | `CN-WITH-DETAIL` | 有详情 |
59
+ | `v-if="detail"` 为 false | `CN-NO-DETAIL` | 无详情 |
60
+
61
+ ### 交互场景
62
+
63
+ 每个用户交互 → 独立场景:
64
+
65
+ | 交互 | 场景 ID | 场景名 |
66
+ |------|---------|--------|
67
+ | 点击确认按钮 | `CN-CONFIRM` | 确认操作 |
68
+ | 点击取消按钮 | `CN-CANCEL` | 取消操作 |
69
+ | 输入文本 | `CN-INPUT` | 输入内容 |
70
+
71
+ ---
72
+
73
+ ## 场景 ID 命名规则
74
+
75
+ 格式:`{组件缩写}-{场景描述}`(大写英文 + 连字符)
76
+
77
+ 示例:
78
+ - `SF-DEFAULT` — SearchFilter 默认
79
+ - `SF-DISABLED` — SearchFilter 禁用
80
+ - `SF-EMPTY` — SearchFilter 空选项
81
+ - `SF-FILTER-MINE` — SearchFilter 切换到我创建的
82
+
83
+ ---
84
+
85
+ ## 断言类型速查
86
+
87
+ | type | 用途 | 必填字段 |
88
+ |------|------|---------|
89
+ | `visible` | 元素可见 | target |
90
+ | `hidden` | 元素隐藏 | target |
91
+ | `disabled` | 元素禁用 | target |
92
+ | `enabled` | 元素可用 | target |
93
+ | `emitted` | 事件触发 | event |
94
+ | `no-emit` | 事件未触发 | event |
95
+ | `text` | 文本内容 | target, value |
96
+ | `value` | 输入值 | target, value |
97
+ | `class` | CSS 类 | target, value |
98
+ | `attribute` | HTML 属性 | target, value |
@@ -0,0 +1,100 @@
1
+ # 组件测试生成模板
2
+
3
+ > AI 生成组件测试脚本时的参考
4
+
5
+ ---
6
+
7
+ ## 基本结构
8
+
9
+ ```typescript
10
+ import { describe, it, expect, vi } from 'vitest';
11
+ import { mount } from '@vue/test-utils';
12
+ import ComponentName from '@/components/ComponentName.vue';
13
+ import { ComponentNameModel } from '@/keve_test_spec/component/models/ComponentName.model';
14
+
15
+ function mountFromScenario(scenarioId: string, overrides: Record<string, unknown> = {}) {
16
+ const scenario = ComponentNameModel.scenarios.find(s => s.id === scenarioId)!;
17
+ return mount(ComponentName, {
18
+ props: { ...scenario.props, ...overrides },
19
+ global: {
20
+ stubs: Object.fromEntries(
21
+ scenario.mountConfig.globalStubs.map(n => [n, true])
22
+ ),
23
+ },
24
+ });
25
+ }
26
+
27
+ describe('ComponentName', () => {
28
+ it('CN-DEFAULT: 默认状态', async () => {
29
+ const wrapper = mountFromScenario('CN-DEFAULT');
30
+ // 断言...
31
+ });
32
+ });
33
+ ```
34
+
35
+ ---
36
+
37
+ ## 常见测试模式
38
+
39
+ ### 默认渲染
40
+
41
+ ```typescript
42
+ it('CN-DEFAULT: 默认状态', async () => {
43
+ const wrapper = mountFromScenario('CN-DEFAULT');
44
+ expect(wrapper.find('.component-root').exists()).toBe(true);
45
+ });
46
+ ```
47
+
48
+ ### Props 变更
49
+
50
+ ```typescript
51
+ it('CN-DISABLED: 禁用状态', async () => {
52
+ const wrapper = mountFromScenario('CN-DISABLED');
53
+ expect(wrapper.find('button').attributes('disabled')).toBeDefined();
54
+ });
55
+ ```
56
+
57
+ ### 事件触发
58
+
59
+ ```typescript
60
+ it('CN-CONFIRM: 点击确认', async () => {
61
+ const wrapper = mountFromScenario('CN-DEFAULT');
62
+ await wrapper.find('[data-test="confirm-btn"]').trigger('click');
63
+ expect(wrapper.emitted('confirm')).toHaveLength(1);
64
+ });
65
+ ```
66
+
67
+ ### 条件渲染
68
+
69
+ ```typescript
70
+ it('CN-WITH-DETAIL: 有详情', async () => {
71
+ const wrapper = mountFromScenario('CN-WITH-DETAIL');
72
+ expect(wrapper.find('.detail-section').exists()).toBe(true);
73
+ });
74
+
75
+ it('CN-NO-DETAIL: 无详情', async () => {
76
+ const wrapper = mountFromScenario('CN-NO-DETAIL');
77
+ expect(wrapper.find('.detail-section').exists()).toBe(false);
78
+ });
79
+ ```
80
+
81
+ ### Slots
82
+
83
+ ```typescript
84
+ it('CN-SLOT-FOOTER: 底部插槽', async () => {
85
+ const wrapper = mount(ComponentName, {
86
+ props: ComponentNameModel.scenarios[0].props,
87
+ slots: { footer: '<div class="custom-footer">Footer</div>' },
88
+ global: { stubs: { RouterLink: true } },
89
+ });
90
+ expect(wrapper.find('.custom-footer').exists()).toBe(true);
91
+ });
92
+ ```
93
+
94
+ ---
95
+
96
+ ## 失败用例处理
97
+
98
+ ```typescript
99
+ it.todo('CN-LOADING: 加载中状态');
100
+ ```
@@ -0,0 +1,91 @@
1
+ # @vue/test-utils API 速查
2
+
3
+ > AI 生成组件测试时的 API 参考
4
+
5
+ ---
6
+
7
+ ## mount / shallowMount
8
+
9
+ ```typescript
10
+ import { mount } from '@vue/test-utils';
11
+
12
+ const wrapper = mount(Component, {
13
+ props: { modelValue: 'test' },
14
+ attrs: { id: 'my-component' },
15
+ slots: { default: '<div>Slot content</div>' },
16
+ global: {
17
+ plugins: [router, pinia],
18
+ stubs: { RouterLink: true },
19
+ mocks: { $t: (key: string) => key },
20
+ provide: { theme: 'dark' },
21
+ },
22
+ });
23
+ ```
24
+
25
+ ---
26
+
27
+ ## Wrapper 方法
28
+
29
+ ### 查询
30
+
31
+ | 方法 | 说明 |
32
+ |------|------|
33
+ | `wrapper.find(selector)` | 查找单个元素 |
34
+ | `wrapper.findAll(selector)` | 查找所有元素 |
35
+ | `wrapper.findComponent(Component)` | 查找子组件 |
36
+ | `wrapper.get(selector)` | 查找元素(不存在则抛错) |
37
+ | `wrapper.vm` | 组件实例 |
38
+
39
+ ### 交互
40
+
41
+ | 方法 | 说明 |
42
+ |------|------|
43
+ | `wrapper.trigger('click')` | 触发 DOM 事件 |
44
+ | `wrapper.trigger('click', { button: 0 })` | 带参数的事件 |
45
+ | `wrapper.setValue('new value')` | 设置表单值 |
46
+ | `wrapper.setChecked(true)` | 设置 checkbox |
47
+ | `wrapper.setSelected('option1')` | 设置 select |
48
+
49
+ ### 断言
50
+
51
+ | 方法 | 说明 |
52
+ |------|------|
53
+ | `wrapper.exists()` | 元素是否存在 |
54
+ | `wrapper.isVisible()` | 元素是否可见(v-show) |
55
+ | `wrapper.text()` | 元素文本内容 |
56
+ | `wrapper.html()` | 元素 HTML |
57
+ | `wrapper.classes()` | 元素 CSS 类列表 |
58
+ | `wrapper.attributes('href')` | 元素属性值 |
59
+ | `wrapper.emitted()` | 所有触发的事件 |
60
+ | `wrapper.emitted('update')` | 特定事件的参数列表 |
61
+
62
+ ### 状态操作
63
+
64
+ | 方法 | 说明 |
65
+ |------|------|
66
+ | `wrapper.setProps({ key: value })` | 更新 Props |
67
+ | `wrapper.setData({ key: value })` | 更新 data(Options API) |
68
+ | `await wrapper.vm.$nextTick()` | 等待下次渲染 |
69
+
70
+ ---
71
+
72
+ ## 常见模式
73
+
74
+ ### 等待异步渲染
75
+
76
+ ```typescript
77
+ await wrapper.setProps({ loading: false });
78
+ await wrapper.vm.$nextTick();
79
+ // 或
80
+ await flushPromises();
81
+ ```
82
+
83
+ ### 测试 v-model
84
+
85
+ ```typescript
86
+ const wrapper = mount(Component, {
87
+ props: { modelValue: 'initial', 'onUpdate:modelValue': (e: any) => wrapper.setProps({ modelValue: e }) },
88
+ });
89
+ await wrapper.find('input').setValue('new value');
90
+ expect(wrapper.emitted('update:modelValue')).toHaveLength(1);
91
+ ```