Files
StarPilot/selfdrive/ui/tests/test_starpilot_navigation_structure.py
T
2026-08-25 17:57:36 -05:00

364 lines
12 KiB
Python

import sys
import types
from unittest.mock import MagicMock
from types import SimpleNamespace
if "cereal.messaging" not in sys.modules:
try:
import cereal.messaging
except ImportError:
msg_mod = types.ModuleType("cereal.messaging")
msg_mod.SubMaster = MagicMock
msg_mod.PubMaster = MagicMock
sys.modules["cereal.messaging"] = msg_mod
if "openpilot.starpilot.common.starpilot_variables" not in sys.modules:
try:
import openpilot.starpilot.common.starpilot_variables
except ImportError:
sv_mod = types.ModuleType("openpilot.starpilot.common.starpilot_variables")
sv_mod.update_starpilot_toggles = lambda: None
sv_mod.migrate_cancel_button_controls = lambda: None
sv_mod.ACTIVE_THEME_PATH = "/tmp"
sv_mod.THEME_SAVE_PATH = "/tmp"
sv_mod.MODELS_PATH = "/tmp"
sv_mod.EARTH_RADIUS = 6371000
sv_mod.STARPILOT_API = ""
sv_mod.KONIK_PATH = "/tmp"
sys.modules["openpilot.starpilot.common.starpilot_variables"] = sv_mod
if "openpilot.common.transformations.orientation" not in sys.modules:
try:
import openpilot.common.transformations.orientation
except ImportError:
trans_mod = types.ModuleType("openpilot.common.transformations.orientation")
trans_mod.rot_from_euler = lambda *a, **k: None
trans_mod.euler_from_rot = lambda *a, **k: None
sys.modules["openpilot.common.transformations.orientation"] = trans_mod
if "openpilot.starpilot.common.accel_profile" not in sys.modules:
try:
import openpilot.starpilot.common.accel_profile
except ImportError:
from collections import defaultdict
accel_mod = types.ModuleType("openpilot.starpilot.common.accel_profile")
accel_mod.ACCELERATION_PROFILES = defaultdict(lambda: 0)
accel_mod.DECELERATION_PROFILES = defaultdict(lambda: 0)
accel_mod.normalize_acceleration_profile = lambda v: v
accel_mod.normalize_deceleration_profile = lambda v: v
sys.modules["openpilot.starpilot.common.accel_profile"] = accel_mod
from openpilot.selfdrive.ui.layouts.settings.starpilot.main_panel import StarPilotLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.navigation import StarPilotNavigationLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanelType
from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import BreadcrumbController, gui_app
def test_root_hub_contains_the_six_categories_in_order():
assert [item["title"] for item in StarPilotLayout.CATEGORIES] == [
"Sounds & Alerts",
"Driving Model",
"Driving Controls",
"System",
"Appearance",
"Vehicle Settings",
]
assert len(StarPilotLayout.CATEGORIES) == 6
assert all(item["title"] != "Navigation & Maps" for item in StarPilotLayout.CATEGORIES)
def test_driving_controls_contains_nested_navigation_folder_and_leaf_routes():
controls = next(item for item in StarPilotLayout.CATEGORIES if item["title"] == "Driving Controls")
assert "panel" not in controls
assert [item["title"] for item in controls["children"]] == [
"Navigation & Maps",
"Gas / Brake",
"Steering",
]
navigation_maps = controls["children"][0]
assert "panel" not in navigation_maps
assert navigation_maps["children"] == [
{"title": "Map Data", "panel": "MAPS", "icon": "navigate"},
{"title": "Navigation", "panel": "NAVIGATION", "icon": "road"},
]
assert controls["children"][1]["panel"] == "LONGITUDINAL"
assert controls["children"][2]["panel"] == "LATERAL"
def test_driving_model_is_a_root_leaf_and_existing_panel_routes_are_preserved():
driving_model = next(item for item in StarPilotLayout.CATEGORIES if item["title"] == "Driving Model")
assert driving_model["panel"] == "DRIVING_MODEL"
assert driving_model["icon"] == "aicar"
assert "children" not in driving_model
assert StarPilotLayout.PANEL_TYPE_MAP["DRIVING_MODEL"] == StarPilotPanelType.DRIVING_MODEL
assert StarPilotLayout.PANEL_TYPE_MAP["MAPS"] == StarPilotPanelType.MAPS
assert StarPilotLayout.PANEL_TYPE_MAP["NAVIGATION"] == StarPilotPanelType.NAVIGATION
assert StarPilotPanelType.NAVIGATION.value == 13
class _FakeHubTile:
def __init__(self, title, desc, icon_key, on_click, bg_color=None):
self.title = title
self.desc = desc
self.icon_key = icon_key
self.on_click = on_click
self.bg_color = bg_color
class _FakeGrid:
def __init__(self):
self.tiles = []
def clear(self):
self.tiles.clear()
def add_tile(self, tile):
self.tiles.append(tile)
class _PanelSpy:
def __init__(self, name):
self.name = name
self.show_count = 0
self.hide_count = 0
self.current_sub_panel = ""
def show_event(self):
self.show_count += 1
def hide_event(self):
self.hide_count += 1
def set_current_sub_panel(self, sub_panel):
self.current_sub_panel = sub_panel
def _make_layout(monkeypatch):
import openpilot.selfdrive.ui.layouts.settings.starpilot.main_panel as main_panel
monkeypatch.setattr(main_panel, "HubTile", _FakeHubTile)
layout = object.__new__(StarPilotLayout)
layout._current_panel = StarPilotPanelType.MAIN
layout._hub_path = []
layout._selected_leaf = None
layout._current_category_idx = None
layout._panel_stack = []
layout._depth_callback = None
layout._main_grid = _FakeGrid()
layout._panels = {}
for panel_type in StarPilotPanelType:
layout._panels[panel_type] = SimpleNamespace(
name=panel_type.name,
instance=None if panel_type == StarPilotPanelType.MAIN else _PanelSpy(panel_type.name),
)
depths = []
layout.set_depth_callback(depths.append)
StarPilotLayout.active_instance = layout
layout._rebuild_grid()
return layout, depths
def _click_title(layout, title):
tile = next(tile for tile in layout._main_grid.tiles if tile.title == title)
tile.on_click()
def test_nested_hub_navigation_back_and_depth_values(monkeypatch):
layout, depths = _make_layout(monkeypatch)
assert len(layout._main_grid.tiles) == 6
assert depths == []
_click_title(layout, "Driving Controls")
assert [folder["title"] for folder in layout._hub_path] == ["Driving Controls"]
assert layout._current_panel == StarPilotPanelType.MAIN
layout._update_depth()
assert depths[-1] == 1
_click_title(layout, "Navigation & Maps")
assert [folder["title"] for folder in layout._hub_path] == ["Driving Controls", "Navigation & Maps"]
layout._update_depth()
assert depths[-1] == 2
_click_title(layout, "Map Data")
assert layout._current_panel == StarPilotPanelType.MAPS
assert layout._selected_leaf["title"] == "Map Data"
assert depths[-1] == 3
maps_panel = layout._panels[StarPilotPanelType.MAPS].instance
assert (maps_panel.show_count, maps_panel.hide_count) == (1, 0)
layout.navigate_back()
assert layout._current_panel == StarPilotPanelType.MAIN
assert [folder["title"] for folder in layout._hub_path] == ["Driving Controls", "Navigation & Maps"]
assert depths[-1] == 2
assert (maps_panel.show_count, maps_panel.hide_count) == (1, 1)
layout.navigate_back()
assert [folder["title"] for folder in layout._hub_path] == ["Driving Controls"]
assert depths[-1] == 1
layout.navigate_back()
assert layout._hub_path == []
assert depths[-1] == 0
def test_root_driving_model_opens_directly_and_sub_panel_depth_is_additive(monkeypatch):
layout, depths = _make_layout(monkeypatch)
_click_title(layout, "Driving Model")
assert layout._hub_path == []
assert layout._current_panel == StarPilotPanelType.DRIVING_MODEL
assert depths[-1] == 1
layout._panel_stack.append((StarPilotPanelType.DRIVING_MODEL, "details"))
layout._commit_navigation()
assert depths[-1] == 2
layout.navigate_back()
assert layout._current_panel == StarPilotPanelType.DRIVING_MODEL
assert layout._panel_stack == []
assert depths[-1] == 1
layout.navigate_back()
assert layout._current_panel == StarPilotPanelType.MAIN
assert depths[-1] == 0
def test_breadcrumb_paths_and_folder_jump_back(monkeypatch):
layout, _ = _make_layout(monkeypatch)
monkeypatch.setattr(gui_app, "_nav_stack", [layout], raising=False)
assert BreadcrumbController.build_path() == [("StarPilot", "action:home")]
_click_title(layout, "Driving Controls")
assert BreadcrumbController.build_path() == [
("StarPilot", "action:home"),
("Driving Controls", "action:hub:1"),
]
_click_title(layout, "Navigation & Maps")
assert BreadcrumbController.build_path() == [
("StarPilot", "action:home"),
("Driving Controls", "action:hub:1"),
("Navigation & Maps", "action:hub:2"),
]
_click_title(layout, "Map Data")
assert BreadcrumbController.build_path()[-1] == ("Map Data", "action:panel")
nav_stack = [layout, object()]
monkeypatch.setattr(gui_app, "_nav_stack", nav_stack, raising=False)
monkeypatch.setattr(gui_app, "pop_widget", lambda: nav_stack.pop(), raising=False)
BreadcrumbController().handle_click("action:hub:1")
assert nav_stack == [layout]
assert layout._current_panel == StarPilotPanelType.MAIN
assert [folder["title"] for folder in layout._hub_path] == ["Driving Controls"]
assert BreadcrumbController.build_path()[-1] == ("Driving Controls", "action:hub:1")
def test_home_breadcrumb_clears_hub_path_panel_stack_and_active_panel(monkeypatch):
layout, _ = _make_layout(monkeypatch)
_click_title(layout, "Driving Controls")
_click_title(layout, "Navigation & Maps")
_click_title(layout, "Map Data")
layout._panel_stack.append((StarPilotPanelType.MAPS, "details"))
nav_stack = [layout, object(), object()]
monkeypatch.setattr(gui_app, "_nav_stack", nav_stack, raising=False)
monkeypatch.setattr(gui_app, "pop_widget", lambda: nav_stack.pop(), raising=False)
BreadcrumbController().handle_click("action:home")
maps_panel = layout._panels[StarPilotPanelType.MAPS].instance
assert nav_stack == [layout]
assert layout._hub_path == []
assert layout._selected_leaf is None
assert layout._panel_stack == []
assert layout._current_panel == StarPilotPanelType.MAIN
assert maps_panel.hide_count == 1
assert BreadcrumbController.build_path() == [("StarPilot", "action:home")]
def test_navigation_start_is_the_summary_action_not_a_duplicate_rail_target():
layout = object.__new__(StarPilotNavigationLayout)
layout._draft_destination = {
"name": "Home",
"place_name": "Home",
"latitude": 1.0,
"longitude": 2.0,
}
layout._selected_favorite = None
layout._favorites = []
action_ids = [action[0] for action in layout._action_definitions()]
assert action_ids == ["action:favorite", "action:home", "action:work"]
def test_rejected_search_invalidates_an_in_flight_request_generation():
layout = object.__new__(StarPilotNavigationLayout)
layout._search_generation = 3
layout._query = "previous"
layout._search_results = []
layout._search_error = ""
layout._draft_destination = None
layout._selected_favorite = None
layout._start_search("ab")
assert layout._search_generation == 4
assert layout._search_error
def test_breadcrumb_mouse_interaction_lifecycle():
import pyray as rl
from openpilot.system.ui.lib.application import MousePos
controller = BreadcrumbController()
controller._rects = {
"action:home": rl.Rectangle(10, 10, 50, 30),
"action:hub:1": rl.Rectangle(70, 10, 80, 30),
}
controller._bounds = rl.Rectangle(0, 0, 200, 50)
# Press on Home
controller.init_interaction(MousePos(20, 20))
assert controller.pressed == "action:home"
# Move within Home rect -> remains pressed
controller.update_interaction(MousePos(25, 20))
assert controller.pressed == "action:home"
# Drag outside Home rect -> cancels pressed target
controller.update_interaction(MousePos(500, 500))
assert controller.pressed is None
# Re-press and cancel
controller.init_interaction(MousePos(20, 20))
assert controller.pressed == "action:home"
controller.cancel_interaction()
assert controller.pressed is None
def test_breadcrumb_panel_stack_unwinds_nav_stack(monkeypatch):
layout, _ = _make_layout(monkeypatch)
_click_title(layout, "Driving Controls")
_click_title(layout, "Navigation & Maps")
_click_title(layout, "Map Data")
layout._panel_stack.append((StarPilotPanelType.MAPS, "sub1"))
layout._panel_stack.append((StarPilotPanelType.MAPS, "sub2"))
nav_stack = [layout, object(), object()]
monkeypatch.setattr(gui_app, "_nav_stack", nav_stack, raising=False)
monkeypatch.setattr(gui_app, "pop_widget", lambda: nav_stack.pop(), raising=False)
BreadcrumbController().handle_click("action:panel_stack:0")
assert nav_stack == [layout]
assert len(layout._panel_stack) == 1
assert layout._panel_stack[0] == (StarPilotPanelType.MAPS, "sub1")