Skip to content

Commit e033507

Browse files
committed
test(shell): 抽出顶栏测试的源码读取器
1 parent 6dfbfa7 commit e033507

2 files changed

Lines changed: 75 additions & 57 deletions

File tree

‎app/src/shell/TopBar.test.tsx‎

Lines changed: 4 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -25,68 +25,15 @@
2525
* 交互用 `fireEvent`;且**没有** `globals` ⇒ RTL 的 auto-cleanup 不生效,必须显式 `afterEach(cleanup)`
2626
* (先例 `components/NoteColorPicker.test.tsx:11`),否则多次 `render` 后 `getByTestId` 会撞多重命中。
2727
*/
28-
import { readFileSync } from "node:fs";
29-
import { dirname, join } from "node:path";
30-
import { fileURLToPath } from "node:url";
3128
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
3229
import { afterEach, describe, expect, it, vi } from "vitest";
3330
import { TopBar } from "./TopBar";
3431
import { NAV_ENTRIES } from "./navRegistry";
3532
import { BREAKPOINTS } from "./breakpoints";
36-
37-
const HERE = dirname(fileURLToPath(import.meta.url));
38-
/** 判据前先剥注释(与 `ui/primitives` 层同一口径):注释里提到 transition 不算犯规 */
39-
const CSS = readFileSync(join(HERE, "TopBar.css"), "utf8").replace(/\/\*[\s\S]*?\*\//g, "");
40-
/**
41-
* G1 改判(T14)的**真源侧**只读件:`motion.css` 的元素级回执 `button:not(.ed-btn)` 与那条唯一的
42-
* reduced-motion 块。顶栏的交互元素全是裸 `<button>` ⇒ 顶栏的时长/缓动**不是顶栏自己的事**:
43-
* 真源在那条元素级规则里,顶栏再写一份就是 R1.1 明禁的第二个真源(`motion.css:63` 同款论证)。
44-
*/
45-
const MOTION_CSS = readFileSync(join(HERE, "..", "ui", "primitives", "motion.css"), "utf8").replace(/\/\*[\s\S]*?\*\//g, "");
46-
const TOPBAR_TSX = readFileSync(join(HERE, "TopBar.tsx"), "utf8");
47-
const APP_TSX = readFileSync(join(HERE, "..", "App.tsx"), "utf8");
48-
const AI_TOAST_TSX = readFileSync(join(HERE, "aiToast.tsx"), "utf8").replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, ""); // 批 7 T1:AI toast 装配件的新家(**剥注释** —— 不剥则注释里的字面量会满足判据,变异体 m9 实测过这个洞)
49-
/**
50-
* App.tsx 的**只留代码**版本:剥块注释与整行 `//` 注释。
51-
* Why 必须剥:App.tsx 的注释里逐字出现了 `<nav>`、`zIndex("toast")` 这些判据串 —— 不剥的话
52-
* 「toast 已搬出导航行」这条判据只要注释还在就会**假绿**(正是本批反复打击的「测不到失败的检查」)。
53-
*/
54-
const APP_CODE = APP_TSX.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
55-
56-
/**
57-
* T10(B13/B15 授权的机械改写):AI toast 的定位/锚点/层级判据搬到**原语层** —— 它们是
58-
* `ui/primitives/Toast.{css,tsx}` 的事实,覆盖**所有** `Toast` 消费者(不再只覆盖 `App.tsx` 一处)。
59-
* 取规则体用「`选择器 {` 到第一个 `}`」(同 `ui/primitives/Toast.placement.test.tsx:38`):
60-
* 防「同一个串写在别的规则里也算过」。
61-
*/
62-
const TOAST_CSS = readFileSync(join(HERE, "..", "ui", "primitives", "Toast.css"), "utf8")
63-
.replace(/\r\n/g, "\n")
64-
.replace(/\/\*[\s\S]*?\*\//g, "");
65-
const TOAST_TSX = readFileSync(join(HERE, "..", "ui", "primitives", "Toast.tsx"), "utf8");
66-
const toastRuleBody = (selector: string): string => {
67-
const at = TOAST_CSS.indexOf(selector);
68-
return at < 0 ? "" : TOAST_CSS.slice(at, TOAST_CSS.indexOf("}", at));
69-
};
70-
const TOAST_BASE_BODY = toastRuleBody(".ed-toast {");
71-
const TOAST_BELOW_NAV_BODY = toastRuleBody(".ed-toast--below-nav");
72-
73-
/** emoji / 图形字符(A2 裁决的机器判据)。`\p{Extended_Pictographic}` 是 Unicode 属性类,覆盖 ✨📡🎙 等 */
74-
const PICTO = /\p{Extended_Pictographic}/u;
75-
76-
/**
77-
* G1 改判的**扫描口径**(一处定义,判据与仪器自证共用 ⇒ 不会各写一份正则而漂移)。
78-
* `withoutTokenVars` 必须先剥掉 `var(--ed-x, <同值兜底>)` 整段:本仓的 token 习惯写法**带同值兜底**
79-
* (`--ed-dur-micro, 120ms`)⇒ 不剥的话「不得写裸 ms/s」会把合法 token 用法判成犯规(**假红**,
80-
* 逼实施者绕开守卫 —— 计划 Task 14 的 G1 新断言原文就有这个洞,T14 实施时实测并修正)。
81-
* 🔴 R1.2(改判不得弱于原判据):属性名还认**厂商前缀 / 大写**(同 `motion-coverage.test.ts:91` 的孪生守卫)—— 旧 G1 是子串匹配,`-webkit-transition:` 在它下面是**红**,新口径漏掉前缀支就是强度回退(T14c 专属变异体实测)。
82-
*/
83-
const motionDecls = (css: string): string[] =>
84-
css.split(";").filter((d) => /(?:^|[;\s])(?:-(?:webkit|moz|ms|o)-)?(?:transition|animation)(?:-duration|-name)?\s*:/i.test(d));
85-
const withoutTokenVars = (decl: string): string => decl.replace(/var\(--ed-[a-z0-9-]+,[^)]*\)/g, "");
86-
const rawTimes = (decls: string[]): string[] => decls.filter((d) => /\d+(?:\.\d+)?\s*(?:ms|s)\b/.test(withoutTokenVars(d)));
87-
/** 裸缓动的第三类:**裸关键字** `linear`/`ease`/`steps()`(Important ②)。边界用 `(?<![\w-])…(?![\w-])` 而非 `\b`:后者会把无兜底的合法 token `var(--ed-ease)` 与 `linear-gradient` 误判成犯规。 */
88-
const rawEases = (decls: string[]): string[] =>
89-
decls.filter((d) => /cubic-bezier|ease-(?:in|out|in-out)\b|(?<![\w-])(?:linear|ease|steps)(?![\w-])/i.test(withoutTokenVars(d)));
33+
import {
34+
AI_TOAST_TSX, APP_CODE, CSS, MOTION_CSS, PICTO, TOAST_BASE_BODY, TOAST_BELOW_NAV_BODY,
35+
TOAST_TSX, TOPBAR_TSX, motionDecls, rawEases, rawTimes,
36+
} from "./topBarTestKit";
9037

9138
afterEach(cleanup);
9239

‎app/src/shell/topBarTestKit.ts‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* topBarTestKit — `TopBar.test.tsx` 的**源码读取器与常量**(批 8 T7 拆件)。
3+
*
4+
* @ai-context 自 `shell/TopBar.test.tsx` **纯搬迁**抽出:这些 `readFileSync` 读取器、剥注释结果与
5+
* 动效扫描器是「把源码 / CSS 变成可判文本」的**仪器**,与被测的顶栏契约(①–④ 四层判据)无关 ⇒
6+
* 物理分离到本件,让判据件腾出行数头寸(原 299 / 余 1)。
7+
* @ai-context 为什么值也要一起搬(而不是只搬 `readFileSync` 调用):`CSS` / `APP_CODE` / `TOAST_*_BODY`
8+
* 都是**模块加载期就读盘算好**的常量,分居两件会让「读一次」变成「读两次」,且判据与仪器之间多一层
9+
* 间接 —— 搬迁的判据是**逐字节等价**,不是「重新设计」。
10+
* @ai-context 🔴 `HERE` 仍指 `app/src/shell/`(本件与判据件同目录)⇒ 所有相对路径一字不改;
11+
* `import.meta.url` 在 Vite/Vitest 下按 Vite 的 `file:` URL 解析,与判据件内的行为逐字相同。
12+
* 副作用:**模块加载期只读磁盘**(5 次 `readFileSync`,皆为仓库内文件),不写盘、不发请求。
13+
* 边界:本件在六棘轮的 PROD 域内(它不是 `*.test.*`)⇒ 零颜色 / 零字号 / 零边框字面量、零原生 `<button>`。
14+
*/
15+
import { readFileSync } from "node:fs";
16+
import { dirname, join } from "node:path";
17+
import { fileURLToPath } from "node:url";
18+
19+
const HERE = dirname(fileURLToPath(import.meta.url));
20+
/** 判据前先剥注释(与 `ui/primitives` 层同一口径):注释里提到 transition 不算犯规 */
21+
export const CSS = readFileSync(join(HERE, "TopBar.css"), "utf8").replace(/\/\*[\s\S]*?\*\//g, "");
22+
/**
23+
* G1 改判(T14)的**真源侧**只读件:`motion.css` 的元素级回执 `button:not(.ed-btn)` 与那条唯一的
24+
* reduced-motion 块。顶栏的交互元素全是裸 `<button>` ⇒ 顶栏的时长/缓动**不是顶栏自己的事**:
25+
* 真源在那条元素级规则里,顶栏再写一份就是 R1.1 明禁的第二个真源(`motion.css:63` 同款论证)。
26+
*/
27+
export const MOTION_CSS = readFileSync(join(HERE, "..", "ui", "primitives", "motion.css"), "utf8").replace(/\/\*[\s\S]*?\*\//g, "");
28+
export const TOPBAR_TSX = readFileSync(join(HERE, "TopBar.tsx"), "utf8");
29+
const APP_TSX = readFileSync(join(HERE, "..", "App.tsx"), "utf8");
30+
export const AI_TOAST_TSX = readFileSync(join(HERE, "aiToast.tsx"), "utf8").replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, ""); // 批 7 T1:AI toast 装配件的新家(**剥注释** —— 不剥则注释里的字面量会满足判据,变异体 m9 实测过这个洞)
31+
/**
32+
* App.tsx 的**只留代码**版本:剥块注释与整行 `//` 注释。
33+
* Why 必须剥:App.tsx 的注释里逐字出现了 `<nav>`、`zIndex("toast")` 这些判据串 —— 不剥的话
34+
* 「toast 已搬出导航行」这条判据只要注释还在就会**假绿**(正是本批反复打击的「测不到失败的检查」)。
35+
*/
36+
export const APP_CODE = APP_TSX.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
37+
38+
/**
39+
* T10(B13/B15 授权的机械改写):AI toast 的定位/锚点/层级判据搬到**原语层** —— 它们是
40+
* `ui/primitives/Toast.{css,tsx}` 的事实,覆盖**所有** `Toast` 消费者(不再只覆盖 `App.tsx` 一处)。
41+
* 取规则体用「`选择器 {` 到第一个 `}`」(同 `ui/primitives/Toast.placement.test.tsx:38`):
42+
* 防「同一个串写在别的规则里也算过」。
43+
*/
44+
export const TOAST_CSS = readFileSync(join(HERE, "..", "ui", "primitives", "Toast.css"), "utf8")
45+
.replace(/\r\n/g, "\n")
46+
.replace(/\/\*[\s\S]*?\*\//g, "");
47+
export const TOAST_TSX = readFileSync(join(HERE, "..", "ui", "primitives", "Toast.tsx"), "utf8");
48+
export const toastRuleBody = (selector: string): string => {
49+
const at = TOAST_CSS.indexOf(selector);
50+
return at < 0 ? "" : TOAST_CSS.slice(at, TOAST_CSS.indexOf("}", at));
51+
};
52+
export const TOAST_BASE_BODY = toastRuleBody(".ed-toast {");
53+
export const TOAST_BELOW_NAV_BODY = toastRuleBody(".ed-toast--below-nav");
54+
55+
/** emoji / 图形字符(A2 裁决的机器判据)。`\p{Extended_Pictographic}` 是 Unicode 属性类,覆盖 ✨📡🎙 等 */
56+
export const PICTO = /\p{Extended_Pictographic}/u;
57+
58+
/**
59+
* G1 改判的**扫描口径**(一处定义,判据与仪器自证共用 ⇒ 不会各写一份正则而漂移)。
60+
* `withoutTokenVars` 必须先剥掉 `var(--ed-x, <同值兜底>)` 整段:本仓的 token 习惯写法**带同值兜底**
61+
* (`--ed-dur-micro, 120ms`)⇒ 不剥的话「不得写裸 ms/s」会把合法 token 用法判成犯规(**假红**,
62+
* 逼实施者绕开守卫 —— 计划 Task 14 的 G1 新断言原文就有这个洞,T14 实施时实测并修正)。
63+
* 🔴 R1.2(改判不得弱于原判据):属性名还认**厂商前缀 / 大写**(同 `motion-coverage.test.ts:91` 的孪生守卫)—— 旧 G1 是子串匹配,`-webkit-transition:` 在它下面是**红**,新口径漏掉前缀支就是强度回退(T14c 专属变异体实测)。
64+
*/
65+
export const motionDecls = (css: string): string[] =>
66+
css.split(";").filter((d) => /(?:^|[;\s])(?:-(?:webkit|moz|ms|o)-)?(?:transition|animation)(?:-duration|-name)?\s*:/i.test(d));
67+
const withoutTokenVars = (decl: string): string => decl.replace(/var\(--ed-[a-z0-9-]+,[^)]*\)/g, "");
68+
export const rawTimes = (decls: string[]): string[] => decls.filter((d) => /\d+(?:\.\d+)?\s*(?:ms|s)\b/.test(withoutTokenVars(d)));
69+
/** 裸缓动的第三类:**裸关键字** `linear`/`ease`/`steps()`(Important ②)。边界用 `(?<![\w-])…(?![\w-])` 而非 `\b`:后者会把无兜底的合法 token `var(--ed-ease)` 与 `linear-gradient` 误判成犯规。 */
70+
export const rawEases = (decls: string[]): string[] =>
71+
decls.filter((d) => /cubic-bezier|ease-(?:in|out|in-out)\b|(?<![\w-])(?:linear|ease|steps)(?![\w-])/i.test(withoutTokenVars(d)));

0 commit comments

Comments
 (0)