| from pathlib import Path | |
| from src.progress import progress_from_events | |
| def test_operation_status_is_fixed_floating_toast(): | |
| html = Path("web/index.html").read_text(encoding="utf-8") | |
| css = Path("web/static/app.css").read_text(encoding="utf-8") | |
| js = Path("web/static/app.js").read_text(encoding="utf-8") | |
| assert 'id="toastLayer"' in html | |
| assert "floating-toast" in html | |
| assert ".toast-layer" in css | |
| assert "position:fixed" in css | |
| assert "operation-status floating-toast" in js | |
| def test_new_build_defaults_are_correct(): | |
| html = Path("web/index.html").read_text(encoding="utf-8") | |
| assert '<option value="Qwen/Qwen3-Coder-Next" selected>Qwen/Qwen3-Coder-Next</option>' in html | |
| assert "Pi 5" not in html | |
| assert '<option value="zero-a10g" selected>ZeroGPU</option>' in html | |
| preferred = html.split('id="preferredHardware"', 1)[1].split("</select>", 1)[0] | |
| assert preferred.count("<option") == 1 | |
| fallback = html.split('id="fallbackHardware"', 1)[1].split("</select>", 1)[0] | |
| assert '<option value="a10g-large" selected>A10G Large</option>' in fallback | |
| def test_progress_timeline_is_monotonic_and_does_not_go_backwards(): | |
| result = progress_from_events([ | |
| {"ts": "2026-06-02T10:00:00+00:00", "step": "bootstrap", "status": "started"}, | |
| {"ts": "2026-06-02T10:01:00+00:00", "step": "pi_run", "status": "running"}, | |
| {"ts": "2026-06-02T10:02:00+00:00", "step": "model_analysis", "status": "success"}, | |
| ], state={"status": "running"}) | |
| assert result["current_step"] == "pi_run" | |
| statuses = {item["step"]: item["status"] for item in result["timeline"]} | |
| assert statuses["bootstrap"] == "done" | |
| assert statuses["model_analysis"] == "done" | |
| assert statuses["pi_run"] == "running" | |
| def test_stopped_timeline_freezes_running_step_without_running_status(): | |
| result = progress_from_events([ | |
| {"ts": "2026-06-02T10:00:00+00:00", "step": "bootstrap", "status": "success"}, | |
| {"ts": "2026-06-02T10:01:00+00:00", "step": "pi_run", "status": "running"}, | |
| ], state={"status": "cancelled"}) | |
| statuses = {item["step"]: item["status"] for item in result["timeline"]} | |
| assert statuses["pi_run"] == "stopped" | |
| assert result["status"] == "cancelled" | |
| def test_frontend_timeline_supports_stopped_status(): | |
| js = Path("web/static/progress.js").read_text(encoding="utf-8") | |
| css = Path("web/static/app.css").read_text(encoding="utf-8") | |
| assert "status==='stopped'" in js | |
| assert "status-stopped" in js | |
| assert ".step.status-stopped .step-dot" in css | |
| def test_app_version_v65(): | |
| assert '"version": "v81-robust-timeline-color-feedback"' in Path("app.py").read_text(encoding="utf-8") | |