import pytest import dpg_gauges as dpgg from dpg_gauges.animation import advance_animation, interpolate, start_animation from dpg_gauges.exceptions import GaugeConfigError, GaugeNotFoundError from dpg_gauges.gauges import DigitalGaugeConfig from dpg_gauges.sizing import ( SizeState, effective_size, requested_size, square_gauge_rect, stretch_gauge_rect, update_measured_size, ) from dpg_gauges.smoothing import SmoothingState, advance_smoothing, smooth_value from dpg_gauges.state import DirtyFlags, clear_registry, get_gauge_state, register_gauge from dpg_gauges.types import AnimationConfig, SmoothingConfig @pytest.fixture(autouse=True) def clean_registry() -> None: clear_registry() dpgg.configure() def test_registry_register_get_and_delete() -> None: with dpgg.digital_gauge(tag="speed", value=42, min_value=0, max_value=100) as config: assert config.tag == "speed" assert dpgg.get_value("speed") == 42 state = get_gauge_state("speed") assert state.gauge_type == "digital" assert state.clamped_value == 42 assert state.displayed_value == 42 with pytest.raises(GaugeConfigError): register_gauge("digital", DigitalGaugeConfig(tag="speed")) dpgg.delete_gauge("speed") with pytest.raises(GaugeNotFoundError): dpgg.get_value("speed") def test_value_clamped_displayed_and_dirty_flags_are_separate() -> None: dpgg.digital_gauge(tag="rpm", value=10, min_value=0, max_value=100, animation=True) state = get_gauge_state("rpm") state.dirty = DirtyFlags.NONE dpgg.set_value("rpm", 150) assert dpgg.get_value("rpm") == 150 assert state.clamped_value == 100 assert state.displayed_value == 10 assert state.animation_state.active assert state.dirty & DirtyFlags.VALUE assert state.dirty & DirtyFlags.DYNAMIC assert state.dirty & DirtyFlags.TEXT def test_update_gauge_marks_static_text_size_and_style_dirty() -> None: dpgg.digital_gauge(tag="temp", value=20, min_value=0, max_value=100) state = get_gauge_state("temp") state.dirty = DirtyFlags.NONE dpgg.update_gauge( "temp", label="Temperature", unit="C", min_value=-40, max_value=140, width=200, style=state.config.style, ) assert state.config.label == "Temperature" assert state.config.unit == "C" assert state.config.min_value == -40 assert state.dirty & DirtyFlags.STATIC assert state.dirty & DirtyFlags.DYNAMIC assert state.dirty & DirtyFlags.TEXT assert state.dirty & DirtyFlags.SIZE assert state.dirty & DirtyFlags.STYLE def test_threshold_marker_and_show_runtime_updates() -> None: dpgg.digital_gauge(tag="fuel", min_value=0, max_value=100) threshold = dpgg.ThresholdBand(0, 15, (255, 0, 0)) marker = dpgg.GaugeMarker(50, (0, 255, 0)) dpgg.set_thresholds("fuel", [threshold]) dpgg.set_markers("fuel", [marker]) dpgg.set_show("fuel", False) state = get_gauge_state("fuel") assert state.config.thresholds == (threshold,) assert state.config.markers == (marker,) assert state.config.show is False assert state.dirty & DirtyFlags.STATIC assert state.dirty & DirtyFlags.SIZE def test_non_finite_values_do_not_crash_state_helpers() -> None: dpgg.digital_gauge(tag="boost", min_value=0, max_value=30) dpgg.set_value("boost", float("nan")) state = get_gauge_state("boost") assert state.target_value != state.target_value assert state.clamped_value is None assert state.displayed_value is None assert state.invalid_value is True def test_sizing_preserves_last_nonzero_size_and_rects() -> None: size = SizeState(requested=requested_size(100, 80)) assert update_measured_size(size, 320, 180) is True assert effective_size(size) == (320, 180) update_measured_size(size, 0, 0) assert effective_size(size) == (320, 180) assert square_gauge_rect(320, 180).width == 180 assert square_gauge_rect(320, 180).x == 70 assert stretch_gauge_rect(320, 180).width == 320 assert stretch_gauge_rect(320, 180).height == 180 def test_animation_interpolation_and_completion() -> None: config = AnimationConfig(enabled=True, duration=1.0) state = start_animation(0, 100, config, now=10.0) assert interpolate(0, 100, 0.25) == 25 value, active = advance_animation(state, config, now=10.5) assert value == 50 assert active is True value, active = advance_animation(state, config, now=11.0) assert value == 100 assert active is False def test_smoothing_progression() -> None: config = SmoothingConfig(enabled=True, alpha=0.5, max_step=10) assert smooth_value(None, 100, config) == (100, False) state = SmoothingState(value=0) value, active = advance_smoothing(state, 100, config) assert value == 10 assert active is True def test_debug_state_reports_model_fields() -> None: dpgg.digital_gauge(tag="debug", value=5, width=120, height=30) debug = dpgg.get_gauge_debug_state("debug") assert debug["gauge_type"] == "digital" assert debug["target_value"] == 5 assert debug["clamped_value"] == 5 assert debug["measured_size"] == (120, 30) assert "VALUE" in debug["dirty_names"]