renderer = new MailBodyRenderer(); } public function testDoubleAsterisksMakeTextBoldAndSingleOnesItalic(): void { $this->assertSame( '

fett und kursiv

', $this->renderer->render('**fett** und *kursiv*', []) ); } public function testDashesBecomeABulletList(): void { $this->assertSame( "", $this->renderer->render("- Ausweis\n- Bankverbindung", []) ); } public function testNumbersBecomeANumberedList(): void { $this->assertSame( "
    \n
  1. Erstens
  2. \n
  3. Zweitens
  4. \n
", $this->renderer->render("1. Erstens\n2. Zweitens", []) ); } /** * The mail layout sets its own h1 above the body and styles nothing below h3, so every * heading is folded into that range rather than producing a tag nobody styled. */ public function testHeadingsAreFoldedIntoTheLevelsTheMailStyles(): void { $this->assertSame( "

Gross

\n

Mittel

\n

Klein

\n

Winzig

", $this->renderer->render("# Gross\n\n## Mittel\n\n### Klein\n\n###### Winzig", []) ); } public function testBlankLinesBecomeParagraphsAndSingleBreaksStay(): void { $this->assertSame( "

Erste Zeile
\nZweite Zeile

\n

Neuer Absatz

", $this->renderer->render("Erste Zeile\nZweite Zeile\n\nNeuer Absatz", []) ); } public function testBareUrlsAndMailAddressesBecomeLinks(): void { $html = $this->renderer->render('Schreib an team@ep-reisen.de oder https://ep-reisen.de/team.', []); $this->assertStringContainsString('team@ep-reisen.de', $html); // The full stop ends the sentence, it is not part of the address. $this->assertStringContainsString('https://ep-reisen.de/team.', $html); } public function testAnEscapedAsteriskStaysLiteral(): void { $this->assertSame('

Ein Sternchen: *

', $this->renderer->render('Ein Sternchen: \*', [])); } /** * Admins write Markdown, so markup they type is dropped rather than passed through. */ public function testMarkupTypedByAnAdminIsStripped(): void { $html = $this->renderer->render('Hallo alle ', []); $this->assertStringNotContainsString('', $html); $this->assertStringNotContainsString('renderer->render('![Ein Bild](https://example.org/pixel.png)', []); $this->assertStringNotContainsString('assertStringContainsString('Ein Bild', $html); } public function testAnUnsafeLinkLosesItsTarget(): void { $html = $this->renderer->render('[klick](javascript:alert(1))', []); $this->assertStringNotContainsString('javascript:', $html); $this->assertStringContainsString('klick', $html); } public function testPlaceholdersAreSubstituted(): void { $this->assertSame( '

Hallo Anna, dein Einsatz Skireise

', $this->renderer->render( 'Hallo {{vorname}}, dein Einsatz {destination}', ['{{vorname}}' => 'Anna', '{destination}' => 'Skireise'] ) ); } /** * The load-bearing case. A teamer's own words go in after the parser has run, so * nothing in them can become a tag, a heading, a list or emphasis - whatever they * happen to contain. */ public function testNothingInAPlaceholderValueBecomesMarkup(): void { $html = $this->renderer->render('Kommentar: {kommentar}', [ '{kommentar}' => "**nicht fett**\n- keine Liste\n# keine Überschrift x", ]); $this->assertStringNotContainsString('', $html); $this->assertStringNotContainsString('
    ', $html); $this->assertStringNotContainsString('assertStringNotContainsString('assertStringNotContainsString('', $html); $this->assertStringContainsString('**nicht fett**', $html); $this->assertStringContainsString('- keine Liste', $html); $this->assertStringContainsString('<b>x</b>', $html); } /** * A rejection comment arrives as one multi-line value inside a single paragraph. */ public function testLineBreaksInsideAPlaceholderValueSurvive(): void { $this->assertSame( "

    Erste Zeile
    \nZweite Zeile

    ", $this->renderer->render('{kommentar}', ['{kommentar}' => "Erste Zeile\nZweite Zeile"]) ); } /** * Documented rather than desired: substitution runs over the finished HTML, so a token * an admin put in backticks is replaced like any other. Harmless, but surprising enough * to pin down. */ public function testATokenInACodeSpanIsStillSubstituted(): void { $this->assertSame( '

    Skireise

    ', $this->renderer->render('`{destination}`', ['{destination}' => 'Skireise']) ); } public function testAnEmptyBodyRendersAsNothing(): void { $this->assertSame('', $this->renderer->render('', [])); $this->assertSame('', $this->renderer->render(null, [])); } }