Delta team/fix comment ui #10
@@ -47,6 +47,60 @@ class PageRegistryMixin:
|
||||
m.status_message.connect(self.statusBar().showMessage)
|
||||
self.monitoring = m
|
||||
return m
|
||||
def current_screen_label(self) -> str:
|
||||
"""Tên màn hình người dùng đang mở, dạng "Workspace ▸ Project".
|
||||
|
||||
Trợ lý Hỗ trợ không nhìn được màn hình, nên "tôi đang ở đâu" là câu nó
|
||||
không thể tự trả lời — chuỗi này được ghép vào prompt của nó
|
||||
(``core/help_knowledge.py``).
|
||||
"""
|
||||
row = self.pages.currentIndex()
|
||||
if not (0 <= row < len(self._nav_defs)):
|
||||
return ""
|
||||
page = tr(self._nav_defs[row][0])
|
||||
if row != self._ROW_WORKSPACE:
|
||||
return page
|
||||
sub = self.workspace.current_subtab()
|
||||
keys = {self.workspace._project_tab_idx: "workspace.tab_project",
|
||||
self.workspace._cowork_tab_idx: "workspace.tab_cowork",
|
||||
self.workspace._co4e_tab_idx: "workspace.tab_co4e",
|
||||
getattr(self.workspace, "_folder_tab_idx", -1): "workspace.tab_folder",
|
||||
self.workspace._graphrag_tab_idx: "workspace.tab_graphrag"}
|
||||
key = keys.get(sub)
|
||||
return f"{page} ▸ {tr(key)}" if key else page
|
||||
|
||||
def help_context(self) -> str:
|
||||
"""Ngữ cảnh sống cho Trợ lý Hỗ trợ: đang ở màn nào, và trên đó bấm được gì.
|
||||
|
||||
Vì sao đọc từ WIDGET ĐANG CHẠY chứ không từ ``docs/screens/controls.json``:
|
||||
file đó được trích tự động nhưng đã cũ — 5/41 file trong đó không còn tồn
|
||||
tại, và nó không có file nào trong ``presentation/`` (chưa sinh lại sau
|
||||
refactor R08). Nạp nó vào prompt là dạy trợ lý về nút của những file đã
|
||||
bị xoá. Cây widget thật thì không bao giờ cũ được.
|
||||
"""
|
||||
from PySide6.QtWidgets import QPushButton, QTabWidget
|
||||
|
||||
from ...core.help_knowledge import screen_context
|
||||
|
||||
page = self.pages.currentWidget()
|
||||
nhan = []
|
||||
if page is not None:
|
||||
for tabs in page.findChildren(QTabWidget):
|
||||
for i in range(tabs.count()):
|
||||
if tabs.isTabVisible(i):
|
||||
nhan.append(f"[tab] {tabs.tabText(i)}")
|
||||
for btn in page.findChildren(QPushButton):
|
||||
text = (btn.text() or "").replace("&", "").strip()
|
||||
if text and btn.isVisible() and btn.isEnabled():
|
||||
nhan.append(text)
|
||||
# Bo trung, giu nguyen thu tu xuat hien tren man hinh.
|
||||
thay, gon = set(), []
|
||||
for n in nhan:
|
||||
if n not in thay:
|
||||
thay.add(n)
|
||||
gon.append(n)
|
||||
return screen_context(self.current_screen_label(), gon)
|
||||
|
||||
def _ensure_page(self, row: int) -> None:
|
||||
"""Build a lazy nav page on first visit and swap it in for its placeholder."""
|
||||
if not (0 <= row < len(self._built)) or self._built[row]:
|
||||
|
||||
@@ -114,12 +114,41 @@ class RailProjectMixin:
|
||||
tree.blockSignals(blocked)
|
||||
self.nav_recents_hdr.setVisible(not self._nav_collapsed)
|
||||
self.nav_recents.setVisible(not self._nav_collapsed)
|
||||
def goto_all_projects(self) -> None:
|
||||
"""Mở khung "Tất cả project…": Workspace ▸ Cowork, bảng lịch sử bung ra.
|
||||
|
||||
Phải đi qua ``_goto``, không được gọi thẳng ``show_history_pane()``:
|
||||
``_goto`` là chỗ duy nhất dời vệt sáng trên thanh menu theo nội dung.
|
||||
Gọi tắt thì nội dung sang Cowork còn vệt sáng ở lại Project — và vì
|
||||
``QTreeWidget`` không phát ``currentItemChanged`` khi bấm lại đúng dòng
|
||||
đang chọn, bấm "Project" sẽ KHÔNG có tác dụng gì cho tới khi người dùng
|
||||
bấm sang mục khác rồi bấm về.
|
||||
|
||||
Gom vào một hàm vì có hai đường vào: link "Tất cả project…" ở mục GẦN
|
||||
ĐÂY, và màn hình mặc định lúc mở app.
|
||||
"""
|
||||
sub = self.workspace._cowork_tab_idx
|
||||
# Chua chon project thi cong project dang giu sub-tab Cowork dong.
|
||||
# show_history_pane() mo no bang cua sau, bo qua cong — the la noi dung
|
||||
# o Cowork trong khi ca cong lan vet sang deu noi la Project. Man hinh
|
||||
# dung cho nguoi chua co project chinh la man quan ly project.
|
||||
if sub < 0 or not self.workspace.subtab_available(sub):
|
||||
self._goto(self._ROW_WORKSPACE, self.workspace._project_tab_idx)
|
||||
return
|
||||
self._goto(self._ROW_WORKSPACE, sub)
|
||||
self.workspace.show_history_pane()
|
||||
# Bang lich su duoc nhung trong sub-tab Cowork cua MOT project, nen
|
||||
# WorkspaceTab._bind_project da dat set_project_filter(pid) — no chi hien
|
||||
# thread cua project dang chon. Vao day bang link "Tat ca project…" ma van
|
||||
# con bo loc thi nguoi dung tao 5 project chi thay 1. Xoa bo loc SAU
|
||||
# show_history_pane(), vi ham do di qua duong doi sub-tab.
|
||||
self.sidebar.set_project_filter("")
|
||||
|
||||
def _on_rail_recent(self, item, _col: int = 0) -> None:
|
||||
"""Bấm một dòng trong mục GẦN ĐÂY: mở hội thoại đó, hoặc mở khung "Tất cả project…"."""
|
||||
data = item.data(0, Qt.UserRole) or {}
|
||||
if data.get("all"):
|
||||
self._goto(self._ROW_WORKSPACE, self.workspace._cowork_tab_idx)
|
||||
self.workspace.show_history_pane()
|
||||
self.goto_all_projects()
|
||||
return
|
||||
path = data.get("path")
|
||||
if path:
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
"""Màn hình app mở lên lần đầu.
|
||||
|
||||
Trước đây `MainWindow.__init__` gọi `_goto(ROW_WORKSPACE, current_subtab())` rồi
|
||||
ngay sau đó `_restore_sessions()` lại `_show_cowork_tab()` để mở lại hội thoại
|
||||
lần trước. Kết quả: thứ người dùng thấy khi bật app là cuộc trò chuyện cũ, không
|
||||
phải danh sách project — và dòng landing ở trên trông như đang quyết định điều đó
|
||||
trong khi thực ra bị ghi đè vài chục dòng sau.
|
||||
|
||||
Giờ landing được chốt SAU restore: hội thoại cũ vẫn được nạp lại (đó là mục đích
|
||||
của restore — phục hồi sau khi thoát đột ngột), nhưng khung nhìn đầu tiên là
|
||||
"Tất cả project…".
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("PySide6", reason="cần PySide6 để dựng cửa sổ thật")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def window(qapp, tmp_path):
|
||||
from cowork_local.presentation.shell.bootstrap import build_config, build_context
|
||||
from cowork_local.presentation.shell.main_window import MainWindow
|
||||
|
||||
config_path = tmp_path / "config.json"
|
||||
build_config(config_path)
|
||||
win = MainWindow(build_context(config_path))
|
||||
yield win
|
||||
win.close()
|
||||
|
||||
|
||||
def test_mo_app_len_thi_dung_o_man_workspace(window):
|
||||
"""Vẫn là màn Workspace, không phải Dashboard/Lịch/Giám sát."""
|
||||
assert window.pages.currentIndex() == window._ROW_WORKSPACE
|
||||
|
||||
|
||||
def test_mo_app_len_thi_o_khung_tat_ca_project(window):
|
||||
"""Đúng khung mà link "Tất cả project…" ở thanh menu mở ra.
|
||||
|
||||
Trừ khi cổng project đang giữ nó đóng: chưa có project nào thì sub-tab
|
||||
Cowork bị ẩn, và màn hình đúng cho người dùng đó là màn quản lý project.
|
||||
Ép mở bằng cửa sau sẽ để nội dung ở Cowork trong khi cổng nói là Project.
|
||||
"""
|
||||
ws = window.workspace
|
||||
if ws.subtab_available(ws._cowork_tab_idx):
|
||||
assert ws.current_subtab() == ws._cowork_tab_idx
|
||||
assert ws._history_opened is True
|
||||
else:
|
||||
assert ws.current_subtab() == ws._project_tab_idx
|
||||
|
||||
|
||||
def test_bang_lich_su_khong_bi_an_di(window):
|
||||
"""Danh sách phải nhìn thấy được — mở đúng tab mà bảng vẫn gập là vô nghĩa.
|
||||
|
||||
Dùng ``isHidden()`` chứ không ``isVisible()``: test không gọi ``show()`` trên
|
||||
cửa sổ (bật cửa sổ thật lên sẽ nhảy ra trước mặt người đang làm việc), mà
|
||||
``isVisible()`` của widget con chỉ True khi đã có tổ tiên được hiện.
|
||||
``isHidden()`` phản ánh đúng thứ ta quan tâm: có ai gọi ``setVisible(False)``
|
||||
lên nó hay không.
|
||||
"""
|
||||
ws = window.workspace
|
||||
if not ws.subtab_available(ws._cowork_tab_idx):
|
||||
pytest.skip("cổng project đang đóng — bảng lịch sử không thuộc màn này")
|
||||
assert ws._sidebar.isHidden() is False
|
||||
|
||||
|
||||
# ---- vệt sáng thanh menu phải khớp nội dung ------------------------------
|
||||
#
|
||||
# Bug: mở app lên ở khung "Tất cả project…" thì bấm "Project" trên thanh menu
|
||||
# KHÔNG có tác dụng; phải bấm sang mục khác rồi bấm về mới được.
|
||||
#
|
||||
# Nguyên nhân: landing gọi thẳng ``workspace.show_history_pane()``, đổi nội dung
|
||||
# sang sub-tab Cowork nhưng bỏ qua ``_goto`` — chỗ duy nhất dời vệt sáng. Vệt
|
||||
# sáng ở lại "Project", mà QTreeWidget không phát ``currentItemChanged`` khi bấm
|
||||
# lại đúng dòng đang chọn, nên cú bấm rơi vào hư không.
|
||||
|
||||
def _hang_dang_chon(window):
|
||||
"""(page, sub) mà thanh menu đang tô sáng, hoặc None."""
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
for tree in (window.nav, window.nav_bottom):
|
||||
item = tree.currentItem()
|
||||
if item is not None:
|
||||
data = item.data(0, Qt.UserRole) or {}
|
||||
return data.get("page"), data.get("sub")
|
||||
return None
|
||||
|
||||
|
||||
def test_vet_sang_thanh_menu_khop_voi_noi_dung_dang_hien(window):
|
||||
"""Không khớp là cú bấm đầu tiên vào đúng mục đó sẽ rơi vào hư không."""
|
||||
dang_chon = _hang_dang_chon(window)
|
||||
|
||||
assert dang_chon is not None, "thanh menu không tô sáng dòng nào"
|
||||
page, sub = dang_chon
|
||||
assert page == window._ROW_WORKSPACE
|
||||
# Bat bien that su quan trong: vet sang KHOP noi dung. Man hinh ha canh la
|
||||
# cai nao thi tuy cong project, nhung hai thu nay khong bao gio duoc lech.
|
||||
assert sub == window.workspace.current_subtab(), (
|
||||
f"nội dung ở sub-tab {window.workspace.current_subtab()} "
|
||||
f"nhưng thanh menu đang sáng ở {sub}")
|
||||
|
||||
|
||||
def test_bam_project_tren_thanh_menu_an_ngay_lan_dau(window):
|
||||
"""Tái hiện đúng thao tác của người dùng: mở app xong bấm ngay "Project"."""
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
project_sub = window.workspace._project_tab_idx
|
||||
muc_project = None
|
||||
for i in range(window.nav.topLevelItemCount()):
|
||||
it = window.nav.topLevelItem(i)
|
||||
data = it.data(0, Qt.UserRole) or {}
|
||||
if data.get("page") == window._ROW_WORKSPACE and data.get("sub") == project_sub:
|
||||
muc_project = it
|
||||
break
|
||||
if muc_project is None:
|
||||
pytest.skip("thanh menu chưa dựng mục con cho sub-tab Project")
|
||||
|
||||
if window.workspace.current_subtab() == project_sub:
|
||||
pytest.skip("cổng project đang đóng — app đã hạ cánh sẵn ở màn Project")
|
||||
|
||||
assert window.nav.currentItem() is not muc_project, (
|
||||
"mục Project đang được tô sáng sẵn dù nội dung ở Cowork — "
|
||||
"bấm vào nó sẽ không phát tín hiệu nào")
|
||||
|
||||
window.nav.setCurrentItem(muc_project)
|
||||
|
||||
assert window.workspace.current_subtab() == project_sub
|
||||
|
||||
|
||||
def test_khi_cong_project_MO_thi_ha_canh_o_cowork_va_vet_sang_theo(window):
|
||||
"""Nhánh của người dùng ĐÃ có project — nhánh mà bug được báo.
|
||||
|
||||
Môi trường test không có project nào (cố ý: ``core/projects.py`` ghi vào
|
||||
``~/.cowork_local`` thật). Mở cổng bằng tay để đi đúng nhánh đó mà không
|
||||
phải tạo project trên đĩa.
|
||||
"""
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
ws = window.workspace
|
||||
if ws._cowork_tab_idx < 0:
|
||||
pytest.skip("bản dựng này không có sub-tab Cowork")
|
||||
|
||||
ws.tabs.setTabVisible(ws._cowork_tab_idx, True)
|
||||
try:
|
||||
window.goto_all_projects()
|
||||
|
||||
assert ws.current_subtab() == ws._cowork_tab_idx
|
||||
assert ws._history_opened is True
|
||||
|
||||
item = window.nav.currentItem()
|
||||
assert item is not None, "thanh menu không tô sáng dòng nào"
|
||||
data = item.data(0, Qt.UserRole) or {}
|
||||
assert data.get("sub") == ws._cowork_tab_idx, (
|
||||
f"nội dung ở Cowork ({ws._cowork_tab_idx}) "
|
||||
f"nhưng thanh menu sáng ở {data.get('sub')}")
|
||||
finally:
|
||||
ws.tabs.setTabVisible(ws._cowork_tab_idx, False)
|
||||
window.goto_all_projects()
|
||||
Reference in New Issue
Block a user