import { describe, expect, it } from "vitest"; import { readFileSync } from "node:fs"; import { resolve } from "node:path"; const readLayoutSource = () => readFileSync(resolve(__dirname, "./Layout.vue"), "utf8"); const readDesktopLayoutSource = () => readFileSync(resolve(__dirname, "./DesktopLayout.vue"), "utf8"); const readWebLayoutSource = () => readFileSync(resolve(__dirname, "./WebLayout.vue"), "utf8"); const readNavigationSource = () => readFileSync(resolve(__dirname, "./layout/navigation.ts"), "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"); const readProfileSettingsSource = () => readFileSync(resolve(__dirname, "../views/ProfileSettings.vue"), "utf8"); const readFileTaskFeedbackSource = () => readFileSync(resolve(__dirname, "../utils/fileTaskFeedback.ts"), "utf8"); const readAttachmentListSource = () => readFileSync(resolve(__dirname, "./attachments/AttachmentList.vue"), "utf8"); const readDocumentDetailSource = () => readFileSync(resolve(__dirname, "../views/documents/DocumentDetail.vue"), "utf8"); describe("desktop layout shell", () => { it("routes desktop and web shells through the runtime flag", () => { const source = readLayoutSource(); expect(source).toContain("const isDesktop = isTauriRuntime()"); expect(source).toContain(""); expect(source).toContain(""); }); it("uses route-only desktop preference storage", () => { const source = readDesktopLayoutSource(); expect(source).toContain("readDesktopRecentRoutes"); expect(source).toContain("readDesktopFavoriteRoutes"); expect(source).toContain("recordDesktopRecentRoute"); expect(source).toContain("currentDesktopRoutePreference"); }); it("shares active route mapping between web and desktop shells", () => { const webLayout = readWebLayoutSource(); const desktopLayout = readDesktopLayoutSource(); const navigation = readNavigationSource(); expect(webLayout).toContain("getActiveLayoutPath(route.path)"); expect(desktopLayout).toContain("getActiveLayoutPath(route.path)"); expect(navigation).toContain("export const getActiveLayoutPath"); }); it("keeps desktop context labels and command entry points de-duplicated", () => { const source = readDesktopLayoutSource(); expect(source).toContain("

{{ TEXT.common.appName }} Desktop

"); expect(source).not.toContain("const sidebarTitle"); expect(source).not.toContain('title="搜索命令"'); expect(source).not.toContain("desktop-statusbar"); expect(source).not.toContain("status-item"); expect(source).not.toContain("accountProjectRoleLabel"); expect(source).not.toContain("projectStatusLabel"); expect(source).toContain("grid-template-rows: 48px auto minmax(0, 1fr);"); expect(source).toContain("const adminDesktopNavigationItems"); expect(source).toContain("const projectDesktopNavigationItems"); expect(source).not.toContain("const root = study.currentStudy?.name || TEXT.menu.currentProject"); }); it("renders desktop preferences as a split settings panel", () => { const preferences = readDesktopPreferencesSource(); const desktopLayout = readDesktopLayoutSource(); const webLayout = readWebLayoutSource(); expect(preferences).toContain('class="preferences-sidebar"'); expect(preferences).toContain('class="preferences-pane"'); expect(preferences).toContain("const activeSectionId = ref"); expect(preferences).toContain('grid-template-columns: 232px minmax(0, 1fr);'); expect(preferences).toContain("height: min(700px, calc(100vh - 96px));"); expect(preferences).toContain("scrollbar-gutter: stable;"); expect(preferences).toContain('{ id: "connection", label: "连接"'); expect(preferences).toContain('{ id: "appearance", label: "外观"'); expect(preferences).toContain('{ id: "notifications", label: "通知"'); expect(preferences).toContain('{ id: "updates", label: "更新"'); expect(preferences).toContain('{ id: "diagnostics", label: "诊断信息"'); expect(preferences).toContain("testDesktopServerConnection"); expect(preferences).toContain("saveDesktopServerConfig"); expect(preferences).toContain("测试连通性"); expect(preferences).not.toContain("保存服务器"); expect(preferences).toContain('ElMessage.success("服务器设置已保存")'); expect(preferences).toContain("ElMessage.error(`保存失败:${message}`)"); expect(preferences).toContain("normalizeDesktopServerUrl"); expect(preferences).toContain("确认切换服务器"); expect(preferences).toContain(''); expect(preferences).toContain(''); expect(preferences).toContain(''); expect(preferences).toContain("const selectSection = (sectionId: PreferenceSectionId)"); expect(preferences).toContain("const setConnectionPending = (message: string)"); expect(preferences).toContain("const scheduleConnectionPending = (message: string)"); expect(preferences).toContain("}, 180);"); expect(preferences).toContain('scrollTo({ top: 0, behavior: "smooth" })'); expect(preferences).not.toContain("connectionStatus.value = null"); expect(preferences).toContain("min-width: 100px;"); expect(preferences).toContain("connection-feedback-enter-active"); expect(preferences).toContain("@media (prefers-reduced-motion: reduce)"); expect(preferences).toContain(':global([data-ctms-theme="dark"] .desktop-preferences)'); expect(preferences).not.toContain(':global([data-ctms-theme="dark"]) .desktop-preferences'); expect(desktopLayout).toContain(':global([data-ctms-theme="dark"] .desktop-workbench)'); expect(desktopLayout).not.toContain(':global([data-ctms-theme="dark"]) .desktop-workbench'); expect(desktopLayout).toContain('width="940px"'); expect(webLayout).toContain('width="940px"'); expect(desktopLayout).toContain('title: "连接设置"'); expect(webLayout).toContain('title: "连接设置"'); expect(desktopLayout).not.toContain('router.push("/desktop/server-settings")'); expect(webLayout).not.toContain('router.push("/desktop/server-settings")'); expect(desktopLayout).not.toContain('width="720px"'); expect(webLayout).not.toContain('width="720px"'); }); it("keeps desktop notification and diagnostics settings out of profile settings", () => { const profile = readProfileSettingsSource(); const preferences = readDesktopPreferencesSource(); expect(profile).not.toContain("form-section--desktop"); expect(profile).not.toContain("客户端与通知"); expect(profile).not.toContain("getDesktopNotificationSubscription"); expect(profile).not.toContain("setDesktopNotificationSubscription"); expect(profile).not.toContain("checkDesktopUpdateAndPrompt"); expect(profile).not.toContain("clientMetadataRows"); expect(profile).not.toContain("desktopNotificationsEnabled"); expect(preferences).toContain("getDesktopNotificationSubscription"); expect(preferences).toContain("checkDesktopUpdateAndPrompt"); expect(preferences).toContain("clientMetadataRows"); expect(preferences).not.toContain('{ label: "主题"'); expect(preferences).not.toContain('{ label: "服务器"'); }); it("keeps workspace tabs stable and draggable like browser tabs", () => { const source = readDesktopLayoutSource(); const tabsStart = source.indexOf('
{"); expect(source).toContain("if (activeMenu.value === path) return;"); expect(source).toContain("next[existingIndex] = { ...next[existingIndex], ...item };"); expect(source).toContain("const [moved] = next.splice(sourceIndex, 1);"); expect(source).toContain(""); expect(source).toContain(":key=\"desktopRouteCacheKey(currentRoute)\""); expect(source).toContain("const desktopRouteCacheKey = (currentRoute: { fullPath: string }) => currentRoute.fullPath;"); expect(source).not.toContain('
'); expect(tabsTemplate).not.toContain('@click="router.push(item.path)"'); expect(source).not.toContain(".slice(-7)"); expect(tabsTemplate).toContain('@contextmenu.prevent.stop="openWorkspaceTabMenu(item, $event)"'); expect(source).toContain("const lastClosedWorkspaceTab = ref(null);"); expect(source).toContain("const workspaceTabMenu = ref<"); expect(source).toContain("const closeOtherWorkspaceTabs = (path: string) => {"); expect(source).toContain("const restoreLastClosedWorkspaceTab = () => {"); expect(source).toContain('id: `workspace-tab:${item.path}`'); expect(source).toContain('title: `切换标签:${item.title}`'); expect(source).toContain('title: "重新打开最近关闭标签"'); }); it("keeps desktop navigation hierarchy aligned with the web sidebar", () => { const desktopLayout = readDesktopLayoutSource(); const navigation = readNavigationSource(); const sidebarStart = desktopLayout.indexOf('