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()