"""Attached confirmation: live decisions, physical targets and safe saved layouts.""" from copy import deepcopy from dataclasses import replace import pytest from openpilot.starpilot.ui.large_speed_geometry import CONFIRMATION_SCALE from openpilot.starpilot.ui.onroad_customization import default_document, validate_document from openpilot.starpilot.ui.onroad_state import AlertSize, OnroadAlert, OnroadInput, manual_slc_confirmation, slc_controls, speed_limit_bounds from openpilot.starpilot.ui.presentation import Profile from openpilot.starpilot.ui.tests.test_slc_ui_actions import state, tap_sign, tap_control from openpilot.starpilot.ui.onroad_state import SlcActionKind @pytest.mark.parametrize('profile', list(Profile)) def test_closed_and_open_card_share_anchor_and_physically_scaled_targets(profile): current, emitted = state(), [] touch = OnroadInput(emitted.append, profile) before = speed_limit_bounds(profile, current) tap_sign(touch, current) assert not emitted opened = touch.present(current) after = speed_limit_bounds(profile, opened) assert before is not None and after is not None assert before[1::2] == after[1::2] assert before[0 if profile == Profile.LARGE else 2] == after[0 if profile == Profile.LARGE else 2] first, last = slc_controls(profile, opened) scale = CONFIRMATION_SCALE[profile] assert (first.bounds[2] - first.bounds[0]) / scale == pytest.approx(144) assert (first.bounds[3] - first.bounds[1]) / scale == pytest.approx(112) assert (last.bounds[0] - first.bounds[2]) / scale == pytest.approx(8) assert (first.bounds[0] - after[0]) / scale == pytest.approx(8) assert (after[2] - last.bounds[2]) / scale == pytest.approx(8) torque = current.customization['layouts'][str(profile)]['torque_bar'] assert after[3] < torque['y'] if profile == Profile.COMPACT: assert after == (148., 8, 460., 160.) assert after[0] > 146 @pytest.mark.parametrize('profile', list(Profile)) @pytest.mark.parametrize('action', [SlcActionKind.ACCEPT, SlcActionKind.REJECT]) def test_touch_choice_remains_available_without_wheel_confirmation(profile, action): current = replace(state(), longitudinal_active=False, slc_system_long_available=False, slc_wheel_confirmation_available=False) emitted = [] touch = OnroadInput(emitted.append, profile) tap_sign(touch, current) tap_control(touch, current, action) assert [request.kind for request in emitted] == [action] @pytest.mark.parametrize('profile', list(Profile)) @pytest.mark.parametrize('change', ['actions_hidden', 'sign_hidden', 'automatic', 'alert', 'toyota_guidance', 'stale']) def test_unavailable_confirmation_has_no_empty_chooser_or_action(profile, change): from openpilot.starpilot.ui.onroad_state import ObservationKind current, emitted = state(), [] touch = OnroadInput(emitted.append, profile) tap_sign(touch, current) if change in ('actions_hidden', 'sign_hidden'): doc = deepcopy(current.customization) key = 'speed_limit_actions' if change == 'actions_hidden' else 'cruise_limits' if profile == Profile.LARGE else 'speed_limit' doc['layouts'][str(profile)][key]['enabled'] = False current = replace(current, customization=doc) elif change == 'automatic': current = replace(current, speed_limit=replace(current.speed_limit, automatic_adoption_pending=True)) elif change == 'alert': current = replace(current, alert=OnroadAlert(size=AlertSize.SMALL)) elif change == 'toyota_guidance': current = replace(current, toyota_cruise_status='raise_dash', toyota_cruise_target_kph=100.) else: current = replace(current, speed_limit=replace(current.speed_limit, kind=ObservationKind.STALE, speed_limit_mps=None)) assert not manual_slc_confirmation(profile, current) assert not touch.present(current).slc_prompt_open assert not slc_controls(profile, current) assert not emitted @pytest.mark.parametrize('profile', list(Profile)) def test_new_layouts_reject_torque_overlap_and_legacy_migration_is_targeted(profile): doc = default_document() owner = 'cruise_limits' if profile == Profile.LARGE else 'speed_limit' doc['layouts'][str(profile)][owner]['y'] = 700 if profile == Profile.LARGE else 50 with pytest.raises(ValueError, match='confirmation'): validate_document(doc) doc['version'] = 11 doc['layouts']['large']['current_speed']['x'] = 800 doc['palette']['text'] = '#ABCDEFEE' original = deepcopy(doc) migrated = validate_document(doc) assert doc == original assert migrated['version'] == 12 assert migrated['palette'] == doc['palette'] assert migrated['layouts']['large']['current_speed'] == doc['layouts']['large']['current_speed'] assert migrated['layouts'][str(profile)][owner]['y'] == default_document()['layouts'][str(profile)][owner]['y'] assert validate_document(migrated) == migrated def test_legacy_sources_keep_guidance_below_the_row_and_above_the_gauge(): doc = default_document() doc.update(version=11, speedSources=True, speedSourcesCard=True) doc['layouts']['large']['speed_limit_actions']['y'] = 351 migrated = validate_document(doc) assert migrated['layouts']['large']['speed_limit_actions']['y'] == 367 assert validate_document(migrated) == migrated def test_large_confirmation_clears_the_taller_torque_arc_when_sidebar_is_hidden(): doc = default_document() doc['layouts']['large']['cruise_limits']['y'] = 550 with pytest.raises(ValueError, match='confirmation'): validate_document(doc) doc['layouts']['large']['speed_limit_actions']['enabled'] = False assert validate_document(doc) == doc def test_large_sources_cannot_be_clipped_below_a_custom_confirmation(): doc = default_document() doc['speedSources'] = True doc['layouts']['large']['cruise_limits'].update(x=1520, y=790) doc['layouts']['large']['speed_limit_actions'].update(x=250, y=400) with pytest.raises(ValueError, match='confirmation'): validate_document(doc) doc['version'] = 11 migrated = validate_document(doc) assert validate_document(migrated) == migrated assert migrated['layouts']['large']['speed_limit_actions'] == doc['layouts']['large']['speed_limit_actions'] @pytest.mark.parametrize('reverse', [False, True]) def test_compact_driver_camera_has_no_hidden_confirmation_targets(reverse): from openpilot.starpilot.ui.appearance_preferences import CameraViewChoice current = state() current = replace(current, reverse_driver_camera=reverse, appearance=replace(current.appearance, camera_view=CameraViewChoice.AUTO if reverse else CameraViewChoice.DRIVER)) assert not manual_slc_confirmation(Profile.COMPACT, current) assert not slc_controls(Profile.COMPACT, replace(current, slc_prompt_open=True)) def unexpected_action(_request): raise AssertionError('Hidden confirmation emitted an action') touch = OnroadInput(unexpected_action, Profile.COMPACT) touch.press(250, 90, current) assert not touch.claimed