补资源总览网格的声明级断言:钉住 width:100% 与 max-width 并存,防止退回单列
- 新增用例「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
This commit is contained in:
@@ -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 列里的标题栏带同款退化(只占 <h2> 宽度、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<HTMLElement>(
|
||||
'.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<HTMLElement>('.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-/);
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user