diet-guard/diet_guard/tests/test_calendar_view.py
Krzysztof kuhy Rudnicki 43e4b4070d Replace PC gate text with glyphs
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 15:35:34 +01:00

131 lines
4.8 KiB
Python

"""Tests for the pure (no-Tk) calendar grid math and status text."""
from __future__ import annotations
from datetime import date
from diet_guard import _gatelock_glyphs as g
from diet_guard._calendar_view import (
CalendarCell,
build_month_cells,
cell_style,
streaks_text,
ytd_text,
)
from diet_guard._daystatus import DayStatus
from diet_guard._gatelock_ui import BG, FG
class TestBuildMonthCells:
"""Boundary matrix for :func:`build_month_cells`."""
def test_blank_cells_outside_the_month_have_no_day_or_status(self) -> None:
# July 2026 starts on a Wednesday, so the first row has leading blanks.
weeks = build_month_cells(2026, 7, {}, today=date(2026, 7, 31))
assert weeks[0][0] == CalendarCell(day=None, status=None)
def test_a_day_present_in_the_status_map_carries_its_status(self) -> None:
weeks = build_month_cells(
2026,
7,
{"2026-07-15": DayStatus.GREEN},
today=date(2026, 7, 31),
)
cell = next(c for week in weeks for c in week if c.day == 15)
assert cell.status is DayStatus.GREEN
def test_a_past_day_absent_from_the_status_map_is_not_logged(self) -> None:
weeks = build_month_cells(2026, 7, {}, today=date(2026, 7, 31))
cell = next(c for week in weeks for c in week if c.day == 1)
assert cell.status is DayStatus.NOT_LOGGED
def test_a_future_day_is_neutral_not_not_logged(self) -> None:
weeks = build_month_cells(2026, 7, {}, today=date(2026, 7, 10))
cell = next(c for week in weeks for c in week if c.day == 15)
assert cell.status is None
def test_today_itself_is_classified_not_treated_as_future(self) -> None:
weeks = build_month_cells(
2026,
7,
{"2026-07-10": DayStatus.RED},
today=date(2026, 7, 10),
)
cell = next(c for week in weeks for c in week if c.day == 10)
assert cell.status is DayStatus.RED
def test_none_status_map_renders_every_past_day_neutral(self) -> None:
weeks = build_month_cells(2026, 7, None, today=date(2026, 7, 31))
cell = next(c for week in weeks for c in week if c.day == 1)
assert cell.status is None
def test_defaults_today_to_the_real_current_date(self) -> None:
# No explicit `today` -- exercises the now_local() fallback branch.
weeks = build_month_cells(2020, 1, {})
assert weeks[0][0].day in (None, 1)
def test_a_28_day_february_produces_four_or_five_weeks(self) -> None:
weeks = build_month_cells(2026, 2, {}, today=date(2026, 2, 28))
assert len(weeks) in (4, 5)
def test_a_31_day_month_starting_on_saturday_produces_six_weeks(self) -> None:
# August 2026 starts on a Saturday and has 31 days -> 6 rows.
weeks = build_month_cells(2026, 8, {}, today=date(2026, 8, 31))
assert len(weeks) == 6
def test_every_day_of_the_month_is_present_exactly_once(self) -> None:
weeks = build_month_cells(2026, 7, {}, today=date(2026, 7, 31))
days = [cell.day for week in weeks for cell in week if cell.day is not None]
assert days == list(range(1, 32))
class TestCellStyle:
"""Boundary matrix for :func:`cell_style`."""
def test_none_status_is_neutral(self) -> None:
bg, _fg, outline = cell_style(None)
assert bg == outline
def test_not_logged_is_hollow_with_a_visible_outline(self) -> None:
"""A "not logged" cell blends into the background but keeps a
near-white outline, distinguishing "no data" from a black square
(safe-design-rules rule 1: no pure black)."""
bg, _fg, outline = cell_style(DayStatus.NOT_LOGGED)
assert bg == BG
assert outline == FG
assert outline != bg
def test_green_yellow_red_each_style_distinctly(self) -> None:
styles = {
status: cell_style(status)
for status in (DayStatus.GREEN, DayStatus.YELLOW, DayStatus.RED)
}
assert len({style[0] for style in styles.values()}) == 3
class TestStreaksText:
""":func:`streaks_text` boundary cases (zero, one)."""
def test_zero_streaks_render_as_zeros(self) -> None:
text = streaks_text({}, today=date(2026, 7, 13))
assert text == f"{g.FOOD} 0 {g.OK} 0"
def test_a_one_day_streak(self) -> None:
text = streaks_text(
{"2026-07-13": DayStatus.GREEN},
today=date(2026, 7, 13),
)
assert text == f"{g.FOOD} 1 {g.OK} 1"
class TestYtdText:
""":func:`ytd_text` renders the tally's counts."""
def test_reports_logged_elapsed_and_adherent_counts(self) -> None:
status_map_ = {
"2026-01-01": DayStatus.GREEN,
"2026-01-02": DayStatus.RED,
}
text = ytd_text(status_map_, today=date(2026, 1, 10))
assert text == f"{g.YEAR_TOTAL} {g.FOOD} 2/10 {g.OK} 1"