Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 81f1757357 |
@@ -4,7 +4,6 @@ import subprocess
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from fenrirscreenreader.core.i18n import _
|
from fenrirscreenreader.core.i18n import _
|
||||||
from fenrirscreenreader.utils.speechd_utils import get_synthesis_voice_name
|
|
||||||
|
|
||||||
|
|
||||||
class command:
|
class command:
|
||||||
@@ -274,13 +273,29 @@ class command:
|
|||||||
for line in lines[1:]:
|
for line in lines[1:]:
|
||||||
if not line.strip():
|
if not line.strip():
|
||||||
continue
|
continue
|
||||||
voice = get_synthesis_voice_name(module, line)
|
if module.lower() == "espeak-ng":
|
||||||
if voice:
|
voice = self.process_espeak_voice(line)
|
||||||
voices.append(voice)
|
if voice:
|
||||||
|
voices.append(voice)
|
||||||
|
else:
|
||||||
|
voices.append(line.strip())
|
||||||
return voices
|
return voices
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def process_espeak_voice(self, voiceLine):
|
||||||
|
"""Process espeak voice format"""
|
||||||
|
parts = [p for p in voiceLine.split() if p]
|
||||||
|
if len(parts) < 2:
|
||||||
|
return None
|
||||||
|
lang_code = parts[-2].lower()
|
||||||
|
variant = parts[-1].lower()
|
||||||
|
return (
|
||||||
|
f"{lang_code}+{variant}"
|
||||||
|
if variant and variant != "none"
|
||||||
|
else lang_code
|
||||||
|
)
|
||||||
|
|
||||||
def set_callback(self, callback):
|
def set_callback(self, callback):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import threading
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from fenrirscreenreader.core.i18n import _
|
from fenrirscreenreader.core.i18n import _
|
||||||
from fenrirscreenreader.utils.speechd_utils import get_synthesis_voice_name
|
|
||||||
|
|
||||||
|
|
||||||
class command:
|
class command:
|
||||||
@@ -180,9 +179,12 @@ class command:
|
|||||||
for line in lines[1:]:
|
for line in lines[1:]:
|
||||||
if not line.strip():
|
if not line.strip():
|
||||||
continue
|
continue
|
||||||
voice = get_synthesis_voice_name(module, line)
|
if module.lower() == "espeak-ng":
|
||||||
if voice:
|
voice = self.process_espeak_voice(line)
|
||||||
voices.append(voice)
|
if voice:
|
||||||
|
voices.append(voice)
|
||||||
|
else:
|
||||||
|
voices.append(line.strip())
|
||||||
|
|
||||||
# Limit voice count to prevent memory issues
|
# Limit voice count to prevent memory issues
|
||||||
if len(voices) > 1000:
|
if len(voices) > 1000:
|
||||||
@@ -203,5 +205,21 @@ class command:
|
|||||||
)
|
)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def process_espeak_voice(self, voiceLine):
|
||||||
|
"""Process espeak voice format"""
|
||||||
|
try:
|
||||||
|
parts = [p for p in voiceLine.split() if p]
|
||||||
|
if len(parts) < 2:
|
||||||
|
return None
|
||||||
|
lang_code = parts[-2].lower()
|
||||||
|
variant = parts[-1].lower()
|
||||||
|
return (
|
||||||
|
f"{lang_code}+{variant}"
|
||||||
|
if variant and variant != "none"
|
||||||
|
else lang_code
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
def set_callback(self, callback):
|
def set_callback(self, callback):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ class command:
|
|||||||
return
|
return
|
||||||
if not self.env["runtime"]["ScreenManager"].is_delta():
|
if not self.env["runtime"]["ScreenManager"].is_delta():
|
||||||
return
|
return
|
||||||
if not self.env["screen"].get("new_delta_is_typing", False):
|
|
||||||
return
|
|
||||||
|
|
||||||
x_move = (
|
x_move = (
|
||||||
self.env["screen"]["new_cursor"]["x"]
|
self.env["screen"]["new_cursor"]["x"]
|
||||||
|
|||||||
@@ -79,11 +79,10 @@ class command:
|
|||||||
# updates
|
# updates
|
||||||
delta_text = self.env["screen"]["new_delta"]
|
delta_text = self.env["screen"]["new_delta"]
|
||||||
delta_length = len(delta_text)
|
delta_length = len(delta_text)
|
||||||
explicit_progress_lines = self.get_explicit_progress_lines(delta_text)
|
|
||||||
if (
|
if (
|
||||||
delta_length > 200
|
delta_length > 200
|
||||||
): # Allow longer progress lines such as terminal status output
|
): # Allow longer progress lines such as terminal status output
|
||||||
if not explicit_progress_lines:
|
if not self.is_explicit_progress_delta(delta_text):
|
||||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
f"Progress filter: delta too long ({delta_length})",
|
f"Progress filter: delta too long ({delta_length})",
|
||||||
debug.DebugLevel.INFO,
|
debug.DebugLevel.INFO,
|
||||||
@@ -92,11 +91,7 @@ class command:
|
|||||||
|
|
||||||
# If delta contains newlines and is substantial, let incoming handler
|
# If delta contains newlines and is substantial, let incoming handler
|
||||||
# deal with it to avoid interfering with multi-line text output
|
# deal with it to avoid interfering with multi-line text output
|
||||||
if (
|
if '\n' in delta_text and delta_length > 50:
|
||||||
"\n" in delta_text
|
|
||||||
and delta_length > 50
|
|
||||||
and not explicit_progress_lines
|
|
||||||
):
|
|
||||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
f"Progress filter: multiline delta ({delta_length} chars)",
|
f"Progress filter: multiline delta ({delta_length} chars)",
|
||||||
debug.DebugLevel.INFO,
|
debug.DebugLevel.INFO,
|
||||||
@@ -124,36 +119,14 @@ class command:
|
|||||||
if not has_percentage:
|
if not has_percentage:
|
||||||
return self.has_progress_status_fields(text)
|
return self.has_progress_status_fields(text)
|
||||||
|
|
||||||
bracketed_bar = re.search(
|
|
||||||
r"\[(?=[#=*>█▉▊▋▌▍▎▏▒▓░.\s-]{2,}\])"
|
|
||||||
r"(?=[^\]]*[#=*>█▉▊▋▌▍▎▏▒▓░])"
|
|
||||||
r"[#=*>█▉▊▋▌▍▎▏▒▓░.\s-]+\]",
|
|
||||||
text,
|
|
||||||
)
|
|
||||||
pipe_bar = re.search(
|
|
||||||
r"\|(?=[#=*>█▉▊▋▌▍▎▏▒▓░.\s-]{2,}\|)"
|
|
||||||
r"(?=[^|]*[#=*>█▉▊▋▌▍▎▏▒▓░])"
|
|
||||||
r"[#=*>█▉▊▋▌▍▎▏▒▓░.\s-]+\|",
|
|
||||||
text,
|
|
||||||
)
|
|
||||||
transfer_rate = re.search(
|
|
||||||
r"\b\d+(?:\.\d+)?\s*[kKmMgGtT](?:i?B)?/s\b",
|
|
||||||
text,
|
|
||||||
)
|
|
||||||
return bool(
|
return bool(
|
||||||
bracketed_bar
|
re.search(
|
||||||
or pipe_bar
|
r"[|\[\]#=*>█▉▊▋▌▍▎▏▒▓░]"
|
||||||
or transfer_rate
|
r"|\b\d+(?:\.\d+)?\s*[kKmMgGtT](?:i?B)?/s\b",
|
||||||
|
text,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_explicit_progress_lines(self, text):
|
|
||||||
"""Return structurally explicit progress rows from a screen delta."""
|
|
||||||
return [
|
|
||||||
line
|
|
||||||
for line in text.splitlines()
|
|
||||||
if self.is_explicit_progress_delta(line)
|
|
||||||
]
|
|
||||||
|
|
||||||
def reset_progress_state(self):
|
def reset_progress_state(self):
|
||||||
"""Reset progress state when a prompt is detected, allowing new progress operations to start fresh"""
|
"""Reset progress state when a prompt is detected, allowing new progress operations to start fresh"""
|
||||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
@@ -167,15 +140,6 @@ class command:
|
|||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
if "\n" in text:
|
|
||||||
explicit_progress_lines = self.get_explicit_progress_lines(text)
|
|
||||||
if not explicit_progress_lines:
|
|
||||||
return
|
|
||||||
# Parallel transfer tools commonly repaint several package rows,
|
|
||||||
# followed by an aggregate row. One tone per update is enough, and
|
|
||||||
# the final explicit row is normally that aggregate.
|
|
||||||
text = explicit_progress_lines[-1]
|
|
||||||
|
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
|
|
||||||
# Debug: Print what we're checking
|
# Debug: Print what we're checking
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import subprocess
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from fenrirscreenreader.core import debug
|
from fenrirscreenreader.core import debug
|
||||||
from fenrirscreenreader.utils.speechd_utils import get_synthesis_voice_name
|
|
||||||
|
|
||||||
|
|
||||||
class DynamicVoiceCommand:
|
class DynamicVoiceCommand:
|
||||||
@@ -355,10 +354,30 @@ def get_module_voices(module):
|
|||||||
for line in lines[1:]:
|
for line in lines[1:]:
|
||||||
if not line.strip():
|
if not line.strip():
|
||||||
continue
|
continue
|
||||||
voice = get_synthesis_voice_name(module, line)
|
if module.lower() == "espeak-ng":
|
||||||
if voice:
|
voice = process_espeak_voice(line)
|
||||||
voices.append(voice)
|
if voice:
|
||||||
|
voices.append(voice)
|
||||||
|
else:
|
||||||
|
voices.append(line.strip())
|
||||||
return voices
|
return voices
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def process_espeak_voice(voiceLine):
|
||||||
|
"""Process espeak voice format"""
|
||||||
|
try:
|
||||||
|
parts = [p for p in voiceLine.split() if p]
|
||||||
|
if len(parts) < 2:
|
||||||
|
return None
|
||||||
|
lang_code = parts[-2].lower()
|
||||||
|
variant = parts[-1].lower()
|
||||||
|
return (
|
||||||
|
f"{lang_code}+{variant}"
|
||||||
|
if variant and variant != "none"
|
||||||
|
else lang_code
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|||||||
@@ -4,16 +4,11 @@
|
|||||||
# Fenrir TTY screen reader
|
# Fenrir TTY screen reader
|
||||||
# By Chrys, Storm Dragon, and contributors.
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import time
|
|
||||||
|
|
||||||
from fenrirscreenreader.core import debug
|
from fenrirscreenreader.core import debug
|
||||||
from fenrirscreenreader.core.i18n import _
|
from fenrirscreenreader.core.i18n import _
|
||||||
from fenrirscreenreader.core.settingsData import settings_data
|
from fenrirscreenreader.core.settingsData import settings_data
|
||||||
from fenrirscreenreader.utils.speechd_utils import (
|
import subprocess
|
||||||
get_synthesis_voice_name,
|
import time
|
||||||
parse_synthesis_voice_line,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SpeechHelperMixin:
|
class SpeechHelperMixin:
|
||||||
@@ -94,15 +89,20 @@ class SpeechHelperMixin:
|
|||||||
for line in lines[1:]:
|
for line in lines[1:]:
|
||||||
if not line.strip():
|
if not line.strip():
|
||||||
continue
|
continue
|
||||||
if module.lower() == "voxin":
|
if module.lower() == "espeak-ng":
|
||||||
|
voice = self._process_espeak_voice(line)
|
||||||
|
if voice:
|
||||||
|
voices.append(voice)
|
||||||
|
elif module.lower() == "voxin":
|
||||||
# For Voxin, store voice name with language
|
# For Voxin, store voice name with language
|
||||||
voice_data = self._process_voxin_voice(line)
|
voice_data = self._process_voxin_voice(line)
|
||||||
if voice_data:
|
if voice_data:
|
||||||
voices.append(voice_data)
|
voices.append(voice_data)
|
||||||
else:
|
else:
|
||||||
voice = get_synthesis_voice_name(module, line)
|
# For other modules, extract first field (voice name)
|
||||||
if voice:
|
parts = line.strip().split()
|
||||||
voices.append(voice)
|
if parts:
|
||||||
|
voices.append(parts[0])
|
||||||
|
|
||||||
self._voices_cache[module] = voices
|
self._voices_cache[module] = voices
|
||||||
return voices
|
return voices
|
||||||
@@ -115,6 +115,23 @@ class SpeechHelperMixin:
|
|||||||
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def _process_espeak_voice(self, voice_line):
|
||||||
|
"""Process espeak-ng voice format into usable voice name.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
voice_line (str): Raw line from spd-say -L output
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Processed voice name (e.g., 'en-us' or 'en-us+f3')
|
||||||
|
"""
|
||||||
|
parts = [p for p in voice_line.split() if p]
|
||||||
|
if len(parts) < 2:
|
||||||
|
return None
|
||||||
|
lang_code = parts[-2].lower()
|
||||||
|
variant = parts[-1].lower()
|
||||||
|
return (f"{lang_code}+{variant}"
|
||||||
|
if variant and variant != "none" else lang_code)
|
||||||
|
|
||||||
def _process_voxin_voice(self, voice_line):
|
def _process_voxin_voice(self, voice_line):
|
||||||
"""Process Voxin voice format with language information.
|
"""Process Voxin voice format with language information.
|
||||||
|
|
||||||
@@ -125,12 +142,11 @@ class SpeechHelperMixin:
|
|||||||
Returns:
|
Returns:
|
||||||
str: Voice name with language encoded (e.g., 'daniel-embedded-high|en-GB')
|
str: Voice name with language encoded (e.g., 'daniel-embedded-high|en-GB')
|
||||||
"""
|
"""
|
||||||
voice_data = parse_synthesis_voice_line(voice_line)
|
parts = [p for p in voice_line.split() if p]
|
||||||
if voice_data is None:
|
if len(parts) < 2:
|
||||||
return None
|
|
||||||
voice_name, language, _variant = voice_data
|
|
||||||
if not language:
|
|
||||||
return None
|
return None
|
||||||
|
voice_name = parts[0]
|
||||||
|
language = parts[1]
|
||||||
# Encode language with voice for later extraction
|
# Encode language with voice for later extraction
|
||||||
return f"{voice_name}|{language}"
|
return f"{voice_name}|{language}"
|
||||||
|
|
||||||
|
|||||||
@@ -4,5 +4,5 @@
|
|||||||
# Fenrir TTY screen reader
|
# Fenrir TTY screen reader
|
||||||
# By Chrys, Storm Dragon, and contributors.
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
version = "2026.07.25"
|
version = "2026.07.15"
|
||||||
code_name = "testing"
|
code_name = "master"
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Fenrir TTY screen reader
|
|
||||||
# By Chrys, Storm Dragon, and contributors.
|
|
||||||
|
|
||||||
|
|
||||||
def parse_synthesis_voice_line(voice_line):
|
|
||||||
"""Parse one column-aligned spd-say synthesis voice row."""
|
|
||||||
fields = voice_line.rsplit(maxsplit=2)
|
|
||||||
if len(fields) != 3:
|
|
||||||
return None
|
|
||||||
|
|
||||||
name, language, variant = (field.strip() for field in fields)
|
|
||||||
if not name:
|
|
||||||
return None
|
|
||||||
return name, language, variant
|
|
||||||
|
|
||||||
|
|
||||||
def get_synthesis_voice_name(module, voice_line):
|
|
||||||
"""Return the value Speech Dispatcher expects for voice selection."""
|
|
||||||
voice_data = parse_synthesis_voice_line(voice_line)
|
|
||||||
if voice_data is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
name, language, variant = voice_data
|
|
||||||
if module.lower() != "espeak-ng":
|
|
||||||
return name
|
|
||||||
|
|
||||||
language = language.lower()
|
|
||||||
variant = variant.lower()
|
|
||||||
if not language:
|
|
||||||
return None
|
|
||||||
if variant and variant != "none":
|
|
||||||
return f"{language}+{variant}"
|
|
||||||
return language
|
|
||||||
@@ -238,55 +238,3 @@ def test_progress_detector_ignores_generic_key_value_text():
|
|||||||
|
|
||||||
command.play_activity_beep.assert_not_called()
|
command.play_activity_beep.assert_not_called()
|
||||||
command.play_progress_tone.assert_not_called()
|
command.play_progress_tone.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
|
||||||
def test_progress_detector_beeps_for_pacman_total_in_multiline_delta():
|
|
||||||
progress_module = _load_progress_module()
|
|
||||||
command = progress_module.command()
|
|
||||||
total_line = (
|
|
||||||
"Total ( 3/749) "
|
|
||||||
"830.7 MiB 4.70 MiB/s 04:05 "
|
|
||||||
"[#################################################"
|
|
||||||
"-----------------------------------------------------------------------] 41%"
|
|
||||||
)
|
|
||||||
sample = (
|
|
||||||
"package-one 125.0 MiB 2.10 MiB/s 00:12 "
|
|
||||||
"[####################--------------------] 50%\n"
|
|
||||||
+ total_line
|
|
||||||
)
|
|
||||||
command.env = {
|
|
||||||
"commandBuffer": {
|
|
||||||
"progress_monitoring": True,
|
|
||||||
"lastProgressValue": -1,
|
|
||||||
"lastProgressTime": 0,
|
|
||||||
},
|
|
||||||
"runtime": {
|
|
||||||
"DebugManager": Mock(write_debug_out=Mock()),
|
|
||||||
"ScreenManager": Mock(is_screen_change=Mock(return_value=False)),
|
|
||||||
"CursorManager": Mock(is_cursor_vertical_move=Mock(return_value=False)),
|
|
||||||
},
|
|
||||||
"screen": {
|
|
||||||
"new_delta": sample,
|
|
||||||
"new_delta_is_typing": False,
|
|
||||||
"new_content_text": sample,
|
|
||||||
"old_cursor": {"x": 0, "y": 0},
|
|
||||||
"new_cursor": {"x": 0, "y": 0},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
command.play_progress_tone = Mock()
|
|
||||||
|
|
||||||
command.run()
|
|
||||||
|
|
||||||
command.play_progress_tone.assert_called_once_with(41.0)
|
|
||||||
assert command.env["commandBuffer"]["lastProgressValue"] == 41.0
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
|
||||||
def test_explicit_progress_delta_ignores_percentage_in_bracketed_prose():
|
|
||||||
progress_module = _load_progress_module()
|
|
||||||
command = progress_module.command()
|
|
||||||
|
|
||||||
assert not command.is_explicit_progress_delta(
|
|
||||||
"Release notes [issue #749] are 41% complete"
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
from types import SimpleNamespace
|
|
||||||
from unittest.mock import Mock
|
|
||||||
|
|
||||||
from fenrirscreenreader.commands.commands import voice_browser
|
|
||||||
from fenrirscreenreader.commands.commands import voice_browser_safe
|
|
||||||
from fenrirscreenreader.core import dynamicVoiceMenu
|
|
||||||
from fenrirscreenreader.core.quickMenuManager import SpeechHelperMixin
|
|
||||||
from fenrirscreenreader.utils.speechd_utils import get_synthesis_voice_name
|
|
||||||
|
|
||||||
|
|
||||||
VOICE_LIST = (
|
|
||||||
" NAME LANGUAGE VARIANT\n"
|
|
||||||
" Perfect Paul en-US none\n"
|
|
||||||
" Big Bob en-US none\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def completed_voice_list():
|
|
||||||
return SimpleNamespace(returncode=0, stdout=VOICE_LIST)
|
|
||||||
|
|
||||||
|
|
||||||
def test_quick_menu_preserves_multiword_synthesis_voice_names(monkeypatch):
|
|
||||||
monkeypatch.setattr(
|
|
||||||
"fenrirscreenreader.core.quickMenuManager.subprocess.run",
|
|
||||||
Mock(return_value=completed_voice_list()),
|
|
||||||
)
|
|
||||||
helper = SpeechHelperMixin()
|
|
||||||
helper.env = {"runtime": {"DebugManager": Mock()}}
|
|
||||||
|
|
||||||
assert helper.get_module_voices("doubletalk") == [
|
|
||||||
"Perfect Paul",
|
|
||||||
"Big Bob",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def test_safe_voice_browser_preserves_multiword_synthesis_voice_names(
|
|
||||||
monkeypatch,
|
|
||||||
):
|
|
||||||
monkeypatch.setattr(
|
|
||||||
voice_browser_safe.subprocess,
|
|
||||||
"run",
|
|
||||||
Mock(return_value=completed_voice_list()),
|
|
||||||
)
|
|
||||||
browser = voice_browser_safe.command()
|
|
||||||
browser.initialize({"runtime": {}})
|
|
||||||
|
|
||||||
assert browser.get_module_voices_with_timeout("doubletalk") == [
|
|
||||||
"Perfect Paul",
|
|
||||||
"Big Bob",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def test_interactive_voice_browser_preserves_multiword_synthesis_voice_names(
|
|
||||||
monkeypatch,
|
|
||||||
):
|
|
||||||
monkeypatch.setattr(
|
|
||||||
voice_browser.subprocess,
|
|
||||||
"run",
|
|
||||||
Mock(return_value=completed_voice_list()),
|
|
||||||
)
|
|
||||||
browser = voice_browser.command()
|
|
||||||
|
|
||||||
assert browser.get_module_voices("doubletalk") == [
|
|
||||||
"Perfect Paul",
|
|
||||||
"Big Bob",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def test_dynamic_voice_menu_preserves_multiword_synthesis_voice_names(
|
|
||||||
monkeypatch,
|
|
||||||
):
|
|
||||||
monkeypatch.setattr(
|
|
||||||
dynamicVoiceMenu.subprocess,
|
|
||||||
"run",
|
|
||||||
Mock(return_value=completed_voice_list()),
|
|
||||||
)
|
|
||||||
|
|
||||||
assert dynamicVoiceMenu.get_module_voices("doubletalk") == [
|
|
||||||
"Perfect Paul",
|
|
||||||
"Big Bob",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def test_espeak_voice_selection_keeps_language_and_variant_behavior():
|
|
||||||
voice = get_synthesis_voice_name(
|
|
||||||
"espeak-ng",
|
|
||||||
"English (America)+female3 en-US female3",
|
|
||||||
)
|
|
||||||
|
|
||||||
assert voice == "en-us+female3"
|
|
||||||
@@ -344,7 +344,6 @@ def test_large_insertion_echo_speaks_pasted_cursor_text():
|
|||||||
"old_cursor": {"x": 2, "y": 0},
|
"old_cursor": {"x": 2, "y": 0},
|
||||||
"new_cursor": {"x": 13, "y": 0},
|
"new_cursor": {"x": 13, "y": 0},
|
||||||
"new_delta": "hello world",
|
"new_delta": "hello world",
|
||||||
"new_delta_is_typing": True,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
command = large_insertion_module.command()
|
command = large_insertion_module.command()
|
||||||
@@ -360,41 +359,6 @@ def test_large_insertion_echo_speaks_pasted_cursor_text():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
|
||||||
def test_large_insertion_echo_ignores_incoming_cursor_progress():
|
|
||||||
large_insertion_module = _load_large_insertion_module()
|
|
||||||
output_manager = Mock(present_text=Mock())
|
|
||||||
settings_manager = Mock()
|
|
||||||
settings_manager.get_setting_as_bool.return_value = False
|
|
||||||
input_manager = Mock()
|
|
||||||
input_manager.get_last_deepest_input.return_value = []
|
|
||||||
input_manager.get_last_event.return_value = None
|
|
||||||
screen_manager = Mock(
|
|
||||||
is_screen_change=Mock(return_value=False),
|
|
||||||
is_delta=Mock(return_value=True),
|
|
||||||
)
|
|
||||||
env = {
|
|
||||||
"runtime": {
|
|
||||||
"InputManager": input_manager,
|
|
||||||
"OutputManager": output_manager,
|
|
||||||
"ScreenManager": screen_manager,
|
|
||||||
"SettingsManager": settings_manager,
|
|
||||||
},
|
|
||||||
"screen": {
|
|
||||||
"old_cursor": {"x": 0, "y": 0},
|
|
||||||
"new_cursor": {"x": 12, "y": 0},
|
|
||||||
"new_delta": "upgrading 1/3",
|
|
||||||
"new_delta_is_typing": False,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
command = large_insertion_module.command()
|
|
||||||
command.initialize(env)
|
|
||||||
|
|
||||||
command.run()
|
|
||||||
|
|
||||||
output_manager.present_text.assert_not_called()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
def test_large_insertion_echo_defers_recent_tab_to_tab_completion():
|
def test_large_insertion_echo_defers_recent_tab_to_tab_completion():
|
||||||
large_insertion_module = _load_large_insertion_module()
|
large_insertion_module = _load_large_insertion_module()
|
||||||
|
|||||||
Reference in New Issue
Block a user