refactor(ui): R08-T07 — bóc settings_dialog.py 727 → 407 dòng thành 4 widget
Bốn mục trong Cài đặt tách thành widget riêng dưới presentation/settings/:
general_settings_widget.py ngôn ngữ, giao diện, khay, gợi ý
provider_settings_widget.py provider, base URL, key, model + 2 nút nền
parameter_settings_widget.py đính kèm, cấu trúc, giới hạn sandbox
routing_settings_widget.py Auto Model Routing
Mỗi widget tự dựng control, tự nạp giá trị, tự có apply_to(data). Dialog chỉ
còn lắp ráp và gọi apply_to lúc lưu — _save từ 34 dòng xuống còn phần khung.
Làm lưới an toàn trước khi bóc: tests/ui/test_settings_dialog_dac_ta.py, 7
bài đặc tả hành vi hiện tại (mục nào có mặt, nạp đúng giá trị gì, lưu ghi vào
đúng ô nào, đổi % sang phân lẻ, xoá cache sau lưu). Bóc xong cả 7 vẫn xanh,
và trong lúc bóc chúng đã đỏ đúng hai lần ở chỗ đáng đỏ.
Đây là repo chưa từng có test Qt nào — thêm tests/ui/conftest.py dựng
QApplication offscreen. Offscreen là bắt buộc chứ không phải cho nhanh: máy
dev là máy làm việc thật, test bật cửa sổ lên là nó nhảy ra che màn hình.
Dọn kèm:
* bỏ vòng "dựng vào layout rồi lại gỡ ra" của mục Chung, cùng widget cao 0px
làm mốc cuộn — không cần nữa khi mục đó tự là một widget
* bỏ _select_combo, _secret, _model_combo, _with_load và 4 hàm provider khác
đã chuyển vào widget (127 dòng)
* bỏ 5 import chết theo (Dict, QSizePolicy, PROVIDER_LABELS, SegmentedControl,
LANGUAGES)
Giữ cầu tương thích: self.routing_*, self.prov_*, self.attach_* … thành
property trỏ vào widget con, vì 5 checker trong tools/ đọc thẳng tên cũ. Bỏ
được khi tools/ chuyển sang đọc self._provider_page.
Hai điều KHÔNG làm, ghi lại để khỏi tưởng là quên:
1. Plan ghi 4 widget và có tên `connector`. Thực tế UI connector đã dời khỏi
Cài đặt từ trước (ghi chú ở settings_dialog.py:180 bản cũ), nên số mục thật
là 5, không phải 4, và không có mục nào tên connector. Bốn mục bóc ra là 4
mục có thật; mục Bảo mật sandbox để nguyên trong dialog lần này.
2. Còn ~108 dòng chết của MS365 (_refresh_ms365_status, _ms365_sign_in,
_show_ms365_device_code, _ms365_sign_out): đọc self.ms365_status,
self.ms365_signin_btn, self.ms365_signout_btn — ba thuộc tính KHÔNG BAO GIỜ
được gán, và không hàm nào có người gọi. Gọi vào là AttributeError. Chưa
xoá vì đó là quyết định của anh Nam, không phải việc kèm theo của T07.
437 test xanh. check_dialogs, check_no_hscroll, check_design_parity đều qua.
Kèm docs/refactor/tin-gui-team-hoa.md — tin báo Hoa về platform/ -> adapters/
và bản vá Windows của AtomicJsonFile.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9d6a7be31b
commit
2b90492994
@@ -0,0 +1,19 @@
|
||||
"""Dựng Qt ở chế độ offscreen cho test giao diện.
|
||||
|
||||
Offscreen là bắt buộc, không phải cho nhanh: máy dev là máy làm việc thật của
|
||||
người dùng. Test bật cửa sổ lên là nó nhảy ra trước mặt, che thứ đang mở.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def qapp():
|
||||
from PySide6.QtWidgets import QApplication
|
||||
app = QApplication.instance() or QApplication([])
|
||||
yield app
|
||||
@@ -0,0 +1,149 @@
|
||||
"""Đặc tả hành vi SettingsDialog TRƯỚC khi tách — R08-T07.
|
||||
|
||||
Không phải test tính năng mới. Đây là lưới an toàn: chốt lại dialog hiện
|
||||
đang làm gì, để khi bóc 727 dòng thành các widget con còn biết mình có làm
|
||||
lệch đi chỗ nào không. Bài nào ở đây đỏ sau khi tách nghĩa là tách sai.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class _Config:
|
||||
"""Đủ dùng cho SettingsDialog, không hơn — xem danh sách ctx.* nó chạm."""
|
||||
|
||||
def __init__(self):
|
||||
self.data = {
|
||||
"active_provider": "openai_compat",
|
||||
"language": "vi",
|
||||
"theme": "dark",
|
||||
"providers": {
|
||||
"openai_compat": {"base_url": "https://api.openai.com/v1",
|
||||
"api_key": "khoa-cu", "model": "gpt-4o"},
|
||||
"ollama": {"base_url": "http://localhost:11434",
|
||||
"api_key": "ollama", "model": "qwen2.5-coder"},
|
||||
},
|
||||
"tray": {"minimize_on_close": True, "notify_on_done": False},
|
||||
"agent_security": {
|
||||
"enabled": True, "cowork_confirm_commands": False,
|
||||
"block_network": True, "command_ai_check": False,
|
||||
"resource_limit_cpu_percent": 55,
|
||||
"resource_limit_memory_mb": 1024,
|
||||
"resource_limit_disk_mb": 2048,
|
||||
},
|
||||
"attachments": {"max_tokens": 32000, "max_files": 7},
|
||||
"structure": {"max_nodes": 300, "max_edges": 600},
|
||||
"routing": {"switch_mode": "auto", "policy": "cost",
|
||||
"min_score_gain": 0.05, "confirm_timeout_sec": 90,
|
||||
"reassess_interval_hours": 12,
|
||||
"per_provider_concurrency": 3,
|
||||
"judge_model": "gpt-4o-mini"},
|
||||
}
|
||||
self._data = self.data
|
||||
self._agent_security = self.data["agent_security"]
|
||||
|
||||
language = property(lambda self: self.data["language"])
|
||||
theme = property(lambda self: self.data["theme"])
|
||||
active_provider = property(lambda self: self.data["active_provider"])
|
||||
agent_security = property(lambda self: self.data["agent_security"])
|
||||
routing = property(lambda self: self.data["routing"])
|
||||
|
||||
|
||||
class _Ctx:
|
||||
def __init__(self):
|
||||
self.config = _Config()
|
||||
self.routing = None
|
||||
self.saves = 0
|
||||
|
||||
def save(self):
|
||||
self.saves += 1
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def dialog(qapp):
|
||||
from cowork_local.ui.settings_dialog import SettingsDialog
|
||||
ctx = _Ctx()
|
||||
dlg = SettingsDialog(ctx)
|
||||
yield dlg, ctx
|
||||
dlg.deleteLater()
|
||||
|
||||
|
||||
# ---- dialog gồm những mục nào -------------------------------------------
|
||||
|
||||
def test_co_dung_nam_muc(dialog):
|
||||
"""Năm mục thật trên màn hình. Plan R08-T07 ghi bốn widget và có một cái
|
||||
tên `connector`, nhưng UI connector đã dời khỏi Settings từ trước (xem
|
||||
ghi chú ở settings_dialog.py:180) — nên con số thật là năm, không bốn."""
|
||||
dlg, _ = dialog
|
||||
labels = [dlg.section_list.item(i).text()
|
||||
for i in range(dlg.section_list.count())]
|
||||
assert len(labels) == 5, labels
|
||||
assert dlg.section_stack.count() == 5
|
||||
|
||||
|
||||
def test_moi_muc_deu_bam_duoc(dialog):
|
||||
dlg, _ = dialog
|
||||
for i in range(dlg.section_list.count()):
|
||||
dlg.section_list.setCurrentRow(i)
|
||||
assert dlg.section_stack.currentIndex() == i
|
||||
|
||||
|
||||
# ---- nạp giá trị từ config ----------------------------------------------
|
||||
|
||||
def test_nap_dung_gia_tri_dang_co(dialog):
|
||||
dlg, ctx = dialog
|
||||
assert dlg.language_combo.currentData() == "vi"
|
||||
assert dlg.theme_combo.currentData() == "dark"
|
||||
assert dlg.provider_combo.currentData() == "openai_compat"
|
||||
assert dlg.prov_base.text() == "https://api.openai.com/v1"
|
||||
assert dlg.tray_chk.isChecked() is True
|
||||
assert dlg.notify_chk.isChecked() is False
|
||||
assert dlg.routing_mode.currentData() == "auto"
|
||||
assert dlg.routing_policy.currentData() == "cost"
|
||||
assert dlg.routing_timeout.value() == 90
|
||||
assert dlg.attach_files.value() == 7
|
||||
assert dlg.struct_nodes.value() == 300
|
||||
assert dlg.sandbox_cpu.value() == 55
|
||||
|
||||
|
||||
def test_khoa_api_khong_hien_ro(dialog):
|
||||
"""QLineEdit.Password — khoá không được đọc được bằng mắt qua vai."""
|
||||
from PySide6.QtWidgets import QLineEdit
|
||||
dlg, _ = dialog
|
||||
assert dlg.prov_key.echoMode() == QLineEdit.Password
|
||||
|
||||
|
||||
# ---- lưu ghi ra đúng chỗ -------------------------------------------------
|
||||
|
||||
def test_luu_ghi_dung_moi_o(dialog):
|
||||
dlg, ctx = dialog
|
||||
dlg.language_combo.setCurrentIndex(
|
||||
dlg.language_combo.findData("en") if dlg.language_combo.findData("en") >= 0 else 0)
|
||||
dlg.tray_chk.setChecked(False)
|
||||
dlg.routing_timeout.setValue(120)
|
||||
dlg.attach_files.setValue(3)
|
||||
dlg.sandbox_cpu.setValue(80)
|
||||
|
||||
dlg._save()
|
||||
d = ctx.config.data
|
||||
|
||||
assert d["tray"]["minimize_on_close"] is False
|
||||
assert d["routing"]["confirm_timeout_sec"] == 120
|
||||
assert d["attachments"]["max_files"] == 3
|
||||
assert d["agent_security"]["resource_limit_cpu_percent"] == 80
|
||||
assert ctx.saves == 1
|
||||
|
||||
|
||||
def test_luu_doi_min_gain_tu_phan_tram_sang_phan_le(dialog):
|
||||
"""Ô nhập là %, config lưu số thập phân. Đây là chỗ dễ tách sai nhất."""
|
||||
dlg, ctx = dialog
|
||||
dlg.routing_min_gain.setValue(25)
|
||||
dlg._save()
|
||||
assert ctx.config.data["routing"]["min_score_gain"] == 0.25
|
||||
|
||||
|
||||
def test_luu_xoa_cache_de_app_doc_lai_ngay(dialog):
|
||||
dlg, ctx = dialog
|
||||
dlg._save()
|
||||
assert ctx.config._data is None
|
||||
assert ctx.config._agent_security is None
|
||||
Reference in New Issue
Block a user