from __future__ import annotations import types from collections import deque from typing import Any from src.desktop_overlay import CompletionOverlayPayload, DesktopOverlayManager class _FakeWidget: def __init__(self, root: "_FakeTk", *, width: int = 360, height: int = 160) -> None: self._root = root self._width = width self._height = height self._exists = True self._after_ids: dict[str, tuple[int, Any]] = {} def withdraw(self) -> None: return None def overrideredirect(self, *_args: Any, **_kwargs: Any) -> None: return None def attributes(self, *_args: Any, **_kwargs: Any) -> None: return None def configure(self, *_args: Any, **_kwargs: Any) -> None: return None def pack(self, *_args: Any, **_kwargs: Any) -> None: return None def place(self, *_args: Any, **_kwargs: Any) -> None: return None def update_idletasks(self) -> None: return None def winfo_width(self) -> int: return self._width def winfo_height(self) -> int: return self._height def winfo_exists(self) -> bool: return self._exists def geometry(self, *_args: Any, **_kwargs: Any) -> None: return None def deiconify(self) -> None: return None def destroy(self) -> None: self._exists = False def after(self, delay_ms: int, callback: Any) -> str: after_id = self._root._schedule(delay_ms, callback) self._after_ids[after_id] = (delay_ms, callback) return after_id def after_cancel(self, after_id: str) -> None: self._after_ids.pop(after_id, None) self._root._cancel(after_id) class _FakeButton(_FakeWidget): def __init__(self, root: "_FakeTk", command: Any | None = None, **_kwargs: Any) -> None: super().__init__(root) self.command = command class _FakeTk(_FakeWidget): def __init__(self) -> None: super().__init__(self) self._events: deque[tuple[str, int, Any]] = deque() self._event_seq = 0 self.scheduled_delays: list[int] = [] self.cards: list[_FakeWidget] = [] def withdraw(self) -> None: return None def winfo_screenwidth(self) -> int: return 1920 def _schedule(self, delay_ms: int, callback: Any) -> str: after_id = f"after-{self._event_seq}" self._event_seq += 1 self.scheduled_delays.append(delay_ms) self._events.append((after_id, delay_ms, callback)) return after_id def _cancel(self, after_id: str) -> None: self._events = deque(event for event in self._events if event[0] != after_id) def mainloop(self) -> None: iterations = 0 while self._events and iterations < 20: after_id, _delay_ms, callback = self._events.popleft() iterations += 1 callback() if any(not card.winfo_exists() for card in self.cards): return class _FakeTkModule(types.SimpleNamespace): def __init__(self, root: _FakeTk) -> None: super().__init__() self._root = root def Tk(self) -> _FakeTk: return self._root def Toplevel(self, _root: _FakeTk) -> _FakeWidget: card = _FakeWidget(self._root) self._root.cards.append(card) return card def Frame(self, root: _FakeWidget, **_kwargs: Any) -> _FakeWidget: return _FakeWidget(root._root) def Label(self, root: _FakeWidget, **_kwargs: Any) -> _FakeWidget: return _FakeWidget(root._root) def Button(self, root: _FakeWidget, command: Any | None = None, **_kwargs: Any) -> _FakeButton: return _FakeButton(root._root, command=command) def test_completion_overlay_auto_dismisses(monkeypatch: Any) -> None: root = _FakeTk() fake_tk = _FakeTkModule(root) monkeypatch.setitem(__import__("sys").modules, "tkinter", fake_tk) manager = DesktopOverlayManager(auto_dismiss_seconds=0.01) manager._queue.put( CompletionOverlayPayload( job_id="job-123", objective="Write a report", return_message="Finished", steps=5, elapsed_seconds=12.4, ) ) manager._ui_main() assert any(delay == 10 for delay in root.scheduled_delays) assert root.cards[0]._exists is False