// Copyright (c) 2012, 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.

import 'package:test/test.dart';
import 'package:yaml/yaml.dart';

import 'utils.dart';

main() {
  var infinity = double.parse("Infinity");
  var nan = double.parse("NaN");

  group('has a friendly error message for', () {
    var tabError = predicate((e) =>
        e.toString().contains('Tab characters are not allowed as indentation'));

    test('using a tab as indentation', () {
      expect(() => loadYaml('foo:\n\tbar'), throwsA(tabError));
    });

    test('using a tab not as indentation', () {
      expect(() => loadYaml('''
          "foo
          \tbar"
          error'''), throwsA(isNot(tabError)));
    });
  });

  group("refuses", () {
    // Regression test for #19.
    test("invalid contents", () {
      expectYamlFails("{");
    });

    test("duplicate mapping keys", () {
      expectYamlFails("{a: 1, a: 2}");
    });

    group("documents that declare version", () {
      test("1.0", () {
        expectYamlFails("""
         %YAML 1.0
         --- text
         """);
      });

      test("1.3", () {
        expectYamlFails("""
           %YAML 1.3
           --- text
           """);
      });

      test("2.0", () {
        expectYamlFails("""
           %YAML 2.0
           --- text
           """);
      });
    });
  });

  test("includes source span information", () {
    var yaml = loadYamlNode(r"""
- foo:
    bar
- 123
""") as YamlList;

    expect(yaml.span.start.line, equals(0));
    expect(yaml.span.start.column, equals(0));
    expect(yaml.span.end.line, equals(3));
    expect(yaml.span.end.column, equals(0));

    var map = yaml.nodes.first as YamlMap;
    expect(map.span.start.line, equals(0));
    expect(map.span.start.column, equals(2));
    expect(map.span.end.line, equals(2));
    expect(map.span.end.column, equals(0));

    var key = map.nodes.keys.first;
    expect(key.span.start.line, equals(0));
    expect(key.span.start.column, equals(2));
    expect(key.span.end.line, equals(0));
    expect(key.span.end.column, equals(5));

    var value = map.nodes.values.first;
    expect(value.span.start.line, equals(1));
    expect(value.span.start.column, equals(4));
    expect(value.span.end.line, equals(1));
    expect(value.span.end.column, equals(7));

    var scalar = yaml.nodes.last;
    expect(scalar.span.start.line, equals(2));
    expect(scalar.span.start.column, equals(2));
    expect(scalar.span.end.line, equals(2));
    expect(scalar.span.end.column, equals(5));
  });

  // The following tests are all taken directly from the YAML spec
  // (http://www.yaml.org/spec/1.2/spec.html). Most of them are code examples
  // that are directly included in the spec, but additional tests are derived
  // from the prose.

  // A few examples from the spec are deliberately excluded, because they test
  // features that this implementation doesn't intend to support (character
  // encoding detection and user-defined tags). More tests are commented out,
  // because they're intended to be supported but not yet implemented.

  // Chapter 2 is just a preview of various Yaml documents. It's probably not
  // necessary to test its examples, but it would be nice to test everything in
  // the spec.
  group('2.1: Collections', () {
    test('[Example 2.1]', () {
      expectYamlLoads(["Mark McGwire", "Sammy Sosa", "Ken Griffey"], """
        - Mark McGwire
        - Sammy Sosa
        - Ken Griffey""");
    });

    test('[Example 2.2]', () {
      expectYamlLoads({"hr": 65, "avg": 0.278, "rbi": 147}, """
        hr:  65    # Home runs
        avg: 0.278 # Batting average
        rbi: 147   # Runs Batted In""");
    });

    test('[Example 2.3]', () {
      expectYamlLoads({
        "american": ["Boston Red Sox", "Detroit Tigers", "New York Yankees"],
        "national": ["New York Mets", "Chicago Cubs", "Atlanta Braves"],
      }, """
        american:
          - Boston Red Sox
          - Detroit Tigers
          - New York Yankees
        national:
          - New York Mets
          - Chicago Cubs
          - Atlanta Braves""");
    });

    test('[Example 2.4]', () {
      expectYamlLoads([
        {"name": "Mark McGwire", "hr": 65, "avg": 0.278},
        {"name": "Sammy Sosa", "hr": 63, "avg": 0.288},
      ], """
        -
          name: Mark McGwire
          hr:   65
          avg:  0.278
        -
          name: Sammy Sosa
          hr:   63
          avg:  0.288""");
    });

    test('[Example 2.5]', () {
      expectYamlLoads([
        ["name", "hr", "avg"],
        ["Mark McGwire", 65, 0.278],
        ["Sammy Sosa", 63, 0.288]
      ], """
        - [name        , hr, avg  ]
        - [Mark McGwire, 65, 0.278]
        - [Sammy Sosa  , 63, 0.288]""");
    });

    test('[Example 2.6]', () {
      expectYamlLoads({
        "Mark McGwire": {"hr": 65, "avg": 0.278},
        "Sammy Sosa": {"hr": 63, "avg": 0.288}
      }, """
        Mark McGwire: {hr: 65, avg: 0.278}
        Sammy Sosa: {
            hr: 63,
            avg: 0.288
          }""");
    });
  });

  group('2.2: Structures', () {
    test('[Example 2.7]', () {
      expectYamlStreamLoads([
        ["Mark McGwire", "Sammy Sosa", "Ken Griffey"],
        ["Chicago Cubs", "St Louis Cardinals"]
      ], """
        # Ranking of 1998 home runs
        ---
        - Mark McGwire
        - Sammy Sosa
        - Ken Griffey

        # Team ranking
        ---
        - Chicago Cubs
        - St Louis Cardinals""");
    });

    test('[Example 2.8]', () {
      expectYamlStreamLoads([
        {"time": "20:03:20", "player": "Sammy Sosa", "action": "strike (miss)"},
        {"time": "20:03:47", "player": "Sammy Sosa", "action": "grand slam"},
      ], """
        ---
        time: 20:03:20
        player: Sammy Sosa
        action: strike (miss)
        ...
        ---
        time: 20:03:47
        player: Sammy Sosa
        action: grand slam
        ...""");
    });

    test('[Example 2.9]', () {
      expectYamlLoads({
        "hr": ["Mark McGwire", "Sammy Sosa"],
        "rbi": ["Sammy Sosa", "Ken Griffey"]
      }, """
        ---
        hr: # 1998 hr ranking
          - Mark McGwire
          - Sammy Sosa
        rbi:
          # 1998 rbi ranking
          - Sammy Sosa
          - Ken Griffey""");
    });

    test('[Example 2.10]', () {
      expectYamlLoads({
        "hr": ["Mark McGwire", "Sammy Sosa"],
        "rbi": ["Sammy Sosa", "Ken Griffey"]
      }, """
        ---
        hr:
          - Mark McGwire
          # Following node labeled SS
          - &SS Sammy Sosa
        rbi:
          - *SS # Subsequent occurrence
          - Ken Griffey""");
    });

    test('[Example 2.11]', () {
      var doc = deepEqualsMap();
      doc[["Detroit Tigers", "Chicago cubs"]] = ["2001-07-23"];
      doc[["New York Yankees", "Atlanta Braves"]] = [
        "2001-07-02",
        "2001-08-12",
        "2001-08-14"
      ];
      expectYamlLoads(doc, """
        ? - Detroit Tigers
          - Chicago cubs
        :
          - 2001-07-23

        ? [ New York Yankees,
            Atlanta Braves ]
        : [ 2001-07-02, 2001-08-12,
            2001-08-14 ]""");
    });

    test('[Example 2.12]', () {
      expectYamlLoads([
        {"item": "Super Hoop", "quantity": 1},
        {"item": "Basketball", "quantity": 4},
        {"item": "Big Shoes", "quantity": 1},
      ], """
        ---
        # Products purchased
        - item    : Super Hoop
          quantity: 1
        - item    : Basketball
          quantity: 4
        - item    : Big Shoes
          quantity: 1""");
    });
  });

  group('2.3: Scalars', () {
    test('[Example 2.13]', () {
      expectYamlLoads(cleanUpLiteral("""
        \\//||\\/||
        // ||  ||__"""), """
        # ASCII Art
        --- |
          \\//||\\/||
          // ||  ||__""");
    });

    test('[Example 2.14]', () {
      expectYamlLoads("Mark McGwire's year was crippled by a knee injury.", """
        --- >
          Mark McGwire's
          year was crippled
          by a knee injury.""");
    });

    test('[Example 2.15]', () {
      expectYamlLoads(cleanUpLiteral("""
        Sammy Sosa completed another fine season with great stats.

          63 Home Runs
          0.288 Batting Average

        What a year!"""), """
        >
         Sammy Sosa completed another
         fine season with great stats.

           63 Home Runs
           0.288 Batting Average

         What a year!""");
    });

    test('[Example 2.16]', () {
      expectYamlLoads({
        "name": "Mark McGwire",
        "accomplishment": "Mark set a major league home run record in 1998.\n",
        "stats": "65 Home Runs\n0.278 Batting Average"
      }, """
        name: Mark McGwire
        accomplishment: >
          Mark set a major league
          home run record in 1998.
        stats: |
          65 Home Runs
          0.278 Batting Average""");
    });

    test('[Example 2.17]', () {
      expectYamlLoads({
        "unicode": "Sosa did fine.\u263A",
        "control": "\b1998\t1999\t2000\n",
        "hex esc": "\r\n is \r\n",
        "single": '"Howdy!" he cried.',
        "quoted": " # Not a 'comment'.",
        "tie-fighter": "|\\-*-/|"
      }, """
        unicode: "Sosa did fine.\\u263A"
        control: "\\b1998\\t1999\\t2000\\n"
        hex esc: "\\x0d\\x0a is \\r\\n"

        single: '"Howdy!" he cried.'
        quoted: ' # Not a ''comment''.'
        tie-fighter: '|\\-*-/|'""");
    });

    test('[Example 2.18]', () {
      expectYamlLoads({
        "plain": "This unquoted scalar spans many lines.",
        "quoted": "So does this quoted scalar.\n"
      }, '''
        plain:
          This unquoted scalar
          spans many lines.

        quoted: "So does this
          quoted scalar.\\n"''');
    });
  });

  group('2.4: Tags', () {
    test('[Example 2.19]', () {
      expectYamlLoads({
        "canonical": 12345,
        "decimal": 12345,
        "octal": 12,
        "hexadecimal": 12
      }, """
        canonical: 12345
        decimal: +12345
        octal: 0o14
        hexadecimal: 0xC""");
    });

    test('[Example 2.20]', () {
      expectYamlLoads({
        "canonical": 1230.15,
        "exponential": 1230.15,
        "fixed": 1230.15,
        "negative infinity": -infinity,
        "not a number": nan
      }, """
        canonical: 1.23015e+3
        exponential: 12.3015e+02
        fixed: 1230.15
        negative infinity: -.inf
        not a number: .NaN""");
    });

    test('[Example 2.21]', () {
      var doc = deepEqualsMap({
        "booleans": [true, false],
        "string": "012345"
      });
      doc[null] = null;
      expectYamlLoads(doc, """
        null:
        booleans: [ true, false ]
        string: '012345'""");
    });

    // Examples 2.22 through 2.26 test custom tag URIs, which this
    // implementation currently doesn't plan to support.
  });

  group('2.5 Full Length Example', () {
    // Example 2.27 tests custom tag URIs, which this implementation currently
    // doesn't plan to support.

    test('[Example 2.28]', () {
      expectYamlStreamLoads([
        {
          "Time": "2001-11-23 15:01:42 -5",
          "User": "ed",
          "Warning": "This is an error message for the log file"
        },
        {
          "Time": "2001-11-23 15:02:31 -5",
          "User": "ed",
          "Warning": "A slightly different error message."
        },
        {
          "DateTime": "2001-11-23 15:03:17 -5",
          "User": "ed",
          "Fatal": 'Unknown variable "bar"',
          "Stack": [
            {
              "file": "TopClass.py",
              "line": 23,
              "code": 'x = MoreObject("345\\n")\n'
            },
            {"file": "MoreClass.py", "line": 58, "code": "foo = bar"}
          ]
        }
      ], """
        ---
        Time: 2001-11-23 15:01:42 -5
        User: ed
        Warning:
          This is an error message
          for the log file
        ---
        Time: 2001-11-23 15:02:31 -5
        User: ed
        Warning:
          A slightly different error
          message.
        ---
        DateTime: 2001-11-23 15:03:17 -5
        User: ed
        Fatal:
          Unknown variable "bar"
        Stack:
          - file: TopClass.py
            line: 23
            code: |
              x = MoreObject("345\\n")
          - file: MoreClass.py
            line: 58
            code: |-
              foo = bar""");
    });
  });

  // Chapter 3 just talks about the structure of loading and dumping Yaml.
  // Chapter 4 explains conventions used in the spec.

  // Chapter 5: Characters
  group('5.1: Character Set', () {
    expectAllowsCharacter(int charCode) {
      var char = new String.fromCharCodes([charCode]);
      expectYamlLoads('The character "$char" is allowed',
          'The character "$char" is allowed');
    }

    expectAllowsQuotedCharacter(int charCode) {
      var char = new String.fromCharCodes([charCode]);
      expectYamlLoads("The character '$char' is allowed",
          '"The character \'$char\' is allowed"');
    }

    expectDisallowsCharacter(int charCode) {
      var char = new String.fromCharCodes([charCode]);
      expectYamlFails('The character "$char" is disallowed');
    }

    test("doesn't include C0 control characters", () {
      expectDisallowsCharacter(0x0);
      expectDisallowsCharacter(0x8);
      expectDisallowsCharacter(0x1F);
    });

    test("includes TAB", () => expectAllowsCharacter(0x9));
    test("doesn't include DEL", () => expectDisallowsCharacter(0x7F));

    test("doesn't include C1 control characters", () {
      expectDisallowsCharacter(0x80);
      expectDisallowsCharacter(0x8A);
      expectDisallowsCharacter(0x9F);
    });

    test("includes NEL", () => expectAllowsCharacter(0x85));

    group("within quoted strings", () {
      test("includes DEL", () => expectAllowsQuotedCharacter(0x7F));
      test("includes C1 control characters", () {
        expectAllowsQuotedCharacter(0x80);
        expectAllowsQuotedCharacter(0x8A);
        expectAllowsQuotedCharacter(0x9F);
      });
    });
  });

  // Skipping section 5.2 (Character Encodings), since at the moment the module
  // assumes that the client code is providing it with a string of the proper
  // encoding.

  group('5.3: Indicator Characters', () {
    test('[Example 5.3]', () {
      expectYamlLoads({
        'sequence': ['one', 'two'],
        'mapping': {'sky': 'blue', 'sea': 'green'}
      }, """
        sequence:
        - one
        - two
        mapping:
          ? sky
          : blue
          sea : green""");
    });

    test('[Example 5.4]', () {
      expectYamlLoads({
        'sequence': ['one', 'two'],
        'mapping': {'sky': 'blue', 'sea': 'green'}
      }, """
        sequence: [ one, two, ]
        mapping: { sky: blue, sea: green }""");
    });

    test('[Example 5.5]', () => expectYamlLoads(null, "# Comment only."));

    // Skipping 5.6 because it uses an undefined tag.

    test('[Example 5.7]', () {
      expectYamlLoads({'literal': "some\ntext\n", 'folded': "some text\n"}, """
        literal: |
          some
          text
        folded: >
          some
          text
        """);
    });

    test('[Example 5.8]', () {
      expectYamlLoads({'single': "text", 'double': "text"}, """
        single: 'text'
        double: "text"
        """);
    });

    test('[Example 5.9]', () {
      expectYamlLoads("text", """
        %YAML 1.2
        --- text""");
    });

    test('[Example 5.10]', () {
      expectYamlFails("commercial-at: @text");
      expectYamlFails("commercial-at: `text");
    });
  });

  group('5.4: Line Break Characters', () {
    group('include', () {
      test('\\n', () => expectYamlLoads([1, 2], indentLiteral("- 1\n- 2")));
      test('\\r', () => expectYamlLoads([1, 2], "- 1\r- 2"));
    });

    group('do not include', () {
      test('form feed', () => expectYamlFails("- 1\x0C- 2"));
      test('NEL', () => expectYamlLoads(["1\x85- 2"], "- 1\x85- 2"));
      test('0x2028', () => expectYamlLoads(["1\u2028- 2"], "- 1\u2028- 2"));
      test('0x2029', () => expectYamlLoads(["1\u2029- 2"], "- 1\u2029- 2"));
    });

    group('in a scalar context must be normalized', () {
      test(
          "from \\r to \\n",
          () => expectYamlLoads(
              ["foo\nbar"], indentLiteral('- |\n  foo\r  bar')));
      test(
          "from \\r\\n to \\n",
          () => expectYamlLoads(
              ["foo\nbar"], indentLiteral('- |\n  foo\r\n  bar')));
    });

    test('[Example 5.11]', () {
      expectYamlLoads(cleanUpLiteral("""
        Line break (no glyph)
        Line break (glyphed)"""), """
        |
          Line break (no glyph)
          Line break (glyphed)""");
    });
  });

  group('5.5: White Space Characters', () {
    test('[Example 5.12]', () {
      expectYamlLoads({
        "quoted": "Quoted \t",
        "block": 'void main() {\n\tprintf("Hello, world!\\n");\n}\n'
      }, """
        # Tabs and spaces
        quoted: "Quoted \t"
        block:\t|
          void main() {
          \tprintf("Hello, world!\\n");
          }
        """);
    });
  });

  group('5.7: Escaped Characters', () {
    test('[Example 5.13]', () {
      expectYamlLoads(
          "Fun with \x5C "
          "\x22 \x07 \x08 \x1B \x0C "
          "\x0A \x0D \x09 \x0B \x00 "
          "\x20 \xA0 \x85 \u2028 \u2029 "
          "A A A",
          '''
        "Fun with \\\\
        \\" \\a \\b \\e \\f \\
        \\n \\r \\t \\v \\0 \\
        \\  \\_ \\N \\L \\P \\
        \\x41 \\u0041 \\U00000041"''');
    });

    test('[Example 5.14]', () {
      expectYamlFails('Bad escape: "\\c"');
      expectYamlFails('Bad escape: "\\xq-"');
    });
  });

  // Chapter 6: Basic Structures
  group('6.1: Indentation Spaces', () {
    test('may not include TAB characters', () {
      expectYamlFails("""
        -
        \t- foo
        \t- bar""");
    });

    test('must be the same for all sibling nodes', () {
      expectYamlFails("""
        -
          - foo
         - bar""");
    });

    test('may be different for the children of sibling nodes', () {
      expectYamlLoads([
        ["foo"],
        ["bar"]
      ], """
        -
          - foo
        -
         - bar""");
    });

    test('[Example 6.1]', () {
      expectYamlLoads({
        "Not indented": {
          "By one space": "By four\n  spaces\n",
          "Flow style": ["By two", "Also by two", "Still by two"]
        }
      }, """
          # Leading comment line spaces are
           # neither content nor indentation.
            
        Not indented:
         By one space: |
            By four
              spaces
         Flow style: [    # Leading spaces
           By two,        # in flow style
          Also by two,    # are neither
          \tStill by two   # content nor
            ]             # indentation.""");
    });

    test('[Example 6.2]', () {
      expectYamlLoads({
        'a': [
          'b',
          ['c', 'd']
        ]
      }, """
        ? a
        : -\tb
          -  -\tc
             - d""");
    });
  });

  group('6.2: Separation Spaces', () {
    test('[Example 6.3]', () {
      expectYamlLoads([
        {'foo': 'bar'},
        ['baz', 'baz']
      ], """
        - foo:\t bar
        - - baz
          -\tbaz""");
    });
  });

  group('6.3: Line Prefixes', () {
    test('[Example 6.4]', () {
      expectYamlLoads({
        "plain": "text lines",
        "quoted": "text lines",
        "block": "text\n \tlines\n"
      }, """
        plain: text
          lines
        quoted: "text
          \tlines"
        block: |
          text
           \tlines
        """);
    });
  });

  group('6.4: Empty Lines', () {
    test('[Example 6.5]', () {
      expectYamlLoads({
        "Folding": "Empty line\nas a line feed",
        "Chomping": "Clipped empty lines\n",
      }, """
        Folding:
          "Empty line
           \t
          as a line feed"
        Chomping: |
          Clipped empty lines
         """);
    });
  });

  group('6.5: Line Folding', () {
    test('[Example 6.6]', () {
      expectYamlLoads("trimmed\n\n\nas space", """
        >-
          trimmed
          
         

          as
          space
        """);
    });

    test('[Example 6.7]', () {
      expectYamlLoads("foo \n\n\t bar\n\nbaz\n", """
        >
          foo 
         
          \t bar

          baz
        """);
    });

    test('[Example 6.8]', () {
      expectYamlLoads(" foo\nbar\nbaz ", '''
        "
          foo 
         
          \t bar

          baz
        "''');
    });
  });

  group('6.6: Comments', () {
    test('must be separated from other tokens by white space characters', () {
      expectYamlLoads("foo#bar", "foo#bar");
      expectYamlLoads("foo:#bar", "foo:#bar");
      expectYamlLoads("-#bar", "-#bar");
    });

    test('[Example 6.9]', () {
      expectYamlLoads({'key': 'value'}, """
        key:    # Comment
          value""");
    });

    group('outside of scalar content', () {
      test('may appear on a line of their own', () {
        expectYamlLoads([1, 2], """
        - 1
        # Comment
        - 2""");
      });

      test('are independent of indentation level', () {
        expectYamlLoads([
          [1, 2]
        ], """
        -
          - 1
         # Comment
          - 2""");
      });

      test('include lines containing only white space characters', () {
        expectYamlLoads([1, 2], """
        - 1
          \t  
        - 2""");
      });
    });

    group('within scalar content', () {
      test('may not appear on a line of their own', () {
        expectYamlLoads(["foo\n# not comment\nbar\n"], """
        - |
          foo
          # not comment
          bar
        """);
      });

      test("don't include lines containing only white space characters", () {
        expectYamlLoads(["foo\n  \t   \nbar\n"], """
        - |
          foo
            \t   
          bar
        """);
      });
    });

    test('[Example 6.10]', () {
      expectYamlLoads(null, """
          # Comment
           
        """);
    });

    test('[Example 6.11]', () {
      expectYamlLoads({'key': 'value'}, """
        key:    # Comment
                # lines
          value
        """);
    });

    group('ending a block scalar header', () {
      test('may not be followed by additional comment lines', () {
        expectYamlLoads(["# not comment\nfoo\n"], """
        - | # comment
            # not comment
            foo
        """);
      });
    });
  });

  group('6.7: Separation Lines', () {
    test('may not be used within implicit keys', () {
      expectYamlFails("""
        [1,
         2]: 3""");
    });

    test('[Example 6.12]', () {
      var doc = deepEqualsMap();
      doc[{'first': 'Sammy', 'last': 'Sosa'}] = {'hr': 65, 'avg': 0.278};
      expectYamlLoads(doc, """
        { first: Sammy, last: Sosa }:
        # Statistics:
          hr:  # Home runs
             65
          avg: # Average
           0.278""");
    });
  });

  group('6.8: Directives', () {
    // TODO(nweiz): assert that this produces a warning
    test('[Example 6.13]', () {
      expectYamlLoads("foo", '''
        %FOO  bar baz # Should be ignored
                      # with a warning.
        --- "foo"''');
    });

    // TODO(nweiz): assert that this produces a warning.
    test('[Example 6.14]', () {
      expectYamlLoads("foo", '''
        %YAML 1.3 # Attempt parsing
                   # with a warning
        ---
        "foo"''');
    });

    test('[Example 6.15]', () {
      expectYamlFails("""
        %YAML 1.2
        %YAML 1.1
        foo""");
    });

    test('[Example 6.16]', () {
      expectYamlLoads("foo", '''
        %TAG !yaml! tag:yaml.org,2002:
        ---
        !yaml!str "foo"''');
    });

    test('[Example 6.17]', () {
      expectYamlFails("""
        %TAG ! !foo
        %TAG ! !foo
        bar""");
    });

    // Examples 6.18 through 6.22 test custom tag URIs, which this
    // implementation currently doesn't plan to support.
  });

  group('6.9: Node Properties', () {
    test('may be specified in any order', () {
      expectYamlLoads(["foo", "bar"], """
        - !!str &a1 foo
        - &a2 !!str bar""");
    });

    test('[Example 6.23]', () {
      expectYamlLoads({"foo": "bar", "baz": "foo"}, '''
        !!str &a1 "foo":
          !!str bar
        &a2 baz : *a1''');
    });

    // Example 6.24 tests custom tag URIs, which this implementation currently
    // doesn't plan to support.

    test('[Example 6.25]', () {
      expectYamlFails("- !<!> foo");
      expectYamlFails("- !<\$:?> foo");
    });

    // Examples 6.26 and 6.27 test custom tag URIs, which this implementation
    // currently doesn't plan to support.

    test('[Example 6.28]', () {
      expectYamlLoads(["12", 12, "12"], '''
        # Assuming conventional resolution:
        - "12"
        - 12
        - ! 12''');
    });

    test('[Example 6.29]', () {
      expectYamlLoads(
          {"First occurrence": "Value", "Second occurrence": "Value"}, """
        First occurrence: &anchor Value
        Second occurrence: *anchor""");
    });
  });

  // Chapter 7: Flow Styles
  group('7.1: Alias Nodes', () {
    test("must not use an anchor that doesn't previously occur", () {
      expectYamlFails("""
        - *anchor
        - &anchor foo""");
    });

    test("don't have to exist for a given anchor node", () {
      expectYamlLoads(["foo"], "- &anchor foo");
    });

    group('must not specify', () {
      test('tag properties', () => expectYamlFails("""
        - &anchor foo
        - !str *anchor"""));

      test('anchor properties', () => expectYamlFails("""
        - &anchor foo
        - &anchor2 *anchor"""));

      test('content', () => expectYamlFails("""
        - &anchor foo
        - *anchor bar"""));
    });

    test('must preserve structural equality', () {
      var doc = loadYaml(cleanUpLiteral("""
        anchor: &anchor [a, b, c]
        alias: *anchor"""));
      var anchorList = doc['anchor'];
      var aliasList = doc['alias'];
      expect(anchorList, same(aliasList));

      doc = loadYaml(cleanUpLiteral("""
        ? &anchor [a, b, c]
        : ? *anchor
          : bar"""));
      anchorList = doc.keys.first;
      aliasList = doc[['a', 'b', 'c']].keys.first;
      expect(anchorList, same(aliasList));
    });

    test('[Example 7.1]', () {
      expectYamlLoads({
        "First occurrence": "Foo",
        "Second occurrence": "Foo",
        "Override anchor": "Bar",
        "Reuse anchor": "Bar",
      }, """
        First occurrence: &anchor Foo
        Second occurrence: *anchor
        Override anchor: &anchor Bar
        Reuse anchor: *anchor""");
    });
  });

  group('7.2: Empty Nodes', () {
    test('[Example 7.2]', () {
      expectYamlLoads({"foo": "", "": "bar"}, """
        {
          foo : !!str,
          !!str : bar,
        }""");
    });

    test('[Example 7.3]', () {
      var doc = deepEqualsMap({"foo": null});
      doc[null] = "bar";
      expectYamlLoads(doc, """
        {
          ? foo :,
          : bar,
        }""");
    });
  });

  group('7.3: Flow Scalar Styles', () {
    test('[Example 7.4]', () {
      expectYamlLoads({
        "implicit block key": [
          {"implicit flow key": "value"}
        ]
      }, '''
        "implicit block key" : [
          "implicit flow key" : value,
         ]''');
    });

    test('[Example 7.5]', () {
      expectYamlLoads(
          "folded to a space,\nto a line feed, or \t \tnon-content", '''
        "folded 
        to a space,\t
         
        to a line feed, or \t\\
         \\ \tnon-content"''');
    });

    test('[Example 7.6]', () {
      expectYamlLoads(" 1st non-empty\n2nd non-empty 3rd non-empty ", '''
        " 1st non-empty

         2nd non-empty 
        \t3rd non-empty "''');
    });

    test('[Example 7.7]', () {
      expectYamlLoads("here's to \"quotes\"", "'here''s to \"quotes\"'");
    });

    test('[Example 7.8]', () {
      expectYamlLoads({
        "implicit block key": [
          {"implicit flow key": "value"}
        ]
      }, """
        'implicit block key' : [
          'implicit flow key' : value,
         ]""");
    });

    test('[Example 7.9]', () {
      expectYamlLoads(" 1st non-empty\n2nd non-empty 3rd non-empty ", """
        ' 1st non-empty

         2nd non-empty 
        \t3rd non-empty '""");
    });

    test('[Example 7.10]', () {
      expectYamlLoads([
        "::vector",
        ": - ()",
        "Up, up, and away!",
        -123,
        "http://example.com/foo#bar",
        [
          "::vector",
          ": - ()",
          "Up, up, and away!",
          -123,
          "http://example.com/foo#bar"
        ]
      ], '''
        # Outside flow collection:
        - ::vector
        - ": - ()"
        - Up, up, and away!
        - -123
        - http://example.com/foo#bar
        # Inside flow collection:
        - [ ::vector,
          ": - ()",
          "Up, up, and away!",
          -123,
          http://example.com/foo#bar ]''');
    });

    test('[Example 7.11]', () {
      expectYamlLoads({
        "implicit block key": [
          {"implicit flow key": "value"}
        ]
      }, """
        implicit block key : [
          implicit flow key : value,
         ]""");
    });

    test('[Example 7.12]', () {
      expectYamlLoads("1st non-empty\n2nd non-empty 3rd non-empty", """
        1st non-empty

         2nd non-empty 
        \t3rd non-empty""");
    });
  });

  group('7.4: Flow Collection Styles', () {
    test('[Example 7.13]', () {
      expectYamlLoads([
        ['one', 'two'],
        ['three', 'four']
      ], """
        - [ one, two, ]
        - [three ,four]""");
    });

    test('[Example 7.14]', () {
      expectYamlLoads([
        "double quoted",
        "single quoted",
        "plain text",
        ["nested"],
        {"single": "pair"}
      ], """
        [
        "double
         quoted", 'single
                   quoted',
        plain
         text, [ nested ],
        single: pair,
        ]""");
    });

    test('[Example 7.15]', () {
      expectYamlLoads([
        {"one": "two", "three": "four"},
        {"five": "six", "seven": "eight"},
      ], """
        - { one : two , three: four , }
        - {five: six,seven : eight}""");
    });

    test('[Example 7.16]', () {
      var doc = deepEqualsMap({"explicit": "entry", "implicit": "entry"});
      doc[null] = null;
      expectYamlLoads(doc, """
        {
        ? explicit: entry,
        implicit: entry,
        ?
        }""");
    });

    test('[Example 7.17]', () {
      var doc = deepEqualsMap({
        "unquoted": "separate",
        "http://foo.com": null,
        "omitted value": null
      });
      doc[null] = "omitted key";
      expectYamlLoads(doc, '''
        {
        unquoted : "separate",
        http://foo.com,
        omitted value:,
        : omitted key,
        }''');
    });

    test('[Example 7.18]', () {
      expectYamlLoads(
          {"adjacent": "value", "readable": "value", "empty": null}, '''
        {
        "adjacent":value,
        "readable": value,
        "empty":
        }''');
    });

    test('[Example 7.19]', () {
      expectYamlLoads([
        {"foo": "bar"}
      ], """
        [
        foo: bar
        ]""");
    });

    test('[Example 7.20]', () {
      expectYamlLoads([
        {"foo bar": "baz"}
      ], """
        [
        ? foo
         bar : baz
        ]""");
    });

    test('[Example 7.21]', () {
      var el1 = deepEqualsMap();
      el1[null] = "empty key entry";

      var el2 = deepEqualsMap();
      el2[{"JSON": "like"}] = "adjacent";

      expectYamlLoads([
        [
          {"YAML": "separate"}
        ],
        [el1],
        [el2]
      ], """
        - [ YAML : separate ]
        - [ : empty key entry ]
        - [ {JSON: like}:adjacent ]""");
    });

    // TODO(nweiz): enable this when we throw an error for long or multiline
    // keys.
    // test('[Example 7.22]', () {
    //   expectYamlFails(
    //     """
    //     [ foo
    //      bar: invalid ]""");
    //
    //   var dotList = new List.filled(1024, ' ');
    //   var dots = dotList.join();
    //   expectYamlFails('[ "foo...$dots...bar": invalid ]');
    // });
  });

  group('7.5: Flow Nodes', () {
    test('[Example 7.23]', () {
      expectYamlLoads([
        ["a", "b"],
        {"a": "b"},
        "a",
        "b",
        "c"
      ], """
        - [ a, b ]
        - { a: b }
        - "a"
        - 'b'
        - c""");
    });

    test('[Example 7.24]', () {
      expectYamlLoads(["a", "b", "c", "c", ""], """
        - !!str "a"
        - 'b'
        - &anchor "c"
        - *anchor
        - !!str""");
    });
  });

  // Chapter 8: Block Styles
  group('8.1: Block Scalar Styles', () {
    test('[Example 8.1]', () {
      expectYamlLoads(["literal\n", " folded\n", "keep\n\n", " strip"], """
        - | # Empty header
         literal
        - >1 # Indentation indicator
          folded
        - |+ # Chomping indicator
         keep

        - >1- # Both indicators
          strip""");
    });

    test('[Example 8.2]', () {
      // Note: in the spec, the fourth element in this array is listed as
      // "\t detected\n", not "\t\ndetected\n". However, I'm reasonably
      // confident that "\t\ndetected\n" is correct when parsed according to the
      // rest of the spec.
      expectYamlLoads(
          ["detected\n", "\n\n# detected\n", " explicit\n", "\t\ndetected\n"],
          """
        - |
         detected
        - >
         
          
          # detected
        - |1
          explicit
        - >
         \t
         detected
        """);
    });

    test('[Example 8.3]', () {
      expectYamlFails("""
        - |
          
         text""");

      expectYamlFails("""
        - >
          text
         text""");

      expectYamlFails("""
        - |2
         text""");
    });

    test('[Example 8.4]', () {
      expectYamlLoads({"strip": "text", "clip": "text\n", "keep": "text\n"}, """
        strip: |-
          text
        clip: |
          text
        keep: |+
          text
        """);
    });

    test('[Example 8.5]', () {
      // This example in the spec only includes a single newline in the "keep"
      // value, but as far as I can tell that's not how it's supposed to be
      // parsed according to the rest of the spec.
      expectYamlLoads(
          {"strip": "# text", "clip": "# text\n", "keep": "# text\n\n"}, """
         # Strip
          # Comments:
        strip: |-
          # text
          
         # Clip
          # comments:

        clip: |
          # text
         
         # Keep
          # comments:

        keep: |+
          # text

         # Trail
          # comments.
        """);
    });

    test('[Example 8.6]', () {
      expectYamlLoads({"strip": "", "clip": "", "keep": "\n"}, """
        strip: >-

        clip: >

        keep: |+

        """);
    });

    test('[Example 8.7]', () {
      expectYamlLoads("literal\n\ttext\n", """
        |
         literal
         \ttext
        """);
    });

    test('[Example 8.8]', () {
      expectYamlLoads("\n\nliteral\n \n\ntext\n", """
        |
         
          
          literal
           
          
          text

         # Comment""");
    });

    test('[Example 8.9]', () {
      expectYamlLoads("folded text\n", """
        >
         folded
         text
        """);
    });

    test('[Example 8.10]', () {
      expectYamlLoads(cleanUpLiteral("""

        folded line
        next line
          * bullet

          * list
          * lines

        last line
        """), """
        >

         folded
         line

         next
         line
           * bullet

           * list
           * lines

         last
         line

        # Comment""");
    });

    // Examples 8.11 through 8.13 are duplicates of 8.10.
  });

  group('8.2: Block Collection Styles', () {
    test('[Example 8.14]', () {
      expectYamlLoads({
        "block sequence": [
          "one",
          {"two": "three"}
        ]
      }, """
        block sequence:
          - one
          - two : three""");
    });

    test('[Example 8.15]', () {
      expectYamlLoads([
        null,
        "block node\n",
        ["one", "two"],
        {"one": "two"}
      ], """
        - # Empty
        - |
         block node
        - - one # Compact
          - two # sequence
        - one: two # Compact mapping""");
    });

    test('[Example 8.16]', () {
      expectYamlLoads({
        "block mapping": {"key": "value"}
      }, """
        block mapping:
         key: value""");
    });

    test('[Example 8.17]', () {
      expectYamlLoads({
        "explicit key": null,
        "block key\n": ["one", "two"]
      }, """
        ? explicit key # Empty value
        ? |
          block key
        : - one # Explicit compact
          - two # block value""");
    });

    test('[Example 8.18]', () {
      var doc = deepEqualsMap({
        'plain key': 'in-line value',
        "quoted key": ["entry"]
      });
      doc[null] = null;
      expectYamlLoads(doc, '''
        plain key: in-line value
        : # Both empty
        "quoted key":
        - entry''');
    });

    test('[Example 8.19]', () {
      var el = deepEqualsMap();
      el[{'earth': 'blue'}] = {'moon': 'white'};
      expectYamlLoads([
        {'sun': 'yellow'},
        el
      ], """
        - sun: yellow
        - ? earth: blue
          : moon: white""");
    });

    test('[Example 8.20]', () {
      expectYamlLoads([
        "flow in block",
        "Block scalar\n",
        {"foo": "bar"}
      ], '''
        -
          "flow in block"
        - >
         Block scalar
        - !!map # Block collection
          foo : bar''');
    });

    test('[Example 8.21]', () {
      // The spec doesn't include a newline after "value" in the parsed map, but
      // the block scalar is clipped so it should be retained.
      expectYamlLoads({"literal": "value\n", "folded": "value"}, """
        literal: |2
          value
        folded:
           !!str
          >1
         value""");
    });

    test('[Example 8.22]', () {
      expectYamlLoads({
        "sequence": [
          "entry",
          ["nested"]
        ],
        "mapping": {"foo": "bar"}
      }, """
        sequence: !!seq
        - entry
        - !!seq
         - nested
        mapping: !!map
         foo: bar""");
    });
  });

  // Chapter 9: YAML Character Stream
  group('9.1: Documents', () {
    // Example 9.1 tests the use of a BOM, which this implementation currently
    // doesn't plan to support.

    test('[Example 9.2]', () {
      expectYamlLoads("Document", """
        %YAML 1.2
        ---
        Document
        ... # Suffix""");
    });

    test('[Example 9.3]', () {
      // The spec example indicates that the comment after "%!PS-Adobe-2.0"
      // should be stripped, which would imply that that line is not part of the
      // literal defined by the "|". The rest of the spec is ambiguous on this
      // point; the allowable indentation for non-indented literal content is
      // not clearly explained. However, if both the "|" and the text were
      // indented the same amount, the text would be part of the literal, which
      // implies that the spec's parse of this document is incorrect.
      expectYamlStreamLoads(
          ["Bare document", "%!PS-Adobe-2.0 # Not the first line\n"], """
        Bare
        document
        ...
        # No document
        ...
        |
        %!PS-Adobe-2.0 # Not the first line
        """);
    });

    test('[Example 9.4]', () {
      expectYamlStreamLoads([
        {"matches %": 20},
        null
      ], """
        ---
        { matches
        % : 20 }
        ...
        ---
        # Empty
        ...""");
    });

    test('[Example 9.5]', () {
      // The spec doesn't have a space between the second
      // "YAML" and "1.2", but this seems to be a typo.
      expectYamlStreamLoads(["%!PS-Adobe-2.0\n", null], """
        %YAML 1.2
        --- |
        %!PS-Adobe-2.0
        ...
        %YAML 1.2
        ---
        # Empty
        ...""");
    });

    test('[Example 9.6]', () {
      expectYamlStreamLoads([
        "Document",
        null,
        {"matches %": 20}
      ], """
        Document
        ---
        # Empty
        ...
        %YAML 1.2
        ---
        matches %: 20""");
    });
  });

  // Chapter 10: Recommended Schemas
  group('10.1: Failsafe Schema', () {
    test('[Example 10.1]', () {
      expectYamlLoads({
        "Block style": {
          "Clark": "Evans",
          "Ingy": "döt Net",
          "Oren": "Ben-Kiki"
        },
        "Flow style": {"Clark": "Evans", "Ingy": "döt Net", "Oren": "Ben-Kiki"}
      }, """
        Block style: !!map
          Clark : Evans
          Ingy  : döt Net
          Oren  : Ben-Kiki

        Flow style: !!map { Clark: Evans, Ingy: döt Net, Oren: Ben-Kiki }""");
    });

    test('[Example 10.2]', () {
      expectYamlLoads({
        "Block style": ["Clark Evans", "Ingy döt Net", "Oren Ben-Kiki"],
        "Flow style": ["Clark Evans", "Ingy döt Net", "Oren Ben-Kiki"]
      }, """
        Block style: !!seq
        - Clark Evans
        - Ingy döt Net
        - Oren Ben-Kiki

        Flow style: !!seq [ Clark Evans, Ingy döt Net, Oren Ben-Kiki ]""");
    });

    test('[Example 10.3]', () {
      expectYamlLoads({
        "Block style": "String: just a theory.",
        "Flow style": "String: just a theory."
      }, '''
        Block style: !!str |-
          String: just a theory.

        Flow style: !!str "String: just a theory."''');
    });
  });

  group('10.2: JSON Schema', () {
    // test('[Example 10.4]', () {
    //   var doc = deepEqualsMap({"key with null value": null});
    //   doc[null] = "value for null key";
    //   expectYamlStreamLoads(doc,
    //     """
    //     !!null null: value for null key
    //     key with null value: !!null null""");
    // });

    // test('[Example 10.5]', () {
    //   expectYamlStreamLoads({
    //     "YAML is a superset of JSON": true,
    //     "Pluto is a planet": false
    //   },
    //     """
    //     YAML is a superset of JSON: !!bool true
    //     Pluto is a planet: !!bool false""");
    // });

    // test('[Example 10.6]', () {
    //   expectYamlStreamLoads({
    //     "negative": -12,
    //     "zero": 0,
    //     "positive": 34
    //   },
    //     """
    //     negative: !!int -12
    //     zero: !!int 0
    //     positive: !!int 34""");
    // });

    // test('[Example 10.7]', () {
    //   expectYamlStreamLoads({
    //     "negative": -1,
    //     "zero": 0,
    //     "positive": 23000,
    //     "infinity": infinity,
    //     "not a number": nan
    //   },
    //     """
    //     negative: !!float -1
    //     zero: !!float 0
    //     positive: !!float 2.3e4
    //     infinity: !!float .inf
    //     not a number: !!float .nan""");
    // });

    // test('[Example 10.8]', () {
    //   expectYamlStreamLoads({
    //     "A null": null,
    //     "Booleans": [true, false],
    //     "Integers": [0, -0, 3, -19],
    //     "Floats": [0, 0, 12000, -200000],
    //     // Despite being invalid in the JSON schema, these values are valid in
    //     // the core schema which this implementation supports.
    //     "Invalid": [ true, null, 7, 0x3A, 12.3]
    //   },
    //     """
    //     A null: null
    //     Booleans: [ true, false ]
    //     Integers: [ 0, -0, 3, -19 ]
    //     Floats: [ 0., -0.0, 12e03, -2E+05 ]
    //     Invalid: [ True, Null, 0o7, 0x3A, +12.3 ]""");
    // });
  });

  group('10.3: Core Schema', () {
    test('[Example 10.9]', () {
      expectYamlLoads({
        "A null": null,
        "Also a null": null,
        "Not a null": "",
        "Booleans": [true, true, false, false],
        "Integers": [0, 7, 0x3A, -19],
        "Floats": [0, 0, 0.5, 12000, -200000],
        "Also floats": [infinity, -infinity, infinity, nan]
      }, '''
        A null: null
        Also a null: # Empty
        Not a null: ""
        Booleans: [ true, True, false, FALSE ]
        Integers: [ 0, 0o7, 0x3A, -19 ]
        Floats: [ 0., -0.0, .5, +12e03, -2E+05 ]
        Also floats: [ .inf, -.Inf, +.INF, .NAN ]''');
    });
  });
}
