diff --git a/apps/ai-game-creator-shell/tests/appSurface/plan-gdd.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/plan-gdd.suite.ts index fd20d253e..cb3e568a0 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/plan-gdd.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/plan-gdd.suite.ts @@ -546,7 +546,18 @@ export function registerPlanGddApprovalTests() { for (const match of componentSources.matchAll( /className=(?:"([^"]+)"|\{`([^`]+)`\})/g, )) { - const literal = (match[1] ?? match[2] ?? '').replace(/\$\{[^}]*\}/g, ' '); + // 条件类长在插值里:`plan-gdd-surface${showCard ? ' …--with-card' : ''}`。 + // 把 `${…}` 整段丢掉等于把它们排除在守门之外,而它们恰恰是最容易被顺手删干净 + // 的一档——`--with-card` 挂着审批卡的行模板,没有它卡片底部会被外壳的 + // `overflow: hidden` 切掉。只取插值里的字符串字面量:三元的条件、变量名都不是 + // 类名,不能混进清单。 + const literal = (match[1] ?? match[2] ?? '').replace( + /\$\{([^}]*)\}/g, + (_whole, expression: string) => + [...expression.matchAll(/'([^']*)'|"([^"]*)"/g)] + .map((piece) => piece[1] ?? piece[2] ?? '') + .join(' '), + ); for (const name of literal.split(/\s+/)) { if (name) { referencedClasses.add(name); @@ -559,9 +570,15 @@ export function registerPlanGddApprovalTests() { 'utf8', ); for (const name of referencedClasses) { + // 子串匹配会把 `.gdd-approval-card__header` 当成 `.gdd-approval-card` 的证据: + // 前缀类的规则被删光、只剩派生类时这里照样绿。要求类名后面不能再跟类名字符, + // 才是「存在这个类的精确 selector」。 + const exactSelector = new RegExp( + `\\.${name.replace(/[^\w-]/g, '\\$&')}(?![\\w-])`, + ); expect( - styles.includes(`.${name}`), - `styles.css 缺少 .${name} 的规则`, + exactSelector.test(styles), + `styles.css 缺少 .${name} 的精确 selector`, ).toBe(true); } // 正文弹层必须是浮层:backdrop 一旦丢掉 fixed 定位,整个 GDD 会内联平铺进 @@ -586,5 +603,12 @@ export function registerPlanGddApprovalTests() { expect(styles).toMatch( /\.game-workbench-chat\s+\.project-supervisor-conversation:has\([^)]*\.planning-lane-runtime-strip[^)]*\)\s*\{[^}]*display:\s*flex/s, ); + // `--with-card` 有两条规则,上面的存在性检查只要还剩一条就绿。承重的是这一条: + // 策划面是 `display: grid` + `overflow: hidden` 的外壳,审批卡的 `max-height` + // 和内滚要靠这个行模板才有边界。只删它、留下那条分隔线,表现是批准后的交付行 + // 连同路径和两个按钮被切在壳外。 + expect(styles).toMatch( + /\.game-workbench-chat\s+\.plan-gdd-surface--with-card\s*\{[^}]*grid-template-rows:\s*auto minmax\(0, 1fr\)/s, + ); }); }