Files
StarPilot/roadscore/tests/test_overlay_view.py
T

120 lines
6.3 KiB
Python

"""Presentation contracts only; these tests never import the runtime or device tools."""
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'prototype'))
from overlay_view import display_text, fit_text, overlay_view, hud_bounds, startup_bounds, EventPresentation
class OverlayTests(unittest.TestCase):
def test_degradation_takes_priority_over_generation(self):
for reason in ('worker_failed', 'holding_accepted_music', 'quality_failures'):
with self.subTest(reason=reason):
view = overlay_view(dict(readiness='READY', job_inflight=True, **{reason: True}))
self.assertEqual(view['activity'], 'DEGRADED')
self.assertFalse(view['ready'])
self.assertEqual(overlay_view(dict(readiness='DEGRADED', job_inflight=True))['activity'], 'DEGRADED')
def test_active_generation_preserves_playback_readiness(self):
view = overlay_view(dict(readiness='READY', job_inflight=True, generation_elapsed_seconds=12.3))
self.assertEqual(view['activity'], 'GENERATING')
self.assertTrue(view['ready'])
self.assertIn('12.3s', view['note'])
def test_legacy_signal_does_not_claim_audible_cue(self):
view = overlay_view(dict(readiness='READY', job_inflight=True, generation_elapsed_seconds=123.4,
turn_signal_music=True))
self.assertEqual(view['event'], '')
self.assertIn('123.4s', view['note'])
def test_active_cue_precedes_queued_and_persistent_signal(self):
view = overlay_view(dict(turn_signal_music=True, gesture_active=['turn_signal_sustain', 'curve_apex'],
gesture_queued=[{'kind': 'arrival'}]))
self.assertEqual(view['event'], 'Curve apex / impact')
self.assertEqual(view['event_state'], 'active')
def test_queued_cue_is_hidden(self):
view = overlay_view(dict(turn_signal_music=True, gesture_queued=[{'kind': 'turn_signal'}]))
self.assertEqual(view['event'], '')
self.assertEqual(view['event_state'], '')
def test_signal_sequence_and_grid_are_not_proof_of_played_audio(self):
for rhythm_enabled in (False, True):
view = overlay_view({'readiness': 'READY', 'signal_shaker': {
'enabled': True, 'sequence_active': True, 'rhythm_enabled': rhythm_enabled}})
self.assertEqual(view['event'], '')
def test_unknown_and_malformed_cues_do_not_invent_a_road_reason(self):
self.assertEqual(overlay_view({'gesture_active': ['unknown_future_kind']})['event'], 'Music cue')
self.assertEqual(overlay_view({'gesture_active': 'curve_apex', 'gesture_queued': [None, {}, 3]})['event'], '')
self.assertEqual(overlay_view({'lead': 4})['event'], '')
self.assertEqual(overlay_view(dict(kind='navigation', phase='anticipation', lead=4))['event'], '')
self.assertEqual(overlay_view(dict(kind='curve', phase='anticipation', lead=4))['event'], '')
def test_startup_badge_clears_home_text_and_visible_footer_icons(self):
self.assertEqual(startup_bounds(536, 240), (294, 172, 230, 60))
self.assertEqual(startup_bounds(536, 240, 256), (294, 172, 230, 60))
self.assertIsNone(startup_bounds(536, 240, 306))
self.assertIsNone(startup_bounds(320, 240))
self.assertEqual(hud_bounds(536, 240), (16, 88, 286, 60))
def test_native_slot_clears_speed_sign_driver_and_steering(self):
x, y, width, height = hud_bounds(536, 240)
# Measured native screenshot + native widget dimensions, with safety margins.
occupied = [(8, 4, 72, 72), (160, 0, 158, 76), (328, 16, 120, 144),
(0, 160, 80, 80), (472, 0, 64, 240)]
for ox, oy, ow, oh in occupied:
self.assertTrue(x + width <= ox or ox + ow <= x or y + height <= oy or oy + oh <= y)
self.assertLessEqual(x + width, 472)
self.assertLessEqual(y + height, 240)
self.assertIsNone(hud_bounds(320, 240))
def test_recent_linger_is_explicit_and_new_active_cue_is_immediate(self):
presenter = EventPresentation()
active = overlay_view(dict(gesture_active=['turn_signal'], readiness='READY'))
self.assertEqual(presenter.update(active, 0)['event_state'], 'active')
recent = presenter.update(overlay_view({'readiness': 'READY'}), .2)
self.assertEqual(recent['event'], 'Recent: Turn signal')
self.assertEqual(recent['event_state'], 'recent')
apex = presenter.update(overlay_view(dict(gesture_active=['curve_apex'], readiness='READY')), .3)
self.assertEqual(apex['event'], 'Curve apex / impact')
self.assertEqual(presenter.update(overlay_view({'readiness': 'READY'}), 3)['event'], '')
def test_hidden_queue_and_degraded_never_wait(self):
presenter = EventPresentation()
queued = overlay_view(dict(gesture_queued=[{'kind': 'curve_apex'}], readiness='READY'))
self.assertEqual(presenter.update(queued, 0)['event'], '')
self.assertEqual(presenter.update(queued, .5)['event'], '')
degraded = presenter.update(overlay_view(dict(readiness='DEGRADED', holding_accepted_music=True)), .6)
self.assertEqual(degraded['activity'], 'DEGRADED')
self.assertEqual(degraded['note'], 'Holding accepted music')
def test_missing_buffer_is_not_zero(self):
for value in (None, float('nan'), float('inf'), '12', True):
self.assertIsNone(overlay_view({'buffered': value})['buffered'])
self.assertEqual(overlay_view({'buffered': -1})['buffered'], 0)
self.assertEqual(overlay_view({})['activity'], 'PREPARING')
def test_stored_score_never_claims_chestnut_compute(self):
view = overlay_view(dict(composer='ace', compute='none', readiness='READY'))
self.assertEqual(view['backend'], ('STORED SCORE', 'NO COMPUTE'))
def test_profile_and_section_intent(self):
view = overlay_view(dict(profile='prism', composer='ace', section='VERSE / CONTINUOUS',
next_section='PRECHORUS', form_labels_are_intent=True))
self.assertEqual(view['profile'], 'Prism')
self.assertEqual(view['section'], 'INTENT: VERSE > PRECHORUS')
self.assertEqual(view['backend'], ('ACE', 'CHESTNUT'))
def test_native_labels_and_truncation_use_supported_characters(self):
self.assertEqual(display_text('Prism · VERSE → CHORUS — café'), 'Prism / VERSE > CHORUS - cafe')
result = fit_text('A long section label', 10, len)
self.assertLessEqual(len(result), 10)
self.assertTrue(result.endswith('...'))
self.assertEqual(fit_text('long', 2, len), '')
if __name__ == '__main__':
unittest.main()