## Summary epic r04 - begin refactor ## Change Type - [x] Cowork feature - [ ] Bug fix - [ ] Core AI contribution - [ ] Test / hardening - [ ] Performance - [ ] Documentation ## Related Work Cowork Task: Core Repo: http://34.143.229.138/gitea-admin/fsg-ai-core-assets Core AI Issue: Core Task: Related PR: ## Scope What is intentionally included? What is intentionally NOT included? ## Validation - [ ] Unit tests - [ ] Integration tests - [ ] Manual verification - [ ] Regression check Commands / evidence: ## Security Impact Permission / credential / network / customer data impact: ## Compatibility - [ ] No breaking change - [ ] Breaking change documented ## Reviewer Notes Anything Cowork reviewers should pay attention to. --------- Co-authored-by: Anh Tran Nguyen Minh <anhtnm1@fpt.com> Co-authored-by: Huong Le Thi Thien <huongltt35@fpt.com> Co-authored-by: Nam Pham Dinh Thanh <nampdt@fpt.com> Co-authored-by: Vu Dam Tuan <vudt15@fpt.com> Co-authored-by: Hiep Ha Van <hiephv3@fpt.com> Co-authored-by: Lam Hoang Van <lamhv7@fpt.com> Reviewed-on: #7 Co-authored-by: Duy Le Huu <duylh19@fpt.com>
This commit was merged in pull request #7.
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
"""EPIC R08-T11: ScheduleTaskTab shell + KanbanBoardWidget, real Qt offscreen.
|
||||
|
||||
Drives the real widgets end to end (build -> refresh -> drag-drop rule via
|
||||
TaskApplicationService -> refresh) against a tmp_path task repository, the
|
||||
way ``test_history_dir_race.py`` proves R06-T04 against real Qt rather than
|
||||
a double. ``TaskApplicationService``'s own business rules are already unit
|
||||
tested (R07-T04); this file exists to prove the WIDGET is actually wired to
|
||||
that service, not to re-test the rules themselves.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||
|
||||
from cowork_local.config import AppConfig # noqa: E402
|
||||
from cowork_local.infrastructure.persistence.json.task_repository_impl import ( # noqa: E402
|
||||
TaskRepository,
|
||||
)
|
||||
from cowork_local.state import AppContext # noqa: E402
|
||||
|
||||
pytest.importorskip("PySide6", reason="Qt is required for the integration suite")
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def qt_app():
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
return QApplication.instance() or QApplication([])
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ctx(qt_app, tmp_path: Path):
|
||||
return AppContext(AppConfig.load(tmp_path / "config.json"))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tasks_dir(tmp_path: Path) -> Path:
|
||||
d = tmp_path / "tasks"
|
||||
d.mkdir()
|
||||
return d
|
||||
|
||||
|
||||
def test_schedule_task_tab_builds_and_refreshes_with_no_tasks(ctx, tasks_dir):
|
||||
from cowork_local.presentation.scheduling.schedule_task_tab import ScheduleTaskTab
|
||||
|
||||
tab = ScheduleTaskTab(ctx, scheduler=None, tasks_dir=tasks_dir)
|
||||
tab.refresh() # must not raise against an empty repo
|
||||
|
||||
assert tab.kanban.columns.keys() # 7 lanes were built
|
||||
|
||||
|
||||
def test_kanban_renders_a_task_into_its_status_lane(ctx, tasks_dir):
|
||||
from cowork_local.presentation.scheduling.kanban_board_widget import KanbanBoardWidget
|
||||
|
||||
repo = TaskRepository(tasks_dir)
|
||||
task = repo.create("My Task", task_type="cowork")
|
||||
task["status"] = "backlog"
|
||||
repo.save(task)
|
||||
|
||||
board = KanbanBoardWidget(ctx, tasks_dir=tasks_dir)
|
||||
board.refresh()
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
|
||||
backlog_ids = [board.columns["backlog"].item(i).data(Qt.UserRole)
|
||||
for i in range(board.columns["backlog"].count())]
|
||||
assert task["task_id"] in backlog_ids
|
||||
|
||||
|
||||
def test_dropping_a_card_on_done_disables_its_schedule_through_the_real_widget(ctx, tasks_dir):
|
||||
"""Same rule TaskApplicationService.move_to_status covers at the unit
|
||||
level (R07-T04) — this proves the Kanban widget's drop handler actually
|
||||
calls it, end to end, with a real TaskRepository on disk."""
|
||||
from cowork_local.presentation.scheduling.kanban_board_widget import KanbanBoardWidget
|
||||
|
||||
repo = TaskRepository(tasks_dir)
|
||||
task = repo.create("Recurring", task_type="cowork")
|
||||
task["schedule"]["enabled"] = True
|
||||
task["schedule"]["run_at"] = "2026-08-28 09:00"
|
||||
task["schedule"]["repeat_type"] = "daily"
|
||||
task["status"] = "scheduled"
|
||||
repo.save(task)
|
||||
|
||||
board = KanbanBoardWidget(ctx, tasks_dir=tasks_dir)
|
||||
board.refresh()
|
||||
|
||||
board._on_task_dropped(task["task_id"], "done")
|
||||
|
||||
on_disk = repo.get(task["task_id"])
|
||||
assert on_disk["status"] == "done"
|
||||
assert on_disk["schedule"]["enabled"] is False
|
||||
|
||||
|
||||
def test_kanban_edit_requested_is_wired_to_the_shells_edit_task(ctx, tasks_dir, monkeypatch):
|
||||
"""Proves ScheduleTaskTab actually connects
|
||||
``kanban.edit_requested -> self._edit_task`` (not just that the Kanban
|
||||
widget emits the signal in isolation) by monkeypatching the dialog class
|
||||
``_edit_task`` opens and checking it was constructed for the right task."""
|
||||
import cowork_local.ui.task_editor_dialog as task_editor_dialog_module
|
||||
from cowork_local.presentation.scheduling.schedule_task_tab import ScheduleTaskTab
|
||||
|
||||
repo = TaskRepository(tasks_dir)
|
||||
task = repo.create("Editable")
|
||||
repo.save(task)
|
||||
|
||||
seen_task_ids = []
|
||||
|
||||
class _FakeDialog:
|
||||
def __init__(self, task, all_tasks, parent, ctx):
|
||||
seen_task_ids.append(task["task_id"] if task else None)
|
||||
self.edited_task = None
|
||||
|
||||
def exec(self):
|
||||
return False # Cancel — nothing further should happen
|
||||
|
||||
monkeypatch.setattr(task_editor_dialog_module, "TaskEditorDialog", _FakeDialog)
|
||||
|
||||
tab = ScheduleTaskTab(ctx, scheduler=None, tasks_dir=tasks_dir)
|
||||
tab.kanban.edit_requested.emit(task["task_id"])
|
||||
|
||||
assert seen_task_ids == [task["task_id"]]
|
||||
Reference in New Issue
Block a user