import os import shutil import pytest from fasthtml.components import Div from components.undo_redo.components.UndoRedo import UndoRedo from components.undo_redo.constants import UndoRedoAttrs from core.dbengine import DbEngine from core.settings_management import SettingsManager, MemoryDbEngine from helpers import matches, div_icon, Contains, DoesNotContain from my_mocks import tabs_manager DB_ENGINE_ROOT = "undo_redo_test_db" TEST_DB_ENTRY = "TestDbEntry" TEST_DB_KEY = "TestDbKey" class TestCommand: def __init__(self, value): self.value = value def __eq__(self, other): if not isinstance(other, TestCommand): return False return self.value == other.value def __hash__(self): return hash(self.value) @pytest.fixture() def engine(session): if os.path.exists(DB_ENGINE_ROOT): shutil.rmtree(DB_ENGINE_ROOT) engine = DbEngine(DB_ENGINE_ROOT) engine.init(session["user_id"]) yield engine shutil.rmtree(DB_ENGINE_ROOT) @pytest.fixture() def settings_manager(engine): return SettingsManager(engine=engine) @pytest.fixture def undo_redo(session, tabs_manager, settings_manager): return UndoRedo(session, UndoRedo.create_component_id(session), settings_manager=settings_manager, tabs_manager=tabs_manager) def init_command(session, settings_manager, undo_redo, value, on_undo=None): settings_manager.save(session, TEST_DB_ENTRY, {TEST_DB_KEY: TestCommand(value)}) undo_redo.snapshot(UndoRedoAttrs(f"Set value to {value}", on_undo=on_undo), TEST_DB_ENTRY, TEST_DB_KEY) def test_i_can_render(undo_redo): actual = undo_redo.__ft__() expected = Div( Div(div_icon("undo", cls=Contains("mmt-btn-disabled")), data_tooltip="Nothing to undo."), Div(div_icon("redo", cls=Contains("mmt-btn-disabled")), data_tooltip="Nothing to redo."), id=undo_redo.get_id(), ) assert matches(actual, expected) def test_i_can_render_when_undoing_and_redoing(session, settings_manager, undo_redo): init_command(session, settings_manager, undo_redo, "1") init_command(session, settings_manager, undo_redo, "2") actual = undo_redo.__ft__() expected = Div( Div(div_icon("undo", cls=DoesNotContain("mmt-btn-disabled")), data_tooltip="Undo 'Set value to 2'."), Div(div_icon("redo", cls=Contains("mmt-btn-disabled")), data_tooltip="Nothing to redo."), id=undo_redo.get_id(), ) assert matches(actual, expected) undo_redo.undo() # The command is now undone. We can redo it and undo the first command. actual = undo_redo.__ft__() expected = Div( Div(div_icon("undo", cls=DoesNotContain("mmt-btn-disabled")), data_tooltip="Undo 'Set value to 1'."), Div(div_icon("redo", cls=DoesNotContain("mmt-btn-disabled")), data_tooltip="Redo 'Set value to 2'."), id=undo_redo.get_id(), ) assert matches(actual, expected) undo_redo.undo() # Undo again, I cannot undo anymore. actual = undo_redo.__ft__() expected = Div( Div(div_icon("undo", cls=Contains("mmt-btn-disabled"))), Div(div_icon("redo", cls=DoesNotContain("mmt-btn-disabled"))), id=undo_redo.get_id(), ) assert matches(actual, expected) undo_redo.redo() # Redo once. actual = undo_redo.__ft__() expected = Div( Div(div_icon("undo", cls=DoesNotContain("mmt-btn-disabled"))), Div(div_icon("redo", cls=DoesNotContain("mmt-btn-disabled"))), id=undo_redo.get_id(), ) assert matches(actual, expected) undo_redo.redo() # Redo a second time. actual = undo_redo.__ft__() expected = Div( Div(div_icon("undo", cls=DoesNotContain("mmt-btn-disabled"))), Div(div_icon("redo", cls=Contains("mmt-btn-disabled"))), id=undo_redo.get_id(), ) assert matches(actual, expected) def test_values_are_correctly_reset(session, settings_manager, undo_redo): # checks that the values are correctly returned # Only checks that hx_swap_oob="true" is automatically put when id is present in the return def on_undo(): current = settings_manager.get(session, TEST_DB_ENTRY, TEST_DB_KEY) return Div(current.value, id='an_id') init_command(session, settings_manager, undo_redo, "1", on_undo=on_undo) init_command(session, settings_manager, undo_redo, "2", on_undo=on_undo) self, res = undo_redo.undo() expected = Div("1", id='an_id', hx_swap_oob="true") assert matches(res, expected) self, res = undo_redo.redo() expected = Div("2", id='an_id', hx_swap_oob="true") assert matches(res, expected) def test_i_can_manage_when_the_entry_was_not_present(session, settings_manager, undo_redo): def on_undo(): snapshot = settings_manager.load(session, TEST_DB_ENTRY) if TEST_DB_KEY in snapshot: return Div(snapshot[TEST_DB_KEY].value, id='an_id') else: return Div("**Not Found**", id='an_id') init_command(session, settings_manager, undo_redo, "1", on_undo=on_undo) self, res = undo_redo.undo() expected = Div("**Not Found**", id='an_id', hx_swap_oob="true") assert matches(res, expected) def test_history_is_rewritten_when_pushing_a_command_after_undo(session, settings_manager, undo_redo): init_command(session, settings_manager, undo_redo, "1") init_command(session, settings_manager, undo_redo, "2") init_command(session, settings_manager, undo_redo, "3") undo_redo.undo() undo_redo.undo() init_command(session, settings_manager, undo_redo, "5") assert len(undo_redo.history) == 3 # do not forget that history always has a default command with digest = None