From fb82365980cf6f427e6b6ea1b1e6aaa53ec7942d Mon Sep 17 00:00:00 2001 From: Kodjo Sossouvi Date: Fri, 25 Jul 2025 09:50:36 +0200 Subject: [PATCH] Improving functionalities and adding unit tests --- .../undo_redo/components/UndoRedo.py | 71 ++++++++------ tests/helpers.py | 16 +++- tests/test_helpers.py | 4 +- tests/test_undo_redo.py | 94 ++++++++++++++++++- 4 files changed, 151 insertions(+), 34 deletions(-) diff --git a/src/components/undo_redo/components/UndoRedo.py b/src/components/undo_redo/components/UndoRedo.py index 53ef27e..9d21f33 100644 --- a/src/components/undo_redo/components/UndoRedo.py +++ b/src/components/undo_redo/components/UndoRedo.py @@ -7,7 +7,7 @@ from components.BaseComponent import BaseComponentSingleton from components.undo_redo.assets.icons import icon_redo, icon_undo from components.undo_redo.commands import UndoRedoCommandManager from components.undo_redo.constants import UNDO_REDO_INSTANCE_ID -from components_helpers import mk_icon +from components_helpers import mk_icon, mk_tooltip logger = logging.getLogger("UndoRedoApp") @@ -40,50 +40,67 @@ class UndoRedo(BaseComponentSingleton): self.index += 1 def undo(self): - logger.info(f"Undo command") - return self + logger.debug(f"Undo command") + if self.index == 0: + logger.debug(f" No command to undo.") + return self + + self.index -= 1 + command = self.history[self.index] + logger.debug(f" Undoing command {command.name} ({command.desc})") + res = command.undo() + return self, res def redo(self): - logger.info("Redo command") - if self.index > 0: - self.history.clear() - self.index = 0 - else: - self.push("something") - return self + logger.debug("Redo command") + if self.index >= len(self.history): + logger.debug(f" No command to redo.") + return self + + command = self.history[self.index] + logger.debug(f" Redoing command {command.name} ({command.desc})") + res = command.redo() + self.index += 1 + return self, res - def __ft__(self): + def __ft__(self, oob=False): return Div( self._mk_undo(), self._mk_redo(), id=self._id, - cls="flex" + cls="flex", + hx_swap_oob="true" if oob else None ) def _mk_undo(self): if self._can_undo(): - return mk_icon(icon_undo, - size=24, - **self._commands.undo()) + command = self.history[self.index - 1] + return mk_tooltip(mk_icon(icon_undo, + size=24, + **self._commands.undo()), + "Undo") else: - return mk_icon(icon_undo, - size=24, - can_select=False, - cls="mmt-btn-disabled") + return mk_tooltip(mk_icon(icon_undo, + size=24, + can_select=False, + cls="mmt-btn-disabled"), + "Nothing to undo") def _mk_redo(self): if self._can_redo(): - return mk_icon(icon_redo, - size=24, - **self._commands.redo()) + return mk_tooltip(mk_icon(icon_redo, + size=24, + **self._commands.redo()), + "Redo") else: - return mk_icon(icon_redo, - size=24, - can_select=False, - cls="mmt-btn-disabled") + return mk_tooltip(mk_icon(icon_redo, + size=24, + can_select=False, + cls="mmt-btn-disabled"), + "Nothing to redo") def _can_undo(self): return self.index > 0 def _can_redo(self): - return self.index < len(self.history) - 1 + return self.index < len(self.history) diff --git a/tests/helpers.py b/tests/helpers.py index 18c304e..9bb5791 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -43,6 +43,12 @@ class Contains: """ s: str +@dataclasses.dataclass +class DoesNotContain: + """ + To check if the attribute does not contain a specific value + """ + s: str @dataclasses.dataclass class JsonViewerNode: @@ -449,6 +455,11 @@ def matches(actual, expected, path=""): elif isinstance(expected.attrs[expected_attr], Contains): assert expected.attrs[expected_attr].s in actual.attrs[expected_attr], \ f"{print_path(path)}Attribute '{expected_attr}' does not contain '{expected.attrs[expected_attr].s}': actual='{actual.attrs[expected_attr]}', expected ='{expected.attrs[expected_attr].s}'." + + elif isinstance(expected.attrs[expected_attr], DoesNotContain): + assert expected.attrs[expected_attr].s not in actual.attrs[expected_attr], \ + f"{print_path(path)}Attribute '{expected_attr}' does contain '{expected.attrs[expected_attr].s}' while it must not: actual='{actual.attrs[expected_attr]}'." + else: assert actual.attrs[expected_attr] == expected.attrs[expected_attr], \ @@ -750,13 +761,14 @@ def icon(name: str): return NotStr(f'