var parser = require('../');
var test = require('tape');

test('output event', function (t) {

  t.plan(1);

  var mockTap = [
    '# is true',
    'ok 1 true value',
    'ok 2 true value',
    '1..2'
  ];

  var p = parser();

  p.on('output', function (output) {

    t.deepEqual(output, {
      asserts: [
        { name: 'true value', number: 1, ok: true, raw: 'ok 1 true value', test: 1, type: 'assert' },
        { name: 'true value', number: 2, ok: true, raw: 'ok 2 true value', test: 1, type: 'assert' }
      ],
      fail: [],
      pass: [
        { name: 'true value', number: 1, ok: true, raw: 'ok 1 true value', test: 1, type: 'assert' },
        { name: 'true value', number: 2, ok: true, raw: 'ok 2 true value', test: 1, type: 'assert' }
      ],
      results: [],
      tests: [
        { name: 'is true', number: 1, raw: '# is true', type: 'test' }
      ],
      versions: [],
      comments: [],
      plans: [{ from: 1, to: 2, raw: '1..2', skip: undefined, type: 'plan' }],
      errors: []
    }, 'output data');
  });

  mockTap.forEach(function (line) {

    p.write(line + '\n');
  });
  p.end();
});

test('output callback', function (t) {

  t.plan(1);

  var mockTap = [
    '# is true',
    'ok 1 true value',
    'ok 2 true value',
    '1..2'
  ];

  var p = parser(function (err, output) {

    t.deepEqual(output, {
      asserts: [
        { name: 'true value', number: 1, ok: true, raw: 'ok 1 true value', test: 1, type: 'assert' },
        { name: 'true value', number: 2, ok: true, raw: 'ok 2 true value', test: 1, type: 'assert' }
      ],
      fail: [],
      pass: [
        { name: 'true value', number: 1, ok: true, raw: 'ok 1 true value', test: 1, type: 'assert' },
        { name: 'true value', number: 2, ok: true, raw: 'ok 2 true value', test: 1, type: 'assert' }
      ],
      results: [],
      tests: [
        { name: 'is true', number: 1, raw: '# is true', type: 'test' }
      ],
      versions: [],
      comments: [],
      plans: [{ from: 1, to: 2, raw: '1..2', skip: undefined, type: 'plan' }],
      errors: []
    }, 'output data');
  });

  mockTap.forEach(function (line) {

    p.write(line + '\n');
  });
  p.end();
});

test('tests', function (t) {

  t.plan(1);

  var mockTap = [
    '# is true',
    'ok 1 true value',
    'ok 2 true value'
  ];

  var p = parser();

  p.on('test', function (test) {

    t.deepEqual(test, {
      type: 'test',
      name: 'is true',
      raw: '# is true',
      number: 1
    }, 'test is parsed');
  });

  mockTap.forEach(function (line) {

    p.write(line + '\n');
  });
  p.end();
});

test('asserts', function (t) {

  t.plan(2);

  var mockTap = [
    '# is true',
    'ok 1 true value',
    'ok 2 true value'
  ];

  var p = parser();

  var asserts = [];
  p.on('assert', function (assert) {
    asserts.push(assert);
  });

  p.on('output', function () {

    t.deepEqual(asserts[0], {
      name: 'true value',
      number: 1,
      ok: true,
      raw: 'ok 1 true value',
      test: 1,
      type: 'assert'
    }, 'assert 1')
    t.deepEqual(asserts[1], {
      name: 'true value',
      number: 2,
      ok: true,
      raw: 'ok 2 true value',
      test: 1,
      type: 'assert'
    }, 'assert 2')
  });

  mockTap.forEach(function (line) {

    p.write(line + '\n');
  });
  p.end();
});

test('results', function (t) {

  t.plan(3);

  var mockTap = [
    '# tests 15',
    '# pass  13',
    '# fail  2'
  ];

  var p = parser();

  var results = [];
  p.on('result', function (result) {
    results.push(result);
  });

  p.on('output', function () {

    t.deepEqual(results[0], {
      count: '15',
      name: 'tests',
      raw: '# tests 15',
      type: 'result'
    }, 'tests');

    t.deepEqual(results[1], {
      count: '13',
      name: 'pass',
      raw: '# pass  13',
      type: 'result'
    }, 'pass');

    t.deepEqual(results[2], {
      count: '2',
      name: 'fail',
      raw: '# fail  2',
      type: 'result'
    }, 'fail');
  });

  mockTap.forEach(function (line) {

    p.write(line + '\n');
  });
  p.end();
});

