miaoda-game-devkit 0.2.20 → 0.2.21
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/README.md +5 -4
- package/dist/react/testing.d.mts +5 -7
- package/dist/react/testing.d.ts +5 -7
- package/dist/react/testing.js +22 -34
- package/dist/react/testing.mjs +22 -34
- package/dist/react/vitest-config.js +25 -37
- package/dist/react/vitest-config.mjs +25 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
Vitest 配置和自动收集运行错误的 Testing Library setup,分别从 `miaoda-game-devkit/react`、
|
|
9
9
|
`miaoda-game-devkit/react/testing` 和 `miaoda-game-devkit/react/vitest-config` 导入;
|
|
10
10
|
- React 的 `playthroughTest` 与项目级 reporter 自动验证独立的 entry/primary DOM
|
|
11
|
-
|
|
11
|
+
输入、有界推进、结果断言及至少两次有序 checkpoint 签到;聚焦运行只提示未检查,
|
|
12
12
|
完整运行缺少有效流程时失败;
|
|
13
13
|
|
|
14
14
|
`playthroughTest` 的主流程回调从参数接收 `user`、`performInput`、`checkpoint`、
|
|
15
|
-
`stepUntil` 和绑定当前测试的 `expect`。按 entry
|
|
16
|
-
有界等待、使用回调提供的 `expect`
|
|
17
|
-
progress
|
|
15
|
+
`stepUntil` 和绑定当前测试的 `expect`。按 entry 输入、`checkpoint("entered")`、primary 输入、
|
|
16
|
+
有界等待、使用回调提供的 `expect` 断言权威结果、
|
|
17
|
+
`checkpoint("progress")` 或 `checkpoint("terminal")` 的顺序记录最低证据。每条流程至少需要
|
|
18
|
+
这两次 checkpoint;checkpoint 只负责签到,不携带或验证状态。大型游戏仍只保留一条最短关键流程;
|
|
18
19
|
分支、关卡规则和恢复清理由专门测试覆盖。不要再次调用 `userEvent.setup()`。
|
|
19
20
|
时间、帧或自定义 scheduler 流程必须显式传入确定性 `step`;精确物理时间、暂停恢复
|
|
20
21
|
或调度清理测试可使用 `ManualGameClock`。
|
package/dist/react/testing.d.mts
CHANGED
|
@@ -47,8 +47,6 @@ interface ReactPlaythroughEvidence {
|
|
|
47
47
|
boundedRuns: number;
|
|
48
48
|
/** 最后一次 stepUntil 成功后新增的 Vitest 断言数。 */
|
|
49
49
|
assertionsAfterOutcome: number;
|
|
50
|
-
/** 已验证相对 entered snapshot 发生变化的进展或终局 checkpoint 次数。 */
|
|
51
|
-
changedOutcomeCheckpoints: number;
|
|
52
50
|
/** 已记录的 checkpoint 类型,供 reporter 给出精确提示。 */
|
|
53
51
|
checkpoints: ReactPlaythroughCheckpointKind[];
|
|
54
52
|
/** helper 是否已经完成全部证据校验。 */
|
|
@@ -57,7 +55,7 @@ interface ReactPlaythroughEvidence {
|
|
|
57
55
|
/** 通过 Vitest task metadata 从 worker 传递给主线程 reporter 的数据。 */
|
|
58
56
|
interface ReactPlaythroughMetadata {
|
|
59
57
|
/** metadata 结构版本,用于拒绝无法识别的旧数据。 */
|
|
60
|
-
version:
|
|
58
|
+
version: 3;
|
|
61
59
|
/** 静态、非交互项目跳过主流程验证时必须提供的理由。 */
|
|
62
60
|
waiverReason?: string;
|
|
63
61
|
/** 测试执行期间持续更新的客观证据。 */
|
|
@@ -66,7 +64,7 @@ interface ReactPlaythroughMetadata {
|
|
|
66
64
|
|
|
67
65
|
/** 玩家输入在最低可玩流程中的语义阶段。 */
|
|
68
66
|
type ReactPlaythroughInputKind = "entry" | "primary";
|
|
69
|
-
/**
|
|
67
|
+
/** 可审计流程签到;entered 是入口,progress/terminal 是有效结果。 */
|
|
70
68
|
type ReactPlaythroughCheckpointKind = "entered" | "progress" | "terminal";
|
|
71
69
|
/** 主流程回调唯一需要学习的测试工具。 */
|
|
72
70
|
interface ReactPlaythroughArguments {
|
|
@@ -82,10 +80,10 @@ interface ReactPlaythroughArguments {
|
|
|
82
80
|
*/
|
|
83
81
|
performInput(kind: ReactPlaythroughInputKind, input: () => void | Promise<void>): Promise<void>;
|
|
84
82
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
83
|
+
* 记录流程签到。先在 entry 后记录 entered,再在 primary、stepUntil 和
|
|
84
|
+
* 结果断言后记录 progress 或 terminal。
|
|
87
85
|
*/
|
|
88
|
-
checkpoint(kind: ReactPlaythroughCheckpointKind
|
|
86
|
+
checkpoint(kind: ReactPlaythroughCheckpointKind): void;
|
|
89
87
|
/**
|
|
90
88
|
* 在硬性步数上限内等待权威结果。
|
|
91
89
|
* 返回后必须再断言该结果,进入中间 active/running 状态不算完成。
|
package/dist/react/testing.d.ts
CHANGED
|
@@ -47,8 +47,6 @@ interface ReactPlaythroughEvidence {
|
|
|
47
47
|
boundedRuns: number;
|
|
48
48
|
/** 最后一次 stepUntil 成功后新增的 Vitest 断言数。 */
|
|
49
49
|
assertionsAfterOutcome: number;
|
|
50
|
-
/** 已验证相对 entered snapshot 发生变化的进展或终局 checkpoint 次数。 */
|
|
51
|
-
changedOutcomeCheckpoints: number;
|
|
52
50
|
/** 已记录的 checkpoint 类型,供 reporter 给出精确提示。 */
|
|
53
51
|
checkpoints: ReactPlaythroughCheckpointKind[];
|
|
54
52
|
/** helper 是否已经完成全部证据校验。 */
|
|
@@ -57,7 +55,7 @@ interface ReactPlaythroughEvidence {
|
|
|
57
55
|
/** 通过 Vitest task metadata 从 worker 传递给主线程 reporter 的数据。 */
|
|
58
56
|
interface ReactPlaythroughMetadata {
|
|
59
57
|
/** metadata 结构版本,用于拒绝无法识别的旧数据。 */
|
|
60
|
-
version:
|
|
58
|
+
version: 3;
|
|
61
59
|
/** 静态、非交互项目跳过主流程验证时必须提供的理由。 */
|
|
62
60
|
waiverReason?: string;
|
|
63
61
|
/** 测试执行期间持续更新的客观证据。 */
|
|
@@ -66,7 +64,7 @@ interface ReactPlaythroughMetadata {
|
|
|
66
64
|
|
|
67
65
|
/** 玩家输入在最低可玩流程中的语义阶段。 */
|
|
68
66
|
type ReactPlaythroughInputKind = "entry" | "primary";
|
|
69
|
-
/**
|
|
67
|
+
/** 可审计流程签到;entered 是入口,progress/terminal 是有效结果。 */
|
|
70
68
|
type ReactPlaythroughCheckpointKind = "entered" | "progress" | "terminal";
|
|
71
69
|
/** 主流程回调唯一需要学习的测试工具。 */
|
|
72
70
|
interface ReactPlaythroughArguments {
|
|
@@ -82,10 +80,10 @@ interface ReactPlaythroughArguments {
|
|
|
82
80
|
*/
|
|
83
81
|
performInput(kind: ReactPlaythroughInputKind, input: () => void | Promise<void>): Promise<void>;
|
|
84
82
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
83
|
+
* 记录流程签到。先在 entry 后记录 entered,再在 primary、stepUntil 和
|
|
84
|
+
* 结果断言后记录 progress 或 terminal。
|
|
87
85
|
*/
|
|
88
|
-
checkpoint(kind: ReactPlaythroughCheckpointKind
|
|
86
|
+
checkpoint(kind: ReactPlaythroughCheckpointKind): void;
|
|
89
87
|
/**
|
|
90
88
|
* 在硬性步数上限内等待权威结果。
|
|
91
89
|
* 返回后必须再断言该结果,进入中间 active/running 状态不算完成。
|
package/dist/react/testing.js
CHANGED
|
@@ -151,20 +151,25 @@ var INPUT_EVENTS = [
|
|
|
151
151
|
"touchstart",
|
|
152
152
|
"touchend"
|
|
153
153
|
];
|
|
154
|
+
var MIN_CHECKPOINTS = 2;
|
|
154
155
|
function describeMissingEvidence(evidence) {
|
|
155
156
|
if (!evidence || evidence.entryInputs === 0) return "entry \u8F93\u5165";
|
|
156
157
|
if (evidence.primaryInputs === 0) return "primary \u8F93\u5165";
|
|
157
158
|
if (!evidence.checkpoints.includes("entered")) return "entered checkpoint";
|
|
158
159
|
if (evidence.boundedRuns === 0) return "\u6709\u754C stepUntil";
|
|
159
160
|
if (evidence.assertionsAfterOutcome === 0) return "stepUntil \u540E\u7684\u7ED3\u679C\u65AD\u8A00";
|
|
160
|
-
if (evidence.
|
|
161
|
-
return
|
|
161
|
+
if (evidence.checkpoints.length < MIN_CHECKPOINTS)
|
|
162
|
+
return `\u81F3\u5C11 ${MIN_CHECKPOINTS} \u4E2A checkpoint`;
|
|
163
|
+
if (!evidence.checkpoints.some(
|
|
164
|
+
(checkpoint) => checkpoint === "progress" || checkpoint === "terminal"
|
|
165
|
+
)) {
|
|
166
|
+
return "progress/terminal checkpoint";
|
|
162
167
|
}
|
|
163
168
|
return "\u5B8C\u6574\u7684 playthrough \u6821\u9A8C\u6807\u8BB0";
|
|
164
169
|
}
|
|
165
170
|
function createMetadata(waiverReason) {
|
|
166
171
|
return {
|
|
167
|
-
version:
|
|
172
|
+
version: 3,
|
|
168
173
|
waiverReason,
|
|
169
174
|
evidence: {
|
|
170
175
|
domInputEvents: 0,
|
|
@@ -172,23 +177,11 @@ function createMetadata(waiverReason) {
|
|
|
172
177
|
primaryInputs: 0,
|
|
173
178
|
boundedRuns: 0,
|
|
174
179
|
assertionsAfterOutcome: 0,
|
|
175
|
-
changedOutcomeCheckpoints: 0,
|
|
176
180
|
checkpoints: [],
|
|
177
181
|
verified: false
|
|
178
182
|
}
|
|
179
183
|
};
|
|
180
184
|
}
|
|
181
|
-
function snapshotFingerprint(snapshot) {
|
|
182
|
-
try {
|
|
183
|
-
const serialized = JSON.stringify(snapshot);
|
|
184
|
-
if (serialized === void 0) throw new Error("unsupported value");
|
|
185
|
-
return serialized;
|
|
186
|
-
} catch {
|
|
187
|
-
throw new Error(
|
|
188
|
-
"checkpoint snapshot must be JSON-serializable. Pass a read-only Telemetry snapshot or a small visible-state object."
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
185
|
function definePlaythrough(element, run, waiverReason) {
|
|
193
186
|
const reason = normalizePlaythroughWaiverReason(waiverReason);
|
|
194
187
|
const metadata = createMetadata(reason);
|
|
@@ -198,7 +191,7 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
198
191
|
}, async ({ expect }) => {
|
|
199
192
|
const evidence = metadata.evidence;
|
|
200
193
|
let assertionsAtOutcome;
|
|
201
|
-
let
|
|
194
|
+
let enteredRecorded = false;
|
|
202
195
|
const recordInput = () => {
|
|
203
196
|
evidence.domInputEvents += 1;
|
|
204
197
|
};
|
|
@@ -218,14 +211,14 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
218
211
|
user,
|
|
219
212
|
expect,
|
|
220
213
|
async performInput(kind, input) {
|
|
221
|
-
if (kind === "entry" &&
|
|
214
|
+
if (kind === "entry" && enteredRecorded) {
|
|
222
215
|
throw new Error(
|
|
223
216
|
'performInput("entry") must run before checkpoint("entered"). Group multiple setup actions in the same callback.'
|
|
224
217
|
);
|
|
225
218
|
}
|
|
226
|
-
if (kind === "primary" &&
|
|
219
|
+
if (kind === "primary" && !enteredRecorded) {
|
|
227
220
|
throw new Error(
|
|
228
|
-
'Before performInput("primary"), run an entry input and checkpoint("entered"
|
|
221
|
+
'Before performInput("primary"), run an entry input and checkpoint("entered").'
|
|
229
222
|
);
|
|
230
223
|
}
|
|
231
224
|
const inputsBefore = evidence.domInputEvents;
|
|
@@ -238,10 +231,9 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
238
231
|
if (kind === "entry") evidence.entryInputs += 1;
|
|
239
232
|
else evidence.primaryInputs += 1;
|
|
240
233
|
},
|
|
241
|
-
checkpoint(kind
|
|
242
|
-
const fingerprint = snapshotFingerprint(snapshot);
|
|
234
|
+
checkpoint(kind) {
|
|
243
235
|
if (kind === "entered") {
|
|
244
|
-
if (
|
|
236
|
+
if (enteredRecorded) {
|
|
245
237
|
throw new Error(
|
|
246
238
|
'checkpoint("entered") may only be recorded once, before the primary input.'
|
|
247
239
|
);
|
|
@@ -251,7 +243,7 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
251
243
|
'checkpoint("entered") must follow performInput("entry", ...).'
|
|
252
244
|
);
|
|
253
245
|
}
|
|
254
|
-
|
|
246
|
+
enteredRecorded = true;
|
|
255
247
|
evidence.checkpoints.push(kind);
|
|
256
248
|
return;
|
|
257
249
|
}
|
|
@@ -267,15 +259,9 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
267
259
|
}
|
|
268
260
|
if (assertionsAtOutcome === void 0 || expect.getState().assertionCalls <= assertionsAtOutcome) {
|
|
269
261
|
throw new Error(
|
|
270
|
-
`Use the expect provided by playthroughTest to assert the authoritative result after stepUntil, then record checkpoint("${kind}"
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
if (fingerprint === enteredSnapshot) {
|
|
274
|
-
throw new Error(
|
|
275
|
-
`checkpoint("${kind}") matches the entered snapshot. Assert a production state change caused by the primary input.`
|
|
262
|
+
`Use the expect provided by playthroughTest to assert the authoritative result after stepUntil, then record checkpoint("${kind}").`
|
|
276
263
|
);
|
|
277
264
|
}
|
|
278
|
-
evidence.changedOutcomeCheckpoints += 1;
|
|
279
265
|
evidence.checkpoints.push(kind);
|
|
280
266
|
},
|
|
281
267
|
async stepUntil(condition, options = {}) {
|
|
@@ -310,9 +296,11 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
310
296
|
"Use the expect provided by playthroughTest to assert an authoritative game outcome after stepUntil returns."
|
|
311
297
|
);
|
|
312
298
|
}
|
|
313
|
-
if (evidence.
|
|
299
|
+
if (evidence.checkpoints.length < MIN_CHECKPOINTS || !evidence.checkpoints.some(
|
|
300
|
+
(checkpoint) => checkpoint === "progress" || checkpoint === "terminal"
|
|
301
|
+
)) {
|
|
314
302
|
throw new Error(
|
|
315
|
-
|
|
303
|
+
`playthroughTest requires at least ${MIN_CHECKPOINTS} checkpoints: checkpoint("entered") and, after asserting the result, checkpoint("progress") or checkpoint("terminal").`
|
|
316
304
|
);
|
|
317
305
|
}
|
|
318
306
|
evidence.verified = true;
|
|
@@ -333,7 +321,7 @@ function auditReactPlaythroughRun(tests) {
|
|
|
333
321
|
const declared = tests.filter((candidate) => candidate.metadata);
|
|
334
322
|
const valid = declared.filter(({ state, metadata }) => {
|
|
335
323
|
const evidence = metadata?.evidence;
|
|
336
|
-
return state === "passed" && evidence?.verified === true && evidence.domInputEvents > 0 && evidence.entryInputs > 0 && evidence.primaryInputs > 0 && evidence.boundedRuns > 0 && evidence.assertionsAfterOutcome > 0 && evidence.
|
|
324
|
+
return state === "passed" && evidence?.verified === true && evidence.domInputEvents > 0 && evidence.entryInputs > 0 && evidence.primaryInputs > 0 && evidence.boundedRuns > 0 && evidence.assertionsAfterOutcome > 0 && evidence.checkpoints.length >= MIN_CHECKPOINTS && evidence.checkpoints.includes("entered") && evidence.checkpoints.some(
|
|
337
325
|
(checkpoint) => checkpoint === "progress" || checkpoint === "terminal"
|
|
338
326
|
);
|
|
339
327
|
});
|
|
@@ -343,7 +331,7 @@ function auditReactPlaythroughRun(tests) {
|
|
|
343
331
|
const issues = [];
|
|
344
332
|
if (declared.length === 0) {
|
|
345
333
|
issues.push(
|
|
346
|
-
'\u7F3A\u5C11\u751F\u4EA7\u6E38\u620F\u53EF\u73A9\u6027\u9A8C\u8BC1\uFF1A\u4F7F\u7528 playthroughTest \u6E32\u67D3 <App />\uFF0C\u4F9D\u6B21\u6267\u884C performInput("entry")\u3001checkpoint("entered")\u3001performInput("primary")\u3001stepUntil\u3001\u7528 playthroughTest \u63D0\u4F9B\u7684 expect \u65AD\u8A00\u7ED3\u679C\uFF0C\u5E76\u8BB0\u5F55 progress
|
|
334
|
+
'\u7F3A\u5C11\u751F\u4EA7\u6E38\u620F\u53EF\u73A9\u6027\u9A8C\u8BC1\uFF1A\u4F7F\u7528 playthroughTest \u6E32\u67D3 <App />\uFF0C\u4F9D\u6B21\u6267\u884C performInput("entry")\u3001checkpoint("entered")\u3001performInput("primary")\u3001stepUntil\u3001\u7528 playthroughTest \u63D0\u4F9B\u7684 expect \u65AD\u8A00\u6743\u5A01\u7ED3\u679C\uFF0C\u5E76\u8BB0\u5F55 checkpoint("progress") \u6216 checkpoint("terminal")\uFF1B\u81F3\u5C11\u5B8C\u6210\u8FD9\u4E24\u6B21 checkpoint \u7B7E\u5230\u3002'
|
|
347
335
|
);
|
|
348
336
|
} else {
|
|
349
337
|
for (const candidate of declared) {
|
package/dist/react/testing.mjs
CHANGED
|
@@ -113,20 +113,25 @@ var INPUT_EVENTS = [
|
|
|
113
113
|
"touchstart",
|
|
114
114
|
"touchend"
|
|
115
115
|
];
|
|
116
|
+
var MIN_CHECKPOINTS = 2;
|
|
116
117
|
function describeMissingEvidence(evidence) {
|
|
117
118
|
if (!evidence || evidence.entryInputs === 0) return "entry \u8F93\u5165";
|
|
118
119
|
if (evidence.primaryInputs === 0) return "primary \u8F93\u5165";
|
|
119
120
|
if (!evidence.checkpoints.includes("entered")) return "entered checkpoint";
|
|
120
121
|
if (evidence.boundedRuns === 0) return "\u6709\u754C stepUntil";
|
|
121
122
|
if (evidence.assertionsAfterOutcome === 0) return "stepUntil \u540E\u7684\u7ED3\u679C\u65AD\u8A00";
|
|
122
|
-
if (evidence.
|
|
123
|
-
return
|
|
123
|
+
if (evidence.checkpoints.length < MIN_CHECKPOINTS)
|
|
124
|
+
return `\u81F3\u5C11 ${MIN_CHECKPOINTS} \u4E2A checkpoint`;
|
|
125
|
+
if (!evidence.checkpoints.some(
|
|
126
|
+
(checkpoint) => checkpoint === "progress" || checkpoint === "terminal"
|
|
127
|
+
)) {
|
|
128
|
+
return "progress/terminal checkpoint";
|
|
124
129
|
}
|
|
125
130
|
return "\u5B8C\u6574\u7684 playthrough \u6821\u9A8C\u6807\u8BB0";
|
|
126
131
|
}
|
|
127
132
|
function createMetadata(waiverReason) {
|
|
128
133
|
return {
|
|
129
|
-
version:
|
|
134
|
+
version: 3,
|
|
130
135
|
waiverReason,
|
|
131
136
|
evidence: {
|
|
132
137
|
domInputEvents: 0,
|
|
@@ -134,23 +139,11 @@ function createMetadata(waiverReason) {
|
|
|
134
139
|
primaryInputs: 0,
|
|
135
140
|
boundedRuns: 0,
|
|
136
141
|
assertionsAfterOutcome: 0,
|
|
137
|
-
changedOutcomeCheckpoints: 0,
|
|
138
142
|
checkpoints: [],
|
|
139
143
|
verified: false
|
|
140
144
|
}
|
|
141
145
|
};
|
|
142
146
|
}
|
|
143
|
-
function snapshotFingerprint(snapshot) {
|
|
144
|
-
try {
|
|
145
|
-
const serialized = JSON.stringify(snapshot);
|
|
146
|
-
if (serialized === void 0) throw new Error("unsupported value");
|
|
147
|
-
return serialized;
|
|
148
|
-
} catch {
|
|
149
|
-
throw new Error(
|
|
150
|
-
"checkpoint snapshot must be JSON-serializable. Pass a read-only Telemetry snapshot or a small visible-state object."
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
147
|
function definePlaythrough(element, run, waiverReason) {
|
|
155
148
|
const reason = normalizePlaythroughWaiverReason(waiverReason);
|
|
156
149
|
const metadata = createMetadata(reason);
|
|
@@ -160,7 +153,7 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
160
153
|
}, async ({ expect }) => {
|
|
161
154
|
const evidence = metadata.evidence;
|
|
162
155
|
let assertionsAtOutcome;
|
|
163
|
-
let
|
|
156
|
+
let enteredRecorded = false;
|
|
164
157
|
const recordInput = () => {
|
|
165
158
|
evidence.domInputEvents += 1;
|
|
166
159
|
};
|
|
@@ -180,14 +173,14 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
180
173
|
user,
|
|
181
174
|
expect,
|
|
182
175
|
async performInput(kind, input) {
|
|
183
|
-
if (kind === "entry" &&
|
|
176
|
+
if (kind === "entry" && enteredRecorded) {
|
|
184
177
|
throw new Error(
|
|
185
178
|
'performInput("entry") must run before checkpoint("entered"). Group multiple setup actions in the same callback.'
|
|
186
179
|
);
|
|
187
180
|
}
|
|
188
|
-
if (kind === "primary" &&
|
|
181
|
+
if (kind === "primary" && !enteredRecorded) {
|
|
189
182
|
throw new Error(
|
|
190
|
-
'Before performInput("primary"), run an entry input and checkpoint("entered"
|
|
183
|
+
'Before performInput("primary"), run an entry input and checkpoint("entered").'
|
|
191
184
|
);
|
|
192
185
|
}
|
|
193
186
|
const inputsBefore = evidence.domInputEvents;
|
|
@@ -200,10 +193,9 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
200
193
|
if (kind === "entry") evidence.entryInputs += 1;
|
|
201
194
|
else evidence.primaryInputs += 1;
|
|
202
195
|
},
|
|
203
|
-
checkpoint(kind
|
|
204
|
-
const fingerprint = snapshotFingerprint(snapshot);
|
|
196
|
+
checkpoint(kind) {
|
|
205
197
|
if (kind === "entered") {
|
|
206
|
-
if (
|
|
198
|
+
if (enteredRecorded) {
|
|
207
199
|
throw new Error(
|
|
208
200
|
'checkpoint("entered") may only be recorded once, before the primary input.'
|
|
209
201
|
);
|
|
@@ -213,7 +205,7 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
213
205
|
'checkpoint("entered") must follow performInput("entry", ...).'
|
|
214
206
|
);
|
|
215
207
|
}
|
|
216
|
-
|
|
208
|
+
enteredRecorded = true;
|
|
217
209
|
evidence.checkpoints.push(kind);
|
|
218
210
|
return;
|
|
219
211
|
}
|
|
@@ -229,15 +221,9 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
229
221
|
}
|
|
230
222
|
if (assertionsAtOutcome === void 0 || expect.getState().assertionCalls <= assertionsAtOutcome) {
|
|
231
223
|
throw new Error(
|
|
232
|
-
`Use the expect provided by playthroughTest to assert the authoritative result after stepUntil, then record checkpoint("${kind}"
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
if (fingerprint === enteredSnapshot) {
|
|
236
|
-
throw new Error(
|
|
237
|
-
`checkpoint("${kind}") matches the entered snapshot. Assert a production state change caused by the primary input.`
|
|
224
|
+
`Use the expect provided by playthroughTest to assert the authoritative result after stepUntil, then record checkpoint("${kind}").`
|
|
238
225
|
);
|
|
239
226
|
}
|
|
240
|
-
evidence.changedOutcomeCheckpoints += 1;
|
|
241
227
|
evidence.checkpoints.push(kind);
|
|
242
228
|
},
|
|
243
229
|
async stepUntil(condition, options = {}) {
|
|
@@ -272,9 +258,11 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
272
258
|
"Use the expect provided by playthroughTest to assert an authoritative game outcome after stepUntil returns."
|
|
273
259
|
);
|
|
274
260
|
}
|
|
275
|
-
if (evidence.
|
|
261
|
+
if (evidence.checkpoints.length < MIN_CHECKPOINTS || !evidence.checkpoints.some(
|
|
262
|
+
(checkpoint) => checkpoint === "progress" || checkpoint === "terminal"
|
|
263
|
+
)) {
|
|
276
264
|
throw new Error(
|
|
277
|
-
|
|
265
|
+
`playthroughTest requires at least ${MIN_CHECKPOINTS} checkpoints: checkpoint("entered") and, after asserting the result, checkpoint("progress") or checkpoint("terminal").`
|
|
278
266
|
);
|
|
279
267
|
}
|
|
280
268
|
evidence.verified = true;
|
|
@@ -295,7 +283,7 @@ function auditReactPlaythroughRun(tests) {
|
|
|
295
283
|
const declared = tests.filter((candidate) => candidate.metadata);
|
|
296
284
|
const valid = declared.filter(({ state, metadata }) => {
|
|
297
285
|
const evidence = metadata?.evidence;
|
|
298
|
-
return state === "passed" && evidence?.verified === true && evidence.domInputEvents > 0 && evidence.entryInputs > 0 && evidence.primaryInputs > 0 && evidence.boundedRuns > 0 && evidence.assertionsAfterOutcome > 0 && evidence.
|
|
286
|
+
return state === "passed" && evidence?.verified === true && evidence.domInputEvents > 0 && evidence.entryInputs > 0 && evidence.primaryInputs > 0 && evidence.boundedRuns > 0 && evidence.assertionsAfterOutcome > 0 && evidence.checkpoints.length >= MIN_CHECKPOINTS && evidence.checkpoints.includes("entered") && evidence.checkpoints.some(
|
|
299
287
|
(checkpoint) => checkpoint === "progress" || checkpoint === "terminal"
|
|
300
288
|
);
|
|
301
289
|
});
|
|
@@ -305,7 +293,7 @@ function auditReactPlaythroughRun(tests) {
|
|
|
305
293
|
const issues = [];
|
|
306
294
|
if (declared.length === 0) {
|
|
307
295
|
issues.push(
|
|
308
|
-
'\u7F3A\u5C11\u751F\u4EA7\u6E38\u620F\u53EF\u73A9\u6027\u9A8C\u8BC1\uFF1A\u4F7F\u7528 playthroughTest \u6E32\u67D3 <App />\uFF0C\u4F9D\u6B21\u6267\u884C performInput("entry")\u3001checkpoint("entered")\u3001performInput("primary")\u3001stepUntil\u3001\u7528 playthroughTest \u63D0\u4F9B\u7684 expect \u65AD\u8A00\u7ED3\u679C\uFF0C\u5E76\u8BB0\u5F55 progress
|
|
296
|
+
'\u7F3A\u5C11\u751F\u4EA7\u6E38\u620F\u53EF\u73A9\u6027\u9A8C\u8BC1\uFF1A\u4F7F\u7528 playthroughTest \u6E32\u67D3 <App />\uFF0C\u4F9D\u6B21\u6267\u884C performInput("entry")\u3001checkpoint("entered")\u3001performInput("primary")\u3001stepUntil\u3001\u7528 playthroughTest \u63D0\u4F9B\u7684 expect \u65AD\u8A00\u6743\u5A01\u7ED3\u679C\uFF0C\u5E76\u8BB0\u5F55 checkpoint("progress") \u6216 checkpoint("terminal")\uFF1B\u81F3\u5C11\u5B8C\u6210\u8FD9\u4E24\u6B21 checkpoint \u7B7E\u5230\u3002'
|
|
309
297
|
);
|
|
310
298
|
} else {
|
|
311
299
|
for (const candidate of declared) {
|
|
@@ -101,20 +101,25 @@ var INPUT_EVENTS = [
|
|
|
101
101
|
"touchstart",
|
|
102
102
|
"touchend"
|
|
103
103
|
];
|
|
104
|
+
var MIN_CHECKPOINTS = 2;
|
|
104
105
|
function describeMissingEvidence(evidence) {
|
|
105
106
|
if (!evidence || evidence.entryInputs === 0) return "entry \u8F93\u5165";
|
|
106
107
|
if (evidence.primaryInputs === 0) return "primary \u8F93\u5165";
|
|
107
108
|
if (!evidence.checkpoints.includes("entered")) return "entered checkpoint";
|
|
108
109
|
if (evidence.boundedRuns === 0) return "\u6709\u754C stepUntil";
|
|
109
110
|
if (evidence.assertionsAfterOutcome === 0) return "stepUntil \u540E\u7684\u7ED3\u679C\u65AD\u8A00";
|
|
110
|
-
if (evidence.
|
|
111
|
-
return
|
|
111
|
+
if (evidence.checkpoints.length < MIN_CHECKPOINTS)
|
|
112
|
+
return `\u81F3\u5C11 ${MIN_CHECKPOINTS} \u4E2A checkpoint`;
|
|
113
|
+
if (!evidence.checkpoints.some(
|
|
114
|
+
(checkpoint) => checkpoint === "progress" || checkpoint === "terminal"
|
|
115
|
+
)) {
|
|
116
|
+
return "progress/terminal checkpoint";
|
|
112
117
|
}
|
|
113
118
|
return "\u5B8C\u6574\u7684 playthrough \u6821\u9A8C\u6807\u8BB0";
|
|
114
119
|
}
|
|
115
120
|
function createMetadata(waiverReason) {
|
|
116
121
|
return {
|
|
117
|
-
version:
|
|
122
|
+
version: 3,
|
|
118
123
|
waiverReason,
|
|
119
124
|
evidence: {
|
|
120
125
|
domInputEvents: 0,
|
|
@@ -122,23 +127,11 @@ function createMetadata(waiverReason) {
|
|
|
122
127
|
primaryInputs: 0,
|
|
123
128
|
boundedRuns: 0,
|
|
124
129
|
assertionsAfterOutcome: 0,
|
|
125
|
-
changedOutcomeCheckpoints: 0,
|
|
126
130
|
checkpoints: [],
|
|
127
131
|
verified: false
|
|
128
132
|
}
|
|
129
133
|
};
|
|
130
134
|
}
|
|
131
|
-
function snapshotFingerprint(snapshot) {
|
|
132
|
-
try {
|
|
133
|
-
const serialized = JSON.stringify(snapshot);
|
|
134
|
-
if (serialized === void 0) throw new Error("unsupported value");
|
|
135
|
-
return serialized;
|
|
136
|
-
} catch {
|
|
137
|
-
throw new Error(
|
|
138
|
-
"checkpoint snapshot must be JSON-serializable. Pass a read-only Telemetry snapshot or a small visible-state object."
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
135
|
function definePlaythrough(element, run, waiverReason) {
|
|
143
136
|
const reason = normalizePlaythroughWaiverReason(waiverReason);
|
|
144
137
|
const metadata = createMetadata(reason);
|
|
@@ -148,7 +141,7 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
148
141
|
}, async ({ expect }) => {
|
|
149
142
|
const evidence = metadata.evidence;
|
|
150
143
|
let assertionsAtOutcome;
|
|
151
|
-
let
|
|
144
|
+
let enteredRecorded = false;
|
|
152
145
|
const recordInput = () => {
|
|
153
146
|
evidence.domInputEvents += 1;
|
|
154
147
|
};
|
|
@@ -168,14 +161,14 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
168
161
|
user,
|
|
169
162
|
expect,
|
|
170
163
|
async performInput(kind, input) {
|
|
171
|
-
if (kind === "entry" &&
|
|
164
|
+
if (kind === "entry" && enteredRecorded) {
|
|
172
165
|
throw new Error(
|
|
173
166
|
'performInput("entry") must run before checkpoint("entered"). Group multiple setup actions in the same callback.'
|
|
174
167
|
);
|
|
175
168
|
}
|
|
176
|
-
if (kind === "primary" &&
|
|
169
|
+
if (kind === "primary" && !enteredRecorded) {
|
|
177
170
|
throw new Error(
|
|
178
|
-
'Before performInput("primary"), run an entry input and checkpoint("entered"
|
|
171
|
+
'Before performInput("primary"), run an entry input and checkpoint("entered").'
|
|
179
172
|
);
|
|
180
173
|
}
|
|
181
174
|
const inputsBefore = evidence.domInputEvents;
|
|
@@ -188,10 +181,9 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
188
181
|
if (kind === "entry") evidence.entryInputs += 1;
|
|
189
182
|
else evidence.primaryInputs += 1;
|
|
190
183
|
},
|
|
191
|
-
checkpoint(kind
|
|
192
|
-
const fingerprint = snapshotFingerprint(snapshot);
|
|
184
|
+
checkpoint(kind) {
|
|
193
185
|
if (kind === "entered") {
|
|
194
|
-
if (
|
|
186
|
+
if (enteredRecorded) {
|
|
195
187
|
throw new Error(
|
|
196
188
|
'checkpoint("entered") may only be recorded once, before the primary input.'
|
|
197
189
|
);
|
|
@@ -201,7 +193,7 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
201
193
|
'checkpoint("entered") must follow performInput("entry", ...).'
|
|
202
194
|
);
|
|
203
195
|
}
|
|
204
|
-
|
|
196
|
+
enteredRecorded = true;
|
|
205
197
|
evidence.checkpoints.push(kind);
|
|
206
198
|
return;
|
|
207
199
|
}
|
|
@@ -217,15 +209,9 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
217
209
|
}
|
|
218
210
|
if (assertionsAtOutcome === void 0 || expect.getState().assertionCalls <= assertionsAtOutcome) {
|
|
219
211
|
throw new Error(
|
|
220
|
-
`Use the expect provided by playthroughTest to assert the authoritative result after stepUntil, then record checkpoint("${kind}"
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
if (fingerprint === enteredSnapshot) {
|
|
224
|
-
throw new Error(
|
|
225
|
-
`checkpoint("${kind}") matches the entered snapshot. Assert a production state change caused by the primary input.`
|
|
212
|
+
`Use the expect provided by playthroughTest to assert the authoritative result after stepUntil, then record checkpoint("${kind}").`
|
|
226
213
|
);
|
|
227
214
|
}
|
|
228
|
-
evidence.changedOutcomeCheckpoints += 1;
|
|
229
215
|
evidence.checkpoints.push(kind);
|
|
230
216
|
},
|
|
231
217
|
async stepUntil(condition, options = {}) {
|
|
@@ -260,9 +246,11 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
260
246
|
"Use the expect provided by playthroughTest to assert an authoritative game outcome after stepUntil returns."
|
|
261
247
|
);
|
|
262
248
|
}
|
|
263
|
-
if (evidence.
|
|
249
|
+
if (evidence.checkpoints.length < MIN_CHECKPOINTS || !evidence.checkpoints.some(
|
|
250
|
+
(checkpoint) => checkpoint === "progress" || checkpoint === "terminal"
|
|
251
|
+
)) {
|
|
264
252
|
throw new Error(
|
|
265
|
-
|
|
253
|
+
`playthroughTest requires at least ${MIN_CHECKPOINTS} checkpoints: checkpoint("entered") and, after asserting the result, checkpoint("progress") or checkpoint("terminal").`
|
|
266
254
|
);
|
|
267
255
|
}
|
|
268
256
|
evidence.verified = true;
|
|
@@ -283,7 +271,7 @@ function auditReactPlaythroughRun(tests) {
|
|
|
283
271
|
const declared = tests.filter((candidate) => candidate.metadata);
|
|
284
272
|
const valid = declared.filter(({ state, metadata }) => {
|
|
285
273
|
const evidence = metadata?.evidence;
|
|
286
|
-
return state === "passed" && evidence?.verified === true && evidence.domInputEvents > 0 && evidence.entryInputs > 0 && evidence.primaryInputs > 0 && evidence.boundedRuns > 0 && evidence.assertionsAfterOutcome > 0 && evidence.
|
|
274
|
+
return state === "passed" && evidence?.verified === true && evidence.domInputEvents > 0 && evidence.entryInputs > 0 && evidence.primaryInputs > 0 && evidence.boundedRuns > 0 && evidence.assertionsAfterOutcome > 0 && evidence.checkpoints.length >= MIN_CHECKPOINTS && evidence.checkpoints.includes("entered") && evidence.checkpoints.some(
|
|
287
275
|
(checkpoint) => checkpoint === "progress" || checkpoint === "terminal"
|
|
288
276
|
);
|
|
289
277
|
});
|
|
@@ -293,7 +281,7 @@ function auditReactPlaythroughRun(tests) {
|
|
|
293
281
|
const issues = [];
|
|
294
282
|
if (declared.length === 0) {
|
|
295
283
|
issues.push(
|
|
296
|
-
'\u7F3A\u5C11\u751F\u4EA7\u6E38\u620F\u53EF\u73A9\u6027\u9A8C\u8BC1\uFF1A\u4F7F\u7528 playthroughTest \u6E32\u67D3 <App />\uFF0C\u4F9D\u6B21\u6267\u884C performInput("entry")\u3001checkpoint("entered")\u3001performInput("primary")\u3001stepUntil\u3001\u7528 playthroughTest \u63D0\u4F9B\u7684 expect \u65AD\u8A00\u7ED3\u679C\uFF0C\u5E76\u8BB0\u5F55 progress
|
|
284
|
+
'\u7F3A\u5C11\u751F\u4EA7\u6E38\u620F\u53EF\u73A9\u6027\u9A8C\u8BC1\uFF1A\u4F7F\u7528 playthroughTest \u6E32\u67D3 <App />\uFF0C\u4F9D\u6B21\u6267\u884C performInput("entry")\u3001checkpoint("entered")\u3001performInput("primary")\u3001stepUntil\u3001\u7528 playthroughTest \u63D0\u4F9B\u7684 expect \u65AD\u8A00\u6743\u5A01\u7ED3\u679C\uFF0C\u5E76\u8BB0\u5F55 checkpoint("progress") \u6216 checkpoint("terminal")\uFF1B\u81F3\u5C11\u5B8C\u6210\u8FD9\u4E24\u6B21 checkpoint \u7B7E\u5230\u3002'
|
|
297
285
|
);
|
|
298
286
|
} else {
|
|
299
287
|
for (const candidate of declared) {
|
|
@@ -323,7 +311,7 @@ var PRODUCTION_PLAYTHROUGH_FILE = "tests/production-playthrough.test.tsx";
|
|
|
323
311
|
function isMetadata(value) {
|
|
324
312
|
if (!value || typeof value !== "object") return false;
|
|
325
313
|
const metadata = value;
|
|
326
|
-
return metadata.version ===
|
|
314
|
+
return metadata.version === 3 && Boolean(metadata.evidence);
|
|
327
315
|
}
|
|
328
316
|
function toAuditInput(test2) {
|
|
329
317
|
const metadata = test2.meta().reactPlaythrough;
|
|
@@ -367,7 +355,7 @@ function assessReactPlaythroughReport(input) {
|
|
|
367
355
|
...base,
|
|
368
356
|
status: "FAILED",
|
|
369
357
|
cause: "\u751F\u4EA7\u53EF\u73A9\u6027\u6D4B\u8BD5\u6587\u4EF6\u4E0D\u5B58\u5728\u3002",
|
|
370
|
-
next: '\u521B\u5EFA\u8BE5\u6587\u4EF6\uFF1A\u4ECE <App /> \u901A\u8FC7\u771F\u5B9E DOM \u8F93\u5165\u4F9D\u6B21\u6267\u884C performInput("entry")\u3001checkpoint("entered")\u3001performInput("primary")\u3001stepUntil\u3001\u7528 playthroughTest \u63D0\u4F9B\u7684 expect \u65AD\u8A00\u7ED3\u679C\uFF0C\u5E76\u8BB0\u5F55 progress
|
|
358
|
+
next: '\u521B\u5EFA\u8BE5\u6587\u4EF6\uFF1A\u4ECE <App /> \u901A\u8FC7\u771F\u5B9E DOM \u8F93\u5165\u4F9D\u6B21\u6267\u884C performInput("entry")\u3001checkpoint("entered")\u3001performInput("primary")\u3001stepUntil\u3001\u7528 playthroughTest \u63D0\u4F9B\u7684 expect \u65AD\u8A00\u6743\u5A01\u7ED3\u679C\uFF0C\u5E76\u8BB0\u5F55 checkpoint("progress") \u6216 checkpoint("terminal")\u3002\u6BCF\u6761\u4E3B\u6D41\u7A0B\u81F3\u5C11\u9700\u8981\u8FD9\u4E24\u6B21 checkpoint \u7B7E\u5230\u3002',
|
|
371
359
|
failsRun: true
|
|
372
360
|
};
|
|
373
361
|
}
|
|
@@ -405,7 +393,7 @@ function assessReactPlaythroughReport(input) {
|
|
|
405
393
|
...base,
|
|
406
394
|
status: "FAILED",
|
|
407
395
|
cause,
|
|
408
|
-
next: missingStep ? "\u8BE5\u6D41\u7A0B\u662F\u65F6\u95F4\u6216\u5E27\u9A71\u52A8\u7684\uFF0C\u4F46 stepUntil \u6CA1\u6709\u63A8\u8FDB\u6E38\u620F\u65F6\u95F4\uFF1B\u4E3A\u751F\u4EA7\u6E38\u620F\u6CE8\u5165 devkit \u7684 GameClock\uFF0C\u5E76\u4F20\u5165 step: () => clock.stepFrame()\u3002\u4E0D\u8981\u7528\u771F\u5B9E setTimeout\u3002" : timedOut ? "\u771F\u5B9E\u8F93\u5165\u5DF2\u6267\u884C\uFF0C\u4F46\u73A9\u6CD5\u6CA1\u6709\u5728\u4E0A\u9650\u5185\u4EA7\u751F\u7ED3\u679C\uFF1B\u68C0\u67E5\u751F\u4EA7\u63A7\u5236\u662F\u5426\u6536\u5230\u8F93\u5165\uFF0C\u518D\u67E5\u770B\u8D85\u65F6\u9519\u8BEF\u4E2D\u7684 Last diagnostics \u5224\u65AD\u662F\u6E38\u620F\u5FAA\u73AF\u3001\u89C4\u5219\u72B6\u6001\u8FD8\u662F UI \u540C\u6B65\u672A\u63A8\u8FDB\u3002" : missingStructuredEvidence ? '\u6309\u987A\u5E8F\u8865\u9F50\uFF1AperformInput("entry") \u540E\
|
|
396
|
+
next: missingStep ? "\u8BE5\u6D41\u7A0B\u662F\u65F6\u95F4\u6216\u5E27\u9A71\u52A8\u7684\uFF0C\u4F46 stepUntil \u6CA1\u6709\u63A8\u8FDB\u6E38\u620F\u65F6\u95F4\uFF1B\u4E3A\u751F\u4EA7\u6E38\u620F\u6CE8\u5165 devkit \u7684 GameClock\uFF0C\u5E76\u4F20\u5165 step: () => clock.stepFrame()\u3002\u4E0D\u8981\u7528\u771F\u5B9E setTimeout\u3002" : timedOut ? "\u771F\u5B9E\u8F93\u5165\u5DF2\u6267\u884C\uFF0C\u4F46\u73A9\u6CD5\u6CA1\u6709\u5728\u4E0A\u9650\u5185\u4EA7\u751F\u7ED3\u679C\uFF1B\u68C0\u67E5\u751F\u4EA7\u63A7\u5236\u662F\u5426\u6536\u5230\u8F93\u5165\uFF0C\u518D\u67E5\u770B\u8D85\u65F6\u9519\u8BEF\u4E2D\u7684 Last diagnostics \u5224\u65AD\u662F\u6E38\u620F\u5FAA\u73AF\u3001\u89C4\u5219\u72B6\u6001\u8FD8\u662F UI \u540C\u6B65\u672A\u63A8\u8FDB\u3002" : missingStructuredEvidence ? '\u6309\u987A\u5E8F\u8865\u9F50\u81F3\u5C11\u4E24\u6B21\u7B7E\u5230\uFF1AperformInput("entry") \u540E\u8C03\u7528 checkpoint("entered")\uFF1B\u518D\u7528 performInput("primary") \u6267\u884C\u6838\u5FC3\u64CD\u4F5C\uFF0CstepUntil \u7B49\u5F85\u7ED3\u679C\uFF0C\u7528\u56DE\u8C03\u63D0\u4F9B\u7684 expect \u65AD\u8A00\u540E\u8C03\u7528 checkpoint("progress") \u6216 checkpoint("terminal")\u3002' : "\u4ECE\u751F\u4EA7\u5165\u53E3\u6267\u884C\u771F\u5B9E DOM \u8F93\u5165\uFF0C\u7528 stepUntil \u6709\u754C\u63A8\u8FDB\u5230\u73A9\u5BB6\u53EF\u89C1\u7ED3\u679C\u6216\u6E38\u620F\u6743\u5A01\u72B6\u6001\uFF0C\u5E76\u5728\u5176\u540E\u65AD\u8A00\uFF1B\u4E0D\u8981\u76F4\u8FBE\u5185\u90E8\u5173\u5361\u6216\u4FEE\u6539\u73A9\u6CD5\u72B6\u6001\u3002",
|
|
409
397
|
failsRun: true
|
|
410
398
|
};
|
|
411
399
|
}
|
|
@@ -67,20 +67,25 @@ var INPUT_EVENTS = [
|
|
|
67
67
|
"touchstart",
|
|
68
68
|
"touchend"
|
|
69
69
|
];
|
|
70
|
+
var MIN_CHECKPOINTS = 2;
|
|
70
71
|
function describeMissingEvidence(evidence) {
|
|
71
72
|
if (!evidence || evidence.entryInputs === 0) return "entry \u8F93\u5165";
|
|
72
73
|
if (evidence.primaryInputs === 0) return "primary \u8F93\u5165";
|
|
73
74
|
if (!evidence.checkpoints.includes("entered")) return "entered checkpoint";
|
|
74
75
|
if (evidence.boundedRuns === 0) return "\u6709\u754C stepUntil";
|
|
75
76
|
if (evidence.assertionsAfterOutcome === 0) return "stepUntil \u540E\u7684\u7ED3\u679C\u65AD\u8A00";
|
|
76
|
-
if (evidence.
|
|
77
|
-
return
|
|
77
|
+
if (evidence.checkpoints.length < MIN_CHECKPOINTS)
|
|
78
|
+
return `\u81F3\u5C11 ${MIN_CHECKPOINTS} \u4E2A checkpoint`;
|
|
79
|
+
if (!evidence.checkpoints.some(
|
|
80
|
+
(checkpoint) => checkpoint === "progress" || checkpoint === "terminal"
|
|
81
|
+
)) {
|
|
82
|
+
return "progress/terminal checkpoint";
|
|
78
83
|
}
|
|
79
84
|
return "\u5B8C\u6574\u7684 playthrough \u6821\u9A8C\u6807\u8BB0";
|
|
80
85
|
}
|
|
81
86
|
function createMetadata(waiverReason) {
|
|
82
87
|
return {
|
|
83
|
-
version:
|
|
88
|
+
version: 3,
|
|
84
89
|
waiverReason,
|
|
85
90
|
evidence: {
|
|
86
91
|
domInputEvents: 0,
|
|
@@ -88,23 +93,11 @@ function createMetadata(waiverReason) {
|
|
|
88
93
|
primaryInputs: 0,
|
|
89
94
|
boundedRuns: 0,
|
|
90
95
|
assertionsAfterOutcome: 0,
|
|
91
|
-
changedOutcomeCheckpoints: 0,
|
|
92
96
|
checkpoints: [],
|
|
93
97
|
verified: false
|
|
94
98
|
}
|
|
95
99
|
};
|
|
96
100
|
}
|
|
97
|
-
function snapshotFingerprint(snapshot) {
|
|
98
|
-
try {
|
|
99
|
-
const serialized = JSON.stringify(snapshot);
|
|
100
|
-
if (serialized === void 0) throw new Error("unsupported value");
|
|
101
|
-
return serialized;
|
|
102
|
-
} catch {
|
|
103
|
-
throw new Error(
|
|
104
|
-
"checkpoint snapshot must be JSON-serializable. Pass a read-only Telemetry snapshot or a small visible-state object."
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
101
|
function definePlaythrough(element, run, waiverReason) {
|
|
109
102
|
const reason = normalizePlaythroughWaiverReason(waiverReason);
|
|
110
103
|
const metadata = createMetadata(reason);
|
|
@@ -114,7 +107,7 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
114
107
|
}, async ({ expect }) => {
|
|
115
108
|
const evidence = metadata.evidence;
|
|
116
109
|
let assertionsAtOutcome;
|
|
117
|
-
let
|
|
110
|
+
let enteredRecorded = false;
|
|
118
111
|
const recordInput = () => {
|
|
119
112
|
evidence.domInputEvents += 1;
|
|
120
113
|
};
|
|
@@ -134,14 +127,14 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
134
127
|
user,
|
|
135
128
|
expect,
|
|
136
129
|
async performInput(kind, input) {
|
|
137
|
-
if (kind === "entry" &&
|
|
130
|
+
if (kind === "entry" && enteredRecorded) {
|
|
138
131
|
throw new Error(
|
|
139
132
|
'performInput("entry") must run before checkpoint("entered"). Group multiple setup actions in the same callback.'
|
|
140
133
|
);
|
|
141
134
|
}
|
|
142
|
-
if (kind === "primary" &&
|
|
135
|
+
if (kind === "primary" && !enteredRecorded) {
|
|
143
136
|
throw new Error(
|
|
144
|
-
'Before performInput("primary"), run an entry input and checkpoint("entered"
|
|
137
|
+
'Before performInput("primary"), run an entry input and checkpoint("entered").'
|
|
145
138
|
);
|
|
146
139
|
}
|
|
147
140
|
const inputsBefore = evidence.domInputEvents;
|
|
@@ -154,10 +147,9 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
154
147
|
if (kind === "entry") evidence.entryInputs += 1;
|
|
155
148
|
else evidence.primaryInputs += 1;
|
|
156
149
|
},
|
|
157
|
-
checkpoint(kind
|
|
158
|
-
const fingerprint = snapshotFingerprint(snapshot);
|
|
150
|
+
checkpoint(kind) {
|
|
159
151
|
if (kind === "entered") {
|
|
160
|
-
if (
|
|
152
|
+
if (enteredRecorded) {
|
|
161
153
|
throw new Error(
|
|
162
154
|
'checkpoint("entered") may only be recorded once, before the primary input.'
|
|
163
155
|
);
|
|
@@ -167,7 +159,7 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
167
159
|
'checkpoint("entered") must follow performInput("entry", ...).'
|
|
168
160
|
);
|
|
169
161
|
}
|
|
170
|
-
|
|
162
|
+
enteredRecorded = true;
|
|
171
163
|
evidence.checkpoints.push(kind);
|
|
172
164
|
return;
|
|
173
165
|
}
|
|
@@ -183,15 +175,9 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
183
175
|
}
|
|
184
176
|
if (assertionsAtOutcome === void 0 || expect.getState().assertionCalls <= assertionsAtOutcome) {
|
|
185
177
|
throw new Error(
|
|
186
|
-
`Use the expect provided by playthroughTest to assert the authoritative result after stepUntil, then record checkpoint("${kind}"
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
if (fingerprint === enteredSnapshot) {
|
|
190
|
-
throw new Error(
|
|
191
|
-
`checkpoint("${kind}") matches the entered snapshot. Assert a production state change caused by the primary input.`
|
|
178
|
+
`Use the expect provided by playthroughTest to assert the authoritative result after stepUntil, then record checkpoint("${kind}").`
|
|
192
179
|
);
|
|
193
180
|
}
|
|
194
|
-
evidence.changedOutcomeCheckpoints += 1;
|
|
195
181
|
evidence.checkpoints.push(kind);
|
|
196
182
|
},
|
|
197
183
|
async stepUntil(condition, options = {}) {
|
|
@@ -226,9 +212,11 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
226
212
|
"Use the expect provided by playthroughTest to assert an authoritative game outcome after stepUntil returns."
|
|
227
213
|
);
|
|
228
214
|
}
|
|
229
|
-
if (evidence.
|
|
215
|
+
if (evidence.checkpoints.length < MIN_CHECKPOINTS || !evidence.checkpoints.some(
|
|
216
|
+
(checkpoint) => checkpoint === "progress" || checkpoint === "terminal"
|
|
217
|
+
)) {
|
|
230
218
|
throw new Error(
|
|
231
|
-
|
|
219
|
+
`playthroughTest requires at least ${MIN_CHECKPOINTS} checkpoints: checkpoint("entered") and, after asserting the result, checkpoint("progress") or checkpoint("terminal").`
|
|
232
220
|
);
|
|
233
221
|
}
|
|
234
222
|
evidence.verified = true;
|
|
@@ -249,7 +237,7 @@ function auditReactPlaythroughRun(tests) {
|
|
|
249
237
|
const declared = tests.filter((candidate) => candidate.metadata);
|
|
250
238
|
const valid = declared.filter(({ state, metadata }) => {
|
|
251
239
|
const evidence = metadata?.evidence;
|
|
252
|
-
return state === "passed" && evidence?.verified === true && evidence.domInputEvents > 0 && evidence.entryInputs > 0 && evidence.primaryInputs > 0 && evidence.boundedRuns > 0 && evidence.assertionsAfterOutcome > 0 && evidence.
|
|
240
|
+
return state === "passed" && evidence?.verified === true && evidence.domInputEvents > 0 && evidence.entryInputs > 0 && evidence.primaryInputs > 0 && evidence.boundedRuns > 0 && evidence.assertionsAfterOutcome > 0 && evidence.checkpoints.length >= MIN_CHECKPOINTS && evidence.checkpoints.includes("entered") && evidence.checkpoints.some(
|
|
253
241
|
(checkpoint) => checkpoint === "progress" || checkpoint === "terminal"
|
|
254
242
|
);
|
|
255
243
|
});
|
|
@@ -259,7 +247,7 @@ function auditReactPlaythroughRun(tests) {
|
|
|
259
247
|
const issues = [];
|
|
260
248
|
if (declared.length === 0) {
|
|
261
249
|
issues.push(
|
|
262
|
-
'\u7F3A\u5C11\u751F\u4EA7\u6E38\u620F\u53EF\u73A9\u6027\u9A8C\u8BC1\uFF1A\u4F7F\u7528 playthroughTest \u6E32\u67D3 <App />\uFF0C\u4F9D\u6B21\u6267\u884C performInput("entry")\u3001checkpoint("entered")\u3001performInput("primary")\u3001stepUntil\u3001\u7528 playthroughTest \u63D0\u4F9B\u7684 expect \u65AD\u8A00\u7ED3\u679C\uFF0C\u5E76\u8BB0\u5F55 progress
|
|
250
|
+
'\u7F3A\u5C11\u751F\u4EA7\u6E38\u620F\u53EF\u73A9\u6027\u9A8C\u8BC1\uFF1A\u4F7F\u7528 playthroughTest \u6E32\u67D3 <App />\uFF0C\u4F9D\u6B21\u6267\u884C performInput("entry")\u3001checkpoint("entered")\u3001performInput("primary")\u3001stepUntil\u3001\u7528 playthroughTest \u63D0\u4F9B\u7684 expect \u65AD\u8A00\u6743\u5A01\u7ED3\u679C\uFF0C\u5E76\u8BB0\u5F55 checkpoint("progress") \u6216 checkpoint("terminal")\uFF1B\u81F3\u5C11\u5B8C\u6210\u8FD9\u4E24\u6B21 checkpoint \u7B7E\u5230\u3002'
|
|
263
251
|
);
|
|
264
252
|
} else {
|
|
265
253
|
for (const candidate of declared) {
|
|
@@ -289,7 +277,7 @@ var PRODUCTION_PLAYTHROUGH_FILE = "tests/production-playthrough.test.tsx";
|
|
|
289
277
|
function isMetadata(value) {
|
|
290
278
|
if (!value || typeof value !== "object") return false;
|
|
291
279
|
const metadata = value;
|
|
292
|
-
return metadata.version ===
|
|
280
|
+
return metadata.version === 3 && Boolean(metadata.evidence);
|
|
293
281
|
}
|
|
294
282
|
function toAuditInput(test2) {
|
|
295
283
|
const metadata = test2.meta().reactPlaythrough;
|
|
@@ -333,7 +321,7 @@ function assessReactPlaythroughReport(input) {
|
|
|
333
321
|
...base,
|
|
334
322
|
status: "FAILED",
|
|
335
323
|
cause: "\u751F\u4EA7\u53EF\u73A9\u6027\u6D4B\u8BD5\u6587\u4EF6\u4E0D\u5B58\u5728\u3002",
|
|
336
|
-
next: '\u521B\u5EFA\u8BE5\u6587\u4EF6\uFF1A\u4ECE <App /> \u901A\u8FC7\u771F\u5B9E DOM \u8F93\u5165\u4F9D\u6B21\u6267\u884C performInput("entry")\u3001checkpoint("entered")\u3001performInput("primary")\u3001stepUntil\u3001\u7528 playthroughTest \u63D0\u4F9B\u7684 expect \u65AD\u8A00\u7ED3\u679C\uFF0C\u5E76\u8BB0\u5F55 progress
|
|
324
|
+
next: '\u521B\u5EFA\u8BE5\u6587\u4EF6\uFF1A\u4ECE <App /> \u901A\u8FC7\u771F\u5B9E DOM \u8F93\u5165\u4F9D\u6B21\u6267\u884C performInput("entry")\u3001checkpoint("entered")\u3001performInput("primary")\u3001stepUntil\u3001\u7528 playthroughTest \u63D0\u4F9B\u7684 expect \u65AD\u8A00\u6743\u5A01\u7ED3\u679C\uFF0C\u5E76\u8BB0\u5F55 checkpoint("progress") \u6216 checkpoint("terminal")\u3002\u6BCF\u6761\u4E3B\u6D41\u7A0B\u81F3\u5C11\u9700\u8981\u8FD9\u4E24\u6B21 checkpoint \u7B7E\u5230\u3002',
|
|
337
325
|
failsRun: true
|
|
338
326
|
};
|
|
339
327
|
}
|
|
@@ -371,7 +359,7 @@ function assessReactPlaythroughReport(input) {
|
|
|
371
359
|
...base,
|
|
372
360
|
status: "FAILED",
|
|
373
361
|
cause,
|
|
374
|
-
next: missingStep ? "\u8BE5\u6D41\u7A0B\u662F\u65F6\u95F4\u6216\u5E27\u9A71\u52A8\u7684\uFF0C\u4F46 stepUntil \u6CA1\u6709\u63A8\u8FDB\u6E38\u620F\u65F6\u95F4\uFF1B\u4E3A\u751F\u4EA7\u6E38\u620F\u6CE8\u5165 devkit \u7684 GameClock\uFF0C\u5E76\u4F20\u5165 step: () => clock.stepFrame()\u3002\u4E0D\u8981\u7528\u771F\u5B9E setTimeout\u3002" : timedOut ? "\u771F\u5B9E\u8F93\u5165\u5DF2\u6267\u884C\uFF0C\u4F46\u73A9\u6CD5\u6CA1\u6709\u5728\u4E0A\u9650\u5185\u4EA7\u751F\u7ED3\u679C\uFF1B\u68C0\u67E5\u751F\u4EA7\u63A7\u5236\u662F\u5426\u6536\u5230\u8F93\u5165\uFF0C\u518D\u67E5\u770B\u8D85\u65F6\u9519\u8BEF\u4E2D\u7684 Last diagnostics \u5224\u65AD\u662F\u6E38\u620F\u5FAA\u73AF\u3001\u89C4\u5219\u72B6\u6001\u8FD8\u662F UI \u540C\u6B65\u672A\u63A8\u8FDB\u3002" : missingStructuredEvidence ? '\u6309\u987A\u5E8F\u8865\u9F50\uFF1AperformInput("entry") \u540E\
|
|
362
|
+
next: missingStep ? "\u8BE5\u6D41\u7A0B\u662F\u65F6\u95F4\u6216\u5E27\u9A71\u52A8\u7684\uFF0C\u4F46 stepUntil \u6CA1\u6709\u63A8\u8FDB\u6E38\u620F\u65F6\u95F4\uFF1B\u4E3A\u751F\u4EA7\u6E38\u620F\u6CE8\u5165 devkit \u7684 GameClock\uFF0C\u5E76\u4F20\u5165 step: () => clock.stepFrame()\u3002\u4E0D\u8981\u7528\u771F\u5B9E setTimeout\u3002" : timedOut ? "\u771F\u5B9E\u8F93\u5165\u5DF2\u6267\u884C\uFF0C\u4F46\u73A9\u6CD5\u6CA1\u6709\u5728\u4E0A\u9650\u5185\u4EA7\u751F\u7ED3\u679C\uFF1B\u68C0\u67E5\u751F\u4EA7\u63A7\u5236\u662F\u5426\u6536\u5230\u8F93\u5165\uFF0C\u518D\u67E5\u770B\u8D85\u65F6\u9519\u8BEF\u4E2D\u7684 Last diagnostics \u5224\u65AD\u662F\u6E38\u620F\u5FAA\u73AF\u3001\u89C4\u5219\u72B6\u6001\u8FD8\u662F UI \u540C\u6B65\u672A\u63A8\u8FDB\u3002" : missingStructuredEvidence ? '\u6309\u987A\u5E8F\u8865\u9F50\u81F3\u5C11\u4E24\u6B21\u7B7E\u5230\uFF1AperformInput("entry") \u540E\u8C03\u7528 checkpoint("entered")\uFF1B\u518D\u7528 performInput("primary") \u6267\u884C\u6838\u5FC3\u64CD\u4F5C\uFF0CstepUntil \u7B49\u5F85\u7ED3\u679C\uFF0C\u7528\u56DE\u8C03\u63D0\u4F9B\u7684 expect \u65AD\u8A00\u540E\u8C03\u7528 checkpoint("progress") \u6216 checkpoint("terminal")\u3002' : "\u4ECE\u751F\u4EA7\u5165\u53E3\u6267\u884C\u771F\u5B9E DOM \u8F93\u5165\uFF0C\u7528 stepUntil \u6709\u754C\u63A8\u8FDB\u5230\u73A9\u5BB6\u53EF\u89C1\u7ED3\u679C\u6216\u6E38\u620F\u6743\u5A01\u72B6\u6001\uFF0C\u5E76\u5728\u5176\u540E\u65AD\u8A00\uFF1B\u4E0D\u8981\u76F4\u8FBE\u5185\u90E8\u5173\u5361\u6216\u4FEE\u6539\u73A9\u6CD5\u72B6\u6001\u3002",
|
|
375
363
|
failsRun: true
|
|
376
364
|
};
|
|
377
365
|
}
|