mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-05 05:44:03 +08:00
120 lines
6.3 KiB
Python
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()
|