Files
pygame-physics-opus5.5/tests/app/test_entry_points_and_edge_cases.py
T
2026-09-24 15:45:31 +02:00

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()