Files
2026-07-25 21:10:53 +02:00

581 lines
26 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
import importlib.util
import json
import os
import time
import uuid
from pathlib import Path
def load_module(module_path: Path, data_dir: Path, plans_dir: Path):
try:
import flask # noqa: F401
except ModuleNotFoundError:
import pytest
pytest.skip("Flask ist in dieser isolierten Testumgebung nicht installiert")
os.environ["TRAININGSTRACKER_DATA_DIR"] = str(data_dir)
os.environ["TRAININGSTRACKER_PLANS_DIR"] = str(plans_dir)
name = f"tracker_test_app_{uuid.uuid4().hex}"
spec = importlib.util.spec_from_file_location(name, module_path)
module = importlib.util.module_from_spec(spec)
assert spec.loader
spec.loader.exec_module(module)
return module
def sample_plan() -> dict:
return {
"name": "Testplan",
"config": {
"meta": {"title": "Test", "weeks": 2},
"front": {
"session_how_body": "Tabata: 8× (20 Sek Arbeit / 10 Sek Pause).",
"timer_note": "20/10 unverändert",
"goals_note": "Stufen sauber steigern",
},
"days": [
{
"num": 1,
"focus": "Ganzkörper",
"rotations": [{
"label": "Rotation A — 3 Sätze",
"exercises": [
{"name": "Squat", "key": "sq", "cue": "20 s sauber"},
{"name": "Push-up", "key": "push", "cue": "20 s stabil"},
],
}],
},
{
"num": 3,
"focus": "Ganzkörper B",
"rotations": [{
"label": "Rotation A — 3 Sätze",
"exercises": [
{"name": "Squat", "key": "sq", "cue": "20 s sauber"},
{"name": "Push-up", "key": "push", "cue": "20 s stabil"},
],
}],
},
],
"prepost": {
"1": {"warmup": "Kreisen · Squat", "cooldown": "Gehen", "stretch": "Wade"},
"3": {"warmup": "Kreisen", "cooldown": "Gehen", "stretch": "Brust"},
},
"phases": {"items": [{"name": "Phase & Technik", "params": "Sauber & kontrolliert", "weeks": 2}]},
"stages": {
"sq": {"name": "Squat", "steps": ["assistiert", "voll", "Goblet schwerer"]},
"push": {"name": "Push-up", "steps": ["erhöht", "voll"]},
},
},
}
def wait_for_analysis(client, plan_id: str, timeout: float = 3.0):
deadline = time.time() + timeout
while time.time() < deadline:
payload = client.get(f"/api/plans/{plan_id}/analysis/status").get_json()
if payload["analysis_state"]["status"] != "running":
return payload
time.sleep(0.02)
raise AssertionError("Analyse blieb im Test zu lange aktiv")
def test_source_is_read_only_and_session_file_strips_all_analysis_fields(tmp_path: Path):
plans = tmp_path / "data" / "trainingsplan" / "plans"
tracker_data = tmp_path / "data" / "trainingstracker"
plans.mkdir(parents=True)
training = plans / "training.json"
training.write_text(json.dumps(sample_plan()), encoding="utf-8")
original = training.read_bytes()
(plans / "food.json").write_text(json.dumps({"name": "Essen", "config": {"type": "recipe", "days": []}}), encoding="utf-8")
module = load_module(Path(__file__).parents[1] / "app.py", tracker_data, plans)
client = module.app.test_client()
result = client.get("/api/plans").get_json()
assert [item["id"] for item in result["plans"]] == ["training.json"]
detail = client.get("/api/plans/training.json").get_json()
assert detail["plan"]["phases"][0]["name"] == "Phase & Technik"
assert detail["plan"]["training_format"]["is_tabata"] is True
tracker = detail["tracker"]
tracker["sessions"] = {"w01-d01": {"status": "completed", "note": "lief gut", "items": {}}}
tracker["analysis_cache"] = {"weeks": {"1": {"bad": True}}}
tracker["analysis_state"] = {"status": "running"}
tracker["analyses"] = [{"legacy": True}]
assert client.put("/api/plans/training.json/tracker", json=tracker).status_code == 200
raw = json.loads((tracker_data / "sessions" / "training.json").read_text(encoding="utf-8"))
assert raw["version"] == 7
assert "analysis_cache" not in raw
assert "analysis_state" not in raw
assert "analyses" not in raw
assert raw["sessions"]["w01-d01"]["note"] == "lief gut"
assert training.read_bytes() == original
def test_week_archive_contains_prompt_dataset_response_and_cache_uses_index(tmp_path: Path, monkeypatch):
plans = tmp_path / "data" / "trainingsplan" / "plans"
tracker_data = tmp_path / "data" / "trainingstracker"
plans.mkdir(parents=True)
training = plans / "training.json"
training.write_text(json.dumps(sample_plan()), encoding="utf-8")
monkeypatch.setenv("OPENAI_API_KEY", "test-key")
monkeypatch.setenv("OPENAI_MODEL", "test-model")
module = load_module(Path(__file__).parents[1] / "app.py", tracker_data, plans)
client = module.app.test_client()
tracker = client.get("/api/plans/training.json").get_json()["tracker"]
tracker["profile"]["start_date"] = "2026-07-27"
tracker["sessions"] = {
"w01-d01": {"status": "completed", "note": "Woche eins", "items": {
"d1-r0-e0": {"done": True, "progression_id": "sq", "exercise_name": "Squat", "progression": "assistiert", "result": "8 / 7", "note": "sauber"}
}},
"w02-d01": {"status": "completed", "note": "Woche zwei", "items": {
"d1-r0-e0": {"done": True, "progression_id": "sq", "exercise_name": "Squat", "progression": "voll", "result": "8 / 8", "note": "gut"}
}},
}
assert client.put("/api/plans/training.json/tracker", json=tracker).status_code == 200
datasets = []
def fake_call(messages, model):
dataset = json.loads(messages[1]["content"].split("Analyse-Datensatz:\n", 1)[1])
datasets.append(dataset)
return {
"headline": "Kompakt", "summary": "Kurze Analyse.", "data_quality": "Ausreichend.",
"metrics": [{"label": "Sessions", "value": "1", "detail": "Woche"}],
"exercise_updates": [{
"name": "Squat", "trend": "up", "current_level": "voll", "evidence": "8 / 8",
"next_action": "Haltezeit auf 30 Sekunden verlängern", "criterion": "sauber",
}],
"plan_adjustments": [{
"target": "Squat", "suggested_change": "Arbeitsintervall auf 30 Sekunden erhöhen",
"reason": "mehr Belastung", "manual_step": "Timer ändern", "condition": "wenn sauber",
}],
"warnings": [], "conclusion": "Weiter.",
}
module._call_ai = fake_call
started = client.post("/api/plans/training.json/analysis", json={"scope": "week", "week": 1})
assert started.status_code == 202
status = wait_for_analysis(client, "training.json")
assert status["analysis_state"]["status"] == "done"
assert len(datasets) == 1
week_record = status["analysis_cache"]["weeks"]["1"]
assert "unverändert" in week_record["result"]["exercise_updates"][0]["next_action"]
plan_dir = tracker_data / "analyses" / "training.json"
archive_files = list(plan_dir.glob("week-01.json"))
assert len(archive_files) == 1
archive = json.loads(archive_files[0].read_text(encoding="utf-8"))
assert archive["request"]["scope"] == "week"
assert archive["request"]["dataset"]["analysis_type"] == "week"
assert archive["request"]["messages"]
assert archive["response"]["headline"] == "Kompakt"
index = json.loads((plan_dir / "index.json").read_text(encoding="utf-8"))
assert index["latest"]["weeks"]["1"] == archive_files[0].name
assert "history" not in index
assert (plan_dir / "state.json").exists()
session_raw = json.loads((tracker_data / "sessions" / "training.json").read_text(encoding="utf-8"))
assert "analysis_cache" not in session_raw and "analysis_state" not in session_raw
cached = client.post("/api/plans/training.json/analysis", json={"scope": "week", "week": 1})
assert cached.status_code == 200
assert cached.get_json()["cached"] is True
assert len(datasets) == 1
overall_start = client.post("/api/plans/training.json/analysis", json={"scope": "overall"})
assert overall_start.status_code == 202
final = wait_for_analysis(client, "training.json")
assert final["analysis_state"]["status"] == "done"
assert len(datasets) == 3
overall_dataset = datasets[-1]
assert overall_dataset["analysis_type"] == "overall"
assert "weekly_analyses" in overall_dataset
assert "sessions" not in overall_dataset
assert len(list(plan_dir.glob("overall.json"))) == 1
def test_frontend_persists_navigation_and_polls_running_job():
source = (Path(__file__).parents[1] / "static" / "index.html").read_text(encoding="utf-8")
assert "localStorage.setItem" in source
assert "analysisSelection" in source
assert "analysis/status" in source
assert "state.tracker.analysis_state.status === 'running'" in source
def test_stopped_session_is_valid_and_included_in_analysis_records(tmp_path: Path):
plans = tmp_path / "data" / "trainingsplan" / "plans"
tracker_data = tmp_path / "data" / "trainingstracker"
plans.mkdir(parents=True)
training = plans / "training.json"
training.write_text(json.dumps(sample_plan()), encoding="utf-8")
module = load_module(Path(__file__).parents[1] / "app.py", tracker_data, plans)
client = module.app.test_client()
tracker = client.get("/api/plans/training.json").get_json()["tracker"]
tracker["sessions"] = {
"w01-d01": {
"status": "stopped",
"started_at": "2026-07-27T08:00:00+00:00",
"stopped_at": "2026-07-27T08:20:00+00:00",
"completed_at": "",
"items": {},
"note": "Vorzeitig beendet",
}
}
assert client.put("/api/plans/training.json/tracker", json=tracker).status_code == 200
plan = module._normalize_plan(training)
saved = module._load_tracker(plan)
records = module._session_records(plan, saved)
assert records[0]["status"] == "stopped"
assert records[0]["stopped_at"] == "2026-07-27T08:20:00+00:00"
assert module._local_metrics(plan, records, week=1)["completed_sessions"] == 0
def test_frontend_supports_stop_continue_and_confirmed_reset():
source = (Path(__file__).parents[1] / "static" / "index.html").read_text(encoding="utf-8")
assert 'id="stopSession"' in source
assert 'id="resetSession"' in source
assert "session.status = 'stopped'" in source
assert "Session fortsetzen" in source
assert "window.confirm" in source
assert "state.tracker.sessions[sessionKey(state.week, state.dayNum)] = blankSession()" in source
def test_variant_clusters_create_reference_reps_and_reference_seconds(tmp_path: Path):
plans = tmp_path / "data" / "trainingsplan" / "plans"
tracker_data = tmp_path / "data" / "trainingstracker"
plans.mkdir(parents=True)
training = plans / "training.json"
training.write_text(json.dumps(sample_plan()), encoding="utf-8")
module = load_module(Path(__file__).parents[1] / "app.py", tracker_data, plans)
plan = module._normalize_plan(training)
records = [
{"week": 1, "day": 1, "session_key": "w01-d01", "items": [
{"exercise": "Push-up", "progression_id": "push", "progression": "Inkline (hoch)", "result": "10"},
{"exercise": "Plank", "progression_id": "plank", "progression": "auf Knien 1520 s", "result": "6x20 s"},
]},
{"week": 1, "day": 3, "session_key": "w01-d03", "items": [
{"exercise": "Push-up", "progression_id": "push", "progression": "Inkline mittel", "result": "5"},
{"exercise": "Plank", "progression_id": "plank", "progression": "voll 2030 s", "result": "3x20 s"},
]},
{"week": 2, "day": 1, "session_key": "w02-d01", "items": [
{"exercise": "Push-up", "progression_id": "push", "progression": "volle Push-ups", "result": "1"},
]},
]
series = module._cluster_progression_series(plan, records, limit=20)
push = next(row for row in series if row["id"] == "push_up")
assert [point["value"] for point in push["points"]] == [1.0, 1.0, 1.0]
plank = next(row for row in series if row["id"] == "plank")
assert plank["unit"] == "Referenzsekunden"
assert plank["points"][0]["value"] == 78.0 # 6×20 s × 0,65
assert plank["points"][1]["value"] == 60.0 # 3×20 s × 1,00
def test_equivalence_guide_and_mobile_faq_are_exposed(tmp_path: Path):
plans = tmp_path / "data" / "trainingsplan" / "plans"
tracker_data = tmp_path / "data" / "trainingstracker"
plans.mkdir(parents=True)
training = plans / "training.json"
training.write_text(json.dumps(sample_plan()), encoding="utf-8")
module = load_module(Path(__file__).parents[1] / "app.py", tracker_data, plans)
plan = module._normalize_plan(training)
guide = module._public_equivalence_guide(plan)
push = next(table for table in guide["tables"] if table["id"] == "push_up")
examples = {row["label"]: row["example"] for row in push["variants"]}
assert examples["Incline hoch, ca. 60 cm"].startswith("10 Reps")
assert examples["Incline mittel"].startswith("5 Reps")
assert examples["Voller Push-up"].startswith("1 Rep")
source = (Path(__file__).parents[1] / "static" / "index.html").read_text(encoding="utf-8")
assert 'data-view="faq"' in source
assert 'id="faqView"' in source
assert "renderFAQ()" in source
assert "grid-template-columns: minmax(0,1fr) auto" in source
assert ".history-item button { width: 100%; min-width: 0;" in source
assert "overflow-x: hidden" in source
def test_plan_infers_structured_result_schemas(tmp_path: Path):
plans = tmp_path / "data" / "trainingsplan" / "plans"
tracker_data = tmp_path / "data" / "trainingstracker"
plans.mkdir(parents=True)
plan_payload = sample_plan()
plan_payload["config"]["days"][0]["rotations"][0]["exercises"] = [
{"name": "KB Floor Press", "key": "press", "cue": "sauber drücken"},
{"name": "Side Plank (Wechsel)", "key": "side", "cue": "links / rechts halten"},
{"name": "Einarm-Rudern (KB)", "key": "row", "cue": "DG1 links / DG2 rechts"},
{"name": "Regeneration prüfen", "key": "", "cue": "Schlaf und Gelenke prüfen"},
]
plan_payload["config"]["stages"].update({
"press": {"name": "Press", "steps": ["leicht", "schwerer"]},
"side": {"name": "Side Plank", "steps": ["auf Knien", "voll, länger"]},
"row": {"name": "Row", "steps": ["leicht", "mehr"]},
})
training = plans / "training.json"
training.write_text(json.dumps(plan_payload), encoding="utf-8")
module = load_module(Path(__file__).parents[1] / "app.py", tracker_data, plans)
plan = module._normalize_plan(training)
exercises = {exercise["name"]: exercise for exercise in plan["days"][0]["rotations"][0]["exercises"]}
press = exercises["KB Floor Press"]["result_schema"]
assert press["mode"] == "reps" and press["weight_mode"] == "required"
assert press["laterality"] == "bilateral" and press["locked_sets"] is True
side = exercises["Side Plank (Wechsel)"]["result_schema"]
assert side["mode"] == "seconds" and side["laterality"] == "unilateral"
assert side["sides_mode"] == "separate"
row = exercises["Einarm-Rudern (KB)"]["result_schema"]
assert row["mode"] == "reps" and row["weight_mode"] == "required"
assert row["laterality"] == "unilateral" and row["sides_mode"] == "separate"
recovery = exercises["Regeneration prüfen"]["result_schema"]
assert recovery["mode"] == "none"
def test_structured_result_is_saved_canonically_and_used_by_analysis(tmp_path: Path):
plans = tmp_path / "data" / "trainingsplan" / "plans"
tracker_data = tmp_path / "data" / "trainingstracker"
plans.mkdir(parents=True)
training = plans / "training.json"
training.write_text(json.dumps(sample_plan()), encoding="utf-8")
module = load_module(Path(__file__).parents[1] / "app.py", tracker_data, plans)
client = module.app.test_client()
tracker = client.get("/api/plans/training.json").get_json()["tracker"]
tracker["sessions"] = {
"w01-d01": {
"status": "completed",
"items": {
"d1-r0-e0": {
"done": True,
"exercise_name": "Einarm-Rudern (KB)",
"progression_id": "row",
"progression": "leicht",
"result": "dieser Alttext wird ersetzt",
"result_data": {
"version": 1,
"mode": "reps",
"laterality": "unilateral",
"sides_mode": "separate",
"sets": 3,
"weight_kg": "4,0",
"values": [],
"left_values": [9, 8, 8],
"right_values": [9, 8, 9],
},
}
},
}
}
assert client.put("/api/plans/training.json/tracker", json=tracker).status_code == 200
raw = json.loads((tracker_data / "sessions" / "training.json").read_text(encoding="utf-8"))
plan = module._normalize_plan(training)
stable_id = plan["days"][0]["rotations"][0]["exercises"][0]["id"]
assert "d1-r0-e0" not in raw["sessions"]["w01-d01"]["items"]
item = raw["sessions"]["w01-d01"]["items"][stable_id]
assert item["result"] == "4 kg · L 9/8/8 · R 9/8/9 Reps"
assert item["result_data"]["version"] == 2
assert item["result_data"]["weight_kg"] == 4.0
assert item["result_data"]["left_values"] == [9.0, 8.0, 8.0]
measure = module._extract_training_measure(item)
assert measure["mode"] == "reps"
assert measure["total"] == 51.0
assert measure["weight_kg"] == 4.0
assert measure["structured"] is True
def test_frontend_uses_exercise_specific_result_editor_and_legacy_fallback():
source = (Path(__file__).parents[1] / "static" / "index.html").read_text(encoding="utf-8")
assert "Ergebnis heute" in source
assert "data-result-value" in source
assert "data-result-weight" in source
assert "data-result-sides" in source
assert "data-result-weight-mode" in source
assert "parseLegacyResultData" in source
assert "result_data" in source
assert "Bitte mindestens einen Satz bzw. ein Intervall eintragen" in source
assert "data-item-result" not in source
def test_explicit_training_format_and_stage_result_schemas(tmp_path: Path):
plans = tmp_path / "data" / "trainingsplan" / "plans"
tracker_data = tmp_path / "data" / "trainingstracker"
plans.mkdir(parents=True)
payload = sample_plan()
payload["config"]["training_format"] = {
"mode": "tabata", "fixed_interval": True,
"work_seconds": 20, "rest_seconds": 10, "rounds": 8,
}
payload["config"]["days"][0]["rotations"][0]["exercises"][0]["result_schema"] = {
"mode": "auto", "weight_mode": "optional", "laterality": "bilateral"
}
payload["config"]["stages"]["sq"] = {
"name": "Squat-Progression",
"steps": ["Deep Squat Hold", "Negative Squats", "Goblet Squat"],
"result_schemas": [
{"mode": "seconds", "weight_mode": "optional", "laterality": "bilateral"},
{"mode": "reps", "weight_mode": "none", "laterality": "bilateral"},
{"mode": "reps", "weight_mode": "required", "laterality": "bilateral"},
],
}
training = plans / "training.json"
training.write_text(json.dumps(payload), encoding="utf-8")
module = load_module(Path(__file__).parents[1] / "app.py", tracker_data, plans)
plan = module._normalize_plan(training)
assert plan["training_format"]["source"] == "plan"
assert plan["training_format"]["mode"] == "tabata"
assert plan["training_format"]["rounds"] == 8
assert plan["training_format"]["work_seconds"] == 20
assert plan["stages"]["sq"]["result_schemas"][0]["mode"] == "seconds"
assert plan["stages"]["sq"]["result_schemas"][1]["mode"] == "reps"
assert plan["stages"]["sq"]["result_schemas"][2]["weight_mode"] == "required"
source = (Path(__file__).parents[1] / "static" / "index.html").read_text(encoding="utf-8")
assert "progressionResultSchema" in source
assert "durch Progressionsstufe vorgegeben" in source
def test_analysis_store_uses_one_deterministic_file_per_scope(tmp_path: Path):
import sys
sys.path.insert(0, str(Path(__file__).parents[1]))
from analysis_store import current_filename
assert current_filename({"type": "week", "week": 3}) == "week-03.json"
assert current_filename({"type": "overall"}) == "overall.json"
def test_frontend_contains_explicit_week_status_and_item_skip_states():
source = (Path(__file__).parents[1] / "static" / "index.html").read_text(encoding="utf-8")
assert "Woche abschließen" in source
assert "Woche wieder öffnen" in source
assert "Übersprungen" in source
assert "Teilweise" in source
assert "eine neue analyse überschreibt die vorherige" in source.lower()
def test_legacy_results_are_migrated_server_side_without_hidden_values(tmp_path: Path):
plans = tmp_path / "data" / "trainingsplan" / "plans"
tracker_data = tmp_path / "data" / "trainingstracker"
plans.mkdir(parents=True)
training = plans / "training.json"
payload = sample_plan()
payload["config"]["days"][0]["rotations"][0]["exercises"][0]["result_schema"] = {
"mode": "reps", "weight_mode": "required", "laterality": "unilateral",
"sides_mode": "separate", "sets": 3,
}
training.write_text(json.dumps(payload), encoding="utf-8")
sessions = tracker_data / "sessions"
sessions.mkdir(parents=True)
(sessions / "training.json").write_text(json.dumps({
"version": 5,
"revision": 4,
"sessions": {
"w01-d01": {
"status": "completed",
"items": {
"d1-r0-e0": {
"done": True,
"progression": "assistiert",
"result": "4kg, 6/6/5 je Seite",
"note": "Altwert",
}
},
}
},
}), encoding="utf-8")
module = load_module(Path(__file__).parents[1] / "app.py", tracker_data, plans)
client = module.app.test_client()
detail = client.get("/api/plans/training.json").get_json()
plan = detail["plan"]
tracker = detail["tracker"]
stable_id = plan["days"][0]["rotations"][0]["exercises"][0]["id"]
item = tracker["sessions"]["w01-d01"]["items"][stable_id]
assert "d1-r0-e0" not in tracker["sessions"]["w01-d01"]["items"]
assert item["result_data"] == {
"version": 2,
"mode": "reps",
"laterality": "unilateral",
"sides_mode": "same",
"sets": 3,
"weight_kg": 4.0,
"values": [6.0, 6.0, 5.0],
"left_values": [None, None, None],
"right_values": [None, None, None],
}
assert item["result_format"]["sides_mode"] == "same"
assert item["result"] == "4 kg · 6/6/5 Reps je Seite"
assert tracker["revision"] == 5
persisted = json.loads((sessions / "training.json").read_text(encoding="utf-8"))
assert persisted["sessions"]["w01-d01"]["items"][stable_id]["result_data"]["version"] == 2
backup = tracker_data / "sessions" / ".migration-backups" / "training.json.pre-result-data-v2.json"
assert json.loads(backup.read_text(encoding="utf-8"))["version"] == 5
assert client.get("/api/plans/training.json").get_json()["tracker"]["revision"] == 5
def test_legacy_mode_override_and_zero_weight_are_preserved(tmp_path: Path):
plans = tmp_path / "plans"
tracker_data = tmp_path / "tracker"
plans.mkdir()
training = plans / "training.json"
training.write_text(json.dumps(sample_plan()), encoding="utf-8")
module = load_module(Path(__file__).parents[1] / "app.py", tracker_data, plans)
seconds, override = module._parse_legacy_result_data("6x20 s", {
"mode": "reps", "weight_mode": "none", "laterality": "bilateral",
"sides_mode": "same", "default_sets": 6,
})
assert seconds["mode"] == "seconds"
assert seconds["values"] == [20.0] * 6
assert override["mode"] == "seconds"
weighted, override = module._parse_legacy_result_data("0kg, 8/7", {
"mode": "reps", "weight_mode": "required", "laterality": "bilateral",
"sides_mode": "same", "default_sets": 2,
})
assert weighted["weight_kg"] == 0.0
assert weighted["values"] == [8.0, 7.0]
assert override is None
def test_empty_stable_placeholder_does_not_erase_completed_legacy_item(tmp_path: Path):
plans = tmp_path / "plans"
tracker_data = tmp_path / "tracker"
plans.mkdir()
training = plans / "training.json"
training.write_text(json.dumps(sample_plan()), encoding="utf-8")
module = load_module(Path(__file__).parents[1] / "app.py", tracker_data, plans)
merged = module._merge_migrated_item(
{"done": True, "completion_status": "completed", "result": "8/7", "note": "wichtig"},
{"done": False, "completion_status": "planned", "result": "", "note": "", "exercise_name": "Squat"},
)
assert merged["done"] is True
assert merged["completion_status"] == "completed"
assert merged["result"] == "8/7"
assert merged["note"] == "wichtig"
assert merged["exercise_name"] == "Squat"
def test_frontend_hotfix_uses_result_data_v2_and_non_overlapping_status_layout():
source = (Path(__file__).parents[1] / "static" / "index.html").read_text(encoding="utf-8")
assert "const RESULT_DATA_VERSION = 2" in source
assert "grid-template-columns: minmax(112px,132px) minmax(0,1fr)" in source
assert "@media (max-width: 420px)" in source
assert ".exercise-title { grid-template-columns: minmax(0,1fr); }" in source
assert ".app { width: min(1180px, 100%)" in source
assert "formatOverride" in source