test('version', function (t) {

  t.plan(1);

  var mockTap = [
    'TAP version 13'
  ];

  var p = parser();

  var results = [];
  p.on('version', function (version) {

    t.deepEqual(version, {
      raw: 'TAP version 13',
      type: 'version'
    }, 'version data');
  });

  mockTap.forEach(function (line) {

    p.write(line + '\n');
  });
  p.end();
});

test('pass', function (t) {

  t.plan(2);

  var mockTap = [
    '# is true',
    'ok 1 true value',
    'ok 2 true value'
  ];

  var p = parser();

  var passes = [];
  p.on('pass', function (pass) {

    passes.push(pass);
  });

  p.on('output', function () {

    t.deepEqual(passes[0], {
      name: 'true value',
      number: 1,
      ok: true,
      raw: 'ok 1 true value',
      test: 1,
      type: 'assert'
    }, 'pass 1');

    t.deepEqual(passes[1], {
      name: 'true value',
      number: 2,
      ok: true,
      raw: 'ok 2 true value',
      test: 1,
      type: 'assert'
    }, 'pass 2');
  });

  mockTap.forEach(function (line) {

    p.write(line + '\n');
  });
  p.end();
});

test('failed assertion', function (t) {

  t.plan(1);

  var mockTap = [
    "TAP version 13",
    "# is true",
    "ok 1 true value",
    "ok 2 true value",
    "not ok 3 strings match",
    "  ---",
    "    operator: equal",
    "    expected: 'you'",
    "    actual:   'me'",
    "    at: Test.<anonymous> (/Users/scott/www/divshot/divshot-cli/test/index.js:8:5)",
    "  ...",
    "not ok 15 plan != count",
    "  ---",
    "    operator: fail",
    "    expected: 4",
    "    actual:   3",
    "  ...",
    "",
    "1..15",
  ];

  var fails = [];
  var p = parser(function () {

    t.deepEqual(
      fails,
      [
        {
          error: {
            actual: 'me',
            at: {
              character: '5',
              file: '/Users/scott/www/divshot/divshot-cli/test/index.js',
              line: '8'
            },
            expected: 'you',
            operator: 'equal',
            raw: [
              "    operator: equal",
              "    expected: 'you'",
              "    actual:   'me'",
              "    at: Test.<anonymous> (/Users/scott/www/divshot/divshot-cli/test/index.js:8:5)"
            ].join('\n')
          },
          name: 'strings match',
          number: 3,
          ok: false,
          raw: 'not ok 3 strings match',
          test: 1,
          type: 'assert'
        },
        {
          error: {
            actual: '3',
            expected: '4',
            operator: 'count',
            raw: [
              "    operator: fail",
              "    expected: 4",
              "    actual:   3"
            ].join('\n')
          },
          name: 'plan != count',
          number: 15,
          ok: false,
          raw: 'not ok 15 plan != count',
          test: 1,
          type: 'plan'
        }
      ],
      'fails'
    );
  });

  p.on('fail', function (fail) {

    fails.push(fail);
  });

  mockTap.forEach(function (line) {

    p.write(line + '\n');
  });
  p.end();
});

test('plan', function (t) {

  var mockTap = [
    '1..2',
  ];

  var plans = [];
  var p = parser(function () {
    t.deepEqual(plans, [
      { from: 1, to: 2, raw: '1..2', skip: undefined, type: 'plan' }
    ]);
    t.end();
  });

  p.on('plan', function (plan) {
    plans.push(plan);
  });

  mockTap.forEach(function (line) {

    p.write(line + '\n');
  });
  p.end();
});


// NOTE: comments output the same as test names.
// This makes it very difficult to parse them.
// Ignoring them for now. Just don't use comments.
test('comments');

test('generic output', function (t) {

  var mockTap = [
    "TAP version 13",
    "# is true",
    "ok 1 true value",
    "not ok 2 strings match",
    "  ---",
    "    operator: equal",
    "    expected: 'you'",
    "    actual:   'me'",
    "    at: Test.<anonymous> (/Users/scott/www/divshot/divshot-cli/test/index.js:8:5)",
    "  ...",
    "ok 3 true value",
    "this is a console log",
    "# false values",
    "ok 4 should be false",
    "ok 5 false value"
  ];

  var comments = [];
  var p = parser(function (err, output) {

    t.deepEqual(
      comments,
      [
        {
          type: 'comment',
          raw: 'this is a console log',
          test: 1
        }
      ],
      'one comment'
    );
    t.end();
  });

  p.on('comment', function (comment) {

    comments.push(comment);
  });

  mockTap.forEach(function (line) {

    p.write(line + '\n');
  });
  p.end();
});

