fix(workspace): bật nút Sửa project khi đã có project đang mở
Vào sub-tab Project khi đang mở một project thì biểu mẫu hiện đủ dữ liệu nhưng nút "Sửa project" vẫn mờ, không bấm được. Trạng thái bật/mờ của ba nút chỉ được tính trong set_project_editable, mà việc đổi project không đi qua hàm đó — _load_current chỉ nạp lại biểu mẫu. _sync_project_buttons có chạy khi đổi sub-tab nhưng chỉ chỉnh ẩn/hiện, nên nút hiện ra mang theo trạng thái mờ tính từ lúc dựng cửa sổ, khi chưa project nào được chọn. Dồn phép tính về _sync_project_buttons — nơi duy nhất biết đủ cả ba yếu tố — và cho nó chạy thêm khi project_selected phát tín hiệu. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -90,6 +90,7 @@ def _row_layout_of(widget: QWidget) -> QLayout | None:
|
||||
return None
|
||||
|
||||
|
||||
|
||||
class ProjectEditingMixin:
|
||||
"""Danh sách project + CRUD + chế độ sửa. Trộn vào ``WorkspaceTab``.
|
||||
|
||||
@@ -133,6 +134,9 @@ class ProjectEditingMixin:
|
||||
# dung luat ma _new_btn da theo (_new_btn.setVisible(on_project) trong
|
||||
# WorkspaceTab._apply_pane_visibility) — hai nut nay phai theo y nhu vay.
|
||||
self.tabs.currentChanged.connect(self._sync_project_buttons)
|
||||
# Đổi project cũng phải đồng bộ lại: ``_load_current`` nạp form và đặt
|
||||
# ``_current_id`` rồi phát tín hiệu này, nhưng không đụng tới ba nút.
|
||||
self.project_selected.connect(self._sync_project_buttons)
|
||||
|
||||
self.project_list.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
self.project_list.customContextMenuRequested.connect(self._show_project_menu)
|
||||
@@ -153,15 +157,14 @@ class ProjectEditingMixin:
|
||||
Bật: ngược lại, và nút Lưu chuyển sang màu xác nhận (token ``success``).
|
||||
"""
|
||||
self._project_editable = on
|
||||
has_project = bool(getattr(self, "_current_id", ""))
|
||||
|
||||
for field in self._editable_fields():
|
||||
# setReadOnly thay vì setEnabled: ô mờ đi thì không bôi đen copy
|
||||
# được nữa, mà đọc và copy chính là việc của chế độ chỉ-xem.
|
||||
field.setReadOnly(not on)
|
||||
self._browse_btn.setEnabled(on and has_project)
|
||||
self._save_btn.setEnabled(on and has_project)
|
||||
self._edit_btn.setEnabled(not on and has_project)
|
||||
# Ba nút không tự bật/tắt ở đây: ``_sync_project_buttons`` mới là nơi
|
||||
# duy nhất tính trạng thái của chúng, vì nó còn chạy cả khi người dùng
|
||||
# đổi project — lúc đó ``set_project_editable`` không được gọi.
|
||||
self._sync_project_buttons()
|
||||
|
||||
# Nút Lưu xanh lá khi đang sửa (hành động xác nhận), về màu nhấn mặc
|
||||
@@ -171,15 +174,25 @@ class ProjectEditingMixin:
|
||||
self._repolish(self._edit_btn)
|
||||
|
||||
def _sync_project_buttons(self, *_a) -> None:
|
||||
"""Ẩn "Sửa project" và "Lưu project" ngoài sub-tab Project.
|
||||
"""Đồng bộ CẢ hiện/ẩn LẪN bật/mờ của ba nút theo trạng thái hiện tại.
|
||||
|
||||
Chúng nằm trên hàng tiêu đề dùng chung, nên không tự ẩn là chúng hiện
|
||||
cả ở Cowork — nơi không có biểu mẫu project nào để sửa hay lưu.
|
||||
Ẩn ngoài sub-tab Project: chúng nằm trên hàng tiêu đề dùng chung, nên
|
||||
không tự ẩn là chúng hiện cả ở Cowork — nơi không có biểu mẫu project
|
||||
nào để sửa hay lưu.
|
||||
|
||||
Bật/mờ cũng tính ở đây chứ không ở ``set_project_editable``: đổi
|
||||
project KHÔNG đi qua hàm đó (``_load_current`` chỉ nạp lại form), nên
|
||||
để ở đó thì "Sửa project" giữ nguyên trạng thái tính từ lúc dựng —
|
||||
lúc chưa project nào được chọn — và cứ mờ mãi dù project đã mở.
|
||||
"""
|
||||
on_project = self.tabs.currentIndex() == self._project_tab_idx
|
||||
has_project = bool(getattr(self, "_current_id", ""))
|
||||
dang_sua = bool(getattr(self, "_project_editable", False))
|
||||
self._edit_btn.setVisible(on_project and has_project)
|
||||
self._save_btn.setVisible(on_project and has_project)
|
||||
self._edit_btn.setEnabled(not dang_sua and has_project)
|
||||
self._save_btn.setEnabled(dang_sua and has_project)
|
||||
self._browse_btn.setEnabled(dang_sua and has_project)
|
||||
|
||||
@staticmethod
|
||||
def _repolish(widget: QWidget) -> None:
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
"""Nút "Sửa project" phải sáng ngay khi đã có project đang mở.
|
||||
|
||||
Triệu chứng: mở app, chọn một project rồi vào sub-tab Project — biểu mẫu hiện
|
||||
đủ tên, mô tả, thư mục làm việc, nhưng nút "Sửa project" vẫn mờ, không bấm được.
|
||||
|
||||
Nguyên nhân: trạng thái bật/mờ của ba nút chỉ được tính trong
|
||||
``set_project_editable``, mà đổi project KHÔNG đi qua hàm đó — ``_load_current``
|
||||
chỉ nạp lại biểu mẫu. ``_sync_project_buttons`` có chạy khi đổi sub-tab nhưng
|
||||
ngày trước chỉ chỉnh ẩn/hiện, nên nút hiện ra mang theo trạng thái mờ tính từ
|
||||
lúc dựng cửa sổ, khi chưa project nào được chọn.
|
||||
|
||||
Các bài dưới đây chốt cả bốn trạng thái: chưa có project → mờ; có project →
|
||||
sáng; đi vòng qua sub-tab khác rồi quay lại → vẫn sáng; đang sửa dở → mờ lại
|
||||
(nếu không thì "đang sửa" và "chưa sửa" trông giống hệt nhau).
|
||||
|
||||
Hai quy ước bắt buộc, lấy từ ``test_project_editing.py`` và
|
||||
``test_project_gate_subtabs.py`` ngay cạnh:
|
||||
|
||||
* **Không tạo, không xoá project nào.** ``core/projects.py`` gắn
|
||||
``PROJECTS_DIR`` vào ``~/.cowork_local`` THẬT, nên tạo project trong test là
|
||||
ghi vào dữ liệu đang dùng của người chạy test. Trạng thái "đã có project"
|
||||
được đặt thẳng vào ``_current_id`` — đúng biến mà ba nút đọc.
|
||||
* **Một cửa sổ cho cả module.** Dựng ``MainWindow`` cho từng bài làm cả bộ
|
||||
``tests/ui`` chết giữa chừng (Qt đổ stack trace, không phải test nào fail),
|
||||
nên fixture ở đây là ``scope="module"`` và mỗi bài tự đặt trạng thái đầu vào
|
||||
của mình.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
pytest.importorskip("PySide6", reason="cần PySide6 để dựng cửa sổ thật")
|
||||
|
||||
_PID_GIA = "project-test-khong-ghi-dia"
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def ws(qapp, tmp_path_factory):
|
||||
"""Màn Workspace của một MainWindow thật, dùng chung cho cả module."""
|
||||
from cowork_local.presentation.shell.bootstrap import build_config, build_context
|
||||
from cowork_local.presentation.shell.main_window import MainWindow
|
||||
|
||||
config_path = tmp_path_factory.mktemp("cfg") / "config.json"
|
||||
build_config(config_path)
|
||||
window = MainWindow(build_context(config_path))
|
||||
yield window.workspace
|
||||
window.close()
|
||||
|
||||
|
||||
def _nap_project(qapp, ws, pid: str) -> None:
|
||||
"""Đi đúng đường ``_load_current`` đi khi người dùng chọn một project.
|
||||
|
||||
Cố ý KHÔNG gọi ``set_project_editable``: chính vì ``_load_current`` không
|
||||
gọi nó mà lỗi mới tồn tại. Gọi nó ở đây là bài test tự tay bật lại nút rồi
|
||||
khẳng định nút đang bật — nó sẽ xanh cả trên bản chưa sửa.
|
||||
|
||||
``_project_editable`` đặt thẳng về ``False`` vì cửa sổ dùng chung cho cả
|
||||
module: bài trước có thể đã để form ở chế độ sửa, mà nạp một project mới
|
||||
thì form luôn ở chế độ chỉ-xem.
|
||||
"""
|
||||
ws.tabs.setCurrentIndex(ws._project_tab_idx)
|
||||
ws._project_editable = False
|
||||
ws._current_id = pid
|
||||
ws.project_selected.emit(pid)
|
||||
qapp.processEvents()
|
||||
|
||||
|
||||
def _chua_co_project(qapp, ws) -> None:
|
||||
"""Trạng thái chưa chọn project nào, đang ở sub-tab Project."""
|
||||
_nap_project(qapp, ws, "")
|
||||
|
||||
|
||||
def _mo_mot_project(qapp, ws) -> None:
|
||||
"""Trạng thái đang mở một project."""
|
||||
_nap_project(qapp, ws, _PID_GIA)
|
||||
|
||||
|
||||
def test_chua_co_project_thi_nut_sua_mo(qapp, ws):
|
||||
"""Chưa chọn project thì không có gì để sửa — đây là hành vi phải giữ."""
|
||||
_chua_co_project(qapp, ws)
|
||||
|
||||
assert ws._edit_btn.isEnabled() is False
|
||||
assert ws._edit_btn.isHidden() is True
|
||||
|
||||
|
||||
def test_da_co_project_thi_nut_sua_sang(qapp, ws):
|
||||
"""Bài đỏ trước khi sửa: nút hiện ra nhưng vẫn mờ."""
|
||||
_mo_mot_project(qapp, ws)
|
||||
|
||||
assert ws._edit_btn.isHidden() is False, "nút phải hiện khi đã có project"
|
||||
assert ws._edit_btn.isEnabled() is True, "nút phải bấm được khi đã có project"
|
||||
|
||||
|
||||
def test_quay_lai_tab_project_thi_nut_van_sang(qapp, ws):
|
||||
"""Đúng thao tác trong ảnh người dùng gửi: rời tab Project rồi quay lại."""
|
||||
if ws.tabs.count() < 2:
|
||||
pytest.skip("bản dựng này chỉ có một sub-tab, không đi vòng được")
|
||||
_mo_mot_project(qapp, ws)
|
||||
|
||||
ws.tabs.setCurrentIndex(1 if ws._project_tab_idx == 0 else 0)
|
||||
qapp.processEvents()
|
||||
ws.tabs.setCurrentIndex(ws._project_tab_idx)
|
||||
qapp.processEvents()
|
||||
|
||||
assert ws._edit_btn.isHidden() is False
|
||||
assert ws._edit_btn.isEnabled() is True
|
||||
|
||||
|
||||
def test_dang_sua_thi_nut_sua_mo_lai_va_nut_luu_sang(qapp, ws):
|
||||
"""Chống sửa quá tay: "Sửa project" chỉ sáng khi CHƯA ở chế độ sửa."""
|
||||
_mo_mot_project(qapp, ws)
|
||||
ws.set_project_editable(True)
|
||||
qapp.processEvents()
|
||||
|
||||
assert ws._edit_btn.isEnabled() is False
|
||||
assert ws._save_btn.isEnabled() is True
|
||||
assert ws._browse_btn.isEnabled() is True
|
||||
|
||||
|
||||
def test_bo_chon_project_thi_nut_sua_mo_lai(qapp, ws):
|
||||
"""Xoá project đang mở đưa ``_current_id`` về rỗng — nút phải mờ lại."""
|
||||
_mo_mot_project(qapp, ws)
|
||||
ws._current_id = ""
|
||||
ws.project_selected.emit("")
|
||||
qapp.processEvents()
|
||||
|
||||
assert ws._edit_btn.isEnabled() is False
|
||||
assert ws._edit_btn.isHidden() is True
|
||||
Reference in New Issue
Block a user