import importlib.util from pathlib import Path from unittest.mock import Mock import pytest from fenrirscreenreader.core.inputManager import InputManager PROJECT_ROOT = Path(__file__).resolve().parents[2] COMMANDS_DIR = ( PROJECT_ROOT / "src" / "fenrirscreenreader" / "commands" / "commands" ) KEYBOARD_DIR = PROJECT_ROOT / "config" / "keyboard" def load_command(name): spec = importlib.util.spec_from_file_location( f"fenrir_{name}", COMMANDS_DIR / f"{name}.py" ) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module.command() def run_command(name, content, cursor_x=0, cursor_y=0): output_manager = Mock() settings_manager = Mock() settings_manager.get_setting_as_bool.return_value = True env = { "punctuation": {"PUNCTDICT": {" ": "space"}}, "screen": { "newCursorReview": {"x": cursor_x, "y": cursor_y}, "new_content_text": content, }, "runtime": { "CursorManager": Mock(), "OutputManager": output_manager, "SettingsManager": settings_manager, }, } command = load_command(name) command.initialize(env) command.run() return output_manager def load_bindings(layout): manager = InputManager() manager.env = { "bindings": {}, "rawBindings": {}, "runtime": {"DebugManager": Mock()}, } manager.load_shortcuts(KEYBOARD_DIR / f"{layout}.conf") return manager.env["bindings"] @pytest.mark.unit def test_current_word_is_spelled_one_character_at_a_time(): output_manager = run_command( "review_curr_word_spell", "hello world", cursor_x=2 ) calls = output_manager.present_text.call_args_list assert [call.args[0] for call in calls] == list("hello") assert calls[0].kwargs == { "interrupt": True, "ignore_punctuation": True, "announce_capital": True, "flush": False, } assert all(call.kwargs["interrupt"] is False for call in calls[1:]) @pytest.mark.unit def test_current_word_spelling_preserves_capitals_and_punctuation(): output_manager = run_command("review_curr_word_spell", "Hi!", cursor_x=1) assert [ call.args[0] for call in output_manager.present_text.call_args_list ] == ["H", "i", "!"] @pytest.mark.unit @pytest.mark.parametrize( "command_name", ["review_curr_word_spell", "review_curr_word_phonetic"], ) def test_current_word_spelling_announces_blank_when_no_word_exists( command_name, ): output_manager = run_command(command_name, " ") output_manager.present_text.assert_called_once_with( "blank", interrupt=True, flush=False ) @pytest.mark.unit def test_current_word_phonetic_spelling_uses_phonetic_names(): output_manager = run_command( "review_curr_word_phonetic", "Az!", cursor_x=1 ) calls = output_manager.present_text.call_args_list assert [call.args[0] for call in calls] == ["Alpha", "zulu", "!"] assert calls[0].kwargs["interrupt"] is True assert all(call.kwargs["interrupt"] is False for call in calls[1:]) @pytest.mark.unit @pytest.mark.parametrize( ("layout", "character_keys", "word_keys"), [ ("desktop", ["KEY_KP2"], ["KEY_KP5"]), ("laptop", ["KEY_COMMA", "KEY_FENRIR"], ["KEY_FENRIR", "KEY_K"]), ], ) def test_progressive_review_bindings(layout, character_keys, word_keys): bindings = load_bindings(layout) assert bindings[str([1, character_keys])] == "REVIEW_CURR_CHAR" assert bindings[str([2, character_keys])] == "REVIEW_CURR_CHAR_PHONETIC" assert bindings[str([1, word_keys])] == "REVIEW_CURR_WORD" assert bindings[str([2, word_keys])] == "REVIEW_CURR_WORD_SPELL" assert bindings[str([3, word_keys])] == "REVIEW_CURR_WORD_PHONETIC" @pytest.mark.unit @pytest.mark.parametrize("layout", ["desktop", "laptop"]) def test_layouts_do_not_bind_removed_keyboard_layout_cycle(layout): bindings = load_bindings(layout) assert "CYCLE_KEYBOARD_LAYOUT" not in bindings.values() removed_commands = { "REVIEW_PREV_CHAR_PHONETIC", "REVIEW_NEXT_CHAR_PHONETIC", "REVIEW_PREV_WORD_PHONETIC", "REVIEW_NEXT_WORD_PHONETIC", } assert removed_commands.isdisjoint(bindings.values()) @pytest.mark.unit def test_obsolete_phonetic_navigation_commands_are_removed(): removed_commands = [ "review_prev_char_phonetic.py", "review_next_char_phonetic.py", "review_prev_word_phonetic.py", "review_next_word_phonetic.py", ] assert all(not (COMMANDS_DIR / name).exists() for name in removed_commands)