Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions tests/test_undo_manager.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
import pytest

from logic.scheduler import Task
from logic.storage import sqlite_storage as storage
from logic.undo_manager import UndoManager


class RecordingScheduler:
def __init__(self):
self.tasks = []
self.deleted = []
self.updated = []
self.completed = []
self.uncompleted = []

def delete_task(self, task_id):
self.deleted.append(task_id)

def update_task(self, task):
self.updated.append(task)

def complete_task(self, task_id):
self.completed.append(task_id)

def uncomplete_task(self, task_id):
self.uncompleted.append(task_id)


class FailingScheduler:
def delete_task(self, _task_id):
Expand All @@ -24,6 +41,64 @@ def test_successful_undo_consumes_action():
assert not manager.can_undo()


def test_undo_delete_restores_original_task(monkeypatch):
scheduler = RecordingScheduler()
manager = UndoManager(scheduler)
task = Task(id="task-1", date="2026-08-28", time="09:00", text="Restore me")
restored = []
monkeypatch.setattr(storage, "create_entry", restored.append)
manager.record_delete(task)

assert manager.undo() == "Undid delete"
assert restored == [task]
assert scheduler.tasks == [task]


def test_undo_edit_restores_snapshot():
scheduler = RecordingScheduler()
manager = UndoManager(scheduler)
snapshot = Task(id="task-1", date="2026-08-28", time="09:00", text="Before")
manager.record_edit(snapshot)

assert manager.undo() == "Undid edit"
assert scheduler.updated == [snapshot]


@pytest.mark.parametrize(
("record_method", "expected_result", "recorded_attribute"),
[
("record_complete", "Undid complete", "uncompleted"),
("record_uncomplete", "Undid uncomplete", "completed"),
],
)
def test_undo_completion_state(record_method, expected_result, recorded_attribute):
scheduler = RecordingScheduler()
manager = UndoManager(scheduler)
getattr(manager, record_method)("task-1")

assert manager.undo() == expected_result
assert getattr(scheduler, recorded_attribute) == ["task-1"]


def test_undo_empty_history_returns_none():
assert UndoManager(RecordingScheduler()).undo() is None


def test_history_limit_drops_oldest_actions():
scheduler = RecordingScheduler()
manager = UndoManager(scheduler)
for index in range(manager.MAX_HISTORY + 1):
manager.record_add(f"task-{index}")

while manager.can_undo():
manager.undo()

assert len(scheduler.deleted) == manager.MAX_HISTORY
assert scheduler.deleted[0] == "task-50"
assert scheduler.deleted[-1] == "task-1"
assert "task-0" not in scheduler.deleted


def test_failed_undo_is_logged_and_remains_available(caplog):
manager = UndoManager(FailingScheduler())
manager.record_add("task-1")
Expand Down