mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-11 12:07:44 +01:00
Booleaness asserts, hoisted imports, fixture(name=...) instead of shadowing arguments, explicit encodings, snake_case names, comparison of bound methods, dead protected-access disables. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzxdWYdwRAtMg7ki6TKxuU
251 lines
9.0 KiB
Python
251 lines
9.0 KiB
Python
"""The lock's "choose lockers" link: sickdays' KeepPanel, embedded in the lock.
|
|
|
|
A recording fake replaces the panel. The pool is written for real before
|
|
``on_saved`` fires: the lock branches on the pool, not on the entry.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
import sickdays
|
|
import sickdays.gui._panel
|
|
|
|
from screen_locker import _ui_flows_sick, _ui_flows_sick_panel
|
|
from screen_locker._ui_flows_sick_panel import KEPT_NOTE, LINK_TEXT, build_sick_panel
|
|
from screen_locker.tests._sick_helpers import mark_sick
|
|
from screen_locker.tests.conftest import create_locker
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Callable, Iterator
|
|
from datetime import date
|
|
from pathlib import Path
|
|
|
|
from screen_locker.screen_lock import ScreenLocker
|
|
|
|
|
|
_MADE: list[FakePanel] = []
|
|
|
|
|
|
class FakePanel:
|
|
"""Records how the lock built it; ``on_saved``/``on_cancel`` are driven."""
|
|
|
|
def __init__(
|
|
self,
|
|
parent: MagicMock,
|
|
day: date,
|
|
*,
|
|
on_saved: Callable[[sickdays.SickDay | None], None],
|
|
on_cancel: Callable[[], None] | None = None,
|
|
paths: sickdays.Paths | None = None,
|
|
) -> None:
|
|
"""Keep every argument for the assertions."""
|
|
self.parent = parent
|
|
self.day = day
|
|
self.on_saved = on_saved
|
|
self.on_cancel = on_cancel
|
|
self.paths = paths
|
|
self.packed = False
|
|
_MADE.append(self)
|
|
|
|
def pack(self, **_kwargs: object) -> None:
|
|
"""Record that the lock showed it."""
|
|
self.packed = True
|
|
|
|
|
|
def _frame(*args: object, **_kwargs: object) -> MagicMock:
|
|
"""A distinct mock frame that remembers its parent, like Tk's ``master``."""
|
|
frame = MagicMock()
|
|
frame.master = args[0] if args else None
|
|
frame.winfo_children.return_value = []
|
|
return frame
|
|
|
|
|
|
@pytest.fixture(name="panels")
|
|
def panels_fixture() -> Iterator[list[FakePanel]]:
|
|
"""Every KeepPanel the lock builds, in order."""
|
|
_MADE.clear()
|
|
with patch.object(_ui_flows_sick_panel, "KeepPanel", FakePanel):
|
|
yield _MADE
|
|
|
|
|
|
@pytest.fixture(name="locker")
|
|
def locker_fixture(
|
|
mock_tk: MagicMock, mock_sys_exit: MagicMock, tmp_path: Path
|
|
) -> ScreenLocker:
|
|
"""A retry-screen lock whose floor and close are observable."""
|
|
built = create_locker(mock_tk, tmp_path)
|
|
mock_tk.Frame.side_effect = _frame
|
|
object.__setattr__(built, "_apply_sick_floor", MagicMock())
|
|
object.__setattr__(built, "close", MagicMock())
|
|
return built
|
|
|
|
|
|
def _backdrop(panel: FakePanel) -> MagicMock:
|
|
"""The full-surface frame the panel's centred box sits on."""
|
|
return panel.parent.master
|
|
|
|
|
|
def _texts(mock: MagicMock) -> list[str]:
|
|
return [str(c.kwargs.get("text", "")) for c in mock.call_args_list]
|
|
|
|
|
|
class TestLink:
|
|
def test_the_link_sits_next_to_the_one_tap_button(
|
|
self, mock_tk: MagicMock, locker: ScreenLocker
|
|
) -> None:
|
|
locker._show_retry_and_sick("nope")
|
|
buttons = mock_tk.Button.call_args_list
|
|
texts = _texts(mock_tk.Button)
|
|
sick = next(c for c in buttons if c.kwargs.get("text") == "I'm sick")
|
|
link = next(c for c in buttons if c.kwargs.get("text") == LINK_TEXT)
|
|
assert sick.kwargs["command"] == locker.mark_sick_and_unlock
|
|
assert link.kwargs["command"] == locker.open_sick_panel
|
|
assert texts.index(LINK_TEXT) == texts.index("I'm sick") + 1
|
|
|
|
def test_opening_builds_todays_panel_on_a_backdrop(
|
|
self, locker: ScreenLocker, panels: list[FakePanel]
|
|
) -> None:
|
|
locker.open_sick_panel()
|
|
panel = panels[0]
|
|
assert panel.day == sickdays.today()
|
|
assert panel.packed
|
|
assert panel.on_cancel is not None
|
|
_backdrop(panel).place.assert_called_once_with(
|
|
relx=0, rely=0, relwidth=1, relheight=1
|
|
)
|
|
|
|
@pytest.mark.usefixtures("dual_output")
|
|
def test_every_monitor_gets_a_panel_and_the_primary_gets_focus(
|
|
self, locker: ScreenLocker, panels: list[FakePanel]
|
|
) -> None:
|
|
locker.open_sick_panel()
|
|
assert len(panels) == 2
|
|
panels[0].parent.tk_focusNext.return_value.focus_set.assert_called_once_with()
|
|
panels[1].parent.tk_focusNext.return_value.focus_set.assert_not_called()
|
|
|
|
def test_reopening_closes_the_previous_panel(
|
|
self, locker: ScreenLocker, panels: list[FakePanel]
|
|
) -> None:
|
|
locker.open_sick_panel()
|
|
first = _backdrop(panels[0])
|
|
locker.open_sick_panel()
|
|
first.destroy.assert_called_once_with()
|
|
|
|
def test_build_sick_panel_passes_paths_through(
|
|
self, mock_tk: MagicMock, panels: list[FakePanel], tmp_path: Path
|
|
) -> None:
|
|
paths = sickdays.Paths.under(tmp_path / "pool")
|
|
saved, cancel = MagicMock(), MagicMock()
|
|
build_sick_panel(
|
|
MagicMock(), MagicMock(), on_saved=saved, on_cancel=cancel, paths=paths
|
|
)
|
|
assert panels[0].paths is paths
|
|
assert panels[0].on_saved is saved
|
|
assert panels[0].on_cancel is cancel
|
|
|
|
|
|
class TestSaved:
|
|
@pytest.fixture(autouse=True)
|
|
def _no_mark_no_push(self) -> Iterator[tuple[MagicMock, MagicMock]]:
|
|
with (
|
|
patch.object(_ui_flows_sick, "mark_today_sick") as mark,
|
|
patch.object(_ui_flows_sick, "push_in_background") as push,
|
|
):
|
|
yield mark, push
|
|
# The panel already wrote and started its own sync.
|
|
mark.assert_not_called()
|
|
push.assert_not_called()
|
|
|
|
def test_sick_and_standing_down_unlocks_like_the_one_tap(
|
|
self, locker: ScreenLocker, panels: list[FakePanel]
|
|
) -> None:
|
|
locker.open_sick_panel()
|
|
day = mark_sick(keep=[sickdays.LOCKER_LEETCODE])
|
|
entry = sickdays.SickDay(
|
|
day=day, is_sick=True, keep=frozenset({sickdays.LOCKER_LEETCODE})
|
|
)
|
|
with patch("screen_locker._startup_checks.record_decision") as record:
|
|
panels[0].on_saved(entry)
|
|
locker._apply_sick_floor.assert_called_once_with()
|
|
assert record.call_args.args[0].reason == "sick_day_marked"
|
|
locker.close.assert_called_once_with()
|
|
|
|
def test_sick_but_workout_kept_stays_locked_with_a_note(
|
|
self, mock_tk: MagicMock, locker: ScreenLocker, panels: list[FakePanel]
|
|
) -> None:
|
|
locker.open_sick_panel()
|
|
backdrop = _backdrop(panels[0])
|
|
day = mark_sick(keep=[sickdays.LOCKER_WORKOUT])
|
|
entry = sickdays.SickDay(
|
|
day=day, is_sick=True, keep=frozenset({sickdays.LOCKER_WORKOUT})
|
|
)
|
|
panels[0].on_saved(entry)
|
|
# The 20:00 floor applies on any sick day, keep or not.
|
|
locker._apply_sick_floor.assert_called_once_with()
|
|
locker.close.assert_not_called()
|
|
backdrop.destroy.assert_called_once_with()
|
|
assert KEPT_NOTE in _texts(mock_tk.Label)
|
|
mock_tk.Button.return_value.focus_set.assert_called_once_with()
|
|
back = next(
|
|
c for c in mock_tk.Button.call_args_list if c.kwargs.get("text") == "Back"
|
|
)
|
|
back.kwargs["command"]()
|
|
assert locker._sick_overlay is None
|
|
|
|
def test_cleared_closes_the_panel_and_leaves_the_lock(
|
|
self, locker: ScreenLocker, panels: list[FakePanel]
|
|
) -> None:
|
|
locker.open_sick_panel()
|
|
backdrop = _backdrop(panels[0])
|
|
entry = sickdays.SickDay(day=sickdays.today(), is_sick=False)
|
|
panels[0].on_saved(entry)
|
|
backdrop.destroy.assert_called_once_with()
|
|
locker._apply_sick_floor.assert_not_called()
|
|
locker.close.assert_not_called()
|
|
|
|
def test_unchanged_save_closes_the_panel_only(
|
|
self, locker: ScreenLocker, panels: list[FakePanel]
|
|
) -> None:
|
|
locker.open_sick_panel()
|
|
backdrop = _backdrop(panels[0])
|
|
panels[0].on_saved(None)
|
|
backdrop.destroy.assert_called_once_with()
|
|
locker._apply_sick_floor.assert_not_called()
|
|
locker.close.assert_not_called()
|
|
|
|
def test_cancel_closes_the_panel_only(
|
|
self, locker: ScreenLocker, panels: list[FakePanel]
|
|
) -> None:
|
|
locker.open_sick_panel()
|
|
backdrop = _backdrop(panels[0])
|
|
assert panels[0].on_cancel is not None
|
|
panels[0].on_cancel()
|
|
backdrop.destroy.assert_called_once_with()
|
|
assert locker._sick_overlay is None
|
|
locker._apply_sick_floor.assert_not_called()
|
|
locker.close.assert_not_called()
|
|
|
|
|
|
class TestOneTapUnchanged:
|
|
def test_one_tap_still_keeps_nothing_and_builds_no_panel(
|
|
self, locker: ScreenLocker, panels: list[FakePanel]
|
|
) -> None:
|
|
with patch.object(_ui_flows_sick, "push_in_background") as push:
|
|
locker.mark_sick_and_unlock()
|
|
assert sickdays.is_sick_day() is True
|
|
assert sickdays.kept_lockers() == frozenset()
|
|
push.assert_called_once_with()
|
|
locker.close.assert_called_once_with()
|
|
assert panels == []
|
|
|
|
|
|
def test_the_pool_fixture_reaches_the_embedded_panel(tmp_path: Path) -> None:
|
|
"""``sickdays.gui._panel`` binds its own ``resolve_paths``/``sync_quietly``."""
|
|
assert sickdays.gui._panel.resolve_paths(None).log == (
|
|
sickdays.Paths.under(tmp_path / "sickdays").log
|
|
)
|
|
assert sickdays.gui._panel.sync_quietly() is False
|