feat(modal): Add patch approval modal manager
- Create src/patch_modal.py with PatchModalManager class - Manage patch approval workflow: request, apply, reject - Provide singleton access via get_patch_modal_manager() - Add 8 unit tests for modal manager
This commit is contained in:
73
src/patch_modal.py
Normal file
73
src/patch_modal.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from typing import Optional, Callable, List
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
@dataclass
|
||||
class PendingPatch:
|
||||
patch_text: str
|
||||
file_paths: List[str]
|
||||
generated_by: str
|
||||
timestamp: float
|
||||
|
||||
class PatchModalManager:
|
||||
def __init__(self):
|
||||
self._pending_patch: Optional[PendingPatch] = None
|
||||
self._show_modal: bool = False
|
||||
self._on_apply_callback: Optional[Callable[[str], bool]] = None
|
||||
self._on_reject_callback: Optional[Callable[[], None]] = None
|
||||
|
||||
def request_patch_approval(self, patch_text: str, file_paths: List[str], generated_by: str = "Tier 4 QA") -> bool:
|
||||
from time import time
|
||||
self._pending_patch = PendingPatch(
|
||||
patch_text=patch_text,
|
||||
file_paths=file_paths,
|
||||
generated_by=generated_by,
|
||||
timestamp=time()
|
||||
)
|
||||
self._show_modal = True
|
||||
return True
|
||||
|
||||
def get_pending_patch(self) -> Optional[PendingPatch]:
|
||||
return self._pending_patch
|
||||
|
||||
def is_modal_shown(self) -> bool:
|
||||
return self._show_modal
|
||||
|
||||
def set_apply_callback(self, callback: Callable[[str], bool]) -> None:
|
||||
self._on_apply_callback = callback
|
||||
|
||||
def set_reject_callback(self, callback: Callable[[], None]) -> None:
|
||||
self._on_reject_callback = callback
|
||||
|
||||
def apply_patch(self, patch_text: str) -> bool:
|
||||
if self._on_apply_callback:
|
||||
return self._on_apply_callback(patch_text)
|
||||
return False
|
||||
|
||||
def reject_patch(self) -> None:
|
||||
self._pending_patch = None
|
||||
self._show_modal = False
|
||||
if self._on_reject_callback:
|
||||
self._on_reject_callback()
|
||||
|
||||
def close_modal(self) -> None:
|
||||
self._show_modal = False
|
||||
|
||||
def reset(self) -> None:
|
||||
self._pending_patch = None
|
||||
self._show_modal = False
|
||||
self._on_apply_callback = None
|
||||
self._on_reject_callback = None
|
||||
|
||||
_patch_modal_manager: Optional[PatchModalManager] = None
|
||||
|
||||
def get_patch_modal_manager() -> PatchModalManager:
|
||||
global _patch_modal_manager
|
||||
if _patch_modal_manager is None:
|
||||
_patch_modal_manager = PatchModalManager()
|
||||
return _patch_modal_manager
|
||||
|
||||
def reset_patch_modal_manager() -> None:
|
||||
global _patch_modal_manager
|
||||
if _patch_modal_manager:
|
||||
_patch_modal_manager.reset()
|
||||
_patch_modal_manager = None
|
||||
89
tests/test_patch_modal.py
Normal file
89
tests/test_patch_modal.py
Normal file
@@ -0,0 +1,89 @@
|
||||
import pytest
|
||||
from src.patch_modal import (
|
||||
PatchModalManager, PendingPatch,
|
||||
get_patch_modal_manager, reset_patch_modal_manager
|
||||
)
|
||||
|
||||
def test_patch_modal_manager_init():
|
||||
manager = PatchModalManager()
|
||||
assert manager.get_pending_patch() is None
|
||||
assert manager.is_modal_shown() is False
|
||||
|
||||
def test_request_patch_approval():
|
||||
manager = PatchModalManager()
|
||||
patch_text = "--- a/test.py\n+++ b/test.py\n@@ -1 +1 @@\n-old\n+new"
|
||||
file_paths = ["test.py"]
|
||||
|
||||
result = manager.request_patch_approval(patch_text, file_paths)
|
||||
assert result is True
|
||||
assert manager.is_modal_shown() is True
|
||||
|
||||
pending = manager.get_pending_patch()
|
||||
assert pending is not None
|
||||
assert pending.patch_text == patch_text
|
||||
assert pending.file_paths == file_paths
|
||||
|
||||
def test_reject_patch():
|
||||
manager = PatchModalManager()
|
||||
manager.request_patch_approval("diff", ["file.py"])
|
||||
|
||||
manager.reject_patch()
|
||||
assert manager.get_pending_patch() is None
|
||||
assert manager.is_modal_shown() is False
|
||||
|
||||
def test_close_modal():
|
||||
manager = PatchModalManager()
|
||||
manager.request_patch_approval("diff", ["file.py"])
|
||||
|
||||
manager.close_modal()
|
||||
assert manager.is_modal_shown() is False
|
||||
|
||||
def test_apply_callback():
|
||||
manager = PatchModalManager()
|
||||
called = []
|
||||
|
||||
def apply_fn(patch: str) -> bool:
|
||||
called.append(patch)
|
||||
return True
|
||||
|
||||
manager.set_apply_callback(apply_fn)
|
||||
|
||||
patch_text = "--- a/test.py\n+++ b/test.py\n"
|
||||
result = manager.apply_patch(patch_text)
|
||||
|
||||
assert result is True
|
||||
assert len(called) == 1
|
||||
assert called[0] == patch_text
|
||||
|
||||
def test_reject_callback():
|
||||
manager = PatchModalManager()
|
||||
called = []
|
||||
|
||||
def reject_fn() -> None:
|
||||
called.append(True)
|
||||
|
||||
manager.set_reject_callback(reject_fn)
|
||||
manager.reject_patch()
|
||||
|
||||
assert len(called) == 1
|
||||
|
||||
def test_reset():
|
||||
manager = PatchModalManager()
|
||||
manager.request_patch_approval("diff", ["file.py"])
|
||||
manager.set_apply_callback(lambda x: True)
|
||||
manager.set_reject_callback(lambda: None)
|
||||
|
||||
manager.reset()
|
||||
|
||||
assert manager.get_pending_patch() is None
|
||||
assert manager.is_modal_shown() is False
|
||||
|
||||
def test_get_patch_modal_manager_singleton():
|
||||
reset_patch_modal_manager()
|
||||
|
||||
mgr1 = get_patch_modal_manager()
|
||||
mgr2 = get_patch_modal_manager()
|
||||
|
||||
assert mgr1 is mgr2
|
||||
|
||||
reset_patch_modal_manager()
|
||||
Reference in New Issue
Block a user