// 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 'test_utils.dart';

void main() {
  test('isEmpty', () {
    shouldPass([], isEmpty);
    shouldFail([1], isEmpty, 'Expected: empty Actual: [1]');
  });

  test('isNotEmpty', () {
    shouldFail([], isNotEmpty, 'Expected: non-empty Actual: []');
    shouldPass([1], isNotEmpty);
  });

  test('contains', () {
    var d = [1, 2];
    shouldPass(d, contains(1));
    shouldFail(
        d,
        contains(0),
        'Expected: contains <0> '
        'Actual: [1, 2] '
        'Which: does not contain <0>');

    shouldFail(
        'String',
        contains(42),
        "Expected: contains <42> Actual: 'String' "
            'Which: does not contain <42>');
  });

  test('equals with matcher element', () {
    var d = ['foo', 'bar'];
    shouldPass(d, equals(['foo', startsWith('ba')]));
    shouldFail(
        d,
        equals(['foo', endsWith('ba')]),
        "Expected: ['foo', <a string ending with 'ba'>] "
        "Actual: ['foo', 'bar'] "
        "Which: at location [1] is 'bar' which "
        "does not match a string ending with 'ba'");
  });

  test('isIn', () {
    // Iterable
    shouldPass(1, isIn([1, 2]));
    shouldFail(0, isIn([1, 2]), 'Expected: is in [1, 2] Actual: <0>');

    // Map
    shouldPass(1, isIn({1: null}));
    shouldFail(0, isIn({1: null}), 'Expected: is in {1: null} Actual: <0>');

    // String
    shouldPass('42', isIn('1421'));
    shouldFail('42', isIn('41'), "Expected: is in '41' Actual: '42'");
    shouldFail(
        0, isIn('a string'), endsWith('not an <Instance of \'Pattern\'>'));

    // Invalid arg
    expect(() => isIn(42), throwsArgumentError);
  });

  test('everyElement', () {
    var d = [1, 2];
    var e = [1, 1, 1];
    shouldFail(
        d,
        everyElement(1),
        'Expected: every element(<1>) '
        'Actual: [1, 2] '
        "Which: has value <2> which doesn't match <1> at index 1");
    shouldPass(e, everyElement(1));
    shouldFail('not iterable', everyElement(1),
        endsWith('not an <Instance of \'Iterable\'>'));
  });

  test('nested everyElement', () {
    var d = [
      ['foo', 'bar'],
      ['foo'],
      []
    ];
    var e = [
      ['foo', 'bar'],
      ['foo'],
      3,
      []
    ];
    shouldPass(d, everyElement(anyOf(isEmpty, contains('foo'))));
    shouldFail(
        d,
        everyElement(everyElement(equals('foo'))),
        "Expected: every element(every element('foo')) "
        "Actual: [['foo', 'bar'], ['foo'], []] "
        "Which: has value ['foo', 'bar'] which has value 'bar' "
        'which is different. Expected: foo Actual: bar ^ '
        'Differ at offset 0 at index 1 at index 0');
    shouldFail(
        d,
        everyElement(allOf(hasLength(greaterThan(0)), contains('foo'))),
        'Expected: every element((an object with length of a value '
        "greater than <0> and contains 'foo')) "
        "Actual: [['foo', 'bar'], ['foo'], []] "
        'Which: has value [] which has length of <0> at index 2');
    shouldFail(
        d,
        everyElement(allOf(contains('foo'), hasLength(greaterThan(0)))),
        "Expected: every element((contains 'foo' and "
        'an object with length of a value greater than <0>)) '
        "Actual: [['foo', 'bar'], ['foo'], []] "
        "Which: has value [] which does not contain 'foo' at index 2");
    shouldFail(
        e,
        everyElement(allOf(contains('foo'), hasLength(greaterThan(0)))),
        "Expected: every element((contains 'foo' and an object with "
        'length of a value greater than <0>)) '
        "Actual: [['foo', 'bar'], ['foo'], 3, []] "
        'Which: has value <3> which is not a string, map or iterable '
        'at index 2');
  });

  test('anyElement', () {
    var d = [1, 2];
    var e = [1, 1, 1];
    shouldPass(d, anyElement(2));
    shouldFail(
        e, anyElement(2), 'Expected: some element <2> Actual: [1, 1, 1]');
    shouldFail('not an iterable', anyElement(2),
        endsWith('not an <Instance of \'Iterable\'>'));
  });

  test('orderedEquals', () {
    shouldPass([null], orderedEquals([null]));
    var d = [1, 2];
    shouldPass(d, orderedEquals([1, 2]));
    shouldFail(
        d,
        orderedEquals([2, 1]),
        'Expected: equals [2, 1] ordered '
        'Actual: [1, 2] '
        'Which: at location [0] is <1> instead of <2>');
    shouldFail('not an iterable', orderedEquals([1]),
        endsWith('not an <Instance of \'Iterable\'>'));
  });

  test('unorderedEquals', () {
    var d = [1, 2];
    shouldPass(d, unorderedEquals([2, 1]));
    shouldFail(
        d,
        unorderedEquals([1]),
        'Expected: equals [1] unordered '
        'Actual: [1, 2] '
        'Which: has too many elements (2 > 1)');
    shouldFail(
        d,
        unorderedEquals([3, 2, 1]),
        'Expected: equals [3, 2, 1] unordered '
        'Actual: [1, 2] '
        'Which: has too few elements (2 < 3)');
    shouldFail(
        d,
        unorderedEquals([3, 1]),
        'Expected: equals [3, 1] unordered '
        'Actual: [1, 2] '
        'Which: has no match for <3> at index 0');
    shouldFail(
        d,
        unorderedEquals([3, 4]),
        'Expected: equals [3, 4] unordered '
        'Actual: [1, 2] '
        'Which: has no match for <3> at index 0'
        ' along with 1 other unmatched');
    shouldFail('not an iterable', unorderedEquals([1]),
        endsWith('not an <Instance of \'Iterable\'>'));
  });

  test('unorderedMatches', () {
    var d = [1, 2];
    shouldPass(d, unorderedMatches([2, 1]));
    shouldPass(d, unorderedMatches([greaterThan(1), greaterThan(0)]));
    shouldPass(d, unorderedMatches([greaterThan(0), greaterThan(1)]));
    shouldPass([2, 1], unorderedMatches([greaterThan(1), greaterThan(0)]));

    shouldPass([2, 1], unorderedMatches([greaterThan(0), greaterThan(1)]));
    // Excersize the case where pairings should get "bumped" multiple times
    shouldPass(
        [0, 1, 2, 3, 5, 6],
        unorderedMatches([
          greaterThan(1), // 6
          equals(2), // 2
          allOf([lessThan(3), isNot(0)]), // 1
          equals(0), // 0
          predicate((int v) => v % 2 == 1), // 3
          equals(5), // 5
        ]));
    shouldFail(
        d,
        unorderedMatches([greaterThan(0)]),
        'Expected: matches [a value greater than <0>] unordered '
        'Actual: [1, 2] '
        'Which: has too many elements (2 > 1)');
    shouldFail(
        d,
        unorderedMatches([3, 2, 1]),
        'Expected: matches [<3>, <2>, <1>] unordered '
        'Actual: [1, 2] '
        'Which: has too few elements (2 < 3)');
    shouldFail(
        d,
        unorderedMatches([3, 1]),
        'Expected: matches [<3>, <1>] unordered '
        'Actual: [1, 2] '
        'Which: has no match for <3> at index 0');
    shouldFail(
        d,
        unorderedMatches([greaterThan(3), greaterThan(0)]),
        'Expected: matches [a value greater than <3>, a value greater than '
        '<0>] unordered '
        'Actual: [1, 2] '
        'Which: has no match for a value greater than <3> at index 0');
    shouldFail('not an iterable', unorderedMatches([greaterThan(1)]),
        endsWith('not an <Instance of \'Iterable\'>'));
  });

  test('containsAll', () {
    var d = [0, 1, 2];
    shouldPass(d, containsAll([1, 2]));
    shouldPass(d, containsAll([2, 1]));
    shouldPass(d, containsAll([greaterThan(0), greaterThan(1)]));
    shouldPass([2, 1], containsAll([greaterThan(0), greaterThan(1)]));
    shouldFail(
        d,
        containsAll([1, 2, 3]),
        'Expected: contains all of [1, 2, 3] '
        'Actual: [0, 1, 2] '
        'Which: has no match for <3> at index 2');
    shouldFail(
        1,
        containsAll([1]),
        'Expected: contains all of [1] '
        'Actual: <1> '
        "Which: not an <Instance of 'Iterable'>");
    shouldFail(
        [-1, 2],
        containsAll([greaterThan(0), greaterThan(1)]),
        'Expected: contains all of [<a value greater than <0>>, '
        '<a value greater than <1>>] '
        'Actual: [-1, 2] '
        'Which: has no match for a value greater than <1> at index 1');
    shouldFail('not an iterable', containsAll([1, 2, 3]),
        endsWith('not an <Instance of \'Iterable\'>'));
  });

  test('containsAllInOrder', () {
    var d = [0, 1, 0, 2];
    shouldPass(d, containsAllInOrder([1, 2]));
    shouldPass(d, containsAllInOrder([greaterThan(0), greaterThan(1)]));
    shouldFail(
        d,
        containsAllInOrder([2, 1]),
        'Expected: contains in order([2, 1]) '
        'Actual: [0, 1, 0, 2] '
        'Which: did not find a value matching <1> following expected prior '
        'values');
    shouldFail(
        d,
        containsAllInOrder([greaterThan(1), greaterThan(0)]),
        'Expected: contains in order([<a value greater than <1>>, '
        '<a value greater than <0>>]) '
        'Actual: [0, 1, 0, 2] '
        'Which: did not find a value matching a value greater than <0> '
        'following expected prior values');
    shouldFail(
        d,
        containsAllInOrder([1, 2, 3]),
        'Expected: contains in order([1, 2, 3]) '
        'Actual: [0, 1, 0, 2] '
        'Which: did not find a value matching <3> following expected prior '
        'values');
    shouldFail(
        1,
        containsAllInOrder([1]),
        'Expected: contains in order([1]) '
        'Actual: <1> '
        "Which: not an <Instance of 'Iterable'>");
  });

  test('containsOnce', () {
    shouldPass([1, 2, 3, 4], containsOnce(2));
    shouldPass([1, 2, 11, 3], containsOnce(greaterThan(10)));
    shouldFail(
        [1, 2, 3, 4],
        containsOnce(10),
        'Expected: contains once(<10>) '
        'Actual: [1, 2, 3, 4] '
        'Which: did not find a value matching <10>');
    shouldFail(
        [1, 2, 3, 4],
        containsOnce(greaterThan(10)),
        'Expected: contains once(a value greater than <10>) '
        'Actual: [1, 2, 3, 4] '
        'Which: did not find a value matching a value greater than <10>');
    shouldFail(
        [1, 2, 1, 2],
        containsOnce(2),
        'Expected: contains once(<2>) '
        'Actual: [1, 2, 1, 2] '
        'Which: expected only one value matching <2> '
        'but found multiple: <2>, <2>');
    shouldFail(
        [1, 2, 10, 20],
        containsOnce(greaterThan(5)),
        'Expected: contains once(a value greater than <5>) '
        'Actual: [1, 2, 10, 20] '
        'Which: expected only one value matching a value greater than <5> '
        'but found multiple: <10>, <20>');
  });

  test('pairwise compare', () {
    var c = [1, 2];
    var d = [1, 2, 3];
    var e = [1, 4, 9];
    shouldFail(
        'x',
        pairwiseCompare(e, (int e, int a) => a <= e, 'less than or equal'),
        'Expected: pairwise less than or equal [1, 4, 9] '
            "Actual: 'x' "
            "Which: not an <Instance of 'Iterable'>");
    shouldFail(
        c,
        pairwiseCompare(e, (int e, int a) => a <= e, 'less than or equal'),
        'Expected: pairwise less than or equal [1, 4, 9] '
        'Actual: [1, 2] '
        'Which: has length 2 instead of 3');
    shouldPass(
        d, pairwiseCompare(e, (int e, int a) => a <= e, 'less than or equal'));
    shouldFail(
        d,
        pairwiseCompare(e, (int e, int a) => a < e, 'less than'),
        'Expected: pairwise less than [1, 4, 9] '
        'Actual: [1, 2, 3] '
        'Which: has <1> which is not less than <1> at index 0');
    shouldPass(
        d, pairwiseCompare(e, (int e, int a) => a * a == e, 'square root of'));
    shouldFail(
        d,
        pairwiseCompare(e, (int e, int a) => a + a == e, 'double'),
        'Expected: pairwise double [1, 4, 9] '
        'Actual: [1, 2, 3] '
        'Which: has <1> which is not double <1> at index 0');
    shouldFail(
        'not an iterable',
        pairwiseCompare(e, (int e, int a) => a + a == e, 'double'),
        endsWith('not an <Instance of \'Iterable\'>'));
  });

  test('isEmpty', () {
    var d = SimpleIterable(0);
    var e = SimpleIterable(1);
    shouldPass(d, isEmpty);
    shouldFail(
        e,
        isEmpty,
        'Expected: empty '
        'Actual: SimpleIterable:[1]');
  });

  test('isNotEmpty', () {
    var d = SimpleIterable(0);
    var e = SimpleIterable(1);
    shouldPass(e, isNotEmpty);
    shouldFail(
        d,
        isNotEmpty,
        'Expected: non-empty '
        'Actual: SimpleIterable:[]');
  });

  test('contains', () {
    var d = SimpleIterable(3);
    shouldPass(d, contains(2));
    shouldFail(
        d,
        contains(5),
        'Expected: contains <5> '
        'Actual: SimpleIterable:[3, 2, 1] '
        'Which: does not contain <5>');
  });
}
