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