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 'feature_matcher.dart'; 6 : import 'interfaces.dart'; 7 : 8 : /// Returns a matcher which matches if the match argument is a string and 9 : /// is equal to [value] when compared case-insensitively. 10 0 : Matcher equalsIgnoringCase(String value) => _IsEqualIgnoringCase(value); 11 : 12 : class _IsEqualIgnoringCase extends FeatureMatcher<String> { 13 : final String _value; 14 : final String _matchValue; 15 : 16 0 : _IsEqualIgnoringCase(String value) 17 : : _value = value, 18 0 : _matchValue = value.toLowerCase(); 19 : 20 0 : @override 21 : bool typedMatches(String item, Map matchState) => 22 0 : _matchValue == item.toLowerCase(); 23 : 24 0 : @override 25 : Description describe(Description description) => 26 0 : description.addDescriptionOf(_value).add(' ignoring case'); 27 : } 28 : 29 : /// Returns a matcher which matches if the match argument is a string and 30 : /// is equal to [value], ignoring whitespace. 31 : /// 32 : /// In this matcher, "ignoring whitespace" means comparing with all runs of 33 : /// whitespace collapsed to single space characters and leading and trailing 34 : /// whitespace removed. 35 : /// 36 : /// For example, the following will all match successfully: 37 : /// 38 : /// expect("hello world", equalsIgnoringWhitespace("hello world")); 39 : /// expect(" hello world", equalsIgnoringWhitespace("hello world")); 40 : /// expect("hello world ", equalsIgnoringWhitespace("hello world")); 41 : /// 42 : /// The following will not match: 43 : /// 44 : /// expect("helloworld", equalsIgnoringWhitespace("hello world")); 45 : /// expect("he llo world", equalsIgnoringWhitespace("hello world")); 46 0 : Matcher equalsIgnoringWhitespace(String value) => 47 0 : _IsEqualIgnoringWhitespace(value); 48 : 49 : class _IsEqualIgnoringWhitespace extends FeatureMatcher<String> { 50 : final String _matchValue; 51 : 52 0 : _IsEqualIgnoringWhitespace(String value) 53 0 : : _matchValue = collapseWhitespace(value); 54 : 55 0 : @override 56 : bool typedMatches(String item, Map matchState) => 57 0 : _matchValue == collapseWhitespace(item); 58 : 59 0 : @override 60 : Description describe(Description description) => 61 0 : description.addDescriptionOf(_matchValue).add(' ignoring whitespace'); 62 : 63 0 : @override 64 : Description describeTypedMismatch(dynamic item, 65 : Description mismatchDescription, Map matchState, bool verbose) { 66 : return mismatchDescription 67 0 : .add('is ') 68 0 : .addDescriptionOf(collapseWhitespace(item)) 69 0 : .add(' with whitespace compressed'); 70 : } 71 : } 72 : 73 : /// Returns a matcher that matches if the match argument is a string and 74 : /// starts with [prefixString]. 75 0 : Matcher startsWith(String prefixString) => _StringStartsWith(prefixString); 76 : 77 : class _StringStartsWith extends FeatureMatcher<String> { 78 : final String _prefix; 79 : 80 0 : const _StringStartsWith(this._prefix); 81 : 82 0 : @override 83 0 : bool typedMatches(dynamic item, Map matchState) => item.startsWith(_prefix); 84 : 85 0 : @override 86 : Description describe(Description description) => 87 0 : description.add('a string starting with ').addDescriptionOf(_prefix); 88 : } 89 : 90 : /// Returns a matcher that matches if the match argument is a string and 91 : /// ends with [suffixString]. 92 0 : Matcher endsWith(String suffixString) => _StringEndsWith(suffixString); 93 : 94 : class _StringEndsWith extends FeatureMatcher<String> { 95 : final String _suffix; 96 : 97 0 : const _StringEndsWith(this._suffix); 98 : 99 0 : @override 100 0 : bool typedMatches(dynamic item, Map matchState) => item.endsWith(_suffix); 101 : 102 0 : @override 103 : Description describe(Description description) => 104 0 : description.add('a string ending with ').addDescriptionOf(_suffix); 105 : } 106 : 107 : /// Returns a matcher that matches if the match argument is a string and 108 : /// contains a given list of [substrings] in relative order. 109 : /// 110 : /// For example, `stringContainsInOrder(["a", "e", "i", "o", "u"])` will match 111 : /// "abcdefghijklmnopqrstuvwxyz". 112 : 113 0 : Matcher stringContainsInOrder(List<String> substrings) => 114 0 : _StringContainsInOrder(substrings); 115 : 116 : class _StringContainsInOrder extends FeatureMatcher<String> { 117 : final List<String> _substrings; 118 : 119 0 : const _StringContainsInOrder(this._substrings); 120 : 121 0 : @override 122 : bool typedMatches(dynamic item, Map matchState) { 123 : var fromIndex = 0; 124 0 : for (var s in _substrings) { 125 0 : var index = item.indexOf(s, fromIndex); 126 0 : if (index < 0) return false; 127 0 : fromIndex = index + s.length; 128 : } 129 : return true; 130 : } 131 : 132 0 : @override 133 0 : Description describe(Description description) => description.addAll( 134 0 : 'a string containing ', ', ', ' in order', _substrings); 135 : } 136 : 137 : /// Returns a matcher that matches if the match argument is a string and 138 : /// matches the regular expression given by [re]. 139 : /// 140 : /// [re] can be a [RegExp] instance or a [String]; in the latter case it will be 141 : /// used to create a RegExp instance. 142 0 : Matcher matches(Pattern re) => _MatchesRegExp(re); 143 : 144 : class _MatchesRegExp extends FeatureMatcher<String> { 145 : final RegExp _regexp; 146 : 147 0 : _MatchesRegExp(Pattern re) 148 0 : : _regexp = (re is String) 149 0 : ? RegExp(re) 150 0 : : (re is RegExp) 151 : ? re 152 0 : : throw ArgumentError('matches requires a regexp or string'); 153 : 154 0 : @override 155 0 : bool typedMatches(dynamic item, Map matchState) => _regexp.hasMatch(item); 156 : 157 0 : @override 158 : Description describe(Description description) => 159 0 : description.add("match '${_regexp.pattern}'"); 160 : } 161 : 162 : /// Utility function to collapse whitespace runs to single spaces 163 : /// and strip leading/trailing whitespace. 164 0 : String collapseWhitespace(String string) { 165 0 : var result = StringBuffer(); 166 : var skipSpace = true; 167 0 : for (var i = 0; i < string.length; i++) { 168 0 : var character = string[i]; 169 0 : if (_isWhitespace(character)) { 170 : if (!skipSpace) { 171 0 : result.write(' '); 172 : skipSpace = true; 173 : } 174 : } else { 175 0 : result.write(character); 176 : skipSpace = false; 177 : } 178 : } 179 0 : return result.toString().trim(); 180 : } 181 : 182 0 : bool _isWhitespace(String ch) => 183 0 : ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';