feat: Add tests for filter presets save/load/delete behavior in SearchFilterWidget
This commit is contained in:
@@ -43,6 +43,8 @@ class SearchFilterWidget:
|
|||||||
self.is_visible = False
|
self.is_visible = False
|
||||||
self._ui_initialized = False
|
self._ui_initialized = False
|
||||||
self.frame = None
|
self.frame = None
|
||||||
|
# May be created in _setup_ui; keep defined for headless/test usage
|
||||||
|
self.status_label = None
|
||||||
|
|
||||||
# Debouncing mechanism to reduce filter update frequency
|
# Debouncing mechanism to reduce filter update frequency
|
||||||
self._update_timer = None
|
self._update_timer = None
|
||||||
@@ -430,6 +432,9 @@ class SearchFilterWidget:
|
|||||||
|
|
||||||
def _update_status(self) -> None:
|
def _update_status(self) -> None:
|
||||||
"""Update filter status display."""
|
"""Update filter status display."""
|
||||||
|
# If UI hasn't been set up yet (e.g., during headless tests), skip.
|
||||||
|
if not getattr(self, "status_label", None):
|
||||||
|
return
|
||||||
summary = self.data_filter.get_filter_summary()
|
summary = self.data_filter.get_filter_summary()
|
||||||
|
|
||||||
if not summary["has_filters"]:
|
if not summary["has_filters"]:
|
||||||
|
|||||||
112
tests/test_filter_presets.py
Normal file
112
tests/test_filter_presets.py
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
"""Tests for filter presets save/load/delete behavior in SearchFilterWidget."""
|
||||||
|
|
||||||
|
import tkinter as tk
|
||||||
|
import pytest
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from src.search_filter_ui import SearchFilterWidget
|
||||||
|
from src.search_filter import DataFilter
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def tk_root():
|
||||||
|
root = tk.Tk()
|
||||||
|
root.withdraw()
|
||||||
|
yield root
|
||||||
|
root.destroy()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def widget(tk_root):
|
||||||
|
# Minimal managers
|
||||||
|
med_mgr = MagicMock()
|
||||||
|
med_mgr.get_medicine_keys.return_value = ["med1", "med2"]
|
||||||
|
m1 = MagicMock(); m1.display_name = "Medicine 1"
|
||||||
|
m2 = MagicMock(); m2.display_name = "Medicine 2"
|
||||||
|
med_mgr.get_medicine.side_effect = lambda k: {"med1": m1, "med2": m2}.get(k)
|
||||||
|
|
||||||
|
path_mgr = MagicMock()
|
||||||
|
path_mgr.get_pathology_keys.return_value = ["path1", "path2"]
|
||||||
|
p1 = MagicMock(); p1.display_name = "Pathology 1"
|
||||||
|
p2 = MagicMock(); p2.display_name = "Pathology 2"
|
||||||
|
path_mgr.get_pathology.side_effect = lambda k: {"path1": p1, "path2": p2}.get(k)
|
||||||
|
|
||||||
|
data_filter = MagicMock(spec=DataFilter)
|
||||||
|
update_cb = MagicMock()
|
||||||
|
|
||||||
|
w = SearchFilterWidget(
|
||||||
|
parent=tk_root,
|
||||||
|
data_filter=data_filter,
|
||||||
|
update_callback=update_cb,
|
||||||
|
medicine_manager=med_mgr,
|
||||||
|
pathology_manager=path_mgr,
|
||||||
|
)
|
||||||
|
return w, data_filter, update_cb
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_preset_creates_when_new(widget, monkeypatch):
|
||||||
|
w, data_filter, _update_cb = widget
|
||||||
|
|
||||||
|
# DataFilter summary to save
|
||||||
|
summary = {"has_filters": True, "search_term": "abc", "filters": {}}
|
||||||
|
data_filter.get_filter_summary.return_value = summary
|
||||||
|
|
||||||
|
# Pretend no existing presets
|
||||||
|
monkeypatch.setattr("src.search_filter_ui.get_pref", lambda k, d=None: {})
|
||||||
|
|
||||||
|
saved = {}
|
||||||
|
def fake_set_pref(key, value):
|
||||||
|
saved[key] = value
|
||||||
|
monkeypatch.setattr("src.search_filter_ui.set_pref", fake_set_pref)
|
||||||
|
|
||||||
|
called = {"saved": False}
|
||||||
|
def fake_save_preferences():
|
||||||
|
called["saved"] = True
|
||||||
|
monkeypatch.setattr("src.search_filter_ui.save_preferences", fake_save_preferences)
|
||||||
|
|
||||||
|
# Bypass dialog
|
||||||
|
monkeypatch.setattr(SearchFilterWidget, "_ask_preset_name", lambda self, initial="": "TestPreset")
|
||||||
|
|
||||||
|
w._save_preset()
|
||||||
|
|
||||||
|
assert "filter_presets" in saved
|
||||||
|
assert saved["filter_presets"]["TestPreset"] == summary
|
||||||
|
assert called["saved"] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_preset_applies_filters(widget, monkeypatch):
|
||||||
|
w, data_filter, update_cb = widget
|
||||||
|
|
||||||
|
# Craft a saved preset summary
|
||||||
|
summary = {
|
||||||
|
"has_filters": True,
|
||||||
|
"search_term": "headache",
|
||||||
|
"filters": {
|
||||||
|
"date_range": {"start": "2024-01-01", "end": "2024-12-31"},
|
||||||
|
"medicines": {"taken": ["med1"], "not_taken": ["med2"]},
|
||||||
|
"pathologies": {"path1": "2-8"}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Provide get_pref to return our preset
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"src.search_filter_ui.get_pref",
|
||||||
|
lambda k, d=None: {"filter_presets": {"MyPreset": summary}}.get(k, d),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Select the preset and load
|
||||||
|
w.preset_var.set("MyPreset")
|
||||||
|
|
||||||
|
# Suppress any warnings
|
||||||
|
monkeypatch.setattr("src.search_filter_ui.messagebox.showwarning", lambda *a, **k: None)
|
||||||
|
|
||||||
|
w._load_preset()
|
||||||
|
|
||||||
|
# Verify DataFilter received expected calls
|
||||||
|
data_filter.clear_all_filters.assert_called()
|
||||||
|
data_filter.set_search_term.assert_called_with("headache")
|
||||||
|
data_filter.set_date_range_filter.assert_called_with("2024-01-01", "2024-12-31")
|
||||||
|
data_filter.set_medicine_filter.assert_any_call("med1", True)
|
||||||
|
data_filter.set_medicine_filter.assert_any_call("med2", False)
|
||||||
|
data_filter.set_pathology_range_filter.assert_any_call("path1", 2, 8)
|
||||||
|
update_cb.assert_called()
|
||||||
Reference in New Issue
Block a user