mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-11-10 18:07:20 +01:00
162 lines
5.7 KiB
Python
162 lines
5.7 KiB
Python
"""The gate's Body tab: its handlers, input parsing and weight graph.
|
|
|
|
Built on the in-memory Tk fakes (``fake_tk``), so no window ever opens.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import date
|
|
from typing import TYPE_CHECKING
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from diet_guard import _gatelock_body
|
|
from diet_guard import _gatelock_glyphs as g
|
|
from diet_guard._body_fat_store import body_fats
|
|
from diet_guard._body_prefs import saved_goal
|
|
from diet_guard._body_store import profile, set_profile, set_weight, weights
|
|
from diet_guard._budget import read_raw_record, write_budget
|
|
from diet_guard._gatelock_body import BodyTab
|
|
from diet_guard._gatelock_body_graph import (
|
|
GraphBox,
|
|
draw_weight_graph,
|
|
graph_points,
|
|
in_range,
|
|
value_span,
|
|
)
|
|
from diet_guard._gatelock_body_input import parse_day, parse_float
|
|
from diet_guard.tests._gate_fixtures import fake_tk
|
|
from diet_guard.tests._tk_fakes import FakeCanvas, FakeNotebook
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Iterator
|
|
|
|
|
|
@pytest.fixture
|
|
def tab() -> Iterator[BodyTab]:
|
|
with (
|
|
fake_tk(),
|
|
patch.object(_gatelock_body, "publish_after_log_detached") as publish,
|
|
):
|
|
body = BodyTab(FakeNotebook())
|
|
body.publish = publish # the test can assert it
|
|
yield body
|
|
|
|
|
|
def _person() -> None:
|
|
set_profile(birth="2000-05-22", height_cm=169.0, sex="m")
|
|
set_weight("2026-09-27", 72.6)
|
|
|
|
|
|
class TestInput:
|
|
def test_parsing(self) -> None:
|
|
assert parse_float(" 72,6 ") == 72.6
|
|
assert parse_float("x") is None
|
|
assert parse_day(" 2026-09-28") == "2026-09-28"
|
|
assert parse_day("today") is None
|
|
|
|
|
|
class TestProfile:
|
|
def test_rejects_then_saves(self, tab: BodyTab) -> None:
|
|
tab.vars.profile.height.set("9")
|
|
tab.save_profile()
|
|
assert tab.vars.status.get().startswith(g.ERROR)
|
|
tab.vars.profile.birth.set("2000-05-22")
|
|
tab.vars.profile.height.set("169")
|
|
tab.vars.profile.sex.set("m")
|
|
tab.save_profile()
|
|
assert profile().complete
|
|
assert tab.vars.status.get() == g.OK
|
|
|
|
def test_complete_profile_loads_into_the_inputs(self) -> None:
|
|
_person()
|
|
with fake_tk():
|
|
body = BodyTab(FakeNotebook())
|
|
assert body.vars.profile.height.get() == "169"
|
|
assert body.vars.goal.answer.get().startswith("→ ")
|
|
|
|
|
|
class TestWeights:
|
|
def test_save_reject_delete(self, tab: BodyTab) -> None:
|
|
write_budget(2000, weight_kg=90.0)
|
|
tab.vars.logs.weight.set("5")
|
|
tab.save_weight()
|
|
assert tab.vars.status.get().startswith(g.ERROR)
|
|
tab.vars.logs.weight.set("80")
|
|
tab.vars.logs.weight_day.set("2026-09-10")
|
|
tab.save_weight()
|
|
assert weights() == {"2026-09-10": 80.0}
|
|
assert (read_raw_record() or {})["w"] == 80.0
|
|
tab.vars.logs.weight_day.set("nope")
|
|
tab.delete_weight()
|
|
assert tab.vars.status.get() == g.ERROR + " " + g.DELETE + " 0000-00-00"
|
|
tab.vars.logs.weight_day.set("2026-09-10")
|
|
tab.delete_weight()
|
|
assert weights() == {}
|
|
|
|
def test_body_fat_save_reject_delete(self, tab: BodyTab) -> None:
|
|
tab.vars.logs.body_fat.set("90")
|
|
tab.save_body_fat()
|
|
assert tab.vars.status.get().startswith(g.ERROR)
|
|
tab.vars.logs.body_fat.set("17")
|
|
tab.save_body_fat()
|
|
assert body_fats() == {tab.vars.logs.fat_day.get(): 17.0}
|
|
tab.vars.logs.fat_day.set("x")
|
|
tab.delete_body_fat()
|
|
assert tab.vars.status.get() == g.ERROR + " " + g.DELETE + " 0000-00-00"
|
|
tab.vars.logs.fat_day.set(next(iter(body_fats())))
|
|
tab.delete_body_fat()
|
|
assert body_fats() == {}
|
|
|
|
|
|
class TestGoalAndGraph:
|
|
def test_goal_change_saves_and_reanswers(self, tab: BodyTab) -> None:
|
|
_person()
|
|
tab.vars.goal.weight_unit.set("lb")
|
|
tab.vars.goal.amount.set("oops")
|
|
tab.goal_changed()
|
|
assert saved_goal().weight_unit == "lb"
|
|
assert saved_goal().amount == 0.5 # unreadable amount kept
|
|
assert tab.vars.goal.amount.get() == "0.5"
|
|
assert tab.vars.goal.answer.get().startswith("→ ")
|
|
|
|
def test_range_and_body_fat_graph(self, tab: BodyTab) -> None:
|
|
_person()
|
|
tab.vars.plan.days[0].set("2500")
|
|
tab.vars.plan.target.set("")
|
|
tab.set_range(None)
|
|
tab.vars.logs.graph.set("fat")
|
|
tab.refresh()
|
|
assert f"2500 {g.EDIT}" in tab.vars.blocks["plan"].get()
|
|
|
|
|
|
class TestGraph:
|
|
def test_in_range_and_span(self) -> None:
|
|
log = {"2026-01-01": 80.0, "2026-09-27": 72.0}
|
|
assert in_range(log, date(2026, 9, 28), None) == log
|
|
assert in_range(log, date(2026, 9, 28), 30) == {"2026-09-27": 72.0}
|
|
low, high = value_span([72.0], (50.0, 71.0))
|
|
assert low < 71.0 < high
|
|
low, high = value_span([72.0], (40.0, 45.0))
|
|
assert low > 45.0
|
|
|
|
def test_single_point_is_centred(self) -> None:
|
|
box = GraphBox(700, 300)
|
|
((x, _),) = graph_points({"2026-09-27": 72.0}, box, (71.0, 73.0))
|
|
assert x == pytest.approx(box.x(0.5))
|
|
|
|
def test_draws_band_line_and_labels_or_a_hint(self) -> None:
|
|
canvas = FakeCanvas()
|
|
box = GraphBox(700, 300)
|
|
draw_weight_graph(canvas, {}, box, None)
|
|
assert canvas.drawn[0][0] == "text"
|
|
assert canvas.drawn[0][2]["text"] == g.EMPTY
|
|
log = {"2026-09-20": 74.0, "2026-09-27": 72.0}
|
|
draw_weight_graph(canvas, log, box, (52.8, 71.1))
|
|
kinds = [kind for kind, _, _ in canvas.drawn]
|
|
assert "rectangle" in kinds
|
|
assert kinds.count("oval") == 2
|
|
draw_weight_graph(canvas, log, box, (10.0, 20.0)) # band far off-axis
|
|
assert "rectangle" not in [kind for kind, _, _ in canvas.drawn]
|