View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.doxia.module.markdown;
20  
21  import javax.inject.Inject;
22  
23  import java.io.IOException;
24  import java.io.Reader;
25  import java.util.Iterator;
26  import java.util.List;
27  
28  import org.apache.maven.doxia.parser.AbstractParser;
29  import org.apache.maven.doxia.parser.AbstractParserTest;
30  import org.apache.maven.doxia.parser.ParseException;
31  import org.apache.maven.doxia.sink.SinkEventAttributes;
32  import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
33  import org.apache.maven.doxia.sink.impl.SinkEventElement;
34  import org.apache.maven.doxia.sink.impl.SinkEventTestingSink;
35  import org.junit.jupiter.api.Test;
36  
37  import static org.junit.jupiter.api.Assertions.assertEquals;
38  import static org.junit.jupiter.api.Assertions.assertFalse;
39  import static org.junit.jupiter.api.Assertions.assertTrue;
40  
41  /**
42   * Tests for {@link MarkdownParser}.
43   *
44   * @author <a href="mailto:julien.nicoulaud@gmail.com">Julien Nicoulaud</a>
45   * @since 1.3
46   */
47  public class MarkdownParserTest extends AbstractParserTest {
48  
49      /**
50       * The {@link MarkdownParser} used for the tests.
51       */
52      @Inject
53      protected MarkdownParser parser;
54  
55      /**
56       * {@inheritDoc}
57       */
58      @Override
59      protected AbstractParser createParser() {
60          return parser;
61      }
62  
63      /**
64       * {@inheritDoc}
65       */
66      @Override
67      protected String outputExtension() {
68          return MarkdownParserModule.FILE_EXTENSION;
69      }
70  
71      /**
72       * Assert the paragraph sink event is fired when parsing "paragraph.md".
73       *
74       * @throws Exception if the event list is not correct when parsing the document
75       */
76      @Test
77      public void testParagraphSinkEvent() throws Exception {
78          Iterator<SinkEventElement> it =
79                  parseFileToEventTestingSink("paragraph").getEventList().iterator();
80  
81          assertSinkEquals(it, "head", "head_", "body", "paragraph", "text", "paragraph_", "body_");
82  
83          assertFalse(it.hasNext());
84      }
85  
86      /**
87       * Assert the bold sink event is fired when parsing "font-bold.md".
88       *
89       * @throws Exception if the event list is not correct when parsing the document
90       */
91      @Test
92      public void testFontBoldSinkEvent() throws Exception {
93          // System.out.println(parseFileToHtml("font-bold"));
94          List<SinkEventElement> eventList =
95                  parseFileToEventTestingSink("font-bold").getEventList();
96          Iterator<SinkEventElement> it = eventList.iterator();
97  
98          assertSinkEquals(it, "head", "head_", "body", "paragraph", "inline", "text", "inline_", "paragraph_", "body_");
99  
100         assertFalse(it.hasNext());
101 
102         SinkEventElement inline = eventList.get(4);
103         assertEquals("inline", inline.getName());
104         SinkEventAttributeSet atts = (SinkEventAttributeSet) inline.getArgs()[0];
105         assertTrue(atts.containsAttribute(SinkEventAttributes.SEMANTICS, "strong"));
106     }
107 
108     /**
109      * Assert the italic sink event is fired when parsing "font-italic.md".
110      *
111      * @throws Exception if the event list is not correct when parsing the document
112      */
113     @Test
114     public void testFontItalicSinkEvent() throws Exception {
115         // System.out.println(parseFileToHtml("font-italic"));
116         List<SinkEventElement> eventList =
117                 parseFileToEventTestingSink("font-italic").getEventList();
118         Iterator<SinkEventElement> it = eventList.iterator();
119 
120         assertSinkEquals(it, "head", "head_", "body", "paragraph", "inline", "text", "inline_", "paragraph_", "body_");
121 
122         assertFalse(it.hasNext());
123         SinkEventElement inline = eventList.get(4);
124         assertEquals("inline", inline.getName());
125         SinkEventAttributeSet atts = (SinkEventAttributeSet) inline.getArgs()[0];
126         assertTrue(atts.containsAttribute(SinkEventAttributes.SEMANTICS, "emphasis"));
127     }
128 
129     /**
130      * Assert the monospaced/code sink event is fired when parsing "font-monospaced.md".
131      *
132      * @throws Exception if the event list is not correct when parsing the document
133      */
134     @Test
135     public void testFontMonospacedSinkEvent() throws Exception {
136         // System.out.println(parseFileToHtml("font-monospaced"));
137         List<SinkEventElement> eventList =
138                 parseFileToEventTestingSink("font-monospaced").getEventList();
139         Iterator<SinkEventElement> it = eventList.iterator();
140 
141         assertSinkEquals(it, "head", "head_", "body", "paragraph", "inline", "text", "inline_", "paragraph_", "body_");
142 
143         assertFalse(it.hasNext());
144         SinkEventElement inline = eventList.get(4);
145         assertEquals("inline", inline.getName());
146         SinkEventAttributeSet atts = (SinkEventAttributeSet) inline.getArgs()[0];
147         assertTrue(atts.containsAttribute(SinkEventAttributes.SEMANTICS, "code"));
148     }
149 
150     /**
151      * Assert the verbatim sink event is fired when parsing "code.md".
152      *
153      * @throws Exception if the event list is not correct when parsing the document
154      */
155     @Test
156     public void testCodeSinkEvent() throws Exception {
157         Iterator<SinkEventElement> it =
158                 parseFileToEventTestingSink("code").getEventList().iterator();
159 
160         assertSinkEquals(
161                 it,
162                 "head",
163                 "head_",
164                 "body",
165                 "paragraph",
166                 "text",
167                 "paragraph_",
168                 "text",
169                 "verbatim",
170                 "inline",
171                 "text",
172                 "inline_",
173                 "verbatim_",
174                 "body_");
175 
176         assertFalse(it.hasNext());
177     }
178 
179     /**
180      * Assert the verbatim sink event is fired when parsing "fenced-code-block.md".
181      *
182      * @throws Exception if the event list is not correct when parsing the document
183      */
184     @Test
185     public void testFencedCodeBlockSinkEvent() throws Exception {
186         List<SinkEventElement> eventList =
187                 parseFileToEventTestingSink("fenced-code-block").getEventList();
188         Iterator<SinkEventElement> it = eventList.iterator();
189 
190         assertSinkEquals(
191                 it,
192                 "head",
193                 "head_",
194                 "body",
195                 "paragraph",
196                 "text",
197                 "paragraph_",
198                 "text",
199                 "verbatim",
200                 "inline",
201                 "text",
202                 "inline_",
203                 "verbatim_",
204                 "body_");
205 
206         assertFalse(it.hasNext());
207 
208         // PRE element must be a "verbatim" Sink event
209         SinkEventElement pre = eventList.get(7);
210         assertEquals("verbatim", pre.getName());
211         SinkEventAttributeSet preAtts = (SinkEventAttributeSet) pre.getArgs()[0];
212         // instead of using a decoration tag on the verbatim event and additional inline event is used
213         assertTrue(preAtts.isEmpty());
214 
215         // * CODE element must be an "inline" Sink event that specifies:
216         // * SEMANTICS = "code" and CLASS = "language-java"
217         SinkEventElement code = eventList.get(8);
218         assertEquals("inline", code.getName());
219         SinkEventAttributeSet codeAtts = (SinkEventAttributeSet) code.getArgs()[0];
220         assertTrue(codeAtts.containsAttribute(SinkEventAttributes.SEMANTICS, "code"));
221         assertTrue(codeAtts.containsAttribute(SinkEventAttributes.CLASS, "language-java"));
222     }
223 
224     /**
225      * Assert the figureGraphics sink event is fired when parsing "image.md".
226      *
227      * @throws Exception if the event list is not correct when parsing the document
228      */
229     @Test
230     public void testImageSinkEvent() throws Exception {
231         Iterator<SinkEventElement> it =
232                 parseFileToEventTestingSink("image").getEventList().iterator();
233 
234         assertSinkEquals(
235                 it, "head", "head_", "body", "paragraph", "text", "figureGraphics", "text", "paragraph_", "body_");
236 
237         assertFalse(it.hasNext());
238     }
239 
240     /**
241      * Assert the link sink event is fired when parsing "link.md".
242      *
243      * @throws Exception if the event list is not correct when parsing the document
244      */
245     @Test
246     public void testLinkSinkEvent() throws Exception {
247         Iterator<SinkEventElement> it =
248                 parseFileToEventTestingSink("link").getEventList().iterator();
249 
250         assertSinkEquals(
251                 it,
252                 "head",
253                 "head_",
254                 "body",
255                 "paragraph",
256                 "text",
257                 "link",
258                 "text",
259                 "link_",
260                 "text",
261                 "paragraph_",
262                 "body_");
263 
264         assertFalse(it.hasNext());
265     }
266 
267     /**
268      * Assert the link sink event is fired when parsing "link.md".
269      *
270      * @throws Exception if the event list is not correct when parsing the document
271      */
272     @Test
273     public void testLinkRewriteSinkEvent() throws Exception {
274         List<SinkEventElement> eventList =
275                 parseFileToEventTestingSink("link_rewrite").getEventList();
276 
277         Iterator<SinkEventElement> it = eventList.iterator();
278         assertSinkEquals(
279                 it,
280                 "head",
281                 "head_",
282                 "body",
283                 "paragraph",
284                 "text",
285                 "link",
286                 "text",
287                 "link_",
288                 "text",
289                 "link",
290                 "text",
291                 "link_",
292                 "text",
293                 "paragraph_",
294                 "body_");
295 
296         assertFalse(it.hasNext());
297 
298         assertEquals("doc.html", eventList.get(5).getArgs()[0]);
299         assertEquals("ftp://doc.md", eventList.get(9).getArgs()[0]);
300     }
301 
302     @Test
303     public void testLinkWithAnchorAndQuery() throws Exception {
304         Iterator<SinkEventElement> it =
305                 parseFileToEventTestingSink("link_anchor_query").getEventList().iterator();
306 
307         assertSinkEquals(it, "head", "head_", "body", "paragraph", "link", "text", "link_", "paragraph_", "body_");
308 
309         assertFalse(it.hasNext());
310     }
311 
312     /**
313      * Assert the list sink event is fired when parsing "list.md".
314      *
315      * @throws Exception if the event list is not correct when parsing the document
316      */
317     @Test
318     public void testListSinkEvent() throws Exception {
319         Iterator<SinkEventElement> it =
320                 parseFileToEventTestingSink("list").getEventList().iterator();
321 
322         assertSinkEquals(
323                 it,
324                 "head",
325                 "head_",
326                 "body",
327                 "list",
328                 "text",
329                 "listItem",
330                 "text",
331                 "listItem_",
332                 "listItem",
333                 "text",
334                 "listItem_",
335                 "text",
336                 "list_",
337                 "body_");
338 
339         assertFalse(it.hasNext());
340     }
341 
342     /**
343      * Assert the numbered list sink event is fired when parsing "numbered-list.md".
344      *
345      * @throws Exception if the event list is not correct when parsing the document
346      */
347     @Test
348     public void testNumberedListSinkEvent() throws Exception {
349         Iterator<SinkEventElement> it =
350                 parseFileToEventTestingSink("numbered-list").getEventList().iterator();
351 
352         assertSinkEquals(
353                 it,
354                 "head",
355                 "head_",
356                 "body",
357                 "numberedList",
358                 "text",
359                 "numberedListItem",
360                 "text",
361                 "numberedListItem_",
362                 "numberedListItem",
363                 "text",
364                 "numberedListItem_",
365                 "text",
366                 "numberedList_",
367                 "body_");
368 
369         assertFalse(it.hasNext());
370     }
371 
372     /**
373      * Assert the metadata is passed through when parsing "metadata.md".
374      *
375      * @throws Exception if the event list is not correct when parsing the document
376      */
377     @Test
378     public void testMetadataSinkEvent() throws Exception {
379         List<SinkEventElement> eventList =
380                 parseFileToEventTestingSink("metadata").getEventList();
381         Iterator<SinkEventElement> it = eventList.iterator();
382 
383         assertSinkEquals(
384                 it,
385                 "head",
386                 "title",
387                 "text",
388                 "text",
389                 "text",
390                 "title_",
391                 "author",
392                 "text",
393                 "author_",
394                 "date",
395                 "text",
396                 "date_",
397                 "unknown",
398                 "head_",
399                 "body",
400                 "section1",
401                 "sectionTitle1",
402                 "text",
403                 "sectionTitle1_",
404                 "paragraph",
405                 "text",
406                 "paragraph_",
407                 "section2",
408                 "sectionTitle2",
409                 "text",
410                 "sectionTitle2_",
411                 "paragraph",
412                 "text",
413                 "paragraph_",
414                 "section2_",
415                 "section1_",
416                 "body_");
417 
418         assertFalse(it.hasNext());
419 
420         // Title must be "A Title & a Test"
421         assertEquals("A Title ", eventList.get(2).getArgs()[0]);
422         assertEquals("&", eventList.get(3).getArgs()[0]);
423         assertEquals(" a 'Test'", eventList.get(4).getArgs()[0]);
424 
425         // Authors on multiple lines are just normalized in terms of whitespaces (i.e. space newline space is replaced
426         // by space)
427         assertEquals(
428                 "Somebody 'Nickname' Great <somebody@somewhere.org>, another author",
429                 eventList.get(7).getArgs()[0]);
430 
431         // Date must be "2013 © Copyleft"
432         assertEquals("2013 \u00A9 Copyleft", eventList.get(10).getArgs()[0]);
433 
434         // * META element must be an "unknown" Sink event that specifies:
435         // * name = "keywords" and content = "maven,doxia,markdown"
436         SinkEventElement meta = eventList.get(12);
437         assertEquals("unknown", meta.getName());
438         assertEquals("meta", meta.getArgs()[0]);
439         SinkEventAttributeSet metaAtts = (SinkEventAttributeSet) meta.getArgs()[2];
440         assertTrue(metaAtts.containsAttribute(SinkEventAttributes.NAME, "keywords"));
441         assertTrue(metaAtts.containsAttribute("content", "maven,doxia,markdown"));
442     }
443 
444     /**
445      * Assert the metadata is passed through when parsing "metadata-yaml.md".
446      *
447      * @throws Exception if the event list is not correct when parsing the document
448      */
449     @Test
450     public void testMetadataYamlSinkEvent() throws Exception {
451         List<SinkEventElement> eventList =
452                 parseFileToEventTestingSink("metadata-yaml").getEventList();
453         Iterator<SinkEventElement> it = eventList.iterator();
454 
455         assertSinkEquals(
456                 it,
457                 "head",
458                 "title",
459                 "text",
460                 "text",
461                 "text",
462                 "title_",
463                 "author",
464                 "text",
465                 "author_",
466                 "author",
467                 "text",
468                 "author_",
469                 "date",
470                 "text",
471                 "date_",
472                 "unknown",
473                 "head_",
474                 "body",
475                 "section1",
476                 "sectionTitle1",
477                 "text",
478                 "sectionTitle1_",
479                 "paragraph",
480                 "text",
481                 "paragraph_",
482                 "section2",
483                 "sectionTitle2",
484                 "text",
485                 "sectionTitle2_",
486                 "paragraph",
487                 "text",
488                 "paragraph_",
489                 "section2_",
490                 "section1_",
491                 "body_");
492 
493         assertFalse(it.hasNext());
494 
495         // Title must be "A Title & a Test"
496         assertEquals("A Title ", eventList.get(2).getArgs()[0]);
497         assertEquals("&", eventList.get(3).getArgs()[0]);
498         assertEquals(" a 'Test'", eventList.get(4).getArgs()[0]);
499 
500         // first author must be "Somebody <somebody@somewhere.org>"
501         assertEquals(
502                 "Somebody 'Nickname' Great <somebody@somewhere.org>",
503                 eventList.get(7).getArgs()[0]);
504 
505         // second author must be "another author"
506         assertEquals("another author", eventList.get(10).getArgs()[0]);
507 
508         // Date must be "2013 © Copyleft"
509         assertEquals("2013 \u00A9 Copyleft", eventList.get(13).getArgs()[0]);
510 
511         // * META element must be an "unknown" Sink event that specifies:
512         // * name = "keywords" and content = "maven,doxia,markdown"
513         SinkEventElement meta = eventList.get(15);
514         assertEquals("unknown", meta.getName());
515         assertEquals("meta", meta.getArgs()[0]);
516         SinkEventAttributeSet metaAtts = (SinkEventAttributeSet) meta.getArgs()[2];
517         assertTrue(metaAtts.containsAttribute(SinkEventAttributes.NAME, "keywords"));
518         assertTrue(metaAtts.containsAttribute("content", "maven,doxia,markdown"));
519     }
520 
521     /**
522      * Assert the first header is passed as title event when parsing "first-heading.md".
523      *
524      * @throws Exception if the event list is not correct when parsing the document
525      */
526     @Test
527     public void testFirstHeadingSinkEvent() throws Exception {
528         Iterator<SinkEventElement> it =
529                 parseFileToEventTestingSink("first-heading").getEventList().iterator();
530 
531         assertSinkEquals(
532                 it,
533                 "head",
534                 "title",
535                 "text",
536                 "title_",
537                 "head_",
538                 "body",
539                 "comment",
540                 "text",
541                 "section1",
542                 "sectionTitle1",
543                 "text",
544                 "sectionTitle1_",
545                 "paragraph",
546                 "text",
547                 "paragraph_",
548                 "section1_",
549                 "body_");
550 
551         assertFalse(it.hasNext());
552     }
553 
554     /**
555      * Assert the first header is passed as title event when parsing "comment-before-heading.md".
556      *
557      * @throws Exception if the event list is not correct when parsing the document
558      */
559     @Test
560     public void testCommentBeforeHeadingSinkEvent() throws Exception {
561         Iterator<SinkEventElement> it = parseFileToEventTestingSink("comment-before-heading")
562                 .getEventList()
563                 .iterator();
564 
565         assertSinkEquals(
566                 it,
567                 "head",
568                 "title",
569                 "text",
570                 "title_",
571                 "head_",
572                 "body",
573                 "comment",
574                 "text",
575                 "section1",
576                 "sectionTitle1",
577                 "text",
578                 "sectionTitle1_",
579                 "paragraph",
580                 "text",
581                 "paragraph_",
582                 "section1_",
583                 "body_");
584 
585         assertFalse(it.hasNext());
586     }
587 
588     /**
589      * Assert the first header is passed as title event when parsing "comment-before-heading.md".
590      *
591      * @throws Exception if the event list is not correct when parsing the document
592      */
593     @Test
594     public void testHtmlContent() throws Exception {
595         Iterator<SinkEventElement> it =
596                 parseFileToEventTestingSink("html-content").getEventList().iterator();
597 
598         assertSinkEquals(
599                 it,
600                 "head",
601                 "head_",
602                 "body",
603                 "division",
604                 "text",
605                 "paragraph",
606                 "inline",
607                 "text",
608                 "inline_",
609                 "text",
610                 "inline",
611                 "text",
612                 "inline_",
613                 "text",
614                 "paragraph_",
615                 "text",
616                 "division_",
617                 "text",
618                 "horizontalRule",
619                 "section1",
620                 "sectionTitle1",
621                 "text",
622                 "sectionTitle1_",
623                 "paragraph",
624                 "text",
625                 "paragraph_",
626                 "text",
627                 "table",
628                 "tableRows",
629                 "text",
630                 "tableRow",
631                 "tableHeaderCell",
632                 "text",
633                 "tableHeaderCell_",
634                 "tableRow_",
635                 "text",
636                 "tableRow",
637                 "tableCell",
638                 "text",
639                 "tableCell_",
640                 "tableRow_",
641                 "text",
642                 "tableRows_",
643                 "table_",
644                 "text",
645                 "section1_",
646                 "body_");
647 
648         assertFalse(it.hasNext());
649     }
650 
651     /**
652      * Parse the file and return a {@link SinkEventTestingSink}.
653      *
654      * @param file the file to parse with {@link #parser}
655      * @return a sink to test parsing events
656      * @throws ParseException if the document parsing failed
657      * @throws IOException if an I/O error occurs while closing test reader
658      */
659     protected SinkEventTestingSink parseFileToEventTestingSink(String file) throws ParseException, IOException {
660         SinkEventTestingSink sink;
661         try (Reader reader = getTestReader(file)) {
662             sink = new SinkEventTestingSink();
663             parser.parse(reader, sink);
664         }
665 
666         return sink;
667     }
668 
669     protected String parseFileToHtml(String file) throws ParseException, IOException {
670         try (Reader reader = getTestReader(file)) {
671             return parser.toHtml(reader).toString();
672         }
673     }
674 
675     @Test
676     public void testTocMacro() throws Exception {
677         Iterator<SinkEventElement> it =
678                 parseFileToEventTestingSink("macro-toc").getEventList().iterator();
679 
680         assertSinkEquals(
681                 it,
682                 "head",
683                 "title",
684                 "text",
685                 "title_",
686                 "head_",
687                 "body",
688                 "list", // TOC start
689                 "listItem",
690                 "link",
691                 "text",
692                 "link_", // emtpy section 2 TOC entry
693                 "list", // sections 3 list start
694                 "listItem",
695                 "link",
696                 "text",
697                 "link_",
698                 "listItem_", // first section 3 TOC entry
699                 "listItem",
700                 "link",
701                 "text",
702                 "link_",
703                 "listItem_", // second section 3 TOC entry
704                 "list_", // sections 3 list end
705                 "listItem_", // emtpy section 2 TOC entry end
706                 "list_", // TOC end
707                 "text",
708                 "section1",
709                 "section2",
710                 "sectionTitle2",
711                 "text",
712                 "sectionTitle2_",
713                 "section2_",
714                 "section2",
715                 "sectionTitle2",
716                 "text",
717                 "sectionTitle2_",
718                 "section2_",
719                 "section1_",
720                 "body_");
721     }
722 
723     // TOC macro fails with EmptyStackException when title 2 followed by title 4 then title 2
724     @Test
725     public void testTocMacroDoxia559() throws Exception {
726         Iterator<SinkEventElement> it = parseFileToEventTestingSink("macro-toc-DOXIA-559")
727                 .getEventList()
728                 .iterator();
729 
730         assertSinkEquals(
731                 it,
732                 "head",
733                 "title",
734                 "text",
735                 "title_",
736                 "head_",
737                 "body",
738                 "list", // TOC start
739                 "listItem",
740                 "link",
741                 "text",
742                 "link_", // first section 2 TOC entry
743                 "list", // sections 3 list start
744                 "listItem",
745                 "link",
746                 "text",
747                 "link_",
748                 "listItem_", // empty section 3 TOC entry
749                 "list_", // sections 3 list end
750                 "listItem_", // first section 2 TOC entry end
751                 "listItem",
752                 "link",
753                 "text",
754                 "link_",
755                 "listItem_", // second section 2 TOC entry
756                 "list_", // TOC end
757                 "text",
758                 "section1",
759                 "section2",
760                 "sectionTitle2",
761                 "text",
762                 "sectionTitle2_",
763                 "section3",
764                 "section4",
765                 "sectionTitle4",
766                 "text",
767                 "sectionTitle4_",
768                 "section4_",
769                 "section3_",
770                 "section2_",
771                 "section2",
772                 "sectionTitle2",
773                 "text",
774                 "sectionTitle2_",
775                 "section2_",
776                 "section1_",
777                 "body_");
778     }
779 
780     // test fix for https://github.com/vsch/flexmark-java/issues/384
781     @Test
782     public void testFlexIssue384() throws Exception {
783         parseFileToEventTestingSink("flex-384");
784     }
785 
786     // Apostrophe versus single quotes
787     // Simple apostrophes (like in Sophie's Choice) must not be replaced with a single quote
788     @Test
789     public void testQuoteVsApostrophe() throws Exception {
790         List<SinkEventElement> eventList =
791                 parseFileToEventTestingSink("quote-vs-apostrophe").getEventList();
792 
793         StringBuilder content = new StringBuilder();
794         for (SinkEventElement element : eventList) {
795             if ("text".equals(element.getName())) {
796                 content.append(element.getArgs()[0]);
797             }
798         }
799         assertEquals(
800                 "This apostrophe isn't a quote." + "This \u2018quoted text\u2019 isn't surrounded by apostrophes.",
801                 content.toString());
802     }
803 
804     @Override
805     protected void assertEventPrefix(Iterator<SinkEventElement> eventIterator) {
806         assertSinkStartsWith(eventIterator, "head", "head_", "body");
807     }
808 
809     @Override
810     protected void assertEventSuffix(Iterator<SinkEventElement> eventIterator) {
811         assertSinkEquals(eventIterator, "body_");
812     }
813 
814     @Override
815     protected String getVerbatimSource() {
816         /**
817          * Markdown doesn't support verbatim text which is not code:
818          * https://spec.commonmark.org/0.31.2/#fenced-code-blocks and https://spec.commonmark.org/0.31.2/#indented-code-blocks
819          */
820         return null;
821     }
822 
823     @Override
824     protected String getVerbatimCodeSource() {
825         return "```" + EOL + "<>{}=#*" + EOL + "```";
826     }
827 }