Lays down the full test surface for the popout state machine ahead of
any transition logic. 62 collected tests across the four categories
from docs/POPOUT_REFACTOR_PLAN.md "Test plan":
1. Read-path queries (4 tests, all passing at commit 3 — these
exercise the parts of the skeleton that are already real:
compute_slider_display_ms, the terminal Closing guard, the
initial state defaults)
2. Per-state transition tests (~22 tests, all failing at commit 3
because the per-event handlers in state.py are stubs returning
[]. Each documents the expected new state and effects for one
specific (state, event) pair. These pass progressively as
commits 4-11 land.)
3. Race-fix invariant tests (6 tests — one for each of the six
structural fixes from the prior fix sweep: EOF race, double-
navigate, persistent viewport, F11 round-trip, seek pin,
pending mute replay. The EOF race test already passes because
dropping VideoEofReached in LoadingVideo is just "stub returns
[]", which is the right behavior for now. The others fail
until their transitions land.)
4. Illegal transition tests (17 parametrized cases — at commit 11
these become BOORU_VIEWER_STRICT_STATE-gated raises. At commits
3-10 they pass trivially because the stubs return [], which is
the release-mode behavior.)
All 62 tests are pure Python:
- Import only `booru_viewer.gui.popout.state` and `popout.viewport`
- Construct StateMachine() directly
- Use direct field mutation (`m.state = State.PLAYING_VIDEO`) for
setup, dispatch the event under test, assert the new state +
returned effects
- No QApplication, no mpv, no httpx, no filesystem outside tmp_path
- Sub-100ms total runtime (currently 0.31s including test discovery)
The forcing function: if state.py grows a PySide6/mpv/httpx import,
this test file fails to collect and the suite breaks. That's the
guardrail that keeps state.py pure as transitions land.
Test count breakdown (62 total):
- 4 trivially-passing (read-path queries + initial state)
- 22 transition tests (one per (state, event) pair)
- 6 invariant tests (mapped to the six race fixes)
- 17 illegal transition cases (parametrized over (state, event) pairs)
- 5 close-from-each-state cases (parametrized)
- 8 misc (state field persistence, window events)
Result at commit 3:
35 failed, 27 passed in 0.31s
The 27 passing are exactly the predicted set: trivial reads + the
illegal-transition pass-throughs (which work today because the stubs
return [] just like release-mode strict-state would). The 35 failing
are the transition handlers that need real implementations.
Phase A test suite (16 tests in tests/core/) still passes — this
commit only adds new tests, no existing test changed.
Test cases for state machine implementation (commits 4-11):
- Each failing test is its own commit acceptance criterion
- Commit N "passes" when the relevant subset of tests turns green
- Final state machine sweep (commit 11): all 62 tests pass