650 lines
27 KiB
Python
650 lines
27 KiB
Python
"""Tests für sn_basis/ui/tabs/print_tab.py::PrintTab (Issue #67).
|
|||
|
|
|
||
|
|
self.logic ist eine PrintLogic-Instanz (modules/print_logic.py) - die Klasse steht wegen einer
|
||
|
|
geplanten Grundüberarbeitung komplett unter "Test ausgesetzt, zunächst Refactor" (#63/#65). Wo eine
|
||
|
|
PrintTab-Methode self.logic.irgendwas(...) aufruft, wird self.logic hier durch ein Fake/Mock ersetzt;
|
||
|
|
geprüft wird nur, dass PrintTab die richtige Methode mit den richtigen Argumenten aufruft, nie das
|
||
|
|
tatsächliche PrintLogic-Verhalten.
|
||
|
|
|
||
|
|
PrintTab(QWidget) erbt von der im Mock-Modus leeren _MockWidget-Klasse (kein setLayout() usw.) - wie bei
|
||
|
|
SettingsTab/BaseDockWidget wird nie PrintTab() echt konstruiert, sondern PrintTab.__new__(PrintTab) mit
|
||
|
|
den je Test nötigen Instanzattributen von Hand.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import unittest
|
||
|
|
from unittest.mock import MagicMock, call, patch
|
||
|
|
|
||
|
|
from sn_basis.functions.qt_wrapper import ArrowDown, ArrowRight
|
||
|
|
from sn_basis.ui.tabs.print_tab import (
|
||
|
|
ATLAS_ERZEUGEN_VAR,
|
||
|
|
DRUCKBEREICH_VAR,
|
||
|
|
DRUCKBEREICH_VERFAHRENSGEBIET,
|
||
|
|
DRUCKPUFFER_STANDARD_CM,
|
||
|
|
DRUCKPUFFER_VAR,
|
||
|
|
FORMFAKTOR_UI_VAR,
|
||
|
|
KARTENNAME_38,
|
||
|
|
KARTENNAME_PLACEHOLDER,
|
||
|
|
KARTENNAME_VAR,
|
||
|
|
MASSSTAB_VAR,
|
||
|
|
MASSSTAB_WIE_KARTENFENSTER,
|
||
|
|
PLOTMASSSTAB_BY_AUSWAHL,
|
||
|
|
THEMA_VAR,
|
||
|
|
THEMA_WIE_KARTENFENSTER,
|
||
|
|
DIN_STANDARD,
|
||
|
|
PrintTab,
|
||
|
|
ZIELGROESSE_UI_VAR,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def _neuer_tab() -> PrintTab:
|
||
|
|
return PrintTab.__new__(PrintTab)
|
||
|
|
|
||
|
|
|
||
|
|
class _FakeCombo:
|
||
|
|
def __init__(self, text: str = "") -> None:
|
||
|
|
self._text = text
|
||
|
|
self.added_items: list = []
|
||
|
|
|
||
|
|
def currentText(self) -> str:
|
||
|
|
return self._text
|
||
|
|
|
||
|
|
def setCurrentText(self, value: str) -> None:
|
||
|
|
self._text = value
|
||
|
|
|
||
|
|
def clear(self) -> None:
|
||
|
|
self.added_items = []
|
||
|
|
|
||
|
|
def addItems(self, items) -> None:
|
||
|
|
self.added_items.extend(items)
|
||
|
|
|
||
|
|
def blockSignals(self, value: bool) -> None:
|
||
|
|
pass
|
||
|
|
|
||
|
|
|
||
|
|
class _FakeCheckbox:
|
||
|
|
def __init__(self, checked: bool = False) -> None:
|
||
|
|
self._checked = checked
|
||
|
|
|
||
|
|
def isChecked(self) -> bool:
|
||
|
|
return self._checked
|
||
|
|
|
||
|
|
def setChecked(self, value: bool) -> None:
|
||
|
|
self._checked = value
|
||
|
|
|
||
|
|
|
||
|
|
class _FakeButton:
|
||
|
|
def __init__(self, text: str = "") -> None:
|
||
|
|
self._text = text
|
||
|
|
|
||
|
|
def setText(self, value: str) -> None:
|
||
|
|
self._text = value
|
||
|
|
|
||
|
|
def text(self) -> str:
|
||
|
|
return self._text
|
||
|
|
|
||
|
|
|
||
|
|
class _FakePruefmanager:
|
||
|
|
def __init__(self, frage_text_ergebnis) -> None:
|
||
|
|
self._frage_text_ergebnis = frage_text_ergebnis
|
||
|
|
self.zeige_hinweis_calls: list = []
|
||
|
|
|
||
|
|
def frage_text(self, titel, meldung, default_text=""):
|
||
|
|
return self._frage_text_ergebnis
|
||
|
|
|
||
|
|
def zeige_hinweis(self, titel, meldung) -> None:
|
||
|
|
self.zeige_hinweis_calls.append((titel, meldung))
|
||
|
|
|
||
|
|
|
||
|
|
class _FakeSignal:
|
||
|
|
def __init__(self) -> None:
|
||
|
|
self.connected: list = []
|
||
|
|
|
||
|
|
def connect(self, fn) -> None:
|
||
|
|
self.connected.append(fn)
|
||
|
|
|
||
|
|
def disconnect(self, fn) -> None:
|
||
|
|
self.connected.remove(fn)
|
||
|
|
|
||
|
|
|
||
|
|
class _FakeProjectSignals:
|
||
|
|
def __init__(self) -> None:
|
||
|
|
self.readProject = _FakeSignal()
|
||
|
|
self.newProjectCreated = _FakeSignal()
|
||
|
|
self.cleared = _FakeSignal()
|
||
|
|
|
||
|
|
|
||
|
|
class _FakeThemeCollection:
|
||
|
|
def __init__(self) -> None:
|
||
|
|
self.mapThemesChanged = _FakeSignal()
|
||
|
|
self.changed = _FakeSignal()
|
||
|
|
self.themeChanged = _FakeSignal()
|
||
|
|
|
||
|
|
|
||
|
|
class SetServicesTests(unittest.TestCase):
|
||
|
|
@patch("sn_basis.ui.tabs.print_tab.PrintLogic")
|
||
|
|
def test_ruft_alle_set_methoden_mit_combo_werten_auf(self, mock_logic_cls):
|
||
|
|
"""Prüft, dass set_services alle set_*-Methoden von PrintLogic mit den aktuellen Combo-Werten aufruft."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
tab._kartenname_combo = _FakeCombo("§38")
|
||
|
|
tab._massstab_combo = _FakeCombo("1:1000")
|
||
|
|
tab._thema_combo = _FakeCombo("Thema A")
|
||
|
|
tab._zielgroesse_combo = _FakeCombo("A3")
|
||
|
|
tab._druckbereich_combo = _FakeCombo("Verfahrensgebiet")
|
||
|
|
tab._endlosrolle_cb = _FakeCheckbox(True)
|
||
|
|
tab._get_current_canvas_scale = lambda: 5000.0
|
||
|
|
mock_logic = mock_logic_cls.return_value
|
||
|
|
|
||
|
|
pruefmanager = object()
|
||
|
|
tab.set_services(pruefmanager, object())
|
||
|
|
|
||
|
|
self.assertIs(tab.pruefmanager, pruefmanager)
|
||
|
|
mock_logic.set_kartenname_for_auswahl.assert_called_once_with("§38")
|
||
|
|
mock_logic.set_plotmassstab_for_auswahl.assert_called_once_with("1:1000", 5000.0)
|
||
|
|
mock_logic.set_view_for_auswahl.assert_called_once_with("Thema A")
|
||
|
|
mock_logic.set_zielgroesse_for_auswahl.assert_called_once_with("A3")
|
||
|
|
mock_logic.set_druckbereich_for_auswahl.assert_called_once_with("Verfahrensgebiet")
|
||
|
|
mock_logic.set_formfaktor.assert_called_once_with(True)
|
||
|
|
|
||
|
|
@patch("sn_basis.ui.tabs.print_tab.PrintLogic")
|
||
|
|
def test_ohne_combos_ruft_keine_set_methode_auf(self, mock_logic_cls):
|
||
|
|
"""Prüft, dass set_services ohne vorhandene Combos keine set_*-Methode von PrintLogic aufruft."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
tab._kartenname_combo = None
|
||
|
|
tab._massstab_combo = None
|
||
|
|
tab._thema_combo = None
|
||
|
|
tab._zielgroesse_combo = None
|
||
|
|
tab._druckbereich_combo = None
|
||
|
|
tab._endlosrolle_cb = None
|
||
|
|
mock_logic = mock_logic_cls.return_value
|
||
|
|
|
||
|
|
tab.set_services(object(), object())
|
||
|
|
|
||
|
|
mock_logic.set_kartenname_for_auswahl.assert_not_called()
|
||
|
|
mock_logic.set_plotmassstab_for_auswahl.assert_not_called()
|
||
|
|
mock_logic.set_view_for_auswahl.assert_not_called()
|
||
|
|
mock_logic.set_zielgroesse_for_auswahl.assert_not_called()
|
||
|
|
mock_logic.set_druckbereich_for_auswahl.assert_not_called()
|
||
|
|
mock_logic.set_formfaktor.assert_not_called()
|
||
|
|
self.assertIs(tab.logic, mock_logic)
|
||
|
|
|
||
|
|
|
||
|
|
class RestoreStateTests(unittest.TestCase):
|
||
|
|
def _neuer_tab_mit_widgets(self) -> PrintTab:
|
||
|
|
tab = _neuer_tab()
|
||
|
|
tab._kartenname_combo = _FakeCombo()
|
||
|
|
tab._massstab_combo = _FakeCombo()
|
||
|
|
tab._thema_combo = _FakeCombo()
|
||
|
|
tab._zielgroesse_combo = _FakeCombo()
|
||
|
|
tab._endlosrolle_cb = _FakeCheckbox()
|
||
|
|
tab._druckbereich_combo = _FakeCombo()
|
||
|
|
tab._druckpuffer_button = _FakeButton()
|
||
|
|
tab._atlas_erzeugen_cb = None
|
||
|
|
tab._atlas_layer_combo = None
|
||
|
|
tab._get_gespeicherte_themen = lambda: ["Thema A"]
|
||
|
|
return tab
|
||
|
|
|
||
|
|
def test_gueltige_gespeicherte_werte_werden_uebernommen(self):
|
||
|
|
"""Prüft, dass _restore_state gültige gespeicherte Werte in die jeweiligen Widgets übernimmt."""
|
||
|
|
tab = self._neuer_tab_mit_widgets()
|
||
|
|
massstab_gueltig = next(iter(PLOTMASSSTAB_BY_AUSWAHL.keys()))
|
||
|
|
werte = {
|
||
|
|
KARTENNAME_VAR: KARTENNAME_38,
|
||
|
|
MASSSTAB_VAR: massstab_gueltig,
|
||
|
|
THEMA_VAR: THEMA_WIE_KARTENFENSTER,
|
||
|
|
ZIELGROESSE_UI_VAR: DIN_STANDARD,
|
||
|
|
FORMFAKTOR_UI_VAR: "Blatt",
|
||
|
|
DRUCKBEREICH_VAR: DRUCKBEREICH_VERFAHRENSGEBIET,
|
||
|
|
DRUCKPUFFER_VAR: "7",
|
||
|
|
}
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.get_variable", side_effect=lambda key, scope=None: werte.get(key)):
|
||
|
|
tab._restore_state()
|
||
|
|
|
||
|
|
self.assertEqual(tab._kartenname_combo.currentText(), KARTENNAME_38)
|
||
|
|
self.assertEqual(tab._massstab_combo.currentText(), massstab_gueltig)
|
||
|
|
self.assertEqual(tab._druckbereich_combo.currentText(), DRUCKBEREICH_VERFAHRENSGEBIET)
|
||
|
|
self.assertEqual(tab._druckpuffer_button.text(), "7 cm")
|
||
|
|
|
||
|
|
def test_ungueltige_oder_fehlende_werte_fallen_auf_defaults_zurueck(self):
|
||
|
|
"""Prüft, dass _restore_state ohne (gültige) gespeicherte Werte auf die Default-Konstanten zurückfällt."""
|
||
|
|
tab = self._neuer_tab_mit_widgets()
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.get_variable", return_value=None):
|
||
|
|
tab._restore_state()
|
||
|
|
|
||
|
|
self.assertEqual(tab._kartenname_combo.currentText(), KARTENNAME_PLACEHOLDER)
|
||
|
|
self.assertEqual(tab._massstab_combo.currentText(), MASSSTAB_WIE_KARTENFENSTER)
|
||
|
|
self.assertEqual(tab._druckbereich_combo.currentText(), DRUCKBEREICH_VERFAHRENSGEBIET)
|
||
|
|
self.assertEqual(tab._druckpuffer_button.text(), f"{DRUCKPUFFER_STANDARD_CM} cm")
|
||
|
|
|
||
|
|
|
||
|
|
class OnDruckpufferClickedTests(unittest.TestCase):
|
||
|
|
def test_gueltige_zahl_setzt_variable_und_button_text(self):
|
||
|
|
"""Prüft, dass eine bestätigte, gültige Zahl den Druckpuffer speichert und den Button-Text aktualisiert."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
tab.pruefmanager = _FakePruefmanager(("5", True))
|
||
|
|
tab._druckpuffer_button = _FakeButton()
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.get_variable", return_value=None), \
|
||
|
|
patch("sn_basis.ui.tabs.print_tab.set_variable") as mock_set:
|
||
|
|
tab._on_druckpuffer_clicked()
|
||
|
|
|
||
|
|
mock_set.assert_called_once_with(DRUCKPUFFER_VAR, "5", scope="project")
|
||
|
|
self.assertEqual(tab._druckpuffer_button.text(), "5 cm")
|
||
|
|
|
||
|
|
def test_abgebrochen_aendert_nichts(self):
|
||
|
|
"""Prüft, dass ein abgebrochener Dialog den Druckpuffer nicht verändert."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
tab.pruefmanager = _FakePruefmanager(("5", False))
|
||
|
|
tab._druckpuffer_button = _FakeButton()
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.get_variable", return_value=None), \
|
||
|
|
patch("sn_basis.ui.tabs.print_tab.set_variable") as mock_set:
|
||
|
|
tab._on_druckpuffer_clicked()
|
||
|
|
|
||
|
|
mock_set.assert_not_called()
|
||
|
|
|
||
|
|
def test_ungueltige_zahl_zeigt_hinweis_und_setzt_nichts(self):
|
||
|
|
"""Prüft, dass eine nicht-numerische Eingabe einen Hinweis zeigt und den Druckpuffer nicht setzt."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
pruefmanager = _FakePruefmanager(("abc", True))
|
||
|
|
tab.pruefmanager = pruefmanager
|
||
|
|
tab._druckpuffer_button = _FakeButton()
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.get_variable", return_value=None), \
|
||
|
|
patch("sn_basis.ui.tabs.print_tab.set_variable") as mock_set:
|
||
|
|
tab._on_druckpuffer_clicked()
|
||
|
|
|
||
|
|
mock_set.assert_not_called()
|
||
|
|
self.assertEqual(len(pruefmanager.zeige_hinweis_calls), 1)
|
||
|
|
|
||
|
|
|
||
|
|
class OnVorlageErstellenTests(unittest.TestCase):
|
||
|
|
def test_ruft_druckvorlage_anlegen_mit_combo_werten_auf(self):
|
||
|
|
"""Prüft, dass _on_vorlage_erstellen druckvorlage_anlegen mit den aktuellen Combo-Werten aufruft."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
mock_logic = MagicMock()
|
||
|
|
tab.logic = mock_logic
|
||
|
|
tab.pruefmanager = object()
|
||
|
|
tab._kartenname_combo = _FakeCombo("§38")
|
||
|
|
tab._massstab_combo = _FakeCombo("1:1000")
|
||
|
|
tab._zielgroesse_combo = _FakeCombo("A3")
|
||
|
|
tab._endlosrolle_cb = _FakeCheckbox(True)
|
||
|
|
tab._druckbereich_combo = _FakeCombo("Verfahrensgebiet")
|
||
|
|
|
||
|
|
tab._on_vorlage_erstellen()
|
||
|
|
|
||
|
|
mock_logic.druckvorlage_anlegen.assert_called_once_with(
|
||
|
|
layer=None,
|
||
|
|
kartenname_auswahl="§38",
|
||
|
|
massstab_auswahl="1:1000",
|
||
|
|
zielgroesse="A3",
|
||
|
|
formfaktor=True,
|
||
|
|
druckbereich_auswahl="Verfahrensgebiet",
|
||
|
|
)
|
||
|
|
|
||
|
|
def test_ohne_logic_tut_nichts(self):
|
||
|
|
"""Prüft, dass _on_vorlage_erstellen ohne gesetzte PrintLogic keine Ausnahme wirft."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
tab.logic = None
|
||
|
|
tab.pruefmanager = object()
|
||
|
|
|
||
|
|
tab._on_vorlage_erstellen() # darf keine Ausnahme werfen
|
||
|
|
|
||
|
|
|
||
|
|
class ProjectSignalsTests(unittest.TestCase):
|
||
|
|
def test_connect_verbindet_alle_drei_signale(self):
|
||
|
|
"""Prüft, dass _connect_project_signals alle drei Projektsignale mit _on_project_changed verbindet."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
projekt = _FakeProjectSignals()
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.QgsProject") as mock_project:
|
||
|
|
mock_project.instance.return_value = projekt
|
||
|
|
tab._connect_project_signals()
|
||
|
|
|
||
|
|
self.assertEqual(projekt.readProject.connected, [tab._on_project_changed])
|
||
|
|
self.assertEqual(projekt.newProjectCreated.connected, [tab._on_project_changed])
|
||
|
|
self.assertEqual(projekt.cleared.connected, [tab._on_project_changed])
|
||
|
|
|
||
|
|
def test_disconnect_trennt_alle_drei_signale(self):
|
||
|
|
"""Prüft, dass _disconnect_project_signals alle drei zuvor verbundenen Projektsignale trennt."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
projekt = _FakeProjectSignals()
|
||
|
|
projekt.readProject.connect(tab._on_project_changed)
|
||
|
|
projekt.newProjectCreated.connect(tab._on_project_changed)
|
||
|
|
projekt.cleared.connect(tab._on_project_changed)
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.QgsProject") as mock_project:
|
||
|
|
mock_project.instance.return_value = projekt
|
||
|
|
tab._disconnect_project_signals()
|
||
|
|
|
||
|
|
self.assertEqual(projekt.readProject.connected, [])
|
||
|
|
self.assertEqual(projekt.newProjectCreated.connected, [])
|
||
|
|
self.assertEqual(projekt.cleared.connected, [])
|
||
|
|
|
||
|
|
def test_instance_wirft_ausnahme_wird_abgefangen(self):
|
||
|
|
"""Prüft, dass eine Ausnahme bei QgsProject.instance() in beiden Signalmethoden abgefangen wird."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.QgsProject") as mock_project:
|
||
|
|
mock_project.instance.side_effect = RuntimeError("kaputt")
|
||
|
|
tab._connect_project_signals() # darf keine Ausnahme werfen
|
||
|
|
tab._disconnect_project_signals() # darf keine Ausnahme werfen
|
||
|
|
|
||
|
|
|
||
|
|
class CleanupTests(unittest.TestCase):
|
||
|
|
def test_ruft_beide_disconnect_methoden_auf(self):
|
||
|
|
"""Prüft, dass cleanup sowohl die Theme- als auch die Projektsignale trennt."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
tab._disconnect_theme_collection_signals = MagicMock()
|
||
|
|
tab._disconnect_project_signals = MagicMock()
|
||
|
|
|
||
|
|
tab.cleanup()
|
||
|
|
|
||
|
|
tab._disconnect_theme_collection_signals.assert_called_once()
|
||
|
|
tab._disconnect_project_signals.assert_called_once()
|
||
|
|
|
||
|
|
|
||
|
|
class ToggleAtlasTests(unittest.TestCase):
|
||
|
|
def test_checked_true_setzt_arrow_down_und_zeigt_inhalt(self):
|
||
|
|
"""Prüft, dass _toggle_atlas bei checked=True den Pfeil nach unten dreht und den Inhalt einblendet."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
tab._atlas_button = MagicMock()
|
||
|
|
tab._atlas_content = MagicMock()
|
||
|
|
|
||
|
|
tab._toggle_atlas(True)
|
||
|
|
|
||
|
|
tab._atlas_button.setArrowType.assert_called_once_with(ArrowDown)
|
||
|
|
tab._atlas_content.setVisible.assert_called_once_with(True)
|
||
|
|
|
||
|
|
def test_checked_false_setzt_arrow_right_und_versteckt_inhalt(self):
|
||
|
|
"""Prüft, dass _toggle_atlas bei checked=False den Pfeil nach rechts dreht und den Inhalt ausblendet."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
tab._atlas_button = MagicMock()
|
||
|
|
tab._atlas_content = MagicMock()
|
||
|
|
|
||
|
|
tab._toggle_atlas(False)
|
||
|
|
|
||
|
|
tab._atlas_button.setArrowType.assert_called_once_with(ArrowRight)
|
||
|
|
tab._atlas_content.setVisible.assert_called_once_with(False)
|
||
|
|
|
||
|
|
|
||
|
|
class RestoreAtlasLayerSelectionTests(unittest.TestCase):
|
||
|
|
def test_vorhandene_layer_id_setzt_layer_in_combo(self):
|
||
|
|
"""Prüft, dass eine gespeicherte Layer-ID den zugehörigen Layer im Atlas-Dropdown auswählt."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
tab._atlas_layer_combo = MagicMock(spec=["setLayer"])
|
||
|
|
fake_layer = object()
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.get_variable", return_value="layer_1"), \
|
||
|
|
patch("sn_basis.ui.tabs.print_tab.QgsProject") as mock_project:
|
||
|
|
mock_project.instance.return_value.mapLayer.return_value = fake_layer
|
||
|
|
tab._restore_atlas_layer_selection()
|
||
|
|
|
||
|
|
tab._atlas_layer_combo.setLayer.assert_called_once_with(fake_layer)
|
||
|
|
|
||
|
|
def test_leere_gespeicherte_layer_id_tut_nichts(self):
|
||
|
|
"""Prüft, dass eine leere gespeicherte Layer-ID das Projekt gar nicht erst anfragt."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
tab._atlas_layer_combo = MagicMock(spec=["setLayer"])
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.get_variable", return_value=""), \
|
||
|
|
patch("sn_basis.ui.tabs.print_tab.QgsProject") as mock_project:
|
||
|
|
tab._restore_atlas_layer_selection()
|
||
|
|
|
||
|
|
tab._atlas_layer_combo.setLayer.assert_not_called()
|
||
|
|
mock_project.instance.assert_not_called()
|
||
|
|
|
||
|
|
|
||
|
|
class ThemeCollectionSignalsTests(unittest.TestCase):
|
||
|
|
def test_connect_verbindet_alle_signale(self):
|
||
|
|
"""Prüft, dass _connect_theme_collection_signals alle Signale der Theme-Collection verbindet."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
tab._theme_signal_connected = False
|
||
|
|
collection = _FakeThemeCollection()
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.QgsProject") as mock_project:
|
||
|
|
mock_project.instance.return_value.mapThemeCollection.return_value = collection
|
||
|
|
tab._connect_theme_collection_signals()
|
||
|
|
|
||
|
|
self.assertEqual(collection.mapThemesChanged.connected, [tab._refresh_thema_combo_live])
|
||
|
|
self.assertTrue(tab._theme_signal_connected)
|
||
|
|
self.assertIs(tab._connected_theme_collection, collection)
|
||
|
|
|
||
|
|
def test_connect_ist_idempotent_wenn_schon_verbunden(self):
|
||
|
|
"""Prüft, dass _connect_theme_collection_signals bei bereits verbundenem Signal nichts erneut tut."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
tab._theme_signal_connected = True
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.QgsProject") as mock_project:
|
||
|
|
tab._connect_theme_collection_signals()
|
||
|
|
|
||
|
|
mock_project.instance.assert_not_called()
|
||
|
|
|
||
|
|
def test_disconnect_trennt_alle_signale(self):
|
||
|
|
"""Prüft, dass _disconnect_theme_collection_signals alle verbundenen Theme-Signale trennt und zurücksetzt."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
collection = _FakeThemeCollection()
|
||
|
|
collection.mapThemesChanged.connect(tab._refresh_thema_combo_live)
|
||
|
|
collection.changed.connect(tab._refresh_thema_combo_live)
|
||
|
|
collection.themeChanged.connect(tab._refresh_thema_combo_live)
|
||
|
|
tab._connected_theme_collection = collection
|
||
|
|
tab._theme_signal_connected = True
|
||
|
|
|
||
|
|
tab._disconnect_theme_collection_signals()
|
||
|
|
|
||
|
|
self.assertEqual(collection.mapThemesChanged.connected, [])
|
||
|
|
self.assertIsNone(tab._connected_theme_collection)
|
||
|
|
self.assertFalse(tab._theme_signal_connected)
|
||
|
|
|
||
|
|
|
||
|
|
class RefreshThemaComboLiveTests(unittest.TestCase):
|
||
|
|
def test_erhaelt_vorherige_auswahl_wenn_noch_vorhanden(self):
|
||
|
|
"""Prüft, dass _refresh_thema_combo_live die vorherige Auswahl beibehält, wenn sie noch existiert."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
combo = _FakeCombo("Thema A")
|
||
|
|
tab._thema_combo = combo
|
||
|
|
tab._get_gespeicherte_themen = lambda: ["Thema A", "Thema B"]
|
||
|
|
tab._on_thema_changed = MagicMock()
|
||
|
|
|
||
|
|
tab._refresh_thema_combo_live()
|
||
|
|
|
||
|
|
self.assertEqual(combo.currentText(), "Thema A")
|
||
|
|
tab._on_thema_changed.assert_called_once_with("Thema A")
|
||
|
|
|
||
|
|
def test_faellt_auf_wie_kartenfenster_zurueck_wenn_auswahl_verschwunden(self):
|
||
|
|
"""Prüft, dass _refresh_thema_combo_live auf 'wie Kartenfenster' zurückfällt, wenn die Auswahl verschwunden ist."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
combo = _FakeCombo("Alte Auswahl")
|
||
|
|
tab._thema_combo = combo
|
||
|
|
tab._get_gespeicherte_themen = lambda: ["Thema B"]
|
||
|
|
tab._on_thema_changed = MagicMock()
|
||
|
|
|
||
|
|
tab._refresh_thema_combo_live()
|
||
|
|
|
||
|
|
self.assertEqual(combo.currentText(), THEMA_WIE_KARTENFENSTER)
|
||
|
|
tab._on_thema_changed.assert_called_once_with(THEMA_WIE_KARTENFENSTER)
|
||
|
|
|
||
|
|
|
||
|
|
class GetGespeicherteThemenTests(unittest.TestCase):
|
||
|
|
def test_dedupliziert_und_filtert_leere_eintraege(self):
|
||
|
|
"""Prüft, dass _get_gespeicherte_themen Duplikate und leere/None-Einträge entfernt."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.QgsProject") as mock_project:
|
||
|
|
mock_project.instance.return_value.mapThemeCollection.return_value.mapThemes.return_value = [
|
||
|
|
"A", "A", "", "B", None,
|
||
|
|
]
|
||
|
|
ergebnis = tab._get_gespeicherte_themen()
|
||
|
|
|
||
|
|
self.assertEqual(ergebnis, ["A", "B"])
|
||
|
|
|
||
|
|
def test_fehler_liefert_leere_liste(self):
|
||
|
|
"""Prüft, dass ein Fehler beim Zugriff auf die Theme-Collection eine leere Liste liefert."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.QgsProject") as mock_project:
|
||
|
|
mock_project.instance.side_effect = RuntimeError("kaputt")
|
||
|
|
ergebnis = tab._get_gespeicherte_themen()
|
||
|
|
|
||
|
|
self.assertEqual(ergebnis, [])
|
||
|
|
|
||
|
|
|
||
|
|
class GetCurrentCanvasScaleTests(unittest.TestCase):
|
||
|
|
def test_liefert_skalierung_als_float(self):
|
||
|
|
"""Prüft, dass _get_current_canvas_scale die Canvas-Skalierung als float liefert."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
fake_iface = MagicMock()
|
||
|
|
fake_iface.mapCanvas.return_value.scale.return_value = 5000
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.iface", fake_iface):
|
||
|
|
self.assertEqual(tab._get_current_canvas_scale(), 5000.0)
|
||
|
|
|
||
|
|
def test_ohne_canvas_liefert_none(self):
|
||
|
|
"""Prüft, dass _get_current_canvas_scale ohne verfügbares Kartenfenster None liefert."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
fake_iface = MagicMock()
|
||
|
|
fake_iface.mapCanvas.return_value = None
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.iface", fake_iface):
|
||
|
|
self.assertIsNone(tab._get_current_canvas_scale())
|
||
|
|
|
||
|
|
def test_skalierung_null_liefert_none(self):
|
||
|
|
"""Prüft, dass eine Skalierung von 0 als None statt als 0.0 gilt."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
fake_iface = MagicMock()
|
||
|
|
fake_iface.mapCanvas.return_value.scale.return_value = 0
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.iface", fake_iface):
|
||
|
|
self.assertIsNone(tab._get_current_canvas_scale())
|
||
|
|
|
||
|
|
|
||
|
|
class OnAtlasLayerChangedTests(unittest.TestCase):
|
||
|
|
def test_none_oder_leer_setzt_variable_leer(self):
|
||
|
|
"""Prüft, dass None oder ein leerer Wert die Abdeckungslayer-Variable leeren."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.set_variable") as mock_set:
|
||
|
|
tab._on_atlas_layer_changed(None)
|
||
|
|
tab._on_atlas_layer_changed("")
|
||
|
|
|
||
|
|
mock_set.assert_has_calls([
|
||
|
|
call("sn_abdeckungslayer", "", scope="project"),
|
||
|
|
call("sn_abdeckungslayer", "", scope="project"),
|
||
|
|
])
|
||
|
|
|
||
|
|
def test_layer_objekt_mit_id_methode_setzt_dessen_id(self):
|
||
|
|
"""Prüft, dass ein echtes Layer-Objekt (mit id()-Methode) dessen ID speichert."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
fake_layer = MagicMock()
|
||
|
|
fake_layer.id.return_value = "layer_42"
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.set_variable") as mock_set:
|
||
|
|
tab._on_atlas_layer_changed(fake_layer)
|
||
|
|
|
||
|
|
mock_set.assert_called_once_with("sn_abdeckungslayer", "layer_42", scope="project")
|
||
|
|
|
||
|
|
def test_string_fallback_sucht_layer_ueber_namen(self):
|
||
|
|
"""Prüft, dass ohne id()-Methode über den Layernamen im Projekt gesucht und dessen ID gespeichert wird."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
fake_layer = MagicMock(spec=["name"])
|
||
|
|
fake_layer.name.return_value = "Mein Layer"
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.set_variable") as mock_set, \
|
||
|
|
patch("sn_basis.ui.tabs.print_tab.QgsProject") as mock_project:
|
||
|
|
mock_project.instance.return_value.mapLayers.return_value = {"layer_7": fake_layer}
|
||
|
|
tab._on_atlas_layer_changed("Mein Layer")
|
||
|
|
|
||
|
|
mock_set.assert_called_once_with("sn_abdeckungslayer", "layer_7", scope="project")
|
||
|
|
|
||
|
|
|
||
|
|
class OnAuswahlAsAtlasClickedTests(unittest.TestCase):
|
||
|
|
def test_erfolgreicher_layer_aktualisiert_atlas_ui(self):
|
||
|
|
"""Prüft, dass ein erfolgreich erzeugter Atlas-Layer den Atlas-Bereich öffnet und ins Dropdown übernimmt."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
fake_layer = object()
|
||
|
|
mock_logic = MagicMock()
|
||
|
|
mock_logic.create_atlas_layer_from_selection.return_value = (fake_layer, "layer_9")
|
||
|
|
tab.logic = mock_logic
|
||
|
|
tab.pruefmanager = object()
|
||
|
|
tab._atlas_button = MagicMock()
|
||
|
|
tab._atlas_content = MagicMock()
|
||
|
|
tab._atlas_layer_combo = MagicMock(spec=["setLayer"])
|
||
|
|
tab._toggle_atlas = MagicMock()
|
||
|
|
|
||
|
|
tab._on_auswahl_as_atlas_clicked()
|
||
|
|
|
||
|
|
tab._atlas_button.setChecked.assert_called_once_with(True)
|
||
|
|
tab._toggle_atlas.assert_called_once_with(True)
|
||
|
|
tab._atlas_layer_combo.setLayer.assert_called_once_with(fake_layer)
|
||
|
|
|
||
|
|
def test_ohne_erzeugten_layer_tut_nichts_weiter(self):
|
||
|
|
"""Prüft, dass ohne erzeugten Atlas-Layer keine UI-Aktualisierung stattfindet."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
mock_logic = MagicMock()
|
||
|
|
mock_logic.create_atlas_layer_from_selection.return_value = (None, None)
|
||
|
|
tab.logic = mock_logic
|
||
|
|
tab.pruefmanager = object()
|
||
|
|
tab._atlas_button = MagicMock()
|
||
|
|
tab._atlas_content = MagicMock()
|
||
|
|
tab._atlas_layer_combo = MagicMock()
|
||
|
|
|
||
|
|
tab._on_auswahl_as_atlas_clicked() # darf keine Ausnahme werfen
|
||
|
|
|
||
|
|
tab._atlas_button.setChecked.assert_not_called()
|
||
|
|
|
||
|
|
|
||
|
|
class PopulateAtlasLayerComboTests(unittest.TestCase):
|
||
|
|
def test_befuellt_combo_ohne_druckrechteck_und_leere_namen(self):
|
||
|
|
"""Prüft, dass _populate_atlas_layer_combo alle Layernamen außer 'Druckrechteck' und leeren Namen einträgt."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
combo = MagicMock(spec=["clear", "addItem", "blockSignals"])
|
||
|
|
tab._atlas_layer_combo = combo
|
||
|
|
|
||
|
|
layer_normal = MagicMock()
|
||
|
|
layer_normal.name.return_value = "Flurstuecke"
|
||
|
|
layer_druckrechteck = MagicMock()
|
||
|
|
layer_druckrechteck.name.return_value = "Druckrechteck"
|
||
|
|
layer_leer = MagicMock()
|
||
|
|
layer_leer.name.return_value = ""
|
||
|
|
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.QgsProject") as mock_project:
|
||
|
|
mock_project.instance.return_value.mapLayers.return_value = {
|
||
|
|
"1": layer_normal, "2": layer_druckrechteck, "3": layer_leer,
|
||
|
|
}
|
||
|
|
tab._populate_atlas_layer_combo()
|
||
|
|
|
||
|
|
alle_aufrufe = [c.args[0] for c in combo.addItem.call_args_list]
|
||
|
|
self.assertIn("", alle_aufrufe)
|
||
|
|
self.assertIn("Flurstuecke", alle_aufrufe)
|
||
|
|
self.assertNotIn("Druckrechteck", alle_aufrufe)
|
||
|
|
self.assertEqual(alle_aufrufe.count(""), 1)
|
||
|
|
|
||
|
|
def test_ohne_atlas_layer_combo_tut_nichts(self):
|
||
|
|
"""Prüft, dass _populate_atlas_layer_combo ohne Atlas-Dropdown keine Ausnahme wirft."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
tab._atlas_layer_combo = None
|
||
|
|
|
||
|
|
tab._populate_atlas_layer_combo() # darf keine Ausnahme werfen
|
||
|
|
|
||
|
|
|
||
|
|
class OnAtlasErzeugenChangedTests(unittest.TestCase):
|
||
|
|
def test_aktiviert_speichert_variable_und_aktiviert_widgets(self):
|
||
|
|
"""Prüft, dass ein aktivierter Atlas-Schalter die Variable setzt und Layer-Combo/Button aktiviert."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
tab._atlas_layer_combo = MagicMock()
|
||
|
|
tab._btn_auswahl_as_atlas = MagicMock()
|
||
|
|
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.set_variable") as mock_set_variable:
|
||
|
|
tab._on_atlas_erzeugen_changed(1)
|
||
|
|
|
||
|
|
mock_set_variable.assert_called_once_with(ATLAS_ERZEUGEN_VAR, "1", scope="project")
|
||
|
|
tab._atlas_layer_combo.setEnabled.assert_called_once_with(True)
|
||
|
|
tab._btn_auswahl_as_atlas.setEnabled.assert_called_once_with(True)
|
||
|
|
|
||
|
|
def test_deaktiviert_speichert_variable_und_deaktiviert_widgets(self):
|
||
|
|
"""Prüft, dass ein deaktivierter Atlas-Schalter die Variable setzt und Layer-Combo/Button deaktiviert."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
tab._atlas_layer_combo = MagicMock()
|
||
|
|
tab._btn_auswahl_as_atlas = MagicMock()
|
||
|
|
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.set_variable") as mock_set_variable:
|
||
|
|
tab._on_atlas_erzeugen_changed(0)
|
||
|
|
|
||
|
|
mock_set_variable.assert_called_once_with(ATLAS_ERZEUGEN_VAR, "0", scope="project")
|
||
|
|
tab._atlas_layer_combo.setEnabled.assert_called_once_with(False)
|
||
|
|
tab._btn_auswahl_as_atlas.setEnabled.assert_called_once_with(False)
|
||
|
|
|
||
|
|
def test_ohne_widgets_wirft_keine_ausnahme(self):
|
||
|
|
"""Prüft, dass _on_atlas_erzeugen_changed ohne Atlas-Combo/-Button keine Ausnahme wirft."""
|
||
|
|
tab = _neuer_tab()
|
||
|
|
tab._atlas_layer_combo = None
|
||
|
|
tab._btn_auswahl_as_atlas = None
|
||
|
|
|
||
|
|
with patch("sn_basis.ui.tabs.print_tab.set_variable"):
|
||
|
|
tab._on_atlas_erzeugen_changed(1) # darf keine Ausnahme werfen
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
unittest.main()
|