87 lines
3.7 KiB
TypeScript
87 lines
3.7 KiB
TypeScript
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("<script>");
|
||
});
|
||
|
||
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("<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="" />);
|
||
expect(html).not.toContain("<img");
|
||
expect(html).toContain("[Bild: Schrankplan]");
|
||
});
|
||
});
|