mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 05:23:41 +08:00
495 lines
20 KiB
Python
495 lines
20 KiB
Python
"""Exercise real layout routing and Widget input with native service owners isolated."""
|
|
|
|
import ast
|
|
from dataclasses import replace
|
|
from enum import IntEnum
|
|
import gc
|
|
from pathlib import Path
|
|
from types import SimpleNamespace as NS
|
|
from unittest.mock import Mock
|
|
import weakref
|
|
|
|
import pyray as rl
|
|
import pytest
|
|
|
|
from openpilot.selfdrive.ui.layouts.arabesque import ArabesqueLayout
|
|
from openpilot.selfdrive.ui.layouts.arabesque import desktop
|
|
from openpilot.selfdrive.ui.layouts.arabesque.theme import DEFAULT_THEME
|
|
from openpilot.selfdrive.ui.layouts.arabesque.tools import terminal
|
|
from openpilot.system.ui.lib.application import gui_app, FontWeight, MouseEvent, MousePos
|
|
from openpilot.system.ui.widgets import Widget
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
|
|
def layout_classes(path, names, namespace):
|
|
# UI owners import device-native IPC; keep these tests usable on a desktop too.
|
|
tree = ast.parse(path.read_text())
|
|
tree.body = ast.parse('from __future__ import annotations').body + [
|
|
node for node in tree.body if isinstance(node, ast.ClassDef) and node.name in names
|
|
]
|
|
exec(compile(ast.fix_missing_locations(tree), str(path), 'exec'), namespace)
|
|
return NS(**namespace)
|
|
|
|
|
|
@pytest.fixture
|
|
def shell(monkeypatch):
|
|
import openpilot.system.ui.widgets as widgets
|
|
monkeypatch.setattr(widgets, 'device', NS(awake=True))
|
|
monkeypatch.setattr(gui_app, '_mouse_events', [])
|
|
monkeypatch.setattr(gui_app, '_show_touches', False)
|
|
monkeypatch.setattr(desktop, 'gui_label', Mock())
|
|
monkeypatch.setattr(terminal, 'gui_label', Mock())
|
|
monkeypatch.setattr(terminal.Terminal, 'show_event', lambda self: None)
|
|
for name in ('draw_rectangle_rec', 'draw_rectangle_rounded', 'draw_rectangle_rounded_lines_ex', 'draw_line_ex', 'draw_ring',
|
|
'draw_circle_v', 'draw_triangle', 'draw_ellipse', 'draw_ellipse_lines', 'begin_scissor_mode', 'end_scissor_mode'):
|
|
monkeypatch.setattr(rl, name, lambda *a: None)
|
|
return ArabesqueLayout(Mock())
|
|
|
|
|
|
def event(x, y, *, pressed=False, released=False, slot=0):
|
|
return MouseEvent(MousePos(x, y), slot, pressed, released, pressed, 1.0)
|
|
|
|
|
|
def frame(view, *events):
|
|
gui_app._mouse_events = list(events)
|
|
view.render()
|
|
|
|
|
|
def tap(view, rect):
|
|
x, y = rect.x + rect.width / 2, rect.y + rect.height / 2
|
|
frame(view, event(x, y, pressed=True))
|
|
frame(view, event(x, y, released=True))
|
|
|
|
|
|
def test_menu_dismissal_tool_title_and_desktop_return(shell):
|
|
shell.set_rect(rl.Rectangle(0, 0, 2160, 1080))
|
|
tap(shell, shell._menu_button)
|
|
assert shell._menu_open
|
|
tap(shell, shell._launchers[0][0])
|
|
assert not shell._menu_open
|
|
assert shell._active_tool is None
|
|
tap(shell, shell._launchers[0][0])
|
|
assert isinstance(shell._active_tool, terminal.Terminal)
|
|
assert not gui_app.mouse_events
|
|
frame(shell)
|
|
assert desktop.gui_label.call_args_list[-1].args[1] == '< Desktop'
|
|
assert desktop.gui_label.call_args_list[-2].args[1] == 'Terminal'
|
|
assert any(call.args[1] == 'Opening local shell…' for call in terminal.gui_label.call_args_list)
|
|
tap(shell, shell._back_button)
|
|
assert shell._active_tool is None
|
|
frame(shell)
|
|
assert any(call.args[1] == 'Arabesque' for call in desktop.gui_label.call_args_list)
|
|
shell._on_exit.assert_not_called()
|
|
tap(shell, shell._menu_button)
|
|
tap(shell, shell._exit_button)
|
|
shell._on_exit.assert_called_once()
|
|
|
|
|
|
def test_drag_stray_release_second_finger_and_hidden_press_do_not_activate(shell):
|
|
shell.set_rect(rl.Rectangle(0, 0, 2160, 1080))
|
|
tap(shell, shell._menu_button)
|
|
frame(shell, event(900, 400, pressed=True), event(100, 170, released=True))
|
|
assert not shell._menu_open
|
|
frame(shell, event(60, 50, pressed=True, slot=1), event(60, 50, released=True, slot=1))
|
|
assert not shell._menu_open
|
|
frame(shell, event(60, 50, released=True))
|
|
assert not shell._menu_open
|
|
tap(shell, shell._menu_button)
|
|
frame(shell, event(100, 170, pressed=True))
|
|
shell.hide_event()
|
|
frame(shell, event(100, 170, released=True))
|
|
shell._on_exit.assert_not_called()
|
|
|
|
|
|
@pytest.mark.parametrize('bounds', [(0, 0, 2160, 1080), (0, 0, 1920, 1080), (45, 30, 1280, 720), (0, 0, 2560, 1080)])
|
|
def test_layout_anchors_and_touch_targets(shell, bounds):
|
|
shell.set_rect(rl.Rectangle(*bounds))
|
|
rect, dock = shell.rect, shell._dock
|
|
assert dock.x + dock.width / 2 == pytest.approx(rect.x + rect.width / 2)
|
|
assert dock.y + dock.height < rect.y + rect.height
|
|
assert dock.width >= 44 and dock.height >= 44
|
|
frame(shell)
|
|
captions = [call.args[0] for call in desktop.gui_label.call_args_list if call.args[1] in ('Terminal', 'Files', 'Rebecca', 'Firefox')]
|
|
assert len(captions) == 4
|
|
for (target, _, _), caption in zip(shell._launchers, captions, strict=True):
|
|
icon_x, icon_y = target.x + target.width / 2, dock.y + 72 * shell._scale
|
|
assert caption.x + caption.width / 2 == pytest.approx(icon_x)
|
|
assert caption.y >= icon_y + 64 * shell._scale
|
|
assert caption.y + caption.height < shell._dock_shelf.y
|
|
assert min(target.width, target.height) >= 44
|
|
back_left, front_left, front_right, back_right = shell._dock_top
|
|
assert back_left.x > front_left.x and back_right.x < front_right.x
|
|
assert back_left.y < dock.y + 136 * shell._scale < front_left.y
|
|
assert front_left.y == pytest.approx(shell._dock_shelf.y)
|
|
assert front_right.y == pytest.approx(shell._dock_shelf.y)
|
|
assert shell._dock_shelf.y + shell._dock_shelf.height < dock.y + dock.height
|
|
assert shell._bar.width == rect.width
|
|
assert shell._exit_button.y > shell._bar.y + shell._bar.height
|
|
assert min(shell._menu_button.width, shell._menu_button.height) >= 44
|
|
assert min(shell._back_button.width, shell._back_button.height) >= 44
|
|
assert shell._tool_rect.y == shell._bar.y + shell._bar.height
|
|
assert shell._tool_rect.x == rect.x
|
|
assert shell._tool_rect.width == rect.width
|
|
assert shell._tool_rect.y + shell._tool_rect.height == pytest.approx(rect.y + rect.height)
|
|
tap(shell, shell._menu_button)
|
|
assert shell._menu_open
|
|
tap(shell, shell._launchers[0][0])
|
|
tap(shell, shell._launchers[0][0])
|
|
frame(shell)
|
|
assert shell._active_tool.rect == shell._tool_rect
|
|
label = desktop.gui_label.call_args
|
|
assert label.args[0] is shell._back_button
|
|
assert label.args[1] == '< Desktop'
|
|
assert label.kwargs['alignment'] == desktop.TextAlignment.CENTER
|
|
|
|
|
|
def test_tool_theme_override_does_not_change_desktop_or_defaults(shell, monkeypatch):
|
|
draw = Mock()
|
|
monkeypatch.setattr(rl, 'draw_rectangle_rec', draw)
|
|
custom = replace(DEFAULT_THEME, background=rl.Color(25, 31, 37, 255), text=rl.Color(240, 230, 220, 255), font_weight=FontWeight.BOLD)
|
|
assert terminal.Terminal()._theme is DEFAULT_THEME
|
|
shell.set_rect(rl.Rectangle(0, 0, 2160, 1080))
|
|
shell._open_tool('Terminal', terminal.Terminal(theme=custom))
|
|
frame(shell)
|
|
assert draw.call_args_list[1].args[1] is custom.background
|
|
assert any(call.args[3] is custom.text and call.args[4] is FontWeight.BOLD for call in terminal.gui_label.call_args_list)
|
|
assert desktop.gui_label.call_args_list[0].args[3] is DEFAULT_THEME.text
|
|
assert DEFAULT_THEME.background.r == 12
|
|
|
|
|
|
@pytest.fixture
|
|
def probe():
|
|
class Probe(Widget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.show_event = Mock()
|
|
self.hide_event = Mock()
|
|
self._handle_mouse_press = Mock()
|
|
self._handle_mouse_release = Mock()
|
|
self._update_state = Mock()
|
|
self.render_calls = Mock()
|
|
|
|
def _render(self, rect, /):
|
|
self.render_calls(rect)
|
|
return Probe()
|
|
|
|
|
|
def test_dock_is_hidden_for_tools_and_returns_on_desktop(shell, probe, monkeypatch):
|
|
triangles = Mock()
|
|
monkeypatch.setattr(rl, 'draw_triangle', triangles)
|
|
shell.set_rect(rl.Rectangle(0, 0, 2160, 1080))
|
|
frame(shell)
|
|
assert any(call.args[0] is shell._dock_top[0] for call in triangles.call_args_list)
|
|
|
|
shell._open_tool('Probe', probe)
|
|
triangles.reset_mock()
|
|
frame(shell)
|
|
assert not any(call.args[0] is shell._dock_top[0] for call in triangles.call_args_list)
|
|
assert probe.rect == shell._tool_rect
|
|
|
|
# The bottom edge now belongs to the tool, including the former dock area.
|
|
x, y = shell.rect.x + shell.rect.width / 2, shell.rect.y + shell.rect.height - 1
|
|
frame(shell, event(x, y, pressed=True))
|
|
frame(shell, event(x, y, released=True))
|
|
probe._handle_mouse_press.assert_called_once()
|
|
probe._handle_mouse_release.assert_called_once()
|
|
|
|
tap(shell, shell._back_button)
|
|
triangles.reset_mock()
|
|
frame(shell)
|
|
assert shell._active_tool is None
|
|
assert any(call.args[0] is shell._dock_top[0] for call in triangles.call_args_list)
|
|
|
|
|
|
def test_tool_input_is_gated_for_menu_chrome_and_disabled_desktop(shell, probe):
|
|
shell.set_rect(rl.Rectangle(0, 0, 2160, 1080))
|
|
shell._open_tool('Probe', probe)
|
|
tap(shell, shell._tool_rect)
|
|
probe._handle_mouse_press.assert_called_once()
|
|
probe._handle_mouse_release.assert_called_once()
|
|
probe._handle_mouse_press.reset_mock()
|
|
probe._handle_mouse_release.reset_mock()
|
|
|
|
# A whole queued gesture after the menu tap must not reach content in the same frame.
|
|
frame(shell, event(60, 50, pressed=True), event(60, 50, released=True),
|
|
event(1000, 500, pressed=True), event(1000, 500, released=True))
|
|
assert not probe.enabled
|
|
probe._handle_mouse_press.assert_not_called()
|
|
probe._handle_mouse_release.assert_not_called()
|
|
tap(shell, shell._menu_button)
|
|
assert shell._menu_open
|
|
tap(shell, shell._tool_rect)
|
|
assert not shell._menu_open
|
|
probe._handle_mouse_press.assert_not_called()
|
|
probe._handle_mouse_release.assert_not_called()
|
|
shell.set_enabled(False)
|
|
tap(shell, shell._tool_rect)
|
|
assert not probe.enabled
|
|
probe._handle_mouse_press.assert_not_called()
|
|
shell.set_enabled(True)
|
|
tap(shell, shell._tool_rect)
|
|
probe._handle_mouse_press.assert_called_once()
|
|
probe._handle_mouse_release.assert_called_once()
|
|
|
|
|
|
def test_tool_lifecycle_runs_once_and_closed_tool_is_not_rendered(shell, probe, monkeypatch):
|
|
shell.set_rect(rl.Rectangle(0, 0, 2160, 1080))
|
|
create = Mock(return_value=probe)
|
|
monkeypatch.setattr(desktop, 'Terminal', create)
|
|
frame(shell)
|
|
create.assert_not_called()
|
|
tap(shell, shell._launchers[0][0])
|
|
probe.show_event.assert_called_once()
|
|
tap(shell, shell._launchers[0][0])
|
|
create.assert_called_once()
|
|
probe._handle_mouse_press.assert_called_once()
|
|
probe._handle_mouse_release.assert_called_once()
|
|
probe._handle_mouse_press.reset_mock()
|
|
probe._handle_mouse_release.reset_mock()
|
|
x, y = shell._back_button.x + 40, shell._back_button.y + 40
|
|
frame(shell, event(x, y, pressed=True))
|
|
frame(shell, event(x, y, released=True), event(1000, 500, pressed=True), event(1000, 500, released=True))
|
|
assert not gui_app.mouse_events
|
|
probe._handle_mouse_press.assert_not_called()
|
|
probe._handle_mouse_release.assert_not_called()
|
|
probe.hide_event.assert_called_once()
|
|
probe.render_calls.reset_mock()
|
|
probe._update_state.reset_mock()
|
|
shell.hide_event()
|
|
for _ in range(3):
|
|
frame(shell)
|
|
probe.hide_event.assert_called_once()
|
|
probe.render_calls.assert_not_called()
|
|
probe._update_state.assert_not_called()
|
|
|
|
|
|
def test_closed_tool_instances_are_released(shell):
|
|
shell.set_rect(rl.Rectangle(0, 0, 2160, 1080))
|
|
instances = []
|
|
for _ in range(100):
|
|
tap(shell, shell._launchers[0][0])
|
|
instances.append(weakref.ref(shell._active_tool))
|
|
tap(shell, shell._back_button)
|
|
gc.collect()
|
|
assert all(instance() is None for instance in instances)
|
|
|
|
|
|
def test_files_has_its_own_launcher_and_blank_shelf_does_not_launch(shell, probe, monkeypatch):
|
|
shell.set_rect(rl.Rectangle(0, 0, 2160, 1080))
|
|
create = Mock(return_value=probe)
|
|
monkeypatch.setattr(desktop, 'Files', create)
|
|
terminal_target, files_target = (item[0] for item in shell._launchers[:2])
|
|
# A gesture crossing launcher boundaries must not start either tool.
|
|
frame(shell, event(terminal_target.x + 20, terminal_target.y + 20, pressed=True))
|
|
frame(shell, event(files_target.x + 20, files_target.y + 20, released=True))
|
|
assert shell._active_tool is None
|
|
tap(shell, rl.Rectangle(shell._dock.x + 2, shell._dock.y + 210, 10, 10))
|
|
assert shell._active_tool is None
|
|
tap(shell, files_target)
|
|
assert shell._active_tool is probe
|
|
create.assert_called_once_with(shell._permitted)
|
|
frame(shell)
|
|
assert desktop.gui_label.call_args_list[-2].args[1] == 'Files'
|
|
tap(shell, shell._back_button)
|
|
probe.hide_event.assert_called_once()
|
|
assert shell._active_tool is None
|
|
|
|
|
|
def test_rebecca_launcher_uses_shared_lifecycle(shell, probe, monkeypatch):
|
|
create = Mock(return_value=probe)
|
|
monkeypatch.setattr(desktop, 'Rebecca', create)
|
|
shell.set_rect(rl.Rectangle(0, 0, 2160, 1080))
|
|
tap(shell, shell._launchers[2][0])
|
|
assert shell._active_tool is probe and not gui_app.mouse_events
|
|
create.assert_called_once_with()
|
|
probe.show_event.assert_called_once()
|
|
frame(shell)
|
|
assert desktop.gui_label.call_args_list[-2].args[1] == 'Rebecca'
|
|
tap(shell, shell._back_button)
|
|
probe.hide_event.assert_called_once()
|
|
|
|
|
|
def test_rebecca_menu_pauses_and_dismissal_does_not_flap(shell, monkeypatch):
|
|
game = desktop.Rebecca()
|
|
monkeypatch.setattr(game, 'show_event', Mock())
|
|
monkeypatch.setattr(game, '_render', Mock())
|
|
monkeypatch.setattr(rl, 'get_frame_time', lambda: 1 / 60)
|
|
shell.set_rect(rl.Rectangle(0, 0, 2160, 1080))
|
|
shell._open_tool('Rebecca', game)
|
|
game._reset()
|
|
frame(shell)
|
|
tap(shell, shell._menu_button)
|
|
frozen = game._distance, game._y, game._vy
|
|
frame(shell)
|
|
tap(shell, game._button)
|
|
assert not shell._menu_open
|
|
assert (game._distance, game._y, game._vy) == frozen
|
|
frame(shell)
|
|
assert game._distance > frozen[0]
|
|
|
|
|
|
@pytest.fixture
|
|
def main(shell, monkeypatch):
|
|
ui = NS(started=False, is_body=False, add_on_body_changed_callbacks=Mock())
|
|
ui.is_offroad = lambda: not ui.started
|
|
sidebar = Mock(is_visible=False)
|
|
sidebar.set_visible.side_effect = lambda visible: setattr(sidebar, 'is_visible', visible)
|
|
settings = Mock()
|
|
namespace = {'rl': rl, 'IntEnum': IntEnum, 'Widget': Widget, 'gui_app': gui_app, 'ui_state': ui,
|
|
'ArabesqueLayout': ArabesqueLayout, 'Sidebar': lambda: sidebar,
|
|
'SIDEBAR_WIDTH': 300, 'HomeLayout': Mock, 'BodyLayout': Mock, 'SettingsLayout': Mock(return_value=settings),
|
|
'AugmentedRoadView': Mock, 'messaging': NS(PubMaster=Mock()),
|
|
'device': NS(add_interactive_timeout_callback=Mock()), 'OnboardingWindow': lambda: NS(completed=True),
|
|
'PanelType': NS(TOGGLES=2, FIREHOSE=4)}
|
|
native = layout_classes(ROOT / 'ui/layouts/main.py', {'MainState', 'MainLayout'}, namespace)
|
|
monkeypatch.setattr(gui_app, 'push_widget', lambda widget: None)
|
|
layout = native.MainLayout()
|
|
layout.set_rect(rl.Rectangle(0, 0, 2160, 1080))
|
|
layout._set_current_layout(native.MainState.SETTINGS)
|
|
return NS(layout=layout, state=native.MainState, ui=ui, settings=settings, native=native)
|
|
|
|
|
|
def test_entry_wiring_and_offroad_gate(main):
|
|
m = main
|
|
callbacks = m.native.SettingsLayout.call_args.kwargs
|
|
assert callbacks['on_open_arabesque'] == m.layout._open_arabesque
|
|
m.ui.started = True
|
|
callbacks['on_open_arabesque']()
|
|
assert m.layout._current_mode == m.state.SETTINGS
|
|
m.ui.started = False
|
|
callbacks['on_open_arabesque']()
|
|
assert m.layout._current_mode == m.state.ARABESQUE
|
|
frame(m.layout)
|
|
m.settings.render.assert_not_called()
|
|
|
|
|
|
def test_handoff_discards_queued_touches_and_restores_settings(main, probe):
|
|
m = main
|
|
gui_app._mouse_events = [event(60, 50, pressed=True), event(60, 50, released=True)]
|
|
m.layout._open_arabesque()
|
|
assert not gui_app.mouse_events
|
|
view = m.layout._layouts[m.state.ARABESQUE]
|
|
frame(m.layout)
|
|
view._open_tool('Probe', probe)
|
|
tap(m.layout, view._menu_button)
|
|
frame(m.layout, event(100, 170, pressed=True))
|
|
frame(m.layout, event(100, 170, released=True), event(1000, 500, pressed=True), event(1000, 500, released=True))
|
|
assert m.layout._current_mode == m.state.SETTINGS
|
|
assert not gui_app.mouse_events
|
|
m.settings.render.assert_not_called()
|
|
assert not view._menu_open
|
|
assert view._active_tool is None
|
|
probe.hide_event.assert_called_once()
|
|
frame(m.layout)
|
|
m.settings.render.assert_called_once()
|
|
|
|
|
|
def test_onroad_transition_draws_normal_alert_owner_immediately(main, probe):
|
|
m = main
|
|
m.layout._open_arabesque()
|
|
view = m.layout._layouts[m.state.ARABESQUE]
|
|
frame(m.layout)
|
|
view._open_tool('Probe', probe)
|
|
tap(m.layout, view._menu_button)
|
|
m.ui.started = True
|
|
frame(m.layout, event(100, 170, pressed=True))
|
|
assert m.layout._current_mode == m.state.ONROAD
|
|
assert not view._menu_open
|
|
assert view._active_tool is None
|
|
probe.hide_event.assert_called_once()
|
|
assert not gui_app.mouse_events
|
|
m.layout._layouts[m.state.ONROAD].render.assert_called_once()
|
|
m.ui.started = False
|
|
frame(m.layout)
|
|
assert m.layout._current_mode == m.state.HOME
|
|
|
|
|
|
def test_shutdown_closes_active_tool_once(main, probe):
|
|
main.layout._open_arabesque()
|
|
view = main.layout._layouts[main.state.ARABESQUE]
|
|
frame(main.layout)
|
|
view._open_tool('Probe', probe)
|
|
main.layout.close()
|
|
main.layout.close()
|
|
assert view._active_tool is None
|
|
probe.hide_event.assert_called_once()
|
|
|
|
|
|
def test_authority_revocation_closes_tool_without_rendering(main, probe):
|
|
main.layout._open_arabesque()
|
|
view = main.layout._layouts[main.state.ARABESQUE]
|
|
frame(main.layout)
|
|
view._open_tool('Probe', probe)
|
|
main.ui.started = True
|
|
main.layout.poll_arabesque()
|
|
assert main.layout._current_mode == main.state.ONROAD
|
|
assert view._active_tool is None
|
|
probe.hide_event.assert_called_once()
|
|
|
|
|
|
def test_covering_widget_pauses_arabesque_runtime_without_rendering(main, monkeypatch):
|
|
main.layout._open_arabesque()
|
|
view = main.layout._layouts[main.state.ARABESQUE]
|
|
view.poll_runtime = Mock()
|
|
main.native.device.awake = True
|
|
monkeypatch.setattr(gui_app, 'get_active_widget', lambda: object())
|
|
main.layout.poll_arabesque()
|
|
view.poll_runtime.assert_called_once_with(False)
|
|
monkeypatch.setattr(gui_app, 'get_active_widget', lambda: main.layout)
|
|
main.layout.poll_arabesque()
|
|
view.poll_runtime.assert_called_with(True)
|
|
|
|
|
|
def test_repeated_entry_exit_reuses_view_and_never_runs_it_closed(main):
|
|
m = main
|
|
view = m.layout._layouts[m.state.ARABESQUE]
|
|
for _ in range(100):
|
|
m.layout._open_arabesque()
|
|
frame(m.layout)
|
|
tap(m.layout, view._menu_button)
|
|
tap(m.layout, view._exit_button)
|
|
assert m.layout._current_mode == m.state.SETTINGS
|
|
assert m.layout._layouts[m.state.ARABESQUE] is view
|
|
view._render = Mock()
|
|
frame(m.layout)
|
|
view._render.assert_not_called()
|
|
|
|
|
|
def test_star_large_routes_shell_and_uses_confirmed_offroad(main, probe):
|
|
namespace = vars(main.native).copy()
|
|
namespace['Destination'] = NS(NETWORK='network')
|
|
namespace['ShellMode'] = NS(HOME='home', SETTINGS='settings', ONROAD='onroad')
|
|
star = layout_classes(ROOT.parent / 'starpilot/ui/runtime_app.py', {'StarMainLayout'}, namespace)
|
|
main.layout.__class__ = star.StarMainLayout
|
|
layout = main.layout
|
|
layout.star, layout.page = Mock(), Mock()
|
|
layout.star.selected = 'developer'
|
|
layout._large_destination = None
|
|
layout._network_bridge = Mock()
|
|
layout.star.adapter.confirmed_offroad.return_value = False
|
|
layout._open_arabesque()
|
|
assert layout._current_mode == main.state.SETTINGS
|
|
layout.star.adapter.confirmed_offroad.return_value = True
|
|
layout._open_arabesque()
|
|
frame(layout)
|
|
view = layout._layouts[main.state.ARABESQUE]
|
|
view._open_tool('Probe', probe)
|
|
layout.page.render.assert_not_called()
|
|
layout.star.adapter.confirmed_offroad.return_value = False
|
|
frame(layout)
|
|
assert layout._current_mode == main.state.HOME
|
|
layout.page.render.assert_called_once()
|
|
probe.hide_event.assert_called_once()
|
|
assert view._active_tool is None
|
|
layout.star.adapter.confirmed_offroad.return_value = True
|
|
layout._set_current_layout(main.state.SETTINGS)
|
|
layout._open_arabesque()
|
|
view._open_tool('Probe', probe)
|
|
layout._native_onroad = layout._layouts[main.state.ONROAD]
|
|
layout.close()
|
|
assert probe.hide_event.call_count == 2
|
|
assert view._active_tool is None
|
|
layout.star.close.assert_called_once()
|
|
layout._native_onroad.close.assert_called_once()
|