HeadingPermalinkExtensionTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4. * This file is part of the league/commonmark package.
  5. *
  6. * (c) Colin O'Dell <colinodell@gmail.com>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace League\CommonMark\Tests\Functional\Extension\HeadingPermalink;
  12. use League\CommonMark\Environment\Environment;
  13. use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
  14. use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
  15. use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkProcessor;
  16. use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer;
  17. use League\CommonMark\MarkdownConverter;
  18. use League\CommonMark\Parser\MarkdownParser;
  19. use League\CommonMark\Xml\XmlRenderer;
  20. use League\Config\Exception\InvalidConfigurationException;
  21. use PHPUnit\Framework\TestCase;
  22. final class HeadingPermalinkExtensionTest extends TestCase
  23. {
  24. /**
  25. * @dataProvider dataProviderForTestHeadingPermalinksWithDefaultOptions
  26. */
  27. public function testHeadingPermalinksWithDefaultOptions(string $input, string $expected): void
  28. {
  29. $environment = new Environment();
  30. $environment->addExtension(new CommonMarkCoreExtension());
  31. $environment->addExtension(new HeadingPermalinkExtension());
  32. $converter = new MarkdownConverter($environment);
  33. $this->assertEquals($expected, \trim((string) $converter->convert($input)));
  34. }
  35. public function dataProviderForTestHeadingPermalinksWithDefaultOptions(): \Generator
  36. {
  37. yield ['# Hello World!', \sprintf('<h1><a id="content-hello-world" href="#content-hello-world" class="heading-permalink" aria-hidden="true" title="Permalink">%s</a>Hello World!</h1>', HeadingPermalinkRenderer::DEFAULT_SYMBOL)];
  38. yield ['# Hello *World*', \sprintf('<h1><a id="content-hello-world" href="#content-hello-world" class="heading-permalink" aria-hidden="true" title="Permalink">%s</a>Hello <em>World</em></h1>', HeadingPermalinkRenderer::DEFAULT_SYMBOL)];
  39. yield ['# Hello `World`', \sprintf('<h1><a id="content-hello-world" href="#content-hello-world" class="heading-permalink" aria-hidden="true" title="Permalink">%s</a>Hello <code>World</code></h1>', HeadingPermalinkRenderer::DEFAULT_SYMBOL)];
  40. yield ["Test\n----", \sprintf('<h2><a id="content-test" href="#content-test" class="heading-permalink" aria-hidden="true" title="Permalink">%s</a>Test</h2>', HeadingPermalinkRenderer::DEFAULT_SYMBOL)];
  41. yield ["# Hello World!\n\n# Hello World!", \sprintf("<h1><a id=\"content-hello-world\" href=\"#content-hello-world\" class=\"heading-permalink\" aria-hidden=\"true\" title=\"Permalink\">%s</a>Hello World!</h1>\n<h1><a id=\"content-hello-world-1\" href=\"#content-hello-world-1\" class=\"heading-permalink\" aria-hidden=\"true\" title=\"Permalink\">%s</a>Hello World!</h1>", HeadingPermalinkRenderer::DEFAULT_SYMBOL, HeadingPermalinkRenderer::DEFAULT_SYMBOL)];
  42. }
  43. /**
  44. * @dataProvider dataProviderForTestHeadingPermalinksWithCustomOptions
  45. */
  46. public function testHeadingPermalinksWithCustomOptions(string $input, string $expected): void
  47. {
  48. $environment = new Environment([
  49. 'heading_permalink' => [
  50. 'html_class' => 'custom-class',
  51. 'id_prefix' => 'custom-id-prefix',
  52. 'fragment_prefix' => 'custom-fragment-prefix',
  53. // Ensure multiple characters are allowed (including multibyte) and special HTML characters are escaped.
  54. 'symbol' => '¶ 🦄️ <3 You',
  55. 'insert' => HeadingPermalinkProcessor::INSERT_AFTER,
  56. 'title' => 'Link',
  57. 'aria_hidden' => false,
  58. ],
  59. ]);
  60. $environment->addExtension(new CommonMarkCoreExtension());
  61. $environment->addExtension(new HeadingPermalinkExtension());
  62. $converter = new MarkdownConverter($environment);
  63. $this->assertEquals($expected, \trim((string) $converter->convert($input)));
  64. }
  65. public function dataProviderForTestHeadingPermalinksWithCustomOptions(): \Generator
  66. {
  67. yield ['# Hello World!', '<h1>Hello World!<a id="custom-id-prefix-hello-world" href="#custom-fragment-prefix-hello-world" class="custom-class" title="Link">¶ 🦄️ &lt;3 You</a></h1>'];
  68. yield ['# Hello *World*', '<h1>Hello <em>World</em><a id="custom-id-prefix-hello-world" href="#custom-fragment-prefix-hello-world" class="custom-class" title="Link">¶ 🦄️ &lt;3 You</a></h1>'];
  69. yield ["Test\n----", '<h2>Test<a id="custom-id-prefix-test" href="#custom-fragment-prefix-test" class="custom-class" title="Link">¶ 🦄️ &lt;3 You</a></h2>'];
  70. }
  71. public function testHeadingPermalinksWithEmptyPrefixes(): void
  72. {
  73. $environment = new Environment([
  74. 'heading_permalink' => [
  75. 'id_prefix' => '',
  76. 'fragment_prefix' => '',
  77. ],
  78. ]);
  79. $environment->addExtension(new CommonMarkCoreExtension());
  80. $environment->addExtension(new HeadingPermalinkExtension());
  81. $converter = new MarkdownConverter($environment);
  82. $input = '# Hello World!';
  83. $expected = \sprintf('<h1><a id="hello-world" href="#hello-world" class="heading-permalink" aria-hidden="true" title="Permalink">%s</a>Hello World!</h1>', HeadingPermalinkRenderer::DEFAULT_SYMBOL);
  84. $this->assertEquals($expected, \trim((string) $converter->convert($input)));
  85. }
  86. public function testHeadingPermalinksWithEmptySymbol(): void
  87. {
  88. $environment = new Environment([
  89. 'heading_permalink' => [
  90. 'symbol' => '',
  91. 'id_prefix' => '',
  92. 'fragment_prefix' => '',
  93. ],
  94. ]);
  95. $environment->addExtension(new CommonMarkCoreExtension());
  96. $environment->addExtension(new HeadingPermalinkExtension());
  97. $converter = new MarkdownConverter($environment);
  98. $input = '# Hello World!';
  99. $expected = '<h1><a id="hello-world" href="#hello-world" class="heading-permalink" aria-hidden="true" title="Permalink"></a>Hello World!</h1>';
  100. $this->assertEquals($expected, \trim((string) $converter->convert($input)));
  101. }
  102. public function testHeadingPermalinksWithInvalidInsertConfigurationValue(): void
  103. {
  104. $this->expectException(InvalidConfigurationException::class);
  105. $environment = new Environment([
  106. 'heading_permalink' => [
  107. 'insert' => 'invalid value here',
  108. ],
  109. ]);
  110. $environment->addExtension(new CommonMarkCoreExtension());
  111. $environment->addExtension(new HeadingPermalinkExtension());
  112. $converter = new MarkdownConverter($environment);
  113. $converter->convert('# This will fail');
  114. }
  115. public function testWithCustomLevels(): void
  116. {
  117. $environment = new Environment([
  118. 'heading_permalink' => [
  119. 'min_heading_level' => 2,
  120. 'max_heading_level' => 3,
  121. ],
  122. ]);
  123. $environment->addExtension(new CommonMarkCoreExtension());
  124. $environment->addExtension(new HeadingPermalinkExtension());
  125. $converter = new MarkdownConverter($environment);
  126. $input = <<<EOT
  127. # 1
  128. ## 2
  129. ### 3
  130. #### 4
  131. EOT;
  132. $expected = <<<EOT
  133. <h1>1</h1>
  134. <h2><a id="content-2" href="#content-2" class="heading-permalink" aria-hidden="true" title="Permalink">¶</a>2</h2>
  135. <h3><a id="content-3" href="#content-3" class="heading-permalink" aria-hidden="true" title="Permalink">¶</a>3</h3>
  136. <h4>4</h4>
  137. EOT;
  138. $this->assertEquals($expected, \trim((string) $converter->convert($input)));
  139. }
  140. public function testHeadingPermalinksWithApplyIdToHeading(): void
  141. {
  142. $environment = new Environment([
  143. 'heading_permalink' => [
  144. 'apply_id_to_heading' => true,
  145. ],
  146. ]);
  147. $environment->addExtension(new CommonMarkCoreExtension());
  148. $environment->addExtension(new HeadingPermalinkExtension());
  149. $converter = new MarkdownConverter($environment);
  150. $input = '# Hello World!';
  151. $expected = \sprintf('<h1 id="content-hello-world"><a href="#content-hello-world" class="heading-permalink" aria-hidden="true" title="Permalink">%s</a>Hello World!</h1>', HeadingPermalinkRenderer::DEFAULT_SYMBOL);
  152. $this->assertEquals($expected, \trim((string) $converter->convert($input)));
  153. }
  154. public function testHeadingPermalinksWithApplyIdToHeadingAndClass(): void
  155. {
  156. $environment = new Environment([
  157. 'heading_permalink' => [
  158. 'apply_id_to_heading' => true,
  159. 'heading_class' => 'heading-anchor',
  160. ],
  161. ]);
  162. $environment->addExtension(new CommonMarkCoreExtension());
  163. $environment->addExtension(new HeadingPermalinkExtension());
  164. $converter = new MarkdownConverter($environment);
  165. $input = '# Hello World!';
  166. $expected = \sprintf('<h1 id="content-hello-world" class="heading-anchor"><a href="#content-hello-world" class="heading-permalink" aria-hidden="true" title="Permalink">%s</a>Hello World!</h1>', HeadingPermalinkRenderer::DEFAULT_SYMBOL);
  167. $this->assertEquals($expected, \trim((string) $converter->convert($input)));
  168. }
  169. public function testHeadingPermalinksWithApplyIdToHeadingWithoutLink(): void
  170. {
  171. $environment = new Environment([
  172. 'heading_permalink' => [
  173. 'insert' => HeadingPermalinkProcessor::INSERT_NONE,
  174. 'apply_id_to_heading' => true,
  175. 'heading_class' => 'heading-anchor',
  176. ],
  177. ]);
  178. $environment->addExtension(new CommonMarkCoreExtension());
  179. $environment->addExtension(new HeadingPermalinkExtension());
  180. $converter = new MarkdownConverter($environment);
  181. $input = '# Hello World!';
  182. $expected = '<h1 id="content-hello-world" class="heading-anchor">Hello World!</h1>';
  183. $this->assertEquals($expected, \trim((string) $converter->convert($input)));
  184. }
  185. public function testXml(): void
  186. {
  187. $md = '# Hello *World*';
  188. $expectedXml = <<<XML
  189. <?xml version="1.0" encoding="UTF-8"?>
  190. <document xmlns="http://commonmark.org/xml/1.0">
  191. <heading level="1">
  192. <heading_permalink slug="hello-world" />
  193. <text>Hello </text>
  194. <emph>
  195. <text>World</text>
  196. </emph>
  197. </heading>
  198. </document>
  199. XML;
  200. $environment = new Environment([
  201. 'heading_permalink' => [
  202. 'id_prefix' => '',
  203. 'fragment_prefix' => '',
  204. ],
  205. ]);
  206. $environment->addExtension(new CommonMarkCoreExtension());
  207. $environment->addExtension(new HeadingPermalinkExtension());
  208. $document = (new MarkdownParser($environment))->parse($md);
  209. $this->assertSame($expectedXml, \rtrim((new XmlRenderer($environment))->renderDocument($document)->getContent()));
  210. }
  211. }