@epoint-testtech/ep-stage-skill 0.0.3-alpha.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 (151) hide show
  1. package/SKILL.md +27 -0
  2. package/codex-skill/ep-stage/create-project/SKILL.md +59 -0
  3. package/codex-skill/ep-stage/glue-test/SKILL.md +258 -0
  4. package/codex-skill/ep-stage/glue-test/references/crud-pipeline.md +139 -0
  5. package/codex-skill/ep-stage/glue-test/references/gap-review-protocol.md +43 -0
  6. package/codex-skill/ep-stage/glue-test/references/harness-principles.md +46 -0
  7. package/codex-skill/ep-stage/glue-test/scripts/generate-crud-spec.mjs +149 -0
  8. package/codex-skill/ep-stage/glue-testcase/SKILL.md +31 -0
  9. package/codex-skill/ep-stage/glue-testcase/examples/observable-testcase.json +40 -0
  10. package/codex-skill/ep-stage/glue-testcase/references/testcase-schema.md +67 -0
  11. package/codex-skill/ep-stage/recording-to-glue/SKILL.md +27 -0
  12. package/codex-skill/ep-stage/scripts/validate-skill.mjs +73 -0
  13. package/dist/src/capability/coverage-diff.d.ts +34 -0
  14. package/dist/src/capability/coverage-diff.d.ts.map +1 -0
  15. package/dist/src/capability/coverage-diff.js +91 -0
  16. package/dist/src/capability/page-structure.d.ts +31 -0
  17. package/dist/src/capability/page-structure.d.ts.map +1 -0
  18. package/dist/src/capability/page-structure.js +50 -0
  19. package/dist/src/capability/scenario-inference.d.ts +36 -0
  20. package/dist/src/capability/scenario-inference.d.ts.map +1 -0
  21. package/dist/src/capability/scenario-inference.js +114 -0
  22. package/dist/src/cli/generate-crud-contract.d.ts +2 -0
  23. package/dist/src/cli/generate-crud-contract.d.ts.map +1 -0
  24. package/dist/src/cli/generate-crud-contract.js +77 -0
  25. package/dist/src/cli/generate-playwright-tests.d.ts +30 -0
  26. package/dist/src/cli/generate-playwright-tests.d.ts.map +1 -0
  27. package/dist/src/cli/generate-playwright-tests.js +81 -0
  28. package/dist/src/cli/run-gap-pipeline.d.ts +256 -0
  29. package/dist/src/cli/run-gap-pipeline.d.ts.map +1 -0
  30. package/dist/src/cli/run-gap-pipeline.js +1468 -0
  31. package/dist/src/context/stage-context.d.ts +63 -0
  32. package/dist/src/context/stage-context.d.ts.map +1 -0
  33. package/dist/src/context/stage-context.js +297 -0
  34. package/dist/src/contracts/crud-business-module.d.ts +645 -0
  35. package/dist/src/contracts/crud-business-module.d.ts.map +1 -0
  36. package/dist/src/contracts/crud-business-module.js +1 -0
  37. package/dist/src/contracts/gap-inference.d.ts +213 -0
  38. package/dist/src/contracts/gap-inference.d.ts.map +1 -0
  39. package/dist/src/contracts/gap-inference.js +11 -0
  40. package/dist/src/contracts/observable-chain.d.ts +250 -0
  41. package/dist/src/contracts/observable-chain.d.ts.map +1 -0
  42. package/dist/src/contracts/observable-chain.js +1 -0
  43. package/dist/src/extractors/code-list.d.ts +40 -0
  44. package/dist/src/extractors/code-list.d.ts.map +1 -0
  45. package/dist/src/extractors/code-list.js +225 -0
  46. package/dist/src/extractors/html-page.d.ts +67 -0
  47. package/dist/src/extractors/html-page.d.ts.map +1 -0
  48. package/dist/src/extractors/html-page.js +195 -0
  49. package/dist/src/extractors/java-action.d.ts +8 -0
  50. package/dist/src/extractors/java-action.d.ts.map +1 -0
  51. package/dist/src/extractors/java-action.js +53 -0
  52. package/dist/src/extractors/spec-yaml.d.ts +28 -0
  53. package/dist/src/extractors/spec-yaml.d.ts.map +1 -0
  54. package/dist/src/extractors/spec-yaml.js +29 -0
  55. package/dist/src/gap-planner/action-candidates.d.ts +9 -0
  56. package/dist/src/gap-planner/action-candidates.d.ts.map +1 -0
  57. package/dist/src/gap-planner/action-candidates.js +66 -0
  58. package/dist/src/gap-planner/list-gap-workflows.d.ts +17 -0
  59. package/dist/src/gap-planner/list-gap-workflows.d.ts.map +1 -0
  60. package/dist/src/gap-planner/list-gap-workflows.js +47 -0
  61. package/dist/src/gap-planner/plan-agent-workflows.d.ts +26 -0
  62. package/dist/src/gap-planner/plan-agent-workflows.d.ts.map +1 -0
  63. package/dist/src/gap-planner/plan-agent-workflows.js +116 -0
  64. package/dist/src/gap-planner/skeleton-coverage.d.ts +9 -0
  65. package/dist/src/gap-planner/skeleton-coverage.d.ts.map +1 -0
  66. package/dist/src/gap-planner/skeleton-coverage.js +41 -0
  67. package/dist/src/gap-planner/stable-id.d.ts +16 -0
  68. package/dist/src/gap-planner/stable-id.d.ts.map +1 -0
  69. package/dist/src/gap-planner/stable-id.js +19 -0
  70. package/dist/src/generalization/generalization-eval.d.ts +71 -0
  71. package/dist/src/generalization/generalization-eval.d.ts.map +1 -0
  72. package/dist/src/generalization/generalization-eval.js +53 -0
  73. package/dist/src/generators/agent-inferred-workflow-script.d.ts +22 -0
  74. package/dist/src/generators/agent-inferred-workflow-script.d.ts.map +1 -0
  75. package/dist/src/generators/agent-inferred-workflow-script.js +230 -0
  76. package/dist/src/generators/stage-skeleton-script.d.ts +107 -0
  77. package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -0
  78. package/dist/src/generators/stage-skeleton-script.js +607 -0
  79. package/dist/src/index.d.ts +52 -0
  80. package/dist/src/index.d.ts.map +1 -0
  81. package/dist/src/index.js +26 -0
  82. package/dist/src/material/material-inventory.d.ts +92 -0
  83. package/dist/src/material/material-inventory.d.ts.map +1 -0
  84. package/dist/src/material/material-inventory.js +191 -0
  85. package/dist/src/normalizers/crud-contract.d.ts +107 -0
  86. package/dist/src/normalizers/crud-contract.d.ts.map +1 -0
  87. package/dist/src/normalizers/crud-contract.js +1068 -0
  88. package/dist/src/testcase/testcase-generator.d.ts +43 -0
  89. package/dist/src/testcase/testcase-generator.d.ts.map +1 -0
  90. package/dist/src/testcase/testcase-generator.js +152 -0
  91. package/dist/src/testcase/testcase-skeleton.d.ts +91 -0
  92. package/dist/src/testcase/testcase-skeleton.d.ts.map +1 -0
  93. package/dist/src/testcase/testcase-skeleton.js +121 -0
  94. package/dist/src/testcase/testcase-spec-assembly.d.ts +11 -0
  95. package/dist/src/testcase/testcase-spec-assembly.d.ts.map +1 -0
  96. package/dist/src/testcase/testcase-spec-assembly.js +24 -0
  97. package/dist/src/trace/review-summary.d.ts +17 -0
  98. package/dist/src/trace/review-summary.d.ts.map +1 -0
  99. package/dist/src/trace/review-summary.js +34 -0
  100. package/dist/src/trace/trace-writer.d.ts +17 -0
  101. package/dist/src/trace/trace-writer.d.ts.map +1 -0
  102. package/dist/src/trace/trace-writer.js +81 -0
  103. package/dist/test/crud-contract.test.d.ts +2 -0
  104. package/dist/test/crud-contract.test.d.ts.map +1 -0
  105. package/dist/test/crud-contract.test.js +819 -0
  106. package/dist/test/gap-inference.test.d.ts +2 -0
  107. package/dist/test/gap-inference.test.d.ts.map +1 -0
  108. package/dist/test/gap-inference.test.js +597 -0
  109. package/dist/test/generalization.test.d.ts +2 -0
  110. package/dist/test/generalization.test.d.ts.map +1 -0
  111. package/dist/test/generalization.test.js +73 -0
  112. package/dist/test/material-inventory.test.d.ts +2 -0
  113. package/dist/test/material-inventory.test.d.ts.map +1 -0
  114. package/dist/test/material-inventory.test.js +141 -0
  115. package/dist/test/observable-chain.test.d.ts +2 -0
  116. package/dist/test/observable-chain.test.d.ts.map +1 -0
  117. package/dist/test/observable-chain.test.js +123 -0
  118. package/dist/test/observable-pipeline.test.d.ts +2 -0
  119. package/dist/test/observable-pipeline.test.d.ts.map +1 -0
  120. package/dist/test/observable-pipeline.test.js +461 -0
  121. package/dist/test/page-structure.test.d.ts +2 -0
  122. package/dist/test/page-structure.test.d.ts.map +1 -0
  123. package/dist/test/page-structure.test.js +45 -0
  124. package/dist/test/scenario-inference.test.d.ts +2 -0
  125. package/dist/test/scenario-inference.test.d.ts.map +1 -0
  126. package/dist/test/scenario-inference.test.js +73 -0
  127. package/dist/test/stage-context.test.d.ts +2 -0
  128. package/dist/test/stage-context.test.d.ts.map +1 -0
  129. package/dist/test/stage-context.test.js +263 -0
  130. package/dist/test/testcase-generator.test.d.ts +2 -0
  131. package/dist/test/testcase-generator.test.d.ts.map +1 -0
  132. package/dist/test/testcase-generator.test.js +276 -0
  133. package/dist/test/testcase-skeleton.test.d.ts +2 -0
  134. package/dist/test/testcase-skeleton.test.d.ts.map +1 -0
  135. package/dist/test/testcase-skeleton.test.js +185 -0
  136. package/dist/test/testcase-spec-assembly.test.d.ts +2 -0
  137. package/dist/test/testcase-spec-assembly.test.d.ts.map +1 -0
  138. package/dist/test/testcase-spec-assembly.test.js +105 -0
  139. package/dist/vitest.config.d.ts +3 -0
  140. package/dist/vitest.config.d.ts.map +1 -0
  141. package/dist/vitest.config.js +7 -0
  142. package/docs/README.md +134 -0
  143. package/docs/mvp-usage-guide.md +298 -0
  144. package/examples/schemeresource-observable-docs/schemeresource.context.md +20 -0
  145. package/examples/schemeresource.module-hints.json +38 -0
  146. package/examples/schemeresource.observable.code_list.md +37 -0
  147. package/examples/zwplace-observable-docs/zwplace.context.md +16 -0
  148. package/examples/zwplace-placecategory-validation.json +29 -0
  149. package/examples/zwplace.module-hints.json +69 -0
  150. package/examples/zwplace.observable.code_list.md +37 -0
  151. package/package.json +38 -0
