Files
dikte/tests/test_hotkey.py
T
yusufipk 80a4832818 Test what the application does, and run it on every pull request
570 tests over the standard library's unittest, so there is nothing to
install beyond the PyQt6 the application already needs. They reach neither
the network, the microphone, nor the real ~/.config/dikte: urllib is faked at
one function, the tools are faked at shutil.which, and every test is handed
its own config and data directory.

What they hold onto is what a change is most likely to move without meaning
to. The request each provider is sent, field by field. A settings window that
loads a value into a widget and writes it back, which is where a setting
added to one half and not the other is silently reset. The dictation chain
end to end: what is transcribed, what is pasted, what lands in the history,
and what happens to the audio afterwards. A config file written by an older
version. A meeting whose two channels heard the same sentence.

59 of them carry @linux_only, because they cover what Dikte is on this
desktop rather than what it does: PipeWire, wl-clipboard, ydotool, KDE's
shortcut file. The other 511 pass on any platform, and that line is worth
holding as the ports arrive.

CONTRIBUTING.md says how to run them, what support.py offers, and the three
things about this codebase that trip up a new test.
2026-08-01 20:06:47 +07:00

223 lines
9.2 KiB
Python

"""Parsing a shortcut, and the KDE entry it is installed as."""
import subprocess
import unittest
from unittest import mock
import hotkey
from tests.support import DikteTest, FakeCompleted, linux_only
SHORTCUTS_RC = """[services][dikte-toggle.desktop]
_launch=Ctrl+Space
[services][org.kde.spectacle.desktop]
RectangularRegionScreenShot=Meta+Shift+Print\tMeta+Shift+Print\t
[kwin]
Overview=Meta+W,Meta+W,Toggle Overview
Switch Window Down=Meta+Alt+Down,Meta+Alt+Down,Switch to Window Below
"""
class ParseShortcut(unittest.TestCase):
def test_the_default(self):
self.assertEqual(hotkey.parse_shortcut("Ctrl+Space"), ({"ctrl"}, 57))
def test_case_and_spacing_do_not_matter(self):
self.assertEqual(hotkey.parse_shortcut(" ctrl + SPACE "), ({"ctrl"}, 57))
def test_several_modifiers(self):
mods, key = hotkey.parse_shortcut("Ctrl+Alt+Shift+D")
self.assertEqual(mods, {"ctrl", "alt", "shift"})
self.assertEqual(key, 32)
def test_the_synonyms_land_on_one_name(self):
self.assertEqual(hotkey.parse_shortcut("Control+Space"),
hotkey.parse_shortcut("Ctrl+Space"))
self.assertEqual(hotkey.parse_shortcut("Meta+Space"),
hotkey.parse_shortcut("Super+Space"))
def test_a_key_on_its_own(self):
self.assertEqual(hotkey.parse_shortcut("F9"), (set(), 67))
def test_modifiers_with_no_key(self):
self.assertEqual(hotkey.parse_shortcut("Ctrl+Alt"), (None, None))
def test_a_key_nobody_mapped(self):
self.assertEqual(hotkey.parse_shortcut("Ctrl+F13"), (None, None))
def test_nothing(self):
self.assertEqual(hotkey.parse_shortcut(""), (None, None))
self.assertEqual(hotkey.parse_shortcut("+++"), (None, None))
def test_something_that_is_not_even_a_string(self):
self.assertEqual(hotkey.parse_shortcut(None), (None, None))
class ModsMatch(unittest.TestCase):
"""The combination has to be exact, or Ctrl+Space fires on Ctrl+Shift+Space."""
def match(self, held, wanted):
return hotkey.EvdevHotkey._mods_match(set(held), set(wanted))
def test_the_wanted_modifier_is_down(self):
self.assertTrue(self.match({29}, {"ctrl"}))
def test_either_side_of_the_keyboard_counts(self):
self.assertTrue(self.match({97}, {"ctrl"}))
def test_nothing_held_and_nothing_wanted(self):
self.assertTrue(self.match(set(), set()))
def test_a_modifier_too_many(self):
self.assertFalse(self.match({29, 42}, {"ctrl"}))
def test_a_modifier_missing(self):
self.assertFalse(self.match(set(), {"ctrl"}))
def test_the_wrong_modifier(self):
self.assertFalse(self.match({56}, {"ctrl"}))
@linux_only
class Bindings(DikteTest):
"""start() before it reaches /dev/input, which a test may not read."""
def test_a_binding_with_no_shortcut_is_skipped(self):
listener = hotkey.EvdevHotkey()
with mock.patch.object(listener, "_open_devices", return_value=[]):
self.assertFalse(listener.start({"toggle": "", "ask": ""}))
def test_an_unparsable_shortcut_is_reported_and_the_rest_go_on(self):
listener = hotkey.EvdevHotkey()
self.addCleanup(listener.stop)
failures = []
listener.failed.connect(failures.append)
with mock.patch.object(listener, "_open_devices", return_value=[99]), \
mock.patch.object(hotkey.threading, "Thread"):
self.assertTrue(listener.start({"toggle": "Ctrl+F13",
"ask": "Ctrl+Space"}))
self.assertEqual(len(failures), 1)
self.assertIn("Ctrl+F13", failures[0])
self.assertEqual(list(listener._bindings), [57])
def test_no_readable_devices_says_what_to_do_about_it(self):
listener = hotkey.EvdevHotkey()
failures = []
listener.failed.connect(failures.append)
with mock.patch.object(listener, "_open_devices", return_value=[]):
self.assertFalse(listener.start({"toggle": "Ctrl+Space"}))
self.assertIn("input", failures[0])
def test_two_shortcuts_on_one_key_are_both_kept(self):
listener = hotkey.EvdevHotkey()
self.addCleanup(listener.stop)
with mock.patch.object(listener, "_open_devices", return_value=[]), \
mock.patch.object(hotkey.threading, "Thread") as thread:
listener._open_devices.return_value = [99]
self.assertTrue(listener.start({"toggle": "Ctrl+Space",
"ask": "Ctrl+Alt+Space"}))
thread.assert_called_once()
self.assertEqual(len(listener._bindings[57]), 2)
@linux_only
class KdeShortcut(DikteTest):
def setUp(self):
super().setUp()
self.apps = self.path("applications")
self.apps.mkdir(parents=True)
self.rc = self.path("kglobalshortcutsrc")
self.patch_attr(hotkey, "APPLICATIONS_DIR", self.apps)
self.patch_attr(hotkey, "SHORTCUTS_FILE", self.rc)
def test_installing_writes_a_desktop_file_kwin_will_launch(self):
with mock.patch.object(subprocess, "run", return_value=FakeCompleted()):
ok, message = hotkey.install_kde_shortcut("Ctrl+Space", "dikte toggle")
self.assertTrue(ok)
text = (self.apps / hotkey.DESKTOP_ID).read_text(encoding="utf-8")
self.assertIn("Exec=dikte toggle", text)
self.assertIn("X-KDE-GlobalAccel-CommandShortcut=true", text)
self.assertIn("log out", message)
def test_the_shortcut_is_registered_under_the_desktop_id(self):
with mock.patch.object(subprocess, "run",
return_value=FakeCompleted()) as run:
hotkey.install_kde_shortcut("Meta+D", "dikte ask",
desktop_id=hotkey.ASK_DESKTOP_ID)
cmd = run.call_args.args[0]
self.assertEqual(cmd[0], "kwriteconfig6")
self.assertIn(hotkey.ASK_DESKTOP_ID, cmd)
self.assertEqual(cmd[-1], "Meta+D")
def test_each_verb_gets_its_own_entry(self):
with mock.patch.object(subprocess, "run", return_value=FakeCompleted()):
hotkey.install_kde_shortcut("Ctrl+Space", "dikte toggle")
hotkey.install_kde_shortcut("Meta+M", "dikte meeting",
desktop_id=hotkey.MEETING_DESKTOP_ID)
self.assertTrue((self.apps / hotkey.DESKTOP_ID).exists())
self.assertTrue((self.apps / hotkey.MEETING_DESKTOP_ID).exists())
def test_no_kwriteconfig_installed(self):
with mock.patch.object(subprocess, "run", side_effect=OSError("nope")):
ok, message = hotkey.install_kde_shortcut("Ctrl+Space", "dikte toggle")
self.assertFalse(ok)
self.assertIn("kglobalshortcutsrc", message)
def test_removing_takes_the_desktop_file_with_it(self):
(self.apps / hotkey.DESKTOP_ID).write_text("[Desktop Entry]", encoding="utf-8")
with mock.patch.object(subprocess, "run", return_value=FakeCompleted()) as run:
hotkey.remove_kde_shortcut()
self.assertFalse((self.apps / hotkey.DESKTOP_ID).exists())
self.assertIn("--delete", run.call_args.args[0])
def test_removing_one_that_was_never_installed(self):
with mock.patch.object(subprocess, "run", side_effect=OSError("nope")):
hotkey.remove_kde_shortcut() # must not raise
def test_the_registered_shortcut_is_read_back(self):
(self.apps / hotkey.DESKTOP_ID).write_text("[Desktop Entry]", encoding="utf-8")
self.rc.write_text(SHORTCUTS_RC, encoding="utf-8")
self.assertEqual(hotkey.kde_shortcut_status(), "Ctrl+Space")
def test_no_desktop_file_means_nothing_is_installed(self):
self.rc.write_text(SHORTCUTS_RC, encoding="utf-8")
self.assertIsNone(hotkey.kde_shortcut_status())
def test_a_desktop_file_with_no_entry_beside_it(self):
(self.apps / hotkey.DESKTOP_ID).write_text("[Desktop Entry]", encoding="utf-8")
self.rc.write_text("[kwin]\nOverview=Meta+W\n", encoding="utf-8")
self.assertIsNone(hotkey.kde_shortcut_status())
def test_no_shortcuts_file_at_all(self):
(self.apps / hotkey.DESKTOP_ID).write_text("[Desktop Entry]", encoding="utf-8")
self.assertIsNone(hotkey.kde_shortcut_status())
def test_a_combination_somebody_else_already_took(self):
self.rc.write_text(SHORTCUTS_RC, encoding="utf-8")
hits = hotkey.conflicting_shortcuts("Meta+W")
self.assertEqual(len(hits), 1)
self.assertIn("kwin", hits[0])
self.assertIn("Overview", hits[0])
def test_our_own_entry_is_not_a_conflict(self):
self.rc.write_text(SHORTCUTS_RC, encoding="utf-8")
self.assertEqual(hotkey.conflicting_shortcuts("Ctrl+Space"), [])
def test_a_free_combination(self):
self.rc.write_text(SHORTCUTS_RC, encoding="utf-8")
self.assertEqual(hotkey.conflicting_shortcuts("Ctrl+Alt+J"), [])
def test_a_tab_separated_entry_is_read_too(self):
self.rc.write_text(SHORTCUTS_RC, encoding="utf-8")
hits = hotkey.conflicting_shortcuts("Meta+Shift+Print")
self.assertEqual(len(hits), 1)
self.assertIn("spectacle", hits[0])
def test_no_shortcuts_file_means_no_conflicts(self):
self.assertEqual(hotkey.conflicting_shortcuts("Ctrl+Space"), [])
if __name__ == "__main__":
unittest.main()