Index: utils/markdown/test/markdown_tests.dart |
diff --git a/utils/markdown/test/markdown_tests.dart b/utils/markdown/test/markdown_tests.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..10e5b9e25ba2fe9cba6bce5f42c73e240eb600cf |
--- /dev/null |
+++ b/utils/markdown/test/markdown_tests.dart |
@@ -0,0 +1,787 @@ |
+// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+/// Unit tests for markdown. |
+#library('markdown_tests'); |
+ |
+#import('../lib.dart'); |
+ |
+// TODO(rnystrom): Better path to unittest. |
+#import('../../../client/testing/unittest/unittest_vm.dart'); |
+ |
+/// Most of these tests are based on observing how showdown behaves: |
+/// http://softwaremaniacs.org/playground/showdown-highlight/ |
+void main() { |
+ group('Paragraphs', () { |
+ validate('consecutive lines form a single paragraph', ''' |
+ This is the first line. |
+ This is the second line. |
+ ''', ''' |
+ <p>This is the first line. |
+ This is the second line.</p> |
+ '''); |
+ |
+ // TODO(rnystrom): The rules here for what happens to lines following a |
+ // paragraph appear to be completely arbitrary in markdown. If it makes the |
+ // code significantly cleaner, we should consider ourselves free to change |
+ // these tests. |
+ |
+ validate('are terminated by a header', ''' |
+ para |
+ # header |
+ ''', ''' |
+ <p>para</p> |
+ <h1>header</h1> |
+ '''); |
+ |
+ validate('are terminated by a setext header', ''' |
+ para |
+ header |
+ == |
+ ''', ''' |
+ <p>para</p> |
+ <h1>header</h1> |
+ '''); |
+ |
+ validate('are terminated by a hr', ''' |
+ para |
+ ___ |
+ ''', ''' |
+ <p>para</p> |
+ <hr /> |
+ '''); |
+ |
+ validate('consume an unordered list', ''' |
+ para |
+ * list |
+ ''', ''' |
+ <p>para |
+ * list</p> |
+ '''); |
+ |
+ validate('consume an ordered list', ''' |
+ para |
+ 1. list |
+ ''', ''' |
+ <p>para |
+ 1. list</p> |
+ '''); |
+ }); |
+ |
+ group('Setext headers', () { |
+ validate('h1', ''' |
+ text |
+ === |
+ ''', ''' |
+ <h1>text</h1> |
+ '''); |
+ |
+ validate('h2', ''' |
+ text |
+ --- |
+ ''', ''' |
+ <h2>text</h2> |
+ '''); |
+ |
+ validate('h1 on first line becomes text', ''' |
+ === |
+ ''', ''' |
+ <p>===</p> |
+ '''); |
+ |
+ validate('h2 on first line becomes text', ''' |
+ - |
+ ''', ''' |
+ <p>-</p> |
+ '''); |
+ |
+ validate('h1 turns preceding list into text', ''' |
+ - list |
+ === |
+ ''', ''' |
+ <h1>- list</h1> |
+ '''); |
+ |
+ validate('h2 turns preceding list into text', ''' |
+ - list |
+ === |
+ ''', ''' |
+ <h1>- list</h1> |
+ '''); |
+ |
+ validate('h1 turns preceding blockquote into text', ''' |
+ > quote |
+ === |
+ ''', ''' |
+ <h1>> quote</h1> |
+ '''); |
+ |
+ validate('h2 turns preceding blockquote into text', ''' |
+ > quote |
+ === |
+ ''', ''' |
+ <h1>> quote</h1> |
+ '''); |
+ }); |
+ |
+ group('Headers', () { |
+ validate('h1', ''' |
+ # header |
+ ''', ''' |
+ <h1>header</h1> |
+ '''); |
+ |
+ validate('h2', ''' |
+ ## header |
+ ''', ''' |
+ <h2>header</h2> |
+ '''); |
+ |
+ validate('h3', ''' |
+ ### header |
+ ''', ''' |
+ <h3>header</h3> |
+ '''); |
+ |
+ validate('h4', ''' |
+ #### header |
+ ''', ''' |
+ <h4>header</h4> |
+ '''); |
+ |
+ validate('h5', ''' |
+ ##### header |
+ ''', ''' |
+ <h5>header</h5> |
+ '''); |
+ |
+ validate('h6', ''' |
+ ###### header |
+ ''', ''' |
+ <h6>header</h6> |
+ '''); |
+ |
+ validate('trailing "#" are removed', ''' |
+ # header ###### |
+ ''', ''' |
+ <h1>header</h1> |
+ '''); |
+ |
+ }); |
+ |
+ group('Unordered lists', () { |
+ validate('asterisk, plus and hyphen', ''' |
+ * star |
+ - dash |
+ + plus |
+ ''', ''' |
+ <ul> |
+ <li>star</li> |
+ <li>dash</li> |
+ <li>plus</li> |
+ </ul> |
+ '''); |
+ |
+ validate('allow numbered lines after first', ''' |
+ * a |
+ 1. b |
+ ''', ''' |
+ <ul> |
+ <li>a</li> |
+ <li>b</li> |
+ </ul> |
+ '''); |
+ |
+ validate('allow a tab after the marker', ''' |
+ *\ta |
+ +\tb |
+ -\tc |
+ 1.\td |
+ ''', ''' |
+ <ul> |
+ <li>a</li> |
+ <li>b</li> |
+ <li>c</li> |
+ <li>d</li> |
+ </ul> |
+ '''); |
+ |
+ validate('wrap items in paragraphs if blank lines separate', ''' |
+ * one |
+ |
+ * two |
+ ''', ''' |
+ <ul> |
+ <li><p>one</p></li> |
+ <li><p>two</p></li> |
+ </ul> |
+ '''); |
+ |
+ validate('force paragraph on item before and after blank lines', ''' |
+ * one |
+ * two |
+ |
+ * three |
+ ''', ''' |
+ <ul> |
+ <li>one</li> |
+ <li> |
+ <p>two</p> |
+ </li> |
+ <li> |
+ <p>three</p> |
+ </li> |
+ </ul> |
+ '''); |
+ |
+ validate('do not force paragraph if item is already block', ''' |
+ * > quote |
+ |
+ * # header |
+ ''', ''' |
+ <ul> |
+ <li><blockquote><p>quote</p></blockquote></li> |
+ <li><h1>header</h1></li> |
+ </ul> |
+ '''); |
+ |
+ validate('can contain multiple paragraphs', ''' |
+ * one |
+ |
+ two |
+ |
+ * three |
+ ''', ''' |
+ <ul> |
+ <li> |
+ <p>one</p> |
+ <p>two</p> |
+ </li> |
+ <li> |
+ <p>three</p> |
+ </li> |
+ </ul> |
+ '''); |
+ |
+ // TODO(rnystrom): This is how most other markdown parsers handle |
+ // this but that seems like a nasty special case. For now, let's not |
+ // worry about it. |
+ /* |
+ validate('can nest using indentation', ''' |
+ * parent |
+ * child |
+ ''', ''' |
+ <ul> |
+ <li>parent |
+ <ul><li>child</li></ul></li> |
+ </ul> |
+ '''); |
+ */ |
+ }); |
+ |
+ group('Ordered lists', () { |
+ validate('start with numbers', ''' |
+ 1. one |
+ 45. two |
+ 12345. three |
+ ''', ''' |
+ <ol> |
+ <li>one</li> |
+ <li>two</li> |
+ <li>three</li> |
+ </ol> |
+ '''); |
+ |
+ validate('allow unordered lines after first', ''' |
+ 1. a |
+ * b |
+ ''', ''' |
+ <ol> |
+ <li>a</li> |
+ <li>b</li> |
+ </ol> |
+ '''); |
+ }); |
+ |
+ group('Blockquotes', () { |
+ validate('single line', ''' |
+ > blah |
+ ''', ''' |
+ <blockquote> |
+ <p>blah</p> |
+ </blockquote> |
+ '''); |
+ |
+ validate('with two paragraphs', ''' |
+ > first |
+ > |
+ > second |
+ ''', ''' |
+ <blockquote> |
+ <p>first</p> |
+ <p>second</p> |
+ </blockquote> |
+ '''); |
+ |
+ validate('nested', ''' |
+ > one |
+ >> two |
+ > > > three |
+ ''', ''' |
+ <blockquote> |
+ <p>one</p> |
+ <blockquote> |
+ <p>two</p> |
+ <blockquote> |
+ <p>three</p> |
+ </blockquote> |
+ </blockquote> |
+ </blockquote> |
+ '''); |
+ }); |
+ |
+ group('Code blocks', () { |
+ validate('single line', ''' |
+ code |
+ ''', ''' |
+ <pre><code>code</code></pre> |
+ '''); |
+ |
+ validate('include leading whitespace after indentation', ''' |
+ zero |
+ one |
+ two |
+ three |
+ ''', ''' |
+ <pre><code>zero |
+ one |
+ two |
+ three</code></pre> |
+ '''); |
+ |
+ validate('escape HTML characters', ''' |
+ <&> |
+ ''', ''' |
+ <pre><code><&></code></pre> |
+ '''); |
+ }); |
+ |
+ group('Horizontal rules', () { |
+ validate('from dashes', ''' |
+ --- |
+ ''', ''' |
+ <hr /> |
+ '''); |
+ |
+ validate('from asterisks', ''' |
+ *** |
+ ''', ''' |
+ <hr /> |
+ '''); |
+ |
+ validate('from underscores', ''' |
+ ___ |
+ ''', ''' |
+ <hr /> |
+ '''); |
+ |
+ validate('can include up to two spaces', ''' |
+ _ _ _ |
+ ''', ''' |
+ <hr /> |
+ '''); |
+ }); |
+ |
+ group('Block-level HTML', () { |
+ validate('single line', ''' |
+ <table></table> |
+ ''', ''' |
+ <table></table> |
+ '''); |
+ |
+ validate('multi-line', ''' |
+ <table> |
+ blah |
+ </table> |
+ ''', ''' |
+ <table> |
+ blah |
+ </table> |
+ '''); |
+ |
+ validate('blank line ends block', ''' |
+ <table> |
+ blah |
+ </table> |
+ |
+ para |
+ ''', ''' |
+ <table> |
+ blah |
+ </table> |
+ <p>para</p> |
+ '''); |
+ |
+ validate('HTML can be bogus', ''' |
+ <bogus> |
+ blah |
+ </weird> |
+ |
+ para |
+ ''', ''' |
+ <bogus> |
+ blah |
+ </weird> |
+ <p>para</p> |
+ '''); |
+ }); |
+ |
+ group('Strong', () { |
+ validate('using asterisks', ''' |
+ before **strong** after |
+ ''', ''' |
+ <p>before <strong>strong</strong> after</p> |
+ '''); |
+ |
+ validate('using underscores', ''' |
+ before __strong__ after |
+ ''', ''' |
+ <p>before <strong>strong</strong> after</p> |
+ '''); |
+ |
+ validate('unmatched asterisks', ''' |
+ before ** after |
+ ''', ''' |
+ <p>before ** after</p> |
+ '''); |
+ |
+ validate('unmatched underscores', ''' |
+ before __ after |
+ ''', ''' |
+ <p>before __ after</p> |
+ '''); |
+ |
+ validate('multiple spans in one text', ''' |
+ a **one** b __two__ c |
+ ''', ''' |
+ <p>a <strong>one</strong> b <strong>two</strong> c</p> |
+ '''); |
+ |
+ validate('multi-line', ''' |
+ before **first |
+ second** after |
+ ''', ''' |
+ <p>before <strong>first |
+ second</strong> after</p> |
+ '''); |
+ }); |
+ |
+ group('Emphasis and strong', () { |
+ validate('single asterisks', ''' |
+ before *em* after |
+ ''', ''' |
+ <p>before <em>em</em> after</p> |
+ '''); |
+ |
+ validate('single underscores', ''' |
+ before _em_ after |
+ ''', ''' |
+ <p>before <em>em</em> after</p> |
+ '''); |
+ |
+ validate('double asterisks', ''' |
+ before **strong** after |
+ ''', ''' |
+ <p>before <strong>strong</strong> after</p> |
+ '''); |
+ |
+ validate('double underscores', ''' |
+ before __strong__ after |
+ ''', ''' |
+ <p>before <strong>strong</strong> after</p> |
+ '''); |
+ |
+ validate('unmatched asterisk', ''' |
+ before *after |
+ ''', ''' |
+ <p>before *after</p> |
+ '''); |
+ |
+ validate('unmatched underscore', ''' |
+ before _after |
+ ''', ''' |
+ <p>before _after</p> |
+ '''); |
+ |
+ validate('multiple spans in one text', ''' |
+ a *one* b _two_ c |
+ ''', ''' |
+ <p>a <em>one</em> b <em>two</em> c</p> |
+ '''); |
+ |
+ validate('multi-line', ''' |
+ before *first |
+ second* after |
+ ''', ''' |
+ <p>before <em>first |
+ second</em> after</p> |
+ '''); |
+ |
+ validate('not processed when surrounded by spaces', ''' |
+ a * b * c _ d _ e |
+ ''', ''' |
+ <p>a * b * c _ d _ e</p> |
+ '''); |
+ |
+ validate('strong then emphasis', ''' |
+ **strong***em* |
+ ''', ''' |
+ <p><strong>strong</strong><em>em</em></p> |
+ '''); |
+ |
+ validate('emphasis then strong', ''' |
+ *em***strong** |
+ ''', ''' |
+ <p><em>em</em><strong>strong</strong></p> |
+ '''); |
+ |
+ validate('emphasis inside strong', ''' |
+ **strong *em*** |
+ ''', ''' |
+ <p><strong>strong <em>em</em></strong></p> |
+ '''); |
+ |
+ validate('mismatched in nested', ''' |
+ *a _b* c_ |
+ ''', ''' |
+ <p><em>a _b</em> c_</p> |
+ '''); |
+ |
+ validate('cannot nest tags of same type', ''' |
+ *a _b *c* d_ e* |
+ ''', ''' |
+ <p><em>a _b </em>c<em> d_ e</em></p> |
+ '''); |
+ }); |
+ |
+ group('Inline code', () { |
+ validate('simple case', ''' |
+ before `source` after |
+ ''', ''' |
+ <p>before <code>source</code> after</p> |
+ '''); |
+ |
+ validate('unmatched backtick', ''' |
+ before ` after |
+ ''', ''' |
+ <p>before ` after</p> |
+ '''); |
+ validate('multiple spans in one text', ''' |
+ a `one` b `two` c |
+ ''', ''' |
+ <p>a <code>one</code> b <code>two</code> c</p> |
+ '''); |
+ |
+ validate('multi-line', ''' |
+ before `first |
+ second` after |
+ ''', ''' |
+ <p>before <code>first |
+ second</code> after</p> |
+ '''); |
+ |
+ validate('double backticks', ''' |
+ before ``can `contain` backticks`` after |
+ ''', ''' |
+ <p>before <code>can `contain` backticks</code> after</p> |
+ '''); |
+ |
+ validate('double backticks with spaces', ''' |
+ before `` `tick` `` after |
+ ''', ''' |
+ <p>before <code>`tick`</code> after</p> |
+ '''); |
+ |
+ validate('ignore markup inside code', ''' |
+ before `*b* _c_` after |
+ ''', ''' |
+ <p>before <code>*b* _c_</code> after</p> |
+ '''); |
+ |
+ validate('escape HTML characters', ''' |
+ `<&>` |
+ ''', ''' |
+ <p><code><&></code></p> |
+ '''); |
+ }); |
+ |
+ group('HTML encoding', () { |
+ validate('less than and ampersand are escaped', ''' |
+ < & |
+ ''', ''' |
+ <p>< &</p> |
+ '''); |
+ validate('greater than is not escaped', ''' |
+ not you > |
+ ''', ''' |
+ <p>not you ></p> |
+ '''); |
+ validate('existing entities are untouched', ''' |
+ & |
+ ''', ''' |
+ <p>&</p> |
+ '''); |
+ }); |
+ |
+ group('Autolinks', () { |
+ validate('basic link', ''' |
+ before <http://foo.com/> after |
+ ''', ''' |
+ <p>before <a href="http://foo.com/">http://foo.com/</a> after</p> |
+ '''); |
+ validate('handles ampersand in url', ''' |
+ <http://foo.com/?a=1&b=2> |
+ ''', ''' |
+ <p><a href="http://foo.com/?a=1&b=2">http://foo.com/?a=1&b=2</a></p> |
+ '''); |
+ }); |
+ |
+ group('Reference links', () { |
+ validate('double quotes for title', ''' |
+ links [are] [a] awesome |
+ |
+ [a]: http://foo.com "woo" |
+ ''', ''' |
+ <p>links <a href="http://foo.com" title="woo">are</a> awesome</p> |
+ '''); |
+ validate('single quoted title', """ |
+ links [are] [a] awesome |
+ |
+ [a]: http://foo.com 'woo' |
+ """, ''' |
+ <p>links <a href="http://foo.com" title="woo">are</a> awesome</p> |
+ '''); |
+ validate('parentheses for title', ''' |
+ links [are] [a] awesome |
+ |
+ [a]: http://foo.com (woo) |
+ ''', ''' |
+ <p>links <a href="http://foo.com" title="woo">are</a> awesome</p> |
+ '''); |
+ validate('no title', ''' |
+ links [are] [a] awesome |
+ |
+ [a]: http://foo.com |
+ ''', ''' |
+ <p>links <a href="http://foo.com">are</a> awesome</p> |
+ '''); |
+ validate('unknown link becomes plaintext', ''' |
+ [not] [known] |
+ ''', ''' |
+ <p>[not] [known]</p> |
+ '''); |
+ validate('can style link contents', ''' |
+ links [*are*] [a] awesome |
+ |
+ [a]: http://foo.com |
+ ''', ''' |
+ <p>links <a href="http://foo.com"><em>are</em></a> awesome</p> |
+ '''); |
+ }); |
+ |
+ group('Inline links', () { |
+ validate('double quotes for title', ''' |
+ links [are](http://foo.com "woo") awesome |
+ ''', ''' |
+ <p>links <a href="http://foo.com" title="woo">are</a> awesome</p> |
+ '''); |
+ validate('no title', ''' |
+ links [are] (http://foo.com) awesome |
+ ''', ''' |
+ <p>links <a href="http://foo.com">are</a> awesome</p> |
+ '''); |
+ validate('can style link contents', ''' |
+ links [*are*](http://foo.com) awesome |
+ ''', ''' |
+ <p>links <a href="http://foo.com"><em>are</em></a> awesome</p> |
+ '''); |
+ }); |
+} |
+ |
+validate(String description, String markdown, String html) { |
+ test(description, () { |
+ markdown = cleanUpLiteral(markdown); |
+ html = cleanUpLiteral(html); |
+ |
+ var result = markdownToHtml(markdown); |
+ var passed = compareOutput(html, result); |
+ |
+ if (!passed) { |
+ // Remove trailing newline. |
+ html = html.substring(0, html.length - 1); |
+ |
+ print('FAIL: $description'); |
+ print(' expect: ${html.replaceAll("\n", "\n ")}'); |
+ print(' actual: ${result.replaceAll("\n", "\n ")}'); |
+ print(''); |
+ } |
+ |
+ expect(passed).isTrue(); |
+ }); |
+} |
+ |
+/// The tests uses triple-quoted strings, which will include leading indentation |
+/// to make them look nice in code. But we don't want the markdown parser to |
+/// actually see that, so this cleans it all up. |
+/// |
+/// Note that this is very sensitive to how the literals are styled. They should |
+/// be: |
+/// ''' |
+/// Text starts on own line. Lines up with subsequent lines. |
+/// Lines are indented exactly 8 characters from the left margin. |
+/// Close is on the same line.''' |
+/// |
+cleanUpLiteral(String text) { |
+ var lines = text.split('\n'); |
+ for (var j = 0; j < lines.length; j++) { |
+ if (lines[j].length > 8) { |
+ lines[j] = lines[j].substring(8, lines[j].length); |
+ } else { |
+ lines[j] = ''; |
+ } |
+ } |
+ |
+ return Strings.join(lines, '\n'); |
+} |
+ |
+/// Does a loose comparison of the two strings of HTML. Ignores differences in |
+/// newlines and indentation. |
+compareOutput(String a, String b) { |
+ int i = 0; |
+ int j = 0; |
+ |
+ skipIgnored(String s, int i) { |
+ // Ignore newlines. |
+ while ((i < s.length) && (s[i] == '\n')) { |
+ i++; |
+ // Ignore indentation. |
+ while ((i < s.length) && (s[i] == ' ')) i++; |
+ } |
+ |
+ return i; |
+ } |
+ |
+ while (true) { |
+ i = skipIgnored(a, i); |
+ j = skipIgnored(b, j); |
+ |
+ // If one string runs out of non-ignored strings, the other must too. |
+ if (i == a.length) return j == b.length; |
+ if (j == b.length) return i == a.length; |
+ |
+ if (a[i] != b[j]) return false; |
+ i++; |
+ j++; |
+ } |
+} |