| from pathlib import Path | |
| ROOT = Path(__file__).resolve().parents[1] | |
| def test_command_input_stays_enabled_while_execution_is_active(): | |
| source = (ROOT / "frontend" / "app.js").read_text(encoding="utf-8") | |
| lock_body = source.split("function lockInput()", 1)[1].split( | |
| "document.getElementById('chat-form')", | |
| 1, | |
| )[0] | |
| assert "chatInput.disabled = false" in lock_body | |
| assert "submitBtn.disabled = false" in lock_body | |
| assert "setSubmitButtonMode(true)" in lock_body | |
| def test_click_stops_but_enter_submits_replacement_command(): | |
| source = (ROOT / "frontend" / "app.js").read_text(encoding="utf-8") | |
| assert "enterSubmittedFromChatInput" in source | |
| assert "e.submitter === submitBtn && !submittedWithEnter" in source | |
| assert "JSON.stringify({ type: 'cancel' })" in source | |
| assert "type: 'run'" in source | |
| cancel_guard = source.index( | |
| "if (busy && e.submitter === submitBtn && !submittedWithEnter)" | |
| ) | |
| prompt_guard = source.index("if (!prompt || !socket", cancel_guard) | |
| assert cancel_guard < prompt_guard | |
| def test_empty_input_does_not_trigger_native_validation_before_stop(): | |
| html = (ROOT / "frontend" / "index.html").read_text(encoding="utf-8") | |
| input_markup = html.split('id="chat-input"', 1)[1].split(">", 1)[0] | |
| assert "required" not in input_markup | |
| def test_interrupt_requested_uses_stopping_button_state(): | |
| source = (ROOT / "frontend" / "app.js").read_text(encoding="utf-8") | |
| handler = source.split("case 'interrupt_requested':", 1)[1].split( | |
| "case 'interrupted':", | |
| 1, | |
| )[0] | |
| assert "setSubmitButtonMode(true, true)" in handler | |
| assert "Đang dừng lệnh..." in source | |
| def test_execution_button_has_spinner_and_stop_square(): | |
| script = (ROOT / "frontend" / "app.js").read_text(encoding="utf-8") | |
| styles = (ROOT / "frontend" / "index.css").read_text(encoding="utf-8") | |
| assert "stop-square" in script | |
| assert "is-executing" in script | |
| assert "#submit-btn.is-executing::before" in styles | |
| assert "@keyframes submitExecutionSpin" in styles | |
| def test_html_cache_busts_live_command_assets(): | |
| html = (ROOT / "frontend" / "index.html").read_text(encoding="utf-8") | |
| assert "/static/app.js?v=39" in html | |
| assert "/static/index.css?v=11" in html | |
| def test_clarification_result_is_not_recorded_as_failed_execution(): | |
| source = (ROOT / "frontend" / "app.js").read_text(encoding="utf-8") | |
| result_handler = source.split("case 'result':", 1)[1].split( | |
| "const badge =", | |
| 1, | |
| )[0] | |
| clarification_guard = result_handler.index( | |
| "['clarify', 'reject'].includes(msg.response_type)" | |
| ) | |
| history_write = result_handler.index("historyData.unshift") | |
| assert clarification_guard < history_write | |