screen-locker/screen_locker/tests/test_status_streak_section.py
Krzysztof kuhy Rudnicki ba3a8a293d Show the workout streak in the status window
Adds StatusSectionsMixin._section_streak, called from
StatusWindow.render: the stored consecutive 5+-workout-week streak and
the reward message the week transition produced, display only. A streak
of 0 is stated plainly, never padded; a "Streak reset" message is
warning-coloured, not green. get_status already returned the streak
(it is asdict(gather_status())); StatusSnapshot gains week_rewards.

The week-transition strings used to go to _logger.info only.
process_week_transition now also keeps them under a week_rewards key in
the existing extra_benefits_state.json (no new file), read back for the
current ISO week by week_rewards(). Their "early-bird extended to 09:00"
clause is dropped: early_bird_extended is read only by display code, no
lock or window decision uses it (early bird is wake-alarm's signed
exemption since 2026-09-20), so it is not shown in the window either.

Per kuhy: no daily-streak state, no shutdown time for streaks, no
freezes or grace days; no enforcement logic touched.

Red first: new section/state/snapshot tests failed, then passed. On a
scratch copy of the real state the window shows "7-week streak" and
"6 workouts in 2026-W43! 7-week streak", no early-bird line.

Removes prompts/TODO-dopamine-ux-04-screen-locker.md (done).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWYdwRAtMg7ki6TKxuU
2026-11-01 20:04:14 +01:00

64 lines
2.4 KiB
Python

"""The status window shows the workout streak and early-bird state it tracks."""
# pylint: disable=protected-access
from __future__ import annotations
from typing import TYPE_CHECKING
from screen_locker.tests._status_view_helpers import (
_make_window,
_snapshot,
_texts,
)
if TYPE_CHECKING:
from unittest.mock import MagicMock
def _fg_of(mock_tk: MagicMock, needle: str) -> list[object]:
return [
c.kwargs.get("fg")
for c in mock_tk.Label.call_args_list
if needle in c.kwargs.get("text", "")
]
class TestSectionStreak:
"""Tests for StatusWindow._section_streak (display only)."""
def test_shows_the_real_week_count_in_success_color(
self, mock_tk: MagicMock
) -> None:
window = _make_window(mock_tk, _snapshot(streak=3))
assert any("3-week streak" in t for t in _texts(mock_tk))
assert window._colors.palette.success in _fg_of(mock_tk, "3-week streak")
def test_zero_streak_is_stated_plainly_not_padded(self, mock_tk: MagicMock) -> None:
window = _make_window(mock_tk, _snapshot(streak=0))
texts = _texts(mock_tk)
assert any("No streak" in t for t in texts)
assert not any("-week streak" in t for t in texts)
assert window._colors.palette.muted in _fg_of(mock_tk, "No streak")
def test_early_bird_is_never_rendered(self, mock_tk: MagicMock) -> None:
"""The 09:00 early-bird extension is gone (wake-alarm owns it)."""
_make_window(mock_tk, _snapshot(streak=2, early_bird_extended=True))
assert not any("arly-bird" in t for t in _texts(mock_tk))
def test_week_reward_reaches_the_window_in_success_color(
self, mock_tk: MagicMock
) -> None:
msg = "5 workouts in 2026-W41! 4-week streak"
window = _make_window(mock_tk, _snapshot(streak=4, week_rewards=(msg,)))
assert msg in _texts(mock_tk)
assert window._colors.palette.success in _fg_of(mock_tk, msg)
def test_no_reward_text_when_the_week_has_none(self, mock_tk: MagicMock) -> None:
_make_window(mock_tk, _snapshot(streak=4))
assert not any("workouts in 20" in t for t in _texts(mock_tk))
def test_a_streak_reset_is_not_painted_as_a_win(self, mock_tk: MagicMock) -> None:
msg = "Streak reset (was 3 weeks of 5+ workouts)"
window = _make_window(mock_tk, _snapshot(streak=0, week_rewards=(msg,)))
assert window._colors.palette.warning in _fg_of(mock_tk, msg)