6dde321bd1
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m5s
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m13s
Project CI / Frontend tests (pull_request) Successful in 3m15s
Project CI / Repository checks (pull_request) Failing after 15s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m56s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 7m55s
Project CI / AI game creator shell web tests (pull_request) Failing after 3m21s
Project CI / Native shell tests (pull_request) Successful in 8m53s
- executeRunLocal 的三条失败路径(缺 Tauri / 缺项目 / 启动预览报错)也改走 onRunNotice 失败色提示,对话区不再出现过程行;随之删除已无调用方的 announceProjectChatMessage 与不再有意义的 announceToChat 参数 - RunNoticeToast 的失败提示延长到 6 秒(成功仍 2.6 秒),避免错误一闪而过 - 版本入口在 UI 编辑器壳里不渲染;版本名改由一层 span 承载省略号,长版本名不再被硬裁 - 视图拿不到 onNotice 时把「在浏览器打开失败」写进 console,不再静默吞掉 - check-native-shells 增加「运行页视图里 openUrl( 只有一个调用点」的判据,堵住自动开浏览器的口子 - 新增外壳级接线用例 runNoticeShellWiring;previewActivation 补「启动失败走失败色提示且不写对话区」;runNoticeToast 补失败时长;样式守卫补省略号 - decision-log 收敛到最终口径(成功与失败都出对话区、失败留 6 秒、编辑器不挂版本入口)
75 lines
3.4 KiB
TypeScript
75 lines
3.4 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { readFileSync } from 'node:fs';
|
|
|
|
import { describe, expect, test } from 'vitest';
|
|
|
|
import { repoPath } from './repoPath';
|
|
import {
|
|
declaration,
|
|
parseStyleSheet,
|
|
resolveDeclarations,
|
|
} from './styleCascade';
|
|
|
|
/**
|
|
* 运行页顶栏动作区的声明级断言。
|
|
*
|
|
* jsdom 不加载全局样式表,所以这里按仓库既有做法(`styleCascade`)解析真实生效的声明:
|
|
* 「版本入口与「在浏览器打开」跟其他动作按钮同一套皮」只由样式决定,用例必须钉在声明上,
|
|
* 否则下一次改动给版本入口单开一套 border / background / hover 时没有任何东西会红。
|
|
*/
|
|
const GLOBAL_CSS_PATH = repoPath('apps/ai-game-creator-shell/src/styles.css');
|
|
const rules = parseStyleSheet(readFileSync(GLOBAL_CSS_PATH, 'utf8'));
|
|
// 同一个求值器限制:全局表里也有 `prefers-reduced-motion` 档,求值时先排除掉。
|
|
const widthRules = rules.filter(
|
|
(rule) => !(rule.media ?? '').includes('prefers-reduced-motion'),
|
|
);
|
|
|
|
function resolved(selectors: readonly string[]): Map<string, string> {
|
|
return resolveDeclarations(widthRules, selectors, 1280);
|
|
}
|
|
|
|
function hasRule(selector: string): boolean {
|
|
return widthRules.some((rule) => rule.selectors.includes(selector));
|
|
}
|
|
|
|
describe('运行页顶栏动作区样式', () => {
|
|
test('运行画面回到两行,页面里不再有预览地址状态行', () => {
|
|
const surface = resolved(['.game-run-surface']);
|
|
expect(declaration(surface, 'grid-template-rows')).toBe(
|
|
'minmax(300px, 1fr) auto',
|
|
);
|
|
// 状态行与那行小字整体退役:地址不再以文字常驻在页面上。
|
|
expect(hasRule('.game-run-status-bar')).toBe(false);
|
|
expect(hasRule('.game-run-status-hint')).toBe(false);
|
|
});
|
|
|
|
test('版本入口住进顶栏动作区,皮由那一组的基础规则给', () => {
|
|
// 动作区的按钮共用一套基础外观(这一组是「在浏览器打开」与版本入口的共同来源)。
|
|
const actionsButton = resolved(['.game-workbench-view-actions button']);
|
|
expect(declaration(actionsButton, 'min-height')).toBe('30px');
|
|
expect(declaration(actionsButton, 'border-radius')).toBe('999px');
|
|
expect(declaration(actionsButton, 'font-size')).toBe('12px');
|
|
expect(declaration(actionsButton, 'font-weight')).toBe('700');
|
|
|
|
// 版本入口只补「版本名可能很长」这一件事,不再自带边框 / 底色 / 文字色 / hover。
|
|
const trigger = resolved(['.game-run-version-trigger']);
|
|
expect(trigger.has('border')).toBe(false);
|
|
expect(trigger.has('background')).toBe(false);
|
|
expect(trigger.has('color')).toBe(false);
|
|
expect(declaration(trigger, 'min-width')).toBe('0');
|
|
// 省略号要真的生效:按钮是 flex 容器,文本必须挂在自带 overflow 的 span 上。
|
|
const label = resolved(['.game-run-version-trigger-label']);
|
|
expect(declaration(label, 'overflow')).toBe('hidden');
|
|
expect(declaration(label, 'text-overflow')).toBe('ellipsis');
|
|
expect(declaration(label, 'white-space')).toBe('nowrap');
|
|
expect(hasRule('.game-run-version-trigger:hover')).toBe(false);
|
|
expect(hasRule('.game-run-version-trigger:focus-visible')).toBe(false);
|
|
|
|
// 版本入口不再绝对定位压在游戏画面上。
|
|
const picker = resolved(['.game-run-version-picker']);
|
|
expect(picker.has('position')).toBe(false);
|
|
expect(picker.has('top')).toBe(false);
|
|
expect(picker.has('right')).toBe(false);
|
|
});
|
|
});
|