mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 23:07:03 +01:00
The last six over-cap files split into eleven new modules, in two passes since the first cut left five still over. python_pkg has no violations left at all, down from 50 when this effort started. Each split carries the whole preamble including the module's pytest fixtures - started, toolkit and session are taken as parameters and resolve lexically, so a hand-written import block would have missed them. ruff then pruned 82 unused imports. Pass count is 277 before and after, checked after both passes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
218 lines
6.0 KiB
Python
218 lines
6.0 KiB
Python
"""Tests for the review window's playback and advance behaviour."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import queue
|
|
from typing import TYPE_CHECKING, cast
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from python_pkg.wsg_grabber import review, ui
|
|
from python_pkg.wsg_grabber.models import (
|
|
ReviewCommand,
|
|
ReviewedItem,
|
|
ReviewItem,
|
|
Verdict,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Iterator
|
|
from pathlib import Path
|
|
|
|
from python_pkg.wsg_grabber.models import DownloadEvent
|
|
from python_pkg.wsg_grabber.review import SessionState
|
|
|
|
|
|
@pytest.fixture
|
|
def toolkit() -> Iterator[MagicMock]:
|
|
"""Replace tkinter in the ui module's namespace.
|
|
|
|
Yields:
|
|
MagicMock: The stand-in toolkit.
|
|
"""
|
|
fake = MagicMock()
|
|
# a fresh mock per widget, else status and filename are the same object
|
|
fake.Frame.side_effect = lambda *_a, **_k: MagicMock()
|
|
fake.Label.side_effect = lambda *_a, **_k: MagicMock()
|
|
fake.Button.side_effect = lambda *_a, **_k: MagicMock()
|
|
fake.BOTH = "both"
|
|
fake.X = "x"
|
|
fake.LEFT = "left"
|
|
fake.RIGHT = "right"
|
|
fake.BOTTOM = "bottom"
|
|
with patch.object(ui, "tk", fake):
|
|
yield fake
|
|
|
|
|
|
def _mock(widget: object) -> MagicMock:
|
|
"""View a mocked widget as the MagicMock it really is.
|
|
|
|
The Widgets dataclass is typed with real Tk classes, so mypy will not let
|
|
the tests reach ``call_args`` without this.
|
|
|
|
Args:
|
|
widget: The stand-in widget.
|
|
|
|
Returns:
|
|
MagicMock: The same object, typed for assertions.
|
|
"""
|
|
return cast("MagicMock", widget)
|
|
|
|
|
|
def _item(tmp_path: Path, name: str = "a") -> ReviewItem:
|
|
"""Build a ReviewItem backed by a real file.
|
|
|
|
Args:
|
|
tmp_path: Per-test temporary directory.
|
|
name: Distinguishes items.
|
|
|
|
Returns:
|
|
ReviewItem: Fixture value.
|
|
"""
|
|
path = tmp_path / f"{name}.webm"
|
|
path.write_bytes(b"video")
|
|
return ReviewItem(
|
|
md5=f"{name}-md5",
|
|
path=path,
|
|
orig_name=f"{name}.webm",
|
|
fsize=5,
|
|
width=4,
|
|
height=3,
|
|
)
|
|
|
|
|
|
def _callbacks() -> tuple[ui.Callbacks, MagicMock]:
|
|
"""Build callbacks that record what the window asked for.
|
|
|
|
Returns:
|
|
tuple: The callbacks and the recorder behind them.
|
|
"""
|
|
recorder = MagicMock()
|
|
_undone: dict[str, ReviewedItem | None] = {"item": None}
|
|
|
|
def commit(state: SessionState, choice: Verdict) -> SessionState:
|
|
recorder.commit(choice)
|
|
if state.current is not None:
|
|
_undone["item"] = ReviewedItem(
|
|
item=state.current,
|
|
choice=choice,
|
|
reviewed_name=state.current.path.name,
|
|
)
|
|
return review.on_verdict(state, choice)
|
|
|
|
def missing(state: SessionState) -> SessionState:
|
|
recorder.missing()
|
|
return review.on_missing_locally(state)
|
|
|
|
def undo(state: SessionState) -> SessionState:
|
|
recorder.undo()
|
|
action = _undone["item"]
|
|
if action is None or not review.can_undo(state):
|
|
return state
|
|
return review.on_undo(state, action)
|
|
|
|
return (
|
|
ui.Callbacks(
|
|
commit=commit,
|
|
missing=missing,
|
|
undo=undo,
|
|
shutdown=recorder.shutdown,
|
|
),
|
|
recorder,
|
|
)
|
|
|
|
|
|
def _window(
|
|
state: SessionState,
|
|
events: queue.SimpleQueue[DownloadEvent] | None = None,
|
|
) -> tuple[ui.ReviewWindow, MagicMock]:
|
|
"""Build a window over a mocked toolkit.
|
|
|
|
Args:
|
|
state: Starting state.
|
|
events: Optional event queue.
|
|
|
|
Returns:
|
|
tuple: The window and the callback recorder.
|
|
"""
|
|
callbacks, recorder = _callbacks()
|
|
window = ui.ReviewWindow(state, events or queue.SimpleQueue(), callbacks)
|
|
return window, recorder
|
|
|
|
|
|
def test_apply_plays_and_labels(toolkit: MagicMock, tmp_path: Path) -> None:
|
|
window, _ = _window(review.initial_state(0, []))
|
|
player = MagicMock()
|
|
window.attach_player(player)
|
|
path = tmp_path / "x.webm"
|
|
path.write_bytes(b"v")
|
|
|
|
window.apply(
|
|
ReviewCommand(
|
|
play=path,
|
|
stop=False,
|
|
status="status text",
|
|
title="title text",
|
|
filename="file text",
|
|
verdicts_enabled=True,
|
|
undo_enabled=False,
|
|
quit_app=False,
|
|
),
|
|
)
|
|
player.play.assert_called_once_with(path)
|
|
_mock(window.widgets.status).configure.assert_called_with(text="status text")
|
|
_mock(window.widgets.filename).configure.assert_called_with(text="file text")
|
|
_mock(window.widgets.root).title.assert_called_with("title text")
|
|
_mock(window.widgets.keep).configure.assert_called_with(state="normal")
|
|
|
|
|
|
def test_the_same_video_is_only_loaded_once(
|
|
toolkit: MagicMock,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
"""refresh() runs every tick; re-sending loadfile restarts the video."""
|
|
window, _ = _window(review.initial_state(1, [_item(tmp_path)]))
|
|
player = MagicMock()
|
|
window.attach_player(player)
|
|
assert player.play.call_count == 1
|
|
|
|
for _ in range(10):
|
|
window.tick()
|
|
assert player.play.call_count == 1
|
|
|
|
|
|
def test_advancing_loads_the_next_video(toolkit: MagicMock, tmp_path: Path) -> None:
|
|
state = review.initial_state(2, [_item(tmp_path, "a"), _item(tmp_path, "b")])
|
|
window, _ = _window(state)
|
|
player = MagicMock()
|
|
window.attach_player(player)
|
|
window.on_keep()
|
|
assert player.play.call_count == 2
|
|
assert player.play.call_args.args[0].name == "b.webm"
|
|
|
|
|
|
def test_stopping_lets_the_same_video_load_again(
|
|
toolkit: MagicMock,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
item = _item(tmp_path)
|
|
window, _ = _window(review.initial_state(1, [item]))
|
|
player = MagicMock()
|
|
window.attach_player(player)
|
|
window.apply(
|
|
ReviewCommand(
|
|
play=None,
|
|
stop=True,
|
|
status="s",
|
|
title="t",
|
|
filename="f",
|
|
verdicts_enabled=False,
|
|
undo_enabled=False,
|
|
quit_app=False,
|
|
),
|
|
)
|
|
player.stop.assert_called_once()
|
|
window.refresh()
|
|
assert player.play.call_count == 2
|