* fix(settings): make model downloads explicit and cancel the whole intent "Use it" on the recommendation card applied a different model than the one shown, a cancelled or failed apply left the staged model behind so the next Test Dictation downloaded it, and cancelling a download tore down and then reloaded the previous model while the dialog waited. - Recommendation is now passive text, compared by size tier, with a link to docs/MODEL_SELECTION.md. The "Use it" button is gone. - Every Settings download asks first and shows size and source host. Declining, cancelling or failing drops the staged model spec and puts the pickers back on the saved config. - Test Dictation refuses a model that is not on disk instead of applying it. - reconfigure() fetches a missing model before releasing the loaded one, so a cancel restores state without reloading or rehashing the old model. Cancellation state is reset only when a download claim is taken or ended. - Follow-layout mode shows the resolved language in the simple Main language control, read-only, and simple edits keep the "layout" setting. Fixes #979 Fixes #980 Fixes #981 Fixes #982 Fixes #985 Fixes #988 * fix(settings): restore follow-layout mode when a model change is declined * fix(settings): keep follow-layout and dictation consistent during model changes * style(tests): format follow-layout regression tests * fix(recognition): skip resume reinit while a model download is running * fix(recognition): keep a lazy reload pending when resume is skipped during a download --------- Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
136 lines
4.8 KiB
Python
136 lines
4.8 KiB
Python
"""Model recommendations are informational and never change the selection."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import inspect
|
|
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 concrete GTK base classes."""
|
|
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) -> Any:
|
|
return settings_dialog.SettingsDialog
|
|
|
|
|
|
def _dialog_stub() -> Mock:
|
|
dialog = Mock()
|
|
dialog.engine_combo.get_active_text.return_value = "whisper.cpp"
|
|
dialog.model_variant_combo.get_active_id.return_value = "small.en"
|
|
dialog.model_combo.get_active_id.return_value = "small"
|
|
dialog.language_combo.get_active_id.return_value = "en-us"
|
|
dialog._get_selected_whispercpp_model.return_value = "small.en"
|
|
dialog._get_recommended_whispercpp_model_for_language.return_value = (
|
|
"small",
|
|
"8GB RAM - good balance",
|
|
)
|
|
dialog._refresh_simple_readout = Mock()
|
|
return dialog
|
|
|
|
|
|
def test_recommendation_has_no_action_button(settings_dialog: Any, dialog_class: Any) -> None:
|
|
source = inspect.getsource(dialog_class.__init__)
|
|
|
|
assert "Use it" not in source
|
|
assert "model_recommendation_button" not in source
|
|
|
|
|
|
def test_same_size_whispercpp_variant_is_not_shown(settings_dialog: Any, dialog_class: Any) -> None:
|
|
dialog = _dialog_stub()
|
|
|
|
with patch.object(settings_dialog, "is_whispercpp_model_downloaded", return_value=True):
|
|
dialog_class._update_model_info(dialog)
|
|
|
|
dialog.model_recommendation_box.hide.assert_called_once_with()
|
|
dialog.model_recommendation.set_text.assert_not_called()
|
|
|
|
|
|
def test_different_size_recommendation_shows_size_and_reason(
|
|
settings_dialog: Any, dialog_class: Any
|
|
) -> None:
|
|
dialog = _dialog_stub()
|
|
dialog._get_selected_whispercpp_model.return_value = "tiny"
|
|
|
|
with patch.object(settings_dialog, "is_whispercpp_model_downloaded", return_value=True):
|
|
dialog_class._update_model_info(dialog)
|
|
|
|
message = dialog.model_recommendation.set_text.call_args.args[0]
|
|
assert message.startswith("Suggested for this computer: Small")
|
|
assert "MB" in message
|
|
assert "8GB RAM - good balance" in message
|
|
dialog.model_recommendation_box.show.assert_called_once_with()
|
|
|
|
|
|
def test_other_engine_recommendation_uses_recommended_model_size(
|
|
settings_dialog: Any, dialog_class: Any
|
|
) -> None:
|
|
dialog = _dialog_stub()
|
|
dialog.engine_combo.get_active_text.return_value = "Vosk"
|
|
dialog.model_combo.get_active_id.return_value = "small"
|
|
dialog.language = "en-us"
|
|
|
|
with (
|
|
patch.object(
|
|
settings_dialog,
|
|
"VOSK_MODEL_INFO",
|
|
{
|
|
"small": {"size_mb": 50, "desc": "Selected model"},
|
|
"large": {"size_mb": 250, "desc": "Recommended model"},
|
|
},
|
|
),
|
|
patch.object(settings_dialog, "_is_vosk_model_downloaded", return_value=True),
|
|
patch.object(settings_dialog, "_get_recommended_vosk_model", return_value=("large", "RAM")),
|
|
):
|
|
dialog_class._update_model_info(dialog)
|
|
|
|
assert "(250 MB, RAM)" in dialog.model_recommendation.set_text.call_args.args[0]
|
|
|
|
|
|
def test_recommendation_does_not_change_picker_or_config_state(
|
|
settings_dialog: Any, dialog_class: Any
|
|
) -> None:
|
|
dialog = _dialog_stub()
|
|
dialog.config_manager = Mock()
|
|
|
|
with patch.object(settings_dialog, "is_whispercpp_model_downloaded", return_value=True):
|
|
dialog_class._update_model_info(dialog)
|
|
|
|
dialog.model_combo.set_active_id.assert_not_called()
|
|
dialog.model_variant_combo.set_active_id.assert_not_called()
|
|
dialog.config_manager.set.assert_not_called()
|
|
dialog.config_manager.save_settings.assert_not_called()
|
|
|
|
|
|
def test_model_selection_docs_url_is_stable(settings_dialog: Any) -> None:
|
|
assert (
|
|
settings_dialog.MODEL_SELECTION_DOC_URL
|
|
== "https://github.com/VocaHQ/vocalinux/blob/main/docs/MODEL_SELECTION.md"
|
|
)
|