Skip to content

Commit 451db3f

Browse files
committed
test(ui): make empty state ratchet probe-free
1 parent 5825fae commit 451db3f

1 file changed

Lines changed: 38 additions & 29 deletions

File tree

‎app/src/ui/primitives/emptyStateRatchet.test.ts‎

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@
2323
* M2 把余量冻结值抬高 1 ⇒ ④ 红(**第一版判据无牙、实测 6/6 绿,已改成「冻结值恰等于实测值」**);
2424
* M3 新增一个带词表的文件 ⇒ ④ 红;M4 删掉 `ChatLaunchMenu` 的必填 prop ⇒ `tsc` 红(vitest 不暴露);
2525
* M0 反方向:把这套守卫放进**迁移前的基线树** ⇒ ② 红(证明判据不是永真)。
26+
*
27+
* ★ 净克隆形态(T13–T15 评审 M-6):⑥a 依赖 gitignored 探针 ⇒ 探针缺席即 `skip`(`console.warn` 说明),
28+
* 不再让净克隆必红 1 条;⑥b 的盘上复算在净克隆里照跑(② 的 `pages/**` · ③ 的同名测试)。
2629
*/
27-
import { readFileSync, readdirSync } from "node:fs";
30+
import { existsSync, readFileSync } from "node:fs";
2831
import { dirname, join } from "node:path";
2932
import { fileURLToPath } from "node:url";
3033
import { describe, expect, it } from "vitest";
@@ -178,6 +181,18 @@ const unmigratedOf = (file: string): Hit[] => {
178181
};
179182
const describeHits = (hits: readonly Hit[]): string => hits.map((h) => `${h.file}:${h.line} ${h.text}`).join("\n");
180183

