diff --git a/src/trademind/server.py b/src/trademind/server.py index 855aa67..33d788c 100644 --- a/src/trademind/server.py +++ b/src/trademind/server.py @@ -186,6 +186,9 @@ async function refresh() { (s.recent_trades || []).slice().reverse()); renderTrading(s.trading || {}); renderTraining(s.training || {}); + // Der Abschnitt muss aus dem Statuslauf heraus sichtbar werden – sein Aufklapp-Knopf + // sitzt darin, er könnte sich sonst nie selbst einblenden. + $("config-section").hidden = !(s.trading || {}).control_enabled; } catch (e) { document.getElementById("sub").textContent = "Status nicht erreichbar: " + e; } } @@ -353,8 +356,7 @@ function fieldRow(f) { } function renderConfig(cfg) { - if (!cfg.available) return; - $("config-section").hidden = false; + if (!cfg.available) { $("config-section").hidden = true; return; } cfgLoaded = cfg; $("cfg-meta").textContent = `${cfg.config_path} · ${cfg.override_count} geänderte(s) Feld(er) in ${cfg.overrides_path}` diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index 3802b2e..f49795b 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -98,6 +98,59 @@ def test_every_referenced_element_exists(): assert f'id="{element_id}"' in _DASHBOARD, f"Element #{element_id} fehlt im HTML" +def function_body(js: str, name: str) -> str: + """Rumpf einer Funktion per Klammerzählung ausschneiden.""" + start = js.index(f"function {name}(") + open_brace = js.index("{", start) + depth = 0 + for i in range(open_brace, len(js)): + depth += js[i] == "{" + depth -= js[i] == "}" + if depth == 0: + return js[open_brace : i + 1] + raise AssertionError(f"Funktion {name} ist nicht geschlossen") + + +def hidden_section_ids() -> list[str]: + return re.findall(r'
]*\shidden', _DASHBOARD) + + +def status_loop_code() -> str: + """Der Code, der bei jedem Statuslauf durchläuft: refresh und die daraus gerufenen + render-Funktionen. Nur was hier steht, kann einen Abschnitt von selbst einblenden.""" + js = script_body() + body = function_body(js, "refresh") + code = [body] + for name in sorted(set(re.findall(r"\b(render\w+)\(", body))): + code.append(function_body(js, name)) + return "\n".join(code) + + +def test_there_are_hidden_sections_to_check(): + assert hidden_section_ids(), "Test greift ins Leere – keine versteckten Abschnitte gefunden" + + +@pytest.mark.parametrize("section_id", hidden_section_ids()) +def test_hidden_sections_are_unhidden_by_the_status_loop(section_id: str): + """Ein Abschnitt, dessen Aufklapp-Knopf in ihm selbst sitzt, blendet sich sonst nie ein. + + Genau das ist passiert: Der Konfigurationsbereich war für Benutzer unerreichbar, weil + nur ein Klick darin ihn sichtbar gemacht hätte. Sichtbarkeit gehört deshalb in + ``refresh()``, das bei jedem Statuslauf durchläuft. + """ + code = status_loop_code() + assert f'$("{section_id}").hidden' in code, ( + f"#{section_id} startet versteckt und wird im Statuslauf nicht sichtbar gemacht – " + "ein Knopf innerhalb des Abschnitts kann ihn nicht einblenden" + ) + + +def test_render_functions_are_called_by_the_status_loop(): + body = function_body(script_body(), "refresh") + for name in ("renderTrading", "renderTraining"): + assert f"{name}(" in body, f"{name} wird nie aufgerufen" + + @pytest.mark.parametrize( "needle", [