Remove tests/ from .gitignore and track the existing test suite:
tests/core/test_db.py — DB schema, migration, CRUD
tests/core/test_cache.py — cache helpers
tests/core/test_config.py — config/path helpers
tests/core/test_concurrency.py — app loop accessor
tests/core/api/test_base.py — Post dataclass, BooruClient
tests/gui/popout/test_state.py — 57 state machine tests
All pure Python, no secrets, no external deps. Uses temp DBs and
synthetic data. Run with: pytest tests/
Removes the tests/ folder from git tracking and adds it to .gitignore.
The 81 tests (16 Phase A core + 65 popout state machine) stay on
disk as local-only working notes, the same way docs/ and project.md
are gitignored. Running them is `pytest tests/` from the project
root inside .venv as before — nothing about the tests themselves
changed, just whether they're version-controlled.
Reverts the related additions in pyproject.toml and README.md from
commit bf14466 (Phase A baseline) so the public surface doesn't
reference a tests/ folder that no longer ships:
- pyproject.toml: drops [project.optional-dependencies] test extra
and [tool.pytest.ini_options]. pytest + pytest-asyncio are still
installed in the local .venv via the previous pip install -e ".[test]"
so the suite keeps running locally; new clones won't get them
automatically.
- README.md: drops the "Run tests:" section from the Linux install
block. The README's install instructions return to their pre-
Phase-A state.
- .gitignore: adds `tests/` alongside the existing `docs/` and
`project.md` lines (the same convention used for the refactor
inventory / plan / notes / final report docs).
The 12 test files removed from tracking (`git rm -r --cached`):
tests/__init__.py
tests/conftest.py
tests/core/__init__.py
tests/core/test_cache.py
tests/core/test_concurrency.py
tests/core/test_config.py
tests/core/test_db.py
tests/core/api/__init__.py
tests/core/api/test_base.py
tests/gui/__init__.py
tests/gui/popout/__init__.py
tests/gui/popout/test_state.py
Verification:
- tests/ still exists on disk
- `pytest tests/` still runs and passes 81 / 81 in 0.11s
- `git ls-files tests/` returns nothing
- `git status` is clean
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