Files
opsxshell/test/stores/tabStore.test.ts
T
Logan.Li 514d62644e v2.0.0: 主题系统+SSH私钥解析修复+会话持久化+连接池优化+UI打磨
核心修复:
- ssh-service.ts: 新增 resolvePrivateKey(),将私钥文件路径(~/.ssh/id_rsa)解析为内容后传给 ssh2,
  解决 'Cannot parse privateKey: Unsupported key format' 错误
- useTerminalConnection.ts + sessionHelper.ts: 根据 authType 过滤字段,password/agent 认证不再传 privateKey
- sessionStore.ts: addSession() 现在先调用 sessionCreate() 持久化到 SQLite,解决恢复标签'无可用连接信息'问题
- database-service.ts: updateSession() 补充 private_key_path 字段持久化
- SessionDialog.tsx: 默认 username='root'、privateKey='~/.ssh/id_rsa'

UI/UX:
- 语义化主题系统(app-theme.ts),强制深色模式,全局无边框设计
- macOS 标题栏支持双击最大化(window:toggleMaximize IPC)
- macOS 菜单栏('OpsXShell' 应用菜单 + Settings Cmd+,)
- WelcomeView 4卡片布局,移除 Settings 按钮
- TabBar/TabItem 背景色统一(bg-dark-800/900),消除白色边框线

架构优化:
- SFTP 生命周期修复(useSFTP.ts useRef + autoConnectAttempted guard)
- 连接池竞态修复,支持快速多标签连接
- IPC 接口规范化,安全错误处理
- Vite 代码分割优化

质量: lint零错误 | tsc零错误 | build成功 | 49 tests pass
2026-04-22 11:15:41 +08:00

178 lines
5.6 KiB
TypeScript

/**
* TabStore Tests - 标签页状态管理测试
*/
import { describe, it, expect, beforeEach } from 'vitest'
import { useTabStore } from '../../src/stores/tabStore'
describe('TabStore', () => {
beforeEach(() => {
// Reset store to initial state
useTabStore.setState({
tabs: [{ id: 'welcome', title: '欢迎', type: 'welcome', isActive: true }],
activeTabId: 'welcome',
})
})
describe('初始状态', () => {
it('应该包含默认的欢迎标签', () => {
const { tabs, activeTabId } = useTabStore.getState()
expect(tabs).toHaveLength(1)
expect(tabs[0].id).toBe('welcome')
expect(tabs[0].type).toBe('welcome')
expect(activeTabId).toBe('welcome')
})
})
describe('addTab', () => {
it('应该添加新的终端标签', () => {
const { addTab, tabs } = useTabStore.getState()
addTab({ title: 'Terminal', type: 'terminal' })
const newTabs = useTabStore.getState().tabs
expect(newTabs).toHaveLength(tabs.length + 1)
expect(newTabs.find(t => t.type === 'terminal')).toBeDefined()
})
it('添加新标签后应该激活它', () => {
const { addTab } = useTabStore.getState()
addTab({ title: 'Terminal', type: 'terminal' })
const { activeTabId, tabs } = useTabStore.getState()
const activeTab = tabs.find(t => t.id === activeTabId)
expect(activeTab?.type).toBe('terminal')
})
it('添加 settings 类型时如果已存在则激活而不是创建新的', () => {
const { addTab } = useTabStore.getState()
addTab({ title: 'Settings', type: 'settings' })
addTab({ title: 'Settings 2', type: 'settings' })
const newTabs = useTabStore.getState().tabs
expect(newTabs).toHaveLength(2) // welcome + settings
})
})
describe('updateTab', () => {
it('应该更新指定标签的标题', () => {
const { updateTab } = useTabStore.getState()
updateTab('welcome', { title: '新的标题' })
const { tabs } = useTabStore.getState()
expect(tabs[0].title).toBe('新的标题')
})
it('应该更新标签的连接状态', () => {
const { updateTab } = useTabStore.getState()
updateTab('welcome', { connectionStatus: 'connected' })
const { tabs } = useTabStore.getState()
expect(tabs[0].connectionStatus).toBe('connected')
})
})
describe('closeTab', () => {
it('不应该关闭最后一个标签', () => {
const { closeTab } = useTabStore.getState()
const closed = closeTab('welcome')
const { tabs } = useTabStore.getState()
expect(tabs).toHaveLength(1)
expect(closed).toBeNull()
})
it('关闭标签后应该激活另一个标签', () => {
const { addTab, closeTab } = useTabStore.getState()
addTab({ title: 'Tab 1', type: 'terminal' })
addTab({ title: 'Tab 2', type: 'sftp' })
const { tabs } = useTabStore.getState()
const tabToClose = tabs.find(t => t.title === 'Tab 1')
if (tabToClose) {
closeTab(tabToClose.id)
const { tabs: newTabs } = useTabStore.getState()
expect(newTabs.find(t => t.isActive)?.title).toBe('Tab 2')
}
})
it('应该返回被关闭的标签', () => {
const { addTab, closeTab } = useTabStore.getState()
addTab({ title: 'Tab 1', type: 'terminal' })
const { tabs } = useTabStore.getState()
const tabToClose = tabs.find(t => t.title === 'Tab 1')
if (tabToClose) {
const closed = closeTab(tabToClose.id)
expect(closed?.title).toBe('Tab 1')
}
})
})
describe('closeOtherTabs', () => {
it('应该关闭除指定标签外的所有标签', () => {
const { addTab, closeOtherTabs } = useTabStore.getState()
addTab({ title: 'Tab 1', type: 'terminal' })
addTab({ title: 'Tab 2', type: 'sftp' })
const tabToKeep = useTabStore.getState().tabs.find(t => t.title === 'Tab 1')
if (tabToKeep) {
closeOtherTabs(tabToKeep.id)
const { tabs: newTabs } = useTabStore.getState()
expect(newTabs).toHaveLength(1)
expect(newTabs[0].title).toBe('Tab 1')
}
})
})
describe('activateTab', () => {
it('应该激活指定的标签', () => {
const { addTab, activateTab } = useTabStore.getState()
addTab({ title: 'Tab 1', type: 'terminal' })
addTab({ title: 'Tab 2', type: 'sftp' })
const { tabs } = useTabStore.getState()
const tabToActivate = tabs.find(t => t.title === 'Tab 1')
if (tabToActivate) {
activateTab(tabToActivate.id)
const { tabs: newTabs, activeTabId } = useTabStore.getState()
const activeTab = newTabs.find(t => t.id === activeTabId)
expect(activeTab?.title).toBe('Tab 1')
}
})
})
describe('getTabById', () => {
it('应该返回指定 ID 的标签', () => {
const { addTab, getTabById } = useTabStore.getState()
addTab({ title: 'Test Tab', type: 'terminal' })
const { tabs } = useTabStore.getState()
const testTab = tabs.find(t => t.title === 'Test Tab')
if (testTab) {
const found = getTabById(testTab.id)
expect(found?.title).toBe('Test Tab')
}
})
it('找不到时应该返回 undefined', () => {
const { getTabById } = useTabStore.getState()
const found = getTabById('non-existent-id')
expect(found).toBeUndefined()
})
})
describe('updateTabTitle', () => {
it('应该更新标签标题', () => {
const { updateTabTitle } = useTabStore.getState()
updateTabTitle('welcome', 'Updated Welcome')
const { tabs } = useTabStore.getState()
expect(tabs[0].title).toBe('Updated Welcome')
})
})
})