mirror of
https://github.com/yusufipk/dikte.git
synced 2026-09-11 19:06:11 +00:00
Merge master into the pw-record raw check
recording_command became a table of sound systems while this branch was open, so the pw-record command it patched now lives in _pulse_record. The check moves there with it, and nothing else about it changes.
This commit is contained in:
@@ -17,6 +17,11 @@ os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||
_SANDBOX = tempfile.mkdtemp(prefix="dikte-tests-")
|
||||
os.environ["XDG_CONFIG_HOME"] = os.path.join(_SANDBOX, "config")
|
||||
os.environ["XDG_DATA_HOME"] = os.path.join(_SANDBOX, "data")
|
||||
# Home goes with them: the shortcut file, the applications directory and every
|
||||
# macOS path start from it rather than from an XDG variable, and a test run is
|
||||
# not allowed to touch the real one.
|
||||
os.environ["HOME"] = os.path.join(_SANDBOX, "home")
|
||||
os.makedirs(os.environ["HOME"], exist_ok=True)
|
||||
atexit.register(shutil.rmtree, _SANDBOX, True)
|
||||
|
||||
# A key sitting in the environment would otherwise reach the code that falls
|
||||
|
||||
+168
-10
@@ -1,8 +1,12 @@
|
||||
"""Level metering, the WAV writer, and what pactl is asked for.
|
||||
"""Level metering, the WAV writer, and what the sound system is asked for.
|
||||
|
||||
The device list is where a platform port lands first, so the parsing is pinned
|
||||
here: a source that is not a monitor is an input, one that is belongs to the
|
||||
speakers, and neither list may go missing when pactl is absent.
|
||||
here: on Linux a source that is not a monitor is an input, one that is belongs
|
||||
to the speakers, and neither list may go missing when pactl is absent. macOS
|
||||
answers the same three questions out of one ffmpeg listing.
|
||||
|
||||
Each class says which machine it is standing on, so both halves run on either
|
||||
one: nothing here reaches a real sound server.
|
||||
"""
|
||||
|
||||
import array
|
||||
@@ -11,6 +15,7 @@ import io
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
import wave
|
||||
from unittest import mock
|
||||
@@ -19,7 +24,6 @@ import audio
|
||||
from tests.support import (
|
||||
DikteTest,
|
||||
FakeCompleted,
|
||||
linux_only,
|
||||
only_these_tools,
|
||||
pcm,
|
||||
silence,
|
||||
@@ -28,6 +32,22 @@ from tests.support import (
|
||||
)
|
||||
|
||||
|
||||
class OnLinux:
|
||||
"""A test that runs as if the machine ran PulseAudio or PipeWire."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.enterContext(mock.patch.object(sys, "platform", "linux"))
|
||||
|
||||
|
||||
class OnMacOS:
|
||||
"""A test that runs as if the machine were a Mac."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.enterContext(mock.patch.object(sys, "platform", "darwin"))
|
||||
|
||||
|
||||
class ChunkLevels(unittest.TestCase):
|
||||
def test_silence(self):
|
||||
self.assertEqual(audio.chunk_levels(silence(0.1)), (0.0, 0.0))
|
||||
@@ -115,8 +135,7 @@ SOURCES = [
|
||||
]
|
||||
|
||||
|
||||
@linux_only
|
||||
class Devices(DikteTest):
|
||||
class Devices(OnLinux, DikteTest):
|
||||
@contextlib.contextmanager
|
||||
def pactl(self, sources=None, sink=None, tools=("pactl",)):
|
||||
payloads = {
|
||||
@@ -216,8 +235,7 @@ class FakeProcess:
|
||||
self._alive = False
|
||||
|
||||
|
||||
@linux_only
|
||||
class RecordingCommand(DikteTest):
|
||||
class RecordingCommand(OnLinux, DikteTest):
|
||||
"""Which program captures the microphone, and how it is asked to."""
|
||||
|
||||
def test_parec_is_preferred(self):
|
||||
@@ -287,8 +305,7 @@ class RecordingCommand(DikteTest):
|
||||
if arg.startswith(flag)])
|
||||
|
||||
|
||||
@linux_only
|
||||
class RecorderChain(DikteTest):
|
||||
class RecorderChain(OnLinux, DikteTest):
|
||||
"""Start to WAV, with pw-record faked out."""
|
||||
|
||||
def record(self, data, target="", max_seconds=300):
|
||||
@@ -423,5 +440,146 @@ class RecorderChain(DikteTest):
|
||||
self.assertFalse(recorder.active)
|
||||
|
||||
|
||||
class MeetingCommand(unittest.TestCase):
|
||||
"""One process reading both devices, because two would drift apart."""
|
||||
|
||||
def command(self, platform, mic="", system="them"):
|
||||
with mock.patch.object(sys, "platform", platform):
|
||||
return audio.meeting_command(mic, system)
|
||||
|
||||
def test_linux_reads_both_through_pulse(self):
|
||||
cmd = self.command("linux", mic="mine")
|
||||
self.assertEqual(cmd.count("pulse"), 2)
|
||||
self.assertEqual(cmd[cmd.index("mine") - 1], "-i")
|
||||
self.assertEqual(cmd[cmd.index("them") - 1], "-i")
|
||||
|
||||
def test_a_mac_reads_both_through_avfoundation(self):
|
||||
cmd = self.command("darwin", mic="1")
|
||||
self.assertEqual(cmd.count("avfoundation"), 2)
|
||||
self.assertIn(":1", cmd)
|
||||
self.assertIn(":them", cmd)
|
||||
|
||||
def test_no_microphone_named_means_the_default_one(self):
|
||||
self.assertIn("default", self.command("linux"))
|
||||
self.assertIn(":default", self.command("darwin"))
|
||||
|
||||
def test_both_merge_the_two_into_one_stereo_stream(self):
|
||||
for platform in ("linux", "darwin"):
|
||||
with self.subTest(platform=platform):
|
||||
cmd = self.command(platform)
|
||||
self.assertIn(audio.MERGE_FILTER, cmd)
|
||||
self.assertEqual(cmd[cmd.index("-map") + 1], "[out]")
|
||||
self.assertEqual(cmd[cmd.index("-f", cmd.index("-map")) + 1], "s16le")
|
||||
|
||||
def test_neither_lets_ffmpeg_read_the_terminal(self):
|
||||
"""It shares stdin with Dikte, and would eat a keypress meant for it."""
|
||||
for platform in ("linux", "darwin"):
|
||||
with self.subTest(platform=platform):
|
||||
self.assertIn("-nostdin", self.command(platform))
|
||||
|
||||
|
||||
class MacDevices(OnMacOS, DikteTest):
|
||||
"""The one ffmpeg listing all three device questions are answered from."""
|
||||
|
||||
LISTING = (
|
||||
"[AVFoundation indev @ 0x7fb] AVFoundation video devices:\n"
|
||||
"[AVFoundation indev @ 0x7fb] [0] FaceTime HD Camera\n"
|
||||
"[AVFoundation indev @ 0x7fb] [1] Capture screen 0\n"
|
||||
"[AVFoundation indev @ 0x7fb] AVFoundation audio devices:\n"
|
||||
"[AVFoundation indev @ 0x7fb] [0] MacBook Pro Microphone\n"
|
||||
"[AVFoundation indev @ 0x7fb] [1] BlackHole 2ch\n"
|
||||
": Input/output error\n"
|
||||
)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def listing(self, stderr=None, tools=("ffmpeg",)):
|
||||
completed = FakeCompleted(
|
||||
returncode=1, stderr=self.LISTING if stderr is None else stderr)
|
||||
with only_these_tools(*tools), \
|
||||
mock.patch.object(subprocess, "run", return_value=completed):
|
||||
yield
|
||||
|
||||
def test_the_audio_half_of_the_listing_is_the_only_half_read(self):
|
||||
with self.listing():
|
||||
self.assertEqual(audio.list_sources(),
|
||||
[("0", "MacBook Pro Microphone"), ("1", "BlackHole 2ch")])
|
||||
|
||||
def test_the_index_is_what_ffmpeg_is_given_and_the_name_what_is_shown(self):
|
||||
with self.listing():
|
||||
name, description = audio.list_sources()[1]
|
||||
self.assertEqual(name, "1")
|
||||
self.assertIn("BlackHole", description)
|
||||
|
||||
def test_no_ffmpeg_installed(self):
|
||||
with only_these_tools():
|
||||
self.assertEqual(audio.list_sources(), [])
|
||||
self.assertEqual(audio.list_monitors(), [])
|
||||
self.assertEqual(audio.default_monitor(), "")
|
||||
|
||||
def test_an_ffmpeg_that_will_not_run(self):
|
||||
with only_these_tools("ffmpeg"), \
|
||||
mock.patch.object(subprocess, "run", side_effect=OSError("nope")):
|
||||
self.assertEqual(audio.list_sources(), [])
|
||||
|
||||
def test_a_listing_with_no_audio_section(self):
|
||||
with self.listing(stderr="[AVFoundation indev @ 0x7fb] [0] FaceTime\n"):
|
||||
self.assertEqual(audio.list_sources(), [])
|
||||
|
||||
def test_the_far_side_of_a_meeting_is_offered_the_same_devices(self):
|
||||
"""macOS calls none of them an output, so the loopback one is in here."""
|
||||
with self.listing():
|
||||
self.assertEqual(audio.list_monitors(), audio.list_sources())
|
||||
|
||||
def test_the_loopback_driver_is_picked_out_by_name(self):
|
||||
with self.listing():
|
||||
self.assertEqual(audio.default_monitor(), "1")
|
||||
|
||||
def test_the_other_two_drivers_people_install(self):
|
||||
for name in ("Loopback Audio", "Soundflower (2ch)"):
|
||||
with self.subTest(name=name):
|
||||
listing = ("AVFoundation audio devices:\n"
|
||||
f"[0] Built-in Microphone\n[1] {name}\n")
|
||||
with self.listing(stderr=listing):
|
||||
self.assertEqual(audio.default_monitor(), "1")
|
||||
|
||||
def test_a_mac_with_nothing_to_record_the_far_side_from(self):
|
||||
listing = "AVFoundation audio devices:\n[0] MacBook Pro Microphone\n"
|
||||
with self.listing(stderr=listing):
|
||||
self.assertEqual(audio.default_monitor(), "")
|
||||
|
||||
|
||||
class MacRecordingCommand(OnMacOS, DikteTest):
|
||||
def test_the_microphone_is_read_through_avfoundation(self):
|
||||
with only_these_tools("ffmpeg"):
|
||||
cmd = audio.recording_command()
|
||||
self.assertEqual(cmd[0], "ffmpeg")
|
||||
self.assertEqual(cmd[cmd.index("-f") + 1], "avfoundation")
|
||||
|
||||
def test_the_empty_half_in_front_of_the_colon_is_the_missing_picture(self):
|
||||
with only_these_tools("ffmpeg"):
|
||||
self.assertIn(":default", audio.recording_command())
|
||||
self.assertIn(":2", audio.recording_command("2"))
|
||||
|
||||
def test_it_captures_the_format_the_rest_of_the_code_expects(self):
|
||||
with only_these_tools("ffmpeg"):
|
||||
cmd = audio.recording_command()
|
||||
self.assertEqual(cmd[cmd.index("-ar") + 1], str(audio.RATE))
|
||||
self.assertEqual(cmd[cmd.index("-ac") + 1], str(audio.CHANNELS))
|
||||
self.assertEqual(cmd[-2:], ["s16le", "-"])
|
||||
|
||||
def test_no_ffmpeg_installed(self):
|
||||
with only_these_tools():
|
||||
self.assertEqual(audio.recording_command(), [])
|
||||
|
||||
def test_what_a_mac_is_told_to_install(self):
|
||||
recorder = audio.Recorder()
|
||||
failures = []
|
||||
recorder.failed.connect(failures.append)
|
||||
with only_these_tools():
|
||||
recorder.start()
|
||||
self.assertIn("brew install ffmpeg", failures[0])
|
||||
self.assertFalse(recorder.active)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -16,6 +16,7 @@ import cleanup
|
||||
import config as cfg
|
||||
import ggml
|
||||
import i18n
|
||||
import paste
|
||||
from tests.support import DikteTest
|
||||
|
||||
|
||||
@@ -453,6 +454,39 @@ class Defaults(unittest.TestCase):
|
||||
with self.subTest(prompt=f"{name}_{suffix}"):
|
||||
self.assertTrue(getattr(cfg, f"{name}_{suffix}").strip())
|
||||
|
||||
def test_the_paste_key_is_the_one_this_desktop_pastes_with(self):
|
||||
"""cmd+v on a Mac, and it must be one paste.py can actually press."""
|
||||
self.assertEqual(cfg.DEFAULTS["paste_shortcut"],
|
||||
paste.desktop().shortcuts[0])
|
||||
|
||||
|
||||
class Directories(unittest.TestCase):
|
||||
"""Where the settings and the recordings are kept, per system."""
|
||||
|
||||
def test_linux_keeps_them_apart_and_follows_xdg(self):
|
||||
with mock.patch.dict(os.environ, {"XDG_CONFIG_HOME": "/c",
|
||||
"XDG_DATA_HOME": "/d"}):
|
||||
config_dir, data_dir = cfg._directories("linux")
|
||||
self.assertEqual(str(config_dir), "/c/dikte")
|
||||
self.assertEqual(str(data_dir), "/d/dikte")
|
||||
|
||||
def test_linux_without_the_variables_set(self):
|
||||
with mock.patch.dict(os.environ, {}, clear=True):
|
||||
config_dir, data_dir = cfg._directories("linux")
|
||||
self.assertTrue(str(config_dir).endswith("/.config/dikte"))
|
||||
self.assertTrue(str(data_dir).endswith("/.local/share/dikte"))
|
||||
|
||||
def test_a_mac_keeps_both_in_application_support(self):
|
||||
config_dir, data_dir = cfg._directories("darwin")
|
||||
self.assertEqual(config_dir, data_dir)
|
||||
self.assertTrue(str(config_dir).endswith("/Library/Application Support/Dikte"))
|
||||
|
||||
def test_a_mac_does_not_read_the_xdg_variables(self):
|
||||
"""A Mac with them set from some other tool still stores in one place."""
|
||||
with mock.patch.dict(os.environ, {"XDG_CONFIG_HOME": "/c"}):
|
||||
config_dir, _ = cfg._directories("darwin")
|
||||
self.assertNotIn("/c", str(config_dir))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
+20
-1
@@ -176,6 +176,9 @@ class Download(Local):
|
||||
class InstallProgram(Local):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# These fixtures are Ubuntu release archives. Keep checking that path
|
||||
# on every host, including the Mac that checks the macOS backend.
|
||||
self.patch_attr(sys, "platform", "linux")
|
||||
# Built once, because the release listing has to publish its checksum
|
||||
# and a tarball is not the same bytes twice.
|
||||
self.archive = tarball({
|
||||
@@ -216,6 +219,23 @@ class InstallProgram(Local):
|
||||
ggml.install_program(ggml.WHISPER)
|
||||
self.assertIn("this machine", str(caught.exception))
|
||||
|
||||
def test_a_mac_does_not_install_an_ubuntu_archive_for_the_same_architecture(self):
|
||||
self.patch_attr(sys, "platform", "darwin")
|
||||
self.patch_attr(ggml, "_arch", lambda: "arm64")
|
||||
listing = self.release("whisper-bin-ubuntu-arm64.tar.gz")
|
||||
with fake_urlopen(listing):
|
||||
with self.assertRaises(ggml.LocalError) as caught:
|
||||
ggml.install_program(ggml.WHISPER)
|
||||
self.assertIn("brew install whisper-cpp", str(caught.exception))
|
||||
|
||||
def test_a_mac_uses_the_native_llama_archive_instead_of_ubuntu(self):
|
||||
self.patch_attr(sys, "platform", "darwin")
|
||||
self.patch_attr(ggml, "_arch", lambda: "arm64")
|
||||
self.assertEqual(
|
||||
ggml._wanted_assets(ggml.LLAMA),
|
||||
("bin-macos-arm64.tar.gz",),
|
||||
)
|
||||
|
||||
def test_what_was_installed_is_remembered(self):
|
||||
path, _ = self.install("whisper-bin-ubuntu-x64.tar.gz")
|
||||
self.assertEqual(ggml.installed_program(ggml.WHISPER), path)
|
||||
@@ -661,4 +681,3 @@ class Sizes(DikteTest):
|
||||
self.assertEqual(ggml.human_size(512), "512 B")
|
||||
self.assertEqual(ggml.human_size(574041195), "547.4 MB")
|
||||
self.assertEqual(ggml.human_size(3_095_033_483), "2.9 GB")
|
||||
|
||||
|
||||
+235
-1
@@ -163,10 +163,13 @@ class Bindings(DikteTest):
|
||||
self.assertEqual(fired({29, 42}), []) # ctrl + shift
|
||||
|
||||
|
||||
@linux_only
|
||||
class Chooser(DikteTest):
|
||||
"""Which desktop is asked to register the shortcut."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.patch_attr(hotkey.sys, "platform", "linux")
|
||||
|
||||
@contextlib.contextmanager
|
||||
def under(self, desktop, has_gsettings=True):
|
||||
"""A session that says it is this desktop, with or without gsettings."""
|
||||
@@ -447,5 +450,236 @@ class KdeShortcut(DikteTest):
|
||||
self.assertEqual(hotkey.conflicting_shortcuts("Ctrl+Space"), [])
|
||||
|
||||
|
||||
# --- macOS ----------------------------------------------------------------
|
||||
|
||||
class ParseMacShortcut(unittest.TestCase):
|
||||
def test_a_combination_a_mac_would_use(self):
|
||||
self.assertEqual(hotkey.parse_macos_shortcut("Cmd+Space"),
|
||||
(hotkey.MAC_MODS["cmd"], 49))
|
||||
|
||||
def test_case_and_spacing_do_not_matter(self):
|
||||
self.assertEqual(hotkey.parse_macos_shortcut(" ctrl + option + a "),
|
||||
hotkey.parse_macos_shortcut("Ctrl+Option+A"))
|
||||
|
||||
def test_several_modifiers_are_one_number(self):
|
||||
modifiers, key = hotkey.parse_macos_shortcut("Cmd+Shift+M")
|
||||
self.assertEqual(modifiers,
|
||||
hotkey.MAC_MODS["cmd"] | hotkey.MAC_MODS["shift"])
|
||||
self.assertEqual(key, hotkey.MAC_KEYS["m"])
|
||||
|
||||
def test_the_names_a_mac_keyboard_uses(self):
|
||||
for name in ("cmd", "command", "meta", "super"):
|
||||
with self.subTest(name=name):
|
||||
self.assertEqual(hotkey.parse_macos_shortcut(f"{name}+space"),
|
||||
hotkey.parse_macos_shortcut("cmd+space"))
|
||||
self.assertEqual(hotkey.parse_macos_shortcut("option+a"),
|
||||
hotkey.parse_macos_shortcut("alt+a"))
|
||||
|
||||
def test_a_key_on_its_own(self):
|
||||
self.assertEqual(hotkey.parse_macos_shortcut("F5"), (0, 96))
|
||||
|
||||
def test_modifiers_with_no_key(self):
|
||||
self.assertEqual(hotkey.parse_macos_shortcut("Cmd+Shift"), (None, None))
|
||||
|
||||
def test_a_key_nobody_mapped(self):
|
||||
self.assertEqual(hotkey.parse_macos_shortcut("Cmd+F13"), (None, None))
|
||||
|
||||
def test_two_keys_are_not_a_shortcut(self):
|
||||
self.assertEqual(hotkey.parse_macos_shortcut("A+B"), (None, None))
|
||||
|
||||
def test_nothing(self):
|
||||
self.assertEqual(hotkey.parse_macos_shortcut(""), (None, None))
|
||||
self.assertEqual(hotkey.parse_macos_shortcut(None), (None, None))
|
||||
|
||||
|
||||
class FakeCarbon:
|
||||
"""Enough of Carbon to watch what the listener asks it for.
|
||||
|
||||
The references are numbers standing in for pointers, which is all the code
|
||||
does with them: it collects them and hands them back to be unregistered.
|
||||
"""
|
||||
|
||||
def __init__(self, install=0, register=0):
|
||||
self.install, self.register = install, register # what they return
|
||||
self.registered = [] # (key, modifiers, identifier)
|
||||
self.unregistered = []
|
||||
self.handlers_removed = 0
|
||||
self.pressed_id = 0
|
||||
self.parameter_result = 0
|
||||
|
||||
def GetApplicationEventTarget(self):
|
||||
return 7000
|
||||
|
||||
def InstallEventHandler(self, target, callback, count, spec, data, out):
|
||||
if self.install == 0:
|
||||
out._obj.value = 8000
|
||||
return self.install
|
||||
|
||||
def RegisterEventHotKey(self, key, modifiers, identifier, target, options, out):
|
||||
if self.register != 0:
|
||||
return self.register
|
||||
self.registered.append((key, modifiers, identifier.id))
|
||||
out._obj.value = 9000 + len(self.registered)
|
||||
return 0
|
||||
|
||||
def UnregisterEventHotKey(self, reference):
|
||||
self.unregistered.append(reference.value)
|
||||
return 0
|
||||
|
||||
def RemoveEventHandler(self, handler):
|
||||
self.handlers_removed += 1
|
||||
return 0
|
||||
|
||||
def GetEventParameter(self, event, kind, name, wanted_type, size, out_size, out):
|
||||
out._obj.id = self.pressed_id
|
||||
return self.parameter_result
|
||||
|
||||
|
||||
class CarbonListener(DikteTest):
|
||||
"""What the listener asks macOS for, without a Mac to ask."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.carbon = FakeCarbon()
|
||||
self.patch_attr(hotkey, "_carbon", lambda: self.carbon)
|
||||
self.addCleanup(hotkey._REGISTERED.clear)
|
||||
self.listener = hotkey.CarbonHotkey()
|
||||
self.addCleanup(self.listener.stop)
|
||||
self.failures = []
|
||||
self.listener.failed.connect(self.failures.append)
|
||||
|
||||
def test_every_binding_is_asked_for_by_position_and_modifier(self):
|
||||
self.assertTrue(self.listener.start({"toggle": "Ctrl+Option+Space"}))
|
||||
self.assertEqual(self.carbon.registered,
|
||||
[(49, hotkey.MAC_MODS["ctrl"] | hotkey.MAC_MODS["option"], 1)])
|
||||
self.assertEqual(self.failures, [])
|
||||
self.assertTrue(self.listener.running)
|
||||
|
||||
def test_a_binding_with_no_shortcut_is_skipped(self):
|
||||
self.assertFalse(self.listener.start({"toggle": "", "ask": ""}))
|
||||
self.assertEqual(self.carbon.registered, [])
|
||||
self.assertFalse(self.listener.running)
|
||||
|
||||
def test_an_unparsable_shortcut_is_reported_and_the_rest_go_on(self):
|
||||
self.assertTrue(self.listener.start({"toggle": "Cmd+F13",
|
||||
"ask": "Cmd+Shift+Space"}))
|
||||
self.assertEqual(len(self.failures), 1)
|
||||
self.assertIn("Cmd+F13", self.failures[0])
|
||||
self.assertEqual(len(self.carbon.registered), 1)
|
||||
|
||||
def test_a_combination_another_application_already_holds(self):
|
||||
"""The conflict warning macOS has: it is the answer to asking."""
|
||||
self.carbon.register = -9878 # eventHotKeyExistsErr
|
||||
self.assertFalse(self.listener.start({"toggle": "Cmd+Shift+Space"}))
|
||||
self.assertIn("Cmd+Shift+Space", self.failures[0])
|
||||
self.assertFalse(self.listener.running)
|
||||
|
||||
def test_a_handler_that_will_not_install(self):
|
||||
self.carbon.install = -50
|
||||
self.assertFalse(self.listener.start({"toggle": "Cmd+Shift+Space"}))
|
||||
self.assertEqual(self.carbon.registered, [])
|
||||
self.assertEqual(len(self.failures), 1)
|
||||
|
||||
def test_no_carbon_to_talk_to(self):
|
||||
self.patch_attr(hotkey, "_carbon",
|
||||
mock.Mock(side_effect=OSError("no such library")))
|
||||
self.assertFalse(self.listener.start({"toggle": "Cmd+Shift+Space"}))
|
||||
self.assertIn("no such library", self.failures[0])
|
||||
|
||||
def test_the_key_press_arrives_under_the_name_it_was_registered_with(self):
|
||||
self.listener.start({"toggle": "Cmd+Shift+Space", "ask": "Cmd+Shift+A"})
|
||||
heard = []
|
||||
self.listener.triggered.connect(heard.append)
|
||||
self.carbon.pressed_id = 2 # the second binding, which is "ask"
|
||||
self.listener._callback(None, 0, None)
|
||||
self.assertEqual(heard, ["ask"])
|
||||
|
||||
def test_a_press_carbon_could_not_identify_is_dropped(self):
|
||||
self.listener.start({"toggle": "Cmd+Shift+Space"})
|
||||
heard = []
|
||||
self.listener.triggered.connect(heard.append)
|
||||
self.carbon.parameter_result = -50
|
||||
self.listener._callback(None, 0, None)
|
||||
self.assertEqual(heard, [])
|
||||
|
||||
def test_stopping_hands_every_registration_back(self):
|
||||
self.listener.start({"toggle": "Cmd+Shift+Space", "ask": "Cmd+Shift+A"})
|
||||
self.listener.stop()
|
||||
self.assertEqual(self.carbon.unregistered, [9001, 9002])
|
||||
self.assertEqual(self.carbon.handlers_removed, 1)
|
||||
self.assertFalse(self.listener.running)
|
||||
|
||||
def test_starting_twice_does_not_leave_the_first_set_behind(self):
|
||||
self.listener.start({"toggle": "Cmd+Shift+Space"})
|
||||
self.listener.start({"toggle": "Cmd+Shift+A"})
|
||||
self.assertEqual(self.carbon.unregistered, [9001])
|
||||
self.assertEqual(len(self.listener._registrations), 1)
|
||||
|
||||
def test_what_it_registered_is_what_the_status_line_reads_back(self):
|
||||
with mock.patch.object(hotkey.sys, "platform", "darwin"):
|
||||
self.listener.start({"toggle": "Ctrl+Option+Space",
|
||||
"meeting": "Ctrl+Option+M"})
|
||||
self.assertEqual(hotkey.shortcut_status(), "Ctrl+Option+Space")
|
||||
self.assertEqual(hotkey.shortcut_status(hotkey.MEETING_DESKTOP_ID),
|
||||
"Ctrl+Option+M")
|
||||
self.listener.stop()
|
||||
self.assertIsNone(hotkey.shortcut_status())
|
||||
|
||||
|
||||
class MacChooser(DikteTest):
|
||||
"""What the shortcut verbs mean where there is nothing to write them into."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.patch_attr(hotkey.sys, "platform", "darwin")
|
||||
self.addCleanup(hotkey._REGISTERED.clear)
|
||||
|
||||
def test_the_listener_is_the_one_macos_has(self):
|
||||
self.assertIsInstance(hotkey.listener(), hotkey.CarbonHotkey)
|
||||
|
||||
def test_everywhere_else_reads_the_keyboard_itself(self):
|
||||
with mock.patch.object(hotkey.sys, "platform", "linux"):
|
||||
self.assertIsInstance(hotkey.listener(), hotkey.EvdevHotkey)
|
||||
|
||||
def test_there_is_nothing_to_install_into(self):
|
||||
self.assertFalse(hotkey.installs_shortcuts())
|
||||
self.assertFalse(hotkey.shortcut_needs_restart())
|
||||
self.assertEqual(hotkey.desktop_name(), "macOS")
|
||||
|
||||
def test_installing_records_it_rather_than_writing_anything(self):
|
||||
with mock.patch.object(hotkey.subprocess, "run") as run:
|
||||
ok, message = hotkey.install_shortcut("Cmd+Shift+Space", "dikte toggle")
|
||||
run.assert_not_called()
|
||||
self.assertTrue(ok)
|
||||
self.assertIn("Cmd+Shift+Space", message)
|
||||
self.assertEqual(hotkey.shortcut_status(), "Cmd+Shift+Space")
|
||||
|
||||
def test_removing_takes_it_back_out(self):
|
||||
hotkey.install_shortcut("Cmd+Shift+Space", "dikte toggle")
|
||||
hotkey.remove_shortcut()
|
||||
self.assertIsNone(hotkey.shortcut_status())
|
||||
|
||||
def test_each_verb_is_kept_apart(self):
|
||||
hotkey.install_shortcut("Cmd+Shift+Space", "dikte toggle")
|
||||
hotkey.install_shortcut("Cmd+Shift+M", "dikte meeting",
|
||||
desktop_id=hotkey.MEETING_DESKTOP_ID)
|
||||
self.assertEqual(hotkey.shortcut_status(), "Cmd+Shift+Space")
|
||||
self.assertEqual(hotkey.shortcut_status(hotkey.MEETING_DESKTOP_ID),
|
||||
"Cmd+Shift+M")
|
||||
|
||||
def test_no_list_of_conflicts_to_read(self):
|
||||
"""Not even KDE's file, which a Mac could well have a copy of."""
|
||||
self.assertEqual(hotkey.conflicting_shortcuts("Ctrl+Space"), [])
|
||||
|
||||
def test_a_combination_is_checked_against_the_mac_table(self):
|
||||
self.assertTrue(hotkey.valid_shortcut("Cmd+Shift+Space"))
|
||||
self.assertFalse(hotkey.valid_shortcut("Ctrl+F13"))
|
||||
|
||||
def test_the_other_table_is_the_one_used_elsewhere(self):
|
||||
with mock.patch.object(hotkey.sys, "platform", "linux"):
|
||||
self.assertTrue(hotkey.valid_shortcut("Ctrl+F1"))
|
||||
self.assertFalse(hotkey.valid_shortcut("Cmd+Space"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
+219
-22
@@ -1,30 +1,37 @@
|
||||
"""The clipboard and the key press, which is where a dictation actually lands.
|
||||
|
||||
Everything here shells out, so the tools are faked. What the tests hold onto is
|
||||
the command line: a paste that presses the wrong keys, or in the wrong order,
|
||||
types nothing and looks like a hang.
|
||||
Everything here is faked: the programs the two Linux desktops shell out to, and
|
||||
the frameworks macOS goes through. What the tests hold onto is what was asked
|
||||
for. A paste that presses the wrong keys, or in the wrong order, types nothing
|
||||
and looks like a hang.
|
||||
|
||||
Both desktops owe the same promises, so those are written once and run against
|
||||
each of them. A third one added to paste.py inherits the same list rather than
|
||||
needing its own copy of it.
|
||||
Every system owes the same promises about the clipboard, so those are written
|
||||
once and run against each of them. A fourth one added to paste.py inherits the
|
||||
same list rather than needing its own copy of it. Each class says which system
|
||||
it is standing on, which is why none of this is skipped anywhere: the Linux half
|
||||
is checked on a Mac and the macOS half on Linux, and a change to the chooser
|
||||
cannot quietly break the platform nobody is sitting at.
|
||||
"""
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from typing import ClassVar
|
||||
from unittest import mock
|
||||
|
||||
import paste
|
||||
from tests.support import DikteTest, FakeCompleted, linux_only, only_these_tools
|
||||
from tests.support import DikteTest, FakeCompleted, only_these_tools
|
||||
|
||||
|
||||
@linux_only
|
||||
class Chooser(DikteTest):
|
||||
"""Which pair of programs this session's clipboard goes through."""
|
||||
|
||||
def under(self, **env):
|
||||
with mock.patch.dict(os.environ, env, clear=True):
|
||||
def under(self, platform="linux", **env):
|
||||
with mock.patch.object(sys, "platform", platform), \
|
||||
mock.patch.dict(os.environ, env, clear=True):
|
||||
return paste.desktop()
|
||||
|
||||
def test_a_wayland_session(self):
|
||||
@@ -46,18 +53,29 @@ class Chooser(DikteTest):
|
||||
def test_nothing_set_at_all(self):
|
||||
self.assertIs(self.under(), paste.WAYLAND)
|
||||
|
||||
def test_a_mac(self):
|
||||
self.assertIs(self.under("darwin"), paste.MACOS)
|
||||
|
||||
class DesktopContract:
|
||||
"""What both desktops owe. Each of them subclasses this once, below."""
|
||||
def test_a_mac_running_an_x_server_is_still_a_mac(self):
|
||||
"""XQuartz sets DISPLAY, and none of X's programs are what pastes here."""
|
||||
self.assertIs(self.under("darwin", DISPLAY=":0"), paste.MACOS)
|
||||
|
||||
|
||||
class Standing:
|
||||
"""A test that runs as if it were sitting at one particular system."""
|
||||
|
||||
env: ClassVar[dict] = {}
|
||||
platform = "linux"
|
||||
here = None
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.enterContext(mock.patch.object(sys, "platform", self.platform))
|
||||
self.enterContext(mock.patch.dict(os.environ, self.env, clear=True))
|
||||
|
||||
# ---- reading the clipboard -------------------------------------------
|
||||
|
||||
class ClipboardContract(Standing):
|
||||
"""What every system owes the text on its way to the clipboard."""
|
||||
|
||||
def test_no_reader_installed(self):
|
||||
with only_these_tools():
|
||||
@@ -81,13 +99,10 @@ class DesktopContract:
|
||||
mock.patch.object(subprocess, "run", side_effect=OSError("nope")):
|
||||
self.assertIsNone(paste.read_clipboard())
|
||||
|
||||
# ---- copying ----------------------------------------------------------
|
||||
|
||||
def test_no_clipboard_tool_installed_says_what_to_install(self):
|
||||
def test_no_clipboard_tool_installed_names_it(self):
|
||||
with only_these_tools(), self.assertRaises(paste.PasteError) as caught:
|
||||
paste.copy("hello")
|
||||
self.assertIn(self.here.clipboard, str(caught.exception))
|
||||
self.assertIn(self.here.packages.split(" and ")[0], str(caught.exception))
|
||||
|
||||
def test_the_text_goes_in_as_utf8(self):
|
||||
with only_these_tools(self.here.clipboard), \
|
||||
@@ -138,7 +153,15 @@ class DesktopContract:
|
||||
paste.copy_bytes(b"\x89PNG\r\n")
|
||||
self.assertEqual(run.call_args.kwargs["input"], b"\x89PNG\r\n")
|
||||
|
||||
# ---- pressing the key -------------------------------------------------
|
||||
def test_the_paste_key_it_offers_is_one_it_can_press(self):
|
||||
"""Whatever Settings lists, pressing it must not come back unknown."""
|
||||
for shortcut in self.here.shortcuts:
|
||||
with self.subTest(shortcut=shortcut):
|
||||
self.assertTrue(self.pressing(shortcut))
|
||||
|
||||
|
||||
class KeyProgramContract(Standing):
|
||||
"""The half of it that is another program: ydotool, xdotool."""
|
||||
|
||||
def press(self, shortcut, result=None):
|
||||
with only_these_tools(self.here.keyboard), \
|
||||
@@ -148,6 +171,10 @@ class DesktopContract:
|
||||
paste.press(shortcut)
|
||||
return run.call_args.args[0]
|
||||
|
||||
def pressing(self, shortcut):
|
||||
"""The command a shortcut would run, for the contract above."""
|
||||
return self.press(shortcut)
|
||||
|
||||
def test_no_keyboard_tool_installed(self):
|
||||
with only_these_tools():
|
||||
self.assertFalse(paste.paste_ready())
|
||||
@@ -180,9 +207,11 @@ class DesktopContract:
|
||||
self.assertIn(self.here.keyboard, str(caught.exception))
|
||||
self.assertIn("no socket", str(caught.exception))
|
||||
|
||||
def test_nothing_named_presses_the_one_this_desktop_pastes_with(self):
|
||||
self.assertEqual(self.press(""), self.press(self.here.shortcuts[0]))
|
||||
|
||||
@linux_only
|
||||
class Wayland(DesktopContract, DikteTest):
|
||||
|
||||
class Wayland(ClipboardContract, KeyProgramContract, DikteTest):
|
||||
env: ClassVar[dict] = {"XDG_SESSION_TYPE": "wayland",
|
||||
"WAYLAND_DISPLAY": "wayland-0"}
|
||||
here = paste.WAYLAND
|
||||
@@ -208,8 +237,7 @@ class Wayland(DesktopContract, DikteTest):
|
||||
self.assertIn("ydotoold", str(caught.exception))
|
||||
|
||||
|
||||
@linux_only
|
||||
class X11(DesktopContract, DikteTest):
|
||||
class X11(ClipboardContract, KeyProgramContract, DikteTest):
|
||||
env: ClassVar[dict] = {"XDG_SESSION_TYPE": "x11", "DISPLAY": ":0"}
|
||||
here = paste.X11
|
||||
|
||||
@@ -232,5 +260,174 @@ class X11(DesktopContract, DikteTest):
|
||||
self.assertNotIn("ydotoold", str(caught.exception))
|
||||
|
||||
|
||||
class FakeCoreGraphics:
|
||||
"""Enough of the two frameworks to watch what a paste does to them.
|
||||
|
||||
The events are numbers standing in for pointers, which is all the code
|
||||
treats them as: it makes them, sets flags on them, posts them, and hands
|
||||
them back.
|
||||
"""
|
||||
|
||||
def __init__(self, trusted=True, makes=None):
|
||||
self.trusted = trusted
|
||||
self.makes = makes # how many it will hand out; None for as many as asked
|
||||
self.made = [] # (keycode, is_down)
|
||||
self.flags = [] # (event, flags)
|
||||
self.posted = [] # (tap, event)
|
||||
self.released = []
|
||||
|
||||
# --- ApplicationServices
|
||||
def AXIsProcessTrusted(self):
|
||||
return self.trusted
|
||||
|
||||
def CGEventCreateKeyboardEvent(self, source, keycode, down):
|
||||
self.made.append((keycode, down))
|
||||
if self.makes is not None and len(self.made) > self.makes:
|
||||
return None
|
||||
return 1000 + len(self.made)
|
||||
|
||||
def CGEventSetFlags(self, event, flags):
|
||||
self.flags.append((event, flags))
|
||||
|
||||
def CGEventPost(self, tap, event):
|
||||
self.posted.append((tap, event))
|
||||
|
||||
# --- CoreFoundation
|
||||
def CFRelease(self, event):
|
||||
self.released.append(event)
|
||||
|
||||
|
||||
class MacOS(ClipboardContract, DikteTest):
|
||||
platform = "darwin"
|
||||
here = paste.MACOS
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.api = FakeCoreGraphics()
|
||||
self.patch_attr(paste, "_macos_api", lambda: (self.api, self.api))
|
||||
self.patch_attr(paste.time, "sleep", lambda seconds: None)
|
||||
# It opens the settings pane once per run; each test gets its own run.
|
||||
self.patch_attr(paste, "_asked_for_permission", False)
|
||||
self.opened = self.patch_attr(paste.subprocess, "Popen", mock.Mock())
|
||||
|
||||
def pressing(self, shortcut):
|
||||
paste.press(shortcut)
|
||||
return self.api.posted
|
||||
|
||||
def test_the_key_goes_in_by_position_with_the_modifiers_on_it(self):
|
||||
paste.press("cmd+v")
|
||||
self.assertEqual(self.api.made, [(9, True), (9, False)])
|
||||
self.assertEqual([flags for _, flags in self.api.flags],
|
||||
[paste.MAC_FLAGS["command"]] * 2)
|
||||
self.assertEqual([tap for tap, _ in self.api.posted],
|
||||
[paste.HID_EVENT_TAP] * 2)
|
||||
|
||||
def test_the_down_is_posted_before_the_up(self):
|
||||
paste.press("cmd+v")
|
||||
self.assertEqual([event for _, event in self.api.posted], [1001, 1002])
|
||||
|
||||
def test_both_events_are_handed_back(self):
|
||||
"""CoreGraphics gives out memory that nothing else will free."""
|
||||
paste.press("cmd+v")
|
||||
self.assertEqual(sorted(self.api.released), [1001, 1002])
|
||||
|
||||
def test_several_modifiers_are_one_number(self):
|
||||
paste.press("cmd+shift+v")
|
||||
self.assertEqual(self.api.flags[0][1],
|
||||
paste.MAC_FLAGS["command"] | paste.MAC_FLAGS["shift"])
|
||||
|
||||
def test_the_names_a_mac_keyboard_uses(self):
|
||||
for name in ("cmd", "command", "meta", "super"):
|
||||
with self.subTest(name=name):
|
||||
self.assertEqual(paste._macos_keys(f"{name}+v"),
|
||||
(9, paste.MAC_FLAGS["command"]))
|
||||
self.assertEqual(paste._macos_keys("option+v"), paste._macos_keys("alt+v"))
|
||||
self.assertEqual(paste._macos_keys("control+v"), paste._macos_keys("ctrl+v"))
|
||||
|
||||
def test_case_and_spacing_do_not_matter(self):
|
||||
self.assertEqual(paste._macos_keys(" Cmd + V "), paste._macos_keys("cmd+v"))
|
||||
|
||||
def test_a_key_nobody_mapped_is_refused_before_anything_is_posted(self):
|
||||
with self.assertRaises(paste.PasteError) as caught:
|
||||
paste.press("cmd+f13")
|
||||
self.assertIn("f13", str(caught.exception))
|
||||
self.assertEqual(self.api.posted, [])
|
||||
|
||||
def test_a_modifier_nobody_mapped(self):
|
||||
with self.assertRaises(paste.PasteError) as caught:
|
||||
paste.press("hyper+v")
|
||||
self.assertIn("hyper", str(caught.exception))
|
||||
|
||||
def test_nothing_at_all(self):
|
||||
with self.assertRaises(paste.PasteError):
|
||||
paste.press("+")
|
||||
|
||||
def test_nothing_is_typed_until_macos_says_so(self):
|
||||
self.api.trusted = False
|
||||
with self.assertRaises(paste.PasteError) as caught:
|
||||
paste.press("cmd+v")
|
||||
self.assertIn("Accessibility", str(caught.exception))
|
||||
self.assertEqual(self.api.posted, [])
|
||||
|
||||
def test_the_permission_pane_is_opened_once_and_not_again(self):
|
||||
"""It is a window in the user's face, and one paste is every dictation."""
|
||||
self.api.trusted = False
|
||||
for _ in range(3):
|
||||
with self.assertRaises(paste.PasteError):
|
||||
paste.press("cmd+v")
|
||||
self.opened.assert_called_once()
|
||||
self.assertIn("Privacy_Accessibility", self.opened.call_args.args[0][1])
|
||||
|
||||
def test_a_system_that_will_not_open_the_pane_still_says_what_is_wrong(self):
|
||||
self.api.trusted = False
|
||||
self.opened.side_effect = OSError("no open(1) here")
|
||||
with self.assertRaises(paste.PasteError) as caught:
|
||||
paste.press("cmd+v")
|
||||
self.assertIn("Accessibility", str(caught.exception))
|
||||
|
||||
def test_readiness_is_the_permission_rather_than_a_program(self):
|
||||
self.assertTrue(paste.paste_ready())
|
||||
self.api.trusted = False
|
||||
self.assertFalse(paste.paste_ready())
|
||||
|
||||
def test_an_event_that_could_not_be_made_takes_its_pair_with_it(self):
|
||||
self.api.makes = 1 # the second one comes back null
|
||||
with self.assertRaises(paste.PasteError):
|
||||
paste.press("cmd+v")
|
||||
self.assertEqual(self.api.released, [1001])
|
||||
self.assertEqual(self.api.posted, [])
|
||||
|
||||
def test_the_frameworks_not_being_there_is_not_a_crash(self):
|
||||
"""Every other system imports this module too, and must survive it."""
|
||||
self.patch_attr(paste, "_macos_api",
|
||||
mock.Mock(side_effect=paste.PasteError("no such library")))
|
||||
self.assertFalse(paste.paste_ready())
|
||||
|
||||
|
||||
class MacClipboardSnapshot(DikteTest):
|
||||
def test_every_native_type_is_restored_and_the_files_are_removed(self):
|
||||
directory = tempfile.mkdtemp(prefix="dikte-test-clipboard-")
|
||||
manifest = '[[{"type":"public.tiff","file":"0-0.bin"}]]'
|
||||
pathlib.Path(directory, "0-0.bin").write_bytes(b"a TIFF")
|
||||
snapshot = paste._MAC_SNAPSHOT(directory, manifest)
|
||||
|
||||
with mock.patch.object(subprocess, "run",
|
||||
return_value=FakeCompleted()) as run:
|
||||
paste.copy_bytes(snapshot)
|
||||
|
||||
self.assertEqual(run.call_args.kwargs["input"], manifest)
|
||||
self.assertEqual(run.call_args.kwargs["env"]["DIKTE_PASTEBOARD_DIR"],
|
||||
directory)
|
||||
self.assertFalse(os.path.exists(directory))
|
||||
|
||||
def test_a_failed_snapshot_leaves_no_temporary_directory(self):
|
||||
directory = tempfile.mkdtemp(prefix="dikte-test-clipboard-")
|
||||
with mock.patch.object(paste.tempfile, "mkdtemp", return_value=directory), \
|
||||
mock.patch.object(subprocess, "run",
|
||||
return_value=FakeCompleted(stdout=b"not json")):
|
||||
self.assertIsNone(paste._macos_snapshot())
|
||||
self.assertFalse(os.path.exists(directory))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
+39
-2
@@ -6,7 +6,9 @@ save, so a setting added to one half and not the other is silently reset the
|
||||
next time anybody presses Save. That is the failure this catches.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
from typing import ClassVar
|
||||
from unittest import mock
|
||||
|
||||
from PyQt6.QtWidgets import QApplication, QMessageBox
|
||||
@@ -15,6 +17,7 @@ import cleanup
|
||||
import config as cfg
|
||||
import hotkey
|
||||
import overlay as overlay_module
|
||||
import paste
|
||||
import settings_ui
|
||||
from tests.support import DikteTest, only_these_tools
|
||||
|
||||
@@ -97,10 +100,16 @@ CHANGED = {
|
||||
|
||||
|
||||
class Settings(DikteTest):
|
||||
# What a Mac shows instead, where the combination on offer is a different
|
||||
# one. Everything else about the window is the same on both.
|
||||
changed = CHANGED
|
||||
platform = "linux"
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# No pactl, no model lists over the network, and no modal dialogue
|
||||
# waiting for somebody to press OK.
|
||||
self.enterContext(mock.patch.object(sys, "platform", self.platform))
|
||||
self.enterContext(only_these_tools())
|
||||
self.enterContext(mock.patch.object(QMessageBox, "information"))
|
||||
self.enterContext(mock.patch.object(settings_ui.SettingsWindow,
|
||||
@@ -133,11 +142,11 @@ class Settings(DikteTest):
|
||||
self.assertEqual(conf.data, before)
|
||||
|
||||
def test_a_setting_of_your_own_survives_the_round_trip(self):
|
||||
self.write_config(CHANGED)
|
||||
self.write_config(self.changed)
|
||||
conf = cfg.Config()
|
||||
self.window(conf)._save()
|
||||
stored = self.read_config_file()
|
||||
for key, value in CHANGED.items():
|
||||
for key, value in self.changed.items():
|
||||
with self.subTest(key=key):
|
||||
self.assertEqual(stored[key], value)
|
||||
|
||||
@@ -304,6 +313,34 @@ class Settings(DikteTest):
|
||||
self.assertFalse(window.file_stop.isEnabled())
|
||||
|
||||
|
||||
class MacSettings(Settings):
|
||||
"""The same window and the same round trip, standing on a Mac.
|
||||
|
||||
Nothing here is about macOS: it is the rest of the window, checked on the
|
||||
platform where three of its widgets are gone and one offers other keys.
|
||||
"""
|
||||
|
||||
platform = "darwin"
|
||||
changed: ClassVar[dict] = {**CHANGED, "paste_shortcut": "cmd+shift+v"}
|
||||
|
||||
def test_there_is_no_install_button_where_nothing_is_installed(self):
|
||||
window = self.window(cfg.Config())
|
||||
labels = [button.text() for button in
|
||||
window.findChildren(settings_ui.QPushButton)]
|
||||
self.assertFalse([text for text in labels if "shortcut" in text.lower()])
|
||||
|
||||
def test_the_listener_is_not_offered_as_a_choice(self):
|
||||
"""It is the whole mechanism there; turning it off would leave nothing."""
|
||||
window = self.window(cfg.Config())
|
||||
self.assertFalse(window.evdev_enabled.isVisible())
|
||||
|
||||
def test_the_paste_keys_on_offer_are_the_ones_a_mac_uses(self):
|
||||
window = self.window(cfg.Config())
|
||||
offered = [window.paste_shortcut.itemText(index)
|
||||
for index in range(window.paste_shortcut.count())]
|
||||
self.assertEqual(offered, paste.MACOS.shortcuts)
|
||||
|
||||
|
||||
class Overlay(DikteTest):
|
||||
def overlay(self, **kwargs):
|
||||
widget = overlay_module.Overlay(**kwargs)
|
||||
|
||||
+14
-3
@@ -32,7 +32,7 @@ class Chain(DikteTest):
|
||||
transcript="uh, book it for Thursday",
|
||||
cleaned="Book it for Thursday.",
|
||||
cleanup_error=None, answer=("Booked.", ""), rms=None,
|
||||
clipboard=b"what was there before"):
|
||||
clipboard=b"what was there before", paste_error=None):
|
||||
pipeline = worker.Pipeline(self.conf)
|
||||
done, failures, stages, cancels = [], [], [], []
|
||||
pipeline.finished.connect(lambda *args: done.append(args))
|
||||
@@ -52,10 +52,13 @@ class Chain(DikteTest):
|
||||
mock.patch.object(paste, "copy") as copy, \
|
||||
mock.patch.object(paste, "copy_bytes") as copy_bytes, \
|
||||
mock.patch.object(paste, "press") as press, \
|
||||
mock.patch.object(paste, "read_clipboard", return_value=clipboard), \
|
||||
mock.patch.object(paste, "read_clipboard",
|
||||
return_value=clipboard) as read_clipboard, \
|
||||
mock.patch.object(worker.time, "sleep", lambda seconds: None):
|
||||
press.side_effect = paste_error
|
||||
calls = {"transcribe": tr, "cleanup": cleanup, "ask": ask_call,
|
||||
"copy": copy, "copy_bytes": copy_bytes, "press": press}
|
||||
"copy": copy, "copy_bytes": copy_bytes, "press": press,
|
||||
"read_clipboard": read_clipboard}
|
||||
pipeline._work(self.wav, duration,
|
||||
self.rms if rms is None else rms, ask, paste_override)
|
||||
return {"done": done, "failures": failures, "stages": stages,
|
||||
@@ -83,9 +86,11 @@ class Chain(DikteTest):
|
||||
|
||||
def test_auto_paste_switched_off_only_copies(self):
|
||||
self.conf["auto_paste"] = False
|
||||
self.conf["restore_clipboard"] = True
|
||||
run = self.run_chain()
|
||||
run["copy"].assert_called_once()
|
||||
run["press"].assert_not_called()
|
||||
run["read_clipboard"].assert_not_called()
|
||||
|
||||
def test_a_run_asked_for_from_a_terminal_pastes_nowhere(self):
|
||||
"""The text comes back down the socket; the focused window is nobody's."""
|
||||
@@ -103,6 +108,12 @@ class Chain(DikteTest):
|
||||
run = self.run_chain()
|
||||
run["copy_bytes"].assert_not_called()
|
||||
|
||||
def test_the_clipboard_is_put_back_when_the_keypress_fails(self):
|
||||
self.conf["restore_clipboard"] = True
|
||||
run = self.run_chain(paste_error=paste.PasteError("not trusted"))
|
||||
self.assertIn("not trusted", run["failures"][0])
|
||||
run["copy_bytes"].assert_called_once_with(b"what was there before")
|
||||
|
||||
def test_the_transcription_is_told_the_language_and_the_glossary(self):
|
||||
self.conf["language"] = "tr"
|
||||
self.conf["transcribe_prompt"] = "Paraşüt"
|
||||
|
||||
Reference in New Issue
Block a user