mirror of
https://github.com/yusufipk/dikte.git
synced 2026-09-11 10:56:10 +00:00
Redesign desktop workspace with compact task views and themes
This commit is contained in:
+7
-3
@@ -148,8 +148,8 @@ class Parser(unittest.TestCase):
|
||||
def parse(self, *argv):
|
||||
return cli.build_parser().parse_args(list(argv))
|
||||
|
||||
def test_no_verb_at_all_is_the_settings_window(self):
|
||||
# argparse leaves the dest as None; run() is what turns it into "".
|
||||
def test_no_verb_uses_the_plain_gui_command(self):
|
||||
# argparse leaves the dest as None; run() selects home.
|
||||
opts = self.parse()
|
||||
self.assertIsNone(opts.verb)
|
||||
self.assertEqual(opts.func, cli.cmd_plain)
|
||||
@@ -700,10 +700,14 @@ class WithoutAnInstance(DikteTest):
|
||||
launch.assert_called_once_with("toggle")
|
||||
|
||||
def test_every_verb_that_opens_a_window_can_start_it(self):
|
||||
for verb in ("settings", "toggle", "ask", "meeting"):
|
||||
for verb in ("home", "settings", "toggle", "ask", "meeting"):
|
||||
with self.subTest(verb=verb):
|
||||
self.assertTrue(self.run_verb([verb])[3].called)
|
||||
|
||||
def test_bare_command_opens_the_daily_workspace(self):
|
||||
_, _, _, launch = self.run_verb([])
|
||||
launch.assert_called_once_with("home")
|
||||
|
||||
def test_a_verb_asked_to_wait_starts_nothing(self):
|
||||
"""There would be no run to wait for; the process would just be replaced."""
|
||||
_, _, _, launch = self.run_verb(["toggle", "--wait"])
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
"""Native workspace navigation, capture boundaries and persisted results."""
|
||||
|
||||
from types import SimpleNamespace
|
||||
from unittest import mock
|
||||
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtWidgets import QApplication, QMessageBox
|
||||
|
||||
from dikte import config as cfg, home_ui, i18n
|
||||
from dikte.app import Dikte
|
||||
from tests import test_ui
|
||||
from tests.support import DikteTest
|
||||
|
||||
|
||||
class Home(DikteTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.fixture = test_ui.Settings("runTest")
|
||||
self.fixture.setUp()
|
||||
self.addCleanup(self.fixture.doCleanups)
|
||||
self.conf = self.fixture.config(transcribe_provider="openai", openai_api_key="test",
|
||||
cleanup_enabled=False)
|
||||
self.settings = self.fixture.window(self.conf)
|
||||
self.controller = SimpleNamespace(
|
||||
conf=self.conf, state="idle", ask_state="idle", meeting_state="idle",
|
||||
meeting_message="", home_messages={}, recording=False, paused=False,
|
||||
paste_override={}, open_settings=mock.Mock(), reset_conversation=mock.Mock(),
|
||||
_recorded_seconds=lambda: 65, meeting_elapsed=SimpleNamespace(elapsed=lambda: 90000),
|
||||
)
|
||||
for name in ("start", "stop", "start_ask", "stop_ask", "_toggle_pause", "_cancel",
|
||||
"cancel_ask", "_toggle_meeting", "cancel_meeting"):
|
||||
setattr(self.controller, name, mock.Mock())
|
||||
self.window = home_ui.HomeWindow(self.controller, self.settings)
|
||||
self.addCleanup(self.window.deleteLater)
|
||||
self.addCleanup(self.window.close)
|
||||
self.window.show()
|
||||
QApplication.processEvents()
|
||||
|
||||
def test_daily_tasks_are_reachable_outside_configuration(self):
|
||||
for mode in ("dictation", "file", "meeting", "ask", "history"):
|
||||
self.window.show_mode(mode)
|
||||
QApplication.processEvents()
|
||||
self.assertEqual(self.window.pages.currentWidget(), self.window.mode_pages[mode])
|
||||
self.window.show_mode("meeting")
|
||||
self.assertTrue(self.settings.minutes_view.isVisible())
|
||||
self.window.show_mode("history")
|
||||
self.assertTrue(self.settings.history.isVisible())
|
||||
self.assertEqual(self.settings.tabs.count(), 7)
|
||||
self.assertFalse(self.settings.tabs.tabBar().isVisible())
|
||||
|
||||
def test_native_chrome_and_capture_geometry(self):
|
||||
self.assertFalse(self.window.windowFlags() & Qt.WindowType.FramelessWindowHint)
|
||||
self.assertEqual(self.window.capture_button.width(), self.window.capture_button.height())
|
||||
self.assertGreaterEqual(self.window.capture_button.height(), 100)
|
||||
|
||||
def test_theme_preview_discard_and_save_keep_runtime_separate(self):
|
||||
self.assertEqual(self.conf["theme"], "nord")
|
||||
self.settings.theme_choice.setCurrentIndex(self.settings.theme_choice.findData("light"))
|
||||
self.assertEqual(self.conf["theme"], "nord")
|
||||
self.assertIn("#FFFFFF", self.settings.styleSheet())
|
||||
self.assertNotEqual(self.window.styleSheet(), self.settings.styleSheet())
|
||||
self.settings._discard_changes()
|
||||
self.assertEqual(self.settings.theme_choice.currentData(), "nord")
|
||||
self.settings.theme_choice.setCurrentIndex(self.settings.theme_choice.findData("dracula"))
|
||||
self.settings._save()
|
||||
self.assertEqual(cfg.Config()["theme"], "dracula")
|
||||
self.assertEqual(self.window._theme_name, "dracula")
|
||||
self.assertIn("#282A36", self.window.styleSheet())
|
||||
|
||||
def test_theme_colors_are_per_overlay_and_unknown_name_falls_back(self):
|
||||
from dikte import overlay, theme
|
||||
dark = overlay.Overlay(theme_name="dark")
|
||||
light = overlay.Overlay(theme_name="light")
|
||||
self.addCleanup(dark.deleteLater)
|
||||
self.addCleanup(light.deleteLater)
|
||||
self.assertEqual(light.colors["base"].name(), "#ffffff")
|
||||
self.assertEqual(dark.colors["base"].name(), "#101010")
|
||||
dark.set_theme("dracula")
|
||||
self.assertEqual(light.colors["base"].name(), "#ffffff")
|
||||
self.assertEqual(theme.stylesheet("unknown"), theme.stylesheet("nord"))
|
||||
|
||||
def test_wide_meeting_page_keeps_actions_compact(self):
|
||||
self.window.resize(900, 700)
|
||||
self.window.show_mode("meeting")
|
||||
QApplication.processEvents()
|
||||
self.assertLess(self.window.meeting_button.width(), 300)
|
||||
self.assertLessEqual(self.settings.minutes_view.width(), 680)
|
||||
|
||||
def test_empty_state_does_not_invent_a_transcript(self):
|
||||
self.assertEqual(self.window.latest_text.toPlainText(), "")
|
||||
self.assertFalse(self.window.copy_button.isEnabled())
|
||||
self.assertFalse(self.window.open_button.isEnabled())
|
||||
|
||||
def test_clearing_history_removes_the_latest_preview(self):
|
||||
cfg.append_history({"text": "Remove this preview"})
|
||||
self.window.refresh_results()
|
||||
self.window.show_mode("history")
|
||||
with mock.patch.object(self.settings, "_confirm", return_value=True):
|
||||
self.settings._clear_history()
|
||||
self.window.show_mode("dictation")
|
||||
self.assertEqual(self.window.latest_text.toPlainText(), "")
|
||||
self.assertFalse(self.window.copy_button.isEnabled())
|
||||
|
||||
def test_missing_local_model_opens_setup_without_recording(self):
|
||||
self.conf["transcribe_provider"] = "local"
|
||||
self.window.refresh()
|
||||
self.assertEqual(self.window.capture_status.text(), "Set up transcription")
|
||||
self.window._capture()
|
||||
self.controller.start.assert_not_called()
|
||||
self.controller.open_settings.assert_called_once()
|
||||
self.assertEqual(self.settings.tabs.currentIndex(), self.settings.api_tab_index)
|
||||
self.assertEqual(self.controller.paste_override, {})
|
||||
|
||||
def test_button_capture_never_automatically_pastes(self):
|
||||
def start():
|
||||
self.assertIs(self.controller.paste_override["dictation"], False)
|
||||
self.controller.state = "recording"
|
||||
self.controller.recording = True
|
||||
self.controller.start.side_effect = start
|
||||
self.window._capture()
|
||||
self.controller.start.assert_called_once()
|
||||
self.assertFalse(self.controller.paste_override["dictation"])
|
||||
self.assertIn("01:05", self.window.capture_status.text())
|
||||
self.assertTrue(self.window.pause_button.isVisible())
|
||||
self.window._capture()
|
||||
self.controller.stop.assert_called_once()
|
||||
|
||||
def test_failed_capture_does_not_leak_a_paste_override(self):
|
||||
self.window._capture()
|
||||
self.assertEqual(self.controller.paste_override, {})
|
||||
|
||||
def test_busy_capture_can_queue_but_does_not_steal_assistant_microphone(self):
|
||||
self.controller.state = "busy"
|
||||
self.window.refresh()
|
||||
self.assertTrue(self.window.capture_button.isEnabled())
|
||||
self.controller.ask_state = "recording"
|
||||
self.controller.recording = True
|
||||
self.window.refresh()
|
||||
self.assertFalse(self.window.capture_button.isEnabled())
|
||||
self.window._capture()
|
||||
self.controller.start.assert_not_called()
|
||||
|
||||
def test_pause_cancel_and_failures_are_visible(self):
|
||||
self.controller.state = "recording"
|
||||
self.controller.recording = True
|
||||
self.controller.paused = True
|
||||
self.window.refresh()
|
||||
self.assertIn("Paused", self.window.capture_status.text())
|
||||
self.window._pause()
|
||||
self.controller._toggle_pause.assert_called_once()
|
||||
self.window._cancel_capture()
|
||||
self.controller._cancel.assert_called_once()
|
||||
self.controller.home_messages["dictation"] = "Microphone permission denied"
|
||||
self.window.refresh()
|
||||
self.assertIn("permission denied", self.window.capture_error.text())
|
||||
|
||||
def test_real_latest_dictation_is_separate_from_assistant_answer(self):
|
||||
cfg.append_history({"ts": "2026-09-09 12:00:00", "text": "Actual transcript"})
|
||||
cfg.append_history({"mode": "ask", "text": "Actual answer"})
|
||||
self.window.refresh_results()
|
||||
self.assertEqual(self.window.latest_text.toPlainText(), "Actual transcript")
|
||||
self.assertEqual(self.window.ask_output.toPlainText(), "Actual answer")
|
||||
cursor = self.window.latest_text.textCursor()
|
||||
cursor.setPosition(3)
|
||||
self.window.latest_text.setTextCursor(cursor)
|
||||
self.window.refresh()
|
||||
self.window.refresh_results()
|
||||
self.assertEqual(self.window.latest_text.textCursor().position(), 3)
|
||||
cfg.clear_history()
|
||||
self.window.refresh_results()
|
||||
self.assertEqual(self.window.latest_text.toPlainText(), "")
|
||||
|
||||
def test_processing_summary_uses_full_models_and_actual_acceleration(self):
|
||||
self.conf["transcribe_provider"] = "local"
|
||||
self.conf["local_model"] = "ggml-large-v3-turbo-q5_0.bin"
|
||||
self.conf["cleanup_enabled"] = True
|
||||
self.conf["cleanup_provider"] = "local"
|
||||
self.conf["local_llm_model"] = "gemma-3-4b-it-Q4_K_M.gguf"
|
||||
self.conf["local_gpu"] = True
|
||||
with mock.patch.object(home_ui.ggml, "state", return_value={
|
||||
"whisper": {"running": True, "backend": "CPU"},
|
||||
"llama": {"running": True, "backend": "Vulkan"},
|
||||
}):
|
||||
text = home_ui.processing_locations(self.conf)
|
||||
self.assertIn("ggml-large-v3-turbo-q5_0.bin (Local CPU)", text)
|
||||
self.assertIn("gemma-3-4b-it-Q4_K_M.gguf (Local GPU)", text)
|
||||
self.assertNotIn("API", text)
|
||||
with mock.patch.object(home_ui.ggml, "state", return_value={}):
|
||||
text = home_ui.processing_locations(self.conf)
|
||||
self.assertNotIn("GPU", text)
|
||||
self.assertIn("(Local)", text)
|
||||
self.assertIn(self.conf["meeting_model"], home_ui.processing_locations(self.conf, "meeting"))
|
||||
self.conf["assistant_cleanup"] = True
|
||||
self.conf["cleanup_provider"] = "gemini"
|
||||
self.conf["assistant_provider"] = "codex"
|
||||
text = home_ui.processing_locations(self.conf, "ask")
|
||||
self.assertIn(self.conf["cleanup_gemini_model"] + " (API)", text)
|
||||
self.assertIn("Codex default model (CLI)", text)
|
||||
|
||||
def test_timestamped_file_summary_uses_the_timestamp_model(self):
|
||||
self.conf["transcribe_provider"] = "openrouter"
|
||||
self.conf["openrouter_transcribe_model"] = "google/gemini-audio"
|
||||
self.conf["openrouter_file_model"] = "openai/whisper-1"
|
||||
text = home_ui.processing_locations(self.conf, "file", file_timestamps=True)
|
||||
self.assertIn("openai/whisper-1 (API)", text)
|
||||
self.assertNotIn("google/gemini-audio", text)
|
||||
|
||||
def test_meeting_summary_uses_segment_model_even_without_file_timestamps(self):
|
||||
self.conf["transcribe_provider"] = "openai"
|
||||
self.conf["transcribe_model"] = "gpt-4o-transcribe"
|
||||
text = home_ui.processing_locations(self.conf, "meeting", file_timestamps=False)
|
||||
self.assertIn("whisper-1 (API)", text)
|
||||
self.assertNotIn("gpt-4o-transcribe", text)
|
||||
self.conf["transcribe_provider"] = "openrouter"
|
||||
self.conf["openrouter_file_model"] = "mistralai/voxtral-small-24b-2507"
|
||||
text = home_ui.processing_locations(self.conf, "meeting", file_timestamps=False)
|
||||
self.assertIn("mistralai/voxtral-small-24b-2507 (API)", text)
|
||||
|
||||
def test_unsupported_meeting_is_disabled(self):
|
||||
self.enterContext(mock.patch.object(home_ui.audio, "sound", return_value=SimpleNamespace(meetings=False)))
|
||||
self.window.show_mode("meeting")
|
||||
self.assertFalse(self.window.meeting_button.isEnabled())
|
||||
self.assertIn("not supported", self.window.meeting_hint.text())
|
||||
self.window._meeting()
|
||||
self.controller._toggle_meeting.assert_not_called()
|
||||
|
||||
def test_assistant_scope_uses_actual_shortcut_and_permissions(self):
|
||||
self.conf["assistant_provider"] = "codex"
|
||||
self.conf["assistant_shortcut"] = "Ctrl+Alt+A"
|
||||
self.conf["assistant_dir"] = self.root
|
||||
self.conf["assistant_codex_sandbox"] = "danger-full-access"
|
||||
self.window.show_mode("ask")
|
||||
self.assertIn("Ctrl+Alt+A", self.window.ask_scope.text())
|
||||
self.assertIn(self.root, self.window.ask_scope.text())
|
||||
self.assertIn("No sandbox at all", self.window.ask_scope.text())
|
||||
self.assertEqual(self.window.ask_button.text(), "Set up assistant")
|
||||
self.window._ask()
|
||||
self.controller.start_ask.assert_not_called()
|
||||
self.controller.open_settings.assert_called_once()
|
||||
|
||||
def test_failed_settings_save_keeps_runtime_config_and_form_edits(self):
|
||||
before = dict(self.conf.data)
|
||||
self.settings.auto_paste.setChecked(not self.conf["auto_paste"])
|
||||
self.assertEqual(self.settings.dirty_label.text(), "Unsaved changes")
|
||||
with mock.patch.object(self.conf, "save", side_effect=OSError("disk full")), mock.patch.object(QMessageBox, "warning"):
|
||||
self.settings._save()
|
||||
self.assertEqual(self.conf.data, before)
|
||||
self.assertNotEqual(self.settings.auto_paste.isChecked(), self.conf["auto_paste"])
|
||||
self.settings.file_path = "/tmp/chosen.wav"
|
||||
self.settings._discard_changes()
|
||||
self.assertEqual(self.settings.dirty_label.text(), "")
|
||||
self.assertEqual(self.settings.file_path, "/tmp/chosen.wav")
|
||||
|
||||
def test_small_window_keeps_navigation_and_footer_accessible(self):
|
||||
self.window.resize(460, 460)
|
||||
QApplication.processEvents()
|
||||
for mode in ("dictation", "file", "meeting", "ask", "history"):
|
||||
self.window.show_mode(mode)
|
||||
QApplication.processEvents()
|
||||
if self.window.footer.isVisible():
|
||||
self.assertTrue(self.window.rect().contains(self.window.footer.geometry()))
|
||||
self.assertTrue(self.window.rect().contains(self.window.mode_buttons["dictation"].geometry().topLeft()))
|
||||
|
||||
def test_apply_merges_unrelated_cli_changes_and_keeps_user_edits(self):
|
||||
self.settings.auto_paste.setChecked(False)
|
||||
self.conf["shortcut"] = "Ctrl+Shift+F9"
|
||||
self.conf["groq_transcribe_model"] = "external-model"
|
||||
self.settings.refresh_configuration()
|
||||
self.assertFalse(self.settings.auto_paste.isChecked())
|
||||
self.settings._save()
|
||||
self.assertFalse(self.conf["auto_paste"])
|
||||
self.assertEqual(self.conf["shortcut"], "Ctrl+Shift+F9")
|
||||
self.assertEqual(self.conf["groq_transcribe_model"], "external-model")
|
||||
self.assertEqual(self.settings._shortcut_rows["toggle"][0].currentText(), "Ctrl+Shift+F9")
|
||||
self.assertEqual(self.settings.dirty_label.text(), "")
|
||||
|
||||
def test_clean_form_refreshes_from_cli_without_changing_file_result(self):
|
||||
self.settings.file_output.setPlainText("Existing file result")
|
||||
self.conf["shortcut"] = "Ctrl+Alt+F9"
|
||||
self.settings.refresh_configuration()
|
||||
self.assertEqual(self.settings._shortcut_rows["toggle"][0].currentText(), "Ctrl+Alt+F9")
|
||||
self.assertEqual(self.settings.file_output.toPlainText(), "Existing file result")
|
||||
self.assertEqual(self.settings.dirty_label.text(), "")
|
||||
|
||||
def test_cached_provider_model_edits_remain_dirty_after_switching_back(self):
|
||||
self.settings._select_data(self.settings.transcribe_provider, "groq")
|
||||
self.settings.transcribe_model.setCurrentText("my-groq-model")
|
||||
self.settings._select_data(self.settings.transcribe_provider, "openai")
|
||||
self.assertEqual(self.settings.dirty_label.text(), "Unsaved changes")
|
||||
self.settings._save()
|
||||
self.assertEqual(self.conf["groq_transcribe_model"], "my-groq-model")
|
||||
|
||||
def test_cli_selected_new_source_is_resolved_when_settings_reopens(self):
|
||||
self.conf["mic_target"] = "new-usb"
|
||||
self.settings.refresh_configuration()
|
||||
with mock.patch.object(home_ui.audio, "list_sources", return_value=[("new-usb", "New USB microphone")]), mock.patch.object(home_ui.audio, "list_monitors", return_value=[]):
|
||||
self.settings.refresh_sources()
|
||||
self.assertEqual(self.settings.mic.currentData(), "new-usb")
|
||||
self.assertEqual(self.settings.mic.currentText(), "New USB microphone")
|
||||
self.settings._save()
|
||||
self.assertEqual(self.conf["mic_target"], "new-usb")
|
||||
|
||||
def test_language_rebuild_preserves_file_result_and_active_mode(self):
|
||||
from dikte.meeting import MeetingPipeline
|
||||
controller = Dikte.__new__(Dikte)
|
||||
controller.__dict__.update(vars(self.controller))
|
||||
controller.meetings = MeetingPipeline(self.conf)
|
||||
controller._make_settings()
|
||||
old_settings = controller.settings_window
|
||||
old_home = home_ui.HomeWindow(controller, old_settings)
|
||||
controller.home_window = old_home
|
||||
old_home.show_mode("file")
|
||||
old_home.show()
|
||||
old_settings.file_path = "/tmp/chosen.wav"
|
||||
old_settings.file_label.setText("chosen.wav")
|
||||
old_settings.file_output.setPlainText("Retained transcript")
|
||||
old_settings.file_segments = [{"text": "Retained transcript", "start": 0, "end": 2}]
|
||||
i18n.set_language("tr")
|
||||
controller._reopen_settings()
|
||||
self.addCleanup(controller.settings_window.deleteLater)
|
||||
self.addCleanup(controller.settings_window.close)
|
||||
self.addCleanup(controller.home_window.deleteLater)
|
||||
self.addCleanup(controller.home_window.close)
|
||||
self.assertEqual(controller.home_window.mode, "file")
|
||||
self.assertEqual(controller.settings_window.file_path, "/tmp/chosen.wav")
|
||||
self.assertEqual(controller.settings_window.file_output.toPlainText(), "Retained transcript")
|
||||
self.assertTrue(controller.settings_window.file_save_srt.isEnabled())
|
||||
self.assertEqual(controller.home_window.mode_buttons["file"].text(), "Dosya")
|
||||
|
||||
def test_disconnected_configured_source_survives_an_unrelated_apply(self):
|
||||
self.conf["mic_target"] = "disconnected-usb"
|
||||
self.settings.refresh_configuration()
|
||||
self.assertEqual(self.settings.mic.currentData(), "disconnected-usb")
|
||||
self.settings.auto_paste.setChecked(False)
|
||||
self.settings._save()
|
||||
self.assertEqual(self.conf["mic_target"], "disconnected-usb")
|
||||
|
||||
def test_source_refresh_keeps_selection_and_discovers_hotplugged_devices(self):
|
||||
self.settings.mic.addItem("Old microphone", "old")
|
||||
self.settings.mic.setCurrentIndex(self.settings.mic.findData("old"))
|
||||
with mock.patch.object(home_ui.audio, "list_sources", return_value=[("usb", "USB microphone")]), mock.patch.object(home_ui.audio, "list_monitors", return_value=[("loop", "Loopback")]):
|
||||
self.settings.refresh_sources()
|
||||
self.assertEqual(self.settings.mic.currentData(), "old")
|
||||
self.assertGreaterEqual(self.settings.mic.findData("usb"), 0)
|
||||
self.assertGreaterEqual(self.settings.meeting_mic.findData("usb"), 0)
|
||||
self.assertGreaterEqual(self.settings.meeting_system.findData("loop"), 0)
|
||||
|
||||
def test_missing_assistant_directory_displays_the_actual_fallback(self):
|
||||
self.conf["assistant_provider"] = "codex"
|
||||
self.conf["assistant_dir"] = "/does/not/exist/dikte-test"
|
||||
self.window.show_mode("ask")
|
||||
self.assertNotIn(self.conf["assistant_dir"], self.window.ask_scope.text())
|
||||
self.assertIn(home_ui.assistant.working_dir(self.conf), self.window.ask_scope.text())
|
||||
|
||||
def test_turkish_task_labels_and_runtime_status(self):
|
||||
i18n.set_language("tr")
|
||||
self.window.refresh()
|
||||
self.assertEqual(self.window.capture_status.text(), "Konuşmaya hazır")
|
||||
self.assertIn("Ses:", self.window.capture_models.text())
|
||||
|
||||
def test_completed_run_refreshes_workspace_without_changing_controller_state(self):
|
||||
controller = Dikte.__new__(Dikte)
|
||||
controller.home_messages = {}
|
||||
controller.home_window = self.window
|
||||
controller._waiters = {}
|
||||
cfg.append_history({"text": "Finished"})
|
||||
controller._settle("dictation", {"ok": True, "text": "Finished"})
|
||||
self.assertEqual(self.window.latest_text.toPlainText(), "Finished")
|
||||
self.assertEqual(controller.home_messages["dictation"], "Transcript ready")
|
||||
+32
-11
@@ -179,10 +179,11 @@ class Settings(DikteTest):
|
||||
Qt.KeyboardModifier.NoModifier,
|
||||
Qt.ScrollPhase.NoScrollPhase, False)
|
||||
|
||||
def test_the_window_opens_with_every_tab_on_it(self):
|
||||
def test_settings_keeps_configuration_and_exposes_separate_task_pages(self):
|
||||
window = self.window(cfg.Config())
|
||||
tabs = window.findChildren(settings_ui.QTabWidget)[0]
|
||||
self.assertEqual(tabs.count(), 10)
|
||||
self.assertEqual(tabs.count(), 7)
|
||||
self.assertEqual(set(window.task_pages), {"file", "minutes", "history"})
|
||||
self.assertEqual(window.windowTitle(), "Dikte Settings")
|
||||
|
||||
def test_no_tab_can_stretch_the_window_past_a_small_screen(self):
|
||||
@@ -1198,7 +1199,7 @@ class Overlay(DikteTest):
|
||||
mock.patch.object(QApplication, "screenAt") as screen_at:
|
||||
widget._reposition()
|
||||
screen_at.assert_not_called()
|
||||
self.assertEqual(widget.pos(), QPoint(1948, 995))
|
||||
self.assertEqual(widget.pos(), QPoint(1948, 1003))
|
||||
|
||||
def _screen(self, name, area):
|
||||
screen = mock.Mock()
|
||||
@@ -1224,7 +1225,7 @@ class Overlay(DikteTest):
|
||||
mock.patch.object(QApplication, "screenAt") as screen_at:
|
||||
widget._reposition()
|
||||
screen_at.assert_not_called()
|
||||
self.assertEqual(widget.pos(), QPoint(1948, 995))
|
||||
self.assertEqual(widget.pos(), QPoint(1948, 1003))
|
||||
|
||||
def test_the_pointer_decides_when_the_compositor_will_not_say(self):
|
||||
"""Every desktop but Plasma, and Plasma while KWin is being replaced."""
|
||||
@@ -1236,7 +1237,7 @@ class Overlay(DikteTest):
|
||||
return_value=screens[0]) as screen_at:
|
||||
widget._reposition()
|
||||
screen_at.assert_called()
|
||||
self.assertEqual(widget.pos(), QPoint(28, 995))
|
||||
self.assertEqual(widget.pos(), QPoint(28, 1003))
|
||||
|
||||
def _two_screens(self):
|
||||
return [self._screen("DP-1", settings_ui.QRect(0, 0, 1920, 1080)),
|
||||
@@ -1258,10 +1259,10 @@ class Overlay(DikteTest):
|
||||
with mock.patch.object(overlay_module, "_kwin", kwin), \
|
||||
mock.patch.object(QApplication, "screens", return_value=screens):
|
||||
widget.show_recording()
|
||||
self.assertEqual(widget.pos(), QPoint(1948, 995))
|
||||
self.assertEqual(widget.pos(), QPoint(1948, 1003))
|
||||
kwin.call.return_value.arguments.return_value = ["DP-1"]
|
||||
self._ticks_on(widget, screens, kwin)
|
||||
self.assertEqual(widget.pos(), QPoint(28, 995))
|
||||
self.assertEqual(widget.pos(), QPoint(28, 1003))
|
||||
|
||||
def test_it_stays_where_it_appeared_unless_it_was_told_otherwise(self):
|
||||
"""Left off, because an indicator that jumps desks mid-sentence is one
|
||||
@@ -1274,7 +1275,7 @@ class Overlay(DikteTest):
|
||||
widget.show_recording()
|
||||
kwin.call.return_value.arguments.return_value = ["DP-1"]
|
||||
self._ticks_on(widget, screens, kwin)
|
||||
self.assertEqual(widget.pos(), QPoint(1948, 995))
|
||||
self.assertEqual(widget.pos(), QPoint(1948, 1003))
|
||||
|
||||
def test_a_named_screen_is_never_left_for_the_pointer(self):
|
||||
"""Naming one is the whole answer; following it would undo the naming."""
|
||||
@@ -1285,7 +1286,7 @@ class Overlay(DikteTest):
|
||||
widget.show_recording()
|
||||
self._ticks_on(widget, screens, kwin)
|
||||
kwin.call.assert_not_called()
|
||||
self.assertEqual(widget.pos(), QPoint(28, 995))
|
||||
self.assertEqual(widget.pos(), QPoint(28, 1003))
|
||||
|
||||
def test_the_one_on_top_goes_where_the_one_underneath_is(self):
|
||||
"""Asking for itself would put the pair on two monitors, with this one
|
||||
@@ -1299,8 +1300,8 @@ class Overlay(DikteTest):
|
||||
kwin.call.return_value.arguments.return_value = ["DP-1"]
|
||||
second = self.overlay(below=first)
|
||||
second.show_busy("Asking Claude…")
|
||||
self.assertEqual(first.pos(), QPoint(1948, 995))
|
||||
self.assertEqual(second.pos(), QPoint(1948, 929))
|
||||
self.assertEqual(first.pos(), QPoint(1948, 1003))
|
||||
self.assertEqual(second.pos(), QPoint(1948, 945))
|
||||
|
||||
def test_the_compositor_is_asked_only_now_and_then(self):
|
||||
"""Every tick would be thirty conversations a second about a hand
|
||||
@@ -1404,6 +1405,7 @@ class LocalModels(DikteTest):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.enterContext(mock.patch.object(settings_ui.LocalModelBox, "_fetch_models"))
|
||||
# A machine Dikte is actually installed on would otherwise answer the
|
||||
# "nothing can transcribe" question from its real binary and model.
|
||||
self.patch_attr(ggml, "BIN_DIR", self.path("bin"))
|
||||
@@ -1687,6 +1689,25 @@ class LocalModels(DikteTest):
|
||||
time.sleep(0.05)
|
||||
_app.processEvents()
|
||||
self.assertEqual(fetch.call_count, 1)
|
||||
|
||||
def test_reloading_settings_keeps_the_fetched_model_choices(self):
|
||||
repo = "ggml-org/SmolLM3-3B-GGUF"
|
||||
box = self.window(self.config(local_llm_repo=repo)).local_llm
|
||||
box._on_listed([("models", [self._item("first.gguf"), self._item("second.gguf")], repo)], "")
|
||||
box.load("first.gguf", repo)
|
||||
self.assertGreaterEqual(box.model.findData("second.gguf"), 0)
|
||||
self.assertEqual(box.selected(), "first.gguf")
|
||||
|
||||
def test_reload_before_repository_debounce_does_not_reuse_previous_catalog(self):
|
||||
first_repo = "ggml-org/SmolLM3-3B-GGUF"
|
||||
second_repo = "ggml-org/gemma-3-4b-it-GGUF"
|
||||
box = self.window(self.config(local_llm_repo=first_repo)).local_llm
|
||||
box._on_listed([("models", [self._item("first.gguf"), self._item("second.gguf")], first_repo)], "")
|
||||
box.repo.setCurrentText(second_repo)
|
||||
box.load("first.gguf", second_repo)
|
||||
self.assertLess(box.model.findData("second.gguf"), 0)
|
||||
self.assertTrue(box._pending)
|
||||
self.assertFalse(box._answered)
|
||||
def test_the_models_are_grouped_by_the_model_rather_than_by_size(self):
|
||||
# Sorted by size alone, the turbo files land between the two medium
|
||||
# ones, half a screen from the model they are a copy of.
|
||||
|
||||
Reference in New Issue
Block a user