补「回到资源总览」命中区域的守卫断言
- 成因回填:分页态标题栏右端的「资源总览」(回到资源总览)按钮点不动,是常驻工具条带 `.game-resource-book-tools` 造成的——它是横跨整行、`z-index: 40` 的绝对定位层,正好盖在栏目标题栏那条带(`z-index: 30`)上,把按钮的命中区域整段吃掉。上一提交撤掉这条带后问题已消失,本次只补守卫,不改实现。 - project-development.suite.ts:新增声明级守卫,钉住浮层提示层(`.game-resource-book-notices`)与返回按钮所在标题栏的层级关系——提示层 `z-index` 高于标题栏(提示要看得见),因此容器必须 `pointer-events: none`、只有提示条自己 `pointer-events: auto`;提示条顶边 ≥ 标题栏 `min-height`,连视觉重叠都不发生;并反向断言常驻工具条带在 styles.css 与 index.tsx 里都已不存在(变异实测:把容器改回 `pointer-events: auto` 该用例立即红灯)。 - project-development.suite.ts:新增渲染级守卫——附件导入失败条在场(提示层挂载)时进入分页态,断言返回按钮不在提示层容器内、不是 disabled,点击后 `data-resource-book-view` 回到 `main` 且「资源总览」region 在场。 - 说明:jsdom 不做布局命中判定,像素级"是否被盖住"只能由声明级的层级/命中声明 + 真机复核确认;渲染级用例钉的是"提示层在场时返回入口仍然渲染、可点、并真的接在返回总览上"。
This commit is contained in:
@@ -1605,6 +1605,135 @@ export function registerProjectWorkbenchFoundationTests() {
|
||||
expect(searchTrigger().className).not.toContain('is-active');
|
||||
});
|
||||
|
||||
it('keeps the overview-return button out of the floating notice layer hit region', () => {
|
||||
const styles = readFileSync(
|
||||
resolve(process.cwd(), 'apps/ai-game-creator-shell/src/styles.css'),
|
||||
'utf8',
|
||||
);
|
||||
const tsxSource = readFileSync(
|
||||
resolve(
|
||||
process.cwd(),
|
||||
'apps/ai-game-creator-shell/src/view/project-development/index.tsx',
|
||||
),
|
||||
'utf8',
|
||||
);
|
||||
const sceneZIndex = styleNumber(
|
||||
styleRuleBody(styles, '\\.game-resource-book-scene'),
|
||||
'z-index',
|
||||
);
|
||||
const titlebar = styleRuleBody(
|
||||
styles,
|
||||
'\\.game-resource-book-scene-titlebar',
|
||||
);
|
||||
const titlebarZIndex = styleNumber(titlebar, 'z-index');
|
||||
const titlebarHeight = styleNumber(titlebar, 'min-height');
|
||||
expect(titlebarZIndex).toBeGreaterThan(sceneZIndex);
|
||||
expect(titlebarHeight).toBeGreaterThan(0);
|
||||
|
||||
// 提示层为了看得见必须浮在栏目标题栏之上——正因为如此,它必须把命中区域限死在提示条
|
||||
// 自己的盒子里:容器 `pointer-events: none` + 提示条 `auto`。分页态标题栏右端的
|
||||
// 「资源总览」(回到资源总览)就在这条带里,容器一旦吃点击,那颗按钮就点不动。
|
||||
const notices = styleRuleBody(styles, '\\.game-resource-book-notices');
|
||||
expect(styleNumber(notices, 'z-index')).toBeGreaterThan(titlebarZIndex);
|
||||
expect(notices).toMatch(/pointer-events:\s*none/u);
|
||||
expect(
|
||||
styleRuleBody(styles, '\\.game-resource-book-notices > \\*'),
|
||||
).toMatch(/pointer-events:\s*auto/u);
|
||||
// 提示条顶边在标题栏那条带以下:连视觉重叠都不会发生,不只是命中被放行。
|
||||
expect(styleNumber(notices, 'top')).toBeGreaterThanOrEqual(titlebarHeight);
|
||||
|
||||
// 成因本身也钉住:横跨整行、z-index 40 的常驻工具条带(当初为常驻搜索框引入)已经不在了,
|
||||
// 它盖在标题栏那条带上时,正好吃掉了返回按钮的命中区域。
|
||||
expect(styles).not.toContain('game-resource-book-tools');
|
||||
expect(tsxSource).not.toContain('game-resource-book-tools');
|
||||
});
|
||||
|
||||
it('returns to the overview from the paged titlebar while the notice layer is on screen', async () => {
|
||||
const manifest = createGameCreationAppManifest(
|
||||
'workbench-return-overview-hit',
|
||||
'回到总览命中区测试',
|
||||
);
|
||||
manifest.assets = [
|
||||
{
|
||||
id: 'return-overview-art',
|
||||
kind: 'character',
|
||||
mediaType: 'image/png',
|
||||
localPath: 'assets/return-hero.png',
|
||||
source: { kind: 'generated', taskId: 'art-asset-plan' },
|
||||
},
|
||||
];
|
||||
const invoke = vi.fn(
|
||||
async (command: string, args?: Record<string, unknown>) => {
|
||||
if (command === 'read_local_project_resource_graph') {
|
||||
return resourceGraphForInputs(args);
|
||||
}
|
||||
if (command === 'read_local_project_resource_canvas_layout') {
|
||||
return {
|
||||
schemaVersion: 'game-creator-resource-layout.v1',
|
||||
projectId: args?.expectedProjectId,
|
||||
mode: args?.mode,
|
||||
revision: 0,
|
||||
positions: [],
|
||||
updatedAt: 0,
|
||||
};
|
||||
}
|
||||
throw new Error(`unexpected invoke ${command}`);
|
||||
},
|
||||
);
|
||||
window.__TAURI__ = { core: { invoke } };
|
||||
|
||||
render(
|
||||
React.createElement(ProjectDevelopmentView, {
|
||||
projectName: manifest.name,
|
||||
projectPath: '/tmp/workbench-return-overview-hit',
|
||||
manifest,
|
||||
attachments: [
|
||||
{
|
||||
fileName: 'broken.txt',
|
||||
mediaType: 'text/plain',
|
||||
status: 'failed' as const,
|
||||
error: '附件导入失败',
|
||||
},
|
||||
],
|
||||
recentRunStatus: null,
|
||||
recentRunStopReason: null,
|
||||
supervisor: React.createElement('div', null, '项目总控'),
|
||||
onHomeOpen: vi.fn(),
|
||||
onProjectsOpen: vi.fn(),
|
||||
}),
|
||||
);
|
||||
|
||||
// 提示层在场:附件导入失败条渲染在这层浮层里,而这层浮层不属于画本场景。
|
||||
const notices = document.querySelector('.game-resource-book-notices');
|
||||
expect(notices).not.toBeNull();
|
||||
expect(notices?.querySelector('.game-attachment-errors')).not.toBeNull();
|
||||
expect(notices?.closest('.game-resource-book-scene')).toBeNull();
|
||||
expect(notices?.querySelector('.game-resource-book-scene')).toBeNull();
|
||||
|
||||
await openResourceBookCategory('角色与对象');
|
||||
const activeTitlebar = document.querySelector<HTMLElement>(
|
||||
'.game-resource-book-scene-titlebar.is-active',
|
||||
);
|
||||
expect(activeTitlebar).not.toBeNull();
|
||||
const returnButton = within(activeTitlebar as HTMLElement).getByRole(
|
||||
'button',
|
||||
{ name: '收起资源' },
|
||||
) as HTMLButtonElement;
|
||||
// 按钮可点:不是 disabled,祖先链上也没有提示层那类跨整行的浮层容器。
|
||||
expect(returnButton.disabled).toBe(false);
|
||||
expect(returnButton.closest('.game-resource-book-notices')).toBeNull();
|
||||
|
||||
// jsdom 不做布局命中的判定,能钉住的是"层级关系 + 按钮真的接在返回总览上":
|
||||
// 默认运行时不带 aria-disabled、点击真的切回总览态。
|
||||
fireEvent.click(returnButton);
|
||||
expect(
|
||||
document
|
||||
.querySelector('[data-resource-book-view]')
|
||||
?.getAttribute('data-resource-book-view'),
|
||||
).toBe('main');
|
||||
expect(screen.getByRole('region', { name: '资源总览' })).not.toBeNull();
|
||||
});
|
||||
|
||||
it('paints the marquee selection box with the scene token root and non-empty geometry', async () => {
|
||||
const manifest = createGameCreationAppManifest(
|
||||
'workbench-marquee-colour',
|
||||
|
||||
Reference in New Issue
Block a user