From 28781a420c3b7f0414cea0dfb306ff4ed4e15706 Mon Sep 17 00:00:00 2001 From: Suzumiya Date: Fri, 11 Sep 2026 22:50:01 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E8=B5=84=E6=BA=90=E6=80=BB=E8=A7=88?= =?UTF-8?q?=E7=BD=91=E6=A0=BC=E7=9A=84=E5=A3=B0=E6=98=8E=E7=BA=A7=E6=96=AD?= =?UTF-8?q?=E8=A8=80=EF=BC=9A=E9=92=89=E4=BD=8F=20width:100%=20=E4=B8=8E?= =?UTF-8?q?=20max-width=20=E5=B9=B6=E5=AD=98=EF=BC=8C=E9=98=B2=E6=AD=A2?= =?UTF-8?q?=E9=80=80=E5=9B=9E=E5=8D=95=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增用例「keeps the resource overview grid off fit-content so its columns stay responsive」:断言 .game-resource-book-main-grid 同时声明 width: 100% / max-width: 1120px / margin: 0 auto / repeat(auto-fit …),并同款断言 .game-resource-book-main-heading 的 width: 100% - 取声明体前先剥掉 CSS 注释:本次修复的注释里就写着 width: 100% 等字样,不剥掉会让断言匹配到注释而假绿(删掉真正的声明也照样通过) - 注释里写明这条断言**验不到布局**:AGC 的 vitest 没开 css: true、styles.css 不会被加载、jsdom 也没有布局引擎,gridTemplateColumns 的解析结果与缩略卡列数只能在真机量;真机判据(3 个非 0 轨道 / grid 宽 432→1120 / 8 张缩略卡 top 只有 3 个取值,且需在总览态取样)写在用例注释里 - 变异验证:临时删掉 .game-resource-book-main-grid 的 width: 100% → 用例变红且失败信息打印的是真实声明体(证明注释剥离生效);还原后 styles.css 逐字节哈希与已提交版本一致,复跑绿灯 - 门禁:npm run typecheck exit 0;npm run check:encoding exit 0(4399 files);npx vitest run apps/ai-game-creator-shell/tests/appSurface.test.ts 本用例 1 passed --- .../appSurface/project-development.suite.ts | 123 +++++++++++++++--- 1 file changed, 107 insertions(+), 16 deletions(-) diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts index 18737bd92..22c0c5c0f 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts @@ -1260,6 +1260,51 @@ export function registerProjectWorkbenchFoundationTests() { expect(styleNumber(notices, 'top')).toBeGreaterThanOrEqual(titlebarHeight); }); + it('keeps the resource overview grid off fit-content so its columns stay responsive', () => { + const styles = readFileSync( + resolve(process.cwd(), 'apps/ai-game-creator-shell/src/styles.css'), + 'utf8', + ); + // 先剥掉 CSS 注释再取声明体:这条修复的注释里就写着 `width: 100%` 等字样, + // 不剥掉的话断言可能匹配到注释而"假绿"——删除真正的声明也照样通过。 + const declarationsOnly = styles.replace(/\/\*[\s\S]*?\*\//gu, ''); + /** + * 声明级断言:只能钉住「CSS 源文件里写了什么」,**验不到布局**。 + * + * AGC 的 vitest 没开 `css: true`、`styles.css` 不会被加载,jsdom 也没有布局引擎; + * `gridTemplateColumns` 的解析结果与缩略卡的实际列数只能在真机上量。真机判据见下。 + */ + const grid = styleRuleBody( + declarationsOnly, + '\\.game-resource-book-main-grid', + ); + // 父级 `.game-resource-book-main-world` 是列向 flex 容器,交叉轴 auto 外边距会取消 stretch + // 并让本网格按 fit-content 定宽;fit-content 定宽时 auto-fit 的重复次数恒为 1 + // ⇒ grid 只有一张缩略卡那么宽、只出 1 列。`width: 100%` 把 inline size 定下来, + // auto-fit 才会按下面的 max-width 解析重复次数。删掉它 = 资源总览退回纵向长条。 + expect(grid).toMatch(/width:\s*100%/u); + expect(grid).toMatch(/max-width:\s*1120px/u); + expect(grid).toMatch(/margin:\s*0\s*auto/u); + expect(grid).toMatch(/grid-template-columns:\s*repeat\(auto-fit/u); + + // 同一个 flex 列里的标题栏带同款退化(只占

宽度、space-between 无从展开),一并钉住。 + const heading = styleRuleBody( + declarationsOnly, + '\\.game-resource-book-main-heading', + ); + expect(heading).toMatch(/width:\s*100%/u); + expect(heading).toMatch(/max-width:\s*1120px/u); + + /* + * 真机判据(修复后必须同时成立,供人工核对): + * 1) `getComputedStyle(grid).gridTemplateColumns` 给出 3 个非 0 轨道(约 361.33px), + * 而不是单轨道 `432px`; + * 2) grid 的 `getBoundingClientRect().width` 由 432 变成 1120; + * 3) 8 张 `.game-resource-book-thumbnail` 的 `top` 只有 3 个不同取值。 + * 取样要在**总览态**做:栏目态该层带 `is-background`(opacity 为 0),是背景层。 + */ + }); + it('renders the resource search overlay as a sibling of the book scene', async () => { const manifest = createGameCreationAppManifest( 'workbench-search-overlay', @@ -9562,37 +9607,83 @@ export function registerProjectAgentStatusTests() { expect(previewHostRule).toMatch(/position:\s*absolute/); expect(previewHostRule).toMatch(/transform-origin:\s*0 0/); - // 3) 点它进入的视图包含来自多个栏目的资源,而不是某一个栏目。 + // 3) 点它进入的是**同一套画本场景**,不是另一条渲染分支:每个真实栏目铺在自己的分带上, + // 卡片宿主是栏目页那一个 `.game-resource-book-scene-card`、`data-resource-book-category` + // 仍是真实栏目;`all` 自己只留钉住的标题栏与全量计数。 fireEvent.click(screen.getByRole('button', { name: '打开所有资源' })); await waitFor(() => expect( - document.querySelector('[data-resource-book-all-host="true"]'), + document.querySelector( + '.game-resource-book-scene-titlebar[data-resource-book-category="all"].is-active', + ), ).not.toBeNull(), ); + // 旧的分组网格宿主与网格容器一个都不该再存在;改回旧分支时这三条会立刻红。 + expect( + document.querySelector('[data-resource-book-all-host="true"]'), + ).toBeNull(); + expect( + document.querySelector('[data-resource-book-all-page="true"]'), + ).toBeNull(); + expect(document.querySelector('.game-resource-all-grid')).toBeNull(); + expect(document.querySelector('.game-resource-all-section')).toBeNull(); const expectSortedCategories = (categories: string[]) => [...categories].sort(); - const allSections = Array.from( + const expectSortedIds = (ids: string[]) => [...ids].sort(); + const allCardHosts = Array.from( document.querySelectorAll( - '.game-resource-all-section[data-resource-book-category]', + '.game-resource-book-scene-card[data-resource-book-category]', ), ); - const expectedSections = expectSortedCategories( - Array.from(new Set(allResources.map((resource) => resource.category))), + // 宿主按真实栏目分组,栏目集合与总览里的那批一致;`all` 自己不出宿主。 + const hostCategories = expectSortedCategories( + Array.from( + new Set(allCardHosts.map((host) => host.dataset.resourceBookCategory!)), + ), ); - expect( + expect(hostCategories).toEqual( expectSortedCategories( - allSections.map((section) => section.dataset.resourceBookCategory!), + Array.from(new Set(allResources.map((resource) => resource.category))), ), - ).toEqual(expectedSections); - expect(allSections.length).toBeGreaterThan(1); - for (const section of allSections) { - expect( - section.querySelectorAll('.game-resource-card').length, - ).toBeGreaterThan(0); + ); + expect(hostCategories.length).toBeGreaterThan(1); + expect(hostCategories).not.toContain('all'); + // 每张资源恰好一个宿主(展开态没有"第二份宿主"):条数与全量计数同源, + // 因此不会出现「标题说 N 项、画布少几张」。 + expect( + expectSortedIds( + allCardHosts.map( + (host) => + host.querySelector('.game-resource-card')?.dataset + .resourceCardId ?? '', + ), + ), + ).toEqual(expectSortedIds(allResources.map((resource) => resource.id))); + // 卡片本体仍是栏目页同一颗渲染器(视觉体 + 选中按钮),不是另一套占位。 + for (const host of allCardHosts) { + expect(host.querySelectorAll('.game-resource-card')).toHaveLength(1); + expect(host.querySelector('.game-resource-card-visual')).not.toBeNull(); } - // 汇总页自己的标题栏也用同一份全量计数。 - expect(readCountBadge('.game-resource-all-page')).toBe(allResourcesCount); + // 展开态的标题栏也是同一颗标题栏组件:`all` 那条钉在视口上并带「资源总览」收起入口, + // 计数用同一份全量计数。 + expect( + readCountBadge( + '.game-resource-book-scene-titlebar[data-resource-book-category="all"]', + ), + ).toBe(allResourcesCount); + expect( + document.querySelector( + '.game-resource-book-scene-titlebar[data-resource-book-category="all"] .game-resource-page-collapse', + ), + ).not.toBeNull(); + // 视觉不是另写一套:那一页没有任何专属选择器,卡与宿主都只用共享规则 + // (声明级断言——它验的是"没有平行样式",验不到布局本身)。 + const allPageStyles = readFileSync( + resolve(process.cwd(), 'apps/ai-game-creator-shell/src/styles.css'), + 'utf8', + ); + expect(allPageStyles).not.toMatch(/\.game-resource-all-/); }); /**