// @vitest-environment jsdom import { readFileSync } from 'node:fs'; import { describe, expect, it } from 'vitest'; import { repoPath } from './repoPath'; import { parseStyleSheet } from './styleCascade'; const STYLES_PATH = repoPath('apps/ai-game-creator-shell/src/styles.css'); /** * 全屏弹层清单:每一层都必须从自绘标题栏下方开始。 * * 标题栏是窗口边框,不是弹层内容 —— 只要有一个全屏遮罩盖住它,弹窗打开时 * 「最小化 / 最大化 / 关闭」就会被挡住。焦点陷阱那一半的问题见 * `themedModal.test.tsx` 与 `WindowChrome.test.tsx`;新增全屏弹层时把类名加进这份清单。 */ const WINDOW_CHROME_SAFE_OVERLAYS = [ // ThemedModal 与共享弹层的通用遮罩:top 由这条规则统一抬到标题栏下方。 '.fixed.inset-0', '.app-update-overlay', '.game-publish-progress-overlay', '.launcher-dialog-backdrop', '.settings-overlay', '.game-approval-backdrop', '.project-chat-settings-backdrop', ] as const; function declarationsForSelector(css: string, selector: string) { const merged = new Map(); for (const rule of parseStyleSheet(css)) { if (!rule.selectors.includes(selector)) { continue; } for (const [property, value] of rule.declarations) { merged.set(property, value); } } return merged; } describe('窗口标题栏与全屏弹层的层叠约定', () => { const css = readFileSync(STYLES_PATH, 'utf8'); it.each(WINDOW_CHROME_SAFE_OVERLAYS)('%s 从标题栏下方开始', (selector) => { const declarations = declarationsForSelector(css, selector); expect( declarations.get('top'), `${selector} 必须声明 top: var(--window-chrome-height)`, ).toBe('var(--window-chrome-height)'); }); });