"""Tests für sn_basis/ui/navigation.py::Navigation (Issue #67, ohne init_ui – s. Docstring dort).""" import unittest from types import SimpleNamespace from unittest.mock import Mock, patch from sn_basis.ui.navigation import Navigation class _FakeQAction: def __init__(self, text, parent=None) -> None: self.text = text self.parent = parent self.tooltip = None self.checkable = None self.checked = None self.triggered = SimpleNamespace(connect=Mock()) def setToolTip(self, text) -> None: self.tooltip = text def setCheckable(self, value: bool) -> None: self.checkable = value def setChecked(self, value: bool) -> None: self.checked = value class _FakeGroup: def __init__(self) -> None: self.added = [] def addAction(self, action) -> None: self.added.append(action) class _FakeMenuOrToolbar: def __init__(self) -> None: self.cleared = False self.added = [] self.removed = [] def clear(self) -> None: self.cleared = True self.added = [] def addAction(self, action) -> None: self.added.append(action) def removeAction(self, action) -> None: self.removed.append(action) class AddActionTests(unittest.TestCase): def test_ohne_plugin_group_liefert_none(self): """Prüft, dass add_action ohne initialisierte Aktionsgruppe None liefert und nichts anlegt.""" nav = Navigation() ergebnis = nav.add_action("Text", callback=lambda: None) self.assertIsNone(ergebnis) self.assertEqual(nav.actions, []) @patch("sn_basis.ui.navigation.get_main_window", return_value=object()) @patch("sn_basis.ui.navigation.QAction", _FakeQAction) def test_erzeugt_action_und_fuegt_sie_gruppe_und_liste_hinzu(self, _mock_window): """Prüft, dass add_action eine Action erzeugt, ihr die Gruppe zuweist und sie priorisiert in der Liste ablegt.""" nav = Navigation() nav.plugin_group = _FakeGroup() callback = Mock() action = nav.add_action("Mein Text", callback, tooltip="Tooltip", priority=5) self.assertEqual(action.text, "Mein Text") self.assertEqual(action.tooltip, "Tooltip") self.assertTrue(action.checkable) action.triggered.connect.assert_called_once_with(callback) self.assertIn(action, nav.plugin_group.added) self.assertEqual(nav.actions, [(5, action)]) class FinalizeMenuAndToolbarTests(unittest.TestCase): def test_ohne_menu_oder_toolbar_tut_nichts(self): """Prüft, dass finalize_menu_and_toolbar ohne Menü oder Toolbar keine Ausnahme wirft.""" nav = Navigation() nav.finalize_menu_and_toolbar() # darf keine Ausnahme werfen def test_sortiert_und_fuegt_aktionen_in_prioritaetsreihenfolge_hinzu(self): """Prüft, dass finalize_menu_and_toolbar Aktionen nach Priorität sortiert in Menü und Toolbar einträgt.""" nav = Navigation() nav.menu = _FakeMenuOrToolbar() nav.toolbar = _FakeMenuOrToolbar() nav.actions = [(20, "a2"), (10, "a1")] nav.finalize_menu_and_toolbar() self.assertTrue(nav.menu.cleared) self.assertTrue(nav.toolbar.cleared) self.assertEqual(nav.menu.added, ["a1", "a2"]) self.assertEqual(nav.toolbar.added, ["a1", "a2"]) class SetActivePluginTests(unittest.TestCase): def test_setzt_nur_aktive_action_auf_checked(self): """Prüft, dass set_active_plugin nur die übergebene Action markiert, alle anderen abwählt.""" action1, action2 = _FakeQAction("a1"), _FakeQAction("a2") nav = Navigation() nav.actions = [(1, action1), (2, action2)] nav.set_active_plugin(action2) self.assertFalse(action1.checked) self.assertTrue(action2.checked) def test_ohne_aktive_action_setzt_alle_auf_false(self): """Prüft, dass set_active_plugin ohne aktive Action alle bestehenden Actions abwählt.""" action1 = _FakeQAction("a1") nav = Navigation() nav.actions = [(1, action1)] nav.set_active_plugin(None) # darf keine Ausnahme werfen self.assertFalse(action1.checked) class RemoveActionTests(unittest.TestCase): def test_none_action_tut_nichts(self): """Prüft, dass remove_action mit None keine Ausnahme wirft und die Liste unverändert lässt.""" nav = Navigation() nav.actions = [(1, "a1")] nav.remove_action(None) self.assertEqual(nav.actions, [(1, "a1")]) def test_entfernt_action_aus_menu_toolbar_und_liste(self): """Prüft, dass remove_action die Action aus Menü, Toolbar und der internen Liste entfernt.""" nav = Navigation() nav.menu = _FakeMenuOrToolbar() nav.toolbar = _FakeMenuOrToolbar() nav.actions = [(1, "a1"), (2, "a2")] nav.remove_action("a1") self.assertEqual(nav.menu.removed, ["a1"]) self.assertEqual(nav.toolbar.removed, ["a1"]) self.assertEqual(nav.actions, [(2, "a2")]) def test_ohne_menu_oder_toolbar_entfernt_trotzdem_aus_liste(self): """Prüft, dass remove_action ohne Menü/Toolbar trotzdem aus der internen Liste entfernt.""" nav = Navigation() nav.actions = [(1, "a1")] nav.remove_action("a1") self.assertEqual(nav.actions, []) class RemoveAllTests(unittest.TestCase): @patch("sn_basis.ui.navigation.remove_toolbar") @patch("sn_basis.ui.navigation.remove_menu") def test_entfernt_menu_toolbar_und_setzt_zustand_zurueck(self, mock_remove_menu, mock_remove_toolbar): """Prüft, dass remove_all Menü und Toolbar über die Wrapper entfernt und den Navigationszustand zurücksetzt.""" nav = Navigation() fake_menu, fake_toolbar = Mock(), Mock() nav.menu = fake_menu nav.toolbar = fake_toolbar nav.actions = [(1, "a1")] nav.plugin_group = object() nav.remove_all() mock_remove_menu.assert_called_once_with(fake_menu) mock_remove_toolbar.assert_called_once_with(fake_toolbar) self.assertIsNone(nav.menu) self.assertIsNone(nav.toolbar) self.assertEqual(nav.actions, []) self.assertIsNone(nav.plugin_group) @patch("sn_basis.ui.navigation.remove_toolbar") @patch("sn_basis.ui.navigation.remove_menu") def test_loescht_menu_und_toolbar_objekt_damit_kein_duplikat_uebrig_bleibt(self, mock_remove_menu, mock_remove_toolbar): """Prüft, dass remove_all deleteLater() auf Menü und Toolbar aufruft, damit sie den Qt-Objektbaum tatsächlich verlassen (Issue #72).""" nav = Navigation() fake_menu, fake_toolbar = Mock(), Mock() nav.menu = fake_menu nav.toolbar = fake_toolbar nav.remove_all() fake_menu.deleteLater.assert_called_once() fake_toolbar.deleteLater.assert_called_once() @patch("sn_basis.ui.navigation.remove_toolbar") @patch("sn_basis.ui.navigation.remove_menu") def test_ohne_menu_und_toolbar_ruft_wrapper_nicht_auf(self, mock_remove_menu, mock_remove_toolbar): """Prüft, dass remove_all ohne vorhandenes Menü/Toolbar die Wrapper nicht aufruft, aber die Liste leert.""" nav = Navigation() nav.actions = [(1, "a1")] nav.remove_all() mock_remove_menu.assert_not_called() mock_remove_toolbar.assert_not_called() self.assertEqual(nav.actions, []) if __name__ == "__main__": unittest.main()