109 lines
3.6 KiB
Python
109 lines
3.6 KiB
Python
"""Shipped patch and DOM contracts for the hands-free conversation instrument."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from pathlib import Path
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
FIXTURE = ROOT / "testing/fixtures/hermes-webui-0.52.181"
|
|
PATCHER = ROOT / "dockerfiles/hermes-webui-atlas-patch.py"
|
|
VOICE_JS = ROOT / "dockerfiles/hermes-webui-atlas-voice.js"
|
|
VOICE_CSS = ROOT / "dockerfiles/hermes-webui-atlas-voice.css"
|
|
DOM_PROBE = ROOT / "testing/probes/hermes_voice_instrument_probe.js"
|
|
|
|
|
|
def _patched_fixture(tmp_path: Path) -> Path:
|
|
target = tmp_path / "hermes-webui"
|
|
shutil.copytree(FIXTURE, target)
|
|
env = os.environ.copy()
|
|
env["HERMES_WEBUI_PATCH_ROOT"] = str(target)
|
|
subprocess.run(
|
|
[sys.executable, str(PATCHER)],
|
|
cwd=ROOT,
|
|
env=env,
|
|
check=True,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
return target
|
|
|
|
|
|
def test_real_upstream_fixture_receives_visual_instrument_contract(tmp_path: Path):
|
|
"""Apply the production patcher to exact fragments from pinned WebUI 0.52.181."""
|
|
target = _patched_fixture(tmp_path)
|
|
index = (target / "static/index.html").read_text(encoding="utf-8")
|
|
|
|
assert index.count('id="voiceInstrumentStyles"') == 1
|
|
assert (
|
|
'href="static/atlas-voice.css?v=__WEBUI_VERSION__"' in index
|
|
)
|
|
assert 'role="status"' in index
|
|
assert 'aria-live="polite"' in index
|
|
assert 'aria-atomic="true"' in index
|
|
for layer in (
|
|
"voice-instrument-halo",
|
|
"voice-instrument-ripple",
|
|
"voice-instrument-orbit",
|
|
"voice-instrument-core",
|
|
"voice-instrument-symbol",
|
|
):
|
|
assert layer in index
|
|
assert '<button' not in index[index.index('id="voiceModeBar"') : index.index('<textarea')]
|
|
|
|
|
|
def test_visual_states_have_distinct_layers_finite_error_and_reduced_motion():
|
|
css = VOICE_CSS.read_text(encoding="utf-8")
|
|
|
|
for state in ("listening", "transcribing", "thinking", "speaking", "error"):
|
|
assert f".voice-mode-indicator.{state}" in css
|
|
for animation in (
|
|
"voice-instrument-breathe",
|
|
"voice-instrument-orbit",
|
|
"voice-instrument-speaking-pulse",
|
|
):
|
|
assert f"@keyframes {animation}" in css
|
|
assert (
|
|
".voice-mode-indicator.speaking.is-playing .voice-instrument-halo"
|
|
in css
|
|
)
|
|
|
|
error_rules = "\n".join(
|
|
match.group(0)
|
|
for match in re.finditer(r"[^{}]*\.error[^{}]*\{[^{}]*\}", css)
|
|
)
|
|
assert "animation:" not in error_rules
|
|
assert "@media (prefers-reduced-motion: reduce)" in css
|
|
reduced = css.split("@media (prefers-reduced-motion: reduce)", 1)[1]
|
|
assert "animation: none !important" in reduced
|
|
assert "transition: none !important" in reduced
|
|
|
|
|
|
def test_dom_probe_exercises_actual_injected_voice_script():
|
|
result = subprocess.run(
|
|
["node", str(DOM_PROBE), str(VOICE_JS)],
|
|
cwd=ROOT,
|
|
check=True,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
assert result.stdout.strip() == "voice instrument DOM contract passed"
|
|
|
|
|
|
def test_visual_slice_preserves_private_voice_request_and_capture_contract():
|
|
script = VOICE_JS.read_text(encoding="utf-8")
|
|
|
|
assert script.count("navigator.mediaDevices.getUserMedia(") == 1
|
|
assert "form.append('file',new File([blob],'voice-input.'+ext" in script
|
|
assert "fetch('/api/transcribe',{method:'POST',body:form})" in script
|
|
assert "body:JSON.stringify({text:chunk,engine:'atlas'})" in script
|
|
assert "speakResponse(generation)" in script
|
|
assert "window._voiceModeImmediateSend" in script
|
|
assert "mute" not in script.lower()
|
|
assert "language" not in script.lower()
|