virtual_keyboard / tests /test_bridge.py
github-actions[bot]
Deploy to HF Spaces
2e0c2a7
"""Tests that verify Gradio bridge elements defined in app.py
are consistent with what keyboard.js expects.
Migrated from test_bridge_elements.py at the project root."""
import re
def _extract_js_required_bridge_ids(js_source: str) -> list[str]:
match = re.search(
r"async function waitForBridgeElements.*?"
r"const required = \[(.*?)\];",
js_source,
re.DOTALL,
)
assert match, "Could not find required bridge elements in waitForBridgeElements()"
ids = re.findall(r"id:\s*'([^']+)'", match.group(1))
assert len(ids) > 0, "No required bridge element IDs found"
return ids
def _extract_gradio_elem_ids(app_source: str) -> set[str]:
return set(re.findall(r'elem_id="([^"]+)"', app_source))
def _extract_js_bridge_action_ids(js_source: str) -> set[str]:
match = re.search(
r"const BRIDGE_ACTIONS = \{(.*?)\};",
js_source,
re.DOTALL,
)
assert match, "Could not find BRIDGE_ACTIONS in keyboard.js"
return set(re.findall(r"'(vk_[^']+)'", match.group(1)))
# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------
def test_required_bridge_elements_exist_in_gradio(js_source, app_source):
"""Every element ID that waitForBridgeElements() lists as required
must have a matching elem_id in the Gradio UI."""
required_ids = _extract_js_required_bridge_ids(js_source)
gradio_ids = _extract_gradio_elem_ids(app_source)
missing = [eid for eid in required_ids if eid not in gradio_ids]
assert not missing, (
f"Required bridge element(s) in keyboard.js not found in app.py: {missing}"
)
def test_bridge_actions_have_gradio_elements(js_source, app_source):
"""Every element ID in BRIDGE_ACTIONS must exist in app.py."""
action_ids = _extract_js_bridge_action_ids(js_source)
gradio_ids = _extract_gradio_elem_ids(app_source)
missing = action_ids - gradio_ids
assert not missing, (
f"BRIDGE_ACTIONS references element(s) not in app.py: {missing}"
)
def test_streaming_bridge_elements_are_optional(js_source):
"""Streaming bridge elements must NOT be in the required list."""
required_ids = _extract_js_required_bridge_ids(js_source)
streaming_ids = [eid for eid in required_ids if "stream" in eid]
assert not streaming_ids, (
f"Streaming bridge element(s) must not be in the required list: {streaming_ids}"
)
# ---------------------------------------------------------------------------
# UI element existence
# ---------------------------------------------------------------------------
def test_settings_panel_elements_exist_in_html(html_source):
"""Settings panel interactive elements must be present in keyboard.html."""
for eid in ["settingsToggleBtn", "settingsPanel", "settingsCloseBtn", "settingsBackdrop"]:
assert eid in html_source, f"Missing settings element: {eid}"
# ---------------------------------------------------------------------------
# Default value assertions (static)
# ---------------------------------------------------------------------------
def test_default_quantization_is_none(html_source):
"""The quantization selector should default to 'none'."""
assert re.search(r'value="none"[^>]*selected', html_source), (
"Default quantization should be 'none' (selected attribute missing)"
)
def test_engine_select_prefers_reverse_parrot(js_source):
"""populateEngineSelect() should prefer 'reverse_parrot' if available."""
assert "hasReverseParrot ? 'reverse_parrot'" in js_source or \
"'reverse_parrot'" in js_source, (
"Engine select should default to reverse_parrot when available"
)
# ---------------------------------------------------------------------------
# Behavioral assertions (static JS source checks)
# ---------------------------------------------------------------------------
def test_keyboard_shortcuts_unconditionally_active(js_source):
"""keyboard.js must not gate shortcut processing on keyboardToggle.checked."""
assert "keyboardToggle.checked" not in js_source, (
"Shortcut processing should not be gated on keyboardToggle.checked"
)
def test_game_error_calls_stop_game_loop(js_source):
"""finishUserTurn's error handler must call stopGameLoop."""
idx = js_source.find("async function finishUserTurn")
assert idx != -1, "Could not find finishUserTurn in keyboard.js"
# The catch block appears within the first ~2000 chars of the function
block = js_source[idx : idx + 2000]
catch_idx = block.find("catch")
assert catch_idx != -1, "Could not find catch block in finishUserTurn"
catch_region = block[catch_idx : catch_idx + 300]
assert "stopGameLoop" in catch_region, (
"finishUserTurn error handler must call stopGameLoop"
)