/** * Vitest Setup - 测试环境配置 */ import '@testing-library/jest-dom' import { vi } from 'vitest' import i18n from '../src/i18n' import { useSettingsStore } from '../src/stores/settingsStore' import type { DesktopApiContract } from '../src/desktop' void i18n.changeLanguage('zh-CN') useSettingsStore.setState((state) => ({ settings: { ...state.settings, language: 'zh-CN' }, isLoaded: state.isLoaded, })) // Mock native desktop API export const mockDesktopApi: DesktopApiContract = { takePendingSshLinks: vi.fn().mockResolvedValue([]), appRuntimeInfo: vi.fn().mockResolvedValue({ appVersion: '2.0.0', os: 'macos', arch: 'aarch64', nativeBackend: 'Tauri 2 / Rust', sshBackend: 'libssh2 / ssh2 0.9', dataPath: '/tmp/opsxshell-data.json', credentialPath: '/tmp/.opsxshell/master.key', logPath: '/tmp/opsxshell/logs/opsxshell.log', }), // SSH sshConnect: vi.fn().mockResolvedValue({ success: true, connectionId: 'test-conn-1' }), sshDisconnect: vi.fn().mockResolvedValue({ success: true }), sshExec: vi.fn().mockResolvedValue({ success: true, result: '', exitCode: 0 }), sshExecCancel: vi.fn().mockResolvedValue({ success: true }), sshHostMetrics: vi.fn().mockResolvedValue({ success: true, metrics: { disks: [] } }), sshKeepalive: vi.fn().mockResolvedValue({ success: true }), sshCreateShell: vi.fn().mockResolvedValue({ success: true, shellId: 'test-shell-1' }), sshShellOpenOutput: vi.fn().mockResolvedValue({ success: true, data: [] }), sshShellWrite: vi.fn().mockResolvedValue({ success: true }), sshShellResize: vi.fn().mockResolvedValue({ success: true }), sshCloseShell: vi.fn().mockResolvedValue({ success: true }), sshForwardLocal: vi.fn().mockResolvedValue({ success: true }), sshForwardRemote: vi.fn().mockResolvedValue({ success: true }), sshForwardDynamic: vi.fn().mockResolvedValue({ success: true }), sshForwardStop: vi.fn().mockResolvedValue({ success: true }), sshForwardList: vi.fn().mockResolvedValue({ success: true, tunnels: [] }), // SFTP sftpConnect: vi.fn().mockResolvedValue({ success: true, sftpId: 'test-sftp-1' }), sftpDisconnect: vi.fn().mockResolvedValue({ success: true }), sftpListDirectory: vi.fn().mockResolvedValue({ success: true, files: [] }), sftpUpload: vi.fn().mockResolvedValue({ success: true }), sftpUploadDir: vi.fn().mockResolvedValue({ success: true }), sftpDownload: vi.fn().mockResolvedValue({ success: true }), sftpDownloadDir: vi.fn().mockResolvedValue({ success: true }), sftpCancelTransfer: vi.fn().mockResolvedValue({ success: true }), sftpRename: vi.fn().mockResolvedValue({ success: true }), sftpDelete: vi.fn().mockResolvedValue({ success: true }), sftpMkdir: vi.fn().mockResolvedValue({ success: true }), sftpStat: vi.fn().mockResolvedValue({ success: true, stats: undefined }), sftpChmod: vi.fn().mockResolvedValue({ success: true }), sftpReadText: vi.fn().mockResolvedValue({ success: true, content: '' }), sftpWriteText: vi.fn().mockResolvedValue({ success: true }), onSFTPProgress: vi.fn(), // Session sessionGetAll: vi.fn().mockResolvedValue({ success: true, sessions: [] }), sessionGetById: vi.fn().mockResolvedValue({ success: true, session: undefined }), sessionCreate: vi.fn().mockImplementation(async (session) => ({ success: true, session: { ...session, id: 'test-session-1' }, })), sessionUpdate: vi.fn().mockImplementation(async (id, updates) => ({ success: true, session: { id, ...updates }, })), sessionDelete: vi.fn().mockResolvedValue({ success: true }), // Settings settingsGet: vi.fn().mockResolvedValue({ success: true, value: undefined }), settingsSet: vi.fn().mockResolvedValue({ success: true }), licenseGet: vi.fn().mockResolvedValue({ plan: 'free', deviceId: 'test-device', activated: false, features: [], }), licenseActivate: vi.fn().mockResolvedValue({ plan: 'pro', deviceId: 'test-device', activated: true, features: ['*'], }), licenseClear: vi.fn().mockResolvedValue({ plan: 'free', deviceId: 'test-device', activated: false, features: [], }), licenseDeviceId: vi.fn().mockResolvedValue('test-device'), licenseSetEmail: vi.fn().mockResolvedValue({ plan: 'free', deviceId: 'test-device', activated: false, features: [], email: '', }), licenseCheckout: vi.fn().mockResolvedValue({ orderNo: 'ORD-TEST', checkoutUrl: 'https://pancake.waffo.ai/store/opsxshell-pwsi6m4n/product/PROD_TEST?test=true', }), aiProviderSaveKey: vi.fn().mockResolvedValue({ success: true }), aiProviderHasKey: vi.fn().mockResolvedValue(false), aiProviderDeleteKey: vi.fn().mockResolvedValue({ success: true }), aiListModels: vi.fn().mockResolvedValue({ success: true, models: [] }), aiComplete: vi.fn().mockResolvedValue({ success: true, text: '建议', model: 'test-model', redactedCount: 0 }), aiTestProvider: vi .fn() .mockResolvedValue({ success: true, status: 200, model: 'test-model', latencyMs: 88, answer: 'OK' }), aiCatalogFetch: vi.fn().mockResolvedValue({ success: false, documents: [], errors: ['offline'] }), // Dialog showSaveDialog: vi.fn().mockResolvedValue({ success: true, canceled: false, filePath: '/tmp/test' }), showOpenDialog: vi.fn().mockResolvedValue({ success: true, canceled: false, filePaths: ['/tmp/test'] }), // File localReadDir: vi.fn().mockResolvedValue({ success: true, files: [] }), readFile: vi.fn().mockResolvedValue({ success: true, data: '' }), writeFile: vi.fn().mockResolvedValue({ success: true }), localGetStats: vi.fn().mockResolvedValue({ success: true }), localRename: vi.fn().mockResolvedValue({ success: true }), localDelete: vi.fn().mockResolvedValue({ success: true }), localMkdir: vi.fn().mockResolvedValue({ success: true }), getHomePath: vi.fn().mockResolvedValue({ success: true, path: '/Users/test' }), getDownloadsPath: vi.fn().mockResolvedValue('/Users/test/Downloads'), openDownloadsFolder: vi.fn().mockResolvedValue(undefined), // App getVersion: vi.fn().mockResolvedValue('1.0.0'), openExternal: vi.fn().mockResolvedValue(undefined), clipboardReadText: vi.fn().mockResolvedValue(''), clipboardWriteText: vi.fn().mockResolvedValue(undefined), toggleMaximize: vi.fn().mockResolvedValue(undefined), setFullscreen: vi.fn().mockResolvedValue(undefined), isFullscreen: vi.fn().mockResolvedValue(false), toggleDevtools: vi.fn().mockResolvedValue(false), applyNativeMenuLocale: vi.fn().mockResolvedValue('en'), // Local terminal localListShells: vi.fn().mockResolvedValue({ success: true, shells: [ { path: '/bin/zsh', name: 'Zsh', kind: 'zsh', isDefault: true }, { path: '/bin/bash', name: 'Bash', kind: 'bash', isDefault: false }, ], }), localCreateShell: vi.fn().mockResolvedValue({ success: true, shellId: 'local-test-1', shellPath: '/bin/zsh', shellName: 'zsh', }), localWrite: vi.fn().mockResolvedValue({ success: true }), localResize: vi.fn().mockResolvedValue({ success: true }), localKill: vi.fn().mockResolvedValue({ success: true }), onLocalData: vi.fn(), onLocalExit: vi.fn(), // Event listeners onShellData: vi.fn(), onShellClosed: vi.fn(), onConnectionClosed: vi.fn(), onHostKeyPrompt: vi.fn(), onMenuNewSession: vi.fn(), onMenuQuickConnect: vi.fn(), onMenuLocalTerminal: vi.fn(), onMenuImportSessions: vi.fn(), onMenuExportSessions: vi.fn(), onMenuOpenSettings: vi.fn(), onSshLinkAvailable: vi.fn(), onProLink: vi.fn(), respondHostKeyPrompt: vi.fn(), knownHostsList: vi.fn().mockResolvedValue({ success: true, entries: [] }), knownHostsRemove: vi.fn().mockResolvedValue({ success: true }), knownHostsClear: vi.fn().mockResolvedValue({ success: true }), removeListener: vi.fn(), removeAllListeners: vi.fn(), } window.opsxApi = mockDesktopApi // Mock navigator Object.defineProperty(window.navigator, 'platform', { configurable: true, value: 'MacIntel', }) // Mock ResizeObserver class ResizeObserverMock { observe() {} unobserve() {} disconnect() {} } window.ResizeObserver = ResizeObserverMock // XTerm probes canvas support during component smoke tests. jsdom intentionally // does not implement a rendering context, so provide the same null fallback // browsers expose when a context type is unavailable. Object.defineProperty(HTMLCanvasElement.prototype, 'getContext', { configurable: true, value: vi.fn(() => null), }) // Mock matchMedia Object.defineProperty(window, 'matchMedia', { writable: true, value: vi.fn().mockImplementation((query) => ({ matches: false, media: query, onchange: null, addListener: vi.fn(), removeListener: vi.fn(), addEventListener: vi.fn(), removeEventListener: vi.fn(), dispatchEvent: vi.fn(), })), }) // Suppress console errors in tests const originalError = console.error beforeAll(() => { console.error = (...args: unknown[]) => { if (typeof args[0] === 'string' && (args[0].includes('Warning:') || args[0].includes('ReactDOM.render'))) { return } originalError.call(console, ...args) } }) afterAll(() => { console.error = originalError })