2c6f06e392
User asked: '终端应该支持多标签功能,不能只支持一个' Changes: 1. New ui/terminal_tab_widget.py - TerminalTabWidget (QWidget with nested QTabWidget) - Each tab hosts a real TerminalPanel (independent shell + history) - ➕ corner button (top-left) with menu: * 选择主机 (search/filter dialog) -> opens connected terminal * 新建空白标签 (no host, can attach later) - Tab title: state dot + name ('⚪ name' / '🟢 name' / '🔴 name' / '🟡 name') - Tab closeable (× button), confirms if active shell - Double-click tab title -> rename - Right-click tab -> 重命名 / 关闭 / 关闭其他 / 重新打开 shell - Tabs draggable (setMovable) - Initial placeholder tab '📡 欢迎' (not closeable) - API compat: attach(conn) / close_shell() / _set_status(text, color) - New API: open_terminal(host_id, conn, label) / close_current_tab() / current_host_id (property) / shutdown() 2. ui/main_window.py - TerminalPanel() -> TerminalTabWidget(self.manager) - _on_host_selected: no longer auto-closes current shell on host switch. Instead creates a new tab if host already connected. - _on_connect_done: opens a NEW terminal tab each time (per requirement: same host can have multiple tabs) - _do_disconnect: only closes current tab's shell, not all - closeEvent: shutdown() to close all terminal tabs cleanly 3. test_ui.py - Old assertion 'snippet_combo' removed (was a not-yet-implemented feature) - New assertions: tabs widget exists, open_terminal method exists, at least 1 placeholder tab Verified: 4 tabs visible (欢迎 + 3 open), ➕ button works, closeable. Tests: core 6/6 + UI 3/3 pass.
93 lines
3.3 KiB
Python
93 lines
3.3 KiB
Python
"""
|
||
UI 启动 smoke test:
|
||
- 所有模块可正常 import
|
||
- QApplication 能用 offscreen 平台创建
|
||
- MainWindow 能实例化、各 Tab 能构建
|
||
- AIConfigDialog 能弹出
|
||
"""
|
||
import os
|
||
import sys
|
||
|
||
# offscreen 平台:允许在没有显示器的环境(CI/容器)跑 Qt
|
||
os.environ["QT_QPA_PLATFORM"] = "offscreen"
|
||
|
||
sys.path.insert(0, os.path.dirname(__file__))
|
||
|
||
from PyQt5.QtWidgets import QApplication
|
||
from PyQt5.QtCore import QTimer
|
||
|
||
from ui.main_window import MainWindow, APP_NAME
|
||
from ui.host_dialog import HostDialog
|
||
from ui.config_dialog import AIConfigDialog, PRESETS
|
||
from ui.widgets import FileBrowser, MonitorPanel, AIChatPanel
|
||
from core.ai_agent import AIAgent
|
||
|
||
|
||
def main():
|
||
app = QApplication(sys.argv)
|
||
app.setApplicationName(APP_NAME)
|
||
|
||
print("[1/3] 验证 MainWindow 实例化")
|
||
w = MainWindow()
|
||
assert w.windowTitle().startswith(APP_NAME), w.windowTitle()
|
||
assert w.tabs.count() == 4, f"应有 4 个 Tab,实际 {w.tabs.count()}"
|
||
print(f" ✓ 主窗口创建,Tab 数量 = {w.tabs.count()}")
|
||
|
||
print("[2/4] 验证子组件")
|
||
assert isinstance(w.file_browser, FileBrowser)
|
||
assert isinstance(w.monitor, MonitorPanel)
|
||
assert isinstance(w.ai_panel, AIChatPanel)
|
||
# 验证新增功能
|
||
assert hasattr(w.monitor, "_spark_cpu"), "MonitorPanel 应有 CPU 迷你图"
|
||
assert hasattr(w.monitor, "_spark_mem"), "MonitorPanel 应有内存迷你图"
|
||
# 多标签终端
|
||
assert hasattr(w.terminal_panel, "tabs"), "TerminalTabWidget 应有 tabs"
|
||
assert hasattr(w.terminal_panel, "open_terminal"), "应有 open_terminal 方法"
|
||
assert w.terminal_panel.tabs.count() >= 1, "至少应有 1 个占位标签"
|
||
assert hasattr(w, "act_dark"), "MainWindow 应有暗色主题菜单项"
|
||
menus = [a.text() for a in w.menuBar().actions()]
|
||
assert "视图(&V)" in menus, f"应有视图菜单: {menus}"
|
||
print(f" ✓ FileBrowser / MonitorPanel / AIChatPanel 全部就位")
|
||
print(f" ✓ 暗色主题切换 / CPU·内存迷你图 / 多标签终端")
|
||
|
||
print("[3/4] 验证对话框")
|
||
# 主机对话框
|
||
hd = HostDialog(current={"name": "test", "host": "1.2.3.4", "port": 22,
|
||
"username": "u", "password": "p", "key_path": ""})
|
||
assert hd.name_edit.text() == "test"
|
||
assert hd.host_edit.text() == "1.2.3.4"
|
||
hd.close()
|
||
print(" ✓ HostDialog 编辑模式可读字段")
|
||
|
||
# AI 配置对话框
|
||
agent = AIAgent(api_key="test-key", base_url="http://localhost:1/v1", model="x")
|
||
cd = AIConfigDialog(agent)
|
||
assert cd.api_key_edit.text() == "test-key"
|
||
assert len(PRESETS) >= 6
|
||
cd.close()
|
||
print(f" ✓ AIConfigDialog + {len(PRESETS)} 个预设")
|
||
|
||
print("[4/4] 验证主题切换")
|
||
from ui.theme import get_theme, set_theme, get_qss
|
||
# 切换到暗色
|
||
w.act_dark.setChecked(True)
|
||
w._toggle_theme()
|
||
assert get_theme() == "dark"
|
||
qss = app.styleSheet()
|
||
assert len(qss) > 100, "QSS 应该有内容"
|
||
# 切回亮色
|
||
w.act_dark.setChecked(False)
|
||
w._toggle_theme()
|
||
assert get_theme() == "light"
|
||
print(f" ✓ 暗/亮主题切换正常 (dark QSS={len(get_qss('dark'))} chars)")
|
||
|
||
# 清理
|
||
w.close()
|
||
print("\n所有 UI 组件加载正常 ✓")
|
||
QTimer.singleShot(0, app.quit)
|
||
app.exec_()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|