290 lines
7.9 KiB
Python
290 lines
7.9 KiB
Python
import importlib.util
|
|
import time
|
|
from pathlib import Path
|
|
from unittest.mock import Mock
|
|
|
|
import pytest
|
|
|
|
from fenrirscreenreader.core.tabCompletionManager import TabCompletionManager
|
|
|
|
|
|
def _build_env(old_text="", cursor=None, screen="pty"):
|
|
if cursor is None:
|
|
cursor = {"x": 0, "y": 0}
|
|
input_manager = Mock()
|
|
input_manager.get_last_event.return_value = {
|
|
"event_name": "KEY_TAB",
|
|
"event_state": 1,
|
|
}
|
|
input_manager.get_last_deepest_input.return_value = [["KEY_TAB"]]
|
|
input_manager.get_last_input_time.return_value = time.time()
|
|
env = {
|
|
"runtime": {
|
|
"InputManager": input_manager,
|
|
},
|
|
"screen": {
|
|
"new_cursor": cursor.copy(),
|
|
"new_content_text": old_text,
|
|
"new_delta": "",
|
|
"new_delta_is_typing": False,
|
|
"newTTY": screen,
|
|
},
|
|
"commandBuffer": {},
|
|
}
|
|
manager = TabCompletionManager()
|
|
manager.initialize(env)
|
|
return manager, env, input_manager
|
|
|
|
|
|
def _load_large_insertion_module():
|
|
module_path = (
|
|
Path(__file__).resolve().parents[2]
|
|
/ "src"
|
|
/ "fenrirscreenreader"
|
|
/ "commands"
|
|
/ "onCursorChange"
|
|
/ "55000-large_insertion_echo.py"
|
|
)
|
|
spec = importlib.util.spec_from_file_location(
|
|
"fenrir_large_insertion_echo", module_path
|
|
)
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def _set_screen_update(env, text, cursor, delta="", typing=False):
|
|
env["screen"]["new_content_text"] = text
|
|
env["screen"]["new_cursor"] = cursor.copy()
|
|
env["screen"]["new_delta"] = delta
|
|
env["screen"]["new_delta_is_typing"] = typing
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_unique_completion_speaks_inserted_suffix():
|
|
manager, env, _input_manager = _build_env(
|
|
"cd Do".ljust(20), {"x": 5, "y": 0}
|
|
)
|
|
|
|
manager.capture_if_tab()
|
|
_set_screen_update(
|
|
env,
|
|
"cd Documents/".ljust(20),
|
|
{"x": 13, "y": 0},
|
|
delta="cuments/",
|
|
typing=True,
|
|
)
|
|
|
|
assert manager.process_update() == "cuments/"
|
|
state = env["commandBuffer"]["tabCompletion"]
|
|
assert state["pending"] is None
|
|
assert state["lastProcessedDelta"] == "cuments/"
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_small_completion_speaks_inserted_suffix():
|
|
manager, env, _input_manager = _build_env(
|
|
"cd gi".ljust(20), {"x": 5, "y": 0}
|
|
)
|
|
|
|
manager.capture_if_tab()
|
|
_set_screen_update(
|
|
env,
|
|
"cd git/".ljust(20),
|
|
{"x": 7, "y": 0},
|
|
delta="t/",
|
|
typing=True,
|
|
)
|
|
|
|
assert manager.process_update() == "t/"
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_completion_processed_once_for_cursor_and_screen_update():
|
|
manager, env, _input_manager = _build_env(
|
|
"cd gi".ljust(20), {"x": 5, "y": 0}
|
|
)
|
|
|
|
manager.capture_if_tab()
|
|
_set_screen_update(
|
|
env,
|
|
"cd git/".ljust(20),
|
|
{"x": 7, "y": 0},
|
|
delta="t/",
|
|
typing=True,
|
|
)
|
|
|
|
assert manager.process_update() == "t/"
|
|
assert manager.process_update() == ""
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_candidate_list_speaks_visible_list_without_cursor_advance():
|
|
old_text = "\n".join(["$ cd Do".ljust(20), "".ljust(20), "".ljust(20)])
|
|
manager, env, _input_manager = _build_env(old_text, {"x": 7, "y": 0})
|
|
|
|
manager.capture_if_tab()
|
|
_set_screen_update(
|
|
env,
|
|
"\n".join(
|
|
[
|
|
"$ cd Do".ljust(20),
|
|
"Documents/ Downloads/".ljust(20),
|
|
"$ cd Do".ljust(20),
|
|
]
|
|
),
|
|
{"x": 7, "y": 2},
|
|
delta="Documents/ Downloads/",
|
|
)
|
|
|
|
assert manager.process_update() == "Documents/ Downloads/"
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_no_screen_change_stays_silent_and_keeps_pending_briefly():
|
|
manager, env, _input_manager = _build_env(
|
|
"cd Do".ljust(20), {"x": 5, "y": 0}
|
|
)
|
|
|
|
manager.capture_if_tab()
|
|
|
|
assert manager.process_update() == ""
|
|
assert env["commandBuffer"]["tabCompletion"]["pending"] is not None
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_timeout_clears_pending_without_speech():
|
|
manager, env, _input_manager = _build_env(
|
|
"cd Do".ljust(20), {"x": 5, "y": 0}
|
|
)
|
|
|
|
manager.capture_if_tab()
|
|
env["commandBuffer"]["tabCompletion"]["pending"]["timestamp"] = (
|
|
time.time() - 1
|
|
)
|
|
_set_screen_update(
|
|
env,
|
|
"unrelated output".ljust(20),
|
|
{"x": 0, "y": 0},
|
|
delta="unrelated output",
|
|
)
|
|
|
|
assert manager.process_update() == ""
|
|
assert env["commandBuffer"]["tabCompletion"]["pending"] is None
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_non_tab_key_does_not_capture():
|
|
manager, env, input_manager = _build_env(
|
|
"cd Do".ljust(20), {"x": 5, "y": 0}
|
|
)
|
|
input_manager.get_last_event.return_value = {
|
|
"event_name": "KEY_A",
|
|
"event_state": 1,
|
|
}
|
|
input_manager.get_last_deepest_input.return_value = [["KEY_A"]]
|
|
|
|
manager.capture_if_tab()
|
|
|
|
assert env["commandBuffer"]["tabCompletion"]["pending"] is None
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_recent_tab_screen_update_works_without_key_input_snapshot():
|
|
manager, env, input_manager = _build_env(
|
|
"cd Documents/".ljust(20), {"x": 13, "y": 0}
|
|
)
|
|
input_manager.get_last_event.return_value = {
|
|
"event_name": "KEY_TAB",
|
|
"event_state": 0,
|
|
}
|
|
env["screen"]["old_content_text"] = "cd Do".ljust(20)
|
|
env["screen"]["old_cursor"] = {"x": 5, "y": 0}
|
|
env["commandBuffer"]["tabCompletion"]["pending"] = None
|
|
_set_screen_update(
|
|
env,
|
|
"cd Documents/".ljust(20),
|
|
{"x": 13, "y": 0},
|
|
delta="cuments/",
|
|
typing=True,
|
|
)
|
|
|
|
assert manager.process_update() == "cuments/"
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_large_insertion_echo_speaks_pasted_cursor_text():
|
|
large_insertion_module = _load_large_insertion_module()
|
|
output_manager = Mock(present_text=Mock())
|
|
settings_manager = Mock()
|
|
settings_manager.get_setting_as_bool.return_value = False
|
|
input_manager = Mock()
|
|
input_manager.get_last_deepest_input.return_value = []
|
|
input_manager.get_last_event.return_value = None
|
|
screen_manager = Mock(
|
|
is_screen_change=Mock(return_value=False),
|
|
is_delta=Mock(return_value=True),
|
|
)
|
|
env = {
|
|
"runtime": {
|
|
"InputManager": input_manager,
|
|
"OutputManager": output_manager,
|
|
"ScreenManager": screen_manager,
|
|
"SettingsManager": settings_manager,
|
|
},
|
|
"screen": {
|
|
"old_cursor": {"x": 2, "y": 0},
|
|
"new_cursor": {"x": 13, "y": 0},
|
|
"new_delta": "hello world",
|
|
},
|
|
}
|
|
command = large_insertion_module.command()
|
|
command.initialize(env)
|
|
|
|
command.run()
|
|
|
|
output_manager.present_text.assert_called_once_with(
|
|
"hello world",
|
|
interrupt=True,
|
|
announce_capital=True,
|
|
flush=False,
|
|
)
|
|
|
|
|
|
@pytest.mark.unit
|
|
def test_large_insertion_echo_defers_recent_tab_to_tab_completion():
|
|
large_insertion_module = _load_large_insertion_module()
|
|
output_manager = Mock(present_text=Mock())
|
|
settings_manager = Mock()
|
|
settings_manager.get_setting_as_bool.return_value = False
|
|
input_manager = Mock()
|
|
input_manager.get_last_deepest_input.return_value = [["KEY_TAB"]]
|
|
input_manager.get_last_event.return_value = {
|
|
"event_name": "KEY_TAB",
|
|
"event_state": 1,
|
|
}
|
|
input_manager.get_last_input_time.return_value = time.time()
|
|
screen_manager = Mock(
|
|
is_screen_change=Mock(return_value=False),
|
|
is_delta=Mock(return_value=True),
|
|
)
|
|
env = {
|
|
"runtime": {
|
|
"InputManager": input_manager,
|
|
"OutputManager": output_manager,
|
|
"ScreenManager": screen_manager,
|
|
"SettingsManager": settings_manager,
|
|
},
|
|
"screen": {
|
|
"old_cursor": {"x": 5, "y": 0},
|
|
"new_cursor": {"x": 13, "y": 0},
|
|
"new_delta": "cuments/",
|
|
},
|
|
}
|
|
command = large_insertion_module.command()
|
|
command.initialize(env)
|
|
|
|
command.run()
|
|
|
|
output_manager.present_text.assert_not_called()
|