feat: release OpsXShell 2.1.0 native Tauri edition

This commit is contained in:
Logan.Li committed 2026-07-30 23:32:51 +08:00
1 parent e9a0d21758
commit a3dba0c6ed
311 files changed
+31748 -14897

No files matched your search

+94 -25
View File
@@ -4,18 +4,33 @@
import '@testing-library/jest-dom'
import { vi } from 'vitest'
import type { DesktopApiContract } from '../src/desktop'
// Mock Electron API
const mockElectronAPI = {
// 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, stdout: '', stderr: '' }),
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 }),
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' }),
@@ -25,20 +40,35 @@ const mockElectronAPI = {
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
getSessions: vi.fn().mockResolvedValue({ success: true, sessions: [] }),
getSession: vi.fn().mockResolvedValue({ success: true, session: null }),
createSession: vi.fn().mockResolvedValue({ success: true, id: 'test-session-1' }),
updateSession: vi.fn().mockResolvedValue({ success: true }),
deleteSession: vi.fn().mockResolvedValue({ success: true }),
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
getSettings: vi.fn().mockResolvedValue({ success: true, settings: {} }),
setSettings: vi.fn().mockResolvedValue({ success: true }),
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' }),
@@ -46,32 +76,66 @@ const mockElectronAPI = {
// File
localReadDir: vi.fn().mockResolvedValue({ success: true, files: [] }),
localReadFile: vi.fn().mockResolvedValue({ success: true, content: '' }),
localWriteFile: vi.fn().mockResolvedValue({ success: true }),
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({ success: true, path: '/Users/test/Downloads' }),
// Zmodem
onZmodemSend: vi.fn(),
getDownloadsPath: vi.fn().mockResolvedValue('/Users/test/Downloads'),
// App
getVersion: vi.fn().mockResolvedValue('1.0.0'),
getPlatform: vi.fn().mockReturnValue('darwin'),
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(),
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.electronAPI = mockElectronAPI as unknown as typeof window.electronAPI
window.opsxApi = mockDesktopApi
// Mock navigator
Object.defineProperty(window, 'navigator', {
value: { platform: 'MacIntel' },
writable: true
Object.defineProperty(window.navigator, 'platform', {
configurable: true,
value: 'MacIntel',
})
// Mock ResizeObserver
@@ -82,10 +146,18 @@ class ResizeObserverMock {
}
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 => ({
value: vi.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
@@ -101,10 +173,7 @@ Object.defineProperty(window, 'matchMedia', {
const originalError = console.error
beforeAll(() => {
console.error = (...args: unknown[]) => {
if (
typeof args[0] === 'string' &&
(args[0].includes('Warning:') || args[0].includes('ReactDOM.render'))
) {
if (typeof args[0] === 'string' && (args[0].includes('Warning:') || args[0].includes('ReactDOM.render'))) {
return
}
originalError.call(console, ...args)