Well, 95% test coverage is okay I guess
This commit is contained in:
parent
ab5ec2bfae
commit
db0476f9ad
15 changed files with 1851 additions and 78 deletions
|
|
@ -1,12 +1,13 @@
|
|||
import pytest
|
||||
import bouquin.settings_dialog as sd
|
||||
|
||||
from bouquin.db import DBManager, DBConfig
|
||||
from bouquin.key_prompt import KeyPrompt
|
||||
import bouquin.settings_dialog as sd
|
||||
from bouquin.settings_dialog import SettingsDialog
|
||||
from bouquin.theme import ThemeManager, ThemeConfig, Theme
|
||||
from bouquin.settings import get_settings
|
||||
from PySide6.QtCore import QTimer
|
||||
from PySide6.QtWidgets import QApplication, QMessageBox, QWidget
|
||||
from PySide6.QtWidgets import QApplication, QMessageBox, QWidget, QDialog
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
|
|
@ -225,3 +226,207 @@ def test_settings_browse_sets_path(qtbot, app, tmp_path, fresh_db, monkeypatch):
|
|||
)
|
||||
dlg._browse()
|
||||
assert dlg.path_edit.text().endswith("new_file.db")
|
||||
|
||||
|
||||
class _Host(QWidget):
|
||||
def __init__(self, themes):
|
||||
super().__init__()
|
||||
self.themes = themes
|
||||
|
||||
|
||||
def _make_host_and_dialog(tmp_db_cfg, fresh_db):
|
||||
# Create a real ThemeManager so we don't have to fake anything here
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
themes = ThemeManager(QApplication.instance(), ThemeConfig(theme=Theme.SYSTEM))
|
||||
host = _Host(themes)
|
||||
dlg = SettingsDialog(tmp_db_cfg, fresh_db, parent=host)
|
||||
return host, dlg
|
||||
|
||||
|
||||
def _clear_qsettings_theme_to_system():
|
||||
"""Make the radio-button default deterministic across the full suite."""
|
||||
s = get_settings()
|
||||
s.clear()
|
||||
s.setValue("ui/theme", "system")
|
||||
|
||||
|
||||
def test_default_theme_radio_is_system_checked(qtbot, tmp_db_cfg, fresh_db):
|
||||
# Ensure no stray theme value from previous tests
|
||||
_clear_qsettings_theme_to_system()
|
||||
|
||||
host, dlg = _make_host_and_dialog(tmp_db_cfg, fresh_db)
|
||||
qtbot.addWidget(host)
|
||||
qtbot.addWidget(dlg)
|
||||
# With fresh settings (system), the 'system' radio should be selected
|
||||
assert dlg.theme_system.isChecked()
|
||||
|
||||
|
||||
def test_save_selects_system_when_no_explicit_choice(
|
||||
qtbot, tmp_db_cfg, fresh_db, monkeypatch
|
||||
):
|
||||
host, dlg = _make_host_and_dialog(tmp_db_cfg, fresh_db)
|
||||
qtbot.addWidget(dlg)
|
||||
# Ensure neither dark nor light is checked so SYSTEM path is taken
|
||||
dlg.theme_dark.setChecked(False)
|
||||
dlg.theme_light.setChecked(False)
|
||||
# This should not raise
|
||||
dlg._save()
|
||||
|
||||
|
||||
def test_save_with_dark_selected(qtbot, tmp_db_cfg, fresh_db):
|
||||
host, dlg = _make_host_and_dialog(tmp_db_cfg, fresh_db)
|
||||
qtbot.addWidget(dlg)
|
||||
dlg.theme_dark.setChecked(True)
|
||||
dlg._save()
|
||||
|
||||
|
||||
def test_change_key_cancel_first_prompt(qtbot, tmp_db_cfg, fresh_db, monkeypatch):
|
||||
host, dlg = _make_host_and_dialog(tmp_db_cfg, fresh_db)
|
||||
qtbot.addWidget(dlg)
|
||||
|
||||
class P1:
|
||||
def __init__(self, *a, **k):
|
||||
pass
|
||||
|
||||
def exec(self):
|
||||
return QDialog.Rejected
|
||||
|
||||
def key(self):
|
||||
return ""
|
||||
|
||||
monkeypatch.setattr("bouquin.settings_dialog.KeyPrompt", P1)
|
||||
dlg._change_key() # returns early
|
||||
|
||||
|
||||
def test_change_key_cancel_second_prompt(qtbot, tmp_db_cfg, fresh_db, monkeypatch):
|
||||
host, dlg = _make_host_and_dialog(tmp_db_cfg, fresh_db)
|
||||
qtbot.addWidget(dlg)
|
||||
|
||||
class P1:
|
||||
def __init__(self, *a, **k):
|
||||
pass
|
||||
|
||||
def exec(self):
|
||||
return QDialog.Accepted
|
||||
|
||||
def key(self):
|
||||
return "abc"
|
||||
|
||||
class P2:
|
||||
def __init__(self, *a, **k):
|
||||
pass
|
||||
|
||||
def exec(self):
|
||||
return QDialog.Rejected
|
||||
|
||||
def key(self):
|
||||
return "abc"
|
||||
|
||||
# First call yields P1, second yields P2
|
||||
seq = [P1, P2]
|
||||
|
||||
def _factory(*a, **k):
|
||||
cls = seq.pop(0)
|
||||
return cls(*a, **k)
|
||||
|
||||
monkeypatch.setattr("bouquin.settings_dialog.KeyPrompt", _factory)
|
||||
dlg._change_key() # returns early
|
||||
|
||||
|
||||
def test_change_key_empty_and_exception_paths(qtbot, tmp_db_cfg, fresh_db, monkeypatch):
|
||||
host, dlg = _make_host_and_dialog(tmp_db_cfg, fresh_db)
|
||||
qtbot.addWidget(dlg)
|
||||
|
||||
# Timer that auto-accepts any modal QMessageBox so we don't hang.
|
||||
def _pump_boxes():
|
||||
# Try both the active modal and the general top-level enumeration
|
||||
m = QApplication.activeModalWidget()
|
||||
if isinstance(m, QMessageBox):
|
||||
m.accept()
|
||||
for w in QApplication.topLevelWidgets():
|
||||
if isinstance(w, QMessageBox):
|
||||
w.accept()
|
||||
|
||||
timer = QTimer()
|
||||
timer.setInterval(10)
|
||||
timer.timeout.connect(_pump_boxes)
|
||||
timer.start()
|
||||
|
||||
try:
|
||||
|
||||
class P1:
|
||||
def __init__(self, *a, **k):
|
||||
pass
|
||||
|
||||
def exec(self):
|
||||
return QDialog.Accepted
|
||||
|
||||
def key(self):
|
||||
return ""
|
||||
|
||||
class P2:
|
||||
def __init__(self, *a, **k):
|
||||
pass
|
||||
|
||||
def exec(self):
|
||||
return QDialog.Accepted
|
||||
|
||||
def key(self):
|
||||
return ""
|
||||
|
||||
seq = [P1, P2, P1, P2]
|
||||
|
||||
def _factory(*a, **k):
|
||||
cls = seq.pop(0)
|
||||
return cls(*a, **k)
|
||||
|
||||
monkeypatch.setattr("bouquin.settings_dialog.KeyPrompt", _factory)
|
||||
# First run triggers empty-key warning path and return (auto-closed)
|
||||
dlg._change_key()
|
||||
|
||||
# Now make rekey() raise to hit the except block (critical dialog)
|
||||
def boom(*a, **k):
|
||||
raise RuntimeError("nope")
|
||||
|
||||
dlg._db.rekey = boom
|
||||
|
||||
# Return a non-empty matching key twice
|
||||
class P3:
|
||||
def __init__(self, *a, **k):
|
||||
pass
|
||||
|
||||
def exec(self):
|
||||
return QDialog.Accepted
|
||||
|
||||
def key(self):
|
||||
return "secret"
|
||||
|
||||
monkeypatch.setattr("bouquin.settings_dialog.KeyPrompt", lambda *a, **k: P3())
|
||||
dlg._change_key()
|
||||
finally:
|
||||
timer.stop()
|
||||
|
||||
|
||||
def test_save_key_btn_clicked_cancel_flow(qtbot, tmp_db_cfg, fresh_db, monkeypatch):
|
||||
host, dlg = _make_host_and_dialog(tmp_db_cfg, fresh_db)
|
||||
qtbot.addWidget(dlg)
|
||||
|
||||
# Make sure we start with no key saved so it will prompt
|
||||
dlg.key = ""
|
||||
|
||||
class P1:
|
||||
def __init__(self, *a, **k):
|
||||
pass
|
||||
|
||||
def exec(self):
|
||||
return QDialog.Rejected
|
||||
|
||||
def key(self):
|
||||
return ""
|
||||
|
||||
monkeypatch.setattr("bouquin.settings_dialog.KeyPrompt", P1)
|
||||
|
||||
dlg.save_key_btn.setChecked(True) # toggles and calls handler
|
||||
# Handler should have undone the checkbox back to False
|
||||
assert not dlg.save_key_btn.isChecked()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue