mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 13:33:42 +08:00
269 lines
9.4 KiB
Python
269 lines
9.4 KiB
Python
"""Drive Files through real Widget touches and temporary filesystem operations."""
|
|
|
|
from threading import Event
|
|
import time
|
|
from types import SimpleNamespace as NS
|
|
from unittest.mock import Mock
|
|
|
|
import pyray as rl
|
|
import pytest
|
|
|
|
from openpilot.selfdrive.ui.layouts.arabesque import desktop
|
|
from openpilot.selfdrive.ui.layouts.arabesque.tools import files
|
|
from openpilot.selfdrive.ui.layouts.arabesque.tools.filesystem import FileSystem
|
|
from openpilot.system.ui.lib.application import gui_app, MouseEvent, MousePos
|
|
|
|
|
|
@pytest.fixture
|
|
def view(tmp_path, monkeypatch):
|
|
import openpilot.system.ui.widgets as widgets
|
|
from openpilot.system.ui.widgets import inputbox
|
|
monkeypatch.setattr(widgets, 'device', NS(awake=True))
|
|
monkeypatch.setattr(gui_app, '_mouse_events', [])
|
|
monkeypatch.setattr(gui_app, '_show_touches', False)
|
|
monkeypatch.setattr(gui_app, 'font', lambda *args: NS(texture=NS(id=1)))
|
|
monkeypatch.setattr(inputbox, 'measure_text_cached', lambda font, text, size: rl.Vector2(len(text) * size / 2, size))
|
|
monkeypatch.setattr(files, 'gui_label', Mock())
|
|
monkeypatch.setattr(desktop, 'gui_label', Mock())
|
|
monkeypatch.setattr(files, 'wrap_text', lambda font, text, size, width: text.splitlines())
|
|
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', 'begin_scissor_mode', 'end_scissor_mode', 'draw_text_ex',
|
|
'draw_line', 'set_exit_key'):
|
|
monkeypatch.setattr(rl, name, Mock())
|
|
for name in ('get_key_pressed', 'get_char_pressed', 'get_mouse_wheel_move'):
|
|
monkeypatch.setattr(rl, name, lambda: 0)
|
|
monkeypatch.setattr(rl, 'is_key_down', lambda key: False)
|
|
tool = files.Files(lambda: True)
|
|
tool._path = tmp_path
|
|
tool._places = (('Home', tmp_path),)
|
|
tool._fs = FileSystem((tmp_path,), (tmp_path / 'managed',), tool._permitted)
|
|
tool.set_rect(rl.Rectangle(0, 104, 2160, 976))
|
|
tool.show_event()
|
|
settle(tool)
|
|
yield tool
|
|
tool.hide_event()
|
|
|
|
|
|
def frame(view, *events):
|
|
gui_app._mouse_events = list(events)
|
|
view.render()
|
|
|
|
|
|
def settle(view):
|
|
deadline = time.monotonic() + 3
|
|
while view._future is not None and time.monotonic() < deadline:
|
|
frame(view)
|
|
time.sleep(.001)
|
|
assert view._future is None
|
|
frame(view)
|
|
|
|
|
|
def event(x, y, *, pressed=False, released=False, slot=0):
|
|
return MouseEvent(MousePos(x, y), slot, pressed, released, pressed, time.monotonic())
|
|
|
|
|
|
def tap(view, action):
|
|
frame(view)
|
|
rect = next(rect for rect, value in view._buttons if value == action)
|
|
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))
|
|
frame(view)
|
|
|
|
|
|
def item(view, name):
|
|
return next(entry for entry in view._entries if entry.path.name == name)
|
|
|
|
|
|
def test_touch_create_rename_copy_move_and_confirm_delete(view, tmp_path):
|
|
tap(view, 'mkdir')
|
|
tap(view, ('key', 'a'))
|
|
tap(view, 'submit')
|
|
settle(view)
|
|
assert (tmp_path / 'a').is_dir()
|
|
tap(view, ('actions', item(view, 'a')))
|
|
tap(view, 'rename')
|
|
tap(view, ('key', files.BACKSPACE_KEY))
|
|
tap(view, ('key', 'b'))
|
|
tap(view, ('key', files.ENTER_KEY))
|
|
settle(view)
|
|
assert (tmp_path / 'b').is_dir() and not (tmp_path / 'a').exists()
|
|
(tmp_path / 'note.txt').write_text('hello')
|
|
tap(view, 'refresh')
|
|
settle(view)
|
|
tap(view, ('actions', item(view, 'note.txt')))
|
|
tap(view, 'copy')
|
|
tap(view, ('open', item(view, 'b')))
|
|
settle(view)
|
|
tap(view, 'paste')
|
|
settle(view)
|
|
assert (tmp_path / 'b' / 'note.txt').read_text() == 'hello'
|
|
tap(view, ('actions', item(view, 'note.txt')))
|
|
tap(view, 'rename')
|
|
view._input.text = 'moved.txt'
|
|
tap(view, 'submit')
|
|
settle(view)
|
|
tap(view, ('actions', item(view, 'moved.txt')))
|
|
tap(view, 'move')
|
|
tap(view, 'up')
|
|
settle(view)
|
|
tap(view, 'paste')
|
|
settle(view)
|
|
assert (tmp_path / 'moved.txt').read_text() == 'hello'
|
|
assert not (tmp_path / 'b' / 'moved.txt').exists()
|
|
tap(view, ('actions', item(view, 'moved.txt')))
|
|
tap(view, 'delete')
|
|
assert (tmp_path / 'moved.txt').exists()
|
|
tap(view, 'close')
|
|
assert (tmp_path / 'moved.txt').exists()
|
|
tap(view, ('actions', item(view, 'moved.txt')))
|
|
tap(view, 'delete')
|
|
tap(view, 'confirm-delete')
|
|
settle(view)
|
|
assert not (tmp_path / 'moved.txt').exists()
|
|
|
|
|
|
def test_preview_hidden_toggle_and_permission_errors(view, tmp_path):
|
|
(tmp_path / '.hidden').touch()
|
|
(tmp_path / 'note').write_text('Hello\nworld')
|
|
(tmp_path / 'managed').mkdir()
|
|
tap(view, 'refresh')
|
|
settle(view)
|
|
assert ('open', item(view, '.hidden')) not in [action for _, action in view._buttons]
|
|
tap(view, 'hidden')
|
|
assert ('open', item(view, '.hidden')) in [action for _, action in view._buttons]
|
|
tap(view, ('open', item(view, 'note')))
|
|
settle(view)
|
|
assert view._preview == 'Hello\nworld' and view._panel == 'preview'
|
|
assert gui_app._nav_stack == []
|
|
tap(view, 'close')
|
|
tap(view, ('open', item(view, 'managed')))
|
|
settle(view)
|
|
assert 'mkdir' not in [action for _, action in view._buttons]
|
|
view._places += (('Missing', tmp_path / 'missing'),)
|
|
tap(view, 'places')
|
|
tap(view, ('place', tmp_path / 'missing'))
|
|
settle(view)
|
|
assert view._path == tmp_path / 'managed' and view._error
|
|
|
|
|
|
def test_drag_and_second_finger_do_not_open_files(view, tmp_path):
|
|
(tmp_path / 'folder').mkdir()
|
|
tap(view, 'refresh')
|
|
settle(view)
|
|
selected = item(view, 'folder')
|
|
row = next(rect for rect, action in view._buttons if action == ('open', selected))
|
|
x, y = row.x + 100, row.y + 20
|
|
frame(view, event(x, y, pressed=True, slot=1), event(x, y, released=True, slot=1))
|
|
assert view._path == tmp_path
|
|
frame(view, event(x, y, pressed=True))
|
|
frame(view, event(x + 70, y))
|
|
frame(view, event(x + 70, y, released=True))
|
|
assert view._path == tmp_path
|
|
|
|
|
|
def test_tapping_to_stop_inertia_does_not_open_a_file(view, tmp_path):
|
|
(tmp_path / 'folder').mkdir()
|
|
tap(view, 'refresh')
|
|
settle(view)
|
|
selected = item(view, 'folder')
|
|
row = next(rect for rect, action in view._buttons if action == ('open', selected))
|
|
view._scroll._velocity_filter_y.x = 2000
|
|
x, y = row.x + 100, row.y + 20
|
|
frame(view, event(x, y, pressed=True))
|
|
frame(view, event(x, y, released=True))
|
|
assert view._path == tmp_path and view._future is None
|
|
|
|
|
|
def test_shell_menu_blocks_inline_keyboard_and_dismissal_does_not_save(view, tmp_path, monkeypatch):
|
|
view.hide_event()
|
|
shell = desktop.ArabesqueLayout(Mock(), lambda: True)
|
|
shell.set_rect(rl.Rectangle(0, 0, 2160, 1080))
|
|
shell._open_tool('Files', view)
|
|
settle(view)
|
|
tap(view, 'mkdir')
|
|
view._input.text = 'blocked'
|
|
shell._menu_open = True
|
|
keys = iter([rl.KEY_ENTER, 0])
|
|
monkeypatch.setattr(rl, 'get_key_pressed', lambda: next(keys, 0))
|
|
frame(shell)
|
|
assert not view.enabled and view._future is None
|
|
assert not (tmp_path / 'blocked').exists()
|
|
# A tap on Save dismisses the shell menu without creating a directory.
|
|
button = next(rect for rect, action in view._buttons if action == 'submit')
|
|
x, y = button.x + 10, button.y + 10
|
|
frame(shell, event(x, y, pressed=True), event(x, y, released=True))
|
|
assert not shell._menu_open
|
|
assert not (tmp_path / 'blocked').exists()
|
|
shell.hide_event()
|
|
|
|
|
|
def test_close_cancels_worker_and_drops_results_without_blocking(view):
|
|
started, stopped = Event(), Event()
|
|
|
|
def work(cancelled):
|
|
started.set()
|
|
cancelled.wait(2)
|
|
stopped.set()
|
|
|
|
view._run('copy', work)
|
|
assert started.wait(1)
|
|
view.hide_event()
|
|
assert stopped.wait(1)
|
|
assert view._executor is None and view._future is None
|
|
|
|
|
|
def test_physical_text_and_enter_in_one_frame_save_the_complete_name(view, tmp_path, monkeypatch):
|
|
tap(view, 'mkdir')
|
|
characters = iter([ord('o'), ord('k'), 0])
|
|
keys = iter([rl.KEY_ENTER, 0])
|
|
monkeypatch.setattr(rl, 'get_char_pressed', lambda: next(characters, 0))
|
|
monkeypatch.setattr(rl, 'get_key_pressed', lambda: next(keys, 0))
|
|
frame(view)
|
|
settle(view)
|
|
assert (tmp_path / 'ok').is_dir()
|
|
|
|
|
|
def test_loss_of_offroad_authority_blocks_confirmation(view, tmp_path):
|
|
tap(view, 'mkdir')
|
|
view._input.text = 'do-not-create'
|
|
view._permitted = lambda: False
|
|
tap(view, 'submit')
|
|
settle(view)
|
|
assert not (tmp_path / 'do-not-create').exists()
|
|
|
|
|
|
@pytest.mark.parametrize('bounds', [(0, 104, 2160, 976), (45, 100, 1280, 616), (0, 104, 2560, 976)])
|
|
def test_controls_fit_scaled_tool_and_long_lists_only_draw_visible_rows(view, tmp_path, bounds):
|
|
for index in range(150):
|
|
(tmp_path / f'{index:03}.txt').touch()
|
|
tap(view, 'refresh')
|
|
settle(view)
|
|
view.set_rect(rl.Rectangle(*bounds))
|
|
frame(view)
|
|
assert len(view._buttons) < 30
|
|
for rect, _ in view._buttons:
|
|
assert rect.x >= view.rect.x and rect.y >= view.rect.y
|
|
assert rect.x + rect.width <= view.rect.x + view.rect.width + 1
|
|
assert rect.y + rect.height <= view.rect.y + view.rect.height + 1
|
|
|
|
|
|
@pytest.mark.parametrize('keyword', [False, True])
|
|
def test_name_input_accepts_base_render_signature_with_unchanged_theme(view, monkeypatch, keyword):
|
|
render = Mock()
|
|
monkeypatch.setattr(files.InputBox, '_render', render)
|
|
rect = rl.Rectangle(0, 0, 400, 104)
|
|
if keyword:
|
|
view._input._render(rect, color=rl.RED, border_color=rl.GREEN, text_color=rl.BLUE, font_size=12)
|
|
else:
|
|
view._input._render(rect, rl.RED, rl.GREEN, rl.BLUE, 12)
|
|
theme = view._input._theme
|
|
render.assert_called_once_with(rect, color=theme.surface, border_color=theme.surface, text_color=theme.text, font_size=40)
|
|
|
|
|
|
def test_mouse_event_accepts_base_keyword_and_preserves_drag(view):
|
|
view._press_pos = MousePos(10, 10)
|
|
view._dragged = False
|
|
view._handle_mouse_event(mouse_event=event(10 + files.DRAG_THRESHOLD + 1, 10))
|
|
assert view._dragged
|