优化桌面端标签导航与后台交互
Client Quality Gates / Shared client and Web (push) Has been cancelled
Client Quality Gates / macOS Desktop (push) Has been cancelled

This commit is contained in:
Cheng Zhou
2026-07-09 08:56:13 +08:00
parent 76f2d9f22a
commit 0aff10fe0d
17 changed files with 1232 additions and 207 deletions
+71 -3
View File
@@ -8,6 +8,7 @@ const readDesktopLayoutSource = () => readFileSync(resolve(__dirname, "./Desktop
const readWebLayoutSource = () => readFileSync(resolve(__dirname, "./WebLayout.vue"), "utf8");
const readNavigationSource = () => readFileSync(resolve(__dirname, "./layout/navigation.ts"), "utf8");
const readTauriConfigSource = () => readFileSync(resolve(__dirname, "../../src-tauri/tauri.conf.json"), "utf8");
const readTauriLibSource = () => readFileSync(resolve(__dirname, "../../src-tauri/src/lib.rs"), "utf8");
const readDesktopPreferencesSource = () => readFileSync(resolve(__dirname, "../views/DesktopPreferences.vue"), "utf8");
const readDesktopServerSettingsSource = () => readFileSync(resolve(__dirname, "../views/DesktopServerSettings.vue"), "utf8");
const readDesktopUpdateManagerSource = () => readFileSync(resolve(__dirname, "../session/desktopUpdateManager.ts"), "utf8");
@@ -92,7 +93,7 @@ describe("desktop layout shell", () => {
expect(source).toContain("const adminDesktopNavigationItems");
expect(source).toContain("const projectDesktopNavigationItems");
expect(source).toContain('id: "desktop:refresh"');
expect(source).toContain('shortcut: "⌘R"');
expect(source).toContain("shortcut: formatDesktopShortcut(desktopShortcutPreferences.value.refresh)");
expect(source).toContain('command === "ctms.desktop.back"');
expect(source).toContain('command === "ctms.desktop.forward"');
expect(source).not.toContain("const root = study.currentStudy?.name || TEXT.menu.currentProject");
@@ -127,12 +128,13 @@ describe("desktop layout shell", () => {
expect(layout).toContain('command="projectEntry"');
expect(layout).toContain("projectEntryMenuLabel");
expect(layout).toContain("openDesktopProjectEntry");
expect(layout).toContain("const showSidebarProjectChooser = computed(() => !study.currentStudy && !isAdminContext.value);");
expect(layout).toContain("padding: 10px 8px 24px;");
expect(layout).toContain("margin-top: 12px;");
expect(layout).toContain("font-size: 14px;");
expect(sidebarHeadTemplate).toContain('<div class="sidebar-title-row">');
expect(sidebarHeadTemplate.indexOf("<h1>{{ TEXT.common.appName }}</h1>")).toBeLessThan(
sidebarHeadTemplate.indexOf('<button v-if="!study.currentStudy" class="study-switcher-trigger empty"'),
sidebarHeadTemplate.indexOf('<button v-if="showSidebarProjectChooser" class="study-switcher-trigger empty"'),
);
expect(sidebarHeadTemplate).not.toContain("study-context-badge");
expect(layout).not.toContain(".study-context-badge");
@@ -158,6 +160,7 @@ describe("desktop layout shell", () => {
expect(preferences).toContain("scrollbar-gutter: stable;");
expect(preferences).toContain('{ id: "connection", label: "连接"');
expect(preferences).toContain('{ id: "appearance", label: "外观"');
expect(preferences).toContain('{ id: "shortcuts", label: "快捷键"');
expect(preferences).toContain('{ id: "notifications", label: "通知"');
expect(preferences).toContain('{ id: "updates", label: "更新"');
expect(preferences).toContain('{ id: "diagnostics", label: "诊断信息"');
@@ -222,7 +225,7 @@ describe("desktop layout shell", () => {
it("keeps workspace tabs stable and draggable like browser tabs", () => {
const source = readDesktopLayoutSource();
const tabsStart = source.indexOf('<div class="workspace-tabs"');
const tabsStart = source.indexOf('<div v-if="workspaceTabs.length" ref="workspaceTabsElement" class="workspace-tabs">');
const tabsEnd = source.indexOf('<main class="desktop-content"', tabsStart);
const tabsTemplate = source.slice(tabsStart, tabsEnd);
@@ -248,11 +251,72 @@ describe("desktop layout shell", () => {
expect(source).toContain("const workspaceTabMenu = ref<");
expect(source).toContain("const closeOtherWorkspaceTabs = (path: string) => {");
expect(source).toContain("const restoreLastClosedWorkspaceTab = () => {");
expect(source).toContain("const workspaceTabFallbackPath = computed(() => {");
expect(source).toContain('if (isAdminContext.value) return adminDesktopNavigationItems.value[0]?.path || "/admin/projects";');
expect(source).toContain("router.push(workspaceTabFallbackPath.value);");
expect(source).not.toContain('router.push(study.currentStudy ? "/project/overview" : "/desktop/project-entry");');
expect(source).toContain('id: `workspace-tab:${item.path}`');
expect(source).toContain('title: `切换标签:${item.title}`');
expect(source).toContain('title: "重新打开最近关闭标签"');
});
it("keeps shortcut navigation history isolated to the active workspace tab", () => {
const source = readDesktopLayoutSource();
expect(source).toContain("const workspaceTabHistories = ref<Record<string, WorkspaceTabHistory>>({});");
expect(source).toContain("recordWorkspaceTabRoute(current.path, fullPath);");
expect(source).toContain("replaceWorkspaceTabRoute(current.path, fullPath);");
expect(source).toContain("currentWorkspaceTabPath(workspaceTabHistories.value[tabPath], tabPath)");
expect(source).toContain("moveWorkspaceTabHistory(workspaceTabHistories.value[tabPath], offset)");
expect(source).toContain("navigateBack: () => navigateCurrentWorkspaceTabHistory(-1)");
expect(source).toContain("navigateForward: () => navigateCurrentWorkspaceTabHistory(1)");
expect(source).toContain('id: "desktop:tab-back"');
expect(source).toContain('id: "desktop:tab-forward"');
expect(source).not.toContain("navigateBack: () => router.back()");
expect(source).not.toContain("navigateForward: () => router.forward()");
});
it("allows desktop shortcuts to be customized from system preferences", () => {
const preferences = readDesktopPreferencesSource();
const tauriLib = readTauriLibSource();
expect(preferences).toContain("activeSectionId === 'shortcuts'");
expect(preferences).toContain("desktopShortcutFromKeyboardEvent");
expect(preferences).toContain("setDesktopShortcutPreference");
expect(preferences).toContain("resetDesktopShortcutPreferences");
expect(preferences).toContain("startDesktopShortcutCapture($event, item.action)");
expect(preferences).toContain('window.addEventListener("keydown", captureDesktopShortcut, { capture: true })');
expect(preferences).toContain('window.addEventListener("pointerdown", cancelDesktopShortcutCaptureOnPointerDown, { capture: true })');
expect(preferences).toContain("event.stopImmediatePropagation()");
expect(preferences).toContain("恢复默认");
expect(tauriLib).not.toContain('Some("CmdOrCtrl+R")');
expect(tauriLib).not.toContain('Some("CmdOrCtrl+[")');
expect(tauriLib).not.toContain('Some("CmdOrCtrl+]")');
});
it("moves overflowing workspace tabs into a fixed dropdown without horizontal scrolling", () => {
const source = readDesktopLayoutSource();
const tabsStart = source.indexOf('<div v-if="workspaceTabs.length" ref="workspaceTabsElement" class="workspace-tabs">');
const tabsEnd = source.indexOf('<main class="desktop-content"', tabsStart);
const tabsTemplate = source.slice(tabsStart, tabsEnd);
expect(tabsTemplate).toContain('v-for="item in visibleWorkspaceTabs"');
expect(tabsTemplate).toContain('v-if="overflowWorkspaceTabs.length"');
expect(tabsTemplate).toContain('popper-class="workspace-tabs-overflow-popper"');
expect(tabsTemplate).toContain('{{ overflowWorkspaceTabs.length }}');
expect(tabsTemplate).toContain('@command="navigateOverflowWorkspaceTab"');
expect(tabsTemplate).toContain('gridTemplateColumns: `repeat(${visibleWorkspaceTabs.length}, minmax(0, 238px))`');
expect(tabsTemplate).toContain('class="workspace-tabs-overflow-header"');
expect(source).toContain("const visibleWorkspaceTabCapacity = computed(() => {");
expect(source).toContain("const visibleWorkspaceTabs = computed(() => {");
expect(source).toContain("const overflowWorkspaceTabs = computed(() => {");
expect(source).toContain('workspaceTabsResizeObserver ||= new ResizeObserver(updateWorkspaceTabsAvailableWidth);');
expect(source).toContain("const DESKTOP_WORKSPACE_TABS_OVERFLOW_WIDTH = 106;");
expect(source).toContain(".workspace-tabs-list {\n display: grid;");
expect(source).toContain("overflow: hidden;");
expect(source).not.toContain("overflow-x: auto;");
});
it("keeps desktop navigation hierarchy aligned with the web sidebar", () => {
const desktopLayout = readDesktopLayoutSource();
const navigation = readNavigationSource();
@@ -267,6 +331,10 @@ describe("desktop layout shell", () => {
expect(sidebarTemplate).toContain(':aria-expanded="isNavigationGroupExpanded(item)"');
expect(sidebarTemplate).toContain('v-show="isNavigationGroupExpanded(item)"');
expect(desktopLayout).toContain("const expandedNavigationGroups = ref<Set<string>>(new Set());");
expect(desktopLayout).toContain("const collapsedNavigationGroups = ref<Set<string>>(new Set());");
expect(desktopLayout).toContain("isNavigationGroupActive(item) && !collapsedNavigationGroups.value.has(key)");
expect(desktopLayout).toContain("nextCollapsed.add(key);");
expect(desktopLayout).toContain("nextCollapsed.delete(key);");
expect(navigation).toContain('label: "系统设置"');
expect(navigation).toContain('label: "邮件服务"');
expect(navigation).toContain('group: "系统设置"');