content: // 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 markdownTests;
  
  import 'package:unittest/unittest.dart';
  
  // TODO(rnystrom): Use "package:" URL (#4968).
  import '../lib/markdown.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>
          ''');
  
      // Windows line endings have a \r\n format
      // instead of the unix \n format.
      validate('take account of windows line endings', '''
          line1\r\n\r\n        line2\r\n
          ''', '''
          <p>line1</p>
          <p>line2</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>
          ''');
  
      validate('can span newlines', '''
          *   one
              two
          *   three
          ''', '''
          <ul>
            <li>
              <p>one
              two</p>
            </li>
            <li>
              three
            </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('code blocks separated by newlines form one block', '''
              zero
              one
  
              two
  
              three
          ''', '''
          <pre><code>zero
           one
  
           two
  
           three</code></pre>
          ''');
  
      validate('code blocks separated by two newlines form multiple blocks', '''
              zero
              one
  
  
              two
  
  
              three
          ''', '''
          <pre><code>zero
           one</code></pre>
          <pre><code>two</code></pre>
          <pre><code>three</code></pre>
          ''');
  
      validate('escape HTML characters', '''
              <&>
          ''', '''
          <pre><code>&lt;&amp;&gt;</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('simple double backticks', '''
          before ``source`` after
          ''', '''
          <p>before <code>source</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('multiline double backticks with spaces', '''
          before ``in `tick`
          another`` after
          ''', '''
          <p>before <code>in `tick`
          another</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>&lt;&amp;&gt;</code></p>
          ''');
  
      validate('escape HTML tags', '''
          '*' `<em>`
          ''', '''
          <p>'*' <code>&lt;em&gt;</code></p>
          ''');
    });
  
    group('HTML encoding', () {
      validate('less than and ampersand are escaped', '''
          < &
          ''', '''
          <p>&lt; &amp;</p>
          ''');
      validate('greater than is not escaped', '''
          not you >
          ''', '''
          <p>not you ></p>
          ''');
      validate('existing entities are untouched', '''
          &amp;
          ''', '''
          <p>&amp;</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&amp;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>
          ''');
      validate('inline styles after a bad link are processed', '''
          [bad] `code`
          ''', '''
          <p>[bad] <code>code</code></p>
          ''');
      validate('empty reference uses text from link', '''
          links [are][] awesome
  
          [are]: http://foo.com
          ''', '''
          <p>links <a href="http://foo.com">are</a> awesome</p>
          ''');
      validate('references are case-insensitive', '''
          links [ARE][] awesome
  
          [are]: http://foo.com
          ''', '''
          <p>links <a href="http://foo.com">ARE</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>
          ''');
    });
  
    group('Resolver', () {
      var nyanResolver = (text) => new Text('~=[,,_${text}_,,]:3');
      validate('simple resolver', '''
          resolve [this] thing
          ''', '''
          <p>resolve ~=[,,_this_,,]:3 thing</p>
          ''', linkResolver: nyanResolver);
    });
  
    group('Custom inline syntax', () {
      List<InlineSyntax> nyanSyntax =
        [new TextSyntax('nyan', sub: '~=[,,_,,]:3')];
      validate('simple inline syntax', '''
          nyan
          ''', '''
          <p>~=[,,_,,]:3</p>
          ''', inlineSyntaxes: nyanSyntax);
  
      // TODO(amouravski): need more tests here for custom syntaxes, as some
      // things are not quite working properly. The regexps are sometime a little
      // too greedy, I think.
    });
  }
  
  /**
   * Removes eight spaces of leading indentation from a multiline string.
   *
   * 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.'''
   *
   * This does nothing if text is only a single line.
   */
  // TODO(nweiz): Make this auto-detect the indentation level from the first
  // non-whitespace line.
  String cleanUpLiteral(String text) {
    var lines = text.split('\n');
    if (lines.length <= 1) return text;
  
    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 lines.join('\n');
  }
  
  validate(String description, String markdown, String html,
           {bool verbose: false, inlineSyntaxes, linkResolver}) {
    test(description, () {
      markdown = cleanUpLiteral(markdown);
      html = cleanUpLiteral(html);
  
      var result = markdownToHtml(markdown, inlineSyntaxes: inlineSyntaxes,
          linkResolver: linkResolver);
      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, verbose: verbose);
    });
  }
  
  /// 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++;
    }
  }
  