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 04da6b4d8..b5040611a 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 @@ -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) => { + 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( + '.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',