diff --git a/owrx/active/list/__init__.py b/owrx/active/list/__init__.py index 5ab5f0f1..9c859f8b 100644 --- a/owrx/active/list/__init__.py +++ b/owrx/active/list/__init__.py @@ -4,17 +4,32 @@ import logging logger = logging.getLogger(__name__) +class ActiveListChange(ABC): + pass + + +class ActiveListIndexUpdated(ActiveListChange): + def __init__(self, index: int, oldValue, newValue): + self.index = index + self.oldValue = oldValue + self.newValue = newValue + + +class ActiveListIndexAppended(ActiveListChange): + def __init__(self, index: int, newValue): + self.index = index + self.newValue = newValue + + +class ActiveListIndexDeleted(ActiveListChange): + def __init__(self, index: int, oldValue): + self.index = index + self.oldValue = oldValue + + class ActiveListListener(ABC): @abstractmethod - def onIndexChanged(self, index, newValue): - pass - - @abstractmethod - def onAppend(self, newValue): - pass - - @abstractmethod - def onDelete(self, index): + def onListChange(self, changes: list[ActiveListChange]): pass @@ -35,30 +50,29 @@ class ActiveList: def append(self, value): self.delegate.append(value) + self.__fireChanges([ActiveListIndexAppended(len(self) - 1, value)]) + + def __fireChanges(self, changes: list[ActiveListChange]): for listener in self.listeners: try: - listener.onAppend(value) + listener.onListChange(changes) except Exception: - logger.exception("Exception during onAppend notification") + logger.exception("Exception during onListChange notification") def remove(self, value): self.__delitem__(self.delegate.index(value)) def __setitem__(self, key, value): + if self.delegate[key] == value: + return + oldValue = self.delegate[key] self.delegate[key] = value - for listener in self.listeners: - try: - listener.onIndexChanged(key, value) - except Exception: - logger.exception("Exception during onKeyChanged notification") + self.__fireChanges([ActiveListIndexUpdated(key, oldValue, value)]) def __delitem__(self, key): + oldValue = self.delegate[key] del self.delegate[key] - for listener in self.listeners: - try: - listener.onDelete(key) - except Exception: - logger.exception("Exception during onDelete notification") + self.__fireChanges([ActiveListIndexDeleted(key, oldValue)]) def __getitem__(self, key): return self.delegate[key] diff --git a/test/owrx/active/list/test_active_list.py b/test/owrx/active/list/test_active_list.py index 6a63dac8..9b834382 100644 --- a/test/owrx/active/list/test_active_list.py +++ b/test/owrx/active/list/test_active_list.py @@ -1,4 +1,4 @@ -from owrx.active.list import ActiveList +from owrx.active.list import ActiveList, ActiveListIndexUpdated, ActiveListIndexAppended, ActiveListIndexDeleted from unittest import TestCase from unittest.mock import Mock @@ -22,17 +22,23 @@ class ActiveListTest(TestCase): listenerMock = Mock() list.addListener(listenerMock) list[0] = "testvalue" - listenerMock.onIndexChanged.assert_called_once_with(0, "testvalue") + listenerMock.onListChange.assert_called_once() + changes, = listenerMock.onListChange.call_args.args + self.assertEqual(len(changes), 1) + self.assertIsInstance(changes[0], ActiveListIndexUpdated) + self.assertEqual(changes[0].index, 0) + self.assertEqual(changes[0].oldValue, "initialvalue") + self.assertEqual(changes[0].newValue, "testvalue") def testListIndexChangeNotficationNotDisturbedByException(self): list = ActiveList(["initialvalue"]) throwingMock = Mock() - throwingMock.onIndexChanged.side_effect = RuntimeError("this is a drill") + throwingMock.onListChange.side_effect = RuntimeError("this is a drill") list.addListener(throwingMock) listenerMock = Mock() list.addListener(listenerMock) list[0] = "testvalue" - listenerMock.onIndexChanged.assert_called_once_with(0, "testvalue") + listenerMock.onListChange.assert_called_once() def testListAppend(self): list = ActiveList() @@ -45,7 +51,12 @@ class ActiveListTest(TestCase): listenerMock = Mock() list.addListener(listenerMock) list.append("testvalue") - listenerMock.onAppend.assert_called_once_with("testvalue") + listenerMock.onListChange.assert_called_once() + changes, = listenerMock.onListChange.call_args.args + self.assertEqual(len(changes), 1) + self.assertIsInstance(changes[0], ActiveListIndexAppended) + self.assertEqual(changes[0].index, 0) + self.assertEqual(changes[0].newValue, "testvalue") def testListDelete(self): list = ActiveList(["value1", "value2"]) @@ -53,12 +64,17 @@ class ActiveListTest(TestCase): self.assertEqual(len(list), 1) self.assertEqual(list[0], "value2") - def testListDelteNotification(self): + def testListDeleteNotification(self): list = ActiveList(["value1", "value2"]) listenerMock = Mock() list.addListener(listenerMock) del list[0] - listenerMock.onDelete.assert_called_once_with(0) + listenerMock.onListChange.assert_called_once() + changes, = listenerMock.onListChange.call_args.args + self.assertEqual(len(changes), 1) + self.assertIsInstance(changes[0], ActiveListIndexDeleted) + self.assertEqual(changes[0].index, 0) + self.assertEqual(changes[0].oldValue, 'value1') def testListDeleteByValue(self): list = ActiveList(["value1", "value2"]) @@ -77,8 +93,8 @@ class ActiveListTest(TestCase): listenerMock = Mock() list.addListener(listenerMock) list[0] = "testvalue" - listenerMock.onIndexChanged.assert_called_once_with(0, "testvalue") + listenerMock.onListChange.assert_called_once() listenerMock.reset_mock() list.removeListener(listenerMock) list[0] = "someothervalue" - listenerMock.onIndexChanged.assert_not_called() + listenerMock.onListChange.assert_not_called()