test('handles HTTP error source', function (t) {

  t.plan(1);

  var mockTap = [
    "TAP version 13",
    "# is true",
    "ok 1 true value",
    "ok 2 true value",
    "not ok 3 strings match",
    "  ---",
    "    operator: equal",
    "    expected: 'you'",
    "    actual:   'me'",
    "    at: Test.<anonymous> (http://localhost:9966/index.js:8:5)",
    "  ...",
    "not ok 15 plan != count",
    "  ---",
    "    operator: fail",
    "    expected: 4",
    "    actual:   3",
    "  ...",
    "",
    "1..15",
  ];

  var p = parser();

  p.on('output', function (output) {
    var assert = output.fail[0];
    t.deepEqual(assert.error.at, { character: '5', file: 'http://localhost:9966/index.js', line: '8' });
  });

  mockTap.forEach(function (line) {

    p.write(line + '\n');
  });
  p.end();
});

test('handles raw error string', function (t) {

  t.plan(1);

  var rawLines = [
    "    operator: deepEqual",
    "    expected:",
    "      { 0: 0, 1: 0, 10: 0, 11: 255, 12: 0, 13: 0, 14: 0, 15: 255, 2: 0, 3: 221, 4: 0, 5: 0, 6: 0, 7: 255, 8: 0, 9: 0 }",
    "    actual:",
    "      { 0: 0, 1: 0, 10: 0, 11: 255, 12: 0, 13: 0, 14: 0, 15: 255, 2: 0, 3: 255, 4: 0, 5: 0, 6: 0, 7: 255, 8: 0, 9: 0 }",
    "    at: Test.<anonymous> (http://localhost:9966/index.js:8:5)"
  ];

  var mockTap = [
    "TAP version 13",
    "# is true",
    "ok 1 true value",
    "ok 2 true value",
    "not ok 3 arrays match",
    "  ---"
  ].concat(rawLines).concat([
    "  ...",
    "not ok 15 plan != count",
    "  ---",
    "    operator: fail",
    "    expected: 4",
    "    actual:   3",
    "  ...",
    "",
    "1..15",
  ]);

  var p = parser();

  p.on('output', function (output) {
    var assert = output.fail[0];
    t.deepEqual(assert.error.raw, rawLines.join('\n'));
  });

  mockTap.forEach(function (line) {

    p.write(line + '\n');
  });
  p.end();
});

test('handles multiline error string with |-', function (t) {

  t.plan(2);

  var mockTap = [
    "TAP version 13",
    "# is true",
    "ok 1 true value",
    "not ok 2 object deep equal with cross",
    "  ---",
    "    operator: deepEqual",
    "    expected: |-",
    "      { a: 1, b: 2 }",
    "    actual: |-",
    "      { a: 1, b: 3 }",
    "    at: Test.<anonymous> (/Users/germ/Projects/a/b/test.js:7:5)",
    "  ...",
    "",
    "1..2",
  ];

  var p = parser();

  p.on('output', function (output) {
    var assert = output.fail[0];
    t.equal(assert.error.expected, '{ a: 1, b: 2 }');
    t.equal(assert.error.actual, '{ a: 1, b: 3 }');
  });

  mockTap.forEach(function (line) {
    p.write(line + '\n');
  });
  p.end();

});

