202 lines
5.8 KiB
Python
202 lines
5.8 KiB
Python
"""
|
|
tests/test_analysis_charts.py
|
|
------------------------------
|
|
Tests for the pure chart-drawing functions in ui/analysis.py.
|
|
Uses matplotlib's Agg (non-interactive) backend so no display is required.
|
|
Each test verifies that the function populates the axes correctly
|
|
and doesn't raise on empty or populated data.
|
|
"""
|
|
|
|
import matplotlib
|
|
matplotlib.use("Agg")
|
|
from matplotlib.figure import Figure
|
|
|
|
import pytest
|
|
from db.models import get_game_by_name, insert_draw
|
|
from ui.analysis import (
|
|
_empty,
|
|
_draw_frequency,
|
|
_draw_heatmap,
|
|
_draw_gap,
|
|
_draw_pairs,
|
|
_draw_odd_even,
|
|
_draw_sum_range,
|
|
_draw_deltas,
|
|
)
|
|
|
|
DRAWS = [
|
|
("2024-01-01", [1, 13, 36, 61, 69]),
|
|
("2024-01-03", [1, 2, 13, 45, 69]),
|
|
("2024-01-05", [2, 13, 22, 36, 55]),
|
|
]
|
|
|
|
|
|
def _ax():
|
|
"""Return a fresh Axes on a headless figure."""
|
|
fig = Figure()
|
|
return fig.add_subplot(111)
|
|
|
|
|
|
@pytest.fixture
|
|
def pb(tmp_db):
|
|
game = get_game_by_name("Powerball")
|
|
for date, nums in DRAWS:
|
|
insert_draw(game["id"], date, nums, bonus=7, source="test")
|
|
return game
|
|
|
|
|
|
# ── _empty ────────────────────────────────────────────────────────────────────
|
|
|
|
def test_empty_disables_axis(tmp_db):
|
|
ax = _ax()
|
|
_empty(ax, "test message")
|
|
assert not ax.get_visible() or not ax.axison
|
|
|
|
|
|
# ── _draw_frequency ───────────────────────────────────────────────────────────
|
|
|
|
def test_draw_frequency_creates_bars(pb):
|
|
ax = _ax()
|
|
_draw_frequency(ax, pb["id"])
|
|
assert len(ax.patches) > 0
|
|
|
|
|
|
def test_draw_frequency_empty_db_no_error(tmp_db):
|
|
pb = get_game_by_name("Powerball")
|
|
ax = _ax()
|
|
_draw_frequency(ax, pb["id"]) # should show _empty placeholder, not raise
|
|
|
|
|
|
def test_draw_frequency_last_n(pb):
|
|
ax = _ax()
|
|
_draw_frequency(ax, pb["id"], last_n=1)
|
|
assert len(ax.patches) > 0
|
|
|
|
|
|
def test_draw_frequency_title_includes_last_n(pb):
|
|
ax = _ax()
|
|
_draw_frequency(ax, pb["id"], last_n=50)
|
|
assert "50" in ax.get_title()
|
|
|
|
|
|
# ── _draw_heatmap ─────────────────────────────────────────────────────────────
|
|
|
|
def test_draw_heatmap_creates_image(pb):
|
|
ax = _ax()
|
|
_draw_heatmap(ax, pb["id"])
|
|
assert len(ax.images) > 0
|
|
|
|
|
|
def test_draw_heatmap_empty_db_no_error(tmp_db):
|
|
pb = get_game_by_name("Powerball")
|
|
ax = _ax()
|
|
_draw_heatmap(ax, pb["id"])
|
|
|
|
|
|
# ── _draw_gap ─────────────────────────────────────────────────────────────────
|
|
|
|
def test_draw_gap_creates_bars(pb):
|
|
ax = _ax()
|
|
_draw_gap(ax, pb["id"])
|
|
assert len(ax.patches) > 0
|
|
|
|
|
|
def test_draw_gap_empty_db_no_error(tmp_db):
|
|
pb = get_game_by_name("Powerball")
|
|
ax = _ax()
|
|
_draw_gap(ax, pb["id"])
|
|
|
|
|
|
# ── _draw_pairs ───────────────────────────────────────────────────────────────
|
|
|
|
def test_draw_pairs_creates_bars(pb):
|
|
ax = _ax()
|
|
_draw_pairs(ax, pb["id"])
|
|
assert len(ax.patches) > 0
|
|
|
|
|
|
def test_draw_pairs_respects_top_n(pb):
|
|
ax = _ax()
|
|
_draw_pairs(ax, pb["id"], top_n=5)
|
|
assert len(ax.patches) <= 5
|
|
|
|
|
|
def test_draw_pairs_empty_db_no_error(tmp_db):
|
|
pb = get_game_by_name("Powerball")
|
|
ax = _ax()
|
|
_draw_pairs(ax, pb["id"])
|
|
|
|
|
|
def test_draw_pairs_title_set(pb):
|
|
ax = _ax()
|
|
_draw_pairs(ax, pb["id"], top_n=10)
|
|
assert "Pair" in ax.get_title() or "pair" in ax.get_title().lower()
|
|
|
|
|
|
# ── _draw_odd_even ────────────────────────────────────────────────────────────
|
|
|
|
def test_draw_odd_even_creates_bars(pb):
|
|
ax = _ax()
|
|
_draw_odd_even(ax, pb["id"])
|
|
assert len(ax.patches) > 0
|
|
|
|
|
|
def test_draw_odd_even_empty_db_no_error(tmp_db):
|
|
pb = get_game_by_name("Powerball")
|
|
ax = _ax()
|
|
_draw_odd_even(ax, pb["id"])
|
|
|
|
|
|
def test_draw_odd_even_title_set(pb):
|
|
ax = _ax()
|
|
_draw_odd_even(ax, pb["id"])
|
|
assert "Odd" in ax.get_title() or "Even" in ax.get_title()
|
|
|
|
|
|
# ── _draw_sum_range ───────────────────────────────────────────────────────────
|
|
|
|
def test_draw_sum_range_creates_patches(pb):
|
|
ax = _ax()
|
|
_draw_sum_range(ax, pb["id"])
|
|
assert len(ax.patches) > 0
|
|
|
|
|
|
def test_draw_sum_range_empty_db_no_error(tmp_db):
|
|
pb = get_game_by_name("Powerball")
|
|
ax = _ax()
|
|
_draw_sum_range(ax, pb["id"])
|
|
|
|
|
|
def test_draw_sum_range_title_set(pb):
|
|
ax = _ax()
|
|
_draw_sum_range(ax, pb["id"])
|
|
assert "Sum" in ax.get_title()
|
|
|
|
|
|
# ── _draw_deltas ──────────────────────────────────────────────────────────────
|
|
|
|
def test_draw_deltas_creates_bars(pb):
|
|
ax = _ax()
|
|
_draw_deltas(ax, pb["id"])
|
|
assert len(ax.patches) > 0
|
|
|
|
|
|
def test_draw_deltas_empty_db_no_error(tmp_db):
|
|
pb = get_game_by_name("Powerball")
|
|
ax = _ax()
|
|
_draw_deltas(ax, pb["id"])
|
|
|
|
|
|
def test_draw_deltas_title_set(pb):
|
|
ax = _ax()
|
|
_draw_deltas(ax, pb["id"])
|
|
assert "Delta" in ax.get_title()
|
|
|
|
|
|
def test_draw_deltas_x_axis_are_positive(pb):
|
|
ax = _ax()
|
|
_draw_deltas(ax, pb["id"])
|
|
# All delta values between consecutive sorted numbers are positive
|
|
x_vals = [p.get_x() for p in ax.patches]
|
|
assert all(x >= 0 for x in x_vals)
|