OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 /// Unit tests for markdown. | |
6 #library('markdown_tests'); | |
7 | |
8 #import('../lib.dart'); | |
9 | |
10 // TODO(rnystrom): Better path to unittest. | |
11 #import('../../../lib/unittest/unittest.dart'); | |
12 | |
13 /// Most of these tests are based on observing how showdown behaves: | |
14 /// http://softwaremaniacs.org/playground/showdown-highlight/ | |
15 void main() { | |
16 group('Paragraphs', () { | |
17 validate('consecutive lines form a single paragraph', ''' | |
18 This is the first line. | |
19 This is the second line. | |
20 ''', ''' | |
21 <p>This is the first line. | |
22 This is the second line.</p> | |
23 '''); | |
24 | |
25 // TODO(rnystrom): The rules here for what happens to lines following a | |
26 // paragraph appear to be completely arbitrary in markdown. If it makes the | |
27 // code significantly cleaner, we should consider ourselves free to change | |
28 // these tests. | |
29 | |
30 validate('are terminated by a header', ''' | |
31 para | |
32 # header | |
33 ''', ''' | |
34 <p>para</p> | |
35 <h1>header</h1> | |
36 '''); | |
37 | |
38 validate('are terminated by a setext header', ''' | |
39 para | |
40 header | |
41 == | |
42 ''', ''' | |
43 <p>para</p> | |
44 <h1>header</h1> | |
45 '''); | |
46 | |
47 validate('are terminated by a hr', ''' | |
48 para | |
49 ___ | |
50 ''', ''' | |
51 <p>para</p> | |
52 <hr /> | |
53 '''); | |
54 | |
55 validate('consume an unordered list', ''' | |
56 para | |
57 * list | |
58 ''', ''' | |
59 <p>para | |
60 * list</p> | |
61 '''); | |
62 | |
63 validate('consume an ordered list', ''' | |
64 para | |
65 1. list | |
66 ''', ''' | |
67 <p>para | |
68 1. list</p> | |
69 '''); | |
70 }); | |
71 | |
72 group('Setext headers', () { | |
73 validate('h1', ''' | |
74 text | |
75 === | |
76 ''', ''' | |
77 <h1>text</h1> | |
78 '''); | |
79 | |
80 validate('h2', ''' | |
81 text | |
82 --- | |
83 ''', ''' | |
84 <h2>text</h2> | |
85 '''); | |
86 | |
87 validate('h1 on first line becomes text', ''' | |
88 === | |
89 ''', ''' | |
90 <p>===</p> | |
91 '''); | |
92 | |
93 validate('h2 on first line becomes text', ''' | |
94 - | |
95 ''', ''' | |
96 <p>-</p> | |
97 '''); | |
98 | |
99 validate('h1 turns preceding list into text', ''' | |
100 - list | |
101 === | |
102 ''', ''' | |
103 <h1>- list</h1> | |
104 '''); | |
105 | |
106 validate('h2 turns preceding list into text', ''' | |
107 - list | |
108 === | |
109 ''', ''' | |
110 <h1>- list</h1> | |
111 '''); | |
112 | |
113 validate('h1 turns preceding blockquote into text', ''' | |
114 > quote | |
115 === | |
116 ''', ''' | |
117 <h1>> quote</h1> | |
118 '''); | |
119 | |
120 validate('h2 turns preceding blockquote into text', ''' | |
121 > quote | |
122 === | |
123 ''', ''' | |
124 <h1>> quote</h1> | |
125 '''); | |
126 }); | |
127 | |
128 group('Headers', () { | |
129 validate('h1', ''' | |
130 # header | |
131 ''', ''' | |
132 <h1>header</h1> | |
133 '''); | |
134 | |
135 validate('h2', ''' | |
136 ## header | |
137 ''', ''' | |
138 <h2>header</h2> | |
139 '''); | |
140 | |
141 validate('h3', ''' | |
142 ### header | |
143 ''', ''' | |
144 <h3>header</h3> | |
145 '''); | |
146 | |
147 validate('h4', ''' | |
148 #### header | |
149 ''', ''' | |
150 <h4>header</h4> | |
151 '''); | |
152 | |
153 validate('h5', ''' | |
154 ##### header | |
155 ''', ''' | |
156 <h5>header</h5> | |
157 '''); | |
158 | |
159 validate('h6', ''' | |
160 ###### header | |
161 ''', ''' | |
162 <h6>header</h6> | |
163 '''); | |
164 | |
165 validate('trailing "#" are removed', ''' | |
166 # header ###### | |
167 ''', ''' | |
168 <h1>header</h1> | |
169 '''); | |
170 | |
171 }); | |
172 | |
173 group('Unordered lists', () { | |
174 validate('asterisk, plus and hyphen', ''' | |
175 * star | |
176 - dash | |
177 + plus | |
178 ''', ''' | |
179 <ul> | |
180 <li>star</li> | |
181 <li>dash</li> | |
182 <li>plus</li> | |
183 </ul> | |
184 '''); | |
185 | |
186 validate('allow numbered lines after first', ''' | |
187 * a | |
188 1. b | |
189 ''', ''' | |
190 <ul> | |
191 <li>a</li> | |
192 <li>b</li> | |
193 </ul> | |
194 '''); | |
195 | |
196 validate('allow a tab after the marker', ''' | |
197 *\ta | |
198 +\tb | |
199 -\tc | |
200 1.\td | |
201 ''', ''' | |
202 <ul> | |
203 <li>a</li> | |
204 <li>b</li> | |
205 <li>c</li> | |
206 <li>d</li> | |
207 </ul> | |
208 '''); | |
209 | |
210 validate('wrap items in paragraphs if blank lines separate', ''' | |
211 * one | |
212 | |
213 * two | |
214 ''', ''' | |
215 <ul> | |
216 <li><p>one</p></li> | |
217 <li><p>two</p></li> | |
218 </ul> | |
219 '''); | |
220 | |
221 validate('force paragraph on item before and after blank lines', ''' | |
222 * one | |
223 * two | |
224 | |
225 * three | |
226 ''', ''' | |
227 <ul> | |
228 <li>one</li> | |
229 <li> | |
230 <p>two</p> | |
231 </li> | |
232 <li> | |
233 <p>three</p> | |
234 </li> | |
235 </ul> | |
236 '''); | |
237 | |
238 validate('do not force paragraph if item is already block', ''' | |
239 * > quote | |
240 | |
241 * # header | |
242 ''', ''' | |
243 <ul> | |
244 <li><blockquote><p>quote</p></blockquote></li> | |
245 <li><h1>header</h1></li> | |
246 </ul> | |
247 '''); | |
248 | |
249 validate('can contain multiple paragraphs', ''' | |
250 * one | |
251 | |
252 two | |
253 | |
254 * three | |
255 ''', ''' | |
256 <ul> | |
257 <li> | |
258 <p>one</p> | |
259 <p>two</p> | |
260 </li> | |
261 <li> | |
262 <p>three</p> | |
263 </li> | |
264 </ul> | |
265 '''); | |
266 | |
267 // TODO(rnystrom): This is how most other markdown parsers handle | |
268 // this but that seems like a nasty special case. For now, let's not | |
269 // worry about it. | |
270 /* | |
271 validate('can nest using indentation', ''' | |
272 * parent | |
273 * child | |
274 ''', ''' | |
275 <ul> | |
276 <li>parent | |
277 <ul><li>child</li></ul></li> | |
278 </ul> | |
279 '''); | |
280 */ | |
281 }); | |
282 | |
283 group('Ordered lists', () { | |
284 validate('start with numbers', ''' | |
285 1. one | |
286 45. two | |
287 12345. three | |
288 ''', ''' | |
289 <ol> | |
290 <li>one</li> | |
291 <li>two</li> | |
292 <li>three</li> | |
293 </ol> | |
294 '''); | |
295 | |
296 validate('allow unordered lines after first', ''' | |
297 1. a | |
298 * b | |
299 ''', ''' | |
300 <ol> | |
301 <li>a</li> | |
302 <li>b</li> | |
303 </ol> | |
304 '''); | |
305 }); | |
306 | |
307 group('Blockquotes', () { | |
308 validate('single line', ''' | |
309 > blah | |
310 ''', ''' | |
311 <blockquote> | |
312 <p>blah</p> | |
313 </blockquote> | |
314 '''); | |
315 | |
316 validate('with two paragraphs', ''' | |
317 > first | |
318 > | |
319 > second | |
320 ''', ''' | |
321 <blockquote> | |
322 <p>first</p> | |
323 <p>second</p> | |
324 </blockquote> | |
325 '''); | |
326 | |
327 validate('nested', ''' | |
328 > one | |
329 >> two | |
330 > > > three | |
331 ''', ''' | |
332 <blockquote> | |
333 <p>one</p> | |
334 <blockquote> | |
335 <p>two</p> | |
336 <blockquote> | |
337 <p>three</p> | |
338 </blockquote> | |
339 </blockquote> | |
340 </blockquote> | |
341 '''); | |
342 }); | |
343 | |
344 group('Code blocks', () { | |
345 validate('single line', ''' | |
346 code | |
347 ''', ''' | |
348 <pre><code>code</code></pre> | |
349 '''); | |
350 | |
351 validate('include leading whitespace after indentation', ''' | |
352 zero | |
353 one | |
354 two | |
355 three | |
356 ''', ''' | |
357 <pre><code>zero | |
358 one | |
359 two | |
360 three</code></pre> | |
361 '''); | |
362 | |
363 validate('escape HTML characters', ''' | |
364 <&> | |
365 ''', ''' | |
366 <pre><code><&></code></pre> | |
367 '''); | |
368 }); | |
369 | |
370 group('Horizontal rules', () { | |
371 validate('from dashes', ''' | |
372 --- | |
373 ''', ''' | |
374 <hr /> | |
375 '''); | |
376 | |
377 validate('from asterisks', ''' | |
378 *** | |
379 ''', ''' | |
380 <hr /> | |
381 '''); | |
382 | |
383 validate('from underscores', ''' | |
384 ___ | |
385 ''', ''' | |
386 <hr /> | |
387 '''); | |
388 | |
389 validate('can include up to two spaces', ''' | |
390 _ _ _ | |
391 ''', ''' | |
392 <hr /> | |
393 '''); | |
394 }); | |
395 | |
396 group('Block-level HTML', () { | |
397 validate('single line', ''' | |
398 <table></table> | |
399 ''', ''' | |
400 <table></table> | |
401 '''); | |
402 | |
403 validate('multi-line', ''' | |
404 <table> | |
405 blah | |
406 </table> | |
407 ''', ''' | |
408 <table> | |
409 blah | |
410 </table> | |
411 '''); | |
412 | |
413 validate('blank line ends block', ''' | |
414 <table> | |
415 blah | |
416 </table> | |
417 | |
418 para | |
419 ''', ''' | |
420 <table> | |
421 blah | |
422 </table> | |
423 <p>para</p> | |
424 '''); | |
425 | |
426 validate('HTML can be bogus', ''' | |
427 <bogus> | |
428 blah | |
429 </weird> | |
430 | |
431 para | |
432 ''', ''' | |
433 <bogus> | |
434 blah | |
435 </weird> | |
436 <p>para</p> | |
437 '''); | |
438 }); | |
439 | |
440 group('Strong', () { | |
441 validate('using asterisks', ''' | |
442 before **strong** after | |
443 ''', ''' | |
444 <p>before <strong>strong</strong> after</p> | |
445 '''); | |
446 | |
447 validate('using underscores', ''' | |
448 before __strong__ after | |
449 ''', ''' | |
450 <p>before <strong>strong</strong> after</p> | |
451 '''); | |
452 | |
453 validate('unmatched asterisks', ''' | |
454 before ** after | |
455 ''', ''' | |
456 <p>before ** after</p> | |
457 '''); | |
458 | |
459 validate('unmatched underscores', ''' | |
460 before __ after | |
461 ''', ''' | |
462 <p>before __ after</p> | |
463 '''); | |
464 | |
465 validate('multiple spans in one text', ''' | |
466 a **one** b __two__ c | |
467 ''', ''' | |
468 <p>a <strong>one</strong> b <strong>two</strong> c</p> | |
469 '''); | |
470 | |
471 validate('multi-line', ''' | |
472 before **first | |
473 second** after | |
474 ''', ''' | |
475 <p>before <strong>first | |
476 second</strong> after</p> | |
477 '''); | |
478 }); | |
479 | |
480 group('Emphasis and strong', () { | |
481 validate('single asterisks', ''' | |
482 before *em* after | |
483 ''', ''' | |
484 <p>before <em>em</em> after</p> | |
485 '''); | |
486 | |
487 validate('single underscores', ''' | |
488 before _em_ after | |
489 ''', ''' | |
490 <p>before <em>em</em> after</p> | |
491 '''); | |
492 | |
493 validate('double asterisks', ''' | |
494 before **strong** after | |
495 ''', ''' | |
496 <p>before <strong>strong</strong> after</p> | |
497 '''); | |
498 | |
499 validate('double underscores', ''' | |
500 before __strong__ after | |
501 ''', ''' | |
502 <p>before <strong>strong</strong> after</p> | |
503 '''); | |
504 | |
505 validate('unmatched asterisk', ''' | |
506 before *after | |
507 ''', ''' | |
508 <p>before *after</p> | |
509 '''); | |
510 | |
511 validate('unmatched underscore', ''' | |
512 before _after | |
513 ''', ''' | |
514 <p>before _after</p> | |
515 '''); | |
516 | |
517 validate('multiple spans in one text', ''' | |
518 a *one* b _two_ c | |
519 ''', ''' | |
520 <p>a <em>one</em> b <em>two</em> c</p> | |
521 '''); | |
522 | |
523 validate('multi-line', ''' | |
524 before *first | |
525 second* after | |
526 ''', ''' | |
527 <p>before <em>first | |
528 second</em> after</p> | |
529 '''); | |
530 | |
531 validate('not processed when surrounded by spaces', ''' | |
532 a * b * c _ d _ e | |
533 ''', ''' | |
534 <p>a * b * c _ d _ e</p> | |
535 '''); | |
536 | |
537 validate('strong then emphasis', ''' | |
538 **strong***em* | |
539 ''', ''' | |
540 <p><strong>strong</strong><em>em</em></p> | |
541 '''); | |
542 | |
543 validate('emphasis then strong', ''' | |
544 *em***strong** | |
545 ''', ''' | |
546 <p><em>em</em><strong>strong</strong></p> | |
547 '''); | |
548 | |
549 validate('emphasis inside strong', ''' | |
550 **strong *em*** | |
551 ''', ''' | |
552 <p><strong>strong <em>em</em></strong></p> | |
553 '''); | |
554 | |
555 validate('mismatched in nested', ''' | |
556 *a _b* c_ | |
557 ''', ''' | |
558 <p><em>a _b</em> c_</p> | |
559 '''); | |
560 | |
561 validate('cannot nest tags of same type', ''' | |
562 *a _b *c* d_ e* | |
563 ''', ''' | |
564 <p><em>a _b </em>c<em> d_ e</em></p> | |
565 '''); | |
566 }); | |
567 | |
568 group('Inline code', () { | |
569 validate('simple case', ''' | |
570 before `source` after | |
571 ''', ''' | |
572 <p>before <code>source</code> after</p> | |
573 '''); | |
574 | |
575 validate('unmatched backtick', ''' | |
576 before ` after | |
577 ''', ''' | |
578 <p>before ` after</p> | |
579 '''); | |
580 validate('multiple spans in one text', ''' | |
581 a `one` b `two` c | |
582 ''', ''' | |
583 <p>a <code>one</code> b <code>two</code> c</p> | |
584 '''); | |
585 | |
586 validate('multi-line', ''' | |
587 before `first | |
588 second` after | |
589 ''', ''' | |
590 <p>before <code>first | |
591 second</code> after</p> | |
592 '''); | |
593 | |
594 validate('double backticks', ''' | |
595 before ``can `contain` backticks`` after | |
596 ''', ''' | |
597 <p>before <code>can `contain` backticks</code> after</p> | |
598 '''); | |
599 | |
600 validate('double backticks with spaces', ''' | |
601 before `` `tick` `` after | |
602 ''', ''' | |
603 <p>before <code>`tick`</code> after</p> | |
604 '''); | |
605 | |
606 validate('multiline double backticks with spaces', ''' | |
607 before ``in `tick` | |
608 another`` after | |
609 ''', ''' | |
610 <p>before <code>in `tick` | |
611 another</code> after</p> | |
612 '''); | |
613 | |
614 validate('ignore markup inside code', ''' | |
615 before `*b* _c_` after | |
616 ''', ''' | |
617 <p>before <code>*b* _c_</code> after</p> | |
618 '''); | |
619 | |
620 validate('escape HTML characters', ''' | |
621 `<&>` | |
622 ''', ''' | |
623 <p><code><&></code></p> | |
624 '''); | |
625 | |
626 validate('escape HTML tags', ''' | |
627 '*' `<em>` | |
628 ''', ''' | |
629 <p>'*' <code><em></code></p> | |
630 '''); | |
631 }); | |
632 | |
633 group('HTML encoding', () { | |
634 validate('less than and ampersand are escaped', ''' | |
635 < & | |
636 ''', ''' | |
637 <p>< &</p> | |
638 '''); | |
639 validate('greater than is not escaped', ''' | |
640 not you > | |
641 ''', ''' | |
642 <p>not you ></p> | |
643 '''); | |
644 validate('existing entities are untouched', ''' | |
645 & | |
646 ''', ''' | |
647 <p>&</p> | |
648 '''); | |
649 }); | |
650 | |
651 group('Autolinks', () { | |
652 validate('basic link', ''' | |
653 before <http://foo.com/> after | |
654 ''', ''' | |
655 <p>before <a href="http://foo.com/">http://foo.com/</a> after</p> | |
656 '''); | |
657 validate('handles ampersand in url', ''' | |
658 <http://foo.com/?a=1&b=2> | |
659 ''', ''' | |
660 <p><a href="http://foo.com/?a=1&b=2">http://foo.com/?a=1&b=2</a></p> | |
661 '''); | |
662 }); | |
663 | |
664 group('Reference links', () { | |
665 validate('double quotes for title', ''' | |
666 links [are] [a] awesome | |
667 | |
668 [a]: http://foo.com "woo" | |
669 ''', ''' | |
670 <p>links <a href="http://foo.com" title="woo">are</a> awesome</p> | |
671 '''); | |
672 validate('single quoted title', """ | |
673 links [are] [a] awesome | |
674 | |
675 [a]: http://foo.com 'woo' | |
676 """, ''' | |
677 <p>links <a href="http://foo.com" title="woo">are</a> awesome</p> | |
678 '''); | |
679 validate('parentheses for title', ''' | |
680 links [are] [a] awesome | |
681 | |
682 [a]: http://foo.com (woo) | |
683 ''', ''' | |
684 <p>links <a href="http://foo.com" title="woo">are</a> awesome</p> | |
685 '''); | |
686 validate('no title', ''' | |
687 links [are] [a] awesome | |
688 | |
689 [a]: http://foo.com | |
690 ''', ''' | |
691 <p>links <a href="http://foo.com">are</a> awesome</p> | |
692 '''); | |
693 validate('unknown link becomes plaintext', ''' | |
694 [not] [known] | |
695 ''', ''' | |
696 <p>[not] [known]</p> | |
697 '''); | |
698 validate('can style link contents', ''' | |
699 links [*are*] [a] awesome | |
700 | |
701 [a]: http://foo.com | |
702 ''', ''' | |
703 <p>links <a href="http://foo.com"><em>are</em></a> awesome</p> | |
704 '''); | |
705 validate('inline styles after a bad link are processed', ''' | |
706 [bad] `code` | |
707 ''', ''' | |
708 <p>[bad] <code>code</code></p> | |
709 '''); | |
710 }); | |
711 | |
712 group('Inline links', () { | |
713 validate('double quotes for title', ''' | |
714 links [are](http://foo.com "woo") awesome | |
715 ''', ''' | |
716 <p>links <a href="http://foo.com" title="woo">are</a> awesome</p> | |
717 '''); | |
718 validate('no title', ''' | |
719 links [are] (http://foo.com) awesome | |
720 ''', ''' | |
721 <p>links <a href="http://foo.com">are</a> awesome</p> | |
722 '''); | |
723 validate('can style link contents', ''' | |
724 links [*are*](http://foo.com) awesome | |
725 ''', ''' | |
726 <p>links <a href="http://foo.com"><em>are</em></a> awesome</p> | |
727 '''); | |
728 }); | |
729 } | |
730 | |
731 validate(String description, String markdown, String html) { | |
732 test(description, () { | |
733 markdown = cleanUpLiteral(markdown); | |
734 html = cleanUpLiteral(html); | |
735 | |
736 var result = markdownToHtml(markdown); | |
737 var passed = compareOutput(html, result); | |
738 | |
739 if (!passed) { | |
740 // Remove trailing newline. | |
741 html = html.substring(0, html.length - 1); | |
742 | |
743 print('FAIL: $description'); | |
744 print(' expect: ${html.replaceAll("\n", "\n ")}'); | |
745 print(' actual: ${result.replaceAll("\n", "\n ")}'); | |
746 print(''); | |
747 } | |
748 | |
749 expect(passed).isTrue(); | |
750 }); | |
751 } | |
752 | |
753 /// The tests uses triple-quoted strings, which will include leading indentation | |
754 /// to make them look nice in code. But we don't want the markdown parser to | |
755 /// actually see that, so this cleans it all up. | |
756 /// | |
757 /// Note that this is very sensitive to how the literals are styled. They should | |
758 /// be: | |
759 /// ''' | |
760 /// Text starts on own line. Lines up with subsequent lines. | |
761 /// Lines are indented exactly 8 characters from the left margin. | |
762 /// Close is on the same line.''' | |
763 /// | |
764 cleanUpLiteral(String text) { | |
765 var lines = text.split('\n'); | |
766 for (var j = 0; j < lines.length; j++) { | |
767 if (lines[j].length > 8) { | |
768 lines[j] = lines[j].substring(8, lines[j].length); | |
769 } else { | |
770 lines[j] = ''; | |
771 } | |
772 } | |
773 | |
774 return Strings.join(lines, '\n'); | |
775 } | |
776 | |
777 /// Does a loose comparison of the two strings of HTML. Ignores differences in | |
778 /// newlines and indentation. | |
779 compareOutput(String a, String b) { | |
780 int i = 0; | |
781 int j = 0; | |
782 | |
783 skipIgnored(String s, int i) { | |
784 // Ignore newlines. | |
785 while ((i < s.length) && (s[i] == '\n')) { | |
786 i++; | |
787 // Ignore indentation. | |
788 while ((i < s.length) && (s[i] == ' ')) i++; | |
789 } | |
790 | |
791 return i; | |
792 } | |
793 | |
794 while (true) { | |
795 i = skipIgnored(a, i); | |
796 j = skipIgnored(b, j); | |
797 | |
798 // If one string runs out of non-ignored strings, the other must too. | |
799 if (i == a.length) return j == b.length; | |
800 if (j == b.length) return i == a.length; | |
801 | |
802 if (a[i] != b[j]) return false; | |
803 i++; | |
804 j++; | |
805 } | |
806 } | |
OLD | NEW |