Files
018-turtle/tests/test_cli.py
Tirsvad b5483a7373 Add the random-walk and spirograph challenges to the command line
turtle-challenges random-walk and turtle-challenges spirograph, the latter with
--gap for the degrees between two circles.

Task: MIL-004#4
2026-10-07 23:59:15 +08:00

116 lines
3.2 KiB
Python

import pytest
from tests.fakes import FakePen, FakeWindow
from turtle_challenges import cli
from turtle_challenges.constants import (
CHALLENGE_NAMES,
DASH_COUNT,
SPIROGRAPH_GAP_DEGREES,
SQUARE_SIDES,
)
from turtle_challenges.pen import Pen, Window
from turtle_challenges.window import TurtleUnavailableError
@pytest.fixture
def fake_pen_and_window(monkeypatch: pytest.MonkeyPatch) -> tuple[FakePen, FakeWindow]:
"""Replace the turtle window and pen of the command line with fakes."""
pen = FakePen()
window = FakeWindow()
def create_fake_window() -> Window:
return window
def create_fake_pen() -> Pen:
return pen
monkeypatch.setattr(cli, "create_window", create_fake_window)
monkeypatch.setattr(cli, "create_pen", create_fake_pen)
return pen, window
def test_every_challenge_name_has_a_challenge() -> None:
challenges = cli.build_challenges(FakePen(), SPIROGRAPH_GAP_DEGREES)
assert set(challenges) == set(CHALLENGE_NAMES)
@pytest.mark.parametrize("name", CHALLENGE_NAMES)
def test_parser_accepts_every_challenge_name(name: str) -> None:
args = cli.build_parser().parse_args([name])
assert args.challenge == name
assert args.gap == SPIROGRAPH_GAP_DEGREES
def test_parser_reads_the_spirograph_gap() -> None:
args = cli.build_parser().parse_args(["spirograph", "--gap", "7.5"])
assert args.gap == 7.5
def test_parser_rejects_an_unknown_challenge(
capsys: pytest.CaptureFixture[str],
) -> None:
with pytest.raises(SystemExit) as exit_info:
cli.build_parser().parse_args(["triangle"])
assert exit_info.value.code == 2
assert "invalid choice" in capsys.readouterr().err
def test_help_lists_every_challenge(capsys: pytest.CaptureFixture[str]) -> None:
with pytest.raises(SystemExit) as exit_info:
cli.build_parser().parse_args(["--help"])
assert exit_info.value.code == 0
output = capsys.readouterr().out
assert all(name in output for name in CHALLENGE_NAMES)
def test_main_runs_the_square_and_waits_for_a_click(
fake_pen_and_window: tuple[FakePen, FakeWindow],
) -> None:
pen, window = fake_pen_and_window
status = cli.main(["square"])
assert status == 0
assert len(pen.args_of("forward")) == SQUARE_SIDES
assert window.waited_for_click
def test_main_runs_the_dashed_line(
fake_pen_and_window: tuple[FakePen, FakeWindow],
) -> None:
pen, _ = fake_pen_and_window
cli.main(["dashed-line"])
assert len(pen.args_of("forward")) == 2 * DASH_COUNT
def test_main_passes_the_gap_to_the_spirograph(
fake_pen_and_window: tuple[FakePen, FakeWindow],
) -> None:
pen, _ = fake_pen_and_window
cli.main(["spirograph", "--gap", "90"])
assert len(pen.args_of("circle")) == 4
def test_main_reports_a_missing_turtle_and_exits_with_status_1(
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
def fail() -> Window:
raise TurtleUnavailableError("no Tk here")
monkeypatch.setattr(cli, "create_window", fail)
with pytest.raises(SystemExit) as exit_info:
cli.main(["square"])
assert exit_info.value.code == 1
assert "no Tk here" in capsys.readouterr().err