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('