Fixed unit tests

This commit is contained in:
2025-08-01 18:55:40 +02:00
parent a6f765c624
commit 3ca23449e4
5 changed files with 181 additions and 25 deletions

View File

@@ -1,6 +1,7 @@
import logging import logging
from dataclasses import dataclass from dataclasses import dataclass
from fastcore.xml import FT
from fasthtml.components import * from fasthtml.components import *
from components.BaseComponent import BaseComponentSingleton from components.BaseComponent import BaseComponentSingleton
@@ -8,6 +9,7 @@ from components.undo_redo.assets.icons import icon_redo, icon_undo
from components.undo_redo.commands import UndoRedoCommandManager from components.undo_redo.commands import UndoRedoCommandManager
from components.undo_redo.constants import UNDO_REDO_INSTANCE_ID, UndoRedoAttrs from components.undo_redo.constants import UNDO_REDO_INSTANCE_ID, UndoRedoAttrs
from components_helpers import mk_icon, mk_tooltip from components_helpers import mk_icon, mk_tooltip
from core.settings_management import NoDefault
logger = logging.getLogger("UndoRedoApp") logger = logging.getLogger("UndoRedoApp")
@@ -64,13 +66,19 @@ class UndoRedo(BaseComponentSingleton):
previous_state = self._settings_manager.load(self._session, None, digest=previous.digest) previous_state = self._settings_manager.load(self._session, None, digest=previous.digest)
# reapply the state # reapply the state
current_state[current.key] = previous_state[current.key] if previous_state is not NoDefault:
current_state[current.key] = previous_state[current.key]
else:
del current_state[current.key]
self._settings_manager.save(self._session, current.entry, current_state) self._settings_manager.save(self._session, current.entry, current_state)
self.index -= 1 self.index -= 1
if current.attrs.on_undo is not None: if current.attrs.on_undo is not None:
return self, current.attrs.on_undo() ret = current.attrs.on_undo()
if isinstance(ret, FT) and 'id' in ret.attrs:
ret.attrs["hx-swap-oob"] = "true"
return self, ret
else: else:
return self return self
@@ -87,13 +95,19 @@ class UndoRedo(BaseComponentSingleton):
next_state = self._settings_manager.load(self._session, None, digest=next_.digest) next_state = self._settings_manager.load(self._session, None, digest=next_.digest)
# reapply the state # reapply the state
current_state[current.key] = next_state[current.key] if current_state is not NoDefault:
current_state[current.key] = next_state[current.key]
else:
current_state = {current.key : next_state[current.key]}
self._settings_manager.save(self._session, current.entry, current_state) self._settings_manager.save(self._session, current.entry, current_state)
self.index += 1 self.index += 1
if current.attrs.on_undo is not None: if current.attrs.on_redo is not None:
return self, current.attrs.on_redo() ret = current.attrs.on_undo()
if isinstance(ret, FT) and 'id' in ret.attrs:
ret.attrs["hx-swap-oob"] = "true"
return self, ret
else: else:
return self return self
@@ -125,7 +139,7 @@ class UndoRedo(BaseComponentSingleton):
def _mk_redo(self): def _mk_redo(self):
if self._can_redo(): if self._can_redo():
command = self.history[self.index] command = self.history[self.index + 1]
return mk_tooltip(mk_icon(icon_redo, return mk_tooltip(mk_icon(icon_redo,
size=24, size=24,
**self._commands.redo()), **self._commands.redo()),

View File

@@ -1,20 +1,66 @@
import os
import shutil
import pytest import pytest
from fasthtml.components import Div from fasthtml.components import Div
from components.undo_redo.components.UndoRedo import UndoRedo 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 core.settings_management import SettingsManager, MemoryDbEngine
from helpers import matches, div_icon, Contains, DoesNotContain from helpers import matches, div_icon, Contains, DoesNotContain
from my_mocks import tabs_manager 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 @pytest.fixture
def undo_redo(session, tabs_manager): def undo_redo(session, tabs_manager, settings_manager):
return UndoRedo(session, return UndoRedo(session,
UndoRedo.create_component_id(session), UndoRedo.create_component_id(session),
settings_manager=SettingsManager(engine=MemoryDbEngine()), settings_manager=settings_manager,
tabs_manager=tabs_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): def test_i_can_render(undo_redo):
actual = undo_redo.__ft__() actual = undo_redo.__ft__()
expected = Div( expected = Div(
@@ -26,13 +72,13 @@ def test_i_can_render(undo_redo):
assert matches(actual, expected) assert matches(actual, expected)
def test_i_can_render_when_undoing_and_redoing(undo_redo): def test_i_can_render_when_undoing_and_redoing(session, settings_manager, undo_redo):
undo_redo.push(UndoableCommand(0, 1)) init_command(session, settings_manager, undo_redo, "1")
undo_redo.push(UndoableCommand(1, 2)) init_command(session, settings_manager, undo_redo, "2")
actual = undo_redo.__ft__() actual = undo_redo.__ft__()
expected = Div( expected = Div(
Div(div_icon("undo", cls=DoesNotContain("mmt-btn-disabled")), data_tooltip="Undo 'Set new value'."), 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."), Div(div_icon("redo", cls=Contains("mmt-btn-disabled")), data_tooltip="Nothing to redo."),
id=undo_redo.get_id(), id=undo_redo.get_id(),
) )
@@ -41,8 +87,8 @@ def test_i_can_render_when_undoing_and_redoing(undo_redo):
undo_redo.undo() # The command is now undone. We can redo it and undo the first command. undo_redo.undo() # The command is now undone. We can redo it and undo the first command.
actual = undo_redo.__ft__() actual = undo_redo.__ft__()
expected = Div( expected = Div(
Div(div_icon("undo", cls=DoesNotContain("mmt-btn-disabled")), data_tooltip="Undo 'Set new value'."), 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 new value'."), Div(div_icon("redo", cls=DoesNotContain("mmt-btn-disabled")), data_tooltip="Redo 'Set value to 2'."),
id=undo_redo.get_id(), id=undo_redo.get_id(),
) )
assert matches(actual, expected) assert matches(actual, expected)
@@ -75,26 +121,48 @@ def test_i_can_render_when_undoing_and_redoing(undo_redo):
assert matches(actual, expected) assert matches(actual, expected)
def test_i_can_undo_and_redo(undo_redo): def test_values_are_correctly_reset(session, settings_manager, undo_redo):
undo_redo.push(UndoableCommand(0, 1)) # checks that the values are correctly returned
undo_redo.push(UndoableCommand(1, 2)) # 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() self, res = undo_redo.undo()
expected = Div(1, hx_swap_oob="true") expected = Div("1", id='an_id', hx_swap_oob="true")
assert matches(res, expected) assert matches(res, expected)
self, res = undo_redo.redo() self, res = undo_redo.redo()
expected = Div(2, hx_swap_oob="true") expected = Div("2", id='an_id', hx_swap_oob="true")
assert matches(res, expected) assert matches(res, expected)
def test_history_is_rewritten_when_pushing_a_command(undo_redo): def test_i_can_manage_when_the_entry_was_not_present(session, settings_manager, undo_redo):
undo_redo.push(UndoableCommand(0, 1)) def on_undo():
undo_redo.push(UndoableCommand(1, 2)) snapshot = settings_manager.load(session, TEST_DB_ENTRY)
undo_redo.push(UndoableCommand(2, 3)) 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()
undo_redo.undo() undo_redo.undo()
undo_redo.push(UndoableCommand(1, 5)) init_command(session, settings_manager, undo_redo, "5")
assert len(undo_redo.history) == 2 assert len(undo_redo.history) == 3 # do not forget that history always has a default command with digest = None

View File

@@ -1,11 +1,15 @@
from unittest.mock import MagicMock
import pytest import pytest
from fastcore.basics import NotStr from fastcore.basics import NotStr
from fasthtml.components import * from fasthtml.components import *
from fasthtml.xtend import Script from fasthtml.xtend import Script
from components.undo_redo.components.UndoRedo import UndoRedo
from components.workflows.components.WorkflowDesigner import WorkflowDesigner, COMPONENT_TYPES from components.workflows.components.WorkflowDesigner import WorkflowDesigner, COMPONENT_TYPES
from components.workflows.constants import ProcessorTypes from components.workflows.constants import ProcessorTypes
from components.workflows.db_management import WorkflowsDesignerSettings, WorkflowComponent, Connection from components.workflows.db_management import WorkflowsDesignerSettings, WorkflowComponent, Connection
from core.instance_manager import InstanceManager
from core.settings_management import SettingsManager, MemoryDbEngine from core.settings_management import SettingsManager, MemoryDbEngine
from helpers import matches, Contains from helpers import matches, Contains
from my_mocks import tabs_manager from my_mocks import tabs_manager
@@ -13,6 +17,27 @@ from my_mocks import tabs_manager
TEST_WORKFLOW_DESIGNER_ID = "workflow_designer_id" TEST_WORKFLOW_DESIGNER_ID = "workflow_designer_id"
@pytest.fixture(autouse=True)
def mock_undo_redo(session):
# Create a mock UndoRedo instance
undo_redo = MagicMock(spec=UndoRedo)
# Store original get method
original_get = InstanceManager.get
def mock_get(sess, instance_id, *args, **kwargs):
if instance_id == UndoRedo.create_component_id(sess):
return undo_redo
return original_get(sess, instance_id, *args, **kwargs)
# Replace get method with our mock
InstanceManager.get = mock_get
yield undo_redo
# Restore original get method after test
InstanceManager.get = original_get
@pytest.fixture @pytest.fixture
def designer(session, tabs_manager): def designer(session, tabs_manager):
return WorkflowDesigner(session=session, _id=TEST_WORKFLOW_DESIGNER_ID, return WorkflowDesigner(session=session, _id=TEST_WORKFLOW_DESIGNER_ID,

View File

@@ -4,10 +4,12 @@ import pandas as pd
import pytest import pytest
from pandas.testing import assert_frame_equal from pandas.testing import assert_frame_equal
from components.undo_redo.components.UndoRedo import UndoRedo
from components.workflows.components.WorkflowDesigner import COMPONENT_TYPES, WorkflowDesigner from components.workflows.components.WorkflowDesigner import COMPONENT_TYPES, WorkflowDesigner
from components.workflows.components.WorkflowPlayer import WorkflowPlayer, WorkflowsPlayerError from components.workflows.components.WorkflowPlayer import WorkflowPlayer, WorkflowsPlayerError
from components.workflows.constants import ProcessorTypes from components.workflows.constants import ProcessorTypes
from components.workflows.db_management import WorkflowComponent, Connection, ComponentState, WorkflowsDesignerSettings from components.workflows.db_management import WorkflowComponent, Connection, ComponentState, WorkflowsDesignerSettings
from core.instance_manager import InstanceManager
from core.settings_management import SettingsManager, MemoryDbEngine from core.settings_management import SettingsManager, MemoryDbEngine
from my_mocks import tabs_manager from my_mocks import tabs_manager
from workflow.engine import DataProcessorError from workflow.engine import DataProcessorError
@@ -16,6 +18,27 @@ TEST_WORKFLOW_DESIGNER_ID = "workflow_designer_id"
TEST_WORKFLOW_PLAYER_ID = "workflow_player_id" TEST_WORKFLOW_PLAYER_ID = "workflow_player_id"
@pytest.fixture(autouse=True)
def mock_undo_redo(session):
# Create a mock UndoRedo instance
undo_redo = MagicMock(spec=UndoRedo)
# Store original get method
original_get = InstanceManager.get
def mock_get(sess, instance_id, *args, **kwargs):
if instance_id == UndoRedo.create_component_id(sess):
return undo_redo
return original_get(sess, instance_id, *args, **kwargs)
# Replace get method with our mock
InstanceManager.get = mock_get
yield undo_redo
# Restore original get method after test
InstanceManager.get = original_get
@pytest.fixture @pytest.fixture
def settings_manager(): def settings_manager():
return SettingsManager(MemoryDbEngine()) return SettingsManager(MemoryDbEngine())

View File

@@ -1,8 +1,12 @@
from unittest.mock import MagicMock
import pytest import pytest
from fasthtml.components import * from fasthtml.components import *
from components.form.components.MyForm import FormField, MyForm from components.form.components.MyForm import FormField, MyForm
from components.undo_redo.components.UndoRedo import UndoRedo
from components.workflows.components.Workflows import Workflows from components.workflows.components.Workflows import Workflows
from core.instance_manager import InstanceManager
from core.settings_management import SettingsManager, MemoryDbEngine from core.settings_management import SettingsManager, MemoryDbEngine
from helpers import matches, div_icon, search_elements_by_name, Contains from helpers import matches, div_icon, search_elements_by_name, Contains
from my_mocks import tabs_manager from my_mocks import tabs_manager
@@ -18,6 +22,28 @@ def workflows(session, tabs_manager):
tabs_manager=tabs_manager) tabs_manager=tabs_manager)
@pytest.fixture(autouse=True)
def mock_undo_redo(session):
# Create a mock UndoRedo instance
undo_redo = MagicMock(spec=UndoRedo)
# Store original get method
original_get = InstanceManager.get
def mock_get(sess, instance_id, *args, **kwargs):
if instance_id == UndoRedo.create_component_id(sess):
return undo_redo
return original_get(sess, instance_id, *args, **kwargs)
# Replace get method with our mock
InstanceManager.get = mock_get
yield undo_redo
# Restore original get method after test
InstanceManager.get = original_get
def test_render_no_workflow(workflows): def test_render_no_workflow(workflows):
actual = workflows.__ft__() actual = workflows.__ft__()
expected = Div( expected = Div(