test('handles multiline error stack with |-', function (t) {

  t.plan(2);

  var mockTap = [
    "TAP version 13",
    "# promise error",
    "not ok 1 TypeError: foo",
    "  ---",
    "    operator: error",
    "    expected: |-",
    "      undefined",
    "    actual: |-",
    "      [TypeError: foo]",
    "    at: process._tickCallback (internal/process/next_tick.js:103:7)",
    "    stack: |-",
    "      TypeError: foo",
    "          at throwError (/Users/germ/Projects/a/b/test.js:17:9)",
    "          at Promise.resolve.then (/Users/germ/Projects/a/b/test.js:24:5)",
    "          at process._tickCallback (internal/process/next_tick.js:103:7)",
    "  ...",
    "",
    "1..1",
    "# tests 1",
    "# pass  0",
    "# fail  1"
  ];

  var p = parser();

  p.on('output', function (output) {
    var assert = output.fail[0];
    t.equal(assert.error.stack, 'TypeError: foo\n'
      + 'at throwError (/Users/germ/Projects/a/b/test.js:17:9)\n'
      + 'at Promise.resolve.then (/Users/germ/Projects/a/b/test.js:24:5)\n'
      + 'at process._tickCallback (internal/process/next_tick.js:103:7)\n'
    );
    t.equal(assert.error.raw, '    operator: error\n'
      + '    expected: |-\n'
      + '      undefined\n'
      + '    actual: |-\n'
      + '      [TypeError: foo]\n'
      + '    at: process._tickCallback (internal/process/next_tick.js:103:7)\n'
      + '    stack: |-\n'
      + 'TypeError: foo\n'
      + 'at throwError (/Users/germ/Projects/a/b/test.js:17:9)\n'
      + 'at Promise.resolve.then (/Users/germ/Projects/a/b/test.js:24:5)\n'
      + 'at process._tickCallback (internal/process/next_tick.js:103:7)'
    );
  });

  mockTap.forEach(function (line) {
    p.write(line + '\n');
  });
  p.end();

});

test('output without plan', function (t) {

  t.plan(1);

  var mockTap = [
    "# is true",
    "ok 1 true value",
  ];

  var p = parser();

  p.on('output', function (output) {

    t.deepEqual(output, {
      asserts: [
        { name: 'true value', number: 1, ok: true, raw: 'ok 1 true value', test: 1, type: 'assert' }
      ],
      fail: [],
      pass: [
        { name: 'true value', number: 1, ok: true, raw: 'ok 1 true value', test: 1, type: 'assert' }
      ],
      results: [],
      tests: [
        { name: 'is true', number: 1, raw: '# is true', type: 'test' }
      ],
      versions: [],
      comments: [],
      plans: [],
      errors: [
        { message: 'no plan provided', type: 'error' }
      ]
    }, 'output data with empty plans and no plan provided');
  });

  mockTap.forEach(function (line) {
    p.write(line + '\n');
  });
  p.end();

});

test('output with assert count and plan mismatch', function (t) {

  t.plan(1);

  var mockTap = [
    "# is true",
    "ok 1 true value",
    "1..2",
  ];

  var p = parser();

  p.on('output', function (output) {

    t.deepEqual(output, {
      asserts: [
        { name: 'true value', number: 1, ok: true, raw: 'ok 1 true value', test: 1, type: 'assert' }
      ],
      fail: [],
      pass: [
        { name: 'true value', number: 1, ok: true, raw: 'ok 1 true value', test: 1, type: 'assert' }
      ],
      results: [],
      tests: [
        { name: 'is true', number: 1, raw: '# is true', type: 'test' }
      ],
      versions: [],
      comments: [],
      plans: [
        { from: 1, to: 2, raw: '1..2', skip: undefined, type: 'plan' }
      ],
      errors: [
        { message: 'incorrect number of assertions made', type: 'error' }
      ]
    }, 'output data with empty assert count and plan mismatch error');
  });

  mockTap.forEach(function (line) {
    p.write(line + '\n');
  });
  p.end();

});

test('output with plan end and assertion number mismatch', function (t) {

  t.plan(1);

  var mockTap = [
    "# is true",
    "ok 1 true value",
    'ok 3 true value',
    "1..2",
  ];

  var p = parser();

  p.on('output', function (output) {

    t.deepEqual(output, {
      asserts: [
        { name: 'true value', number: 1, ok: true, raw: 'ok 1 true value', test: 1, type: 'assert' },
        { name: 'true value', number: 3, ok: true, raw: 'ok 3 true value', test: 1, type: 'assert' }
      ],
      fail: [],
      pass: [
        { name: 'true value', number: 1, ok: true, raw: 'ok 1 true value', test: 1, type: 'assert' },
        { name: 'true value', number: 3, ok: true, raw: 'ok 3 true value', test: 1, type: 'assert' }
      ],
      results: [],
      tests: [
        { name: 'is true', number: 1, raw: '# is true', type: 'test' }
      ],
      versions: [],
      comments: [],
      plans: [
        { from: 1, to: 2, raw: '1..2', skip: undefined, type: 'plan' }
      ],
      errors: [
        { message: 'last assertion number does not equal the plan end', type: 'error' }
      ]
    }, 'output data with plan end error');
  });

  mockTap.forEach(function (line) {
    p.write(line + '\n');
  });
  p.end();

});
