feat(vorlagen): Editor, Liste und Versionsgeschichte (#24)
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s

This commit was merged in pull request #24.
This commit is contained in:
2026-09-25 08:23:22 +00:00
parent d8d5444e9f
commit e3d80e5982
65 changed files with 5454 additions and 103 deletions
+86
View File
@@ -0,0 +1,86 @@
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it } from "vitest";
import { Markdown } from "@/components/markdown";
/**
* Hilfetexte als Markdown – und nichts darueber hinaus (#010).
*
* Ein Hilfetext wird von einem Redakteur geschrieben und von jedem Techniker
* geoeffnet. Rohes HTML darin waere fremder Code in fremden Browsern; ueber den
* JSON-Import kaeme er sogar von ausserhalb des Hauses. `react-markdown` laesst
* HTML von sich aus fallen — dieser Test haelt fest, dass niemand spaeter
* `rehype-raw` dazustellt, weil „der Kunde Tabellen in HTML schreibt".
*
* Gerendert wird mit `react-dom/server`, damit der Test ohne DOM-Umgebung laeuft.
*/
function alsHtml(text: string): string {
return renderToStaticMarkup(<Markdown text={text} />);
}
describe("Markdown im Hilfetext", () => {
it("rendert Fettschrift, Listen und Code", () => {
const html = alsHtml("**wichtig**\n\n- eins\n- zwei\n\n`Get-Help`");
expect(html).toContain("<strong>wichtig</strong>");
expect(html).toContain("<li>eins</li>");
expect(html).toContain("Get-Help");
});
it("rendert Tabellen aus GitHub-Markdown", () => {
const html = alsHtml("| Gerät | Port |\n|---|---|\n| Switch | 24 |");
expect(html).toContain("<table>");
expect(html).toContain("Switch");
});
it("laesst ein script-Element nicht durch", () => {
const html = alsHtml('Hinweis <script>alert("1")</script> Ende');
expect(html).not.toContain("<script>");
// Der Text bleibt sichtbar – maskiert, nicht heimlich verschluckt.
expect(html).toContain("&lt;script&gt;");
});
it("laesst weder Bilder mit Ereignisattribut noch eingebettete Rahmen durch", () => {
// Geprueft wird die *Auszeichnung*, nicht das Wort: „onerror" steht danach
// noch im Text, aber maskiert – es wird angezeigt und nicht ausgefuehrt.
const html = alsHtml('<img src=x onerror="alert(1)"> <iframe src="https://fremd.example"></iframe>');
expect(html).not.toContain("<img");
expect(html).not.toContain("<iframe");
expect(html).toContain("&lt;img");
});
it("oeffnet Links in einem neuen Tab ohne Herkunft", () => {
// Ein Klick mitten im Lauf darf den Lauf nicht verlassen, und die Zielseite
// muss nicht erfahren, aus welcher Installation der Klick kam.
const html = alsHtml("[Doku](https://intern.example/doku)");
expect(html).toContain('target="_blank"');
expect(html).toContain('rel="noreferrer"');
});
it("macht aus einem javascript-Link keinen klickbaren Verweis", () => {
// Markdown erlaubt `[Text](javascript:…)`. react-markdown filtert das
// Protokoll; faellt dieser Schutz weg, faellt es hier auf.
const html = alsHtml("[klick mich](javascript:alert(1))");
expect(html).not.toContain("javascript:alert");
});
it("gibt bei leerem Text nichts aus, ohne zu scheitern", () => {
expect(() => alsHtml("")).not.toThrow();
});
});
describe("Verweise und Bilder", () => {
it("macht aus einem Verweis ohne erlaubtes Protokoll keinen Verweis", () => {
// react-markdown leert den href. Ein <a href=""> laedt beim Klick die
// aktuelle Seite in einem neuen Tab – verwirrender als gar kein Verweis.
const html = renderToStaticMarkup(<Markdown text="[klick mich](javascript:alert(1))" />);
expect(html).not.toContain("<a");
expect(html).toContain("klick mich");
});
it("zeigt ein Bild als Alternativtext, statt es einzubinden", () => {
// Ein Bild laege auf einem fremden Host, und img-src erlaubt nur self.
const html = renderToStaticMarkup(<Markdown text="![Schrankplan](https://fremd.example/p.png)" />);
expect(html).not.toContain("<img");
expect(html).toContain("[Bild: Schrankplan]");
});
});