184+
/**
185+
* 探针(gitignored,`tmp/t13/slice.json`):**在场**时 ⑥a 做双向对拍;**缺席**(净克隆 / `git archive`
186+
* 树 / CI)时 ⑥a 走 `skipIf` 并打印原因,而不是用必然失败的哨兵把「净克隆」判成红 —— T13–T15 评审
187+
* M-6 的结清。第二源不因此消失:⑥b 是**盘上可复算**的那半(② 在 `pages/` 下 · ③ 有同名测试),
188+
* 任何树里都跑,故判据在净克隆里仍有牙。
189+
*/
190+
const PROBE = join(SRC, "..", "..", ".superpowers/sdd/2026-09-12-frontend-redesign-batch4-primitives/tmp/t13/slice.json");
191+
const PROBE_ABSENT = !existsSync(PROBE);
192+
if (PROBE_ABSENT) {
193+
console.warn("⑥a 未跑到:探针 tmp/t13/slice.json 不在场(净克隆 / CI)⇒ 只跑 ⑥b 的盘上复算");
194+
}
195+
181196
describe("空态切片棘轮(B11)", () => {
182197
it("① 切片清单是显式 28 文件,判据来源逐条合法", () => {
183198
expect(SLICE).toHaveLength(28);
@@ -252,36 +267,30 @@ describe("空态切片棘轮(B11)", () => {
252267
expect(stripComments("const b = `\u6682\u65e0`;").match(EMPTY_RE), "真文案被误剥").not.toBeNull();
253268
});
254269

255-
it("⑥ 与 gitignored 探针 `tmp/t13/slice.json` 双向对拍(探针不在场 ⇒ 明说未跑到)", () => {
256-
const probe = join(
257-
SRC, "..", "..",
258-
".superpowers/sdd/2026-09-12-frontend-redesign-batch4-primitives/tmp/t13/slice.json",
270+
it.skipIf(PROBE_ABSENT)("⑥a 与 gitignored 探针 `tmp/t13/slice.json` 双向对拍(探针不在场 ⇒ 本用例 skip,见 ⑥b)", () => {
271+
const payload: { slice: { rel: string; why: string }[]; rest: { rel: string }[] } = JSON.parse(
272+
readFileSync(PROBE, "utf8"),
259273
);
260-
let payload: { slice: { rel: string; why: string }[]; rest: { rel: string }[] } | null = null;
261-
try {
262-
const parsed: { slice: { rel: string; why: string }[]; rest: { rel: string }[] } = JSON.parse(
263-
readFileSync(probe, "utf8"),
264-
);
265-
payload = parsed;
266-
} catch {
267-
payload = null;
268-
}
269-
if (payload === null) {
270-
// 干净克隆 / 导出树没有 `.superpowers/`(gitignored)⇒ **不假装有牙**:用一条必然失败的哨兵
271-
// 把「今天这条对拍没跑到」显形,而不是静默通过。唯一例外:变异体实验的导出树显式设
272-
// `T13_NO_PROBE=1`(那些树里没有探针,但 ①–⑤ 仍逐条在跑)——那是**登记过的跳过**,不是默认值。
273-
expect(
274-
process.env.T13_NO_PROBE === "1" || readdirSync(SRC).includes("__t13_probe_absent__"),
275-
"探针缺失 ⇒ 清单对拍未跑到(本判据今日未验证;请在有 .superpowers/ 的工作树里跑)",
276-
).toBe(true);
277-
return;
278-
}
279-
const probeSlice: { rel: string; why: string }[] = payload.slice;
280-
const probeRest: { rel: string }[] = payload.rest;
281-
expect([...SLICE].sort(), "切片集与探针不一致(双向差集必须为空)").toEqual(probeSlice.map((s) => s.rel).sort());
282-
expect(Object.keys(FROZEN_REST).sort(), "冻结余量集与探针的余量集不一致").toEqual(probeRest.map((r) => r.rel).sort());
274+
expect([...SLICE].sort(), "切片集与探针不一致(双向差集必须为空)").toEqual(payload.slice.map((s) => s.rel).sort());
275+
expect(Object.keys(FROZEN_REST).sort(), "冻结余量集与探针的余量集不一致").toEqual(payload.rest.map((r) => r.rel).sort());
283276
const why: Record<string, string> = {};
284-
for (const s of probeSlice) why[s.rel] = s.why;
277+
for (const s of payload.slice) why[s.rel] = s.why;
285278
for (const f of SLICE) expect(why[f], `${f} 的判据来源与探针不一致`).toBe(SLICE_REASONS[f]);
286279
});
280+
281+
it("⑥b 判据来源**盘上可复算**(净克隆也跑):② 必须在 `pages/` 下 · ③ 必须有同名测试", () => {
282+
const testRels = new Set(walkSources(SRC).map((f) => relOf(SRC, f)).filter((r) => /\.test\.tsx?$/.test(r)));
283+
const bad: string[] = [];
284+
for (const f of SLICE) {
285+
const why = SLICE_REASONS[f];
286+
if (why.includes("2") && !f.startsWith("pages/")) bad.push(`${f}: 判据含 ②(pages/**)却不在 pages/ 下`);
287+
if (why.includes("3") && !testRels.has(f.replace(/\.(tsx?)$/, ".test.$1")))
288+
bad.push(`${f}: 判据含 ③(同名测试)却找不到同名测试文件`);
289+
}
290+
expect(bad, `切片判据来源与盘上事实不符(净克隆里的第二源):\n${bad.join("\n")}`).toEqual([]);
291+
// 反向(防真空):域内所有 `pages/**` 的命中文件都必须在切片里 —— 少一个 = 切片不完整(判 ② 的漏登记)
292+
const pagesHits = [...new Set(ALL_HITS.map((h) => h.file))].filter((f) => f.startsWith("pages/"));
293+
expect(pagesHits.length, "域内 pages/** 一处命中都没有 ⇒ 上一条空真").toBeGreaterThan(0);
294+
expect(pagesHits.filter((f) => !SLICE.includes(f)), "这些 pages/** 有命中却不在切片里").toEqual([]);
295+
});
287296
});

0 commit comments

Comments
 (0)