Spaces:
Sleeping
Sleeping
| import unittest | |
| from streamlit.testing.v1 import AppTest | |
| import app | |
| class RunRateLabAnalyticsTests(unittest.TestCase): | |
| def test_probabilities_sum_to_approximately_100(self): | |
| probabilities, metrics = app.simulate_outcome_distribution( | |
| "Length Ball", | |
| timing_error_ms=0, | |
| power=70, | |
| required_rate=8.5, | |
| samples=5000, | |
| ) | |
| self.assertAlmostEqual(probabilities["Probability"].sum(), 100.0, places=6) | |
| self.assertGreater(metrics["expected_runs"], 0) | |
| self.assertEqual(metrics["shot_intent"], "Drive") | |
| self.assertEqual(metrics["aim_lane"], "Straight") | |
| def test_perfect_timing_beats_large_timing_error(self): | |
| pitch = app.get_pitch("Length Ball") | |
| perfect_contact = app.calculate_contact(0, 70, pitch) | |
| late_contact = app.calculate_contact(180, 70, pitch) | |
| self.assertGreater(perfect_contact, late_contact) | |
| def test_required_rate_increases_wicket_risk(self): | |
| pitch = app.get_pitch("Cutter") | |
| contact = app.calculate_contact(20, 85, pitch) | |
| normal_risk = app.calculate_wicket_risk(contact, 85, 20, 8.0, pitch) | |
| pressure_risk = app.calculate_wicket_risk(contact, 85, 20, 12.0, pitch) | |
| self.assertGreater(pressure_risk, normal_risk) | |
| def test_overcharged_power_increases_wicket_risk(self): | |
| pitch = app.get_pitch("Bouncer") | |
| contact = app.calculate_contact(0, 95, pitch) | |
| normal_risk = app.calculate_wicket_risk(contact, 80, 0, 8.0, pitch) | |
| overhit_risk = app.calculate_wicket_risk(contact, 95, 0, 8.0, pitch) | |
| self.assertGreater(overhit_risk, normal_risk) | |
| def test_loft_has_higher_upside_and_risk_than_defend(self): | |
| pitch = app.get_pitch("Half Volley") | |
| defend_contact = app.calculate_contact(0, 88, pitch, "Defend", "Straight") | |
| defend_risk = app.calculate_wicket_risk(defend_contact, 88, 0, 8.0, pitch, "Defend", "Straight") | |
| defend_xruns = app.calculate_expected_runs(defend_contact, 88, defend_risk, "Defend", "Straight") | |
| loft_contact = app.calculate_contact(0, 88, pitch, "Loft", "Straight") | |
| loft_risk = app.calculate_wicket_risk(loft_contact, 88, 0, 8.0, pitch, "Loft", "Straight") | |
| loft_xruns = app.calculate_expected_runs(loft_contact, 88, loft_risk, "Loft", "Straight") | |
| self.assertGreater(loft_risk, defend_risk) | |
| self.assertGreater(loft_xruns, defend_xruns) | |
| def test_correct_shot_delivery_matchup_improves_contact(self): | |
| pitch = app.get_pitch("Bouncer") | |
| drive_contact = app.calculate_contact(0, 76, pitch, "Drive", "Straight") | |
| pull_contact = app.calculate_contact(0, 76, pitch, "Cut / Pull", "Straight") | |
| self.assertGreater(pull_contact, drive_contact) | |
| def test_decision_quality_thresholds(self): | |
| self.assertEqual(app.classify_decision_quality(4.5, 0.12), "Excellent Process") | |
| self.assertEqual(app.classify_decision_quality(3.0, 0.20), "Good Process") | |
| self.assertEqual(app.classify_decision_quality(4.0, 0.35), "Risky But Rational") | |
| self.assertEqual(app.classify_decision_quality(1.2, 0.18), "Low-Value Shot") | |
| self.assertEqual(app.classify_decision_quality(1.9, 0.35), "Poor Risk Tradeoff") | |
| def test_luck_classification(self): | |
| self.assertEqual(app.classify_luck(6, 2.0, False, 0.15), "Lucky Result") | |
| self.assertEqual(app.classify_luck(0, 4.0, False, 0.12), "Unlucky Result") | |
| self.assertEqual(app.classify_luck(0, 3.0, True, 0.10), "Unlucky Result") | |
| self.assertEqual(app.classify_luck(2, 2.2, False, 0.15), "Expected Result") | |
| def test_generated_game_model_uses_python_constants(self): | |
| rendered = app.build_game_html(180) | |
| self.assertNotIn("__DELIVERIES_JSON__", rendered) | |
| self.assertNotIn("__SHOTS_JSON__", rendered) | |
| self.assertNotIn("__AIMS_JSON__", rendered) | |
| for delivery in app.PITCH_MODEL["Delivery"]: | |
| self.assertIn(delivery, rendered) | |
| for shot in app.SHOT_MODEL["Shot"]: | |
| self.assertIn(shot, rendered) | |
| for aim in app.AIM_MODEL["Aim"]: | |
| self.assertIn(aim, rendered) | |
| def test_sample_table_has_ten_trials(self): | |
| table = app.simulate_sample_table("Yorker", 0, 70, 9.0, shot_intent="Defend", aim_lane="Straight") | |
| self.assertEqual(len(table), 10) | |
| self.assertIn("Shot", table.columns) | |
| self.assertIn("Aim", table.columns) | |
| self.assertIn("Decision", table.columns) | |
| def test_learning_summary_handles_partial_logs(self): | |
| summary = app.build_learning_summary([{"actual_runs": 4}, {"unexpected": 99}]) | |
| self.assertEqual(summary["balls"], 2.0) | |
| self.assertEqual(summary["avg_xruns"], 0.0) | |
| self.assertEqual(summary["avg_wicket_risk"], 0.0) | |
| self.assertEqual(summary["avg_actual_runs"], 4.0) | |
| def test_game_pauses_for_review_between_balls(self): | |
| self.assertIn('id="nextBallBtn"', app.GAME_HTML) | |
| self.assertIn('state.mode = "between"', app.GAME_HTML) | |
| self.assertIn('state.mode === "between" ? "Review"', app.GAME_HTML) | |
| self.assertIn("Shot Intent", app.GAME_HTML) | |
| self.assertIn("Aim Lane", app.GAME_HTML) | |
| self.assertIn("Process Score", app.GAME_HTML) | |
| self.assertIn('id="processPill"', app.GAME_HTML) | |
| self.assertIn('id="settingsModal"', app.GAME_HTML) | |
| self.assertIn("Reduce Flashing", app.GAME_HTML) | |
| self.assertIn("hidden-during-play", app.GAME_HTML) | |
| self.assertIn("hidden-for-review", app.GAME_HTML) | |
| self.assertIn("Coach ready. Press Next Ball when done.", app.GAME_HTML) | |
| self.assertIn("Restart Match", app.GAME_HTML) | |
| class RunRateLabStreamlitTests(unittest.TestCase): | |
| def test_streamlit_app_renders(self): | |
| test_app = AppTest.from_file("app.py") | |
| test_app.run(timeout=15) | |
| self.assertFalse(test_app.exception) | |
| self.assertTrue(any("RunRate Lab" in title.value for title in test_app.title)) | |
| self.assertGreaterEqual(len(test_app.metric), 4) | |
| if __name__ == "__main__": | |
| unittest.main() | |