Spaces:
Running
Running
| """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" | |
| ) | |