Files
openless/openless-all/app/scripts/windows-ui-config.test.mjs
T
sim 3ff3df1626 fix(merge): 落实合并后的契约与目录对齐
- build-mac.sh 移除 hdiutil DMG 兜底:bundle contract 测试显式禁止手搓 DMG
  绕过签名/公证链路;DMG 缺失即失败(保留 qwen3-asr-rs 剪枝与 BUILD_START_TS
  新鲜度校验)
- 红绿灯 y=26 契约:tao 几何下视觉顶距≈y-14,x==y 并不相等(实测左 21.5px)
- 胶囊几何契约同步 typeless 206×57(原 460×128 的 1/5 面积)
- LLM_LABELS 补 tencentTokenHub 静态目录项(ASR_LABELS 已含 tencent-cloud)
2026-09-10 16:54:34 +08:00

252 lines
9.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { readFile } from 'node:fs/promises';
function assertEqual(actual, expected, name) {
if (actual !== expected) {
throw new Error(`${name}: expected ${expected}, got ${actual}`);
}
}
function assertMatch(source, pattern, name) {
if (!pattern.test(source)) {
throw new Error(`${name}: pattern ${pattern} not found`);
}
}
const raw = await readFile(new URL('../src-tauri/tauri.conf.json', import.meta.url), 'utf-8');
const config = JSON.parse(raw);
const capsuleWindow = config.app.windows.find((window) => window.label === 'capsule');
const mainWindow = config.app.windows.find((window) => window.label === 'main');
const libRs = await readFile(new URL('../src-tauri/src/lib.rs', import.meta.url), 'utf-8');
// 契约校验编译进二进制的胶囊子系统并集;原生窗口操作属于显式 Tauri Host。
const coordinatorRs =
(await readFile(new URL('../src-tauri/src/coordinator.rs', import.meta.url), 'utf-8')) +
'\n' +
(await readFile(
new URL('../src-tauri/src/coordinator/capsule_focus.rs', import.meta.url),
'utf-8',
)) +
'\n' +
(await readFile(new URL('../src-tauri/src/tauri_coordinator_host.rs', import.meta.url), 'utf-8'));
const capsuleTsx = await readFile(
new URL('../src/components/Capsule.tsx', import.meta.url),
'utf-8',
);
const capsuleLayoutTs = await readFile(
new URL('../src/lib/capsuleLayout.ts', import.meta.url),
'utf-8',
);
const windowChromeTsx = await readFile(
new URL('../src/components/WindowChrome.tsx', import.meta.url),
'utf-8',
);
const floatingShellTsx = await readFile(
new URL('../src/components/FloatingShell.tsx', import.meta.url),
'utf-8',
);
const themeModeTs = await readFile(new URL('../src/lib/themeMode.ts', import.meta.url), 'utf-8');
const platformTs = await readFile(new URL('../src/lib/platform.ts', import.meta.url), 'utf-8');
if (!capsuleWindow) {
throw new Error('capsule window config missing');
}
if (!mainWindow) {
throw new Error('main window config missing');
}
assertEqual(capsuleWindow.width, 460, 'windows capsule config keeps the shared bootstrap width');
assertEqual(capsuleWindow.height, 180, 'windows capsule config keeps the shared bootstrap height');
assertEqual(capsuleWindow.transparent, true, 'capsule window should keep transparent visuals');
assertEqual(
capsuleWindow.alwaysOnTop,
true,
'capsule window should stay above the focused app while recording',
);
assertEqual(mainWindow.decorations, true, 'windows main window should keep native decorations');
assertEqual(
mainWindow.visible,
false,
'windows main window should stay hidden until the intended first show point',
);
assertMatch(
libRs,
/fn apply_windows_caption_theme[\s\S]*?DWMWA_USE_IMMERSIVE_DARK_MODE[\s\S]*?DWMWA_CAPTION_COLOR[\s\S]*?DWMWA_TEXT_COLOR[\s\S]*?DWMWA_BORDER_COLOR/,
'windows runtime should sync immersive dark mode and caption/text/border colors',
);
assertMatch(
libRs,
/#\[tauri::command\][\s\S]*?fn set_windows_caption_theme/,
'windows caption theme should be exposed as a Tauri command',
);
assertMatch(
themeModeTs,
/export function applyThemeMode[\s\S]*?syncWindowsCaptionTheme/,
'applyThemeMode should sync Windows native caption theme',
);
assertMatch(
platformTs,
/export async function syncWindowsCaptionTheme[\s\S]*?set_windows_caption_theme/,
'platform IPC wrapper should invoke set_windows_caption_theme',
);
assertMatch(
coordinatorRs,
/#\[cfg\(target_os = "macos"\)\][\s\S]*?orderFrontRegardless/,
'macOS capsule should show without taking the key window',
);
const tokensCss = await readFile(new URL('../src/styles/tokens.css', import.meta.url), 'utf-8');
if (!/os === 'win' \|\| os === 'android' \? 0 : 14/.test(windowChromeTsx)) {
throw new Error(
'windows main shell should rely on native decorations instead of a frameless chrome shell',
);
}
assertMatch(
windowChromeTsx,
/\/\/ Windows: decorations:true 时外层不画圆角/,
'windows WindowChrome should defer chrome to native decorations',
);
assertMatch(
windowChromeTsx,
/const MAC_TITLEBAR_HEIGHT = 44;/,
'macOS drag region should reserve the native traffic-light area',
);
assertEqual(mainWindow.trafficLightPosition.x, 16, 'traffic lights should have a 16px left inset');
// tao 的 inset_traffic_lights 里 y 只缩放标题栏容器(斜率 1),视觉顶距 ≈ y-14;
// 左 16 时实测左距 21.5px,y=26 才让顶距与之相等(x==y 反而不等)。
assertEqual(mainWindow.trafficLightPosition.y, 26, 'traffic lights should have an equal visual top inset');
assertEqual(mainWindow.width, 1300, 'main window should use the reviewed default width');
assertEqual(mainWindow.height, 835, 'main window should use the reviewed default height');
assertEqual(mainWindow.resizable, true, 'users should still be able to resize the main window');
assertMatch(
libRs,
/show_main_window[\s\S]*?set_focus\(\)/,
'macOS main window should rely on native traffic lights instead of manually moving standardWindowButton frames',
);
if (/standardWindowButton|setFrameOrigin: origin|tune_macos_main_window_controls/.test(libRs)) {
throw new Error(
'macOS traffic lights should not be manually repositioned; keep native AppKit button frames visible',
);
}
if (!/className=\"ol-linux-close-btn\"/.test(windowChromeTsx)) {
throw new Error('linux titlebar should keep the close button treatment');
}
assertMatch(
tokensCss,
/--ol-motion-spring:[\s\S]*?--ol-motion-soft:[\s\S]*?--ol-motion-quick:/,
'shared motion tokens should drive shell animations and transitions',
);
assertMatch(
windowChromeTsx,
/function LinuxTitlebar\(\)/,
'linux should keep the custom ol-linux-titlebar shell',
);
assertMatch(
floatingShellTsx,
/className="ol-console-main"[\s\S]*?borderRadius:\s*0,[\s\S]*?boxShadow:\s*'none'/,
'main content should keep the intentional flush shell treatment',
);
assertMatch(
coordinatorRs,
/let visible = !matches!\(state,\s*CapsuleState::Idle\);/,
'capsule should stay visible until the unified idle hide path runs',
);
assertMatch(
coordinatorRs,
/fn hide_capsule_window_if_present\(\)/,
'windows capsule lifecycle should include an explicit native hide helper',
);
assertMatch(
coordinatorRs,
/ShowWindow\(hwnd, SW_HIDE\)/,
'windows capsule hide helper should force the native window hidden',
);
assertMatch(
coordinatorRs,
/SetWindowPos\([\s\S]*?HWND_NOTOPMOST[\s\S]*?SWP_HIDEWINDOW/m,
'windows capsule hide helper should drop topmost participation when inactive',
);
if (
!/export function getCapsuleHostMetrics\(\s*os: OS,\s*translationActive: boolean,\s*style: CapsuleStyle = 'siri',?\s*\): CapsuleHostMetrics/.test(
capsuleLayoutTs,
)
) {
throw new Error(
'capsule layout should define explicit host metrics separate from the visible pill metrics',
);
}
assertMatch(
capsuleLayoutTs,
// The 1.3.14 voice-orb shell replaced the legacy 196px Windows pill and its
// 12px host inset. Keep the frontend and native stage contracts in sync.
/const VOICE_ORB_STAGE_WIDTH = 460;[\s\S]*?const VOICE_ORB_STAGE_HEIGHT = 180;/,
'capsule layout should keep the shared 460x180 voice-orb stage',
);
assertMatch(
capsuleLayoutTs,
/const stage = getCapsulePillMetrics\(os\);[\s\S]*?width: stage\.width,[\s\S]*?height: style === 'siri' \? stage\.height : style === 'classic' \? 100 : 128,[\s\S]*?horizontalInset: 0,[\s\S]*?bottomInset: style === 'siri' \? 0 : 16,[\s\S]*?badgeGap: 8,[\s\S]*?boxSizing: 'border-box'/,
'capsule host metrics should preserve Siri and reserve compact surfaces for Classic and Typeless',
);
if (
!/const hostMetrics = getCapsuleHostMetrics\(os,\s*translation,\s*capsuleStyle\);/.test(
capsuleTsx,
)
) {
throw new Error('capsule should derive host metrics from the shared layout contract');
}
if (
!/return\s*\(\s*<div\s*style=\{\{[\s\S]*?width:\s*'100%',[\s\S]*?height:\s*'100%',[\s\S]*?position:\s*'relative',[\s\S]*?display:\s*'flex',[\s\S]*?alignItems:\s*capsuleStyle === 'siri' \? 'center' : 'flex-end',[\s\S]*?justifyContent:\s*'center',[\s\S]*?paddingLeft:\s*hostMetrics\.horizontalInset,[\s\S]*?paddingRight:\s*hostMetrics\.horizontalInset,[\s\S]*?\}\}/.test(
capsuleTsx,
)
) {
throw new Error('capsule host should center the pill within the shared layout contract');
}
if (
!/paddingLeft:\s*hostMetrics\.horizontalInset,/.test(capsuleTsx) ||
!/paddingRight:\s*hostMetrics\.horizontalInset,/.test(capsuleTsx)
) {
throw new Error('capsule host should consume the shared horizontal inset contract');
}
if (!/paddingBottom:\s*hostMetrics\.bottomInset/.test(capsuleTsx)) {
throw new Error('all capsule hosts should respect their style-specific bottom inset');
}
if (!/const badgeBottom = Math\.round\(metrics\.height \* 0\.73\);/.test(capsuleTsx)) {
throw new Error('translation badge should anchor proportionally within the voice-orb stage');
}
assertMatch(
libRs,
/fn capsule_window_bounds_for_style\(style: types::CapsuleStyle\)[\s\S]*?types::CapsuleStyle::Typeless => 206\.0,[\s\S]*?CapsuleStyle::Siri \| types::CapsuleStyle::Classic => 460\.0,[\s\S]*?types::CapsuleStyle::Siri => 180\.0,[\s\S]*?types::CapsuleStyle::Classic => 100\.0,[\s\S]*?types::CapsuleStyle::Typeless => 57\.0,[\s\S]*?bottom_inset: 0\.0,/,
'native capsule bounds should match the frontend dimensions (typeless = 1/5 area of the original 460x128)',
);
assertMatch(
libRs,
/fn capsule_visual_height\(_translation_active: bool\) -> f64[\s\S]*?140\.0/,
'runtime capsule visual anchor should preserve the intentional 140px height',
);
if (!/window\.set_size\(LogicalSize::new\(bounds\.width, bounds\.height\)\)\?/.test(libRs)) {
throw new Error('capsule positioning should resync runtime size with the computed layout');
}
if (!/let _ = window\.hide\(\);/.test(coordinatorRs)) {
throw new Error('capsule should be hidden once it leaves active states');
}