@@ -0,0 +1,263 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import JSON5 from 'json5';
5
+ import { describe, expect, it } from 'vitest';
6
+ import { readStageContext, resolveStageContext, writeProjectIndex, } from '../src/index.js';
7
+ /**
8
+ * 创建隔离的临时目录,避免测试读写真实用户 HOME 或项目文件。
9
+ *
10
+ * @param prefix - 临时目录前缀。
11
+ * @returns 生成后的临时目录绝对路径。
12
+ */
13
+ function createTempDir(prefix) {
14
+ return path.join(os.tmpdir(), `${prefix}-${crypto.randomUUID()}`);
15
+ }
16
+ describe('stage context resolution', () => {
17
+ it('parses explicit stage-context.md and normalizes relative env path', () => {
18
+ const projectDir = createTempDir('ep-stage-context');
19
+ mkdirSync(projectDir, { recursive: true });
20
+ const knowledgeRoot = path.join(projectDir, '..', 'knowledge-project');
21
+ const stageContextPath = path.join(projectDir, 'stage-context.md');
22
+ writeFileSync(stageContextPath, [
23
+ '---',
24
+ 'projectName: demo-project',
25
+ 'projectDir: .',
26
+ 'mode: glue',
27
+ 'envPath: .env',
28
+ 'knowledgeRoot: ../knowledge-project',
29
+ 'codeListPaths:',
30
+ ' - ../knowledge-project/_docs/code_list.md',
31
+ '---',
32
+ '',
33
+ '# demo-project',
34
+ ].join('\n'));
35
+ const context = readStageContext(stageContextPath);
36
+ expect(context).toMatchObject({
37
+ projectName: 'demo-project',
38
+ projectDir,
39
+ mode: 'glue',
40
+ envPath: '.env',
41
+ envFilePath: path.join(projectDir, '.env'),
42
+ knowledgeRoot: path.resolve(knowledgeRoot),
43
+ stageContextPath,
44
+ codeListPaths: [path.resolve(projectDir, '../knowledge-project/_docs/code_list.md')],
45
+ });
46
+ });
47
+ it('resolves current project from user index by code list path when local context is absent', () => {
48
+ const homeDir = createTempDir('ep-stage-home');
49
+ const projectDir = createTempDir('ep-stage-project');
50
+ const cwd = createTempDir('ep-stage-outside-cwd');
51
+ const knowledgeRoot = createTempDir('ep-stage-knowledge');
52
+ const codeListPath = path.join(knowledgeRoot, '_docs', 'code_list.md');
53
+ mkdirSync(cwd, { recursive: true });
54
+ mkdirSync(projectDir, { recursive: true });
55
+ mkdirSync(path.dirname(codeListPath), { recursive: true });
56
+ writeFileSync(path.join(projectDir, '.env'), 'LOGIN_SYSTEM_URL=http://example.test/login\n');
57
+ writeFileSync(codeListPath, '# code list\n');
58
+ writeProjectIndex({
59
+ homeDir,
60
+ projects: [
61
+ {
62
+ projectName: 'indexed-project',
63
+ projectDir,
64
+ mode: 'glue',
65
+ envPath: '.env',
66
+ knowledgeRoot,
67
+ codeListPaths: [codeListPath],
68
+ },
69
+ ],
70
+ });
71
+ const result = resolveStageContext({
72
+ cwd,
73
+ codeListPath,
74
+ homeDir,
75
+ });
76
+ expect(result.context).toMatchObject({
77
+ projectName: 'indexed-project',
78
+ projectDir,
79
+ mode: 'glue',
80
+ envFilePath: path.join(projectDir, '.env'),
81
+ knowledgeRoot,
82
+ codeListPaths: [codeListPath],
83
+ });
84
+ expect(result.trace).toMatchObject({
85
+ stage: 'context-resolution',
86
+ stageLabel: '项目上下文解析',
87
+ gate: { status: 'confirmed' },
88
+ reasoningSummary: {
89
+ conclusion: expect.stringContaining('projects.index.json5'),
90
+ needsHumanReview: false,
91
+ },
92
+ });
93
+ });
94
+ it('uses stage-context.md as source of truth when user index has stageContextPath', () => {
95
+ const homeDir = createTempDir('ep-stage-home-source');
96
+ const projectDir = createTempDir('ep-stage-source-project');
97
+ const oldKnowledgeRoot = path.join(projectDir, 'old knowledge');
98
+ const newKnowledgeRoot = path.join(projectDir, 'new knowledge');
99
+ const stageContextPath = path.join(projectDir, 'stage-context.md');
100
+ const codeListPath = path.join(newKnowledgeRoot, '_docs', 'code_list.md');
101
+ mkdirSync(path.dirname(codeListPath), { recursive: true });
102
+ writeFileSync(path.join(projectDir, 'actual.env'), 'LOGIN_USERNAME=demo-user\n');
103
+ writeFileSync(codeListPath, '# code list\n');
104
+ writeFileSync(stageContextPath, [
105
+ '---',
106
+ 'projectName: context-project',
107
+ 'projectDir: .',
108
+ 'mode: glue',
109
+ 'envPath: actual.env',
110
+ 'knowledgeRoot: "new knowledge"',
111
+ 'codeListPaths:',
112
+ ' - "new knowledge/_docs/code_list.md"',
113
+ '---',
114
+ '',
115
+ '# context-project',
116
+ ].join('\n'));
117
+ writeProjectIndex({
118
+ homeDir,
119
+ projects: [
120
+ {
121
+ projectName: 'stale-index-project',
122
+ projectDir,
123
+ mode: 'glue',
124
+ envPath: 'old.env',
125
+ knowledgeRoot: oldKnowledgeRoot,
126
+ codeListPaths: [codeListPath],
127
+ stageContextPath,
128
+ },
129
+ ],
130
+ });
131
+ const result = resolveStageContext({
132
+ cwd: createTempDir('ep-stage-source-outside'),
133
+ codeListPath,
134
+ homeDir,
135
+ });
136
+ expect(result.context).toMatchObject({
137
+ projectName: 'context-project',
138
+ envPath: 'actual.env',
139
+ envFilePath: path.join(projectDir, 'actual.env'),
140
+ knowledgeRoot: newKnowledgeRoot,
141
+ codeListPaths: [codeListPath],
142
+ stageContextPath,
143
+ });
144
+ expect(result.context.knowledgeRoot).not.toBe(oldKnowledgeRoot);
145
+ expect(result.trace.inputs).toContainEqual(expect.objectContaining({
146
+ kind: 'context_source',
147
+ path: path.join(homeDir, '.ep-stage', 'projects.index.json5'),
148
+ }));
149
+ expect(result.trace.outputs).toContainEqual(expect.objectContaining({
150
+ kind: 'stage_context',
151
+ path: stageContextPath,
152
+ }));
153
+ });
154
+ it('reads user project index with JSON5 comments and trailing commas', () => {
155
+ const homeDir = createTempDir('ep-stage-home-json5');
156
+ const projectDir = createTempDir('ep-stage-json5-project');
157
+ const codeListPath = path.join(projectDir, 'knowledge', '_docs', 'code_list.md');
158
+ const indexDir = path.join(homeDir, '.ep-stage');
159
+ mkdirSync(indexDir, { recursive: true });
160
+ mkdirSync(path.dirname(codeListPath), { recursive: true });
161
+ writeFileSync(path.join(projectDir, '.env'), 'LOGIN_USERNAME=demo-user\n');
162
+ writeFileSync(codeListPath, '# code list\n');
163
+ writeFileSync(path.join(indexDir, 'projects.index.json5'), `{
164
+ // 用户可编辑索引允许注释
165
+ projects: [
166
+ {
167
+ projectName: 'json5-project',
168
+ projectDir: '${projectDir}',
169
+ mode: 'glue',
170
+ envPath: '.env',
171
+ codeListPaths: [
172
+ '${codeListPath}',
173
+ ],
174
+ },
175
+ ],
176
+ }`);
177
+ const result = resolveStageContext({
178
+ projectDir,
179
+ codeListPath,
180
+ homeDir,
181
+ });
182
+ expect(result.context.projectName).toBe('json5-project');
183
+ expect(result.context.envFilePath).toBe(path.join(projectDir, '.env'));
184
+ expect(result.trace.reasoningSummary?.evidenceChain[0]).toMatchObject({
185
+ source: 'human',
186
+ path: path.join(indexDir, 'projects.index.json5'),
187
+ });
188
+ });
189
+ it('creates a debug stage context from explicit projectDir when no context or index exists', () => {
190
+ const homeDir = createTempDir('ep-stage-home-debug');
191
+ const projectDir = createTempDir('ep-stage-debug-project');
192
+ const codeListPath = path.join(projectDir, 'knowledge', '_docs', 'code_list.md');
193
+ mkdirSync(path.dirname(codeListPath), { recursive: true });
194
+ writeFileSync(codeListPath, '# code list\n');
195
+ const result = resolveStageContext({
196
+ projectDir,
197
+ codeListPath,
198
+ homeDir,
199
+ });
200
+ expect(result.context).toMatchObject({
201
+ projectName: path.basename(projectDir),
202
+ projectDir,
203
+ mode: 'glue',
204
+ envPath: '.env',
205
+ envFilePath: path.join(projectDir, '.env'),
206
+ codeListPaths: [codeListPath],
207
+ });
208
+ expect(result.trace).toMatchObject({
209
+ gate: { status: 'needs_review' },
210
+ reasoningSummary: {
211
+ conclusion: expect.stringContaining('显式 --project-dir'),
212
+ needsHumanReview: true,
213
+ },
214
+ });
215
+ });
216
+ it('writes v1 project index and keeps reading legacy JSON5 index files', () => {
217
+ const homeDir = createTempDir('ep-stage-home-version');
218
+ const projectDir = createTempDir('ep-stage-version-project');
219
+ mkdirSync(projectDir, { recursive: true });
220
+ const indexPath = writeProjectIndex({
221
+ homeDir,
222
+ projects: [
223
+ {
224
+ projectName: 'versioned-project',
225
+ projectDir,
226
+ mode: 'glue',
227
+ envPath: '.env',
228
+ },
229
+ ],
230
+ });
231
+ const parsedIndex = JSON5.parse(readFileSync(indexPath, 'utf8'));
232
+ expect(parsedIndex).toMatchObject({
233
+ version: 1,
234
+ projects: [
235
+ {
236
+ projectName: 'versioned-project',
237
+ projectDir,
238
+ },
239
+ ],
240
+ });
241
+ const legacyHomeDir = createTempDir('ep-stage-legacy-version');
242
+ const legacyProjectDir = createTempDir('ep-stage-legacy-project');
243
+ const legacyIndexDir = path.join(legacyHomeDir, '.ep-stage');
244
+ mkdirSync(legacyProjectDir, { recursive: true });
245
+ mkdirSync(legacyIndexDir, { recursive: true });
246
+ writeFileSync(path.join(legacyProjectDir, '.env'), 'LOGIN_USERNAME=demo-user\n');
247
+ writeFileSync(path.join(legacyIndexDir, 'projects.index.json5'), `{
248
+ // legacy index without version
249
+ projects: [
250
+ {
251
+ projectName: 'legacy-project',
252
+ projectDir: '${legacyProjectDir}',
253
+ mode: 'glue',
254
+ envPath: '.env',
255
+ },
256
+ ],
257
+ }`);
258
+ expect(resolveStageContext({
259
+ projectDir: legacyProjectDir,
260
+ homeDir: legacyHomeDir,
261
+ }).context.projectName).toBe('legacy-project');
262
+ });
263
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=testcase-generator.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testcase-generator.test.d.ts","sourceRoot":"","sources":["../../test/testcase-generator.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,276 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { generateGlueTestcases } from '../src/index.js';
3
+ /**
4
+ * 构造用例生成器测试所需的最小 CRUD 契约。
5
+ *
6
+ * @returns 包含完整 CRUD 流程槽位的测试契约。
7
+ */
8
+ function createCrudContract() {
9
+ return {
10
+ contractVersion: 'crud-business-module/v1',
11
+ module: {
12
+ id: 'zwplace',
13
+ label: '场所窗口信息管理',
14
+ },
15
+ pages: {
16
+ list: {
17
+ role: 'list',
18
+ pageId: 'gxhzwplacelist',
19
+ title: '场所窗口信息列表',
20
+ iframeSrcKeyword: 'gxhzwplacelist',
21
+ fields: [],
22
+ buttons: [],
23
+ grids: [],
24
+ dialogs: [],
25
+ sources: [],
26
+ },
27
+ },
28
+ dataKey: {
29
+ field: 'windowname',
30
+ label: '窗口名称',
31
+ availability: 'derivable',
32
+ generationStrategy: 'timestamp_prefix',
33
+ sources: [],
34
+ },
35
+ searchConditions: [
36
+ {
37
+ field: 'windowname',
38
+ label: '窗口名称',
39
+ component: 'input',
40
+ seedValue: '自动化测试窗口',
41
+ generationStrategy: 'timestamp_prefix',
42
+ sources: [],
43
+ },
44
+ ],
45
+ flows: {
46
+ create: {
47
+ entryButton: {
48
+ pageRole: 'list',
49
+ label: '新增场所窗口',
50
+ sources: [],
51
+ },
52
+ targetPageId: 'gxhzwplaceadd',
53
+ saveButton: {
54
+ pageRole: 'add',
55
+ label: '保存并关闭',
56
+ sources: [],
57
+ },
58
+ overrideFields: [],
59
+ },
60
+ search: {
61
+ searchField: {
62
+ pageRole: 'list',
63
+ field: 'windowname',
64
+ label: '窗口名称',
65
+ sources: [],
66
+ },
67
+ submitControl: {
68
+ pageRole: 'list',
69
+ label: '查询',
70
+ sources: [],
71
+ },
72
+ resultGrid: 'datagrid',
73
+ },
74
+ update: {
75
+ entryAction: {
76
+ pageRole: 'list',
77
+ label: '修改',
78
+ sources: [],
79
+ },
80
+ targetPageId: 'gxhzwplaceedit',
81
+ updateField: {
82
+ pageRole: 'edit',
83
+ field: 'windowname',
84
+ label: '窗口名称',
85
+ sources: [],
86
+ },
87
+ saveButton: {
88
+ pageRole: 'edit',
89
+ label: '保存并关闭',
90
+ sources: [],
91
+ },
92
+ },
93
+ delete: {
94
+ entryButton: {
95
+ pageRole: 'list',
96
+ label: '删除选定',
97
+ sources: [],
98
+ },
99
+ expectedOutcome: 'success_delete',
100
+ confirmControl: {
101
+ pageRole: 'list',
102
+ label: '确定',
103
+ sources: [],
104
+ },
105
+ },
106
+ },
107
+ assertions: [],
108
+ businessRules: [],
109
+ unresolvedSlots: [],
110
+ };
111
+ }
112
+ /**
113
+ * 构造用例生成器测试所需的 coverage diff trace。
114
+ *
115
+ * @returns 默认无未确认项的 coverage diff。
116
+ */
117
+ function createCoverageDiff() {
118
+ return {
119
+ moduleId: 'zwplace',
120
+ skeletonCoverageId: 'crud-plus-export/v1',
121
+ covered: [
122
+ {
123
+ actionId: 'zwplace.list.create',
124
+ label: '新增场所信息管理',
125
+ pageId: 'gxhzwplacelist',
126
+ status: 'covered',
127
+ reason: '动作标签已被当前骨架能力覆盖。',
128
+ },
129
+ ],
130
+ partiallyCovered: [],
131
+ gapCandidates: [],
132
+ unresolved: [],
133
+ nestedCrudCandidates: [],
134
+ baseline: { plannedWorkflows: [], candidateWorkflows: [], unresolvedWorkflows: [] },
135
+ comparison: { both: [], onlyBaseline: [], onlyGeneralized: [] },
136
+ reasoningSummary: {
137
+ conclusion: 'CRUD 单页能力已覆盖。',
138
+ evidenceChain: [],
139
+ alternatives: ['crud.single-page'],
140
+ confidence: 'medium',
141
+ risks: [],
142
+ needsHumanReview: false,
143
+ },
144
+ };
145
+ }
146
+ /**
147
+ * 胶水测试用例生成器测试。
148
+ *
149
+ * 验证 generateGlueTestcases 能从同一份 CRUD contract、已确认场景与 coverage diff
150
+ * 生成可审阅 JSON + Markdown。
151
+ */
152
+ describe('glue testcase generator', () => {
153
+ it('renders reviewable markdown and json cases from a confirmed scenario and contract', () => {
154
+ const output = generateGlueTestcases({
155
+ contract: createCrudContract(),
156
+ menu: '场所窗口信息管理>场所窗口信息列表',
157
+ scenarios: ['crud.single-page', 'crud.nested'],
158
+ confirmedScenarios: ['crud.single-page'],
159
+ coverageDiff: createCoverageDiff(),
160
+ });
161
+ expect(output.json.cases).toHaveLength(4);
162
+ expect(output.json.cases.map((item) => item.caseId)).toEqual([
163
+ 'zwplace.create',
164
+ 'zwplace.read',
165
+ 'zwplace.update',
166
+ 'zwplace.delete',
167
+ ]);
168
+ expect(output.json.cases[0]).toMatchObject({
169
+ caseId: 'zwplace.create',
170
+ reviewStatus: 'confirmed',
171
+ });
172
+ expect(output.json.cases[0]?.evidence).toContain('skeleton=crud.skeleton-testcase/v1');
173
+ expect(output.markdown).toContain('| TC-CRUD-001 |');
174
+ expect(output.markdown).toContain('查询字段:窗口名称');
175
+ expect(output.markdown).not.toContain('{{');
176
+ });
177
+ it('requires contract and menu instead of free-form module fields', () => {
178
+ expect(() => generateGlueTestcases({
179
+ moduleId: 'zwplace',
180
+ moduleName: '场所窗口信息管理',
181
+ scenarioStatus: 'confirmed',
182
+ scenarios: ['crud.single-page'],
183
+ confirmedScenarios: ['crud.single-page'],
184
+ coverageDiff: createCoverageDiff(),
185
+ })).toThrow('生成胶水测试用例必须提供 contract 和 menu');
186
+ });
187
+ it('keeps testcase gate in needs_review when coverage diff has unresolved items', () => {
188
+ const coverageDiff = createCoverageDiff();
189
+ coverageDiff.unresolved = [
190
+ {
191
+ actionId: 'zwplace.list.export',
192
+ label: '导出',
193
+ pageId: 'gxhzwplacelist',
194
+ status: 'unresolved',
195
+ reason: '导出动作是否纳入本轮 CRUD 用例需要人工确认。',
196
+ },
197
+ ];
198
+ const output = generateGlueTestcases({
199
+ contract: createCrudContract(),
200
+ menu: '场所窗口信息管理>场所窗口信息列表',
201
+ scenarios: ['crud.single-page'],
202
+ confirmedScenarios: ['crud.single-page'],
203
+ coverageDiff,
204
+ });
205
+ expect(output.json.cases.every((item) => item.reviewStatus === 'needs_review')).toBe(true);
206
+ expect(output.json.cases[0]?.unresolved).toEqual([
207
+ 'coverageDiff.unresolved: 导出动作是否纳入本轮 CRUD 用例需要人工确认。',
208
+ ]);
209
+ expect(output.json.reasoningSummary.risks).toContain('coverageDiff.unresolved: 导出动作是否纳入本轮 CRUD 用例需要人工确认。');
210
+ expect(output.json.reasoningSummary.evidenceChain).toContainEqual({
211
+ source: 'human',
212
+ path: 'testcase-generation.json',
213
+ text: 'unresolved=coverageDiff.unresolved: 导出动作是否纳入本轮 CRUD 用例需要人工确认。',
214
+ });
215
+ });
216
+ it('renders nested CRUD candidates with the CRUD testcase skeleton and unresolved gate', () => {
217
+ const coverageDiff = createCoverageDiff();
218
+ coverageDiff.nestedCrudCandidates = [
219
+ {
220
+ pageId: 'gxhzwplaceadd',
221
+ evidence: ['弹窗页面中存在 mini-datagrid', '按钮 添加窗口'],
222
+ supportedActions: ['create', 'delete'],
223
+ unresolvedReason: '子表保存后断言需要人工确认',
224
+ },
225
+ ];
226
+ const output = generateGlueTestcases({
227
+ contract: createCrudContract(),
228
+ menu: '场所窗口信息管理>场所窗口信息列表',
229
+ scenarios: ['crud.nested'],
230
+ confirmedScenarios: ['crud.nested'],
231
+ coverageDiff,
232
+ });
233
+ expect(output.json.cases[0]).toMatchObject({
234
+ scenario: 'crud.nested',
235
+ reviewStatus: 'needs_review',
236
+ unresolved: ['coverageDiff.nestedCrudCandidates[gxhzwplaceadd]: 子表保存后断言需要人工确认'],
237
+ });
238
+ expect(output.json.cases[0]?.evidence).toContain('skeleton=crud.skeleton-testcase/v1');
239
+ expect(output.markdown).toContain('| TC-CRUD-001 |');
240
+ expect(output.markdown).not.toContain('{{');
241
+ });
242
+ it('does not let nested CRUD candidates block a confirmed crud.single-page scenario', () => {
243
+ const coverageDiff = createCoverageDiff();
244
+ coverageDiff.nestedCrudCandidates = [
245
+ {
246
+ pageId: 'gxhzwplaceadd',
247
+ evidence: ['弹窗页面中存在 mini-datagrid', '按钮 添加窗口'],
248
+ supportedActions: ['create', 'delete'],
249
+ unresolvedReason: '子表保存后断言需要人工确认',
250
+ },
251
+ ];
252
+ const output = generateGlueTestcases({
253
+ contract: createCrudContract(),
254
+ menu: '场所窗口信息管理>场所窗口信息列表',
255
+ scenarios: ['crud.single-page', 'crud.nested'],
256
+ confirmedScenarios: ['crud.single-page'],
257
+ coverageDiff,
258
+ });
259
+ expect(output.json.cases.every((item) => item.reviewStatus === 'confirmed')).toBe(true);
260
+ expect(output.json.cases[0]?.unresolved).toEqual([]);
261
+ expect(output.json.reasoningSummary.needsHumanReview).toBe(false);
262
+ expect(output.json.reasoningSummary.risks).toEqual([]);
263
+ });
264
+ it('surfaces inferred menu candidates in testcase unresolved output for human review', () => {
265
+ const output = generateGlueTestcases({
266
+ contract: createCrudContract(),
267
+ menu: '未确认菜单路径',
268
+ menuReviewHint: 'menu.candidate: 场所窗口信息管理>场所窗口信息列表 | 场所窗口信息列表',
269
+ scenarios: ['crud.single-page'],
270
+ confirmedScenarios: ['crud.single-page'],
271
+ coverageDiff: createCoverageDiff(),
272
+ });
273
+ expect(output.json.cases[0]?.unresolved).toContain('menu.candidate: 场所窗口信息管理>场所窗口信息列表 | 场所窗口信息列表');
274
+ expect(output.markdown).toContain('menu.candidate: 场所窗口信息管理>场所窗口信息列表 | 场所窗口信息列表');
275
+ });
276
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=testcase-skeleton.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testcase-skeleton.test.d.ts","sourceRoot":"","sources":["../../test/testcase-skeleton.test.ts"],"names":[],"mappings":""}