Bootstrap lokal einbetten und Stil der AG anlegen (#27)

- werkzeuge/bootstrap/: Bootstrap 5.3.8 (CSS, JS-Bundle) und Bootstrap
  Icons 1.13.1 (CSS, woff2/woff) byte-gleich aus den npm-Paketen,
  Integrität geprüft; README mit Version, Quelle, Integrität, Lizenz
  und Aktualisierung; Lizenzen (MIT) daneben; .gitattributes: binary,
  die README bleibt Text mit Diff.
- werkzeuge/stil/ag_prozesse.css: eigene kleine Stilschicht (ag-*):
  Seite in voller Höhe, dunkle Navigationsleiste, hellgrauer
  Hintergrund, Karten mit farbigem Rand, Tabellenziffern, Druck.
  Vorbild VLN_SN/EA-Bilanz, nichts übernommen.
- Vorschau: Kopfzeile als dunkle Navigationsleiste mit Icon-Knöpfen;
  JavaScript, IDs und Zeichenfläche (Wasserzeichen) unverändert.
- scripts/vorschau.py bettet Bootstrap, Icons und Stil ein und setzt die
  Icon-Schrift als data:-URI ein (_schriften_einbetten); ein nicht
  einbettbarer Schriftverweis bricht laut ab.
- Tests: drei neue in test_vorschau.py; der Aufbau von
  test_bibliothek_mit_script_ende_wird_abgelehnt kopiert jetzt ganz
  werkzeuge/ (Erwartung unverändert).
- AGENTS.md: eingebettete Fremdbibliotheken sind bpmn-js und Bootstrap.
- Tests: python -m pytest -q -> 256 bestanden, 1 übersprungen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-25 20:11:29 +02:00
co-authored by Claude Opus 5.5
parent 73a7f41492
commit b341a658bc
17 changed files with 248 additions and 58 deletions
+38 -2
View File
@@ -33,6 +33,42 @@ def test_lizenz_von_bpmn_js_liegt_bei():
assert "watermark" in lizenz
BOOTSTRAP = ROOT / "werkzeuge" / "bootstrap"
STIL = ROOT / "werkzeuge" / "stil" / "ag_prozesse.css"
def test_bootstrap_und_icons_liegen_mit_lizenz_bei():
"""Prüft, dass Bootstrap (CSS, JS-Bundle) und Bootstrap Icons (CSS, Schrift) lokal mit ihren MIT-Lizenzen und einer
README zu Version und Quelle eingebettet sind (Issue #27)."""
for datei in ("bootstrap.min.css", "bootstrap.bundle.min.js", "LICENSE", "README.md",
"icons/bootstrap-icons.min.css", "icons/fonts/bootstrap-icons.woff2", "icons/LICENSE"):
assert (BOOTSTRAP / datei).is_file(), f"fehlt: werkzeuge/bootstrap/{datei}"
for lizenz in ("LICENSE", "icons/LICENSE"):
assert "MIT License" in (BOOTSTRAP / lizenz).read_text(encoding="utf-8")
def test_vorlage_nutzt_bootstrap_und_den_stil_der_ag():
"""Prüft, dass die Vorschau Bootstrap, die Bootstrap Icons und das gemeinsame Stylesheet der AG einbindet
(Issue #27)."""
html = vorschau.VORLAGE.read_text(encoding="utf-8")
for verweis in ("../bootstrap/bootstrap.min.css", "../bootstrap/icons/bootstrap-icons.min.css",
"../stil/ag_prozesse.css"):
assert f'href="{verweis}"' in html, f"nicht eingebunden: {verweis}"
assert STIL.is_file()
def test_erzeugte_seite_laedt_auch_die_icon_schrift_nicht_nach():
"""Prüft, dass in der erzeugten Seite jede CSS-Adresse url(...) eine data:-Quelle oder ein Fragment im selben
Dokument ist – auch die Schrift der Bootstrap Icons, die im Original relativ auf fonts/ verweist (Issue #27)."""
html = vorschau.baue_html(BEISPIEL_BPMN.read_text(encoding="utf-8"), BEISPIEL_BPMN.name)
adressen = re.findall(r"url\(\s*['\"]?([^'\")]+)", html)
assert any(a.startswith("data:font/woff2;base64,") for a in adressen), "Icon-Schrift nicht eingebettet"
# erlaubt sind data:-Quellen und Verweise auf SVG-Fragmente im selben Dokument (url(#…), aus bpmn-js)
fremd = [a for a in adressen if not a.startswith(("data:", "#"))]
assert fremd == [], fremd[:3]
assert ".ag-nav" in html, "Stil der AG nicht eingebettet"
def test_erzeugte_seite_ist_in_sich_geschlossen():
"""Prüft, dass die erzeugte Seite Bibliothek und Stile eingebettet hat und keine Datei mehr nachlädt."""
html = vorschau.baue_html(BEISPIEL_BPMN.read_text(encoding="utf-8"), BEISPIEL_BPMN.name)
@@ -62,8 +98,8 @@ def test_script_ende_im_diagramm_bricht_die_seite_nicht_auf():
def test_bibliothek_mit_script_ende_wird_abgelehnt(tmp_path, monkeypatch):
"""Prüft, dass eine (etwa nach einem Update) nicht sicher einbettbare Bibliothek einen Fehler auslöst."""
kopie = tmp_path / "vorschau"
shutil.copytree(vorschau.VORSCHAU, kopie)
shutil.copytree(vorschau.VORSCHAU.parent, tmp_path / "werkzeuge") # Vorlage braucht auch ../bootstrap, ../stil
kopie = tmp_path / "werkzeuge" / "vorschau"
js = kopie / "bpmn-js" / "bpmn-navigated-viewer.production.min.js"
js.write_text(js.read_text(encoding="utf-8") + '\nvar x = "</script>";', encoding="utf-8")
monkeypatch.setattr(vorschau, "VORSCHAU", kopie)