112 lines
3.7 KiB
Python
112 lines
3.7 KiB
Python
"""Einstiegspunkte (``python -m sandbox``, CLI) und Randfälle der Eingabeverarbeitung."""
|
|
|
|
import runpy
|
|
import sys
|
|
from dataclasses import replace
|
|
from pathlib import Path
|
|
|
|
import pygame
|
|
import pytest
|
|
|
|
from sandbox import main as main_module
|
|
from sandbox.config import APP, INTERACTION, Theme, UiConfig
|
|
from sandbox.main import App, FrameStats, main
|
|
from sandbox.ui.style import UiStyle, _load_font
|
|
from tests.app.driver import click, frames, key, move, post, press, release, screen_pos
|
|
|
|
|
|
def test_python_dash_m_starts_the_app(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
|
screenshot = tmp_path / "module.png"
|
|
monkeypatch.setattr(
|
|
sys, "argv", ["sandbox", "--screenshot", str(screenshot), "--seconds", "0.1"]
|
|
)
|
|
with pytest.raises(SystemExit) as exit_info:
|
|
runpy.run_module("sandbox", run_name="__main__")
|
|
assert exit_info.value.code == 0
|
|
assert screenshot.exists()
|
|
|
|
|
|
def test_main_without_options_runs_the_interactive_loop(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
calls: list[float | None] = []
|
|
monkeypatch.setattr(
|
|
main_module.App, "run", lambda self, max_seconds=None: calls.append(max_seconds)
|
|
)
|
|
assert main([]) == 0
|
|
assert calls == [None]
|
|
|
|
|
|
def test_main_loop_ends_when_the_window_is_closed(app: App) -> None:
|
|
post(pygame.QUIT)
|
|
app.run() # kehrt nach dem ersten Frame zurück
|
|
assert not app.running
|
|
|
|
|
|
def test_frame_stats_are_smoothed_and_ignore_empty_frames() -> None:
|
|
stats = FrameStats(smoothing=0.5)
|
|
stats.update(1 / 50, 0.002)
|
|
assert stats.fps == pytest.approx(50)
|
|
stats.update(1 / 100, 0.004)
|
|
assert stats.fps == pytest.approx(75)
|
|
stats.update(0.0, 0.0) # z. B. allererster Frame: keine Division durch 0
|
|
assert stats.fps == pytest.approx(75)
|
|
assert stats.physics_ms > 0
|
|
|
|
|
|
def test_clicks_outside_the_world_without_sidebar_are_ignored(app: App) -> None:
|
|
app.input.sidebar = None # InputHandler auch ohne Sidebar nutzbar
|
|
click(app, (10, 10)) # links neben der Welt
|
|
assert app.world.bodies == []
|
|
click(app, screen_pos(app, 400, 300))
|
|
assert len(app.world.bodies) == 1
|
|
|
|
|
|
def test_middle_click_and_delete_without_target_do_nothing(app: App) -> None:
|
|
click(app, screen_pos(app, 400, 300), pygame.BUTTON_MIDDLE)
|
|
assert app.world.bodies == []
|
|
move(app, screen_pos(app, 600, 300))
|
|
key(app, "delete") # nichts unter der Maus
|
|
click(app, screen_pos(app, 600, 300), pygame.BUTTON_RIGHT)
|
|
assert app.world.bodies == []
|
|
|
|
|
|
def test_spawn_drag_is_ignored_when_the_world_is_full(app: App) -> None:
|
|
app.close()
|
|
full = App(replace(APP, interaction=replace(INTERACTION, max_bodies=1)))
|
|
try:
|
|
click(full, screen_pos(full, 400, 300))
|
|
tile = full.sidebar.widget("spawn_tile").rect
|
|
press(full, tile.center)
|
|
move(full, screen_pos(full, 200, 200))
|
|
assert len(full.world.bodies) == 1
|
|
assert full.drag.body is None
|
|
assert full.input.spawn_preview() is None # voll: keine Vorschau mehr
|
|
release(full, screen_pos(full, 200, 200))
|
|
frames(full)
|
|
finally:
|
|
full.close()
|
|
|
|
|
|
def test_unknown_widget_names_raise(app: App) -> None:
|
|
with pytest.raises(KeyError):
|
|
app.sidebar.widget("gibt-es-nicht")
|
|
|
|
|
|
def test_missing_system_fonts_fall_back_to_the_builtin_font() -> None:
|
|
pygame.font.init()
|
|
try:
|
|
font = _load_font(("keine-solche-schrift-xyz",), 14, bold=True)
|
|
assert font.bold
|
|
assert font.size("Test")[0] > 0
|
|
finally:
|
|
pygame.font.quit()
|
|
|
|
|
|
def test_style_initializes_the_font_module_itself() -> None:
|
|
pygame.font.quit()
|
|
style = UiStyle(Theme(), UiConfig())
|
|
try:
|
|
assert pygame.font.get_init()
|
|
assert style.render_text("x", style.label_font, (0, 0, 0)).get_width() > 0
|
|
finally:
|
|
pygame.font.quit()
|