File size: 2,703 Bytes
9577cee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55b12be
9577cee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6e21858
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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")