diff --git a/USER_GUIDE.md b/USER_GUIDE.md index bb5c1c1..68d0513 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -487,3 +487,19 @@ Primary action buttons show their keyboard shortcuts in the button text (e.g., " *This document was generated by the documentation consolidation system.* *Last updated: 2025-08-05 14:53:36* + +## New in v1.14.9: Filters, columns, and exports + +### Filter presets (Save/Load/Delete) +- Open the Search/Filter panel (Ctrl+F), set filters, then click Save to store a named preset. +- A themed modal dialog asks for a name and shows if you’ll overwrite an existing preset. +- Load via the presets dropdown → Load. Delete via Delete. +- Presets persist across restarts. + +### Persistent column widths and sort +- Resize columns; widths are saved automatically and restored next run. +- Click a header to sort; the last sorted column and direction are remembered and re-applied on refresh/startup. + +### Export current (filtered) data +- In Export (Ctrl+E), choose scope: All data or Current filtered view. +- Works with CSV, JSON, XML, and PDF exporters. diff --git a/docs/FEATURES.md b/docs/FEATURES.md index f8640d0..927701a 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -209,6 +209,11 @@ Powerful data filtering and search capabilities for analyzing your health data. - Filter to last 30 days with depression scores between 3-6 - Combine filters: High anxiety + specific medicine + date range +#### Presets and Persistence (v1.14.9) +- Save/Load/Delete filter presets directly from the Search/Filter panel. Presets are named and persist across restarts. Save dialog is themed and shows overwrite/new hints. +- Column widths and last sorted column/direction are remembered. Resizing headers or sorting stores preferences; they’re re-applied on refresh/startup. +- Export can target the current filtered view: choose in the Export window to export only matching rows (CSV/JSON/XML/PDF). + ### 📝 Data Management Robust data handling with comprehensive backup and migration support. diff --git a/tests/test_persistence_features.py b/tests/test_persistence_features.py new file mode 100644 index 0000000..d35f43a --- /dev/null +++ b/tests/test_persistence_features.py @@ -0,0 +1,72 @@ +"""Tests for persistence features: column widths and last sort reapplication.""" + +import tkinter as tk +from tkinter import ttk + +import pytest + +from src.ui_manager import UIManager + + +@pytest.fixture +def root_window(): + root = tk.Tk() + yield root + root.destroy() + + +@pytest.fixture +def ui_manager(root_window, mock_logger): + return UIManager(root_window, mock_logger) + + +def test_table_applies_saved_column_widths(ui_manager, root_window, monkeypatch): + # Provide a fake get_pref that returns widths for some columns + saved = {"column_widths": {"Date": 123, "Note": 456}} + + def fake_get_pref(key, default=None): # type: ignore[override] + return saved.get(key, default) + + monkeypatch.setattr("src.ui_manager.get_pref", fake_get_pref) + + main = ttk.Frame(root_window) + table_ui = ui_manager.create_table_frame(main) + tree: ttk.Treeview = table_ui["tree"] + + # Verify widths applied + assert int(tree.column("Date", option="width")) == 123 + assert int(tree.column("Note", option="width")) == 456 + + +def test_reapply_last_sort_descending(ui_manager, root_window, monkeypatch): + # Simulate last sort on 'Date' descending + saved = {"last_sort": {"column": "Date", "ascending": False}} + + def fake_get_pref(key, default=None): # type: ignore[override] + return saved.get(key, default) + + monkeypatch.setattr("src.ui_manager.get_pref", fake_get_pref) + + main = ttk.Frame(root_window) + table_ui = ui_manager.create_table_frame(main) + tree: ttk.Treeview = table_ui["tree"] + + # Insert a few rows with Date values that sort numerically + # Columns are dynamic; ensure we provide a value for each column + cols = list(tree["columns"]) + idx_date = cols.index("Date") + + def row_with_date(val: str): + row = [""] * len(cols) + row[idx_date] = val + return row + + tree.insert("", "end", values=row_with_date("1")) + tree.insert("", "end", values=row_with_date("3")) + tree.insert("", "end", values=row_with_date("2")) + + # Reapply last sort (descending) and verify first row has Date '3' + ui_manager.reapply_last_sort(tree) + first_item = tree.get_children("")[0] + first_vals = tree.item(first_item, "values") + assert str(first_vals[idx_date]) == "3"