miaoda-game-devkit 0.2.17 → 0.2.20
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 +14 -9
- package/dist/{gameplay-audit-DmbBA0M_.d.mts → gameplay-audit-CKgNYw2r.d.mts} +46 -13
- package/dist/{gameplay-audit-DmbBA0M_.d.ts → gameplay-audit-CKgNYw2r.d.ts} +46 -13
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +346 -48
- package/dist/index.mjs +346 -48
- package/dist/react/testing.d.mts +27 -2
- package/dist/react/testing.d.ts +27 -2
- package/dist/react/testing.js +114 -14
- package/dist/react/testing.mjs +113 -13
- package/dist/react/vitest-config.js +118 -17
- package/dist/react/vitest-config.mjs +117 -16
- package/dist/vitest-config.d.mts +1 -1
- package/dist/vitest-config.d.ts +1 -1
- package/dist/vitest-config.js +9 -9
- package/dist/vitest-config.mjs +9 -9
- package/package.json +1 -1
|
@@ -101,27 +101,54 @@ var INPUT_EVENTS = [
|
|
|
101
101
|
"touchstart",
|
|
102
102
|
"touchend"
|
|
103
103
|
];
|
|
104
|
+
function describeMissingEvidence(evidence) {
|
|
105
|
+
if (!evidence || evidence.entryInputs === 0) return "entry \u8F93\u5165";
|
|
106
|
+
if (evidence.primaryInputs === 0) return "primary \u8F93\u5165";
|
|
107
|
+
if (!evidence.checkpoints.includes("entered")) return "entered checkpoint";
|
|
108
|
+
if (evidence.boundedRuns === 0) return "\u6709\u754C stepUntil";
|
|
109
|
+
if (evidence.assertionsAfterOutcome === 0) return "stepUntil \u540E\u7684\u7ED3\u679C\u65AD\u8A00";
|
|
110
|
+
if (evidence.changedOutcomeCheckpoints === 0) {
|
|
111
|
+
return "\u53D1\u751F\u72B6\u6001\u53D8\u5316\u7684 progress/terminal checkpoint";
|
|
112
|
+
}
|
|
113
|
+
return "\u5B8C\u6574\u7684 playthrough \u6821\u9A8C\u6807\u8BB0";
|
|
114
|
+
}
|
|
104
115
|
function createMetadata(waiverReason) {
|
|
105
116
|
return {
|
|
106
|
-
version:
|
|
117
|
+
version: 2,
|
|
107
118
|
waiverReason,
|
|
108
119
|
evidence: {
|
|
109
120
|
domInputEvents: 0,
|
|
121
|
+
entryInputs: 0,
|
|
122
|
+
primaryInputs: 0,
|
|
110
123
|
boundedRuns: 0,
|
|
111
124
|
assertionsAfterOutcome: 0,
|
|
125
|
+
changedOutcomeCheckpoints: 0,
|
|
126
|
+
checkpoints: [],
|
|
112
127
|
verified: false
|
|
113
128
|
}
|
|
114
129
|
};
|
|
115
130
|
}
|
|
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
|
+
}
|
|
116
142
|
function definePlaythrough(element, run, waiverReason) {
|
|
117
143
|
const reason = normalizePlaythroughWaiverReason(waiverReason);
|
|
118
144
|
const metadata = createMetadata(reason);
|
|
119
145
|
(0, import_vitest.test)("production game completes a bounded playthrough", {
|
|
120
146
|
skip: Boolean(reason),
|
|
121
147
|
meta: { reactPlaythrough: metadata }
|
|
122
|
-
}, async () => {
|
|
148
|
+
}, async ({ expect }) => {
|
|
123
149
|
const evidence = metadata.evidence;
|
|
124
150
|
let assertionsAtOutcome;
|
|
151
|
+
let enteredSnapshot;
|
|
125
152
|
const recordInput = () => {
|
|
126
153
|
evidence.domInputEvents += 1;
|
|
127
154
|
};
|
|
@@ -139,23 +166,90 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
139
166
|
await run({
|
|
140
167
|
view,
|
|
141
168
|
user,
|
|
169
|
+
expect,
|
|
170
|
+
async performInput(kind, input) {
|
|
171
|
+
if (kind === "entry" && enteredSnapshot !== void 0) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
'performInput("entry") must run before checkpoint("entered"). Group multiple setup actions in the same callback.'
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
if (kind === "primary" && enteredSnapshot === void 0) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
'Before performInput("primary"), run an entry input and checkpoint("entered", snapshot).'
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
const inputsBefore = evidence.domInputEvents;
|
|
182
|
+
await input();
|
|
183
|
+
if (evidence.domInputEvents === inputsBefore) {
|
|
184
|
+
throw new Error(
|
|
185
|
+
`performInput("${kind}") did not dispatch a supported production DOM input. Use the provided user or dispatch a real keyboard, pointer, or touch event to the production target.`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
if (kind === "entry") evidence.entryInputs += 1;
|
|
189
|
+
else evidence.primaryInputs += 1;
|
|
190
|
+
},
|
|
191
|
+
checkpoint(kind, snapshot) {
|
|
192
|
+
const fingerprint = snapshotFingerprint(snapshot);
|
|
193
|
+
if (kind === "entered") {
|
|
194
|
+
if (enteredSnapshot !== void 0) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
'checkpoint("entered") may only be recorded once, before the primary input.'
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
if (evidence.entryInputs === 0) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
'checkpoint("entered") must follow performInput("entry", ...).'
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
enteredSnapshot = fingerprint;
|
|
205
|
+
evidence.checkpoints.push(kind);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (evidence.primaryInputs === 0) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
`checkpoint("${kind}") must follow performInput("primary", ...).`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
if (evidence.boundedRuns === 0) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`checkpoint("${kind}") must be recorded after stepUntil returns.`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
if (assertionsAtOutcome === void 0 || expect.getState().assertionCalls <= assertionsAtOutcome) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`Use the expect provided by playthroughTest to assert the authoritative result after stepUntil, then record checkpoint("${kind}", snapshot).`
|
|
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.`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
evidence.changedOutcomeCheckpoints += 1;
|
|
229
|
+
evidence.checkpoints.push(kind);
|
|
230
|
+
},
|
|
142
231
|
async stepUntil(condition, options = {}) {
|
|
143
232
|
const steps = await runBoundedUntil(condition, options);
|
|
144
|
-
if (evidence.
|
|
233
|
+
if (evidence.primaryInputs === 0) {
|
|
145
234
|
throw new Error(
|
|
146
|
-
|
|
235
|
+
'stepUntil must follow performInput("primary", ...). A menu/help click is not gameplay evidence.'
|
|
147
236
|
);
|
|
148
237
|
}
|
|
149
238
|
evidence.boundedRuns += 1;
|
|
150
|
-
assertionsAtOutcome =
|
|
239
|
+
assertionsAtOutcome = expect.getState().assertionCalls;
|
|
151
240
|
return steps;
|
|
152
241
|
}
|
|
153
242
|
});
|
|
154
|
-
const assertionCalls =
|
|
243
|
+
const assertionCalls = expect.getState().assertionCalls;
|
|
155
244
|
evidence.assertionsAfterOutcome = assertionsAtOutcome === void 0 ? 0 : assertionCalls - assertionsAtOutcome;
|
|
156
|
-
if (evidence.
|
|
245
|
+
if (evidence.entryInputs === 0) {
|
|
157
246
|
throw new Error(
|
|
158
|
-
|
|
247
|
+
'playthroughTest must perform an entry input with performInput("entry", ...).'
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
if (evidence.primaryInputs === 0) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
'playthroughTest must perform a core game action with performInput("primary", ...).'
|
|
159
253
|
);
|
|
160
254
|
}
|
|
161
255
|
if (evidence.boundedRuns === 0) {
|
|
@@ -163,7 +257,12 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
163
257
|
}
|
|
164
258
|
if (evidence.assertionsAfterOutcome === 0) {
|
|
165
259
|
throw new Error(
|
|
166
|
-
"
|
|
260
|
+
"Use the expect provided by playthroughTest to assert an authoritative game outcome after stepUntil returns."
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
if (evidence.changedOutcomeCheckpoints === 0) {
|
|
264
|
+
throw new Error(
|
|
265
|
+
'After asserting the result, record checkpoint("progress", snapshot) or checkpoint("terminal", snapshot). The snapshot must differ from checkpoint("entered").'
|
|
167
266
|
);
|
|
168
267
|
}
|
|
169
268
|
evidence.verified = true;
|
|
@@ -184,7 +283,9 @@ function auditReactPlaythroughRun(tests) {
|
|
|
184
283
|
const declared = tests.filter((candidate) => candidate.metadata);
|
|
185
284
|
const valid = declared.filter(({ state, metadata }) => {
|
|
186
285
|
const evidence = metadata?.evidence;
|
|
187
|
-
return state === "passed" && evidence?.verified === true && evidence.domInputEvents > 0 && evidence.boundedRuns > 0 && evidence.assertionsAfterOutcome > 0
|
|
286
|
+
return state === "passed" && evidence?.verified === true && evidence.domInputEvents > 0 && evidence.entryInputs > 0 && evidence.primaryInputs > 0 && evidence.boundedRuns > 0 && evidence.assertionsAfterOutcome > 0 && evidence.changedOutcomeCheckpoints > 0 && evidence.checkpoints.includes("entered") && evidence.checkpoints.some(
|
|
287
|
+
(checkpoint) => checkpoint === "progress" || checkpoint === "terminal"
|
|
288
|
+
);
|
|
188
289
|
});
|
|
189
290
|
const waivers = declared.filter(
|
|
190
291
|
({ state, metadata }) => state === "skipped" && (metadata?.waiverReason?.trim().length ?? 0) >= 20
|
|
@@ -192,7 +293,7 @@ function auditReactPlaythroughRun(tests) {
|
|
|
192
293
|
const issues = [];
|
|
193
294
|
if (declared.length === 0) {
|
|
194
295
|
issues.push(
|
|
195
|
-
|
|
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/terminal checkpoint\u3002'
|
|
196
297
|
);
|
|
197
298
|
} else {
|
|
198
299
|
for (const candidate of declared) {
|
|
@@ -206,9 +307,8 @@ function auditReactPlaythroughRun(tests) {
|
|
|
206
307
|
} else if (candidate.state !== "passed") {
|
|
207
308
|
issues.push(`\u73A9\u6CD5\u9A8C\u8BC1\u201C${candidate.name}\u201D\u7684\u72B6\u6001\u4E3A ${candidate.state}\u3002`);
|
|
208
309
|
} else {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
);
|
|
310
|
+
const missing = describeMissingEvidence(candidate.metadata?.evidence);
|
|
311
|
+
issues.push(`\u73A9\u6CD5\u9A8C\u8BC1\u201C${candidate.name}\u201D\u7F3A\u5C11${missing}\u3002`);
|
|
212
312
|
}
|
|
213
313
|
}
|
|
214
314
|
}
|
|
@@ -223,7 +323,7 @@ var PRODUCTION_PLAYTHROUGH_FILE = "tests/production-playthrough.test.tsx";
|
|
|
223
323
|
function isMetadata(value) {
|
|
224
324
|
if (!value || typeof value !== "object") return false;
|
|
225
325
|
const metadata = value;
|
|
226
|
-
return metadata.version ===
|
|
326
|
+
return metadata.version === 2 && Boolean(metadata.evidence);
|
|
227
327
|
}
|
|
228
328
|
function toAuditInput(test2) {
|
|
229
329
|
const metadata = test2.meta().reactPlaythrough;
|
|
@@ -267,7 +367,7 @@ function assessReactPlaythroughReport(input) {
|
|
|
267
367
|
...base,
|
|
268
368
|
status: "FAILED",
|
|
269
369
|
cause: "\u751F\u4EA7\u53EF\u73A9\u6027\u6D4B\u8BD5\u6587\u4EF6\u4E0D\u5B58\u5728\u3002",
|
|
270
|
-
next:
|
|
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/terminal checkpoint\u3002Canvas \u7528\u771F\u5B9E DOM \u4E8B\u4EF6\u8F93\u5165\u5E76\u8BFB\u53D6 Telemetry snapshot\u3002',
|
|
271
371
|
failsRun: true
|
|
272
372
|
};
|
|
273
373
|
}
|
|
@@ -300,11 +400,12 @@ function assessReactPlaythroughReport(input) {
|
|
|
300
400
|
const cause = productionModule.errors[0] ?? input.unhandledErrors?.[0] ?? audit.issues[0] ?? "\u751F\u4EA7\u53EF\u73A9\u6D41\u7A0B\u6CA1\u6709\u7559\u4E0B\u5B8C\u6574\u8BC1\u636E\u3002";
|
|
301
401
|
const timedOut = /outcome was not reached within \d+ steps/i.test(cause);
|
|
302
402
|
const missingStep = /No step callback was provided/i.test(cause);
|
|
403
|
+
const missingStructuredEvidence = /performInput|checkpoint/.test(cause);
|
|
303
404
|
return {
|
|
304
405
|
...base,
|
|
305
406
|
status: "FAILED",
|
|
306
407
|
cause,
|
|
307
|
-
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" : "\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\u771F\u5B9E\u72B6\u6001\u7684\u53EA\u8BFB snapshot\uFF0C\u5E76\u5728\u5176\u540E\u65AD\u8A00\uFF1B\u4E0D\u8981\u76F4\u8FBE\u5185\u90E8\u5173\u5361\u6216\u4FEE\u6539\u73A9\u6CD5\u72B6\u6001\u3002",
|
|
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\u8BB0\u5F55 entered snapshot\uFF1B\u518D\u7528 performInput("primary") \u6267\u884C\u6838\u5FC3\u64CD\u4F5C\uFF0CstepUntil \u7B49\u5F85\u53D8\u5316\uFF0C\u65AD\u8A00\u540E\u8BB0\u5F55 progress \u6216 terminal snapshot\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\u771F\u5B9E\u72B6\u6001\u7684\u53EA\u8BFB snapshot\uFF0C\u5E76\u5728\u5176\u540E\u65AD\u8A00\uFF1B\u4E0D\u8981\u76F4\u8FBE\u5185\u90E8\u5173\u5361\u6216\u4FEE\u6539\u73A9\u6CD5\u72B6\u6001\u3002",
|
|
308
409
|
failsRun: true
|
|
309
410
|
};
|
|
310
411
|
}
|
|
@@ -10,7 +10,7 @@ import { relative, resolve } from "path";
|
|
|
10
10
|
// src/react/react-playthrough.ts
|
|
11
11
|
import { render } from "@testing-library/react";
|
|
12
12
|
import userEvent from "@testing-library/user-event";
|
|
13
|
-
import {
|
|
13
|
+
import { test } from "vitest";
|
|
14
14
|
|
|
15
15
|
// src/react/react-playthrough-core.ts
|
|
16
16
|
import { act } from "@testing-library/react";
|
|
@@ -67,27 +67,54 @@ var INPUT_EVENTS = [
|
|
|
67
67
|
"touchstart",
|
|
68
68
|
"touchend"
|
|
69
69
|
];
|
|
70
|
+
function describeMissingEvidence(evidence) {
|
|
71
|
+
if (!evidence || evidence.entryInputs === 0) return "entry \u8F93\u5165";
|
|
72
|
+
if (evidence.primaryInputs === 0) return "primary \u8F93\u5165";
|
|
73
|
+
if (!evidence.checkpoints.includes("entered")) return "entered checkpoint";
|
|
74
|
+
if (evidence.boundedRuns === 0) return "\u6709\u754C stepUntil";
|
|
75
|
+
if (evidence.assertionsAfterOutcome === 0) return "stepUntil \u540E\u7684\u7ED3\u679C\u65AD\u8A00";
|
|
76
|
+
if (evidence.changedOutcomeCheckpoints === 0) {
|
|
77
|
+
return "\u53D1\u751F\u72B6\u6001\u53D8\u5316\u7684 progress/terminal checkpoint";
|
|
78
|
+
}
|
|
79
|
+
return "\u5B8C\u6574\u7684 playthrough \u6821\u9A8C\u6807\u8BB0";
|
|
80
|
+
}
|
|
70
81
|
function createMetadata(waiverReason) {
|
|
71
82
|
return {
|
|
72
|
-
version:
|
|
83
|
+
version: 2,
|
|
73
84
|
waiverReason,
|
|
74
85
|
evidence: {
|
|
75
86
|
domInputEvents: 0,
|
|
87
|
+
entryInputs: 0,
|
|
88
|
+
primaryInputs: 0,
|
|
76
89
|
boundedRuns: 0,
|
|
77
90
|
assertionsAfterOutcome: 0,
|
|
91
|
+
changedOutcomeCheckpoints: 0,
|
|
92
|
+
checkpoints: [],
|
|
78
93
|
verified: false
|
|
79
94
|
}
|
|
80
95
|
};
|
|
81
96
|
}
|
|
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
|
+
}
|
|
82
108
|
function definePlaythrough(element, run, waiverReason) {
|
|
83
109
|
const reason = normalizePlaythroughWaiverReason(waiverReason);
|
|
84
110
|
const metadata = createMetadata(reason);
|
|
85
111
|
test("production game completes a bounded playthrough", {
|
|
86
112
|
skip: Boolean(reason),
|
|
87
113
|
meta: { reactPlaythrough: metadata }
|
|
88
|
-
}, async () => {
|
|
114
|
+
}, async ({ expect }) => {
|
|
89
115
|
const evidence = metadata.evidence;
|
|
90
116
|
let assertionsAtOutcome;
|
|
117
|
+
let enteredSnapshot;
|
|
91
118
|
const recordInput = () => {
|
|
92
119
|
evidence.domInputEvents += 1;
|
|
93
120
|
};
|
|
@@ -105,11 +132,73 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
105
132
|
await run({
|
|
106
133
|
view,
|
|
107
134
|
user,
|
|
135
|
+
expect,
|
|
136
|
+
async performInput(kind, input) {
|
|
137
|
+
if (kind === "entry" && enteredSnapshot !== void 0) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
'performInput("entry") must run before checkpoint("entered"). Group multiple setup actions in the same callback.'
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
if (kind === "primary" && enteredSnapshot === void 0) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
'Before performInput("primary"), run an entry input and checkpoint("entered", snapshot).'
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
const inputsBefore = evidence.domInputEvents;
|
|
148
|
+
await input();
|
|
149
|
+
if (evidence.domInputEvents === inputsBefore) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
`performInput("${kind}") did not dispatch a supported production DOM input. Use the provided user or dispatch a real keyboard, pointer, or touch event to the production target.`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
if (kind === "entry") evidence.entryInputs += 1;
|
|
155
|
+
else evidence.primaryInputs += 1;
|
|
156
|
+
},
|
|
157
|
+
checkpoint(kind, snapshot) {
|
|
158
|
+
const fingerprint = snapshotFingerprint(snapshot);
|
|
159
|
+
if (kind === "entered") {
|
|
160
|
+
if (enteredSnapshot !== void 0) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
'checkpoint("entered") may only be recorded once, before the primary input.'
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
if (evidence.entryInputs === 0) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
'checkpoint("entered") must follow performInput("entry", ...).'
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
enteredSnapshot = fingerprint;
|
|
171
|
+
evidence.checkpoints.push(kind);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (evidence.primaryInputs === 0) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`checkpoint("${kind}") must follow performInput("primary", ...).`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
if (evidence.boundedRuns === 0) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`checkpoint("${kind}") must be recorded after stepUntil returns.`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
if (assertionsAtOutcome === void 0 || expect.getState().assertionCalls <= assertionsAtOutcome) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
`Use the expect provided by playthroughTest to assert the authoritative result after stepUntil, then record checkpoint("${kind}", snapshot).`
|
|
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.`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
evidence.changedOutcomeCheckpoints += 1;
|
|
195
|
+
evidence.checkpoints.push(kind);
|
|
196
|
+
},
|
|
108
197
|
async stepUntil(condition, options = {}) {
|
|
109
198
|
const steps = await runBoundedUntil(condition, options);
|
|
110
|
-
if (evidence.
|
|
199
|
+
if (evidence.primaryInputs === 0) {
|
|
111
200
|
throw new Error(
|
|
112
|
-
|
|
201
|
+
'stepUntil must follow performInput("primary", ...). A menu/help click is not gameplay evidence.'
|
|
113
202
|
);
|
|
114
203
|
}
|
|
115
204
|
evidence.boundedRuns += 1;
|
|
@@ -119,9 +208,14 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
119
208
|
});
|
|
120
209
|
const assertionCalls = expect.getState().assertionCalls;
|
|
121
210
|
evidence.assertionsAfterOutcome = assertionsAtOutcome === void 0 ? 0 : assertionCalls - assertionsAtOutcome;
|
|
122
|
-
if (evidence.
|
|
211
|
+
if (evidence.entryInputs === 0) {
|
|
123
212
|
throw new Error(
|
|
124
|
-
|
|
213
|
+
'playthroughTest must perform an entry input with performInput("entry", ...).'
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
if (evidence.primaryInputs === 0) {
|
|
217
|
+
throw new Error(
|
|
218
|
+
'playthroughTest must perform a core game action with performInput("primary", ...).'
|
|
125
219
|
);
|
|
126
220
|
}
|
|
127
221
|
if (evidence.boundedRuns === 0) {
|
|
@@ -129,7 +223,12 @@ function definePlaythrough(element, run, waiverReason) {
|
|
|
129
223
|
}
|
|
130
224
|
if (evidence.assertionsAfterOutcome === 0) {
|
|
131
225
|
throw new Error(
|
|
132
|
-
"
|
|
226
|
+
"Use the expect provided by playthroughTest to assert an authoritative game outcome after stepUntil returns."
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
if (evidence.changedOutcomeCheckpoints === 0) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
'After asserting the result, record checkpoint("progress", snapshot) or checkpoint("terminal", snapshot). The snapshot must differ from checkpoint("entered").'
|
|
133
232
|
);
|
|
134
233
|
}
|
|
135
234
|
evidence.verified = true;
|
|
@@ -150,7 +249,9 @@ function auditReactPlaythroughRun(tests) {
|
|
|
150
249
|
const declared = tests.filter((candidate) => candidate.metadata);
|
|
151
250
|
const valid = declared.filter(({ state, metadata }) => {
|
|
152
251
|
const evidence = metadata?.evidence;
|
|
153
|
-
return state === "passed" && evidence?.verified === true && evidence.domInputEvents > 0 && evidence.boundedRuns > 0 && evidence.assertionsAfterOutcome > 0
|
|
252
|
+
return state === "passed" && evidence?.verified === true && evidence.domInputEvents > 0 && evidence.entryInputs > 0 && evidence.primaryInputs > 0 && evidence.boundedRuns > 0 && evidence.assertionsAfterOutcome > 0 && evidence.changedOutcomeCheckpoints > 0 && evidence.checkpoints.includes("entered") && evidence.checkpoints.some(
|
|
253
|
+
(checkpoint) => checkpoint === "progress" || checkpoint === "terminal"
|
|
254
|
+
);
|
|
154
255
|
});
|
|
155
256
|
const waivers = declared.filter(
|
|
156
257
|
({ state, metadata }) => state === "skipped" && (metadata?.waiverReason?.trim().length ?? 0) >= 20
|
|
@@ -158,7 +259,7 @@ function auditReactPlaythroughRun(tests) {
|
|
|
158
259
|
const issues = [];
|
|
159
260
|
if (declared.length === 0) {
|
|
160
261
|
issues.push(
|
|
161
|
-
|
|
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/terminal checkpoint\u3002'
|
|
162
263
|
);
|
|
163
264
|
} else {
|
|
164
265
|
for (const candidate of declared) {
|
|
@@ -172,9 +273,8 @@ function auditReactPlaythroughRun(tests) {
|
|
|
172
273
|
} else if (candidate.state !== "passed") {
|
|
173
274
|
issues.push(`\u73A9\u6CD5\u9A8C\u8BC1\u201C${candidate.name}\u201D\u7684\u72B6\u6001\u4E3A ${candidate.state}\u3002`);
|
|
174
275
|
} else {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
);
|
|
276
|
+
const missing = describeMissingEvidence(candidate.metadata?.evidence);
|
|
277
|
+
issues.push(`\u73A9\u6CD5\u9A8C\u8BC1\u201C${candidate.name}\u201D\u7F3A\u5C11${missing}\u3002`);
|
|
178
278
|
}
|
|
179
279
|
}
|
|
180
280
|
}
|
|
@@ -189,7 +289,7 @@ var PRODUCTION_PLAYTHROUGH_FILE = "tests/production-playthrough.test.tsx";
|
|
|
189
289
|
function isMetadata(value) {
|
|
190
290
|
if (!value || typeof value !== "object") return false;
|
|
191
291
|
const metadata = value;
|
|
192
|
-
return metadata.version ===
|
|
292
|
+
return metadata.version === 2 && Boolean(metadata.evidence);
|
|
193
293
|
}
|
|
194
294
|
function toAuditInput(test2) {
|
|
195
295
|
const metadata = test2.meta().reactPlaythrough;
|
|
@@ -233,7 +333,7 @@ function assessReactPlaythroughReport(input) {
|
|
|
233
333
|
...base,
|
|
234
334
|
status: "FAILED",
|
|
235
335
|
cause: "\u751F\u4EA7\u53EF\u73A9\u6027\u6D4B\u8BD5\u6587\u4EF6\u4E0D\u5B58\u5728\u3002",
|
|
236
|
-
next:
|
|
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/terminal checkpoint\u3002Canvas \u7528\u771F\u5B9E DOM \u4E8B\u4EF6\u8F93\u5165\u5E76\u8BFB\u53D6 Telemetry snapshot\u3002',
|
|
237
337
|
failsRun: true
|
|
238
338
|
};
|
|
239
339
|
}
|
|
@@ -266,11 +366,12 @@ function assessReactPlaythroughReport(input) {
|
|
|
266
366
|
const cause = productionModule.errors[0] ?? input.unhandledErrors?.[0] ?? audit.issues[0] ?? "\u751F\u4EA7\u53EF\u73A9\u6D41\u7A0B\u6CA1\u6709\u7559\u4E0B\u5B8C\u6574\u8BC1\u636E\u3002";
|
|
267
367
|
const timedOut = /outcome was not reached within \d+ steps/i.test(cause);
|
|
268
368
|
const missingStep = /No step callback was provided/i.test(cause);
|
|
369
|
+
const missingStructuredEvidence = /performInput|checkpoint/.test(cause);
|
|
269
370
|
return {
|
|
270
371
|
...base,
|
|
271
372
|
status: "FAILED",
|
|
272
373
|
cause,
|
|
273
|
-
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" : "\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\u771F\u5B9E\u72B6\u6001\u7684\u53EA\u8BFB snapshot\uFF0C\u5E76\u5728\u5176\u540E\u65AD\u8A00\uFF1B\u4E0D\u8981\u76F4\u8FBE\u5185\u90E8\u5173\u5361\u6216\u4FEE\u6539\u73A9\u6CD5\u72B6\u6001\u3002",
|
|
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\u8BB0\u5F55 entered snapshot\uFF1B\u518D\u7528 performInput("primary") \u6267\u884C\u6838\u5FC3\u64CD\u4F5C\uFF0CstepUntil \u7B49\u5F85\u53D8\u5316\uFF0C\u65AD\u8A00\u540E\u8BB0\u5F55 progress \u6216 terminal snapshot\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\u771F\u5B9E\u72B6\u6001\u7684\u53EA\u8BFB snapshot\uFF0C\u5E76\u5728\u5176\u540E\u65AD\u8A00\uFF1B\u4E0D\u8981\u76F4\u8FBE\u5185\u90E8\u5173\u5361\u6216\u4FEE\u6539\u73A9\u6CD5\u72B6\u6001\u3002",
|
|
274
375
|
failsRun: true
|
|
275
376
|
};
|
|
276
377
|
}
|
package/dist/vitest-config.d.mts
CHANGED
package/dist/vitest-config.d.ts
CHANGED
package/dist/vitest-config.js
CHANGED
|
@@ -275,14 +275,14 @@ function auditGameplayCollection(options, file, tests) {
|
|
|
275
275
|
function getGameplayEvidenceIssues(id, requirements, evidence) {
|
|
276
276
|
const issues = [];
|
|
277
277
|
const inputEvents = evidence.mouseEvents + evidence.keyboardEvents + evidence.touchEvents;
|
|
278
|
-
if (requirements.requireInput && inputEvents === 0) {
|
|
279
|
-
issues.push(`contract "${id}" \u672A\u8BB0\u5F55\
|
|
278
|
+
if (requirements.requireInput && (inputEvents === 0 || evidence.consumedInputEvents === 0)) {
|
|
279
|
+
issues.push(`contract "${id}" \u672A\u8BB0\u5F55\u88AB Phaser InputPlugin \u6D88\u8D39\u7684 DOM \u8F93\u5165\u3002`);
|
|
280
280
|
}
|
|
281
|
-
if (requirements.requireFrameAdvance && evidence.frames === 0) {
|
|
282
|
-
issues.push(`contract "${id}" \u672A\
|
|
281
|
+
if (requirements.requireFrameAdvance && (evidence.frames === 0 || evidence.observedFrames === 0)) {
|
|
282
|
+
issues.push(`contract "${id}" \u672A\u8BB0\u5F55\u7531\u6D3B\u52A8 Scene \u5B9E\u9645\u6267\u884C\u7684\u5B8C\u6574 Phaser \u5E27\u3002`);
|
|
283
283
|
}
|
|
284
|
-
if (requirements.requirePhysicsStep && evidence.physicsSteps === 0) {
|
|
285
|
-
issues.push(`contract "${id}" \u672A\
|
|
284
|
+
if (requirements.requirePhysicsStep && (evidence.physicsSteps === 0 || evidence.observedPhysicsSteps === 0)) {
|
|
285
|
+
issues.push(`contract "${id}" \u672A\u8BB0\u5F55 Arcade WORLD_STEP\u3002`);
|
|
286
286
|
}
|
|
287
287
|
for (const target of normalizeList(requirements.requireTransition)) {
|
|
288
288
|
if (!evidence.transitions.some((transition) => transition.to === target)) {
|
|
@@ -455,9 +455,9 @@ function formatGameplayIssues(issues) {
|
|
|
455
455
|
unhandled: "\u5F02\u6B65\u9519\u8BEF\uFF1A\u5148\u4FEE\u590D\u4E0A\u65B9\u663E\u793A\u7684\u7B2C\u4E00\u6761\u9519\u8BEF\uFF1B\u8F93\u5165\u3001\u5E27\u63A8\u8FDB\u548C\u751F\u547D\u5468\u671F\u64CD\u4F5C\u90FD\u8981\u5728\u6B63\u786E\u7684 host \u751F\u547D\u5468\u671F\u5185\u5E76\u4F7F\u7528 await\u3002"
|
|
456
456
|
};
|
|
457
457
|
const getCategory = (issue) => {
|
|
458
|
-
if (issue.includes("\u672A\u8BB0\u5F55\u771F\u5B9E DOM \u8F93\u5165")) return { category: "input", key: "input" };
|
|
459
|
-
if (issue.includes("\u672A\u63A8\u8FDB\u5B8C\u6574 Phaser \u5E27")) return { category: "frames", key: "frames" };
|
|
460
|
-
if (issue.includes("\u672A\u63A8\u8FDB Arcade Physics")) return { category: "physics", key: "physics" };
|
|
458
|
+
if (issue.includes("\u672A\u8BB0\u5F55\u771F\u5B9E DOM \u8F93\u5165") || issue.includes("Phaser InputPlugin \u6D88\u8D39\u7684 DOM \u8F93\u5165")) return { category: "input", key: "input" };
|
|
459
|
+
if (issue.includes("\u672A\u63A8\u8FDB\u5B8C\u6574 Phaser \u5E27") || issue.includes("\u6D3B\u52A8 Scene \u5B9E\u9645\u6267\u884C\u7684\u5B8C\u6574 Phaser \u5E27")) return { category: "frames", key: "frames" };
|
|
460
|
+
if (issue.includes("\u672A\u63A8\u8FDB Arcade Physics") || issue.includes("\u672A\u8BB0\u5F55 Arcade WORLD_STEP")) return { category: "physics", key: "physics" };
|
|
461
461
|
if (issue.includes("\u672A\u8BB0\u5F55\u5230 ") && issue.includes("Scene \u8F6C\u573A")) {
|
|
462
462
|
const target = issue.match(/未记录到 (.+?) 的 Scene 转场/)?.[1] ?? "unknown";
|
|
463
463
|
return { category: "transition", key: `transition:${target}` };
|
package/dist/vitest-config.mjs
CHANGED
|
@@ -251,14 +251,14 @@ function auditGameplayCollection(options, file, tests) {
|
|
|
251
251
|
function getGameplayEvidenceIssues(id, requirements, evidence) {
|
|
252
252
|
const issues = [];
|
|
253
253
|
const inputEvents = evidence.mouseEvents + evidence.keyboardEvents + evidence.touchEvents;
|
|
254
|
-
if (requirements.requireInput && inputEvents === 0) {
|
|
255
|
-
issues.push(`contract "${id}" \u672A\u8BB0\u5F55\
|
|
254
|
+
if (requirements.requireInput && (inputEvents === 0 || evidence.consumedInputEvents === 0)) {
|
|
255
|
+
issues.push(`contract "${id}" \u672A\u8BB0\u5F55\u88AB Phaser InputPlugin \u6D88\u8D39\u7684 DOM \u8F93\u5165\u3002`);
|
|
256
256
|
}
|
|
257
|
-
if (requirements.requireFrameAdvance && evidence.frames === 0) {
|
|
258
|
-
issues.push(`contract "${id}" \u672A\
|
|
257
|
+
if (requirements.requireFrameAdvance && (evidence.frames === 0 || evidence.observedFrames === 0)) {
|
|
258
|
+
issues.push(`contract "${id}" \u672A\u8BB0\u5F55\u7531\u6D3B\u52A8 Scene \u5B9E\u9645\u6267\u884C\u7684\u5B8C\u6574 Phaser \u5E27\u3002`);
|
|
259
259
|
}
|
|
260
|
-
if (requirements.requirePhysicsStep && evidence.physicsSteps === 0) {
|
|
261
|
-
issues.push(`contract "${id}" \u672A\
|
|
260
|
+
if (requirements.requirePhysicsStep && (evidence.physicsSteps === 0 || evidence.observedPhysicsSteps === 0)) {
|
|
261
|
+
issues.push(`contract "${id}" \u672A\u8BB0\u5F55 Arcade WORLD_STEP\u3002`);
|
|
262
262
|
}
|
|
263
263
|
for (const target of normalizeList(requirements.requireTransition)) {
|
|
264
264
|
if (!evidence.transitions.some((transition) => transition.to === target)) {
|
|
@@ -431,9 +431,9 @@ function formatGameplayIssues(issues) {
|
|
|
431
431
|
unhandled: "\u5F02\u6B65\u9519\u8BEF\uFF1A\u5148\u4FEE\u590D\u4E0A\u65B9\u663E\u793A\u7684\u7B2C\u4E00\u6761\u9519\u8BEF\uFF1B\u8F93\u5165\u3001\u5E27\u63A8\u8FDB\u548C\u751F\u547D\u5468\u671F\u64CD\u4F5C\u90FD\u8981\u5728\u6B63\u786E\u7684 host \u751F\u547D\u5468\u671F\u5185\u5E76\u4F7F\u7528 await\u3002"
|
|
432
432
|
};
|
|
433
433
|
const getCategory = (issue) => {
|
|
434
|
-
if (issue.includes("\u672A\u8BB0\u5F55\u771F\u5B9E DOM \u8F93\u5165")) return { category: "input", key: "input" };
|
|
435
|
-
if (issue.includes("\u672A\u63A8\u8FDB\u5B8C\u6574 Phaser \u5E27")) return { category: "frames", key: "frames" };
|
|
436
|
-
if (issue.includes("\u672A\u63A8\u8FDB Arcade Physics")) return { category: "physics", key: "physics" };
|
|
434
|
+
if (issue.includes("\u672A\u8BB0\u5F55\u771F\u5B9E DOM \u8F93\u5165") || issue.includes("Phaser InputPlugin \u6D88\u8D39\u7684 DOM \u8F93\u5165")) return { category: "input", key: "input" };
|
|
435
|
+
if (issue.includes("\u672A\u63A8\u8FDB\u5B8C\u6574 Phaser \u5E27") || issue.includes("\u6D3B\u52A8 Scene \u5B9E\u9645\u6267\u884C\u7684\u5B8C\u6574 Phaser \u5E27")) return { category: "frames", key: "frames" };
|
|
436
|
+
if (issue.includes("\u672A\u63A8\u8FDB Arcade Physics") || issue.includes("\u672A\u8BB0\u5F55 Arcade WORLD_STEP")) return { category: "physics", key: "physics" };
|
|
437
437
|
if (issue.includes("\u672A\u8BB0\u5F55\u5230 ") && issue.includes("Scene \u8F6C\u573A")) {
|
|
438
438
|
const target = issue.match(/未记录到 (.+?) 的 Scene 转场/)?.[1] ?? "unknown";
|
|
439
439
|
return { category: "transition", key: `transition:${target}` };
|