Files
openrun/tests/unit/test_plots.py

64 lines
2.1 KiB
Python
Raw Permalink Normal View History

2026-05-19 08:34:22 -04:00
"""Smoke tests for `openrun.plots`.
We deliberately don't visual-regress matplotlib output — see ROADMAP test
conventions. Each test asserts only structural properties: bar count, axis
labels, "no data" sentinel behavior.
"""
from __future__ import annotations
import matplotlib
matplotlib.use("Agg") # must be set before pyplot import
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from openrun.plots import plot_fit_decoupling
def _synthetic_records(duration_s: int = 1800, decel_at: int = 900) -> pd.DataFrame:
"""A FIT-records-like frame: constant HR, but speed drops halfway through.
With 2 segments, the second-half efficiency must be lower positive decoupling.
"""
elapsed = np.arange(duration_s, dtype=float)
speed = np.where(elapsed < decel_at, 3.0, 2.5)
hr = np.full(duration_s, 150.0)
return pd.DataFrame({"elapsed_s": elapsed, "speed_mps": speed, "heart_rate": hr})
def test_plot_fit_decoupling_returns_axes_with_n_bars() -> None:
records = _synthetic_records()
ax = plot_fit_decoupling(records, segments=4)
bars = [p for p in ax.patches if hasattr(p, "get_height")]
assert len(bars) == 4
assert ax.get_ylabel() == "decoupling (%)"
plt.close(ax.figure)
def test_plot_fit_decoupling_segments_default_is_two() -> None:
records = _synthetic_records()
ax = plot_fit_decoupling(records)
bars = [p for p in ax.patches if hasattr(p, "get_height")]
assert len(bars) == 2
plt.close(ax.figure)
def test_plot_fit_decoupling_handles_empty_records() -> None:
"""Empty records frame → 'no usable records' sentinel, not a crash."""
empty = pd.DataFrame(columns=["elapsed_s", "speed_mps", "heart_rate"])
ax = plot_fit_decoupling(empty)
texts = [t.get_text() for t in ax.texts]
assert any("no usable records" in t for t in texts)
plt.close(ax.figure)
def test_plot_fit_decoupling_respects_caller_axes() -> None:
"""When the caller passes ax=, no new figure is created."""
fig, ax = plt.subplots()
out = plot_fit_decoupling(_synthetic_records(), ax=ax)
assert out is ax
plt.close(fig)