""" tests/test_widgets.py --------------------- Tests for ui/widgets.py — ball_color() is pure and testable without a display. BallsBar creation tests require Tkinter and are skipped when unavailable. """ import pytest from ui.widgets import ball_color, BallsBar def _has_display(): try: import tkinter as tk r = tk.Tk(); r.withdraw(); r.destroy() return True except Exception: return False # ── ball_color — pure function, no display needed ───────────────────────────── def test_ball_color_returns_two_hex_strings(): bg, fg = ball_color(7) assert bg.startswith("#") and len(bg) == 7 assert fg.startswith("#") and len(fg) == 7 def test_ball_color_bonus_is_red(): bg, _ = ball_color(7, is_bonus=True) assert bg == "#e74c3c" def test_ball_color_bonus_same_regardless_of_number(): assert ball_color(1, is_bonus=True) == ball_color(26, is_bonus=True) assert ball_color(99, is_bonus=True) == ball_color(5, is_bonus=True) def test_ball_color_different_ranges_have_different_colors(): bgs = {ball_color(n)[0] for n in [1, 15, 25, 35, 45, 55, 65, 75]} assert len(bgs) >= 5 # at least 5 distinct background colors def test_ball_color_boundary_values(): # Each range boundary should resolve without error for n in [1, 9, 10, 19, 20, 29, 30, 39, 40, 49, 50, 59, 60, 69, 70, 99]: bg, fg = ball_color(n) assert bg.startswith("#") assert fg.startswith("#") def test_ball_color_out_of_defined_range(): # Number > 99 falls back gracefully bg, fg = ball_color(200) assert bg.startswith("#") assert fg.startswith("#") def test_ball_color_is_deterministic(): for n in [1, 7, 14, 22, 35, 49, 69]: assert ball_color(n) == ball_color(n) def test_ball_color_non_bonus_not_red(): # Main balls in defined ranges should not be the bonus red for n in range(1, 70): bg, _ = ball_color(n, is_bonus=False) assert bg != "#e74c3c" or n >= 60 # only 60-69 range is red # ── BallsBar — needs display ────────────────────────────────────────────────── @pytest.mark.skipif(not _has_display(), reason="no display available") def test_ballsbar_creates_widget(): import tkinter as tk try: root = tk.Tk(); root.withdraw() except Exception: pytest.skip("Tkinter init failed") try: bar = BallsBar(root, numbers=[1, 7, 14, 22, 35], bonus=3) assert bar.winfo_exists() finally: root.destroy() @pytest.mark.skipif(not _has_display(), reason="no display available") def test_ballsbar_no_bonus(): import tkinter as tk try: root = tk.Tk(); root.withdraw() except Exception: pytest.skip("Tkinter init failed") try: bar = BallsBar(root, numbers=[5, 12, 30, 44, 66]) assert bar.winfo_exists() assert int(bar.cget("width")) > 0 finally: root.destroy() @pytest.mark.skipif(not _has_display(), reason="no display available") def test_ballsbar_with_highlights(): import tkinter as tk try: root = tk.Tk(); root.withdraw() except Exception: pytest.skip("Tkinter init failed") try: hi = {7: ("#27ae60", "#ffffff"), 14: ("#cccccc", "#888888")} bar = BallsBar(root, numbers=[1, 7, 14, 22, 35], highlights=hi) assert bar.winfo_exists() finally: root.destroy() @pytest.mark.skipif(not _has_display(), reason="no display available") def test_ballsbar_larger_radius(): import tkinter as tk try: root = tk.Tk(); root.withdraw() except Exception: pytest.skip("Tkinter init failed") try: bar_small = BallsBar(root, numbers=[1, 2, 3], radius=11) bar_normal = BallsBar(root, numbers=[1, 2, 3], radius=14) w_small = int(bar_small.cget("width")) w_normal = int(bar_normal.cget("width")) assert w_normal > w_small finally: root.destroy()