Files
opsxshell/test/setup.ts
T
Logan.LiandCursor 9ce71795b8 feat: add terminal broadcast, context menu, and shell close
Complete multi-pane broadcast, standard terminal right-click actions, batch host selection with progress, and native ssh_close_shell so closing a pane tears down the remote shell.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 19:53:39 +08:00

190 lines
7.1 KiB
TypeScript

/**
* Vitest Setup - 测试环境配置
*/
import '@testing-library/jest-dom'
import { vi } from 'vitest'
import type { DesktopApiContract } from '../src/desktop'
// 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/.opsxshsell/master.key',
}),
// 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 }),
sshKeepalive: vi.fn().mockResolvedValue({ success: true }),
sshCreateShell: vi.fn().mockResolvedValue({ success: true, shellId: 'test-shell-1' }),
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' }),
sftpListDirectory: vi.fn().mockResolvedValue({ success: true, files: [] }),
sftpUpload: vi.fn().mockResolvedValue({ success: true }),
sftpDownload: 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 }),
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'),
// 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),
// 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(),
// 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(),
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
})