miaoda-game-devkit 0.6.4 → 0.6.6
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/dist/cli/phaser-lint.js +2 -2
- package/dist/cli/react-lint.js +2 -2
- package/dist/react/testing.d.mts +12 -0
- package/dist/react/testing.d.ts +12 -0
- package/dist/react/testing.js +112 -14
- package/dist/react/testing.mjs +112 -14
- package/dist/react/vitest-config.js +636 -440
- package/dist/react/vitest-config.mjs +640 -444
- package/dist/react/vitest-setup.js +112 -14
- package/dist/react/vitest-setup.mjs +112 -14
- package/dist/rules/react-test-boundary-plugin.js +1 -1
- package/package.json +1 -1
|
@@ -31,47 +31,145 @@ function safeJson(value) {
|
|
|
31
31
|
return void 0;
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
function
|
|
34
|
+
function diagnosticValue(value) {
|
|
35
|
+
if (value === void 0) return void 0;
|
|
35
36
|
if (typeof value === "string") {
|
|
36
|
-
return value.
|
|
37
|
+
return truncate(value.replace(/\s+/g, " ").trim());
|
|
38
|
+
}
|
|
39
|
+
if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
40
|
+
return String(value);
|
|
41
|
+
}
|
|
42
|
+
if (typeof value === "symbol") return value.toString();
|
|
43
|
+
if (typeof value === "function") {
|
|
44
|
+
return `Function<${value.name || "anonymous"}>`;
|
|
45
|
+
}
|
|
46
|
+
const json = safeJson(value);
|
|
47
|
+
return truncate(json ?? String(value));
|
|
48
|
+
}
|
|
49
|
+
function normalizeFile(value) {
|
|
50
|
+
return value.replaceAll("\\", "/").replace(/^file:\/\//, "");
|
|
51
|
+
}
|
|
52
|
+
function failureLayer(file) {
|
|
53
|
+
const normalized = normalizeFile(file);
|
|
54
|
+
if (normalized.includes("/miaoda-game-devkit/") || normalized.includes("/packages/game-devkit/")) {
|
|
55
|
+
return "harness";
|
|
56
|
+
}
|
|
57
|
+
if (normalized.includes("/node_modules/")) return "dependency";
|
|
58
|
+
if (/(?:^|\/)tests?\//.test(normalized) || /\.(?:test|spec)\.[cm]?[jt]sx?$/.test(normalized)) {
|
|
59
|
+
return "test";
|
|
60
|
+
}
|
|
61
|
+
if (/(?:^|\/)src\//.test(normalized)) return "product";
|
|
62
|
+
return "unknown";
|
|
63
|
+
}
|
|
64
|
+
function parsedOrigin(value) {
|
|
65
|
+
if (!value || typeof value !== "object") return void 0;
|
|
66
|
+
const frame = value;
|
|
67
|
+
if (typeof frame.file !== "string" || typeof frame.line !== "number" || typeof frame.column !== "number") {
|
|
68
|
+
return void 0;
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
file: normalizeFile(frame.file),
|
|
72
|
+
line: frame.line,
|
|
73
|
+
column: frame.column
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function stackOrigins(record) {
|
|
77
|
+
if (Array.isArray(record.stacks)) {
|
|
78
|
+
const parsed = record.stacks.map(parsedOrigin).filter((origin) => Boolean(origin));
|
|
79
|
+
if (parsed.length > 0) return parsed;
|
|
80
|
+
}
|
|
81
|
+
if (typeof record.stack !== "string") return [];
|
|
82
|
+
const origins = [];
|
|
83
|
+
const pattern = /(?:at\s+.*?\()?((?:file:\/\/)?[^()\s]+):(\d+):(\d+)\)?/g;
|
|
84
|
+
for (const match of record.stack.matchAll(pattern)) {
|
|
85
|
+
origins.push({
|
|
86
|
+
file: normalizeFile(match[1]),
|
|
87
|
+
line: Number(match[2]),
|
|
88
|
+
column: Number(match[3])
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return origins;
|
|
92
|
+
}
|
|
93
|
+
function selectOrigin(record) {
|
|
94
|
+
const origins = stackOrigins(record);
|
|
95
|
+
return origins.find((origin) => {
|
|
96
|
+
const layer = failureLayer(origin.file);
|
|
97
|
+
return layer === "product" || layer === "test";
|
|
98
|
+
}) ?? origins.find((origin) => failureLayer(origin.file) !== "dependency");
|
|
99
|
+
}
|
|
100
|
+
function fallbackCode(record, origin, defaultCode) {
|
|
101
|
+
if (typeof record.code === "string" && record.code.trim()) {
|
|
102
|
+
return record.code.trim();
|
|
103
|
+
}
|
|
104
|
+
if (record.name === "AssertionError" && record.actual !== void 0 && record.expected !== void 0) {
|
|
105
|
+
return "EXPECTATION_MISMATCH";
|
|
106
|
+
}
|
|
107
|
+
if (record.name === "TypeError" && origin) {
|
|
108
|
+
const layer = failureLayer(origin.file);
|
|
109
|
+
if (layer === "product") return "PRODUCT_RUNTIME_TYPE_ERROR";
|
|
110
|
+
if (layer === "test") return "TEST_API_MISMATCH";
|
|
111
|
+
}
|
|
112
|
+
return defaultCode;
|
|
113
|
+
}
|
|
114
|
+
function structuredEntry(record, message, defaultCode) {
|
|
115
|
+
const origin = selectOrigin(record);
|
|
116
|
+
return {
|
|
117
|
+
code: fallbackCode(record, origin, defaultCode),
|
|
118
|
+
message: truncate(message),
|
|
119
|
+
errorName: typeof record.name === "string" && record.name.trim() ? record.name.trim() : void 0,
|
|
120
|
+
actual: diagnosticValue(record.actual),
|
|
121
|
+
expected: diagnosticValue(record.expected),
|
|
122
|
+
origin,
|
|
123
|
+
layer: origin ? failureLayer(origin.file) : void 0
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function collectEntries(value, fallbackCode2, seen) {
|
|
127
|
+
if (typeof value === "string") {
|
|
128
|
+
return value.trim() ? [{ code: fallbackCode2, message: truncate(value) }] : [];
|
|
37
129
|
}
|
|
38
130
|
if (value === null || value === void 0 || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || typeof value === "symbol") {
|
|
39
|
-
return [{ code:
|
|
131
|
+
return [{ code: fallbackCode2, message: String(value) }];
|
|
40
132
|
}
|
|
41
133
|
if (typeof value === "function") {
|
|
42
134
|
return [
|
|
43
|
-
{ code:
|
|
135
|
+
{ code: fallbackCode2, message: `Function<${value.name || "anonymous"}>` }
|
|
44
136
|
];
|
|
45
137
|
}
|
|
46
138
|
if (seen.has(value)) return [];
|
|
47
139
|
seen.add(value);
|
|
48
140
|
if (Array.isArray(value)) {
|
|
49
|
-
return value.flatMap((item) => collectEntries(item,
|
|
141
|
+
return value.flatMap((item) => collectEntries(item, fallbackCode2, seen));
|
|
50
142
|
}
|
|
51
143
|
const record = value;
|
|
52
|
-
const code = typeof record.code === "string" && record.code.trim() ? record.code.trim() : fallbackCode;
|
|
53
144
|
const entries = [];
|
|
54
145
|
if (typeof record.message === "string" && record.message.trim()) {
|
|
55
|
-
entries.push(
|
|
146
|
+
entries.push(structuredEntry(record, record.message, fallbackCode2));
|
|
56
147
|
}
|
|
57
148
|
if (record.cause !== void 0) {
|
|
58
|
-
entries.push(...collectEntries(record.cause,
|
|
149
|
+
entries.push(...collectEntries(record.cause, fallbackCode2, seen));
|
|
59
150
|
}
|
|
60
151
|
if (Array.isArray(record.errors)) {
|
|
61
|
-
entries.push(...collectEntries(record.errors,
|
|
152
|
+
entries.push(...collectEntries(record.errors, fallbackCode2, seen));
|
|
62
153
|
}
|
|
63
154
|
if (entries.length > 0) return entries;
|
|
64
155
|
if (typeof record.stack === "string" && record.stack.trim()) {
|
|
65
|
-
return [
|
|
156
|
+
return [structuredEntry(record, record.stack, fallbackCode2)];
|
|
66
157
|
}
|
|
67
158
|
const json = safeJson(value);
|
|
68
|
-
return json && json !== "{}" ? [
|
|
159
|
+
return json && json !== "{}" ? [structuredEntry(record, json, fallbackCode2)] : [];
|
|
69
160
|
}
|
|
70
|
-
function extractFailureEntries(value,
|
|
71
|
-
const entries = collectEntries(value,
|
|
161
|
+
function extractFailureEntries(value, fallbackCode2 = "TEST_FAILURE") {
|
|
162
|
+
const entries = collectEntries(value, fallbackCode2, /* @__PURE__ */ new WeakSet());
|
|
72
163
|
const keys = /* @__PURE__ */ new Set();
|
|
73
164
|
return entries.filter((entry) => {
|
|
74
|
-
const key =
|
|
165
|
+
const key = [
|
|
166
|
+
entry.code,
|
|
167
|
+
entry.message,
|
|
168
|
+
entry.errorName,
|
|
169
|
+
entry.actual,
|
|
170
|
+
entry.expected,
|
|
171
|
+
entry.origin ? `${entry.origin.file}:${entry.origin.line}:${entry.origin.column}` : void 0
|
|
172
|
+
].join("\0");
|
|
75
173
|
if (keys.has(key)) return false;
|
|
76
174
|
keys.add(key);
|
|
77
175
|
return true;
|
|
@@ -29,47 +29,145 @@ function safeJson(value) {
|
|
|
29
29
|
return void 0;
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
-
function
|
|
32
|
+
function diagnosticValue(value) {
|
|
33
|
+
if (value === void 0) return void 0;
|
|
33
34
|
if (typeof value === "string") {
|
|
34
|
-
return value.
|
|
35
|
+
return truncate(value.replace(/\s+/g, " ").trim());
|
|
36
|
+
}
|
|
37
|
+
if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
38
|
+
return String(value);
|
|
39
|
+
}
|
|
40
|
+
if (typeof value === "symbol") return value.toString();
|
|
41
|
+
if (typeof value === "function") {
|
|
42
|
+
return `Function<${value.name || "anonymous"}>`;
|
|
43
|
+
}
|
|
44
|
+
const json = safeJson(value);
|
|
45
|
+
return truncate(json ?? String(value));
|
|
46
|
+
}
|
|
47
|
+
function normalizeFile(value) {
|
|
48
|
+
return value.replaceAll("\\", "/").replace(/^file:\/\//, "");
|
|
49
|
+
}
|
|
50
|
+
function failureLayer(file) {
|
|
51
|
+
const normalized = normalizeFile(file);
|
|
52
|
+
if (normalized.includes("/miaoda-game-devkit/") || normalized.includes("/packages/game-devkit/")) {
|
|
53
|
+
return "harness";
|
|
54
|
+
}
|
|
55
|
+
if (normalized.includes("/node_modules/")) return "dependency";
|
|
56
|
+
if (/(?:^|\/)tests?\//.test(normalized) || /\.(?:test|spec)\.[cm]?[jt]sx?$/.test(normalized)) {
|
|
57
|
+
return "test";
|
|
58
|
+
}
|
|
59
|
+
if (/(?:^|\/)src\//.test(normalized)) return "product";
|
|
60
|
+
return "unknown";
|
|
61
|
+
}
|
|
62
|
+
function parsedOrigin(value) {
|
|
63
|
+
if (!value || typeof value !== "object") return void 0;
|
|
64
|
+
const frame = value;
|
|
65
|
+
if (typeof frame.file !== "string" || typeof frame.line !== "number" || typeof frame.column !== "number") {
|
|
66
|
+
return void 0;
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
file: normalizeFile(frame.file),
|
|
70
|
+
line: frame.line,
|
|
71
|
+
column: frame.column
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function stackOrigins(record) {
|
|
75
|
+
if (Array.isArray(record.stacks)) {
|
|
76
|
+
const parsed = record.stacks.map(parsedOrigin).filter((origin) => Boolean(origin));
|
|
77
|
+
if (parsed.length > 0) return parsed;
|
|
78
|
+
}
|
|
79
|
+
if (typeof record.stack !== "string") return [];
|
|
80
|
+
const origins = [];
|
|
81
|
+
const pattern = /(?:at\s+.*?\()?((?:file:\/\/)?[^()\s]+):(\d+):(\d+)\)?/g;
|
|
82
|
+
for (const match of record.stack.matchAll(pattern)) {
|
|
83
|
+
origins.push({
|
|
84
|
+
file: normalizeFile(match[1]),
|
|
85
|
+
line: Number(match[2]),
|
|
86
|
+
column: Number(match[3])
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return origins;
|
|
90
|
+
}
|
|
91
|
+
function selectOrigin(record) {
|
|
92
|
+
const origins = stackOrigins(record);
|
|
93
|
+
return origins.find((origin) => {
|
|
94
|
+
const layer = failureLayer(origin.file);
|
|
95
|
+
return layer === "product" || layer === "test";
|
|
96
|
+
}) ?? origins.find((origin) => failureLayer(origin.file) !== "dependency");
|
|
97
|
+
}
|
|
98
|
+
function fallbackCode(record, origin, defaultCode) {
|
|
99
|
+
if (typeof record.code === "string" && record.code.trim()) {
|
|
100
|
+
return record.code.trim();
|
|
101
|
+
}
|
|
102
|
+
if (record.name === "AssertionError" && record.actual !== void 0 && record.expected !== void 0) {
|
|
103
|
+
return "EXPECTATION_MISMATCH";
|
|
104
|
+
}
|
|
105
|
+
if (record.name === "TypeError" && origin) {
|
|
106
|
+
const layer = failureLayer(origin.file);
|
|
107
|
+
if (layer === "product") return "PRODUCT_RUNTIME_TYPE_ERROR";
|
|
108
|
+
if (layer === "test") return "TEST_API_MISMATCH";
|
|
109
|
+
}
|
|
110
|
+
return defaultCode;
|
|
111
|
+
}
|
|
112
|
+
function structuredEntry(record, message, defaultCode) {
|
|
113
|
+
const origin = selectOrigin(record);
|
|
114
|
+
return {
|
|
115
|
+
code: fallbackCode(record, origin, defaultCode),
|
|
116
|
+
message: truncate(message),
|
|
117
|
+
errorName: typeof record.name === "string" && record.name.trim() ? record.name.trim() : void 0,
|
|
118
|
+
actual: diagnosticValue(record.actual),
|
|
119
|
+
expected: diagnosticValue(record.expected),
|
|
120
|
+
origin,
|
|
121
|
+
layer: origin ? failureLayer(origin.file) : void 0
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function collectEntries(value, fallbackCode2, seen) {
|
|
125
|
+
if (typeof value === "string") {
|
|
126
|
+
return value.trim() ? [{ code: fallbackCode2, message: truncate(value) }] : [];
|
|
35
127
|
}
|
|
36
128
|
if (value === null || value === void 0 || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || typeof value === "symbol") {
|
|
37
|
-
return [{ code:
|
|
129
|
+
return [{ code: fallbackCode2, message: String(value) }];
|
|
38
130
|
}
|
|
39
131
|
if (typeof value === "function") {
|
|
40
132
|
return [
|
|
41
|
-
{ code:
|
|
133
|
+
{ code: fallbackCode2, message: `Function<${value.name || "anonymous"}>` }
|
|
42
134
|
];
|
|
43
135
|
}
|
|
44
136
|
if (seen.has(value)) return [];
|
|
45
137
|
seen.add(value);
|
|
46
138
|
if (Array.isArray(value)) {
|
|
47
|
-
return value.flatMap((item) => collectEntries(item,
|
|
139
|
+
return value.flatMap((item) => collectEntries(item, fallbackCode2, seen));
|
|
48
140
|
}
|
|
49
141
|
const record = value;
|
|
50
|
-
const code = typeof record.code === "string" && record.code.trim() ? record.code.trim() : fallbackCode;
|
|
51
142
|
const entries = [];
|
|
52
143
|
if (typeof record.message === "string" && record.message.trim()) {
|
|
53
|
-
entries.push(
|
|
144
|
+
entries.push(structuredEntry(record, record.message, fallbackCode2));
|
|
54
145
|
}
|
|
55
146
|
if (record.cause !== void 0) {
|
|
56
|
-
entries.push(...collectEntries(record.cause,
|
|
147
|
+
entries.push(...collectEntries(record.cause, fallbackCode2, seen));
|
|
57
148
|
}
|
|
58
149
|
if (Array.isArray(record.errors)) {
|
|
59
|
-
entries.push(...collectEntries(record.errors,
|
|
150
|
+
entries.push(...collectEntries(record.errors, fallbackCode2, seen));
|
|
60
151
|
}
|
|
61
152
|
if (entries.length > 0) return entries;
|
|
62
153
|
if (typeof record.stack === "string" && record.stack.trim()) {
|
|
63
|
-
return [
|
|
154
|
+
return [structuredEntry(record, record.stack, fallbackCode2)];
|
|
64
155
|
}
|
|
65
156
|
const json = safeJson(value);
|
|
66
|
-
return json && json !== "{}" ? [
|
|
157
|
+
return json && json !== "{}" ? [structuredEntry(record, json, fallbackCode2)] : [];
|
|
67
158
|
}
|
|
68
|
-
function extractFailureEntries(value,
|
|
69
|
-
const entries = collectEntries(value,
|
|
159
|
+
function extractFailureEntries(value, fallbackCode2 = "TEST_FAILURE") {
|
|
160
|
+
const entries = collectEntries(value, fallbackCode2, /* @__PURE__ */ new WeakSet());
|
|
70
161
|
const keys = /* @__PURE__ */ new Set();
|
|
71
162
|
return entries.filter((entry) => {
|
|
72
|
-
const key =
|
|
163
|
+
const key = [
|
|
164
|
+
entry.code,
|
|
165
|
+
entry.message,
|
|
166
|
+
entry.errorName,
|
|
167
|
+
entry.actual,
|
|
168
|
+
entry.expected,
|
|
169
|
+
entry.origin ? `${entry.origin.file}:${entry.origin.line}:${entry.origin.column}` : void 0
|
|
170
|
+
].join("\0");
|
|
73
171
|
if (keys.has(key)) return false;
|
|
74
172
|
keys.add(key);
|
|
75
173
|
return true;
|
|
@@ -57,7 +57,7 @@ var rule = {
|
|
|
57
57
|
providedUser: "Use the user provided by playthroughTest; do not import or create another userEvent instance in the production playthrough.",
|
|
58
58
|
productionTestingImport: "Production source must not import miaoda-game-devkit/react/testing. Inject test clocks and observers through the production App factory boundary.",
|
|
59
59
|
productionEntry: "Render <App /> from the production playthrough. Import Controller and Telemetry helpers when needed, but do not render an internal game component directly.",
|
|
60
|
-
emptyStep:
|
|
60
|
+
emptyStep: 'A deterministic step must advance production-owned time, frames, or queued work. An empty step is not gameplay evidence. Add import { ManualGameClock } from "miaoda-game-devkit/react/testing"; inject it through the production <App /> factory, and use step: () => clock.stepFrame().'
|
|
61
61
|
},
|
|
62
62
|
schema: []
|
|
63
63
|
},
|