7 changed files with 227 additions and 29 deletions
@ -0,0 +1,155 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
namespace App\Tests\Service; |
||||||
|
|
||||||
|
use App\Entity\ArticleHighlight; |
||||||
|
use App\Service\ArticleBodyHighlightInjector; |
||||||
|
use App\Service\HighlightAuthorMetadataProvider; |
||||||
|
use App\Util\CommonMark\Converter; |
||||||
|
use League\CommonMark\Exception\CommonMarkException; |
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; |
||||||
|
|
||||||
|
/** |
||||||
|
* Exercises the same two steps as {@see \App\Controller\ArticleController::renderArticle}: |
||||||
|
* {@see Converter::convertToHTML} then {@see ArticleBodyHighlightInjector::inject}. Unit tests |
||||||
|
* that only hand-build HTML can pass while production (real CommonMark output) still produces |
||||||
|
* no <mark> if the pipeline or matching rules diverge; these tests assert the literal |
||||||
|
* <mark class="user-highlight__marker"> in the final string. |
||||||
|
*/ |
||||||
|
final class ArticleHighlightCommonMarkPipelineTest extends KernelTestCase |
||||||
|
{ |
||||||
|
private const AUTHOR_HEX = 'd475ce4b3977507130f42c7f8634fef936800f3ae74d5ecf8089280cdc1923e9'; |
||||||
|
|
||||||
|
private function getConverter(): Converter |
||||||
|
{ |
||||||
|
$container = static::getContainer(); |
||||||
|
if (!$container->has(Converter::class)) { |
||||||
|
self::fail('Converter service must be registered in the test kernel.'); |
||||||
|
} |
||||||
|
/** @var Converter $c */ |
||||||
|
$c = $container->get(Converter::class); |
||||||
|
|
||||||
|
return $c; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @throws CommonMarkException |
||||||
|
*/ |
||||||
|
public function testCommonMarkParagraphYieldsMarkTagsInFinalHtml(): void |
||||||
|
{ |
||||||
|
self::bootKernel(); |
||||||
|
$markdown = "Here is a simple paragraph for the test.\n"; |
||||||
|
$html = $this->getConverter()->convertToHTML($markdown); |
||||||
|
$eid = '0000000000000000000000000000000000000000000000000000000000000cc1'; |
||||||
|
$highlights = [ |
||||||
|
$this->makeHighlight($eid, 'Here is a simple paragraph for the test.', [], 1), |
||||||
|
]; |
||||||
|
$out = $this->createInjector()->inject($html, $highlights)['html']; |
||||||
|
|
||||||
|
$this->assertStringContainsString('<mark', $out, 'Injected body must include a <mark> element.'); |
||||||
|
$this->assertStringContainsString('user-highlight__marker', $out); |
||||||
|
$this->assertMarkWithFragmentId($out, $eid); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @throws CommonMarkException |
||||||
|
*/ |
||||||
|
public function testCommonMarkWithFootnoteRendersToHtmlThatStillInjectsMarkTags(): void |
||||||
|
{ |
||||||
|
self::bootKernel(); |
||||||
|
$markdown = 'keeping track of things in the informational realm[^a] always implies keeping track of time'."\n\n" |
||||||
|
."[^a]: A footnote for test.\n"; |
||||||
|
$html = $this->getConverter()->convertToHTML($markdown); |
||||||
|
$this->assertStringContainsString('fnref', $html, 'Sanity: League footnote extension should emit a fnref <sup>.'); |
||||||
|
|
||||||
|
$content = 'keeping track of things in the infor'."\xC2\xAD".'ma'."\xC2\xAD".'tional realm always implies keeping track of time'; |
||||||
|
$eid = 'f56a6221e8575b051cd6df34e9b61654e08a241b4c5ced3b48c0b769b24ada7d'; |
||||||
|
$highlights = [ |
||||||
|
$this->makeHighlight( |
||||||
|
$eid, |
||||||
|
$content, |
||||||
|
[['a', '30023:6e468422dfb74a5738702a8823b9b28168abab8655faacb6853cd0ee15deee93:bitcoin-is-time']], |
||||||
|
1762697677 |
||||||
|
), |
||||||
|
]; |
||||||
|
$out = $this->createInjector()->inject($html, $highlights)['html']; |
||||||
|
|
||||||
|
$this->assertStringContainsString('<mark', $out); |
||||||
|
$this->assertStringContainsString('user-highlight__marker', $out); |
||||||
|
$this->assertMarkWithFragmentId($out, $eid); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @throws CommonMarkException |
||||||
|
*/ |
||||||
|
public function testCommonMarkWithSmartPunctRendersToHtmlThatMatchesAsciiHighlightContentWithMarks(): void |
||||||
|
{ |
||||||
|
self::bootKernel(); |
||||||
|
$markdown = "Here's the point...\n"; |
||||||
|
$html = $this->getConverter()->convertToHTML($markdown); |
||||||
|
$eid = '0000000000000000000000000000000000000000000000000000000000000dd1'; |
||||||
|
$highlights = [ |
||||||
|
$this->makeHighlight($eid, "Here's the point...", [], 1), |
||||||
|
]; |
||||||
|
$out = $this->createInjector()->inject($html, $highlights)['html']; |
||||||
|
|
||||||
|
$this->assertNotEquals($html, $out, 'Body should change when a mark is injected.'); |
||||||
|
$this->assertStringContainsString('<mark', $out); |
||||||
|
$this->assertMarkWithFragmentId($out, $eid); |
||||||
|
} |
||||||
|
|
||||||
|
private function createInjector(): ArticleBodyHighlightInjector |
||||||
|
{ |
||||||
|
$meta = $this->createMock(HighlightAuthorMetadataProvider::class); |
||||||
|
$meta->method('getMetadata')->willReturn( |
||||||
|
(object) ['display_name' => 'Test', 'name' => 'Test', 'picture' => ''], |
||||||
|
); |
||||||
|
|
||||||
|
return new ArticleBodyHighlightInjector($meta); |
||||||
|
} |
||||||
|
|
||||||
|
private function makeHighlight( |
||||||
|
string $eventId64, |
||||||
|
string $content, |
||||||
|
array $tags, |
||||||
|
int $createdAt, |
||||||
|
): ArticleHighlight { |
||||||
|
$h = new ArticleHighlight(); |
||||||
|
$h->setEventId($eventId64); |
||||||
|
$h->setContent($content); |
||||||
|
$h->setTags($tags); |
||||||
|
$h->setEventCreatedAt($createdAt); |
||||||
|
$h->setAuthorPubkey(self::AUTHOR_HEX); |
||||||
|
|
||||||
|
return $h; |
||||||
|
} |
||||||
|
|
||||||
|
private function assertMarkWithFragmentId(string $html, string $eid): void |
||||||
|
{ |
||||||
|
$eid = strtolower($eid); |
||||||
|
if (1 === preg_match( |
||||||
|
'/<mark\\b[^>]*\\bclass="user-highlight__marker"[^>]*\\bid="highlight-'.preg_quote($eid, '/').'"/', |
||||||
|
$html |
||||||
|
)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if (1 === preg_match( |
||||||
|
'/<mark\\b[^>]*\\bid="highlight-'.preg_quote($eid, '/').'"[^>]*\\bclass="user-highlight__marker"/', |
||||||
|
$html |
||||||
|
)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
$this->fail('Expected <mark> with class user-highlight__marker and id highlight-'.$eid.' in: '.$this->excerpt($html)); |
||||||
|
} |
||||||
|
|
||||||
|
private function excerpt(string $html, int $max = 2000): string |
||||||
|
{ |
||||||
|
if (\strlen($html) <= $max) { |
||||||
|
return $html; |
||||||
|
} |
||||||
|
|
||||||
|
return \substr($html, 0, $max).'…'; |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue