LCOV - code coverage report
Current view: top level - matcher-0.12.11/lib/src - iterable_matchers.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 142 0.0 %
Date: 2021-11-28 14:37:50 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
       2             : // for details. All rights reserved. Use of this source code is governed by a
       3             : // BSD-style license that can be found in the LICENSE file.
       4             : 
       5             : import 'description.dart';
       6             : import 'equals_matcher.dart';
       7             : import 'feature_matcher.dart';
       8             : import 'interfaces.dart';
       9             : import 'util.dart';
      10             : 
      11             : /// Returns a matcher which matches [Iterable]s in which all elements
      12             : /// match the given [valueOrMatcher].
      13           0 : Matcher everyElement(Object? valueOrMatcher) =>
      14           0 :     _EveryElement(wrapMatcher(valueOrMatcher));
      15             : 
      16             : class _EveryElement extends _IterableMatcher {
      17             :   final Matcher _matcher;
      18             : 
      19           0 :   _EveryElement(this._matcher);
      20             : 
      21           0 :   @override
      22             :   bool typedMatches(Iterable item, Map matchState) {
      23             :     var i = 0;
      24           0 :     for (var element in item) {
      25           0 :       if (!_matcher.matches(element, matchState)) {
      26           0 :         addStateInfo(matchState, {'index': i, 'element': element});
      27             :         return false;
      28             :       }
      29           0 :       ++i;
      30             :     }
      31             :     return true;
      32             :   }
      33             : 
      34           0 :   @override
      35             :   Description describe(Description description) =>
      36           0 :       description.add('every element(').addDescriptionOf(_matcher).add(')');
      37             : 
      38           0 :   @override
      39             :   Description describeTypedMismatch(dynamic item,
      40             :       Description mismatchDescription, Map matchState, bool verbose) {
      41           0 :     if (matchState['index'] != null) {
      42           0 :       var index = matchState['index'];
      43           0 :       var element = matchState['element'];
      44             :       mismatchDescription
      45           0 :           .add('has value ')
      46           0 :           .addDescriptionOf(element)
      47           0 :           .add(' which ');
      48           0 :       var subDescription = StringDescription();
      49           0 :       _matcher.describeMismatch(
      50           0 :           element, subDescription, matchState['state'] as Map, verbose);
      51           0 :       if (subDescription.length > 0) {
      52           0 :         mismatchDescription.add(subDescription.toString());
      53             :       } else {
      54           0 :         mismatchDescription.add("doesn't match ");
      55           0 :         _matcher.describe(mismatchDescription);
      56             :       }
      57           0 :       mismatchDescription.add(' at index $index');
      58             :       return mismatchDescription;
      59             :     }
      60             :     return super
      61           0 :         .describeMismatch(item, mismatchDescription, matchState, verbose);
      62             :   }
      63             : }
      64             : 
      65             : /// Returns a matcher which matches [Iterable]s in which at least one
      66             : /// element matches the given [valueOrMatcher].
      67           0 : Matcher anyElement(Object? valueOrMatcher) =>
      68           0 :     _AnyElement(wrapMatcher(valueOrMatcher));
      69             : 
      70             : class _AnyElement extends _IterableMatcher {
      71             :   final Matcher _matcher;
      72             : 
      73           0 :   _AnyElement(this._matcher);
      74             : 
      75           0 :   @override
      76             :   bool typedMatches(Iterable item, Map matchState) =>
      77           0 :       item.any((e) => _matcher.matches(e, matchState));
      78             : 
      79           0 :   @override
      80             :   Description describe(Description description) =>
      81           0 :       description.add('some element ').addDescriptionOf(_matcher);
      82             : }
      83             : 
      84             : /// Returns a matcher which matches [Iterable]s that have the same
      85             : /// length and the same elements as [expected], in the same order.
      86             : ///
      87             : /// This is equivalent to [equals] but does not recurse.
      88           0 : Matcher orderedEquals(Iterable expected) => _OrderedEquals(expected);
      89             : 
      90             : class _OrderedEquals extends _IterableMatcher {
      91             :   final Iterable _expected;
      92             :   final Matcher _matcher;
      93             : 
      94           0 :   _OrderedEquals(this._expected) : _matcher = equals(_expected, 1);
      95             : 
      96           0 :   @override
      97             :   bool typedMatches(Iterable item, Map matchState) =>
      98           0 :       _matcher.matches(item, matchState);
      99             : 
     100           0 :   @override
     101             :   Description describe(Description description) =>
     102           0 :       description.add('equals ').addDescriptionOf(_expected).add(' ordered');
     103             : 
     104           0 :   @override
     105             :   Description describeTypedMismatch(Iterable item,
     106             :       Description mismatchDescription, Map matchState, bool verbose) {
     107           0 :     return _matcher.describeMismatch(
     108             :         item, mismatchDescription, matchState, verbose);
     109             :   }
     110             : }
     111             : 
     112             : /// Returns a matcher which matches [Iterable]s that have the same length and
     113             : /// the same elements as [expected], but not necessarily in the same order.
     114             : ///
     115             : /// Note that this is worst case O(n^2) runtime and memory usage so it should
     116             : /// only be used on small iterables.
     117           0 : Matcher unorderedEquals(Iterable expected) => _UnorderedEquals(expected);
     118             : 
     119             : class _UnorderedEquals extends _UnorderedMatches {
     120             :   final List _expectedValues;
     121             : 
     122           0 :   _UnorderedEquals(Iterable expected)
     123           0 :       : _expectedValues = expected.toList(),
     124           0 :         super(expected.map(equals));
     125             : 
     126           0 :   @override
     127             :   Description describe(Description description) => description
     128           0 :       .add('equals ')
     129           0 :       .addDescriptionOf(_expectedValues)
     130           0 :       .add(' unordered');
     131             : }
     132             : 
     133             : /// Iterable matchers match against [Iterable]s. We add this intermediate
     134             : /// class to give better mismatch error messages than the base Matcher class.
     135             : abstract class _IterableMatcher extends FeatureMatcher<Iterable> {
     136           0 :   const _IterableMatcher();
     137             : }
     138             : 
     139             : /// Returns a matcher which matches [Iterable]s whose elements match the
     140             : /// matchers in [expected], but not necessarily in the same order.
     141             : ///
     142             : /// Note that this is worst case O(n^2) runtime and memory usage so it should
     143             : /// only be used on small iterables.
     144           0 : Matcher unorderedMatches(Iterable expected) => _UnorderedMatches(expected);
     145             : 
     146             : class _UnorderedMatches extends _IterableMatcher {
     147             :   final List<Matcher> _expected;
     148             :   final bool _allowUnmatchedValues;
     149             : 
     150           0 :   _UnorderedMatches(Iterable expected, {bool allowUnmatchedValues = false})
     151           0 :       : _expected = expected.map(wrapMatcher).toList(),
     152             :         _allowUnmatchedValues = allowUnmatchedValues;
     153             : 
     154           0 :   String? _test(List values) {
     155             :     // Check the lengths are the same.
     156           0 :     if (_expected.length > values.length) {
     157           0 :       return 'has too few elements (${values.length} < ${_expected.length})';
     158           0 :     } else if (!_allowUnmatchedValues && _expected.length < values.length) {
     159           0 :       return 'has too many elements (${values.length} > ${_expected.length})';
     160             :     }
     161             : 
     162           0 :     var edges = List.generate(values.length, (_) => <int>[], growable: false);
     163           0 :     for (var v = 0; v < values.length; v++) {
     164           0 :       for (var m = 0; m < _expected.length; m++) {
     165           0 :         if (_expected[m].matches(values[v], {})) {
     166           0 :           edges[v].add(m);
     167             :         }
     168             :       }
     169             :     }
     170             :     // The index into `values` matched with each matcher or `null` if no value
     171             :     // has been matched yet.
     172           0 :     var matched = List<int?>.filled(_expected.length, null);
     173           0 :     for (var valueIndex = 0; valueIndex < values.length; valueIndex++) {
     174           0 :       _findPairing(edges, valueIndex, matched);
     175             :     }
     176             :     for (var matcherIndex = 0;
     177           0 :         matcherIndex < _expected.length;
     178           0 :         matcherIndex++) {
     179           0 :       if (matched[matcherIndex] == null) {
     180           0 :         final description = StringDescription()
     181           0 :             .add('has no match for ')
     182           0 :             .addDescriptionOf(_expected[matcherIndex])
     183           0 :             .add(' at index $matcherIndex');
     184             :         final remainingUnmatched =
     185           0 :             matched.sublist(matcherIndex + 1).where((m) => m == null).length;
     186           0 :         return remainingUnmatched == 0
     187           0 :             ? description.toString()
     188             :             : description
     189           0 :                 .add(' along with $remainingUnmatched other unmatched')
     190           0 :                 .toString();
     191             :       }
     192             :     }
     193             :     return null;
     194             :   }
     195             : 
     196           0 :   @override
     197             :   bool typedMatches(Iterable item, Map mismatchState) =>
     198           0 :       _test(item.toList()) == null;
     199             : 
     200           0 :   @override
     201             :   Description describe(Description description) => description
     202           0 :       .add('matches ')
     203           0 :       .addAll('[', ', ', ']', _expected)
     204           0 :       .add(' unordered');
     205             : 
     206           0 :   @override
     207             :   Description describeTypedMismatch(dynamic item,
     208             :           Description mismatchDescription, Map matchState, bool verbose) =>
     209           0 :       mismatchDescription.add(_test(item.toList())!);
     210             : 
     211             :   /// Returns `true` if the value at [valueIndex] can be paired with some
     212             :   /// unmatched matcher and updates the state of [matched].
     213             :   ///
     214             :   /// If there is a conflict where multiple values may match the same matcher
     215             :   /// recursively looks for a new place to match the old value.
     216           0 :   bool _findPairing(
     217             :           List<List<int>> edges, int valueIndex, List<int?> matched) =>
     218           0 :       _findPairingInner(edges, valueIndex, matched, <int>{});
     219             : 
     220             :   /// Implementation of [_findPairing], tracks [reserved] which are the
     221             :   /// matchers that have been used _during_ this search.
     222           0 :   bool _findPairingInner(List<List<int>> edges, int valueIndex,
     223             :       List<int?> matched, Set<int> reserved) {
     224             :     final possiblePairings =
     225           0 :         edges[valueIndex].where((m) => !reserved.contains(m));
     226           0 :     for (final matcherIndex in possiblePairings) {
     227           0 :       reserved.add(matcherIndex);
     228           0 :       final previouslyMatched = matched[matcherIndex];
     229             :       if (previouslyMatched == null ||
     230             :           // If the matcher isn't already free, check whether the existing value
     231             :           // occupying the matcher can be bumped to another one.
     232           0 :           _findPairingInner(edges, matched[matcherIndex]!, matched, reserved)) {
     233           0 :         matched[matcherIndex] = valueIndex;
     234             :         return true;
     235             :       }
     236             :     }
     237             :     return false;
     238             :   }
     239             : }
     240             : 
     241             : /// A pairwise matcher for [Iterable]s.
     242             : ///
     243             : /// The [comparator] function, taking an expected and an actual argument, and
     244             : /// returning whether they match, will be applied to each pair in order.
     245             : /// [description] should be a meaningful name for the comparator.
     246           0 : Matcher pairwiseCompare<S, T>(Iterable<S> expected,
     247             :         bool Function(S, T) comparator, String description) =>
     248           0 :     _PairwiseCompare(expected, comparator, description);
     249             : 
     250             : typedef _Comparator<S, T> = bool Function(S a, T b);
     251             : 
     252             : class _PairwiseCompare<S, T> extends _IterableMatcher {
     253             :   final Iterable<S> _expected;
     254             :   final _Comparator<S, T> _comparator;
     255             :   final String _description;
     256             : 
     257           0 :   _PairwiseCompare(this._expected, this._comparator, this._description);
     258             : 
     259           0 :   @override
     260             :   bool typedMatches(Iterable item, Map matchState) {
     261           0 :     if (item.length != _expected.length) return false;
     262           0 :     var iterator = item.iterator;
     263             :     var i = 0;
     264           0 :     for (var e in _expected) {
     265           0 :       iterator.moveNext();
     266           0 :       if (!_comparator(e, iterator.current as T)) {
     267           0 :         addStateInfo(matchState,
     268           0 :             {'index': i, 'expected': e, 'actual': iterator.current});
     269             :         return false;
     270             :       }
     271           0 :       i++;
     272             :     }
     273             :     return true;
     274             :   }
     275             : 
     276           0 :   @override
     277             :   Description describe(Description description) =>
     278           0 :       description.add('pairwise $_description ').addDescriptionOf(_expected);
     279             : 
     280           0 :   @override
     281             :   Description describeTypedMismatch(Iterable item,
     282             :       Description mismatchDescription, Map matchState, bool verbose) {
     283           0 :     if (item.length != _expected.length) {
     284             :       return mismatchDescription
     285           0 :           .add('has length ${item.length} instead of ${_expected.length}');
     286             :     } else {
     287             :       return mismatchDescription
     288           0 :           .add('has ')
     289           0 :           .addDescriptionOf(matchState['actual'])
     290           0 :           .add(' which is not $_description ')
     291           0 :           .addDescriptionOf(matchState['expected'])
     292           0 :           .add(' at index ${matchState["index"]}');
     293             :     }
     294             :   }
     295             : }
     296             : 
     297             : /// Matches [Iterable]s which contain an element matching every value in
     298             : /// [expected] in any order, and may contain additional values.
     299             : ///
     300             : /// For example: `[0, 1, 0, 2, 0]` matches `containsAll([1, 2])` and
     301             : /// `containsAll([2, 1])` but not `containsAll([1, 2, 3])`.
     302             : ///
     303             : /// Will only match values which implement [Iterable].
     304             : ///
     305             : /// Each element in the value will only be considered a match for a single
     306             : /// matcher in [expected] even if it could satisfy more than one. For instance
     307             : /// `containsAll([greaterThan(1), greaterThan(2)])` will not be satisfied by
     308             : /// `[3]`. To check that all matchers are satisfied within an iterable and allow
     309             : /// the same element to satisfy multiple matchers use
     310             : /// `allOf(matchers.map(contains))`.
     311             : ///
     312             : /// Note that this is worst case O(n^2) runtime and memory usage so it should
     313             : /// only be used on small iterables.
     314           0 : Matcher containsAll(Iterable expected) => _ContainsAll(expected);
     315             : 
     316             : class _ContainsAll extends _UnorderedMatches {
     317             :   final Iterable _unwrappedExpected;
     318             : 
     319           0 :   _ContainsAll(Iterable expected)
     320             :       : _unwrappedExpected = expected,
     321           0 :         super(expected.map(wrapMatcher), allowUnmatchedValues: true);
     322           0 :   @override
     323             :   Description describe(Description description) =>
     324           0 :       description.add('contains all of ').addDescriptionOf(_unwrappedExpected);
     325             : }
     326             : 
     327             : /// Matches [Iterable]s which contain an element matching every value in
     328             : /// [expected] in the same order, but may contain additional values interleaved
     329             : /// throughout.
     330             : ///
     331             : /// For example: `[0, 1, 0, 2, 0]` matches `containsAllInOrder([1, 2])` but not
     332             : /// `containsAllInOrder([2, 1])` or `containsAllInOrder([1, 2, 3])`.
     333             : ///
     334             : /// Will only match values which implement [Iterable].
     335           0 : Matcher containsAllInOrder(Iterable expected) => _ContainsAllInOrder(expected);
     336             : 
     337             : class _ContainsAllInOrder extends _IterableMatcher {
     338             :   final Iterable _expected;
     339             : 
     340           0 :   _ContainsAllInOrder(this._expected);
     341             : 
     342           0 :   String? _test(Iterable item, Map matchState) {
     343           0 :     var matchers = _expected.map(wrapMatcher).toList();
     344             :     var matcherIndex = 0;
     345           0 :     for (var value in item) {
     346           0 :       if (matchers[matcherIndex].matches(value, matchState)) matcherIndex++;
     347           0 :       if (matcherIndex == matchers.length) return null;
     348             :     }
     349           0 :     return StringDescription()
     350           0 :         .add('did not find a value matching ')
     351           0 :         .addDescriptionOf(matchers[matcherIndex])
     352           0 :         .add(' following expected prior values')
     353           0 :         .toString();
     354             :   }
     355             : 
     356           0 :   @override
     357             :   bool typedMatches(Iterable item, Map matchState) =>
     358           0 :       _test(item, matchState) == null;
     359             : 
     360           0 :   @override
     361             :   Description describe(Description description) => description
     362           0 :       .add('contains in order(')
     363           0 :       .addDescriptionOf(_expected)
     364           0 :       .add(')');
     365             : 
     366           0 :   @override
     367             :   Description describeTypedMismatch(Iterable item,
     368             :           Description mismatchDescription, Map matchState, bool verbose) =>
     369           0 :       mismatchDescription.add(_test(item, matchState)!);
     370             : }

Generated by: LCOV version 1.14