Import the vocalinux source tree (tray app, Next.js site in web/, packaging, installers, docs and tests) on top of the Gitea template commit. The project README replaces the template README, and the template CI files (.gitea/workflows/ci.yml, .jscpd.json, CI.md) are kept.
98 lines
3.6 KiB
Python
98 lines
3.6 KiB
Python
"""The Dictation Tone row is grayed out while sound effects are off (#849)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import sys
|
|
from collections.abc import Iterator
|
|
from typing import Any
|
|
from unittest.mock import MagicMock, Mock, patch
|
|
|
|
import pytest
|
|
|
|
import vocalinux.ui
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def settings_dialog() -> Iterator[Any]:
|
|
"""Import settings_dialog with real base classes for its GTK subclasses.
|
|
|
|
Same approach as tests/test_follow_keyboard_layout.py; both sys.modules and
|
|
the package attribute are restored so later tests patching the module by
|
|
name do not reach a second module object (#686).
|
|
"""
|
|
repository = sys.modules["gi.repository"]
|
|
bases = {name: type(name, (), {}) for name in ("Box", "ListBoxRow", "Dialog")}
|
|
saved_module = sys.modules.pop("vocalinux.ui.settings_dialog", None)
|
|
saved_attribute = getattr(vocalinux.ui, "settings_dialog", None)
|
|
try:
|
|
with patch.object(repository, "Gtk", MagicMock(**bases)):
|
|
module = importlib.import_module("vocalinux.ui.settings_dialog")
|
|
yield module
|
|
finally:
|
|
sys.modules.pop("vocalinux.ui.settings_dialog", None)
|
|
if saved_module is not None:
|
|
sys.modules["vocalinux.ui.settings_dialog"] = saved_module
|
|
if saved_attribute is not None:
|
|
vocalinux.ui.settings_dialog = saved_attribute
|
|
elif hasattr(vocalinux.ui, "settings_dialog"):
|
|
del vocalinux.ui.settings_dialog
|
|
|
|
|
|
@pytest.fixture
|
|
def dialog_class(settings_dialog: Any) -> type[Any]:
|
|
return settings_dialog.SettingsDialog
|
|
|
|
|
|
def _dialog_stub(dialog_class: type[Any], initializing: bool = False) -> Mock:
|
|
"""A Mock dialog whose self-calls reach the real sensitivity helper."""
|
|
dialog = Mock()
|
|
dialog._initializing = initializing
|
|
dialog._applying_settings = False
|
|
dialog._update_sound_effects_sensitivity = (
|
|
lambda enabled: dialog_class._update_sound_effects_sensitivity(dialog, enabled)
|
|
)
|
|
return dialog
|
|
|
|
|
|
@pytest.mark.parametrize("enabled", [True, False])
|
|
def test_toggling_sound_effects_sets_tone_row_sensitivity(
|
|
dialog_class: type[Any], enabled: bool
|
|
) -> None:
|
|
dialog = _dialog_stub(dialog_class)
|
|
|
|
dialog_class._on_sound_effects_toggled(dialog, Mock(), enabled)
|
|
|
|
dialog.tone_row.set_sensitive.assert_called_once_with(enabled)
|
|
dialog.config_manager.set_sound_effects_enabled.assert_called_once_with(enabled)
|
|
|
|
|
|
def test_sensitivity_still_updates_while_settings_load(dialog_class: type[Any]) -> None:
|
|
"""set_active() during load must sync the row even though saving is skipped."""
|
|
dialog = _dialog_stub(dialog_class, initializing=True)
|
|
|
|
dialog_class._on_sound_effects_toggled(dialog, Mock(), False)
|
|
|
|
dialog.tone_row.set_sensitive.assert_called_once_with(False)
|
|
dialog.config_manager.set_sound_effects_enabled.assert_not_called()
|
|
|
|
|
|
def test_loading_settings_syncs_tone_row_sensitivity(
|
|
settings_dialog: Any, dialog_class: type[Any]
|
|
) -> None:
|
|
"""A saved sound-effects=off state must gray out the tone row on load."""
|
|
dialog = _dialog_stub(dialog_class)
|
|
dialog._get_current_settings.return_value = {
|
|
"engine": "whisper_cpp",
|
|
"language": "en",
|
|
"model_size": "base",
|
|
}
|
|
dialog.config_manager.get_settings.return_value = {}
|
|
dialog.config_manager.is_sound_effects_enabled.return_value = False
|
|
dialog.engine_combo.get_model.return_value = [["whisper.cpp"]]
|
|
|
|
with patch.object(settings_dialog, "get_available_engines", return_value={"whisper_cpp": True}):
|
|
dialog_class._load_and_apply_settings(dialog)
|
|
|
|
dialog.tone_row.set_sensitive.assert_called_once_with(False)
|