- Introduced the `history_overview_viz` widget to the dashboard, allowing users to visualize consolidated history data across various metrics. - Updated widget configuration to include `history_overview_viz` in the allowed widgets and added validation for its configuration. - Enhanced the widget catalog with details for the new `history_overview_viz` entry. - Implemented default values and validation logic for the widget's configuration, ensuring proper handling of user inputs. - Added tests to ensure proper validation of the `history_overview_viz` widget configuration. - Bumped application version to reflect the addition of the new widget.
308 lines
11 KiB
Python
308 lines
11 KiB
Python
import pytest
|
|
|
|
from dashboard_layout_schema import DashboardLayoutPayload, coalesce_effective_layout, default_layout_dict
|
|
from dashboard_widget_config import validate_widget_entry_config
|
|
|
|
|
|
def test_body_chart_days_bounds():
|
|
assert validate_widget_entry_config("body_overview", {"chart_days": 7}) == {"chart_days": 7}
|
|
assert validate_widget_entry_config("body_overview", {"chart_days": 90}) == {"chart_days": 90}
|
|
assert validate_widget_entry_config("body_overview", {"chart_days": 42.0}) == {"chart_days": 42}
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("body_overview", {"chart_days": 6})
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("body_overview", {"chart_days": 91})
|
|
|
|
|
|
def test_body_history_viz_empty_expands_defaults():
|
|
d = validate_widget_entry_config("body_history_viz", {})
|
|
assert d["chart_days"] == 30
|
|
assert d["show_kpis"] is True
|
|
assert d["show_weight_chart"] is True
|
|
assert d["kpi_detail"] == "compact"
|
|
assert d["show_body_fat_chart"] is False
|
|
|
|
|
|
def test_body_history_viz_chart_days_and_merge():
|
|
d = validate_widget_entry_config("body_history_viz", {"chart_days": 60})
|
|
assert d["chart_days"] == 60
|
|
assert d["show_goals_strip"] is False
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("body_history_viz", {"chart_days": 5})
|
|
|
|
|
|
def test_body_history_viz_requires_visible_block():
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config(
|
|
"body_history_viz",
|
|
{"show_kpis": False, "show_weight_chart": False},
|
|
)
|
|
|
|
|
|
def test_body_history_viz_unknown_key():
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("body_history_viz", {"evil": True})
|
|
|
|
|
|
def test_nutrition_history_viz_empty_expands_defaults():
|
|
d = validate_widget_entry_config("nutrition_history_viz", {})
|
|
assert d["chart_days"] == 30
|
|
assert d["show_kpis"] is True
|
|
assert d["show_kcal_vs_weight"] is True
|
|
assert d["kpi_detail"] == "compact"
|
|
assert d["show_calorie_balance_chart"] is False
|
|
assert d["show_energy_protein_charts"] is False
|
|
|
|
|
|
def test_nutrition_history_viz_chart_days_and_merge():
|
|
d = validate_widget_entry_config("nutrition_history_viz", {"chart_days": 45})
|
|
assert d["chart_days"] == 45
|
|
assert d["show_goals_strip"] is False
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("nutrition_history_viz", {"chart_days": 5})
|
|
|
|
|
|
def test_nutrition_history_viz_requires_visible_block():
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config(
|
|
"nutrition_history_viz",
|
|
{"show_kpis": False, "show_kcal_vs_weight": False, "show_macro_daily_bars": False, "show_macro_distribution_pair": False},
|
|
)
|
|
|
|
|
|
def test_nutrition_history_viz_unknown_key():
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("nutrition_history_viz", {"evil": True})
|
|
|
|
|
|
def test_fitness_history_viz_empty_expands_defaults():
|
|
d = validate_widget_entry_config("fitness_history_viz", {})
|
|
assert d["chart_days"] == 30
|
|
assert d["show_kpis"] is True
|
|
assert d["show_chart_training_volume"] is True
|
|
assert d["kpi_detail"] == "compact"
|
|
assert d["show_layer_meta"] is False
|
|
assert d["show_chart_load_monitoring"] is False
|
|
|
|
|
|
def test_fitness_history_viz_chart_days_and_merge():
|
|
d = validate_widget_entry_config("fitness_history_viz", {"chart_days": 60})
|
|
assert d["chart_days"] == 60
|
|
assert d["show_progress_insights"] is False
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("fitness_history_viz", {"chart_days": 5})
|
|
|
|
|
|
def test_fitness_history_viz_requires_visible_block():
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config(
|
|
"fitness_history_viz",
|
|
{
|
|
"show_kpis": False,
|
|
"show_progress_insights": False,
|
|
"show_chart_training_volume": False,
|
|
"show_chart_training_type_distribution": False,
|
|
"show_chart_quality_sessions": False,
|
|
"show_chart_load_monitoring": False,
|
|
},
|
|
)
|
|
|
|
|
|
def test_fitness_history_viz_unknown_key():
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("fitness_history_viz", {"evil": True})
|
|
|
|
|
|
def test_recovery_history_viz_empty_expands_defaults():
|
|
d = validate_widget_entry_config("recovery_history_viz", {})
|
|
assert d["chart_days"] == 30
|
|
assert d["show_kpis"] is True
|
|
assert d["show_chart_recovery_score"] is True
|
|
assert d["kpi_detail"] == "compact"
|
|
assert d["show_heart_context_card"] is False
|
|
assert d["show_vitals_extra_trends"] is False
|
|
|
|
|
|
def test_recovery_history_viz_chart_days_and_merge():
|
|
d = validate_widget_entry_config("recovery_history_viz", {"chart_days": 42})
|
|
assert d["chart_days"] == 42
|
|
assert d["show_layer_meta"] is False
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("recovery_history_viz", {"chart_days": 3})
|
|
|
|
|
|
def test_recovery_history_viz_requires_visible_block():
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config(
|
|
"recovery_history_viz",
|
|
{
|
|
"show_kpis": False,
|
|
"show_progress_insights": False,
|
|
"show_heart_context_card": False,
|
|
"show_vitals_extra_trends": False,
|
|
"show_chart_recovery_score": False,
|
|
"show_chart_sleep_quality": False,
|
|
"show_chart_sleep_debt": False,
|
|
"show_chart_hrv_rhr": False,
|
|
},
|
|
)
|
|
|
|
|
|
def test_recovery_history_viz_unknown_key():
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("recovery_history_viz", {"evil": True})
|
|
|
|
|
|
def test_history_overview_viz_empty_expands_defaults():
|
|
d = validate_widget_entry_config("history_overview_viz", {})
|
|
assert d["chart_days"] == 30
|
|
assert d["show_confidence_banner"] is True
|
|
assert d["show_area_summaries"] is True
|
|
assert d["show_correlation_c1_c3"] is True
|
|
assert d["show_drivers_c4"] is True
|
|
|
|
|
|
def test_history_overview_viz_chart_days_and_merge():
|
|
d = validate_widget_entry_config("history_overview_viz", {"chart_days": 60})
|
|
assert d["chart_days"] == 60
|
|
assert d["show_intro_blurb"] is True
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("history_overview_viz", {"chart_days": 5})
|
|
|
|
|
|
def test_history_overview_viz_requires_visible_block():
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config(
|
|
"history_overview_viz",
|
|
{
|
|
"show_confidence_banner": False,
|
|
"show_area_summaries": False,
|
|
"show_correlation_c1_c3": False,
|
|
"show_drivers_c4": False,
|
|
},
|
|
)
|
|
|
|
|
|
def test_history_overview_viz_unknown_key():
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("history_overview_viz", {"evil": True})
|
|
|
|
|
|
def test_welcome_config_rejected_unknown_key():
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("welcome", {"x": 1})
|
|
|
|
|
|
def test_body_unknown_key():
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("body_overview", {"chart_days": 30, "extra": 1})
|
|
|
|
|
|
def test_activity_chart_days():
|
|
assert validate_widget_entry_config("activity_overview", {"chart_days": 14}) == {"chart_days": 14}
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("activity_overview", {"chart_days": 5})
|
|
|
|
|
|
def test_kpi_board_tiles():
|
|
assert validate_widget_entry_config("kpi_board", {}) == {}
|
|
assert validate_widget_entry_config("kpi_board", {"tiles": []}) == {"tiles": []}
|
|
assert validate_widget_entry_config(
|
|
"kpi_board",
|
|
{"tiles": [{"id": "body_fat"}, {"id": "avg_kcal"}, {"id": "ref:hr_max"}]},
|
|
) == {"tiles": [{"id": "body_fat"}, {"id": "avg_kcal"}, {"id": "ref:hr_max"}]}
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("kpi_board", {"tiles": [{"id": "unknown"}]})
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("kpi_board", {"tiles": [{"id": "body_fat"}, {"id": "body_fat"}]})
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("kpi_board", {"extra": 1})
|
|
|
|
|
|
def test_quick_capture_visibility():
|
|
assert validate_widget_entry_config("quick_capture", {}) == {}
|
|
assert validate_widget_entry_config("quick_capture", {"show_weight": False}) == {"show_weight": False}
|
|
full = {
|
|
"show_weight": True,
|
|
"show_resting_hr": False,
|
|
"show_hrv": True,
|
|
"show_vo2_max": False,
|
|
}
|
|
assert validate_widget_entry_config("quick_capture", full) == full
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("quick_capture", {"show_weight": "yes"})
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config(
|
|
"quick_capture",
|
|
{
|
|
"show_weight": False,
|
|
"show_resting_hr": False,
|
|
"show_hrv": False,
|
|
"show_vo2_max": False,
|
|
},
|
|
)
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("quick_capture", {"extra": 1})
|
|
|
|
|
|
def test_nutrition_detail_charts_days():
|
|
assert validate_widget_entry_config("nutrition_detail_charts", {}) == {}
|
|
assert validate_widget_entry_config("nutrition_detail_charts", {"chart_days": 60}) == {"chart_days": 60}
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("nutrition_detail_charts", {"chart_days": 3})
|
|
|
|
|
|
def test_recovery_charts_panel_days():
|
|
assert validate_widget_entry_config("recovery_charts_panel", {}) == {}
|
|
assert validate_widget_entry_config("recovery_charts_panel", {"chart_days": 28}) == {"chart_days": 28}
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("recovery_charts_panel", {"chart_days": 99})
|
|
|
|
|
|
def test_trend_kcal_weight_chart_days():
|
|
assert validate_widget_entry_config("trend_kcal_weight", {}) == {}
|
|
assert validate_widget_entry_config("trend_kcal_weight", {"chart_days": 30}) == {"chart_days": 30}
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("trend_kcal_weight", {"chart_days": 6})
|
|
|
|
|
|
def test_kpi_board_legacy_chart_days_dropped():
|
|
"""Nur chart_days (Alt-Layouts) → automatische Kachelwahl, kein Ø-Kal-Fenster mehr."""
|
|
assert validate_widget_entry_config("kpi_board", {"chart_days": 14}) == {}
|
|
assert validate_widget_entry_config("kpi_board", {"chart_days": 5}) == {}
|
|
|
|
|
|
def test_welcome_still_rejects_config():
|
|
with pytest.raises(ValueError):
|
|
validate_widget_entry_config("welcome", {"chart_days": 30})
|
|
|
|
|
|
def test_layout_payload_with_chart_days_roundtrip():
|
|
p = DashboardLayoutPayload.model_validate(
|
|
{
|
|
"version": 1,
|
|
"widgets": [
|
|
{"id": "welcome", "enabled": True},
|
|
{
|
|
"id": "body_overview",
|
|
"enabled": True,
|
|
"config": {"chart_days": 42},
|
|
},
|
|
],
|
|
}
|
|
)
|
|
d = p.to_stored_dict()
|
|
assert d["widgets"][1]["config"]["chart_days"] == 42
|
|
|
|
|
|
def test_coalesce_rejects_invalid_widget_config():
|
|
raw = {
|
|
"version": 1,
|
|
"widgets": [
|
|
{"id": "welcome", "enabled": True, "config": {"evil": True}},
|
|
],
|
|
}
|
|
custom, eff = coalesce_effective_layout(raw)
|
|
assert custom is False
|
|
assert eff == default_layout_dict()
|