Files

112 lines
4.0 KiB
Python

"""Sanitizing chat-formatted replies into speakable prose. Pure string logic
— no audio hardware, no Qt (see the testing conventions in CLAUDE.md)."""
import sys
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from bolt_assistant.speech_text import for_display, for_speech, is_question
def test_bold_markers_are_not_spoken():
assert "*" not in for_speech("Here's the **maji-desktop** snapshot")
assert for_speech("Here's the **maji-desktop** snapshot") == "Here's the maji-desktop snapshot"
def test_italics_and_underscores_dropped():
assert for_speech("that is _really_ odd") == "that is really odd"
assert for_speech("***everything*** is fine") == "everything is fine"
def test_bullet_list_becomes_sentences():
spoken = for_speech(
"System status:\n"
"* **OS:** Linux 7.0.0\n"
"* **Uptime:** 1 day\n"
)
assert "*" not in spoken
assert spoken == "System status: OS: Linux 7.0.0. Uptime: 1 day."
def test_emoji_and_symbols_removed():
assert for_speech("✅ Done 🚀 — all good") == "Done all good"
assert for_speech("→ next step") == "to next step"
def test_urls_and_code_are_not_read_out_character_by_character():
assert for_speech("see https://example.com/x?y=1 for docs") == "see link for docs"
assert for_speech("run `sudo reboot` now") == "run sudo reboot now"
assert for_speech("here:\n```\nls -la\n```\n") == "here: (code)."
def test_headings_quotes_and_rules_stripped():
assert for_speech("## Summary\n---\n> quoted bit") == "Summary. quoted bit."
def test_ampersand_and_percent_are_spoken_as_words():
assert for_speech("R&D at 50% capacity") == "R and D at 50 percent capacity"
def test_blank_and_symbol_only_input():
assert for_speech("") == ""
assert for_speech(None) == ""
assert for_speech("***") == ""
def test_display_keeps_emoji_but_drops_markdown():
assert for_display("**Done** ✅") == "Done ✅"
assert for_display("* one\n* two") == "• one • two"
def test_is_question_only_fires_on_a_trailing_question():
assert is_question("Ready to run a command or start a project?")
assert is_question("It's 7:15 AM. Want me to set a timer?")
assert not is_question("It's 7:15 AM on July 23, 2026.")
assert not is_question("What time is it? It's 7:15 AM.") # asked in passing
def test_is_question_ignores_trailing_decoration():
assert is_question("Ready to go? 🚀")
assert is_question('Shall I continue?"')
assert is_question("Want me to fix it? **")
def test_is_question_ignores_question_marks_that_are_not_spoken():
# The '?' here is inside a URL query string, which for_speech strips.
assert not is_question("Docs are at https://example.com/x?y=1")
assert not is_question("")
assert not is_question(None)
# ── ElevenLabs v3 delivery tags (observed live 2026-07-31) ──────────────────
# "[laughing] That one came through clean" was spoken as "laughing That one
# came through clean": the tags mean something to the dialogue model inside a
# dialoguectl scene, and nothing at all to the ordinary reply voice.
def test_delivery_tags_are_never_spoken_aloud():
spoken = for_speech("[laughing] That one came through clean.")
assert "laughing" not in spoken
assert spoken.startswith("That one came through clean")
def test_the_bubble_does_not_caption_a_laugh_nobody_heard():
assert "[laughing]" not in for_display("[laughing] All good.")
@pytest.mark.parametrize("tag", [
"[whispering]", "[cheerfully]", "[sighs]", "[laughs]", "[clears throat]",
"[nervously]", "[excited]", "[pause]", "[shouting]",
])
def test_the_common_tags_are_all_covered(tag):
assert "" == for_speech(tag).strip()
def test_ordinary_bracketed_text_survives():
"""The tags are matched narrowly on purpose — real bracketed content is
part of what the user asked to hear."""
assert "1" in for_speech("See reference [1] for details.")
assert "docs" in for_speech("It's in [the docs] somewhere.")