from unittest.mock import Mock import pytest from fenrirscreenreader.core.speechHistoryManager import SpeechHistoryManager def build_speech_history_manager(history_size=3): spoken_messages = [] output_manager = Mock() def _capture_message(message, **_kwargs): spoken_messages.append(message) output_manager.present_text.side_effect = _capture_message settings_manager = Mock() settings_manager.get_setting_as_int.return_value = history_size memory_manager = Mock(add_value_to_first_index=Mock()) input_manager = Mock(reset_input_state=Mock()) input_driver = Mock(refresh_grabs=Mock()) env = { "runtime": { "OutputManager": output_manager, "SettingsManager": settings_manager, "MemoryManager": memory_manager, "InputManager": input_manager, "InputDriver": input_driver, }, "bindings": {"original": "COMMAND"}, "rawBindings": {"original": [1, ["KEY_FENRIR"]]}, } manager = SpeechHistoryManager() manager.initialize(env) return manager, env, spoken_messages, memory_manager @pytest.mark.unit def test_speech_history_keeps_configured_number_of_items(): manager, _env, _spoken_messages, _memory_manager = ( build_speech_history_manager(history_size=2) ) assert manager.add_text("one") assert manager.add_text("two") assert manager.add_text("three") assert manager.history == ["three", "two"] @pytest.mark.unit def test_speech_history_suppresses_exact_duplicates_until_item_drops(): manager, _env, _spoken_messages, _memory_manager = ( build_speech_history_manager(history_size=2) ) assert manager.add_text("hello world") assert not manager.add_text("hello world") assert manager.add_text("other") assert manager.add_text("third") assert manager.add_text("hello world") assert manager.history == ["hello world", "third"] @pytest.mark.unit def test_speech_history_dedupe_keeps_case_and_suppresses_whitespace_variants(): manager, _env, _spoken_messages, _memory_manager = ( build_speech_history_manager() ) assert manager.add_text("hello") assert manager.add_text("Hello") assert not manager.add_text("hello ") assert not manager.add_text("hello ") assert manager.history == ["Hello", "hello"] @pytest.mark.unit def test_open_empty_history_announces_empty_without_modal_bindings(): manager, env, spoken_messages, _memory_manager = ( build_speech_history_manager() ) assert not manager.open_history() assert not manager.is_active() assert spoken_messages == ["speech history empty"] assert env["bindings"] == {"original": "COMMAND"} @pytest.mark.unit def test_open_history_installs_modal_bindings_and_replay_is_not_recorded(): manager, env, spoken_messages, _memory_manager = ( build_speech_history_manager() ) env["rawBindings"]["ctrl_shut_up"] = [1, ["KEY_CTRL"]] manager.add_text("first") manager.add_text("second") assert manager.open_history() manager.add_text("replayed") assert manager.is_active() assert spoken_messages == ["Speech history"] assert manager.curr_index == -1 assert manager.history == ["second", "first"] assert "original" not in env["bindings"] assert env["rawBindings"]["original"] == [1, ["KEY_FENRIR"]] assert env["rawBindings"]["ctrl_shut_up"] == [1, ["KEY_CTRL"]] assert env["bindings"][str([1, ["KEY_UP"]])] == "SPEECH_HISTORY_PREV" assert env["bindings"][str([1, ["KEY_ENTER"]])] == "SPEECH_HISTORY_COPY" assert env["bindings"][str([1, ["KEY_ESC"]])] == "SPEECH_HISTORY_CLOSE" assert env["rawBindings"][str([1, ["KEY_UP"]])] == [1, ["KEY_UP"]] input_driver = env["runtime"]["InputDriver"] input_driver.refresh_grabs.assert_called_once_with(force=True) @pytest.mark.unit def test_navigation_moves_between_newer_and_older_items(): manager, _env, spoken_messages, _memory_manager = ( build_speech_history_manager() ) manager.add_text("oldest") manager.add_text("middle") manager.add_text("newest") manager.open_history() manager.prev_entry() manager.prev_entry() manager.prev_entry() manager.next_entry() assert spoken_messages[-4:] == ["newest", "middle", "oldest", "middle"] @pytest.mark.unit def test_copy_current_adds_clipboard_and_restores_bindings(): manager, env, spoken_messages, memory_manager = ( build_speech_history_manager() ) manager.add_text("first") manager.add_text("second") manager.open_history() manager.prev_entry() manager.prev_entry() manager.copy_current_to_clipboard() memory_manager.add_value_to_first_index.assert_called_once_with( "clipboardHistory", "first" ) assert spoken_messages[-1] == "copied to clipboard" assert not manager.is_active() assert env["bindings"] == {"original": "COMMAND"} assert env["rawBindings"] == {"original": [1, ["KEY_FENRIR"]]} env["runtime"]["InputManager"].reset_input_state.assert_called_once_with() assert env["runtime"]["InputDriver"].refresh_grabs.call_count == 2 @pytest.mark.unit def test_close_history_restores_keyboard_state_and_grabs(): manager, env, _spoken_messages, _memory_manager = ( build_speech_history_manager() ) manager.add_text("first") manager.open_history() manager.close_history() assert not manager.is_active() assert env["bindings"] == {"original": "COMMAND"} assert env["rawBindings"] == {"original": [1, ["KEY_FENRIR"]]} env["runtime"]["InputManager"].reset_input_state.assert_called_once_with() assert env["runtime"]["InputDriver"].refresh_grabs.call_count == 2