fix(settings): gray out Dictation Tone while sound effects are off (#877)
* fix(settings): gray out Dictation Tone while sound effects are off The Enable Sound Effects switch mutes every dictation cue, so the tone picker changed nothing while it was off. The row now goes insensitive with the switch and follows the state loaded from config, matching the keep-alive timeout and auto-pause sensitivity pattern. Also bump the pre-commit isort pin 5.13.2 -> 8.0.1 to match uv.lock: the two versions disagree on single-name import wrapping, so the old hook collapsed imports the locked isort (and CI) then expanded back. Fixes #849 * test(settings): cover tone-row sensitivity on settings load --------- Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
This commit is contained in:
co-authored by
Jatin K Malik
parent
77def71354
commit
d29c884782
@@ -12,7 +12,7 @@ repos:
|
||||
|
||||
# Import sorting with isort (black-compatible)
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.13.2
|
||||
rev: 8.0.1
|
||||
hooks:
|
||||
- id: isort
|
||||
args: ["--profile", "black"]
|
||||
|
||||
@@ -3524,11 +3524,16 @@ class SettingsDialog(Gtk.Dialog):
|
||||
logger.info(f"Paste shortcut set to {self.config_manager.get_paste_shortcut()}")
|
||||
return False
|
||||
|
||||
def _update_sound_effects_sensitivity(self, enabled: bool) -> None:
|
||||
"""Gray out the tone picker while sound effects are disabled."""
|
||||
self.tone_row.set_sensitive(enabled)
|
||||
|
||||
def _on_sound_effects_toggled(self, widget, state):
|
||||
enabled = bool(state)
|
||||
self._update_sound_effects_sensitivity(enabled)
|
||||
if self._initializing or self._applying_settings:
|
||||
return False
|
||||
|
||||
enabled = bool(state)
|
||||
logger.info(f"Sound effects toggled: {enabled}")
|
||||
self.config_manager.set_sound_effects_enabled(enabled)
|
||||
self.config_manager.save_settings()
|
||||
@@ -5994,7 +5999,9 @@ class SettingsDialog(Gtk.Dialog):
|
||||
self.append_trailing_space_switch.set_active(append_trailing_space)
|
||||
if not self.paste_shortcut_combo.set_active_id(paste_shortcut):
|
||||
self.paste_shortcut_combo.set_active_id(DEFAULT_PASTE_SHORTCUT)
|
||||
self.sound_effects_switch.set_active(self.config_manager.is_sound_effects_enabled())
|
||||
sound_effects_enabled = self.config_manager.is_sound_effects_enabled()
|
||||
self.sound_effects_switch.set_active(sound_effects_enabled)
|
||||
self._update_sound_effects_sensitivity(sound_effects_enabled)
|
||||
duck_enabled = self.config_manager.is_playback_duck_enabled()
|
||||
self.duck_level_scale.set_value(self.config_manager.get_playback_duck_percent())
|
||||
self.duck_playback_switch.set_active(duck_enabled)
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user