Index: lib/unittest/core_matchers.dart |
=================================================================== |
--- lib/unittest/core_matchers.dart (revision 0) |
+++ lib/unittest/core_matchers.dart (revision 0) |
@@ -0,0 +1,337 @@ |
+// 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. |
+ |
+/** Returns a matcher that matches any null value. */ |
+IMatcher isNull() => new _IsNull(); |
Bob Nystrom
2012/05/30 23:23:51
Make it a var:
final isNull = const _IsNull();
P
gram
2012/06/01 17:33:15
Done.
|
+ |
+/** Returns a matcher that matches any non-null value. */ |
+IMatcher isNotNull() => isNot(isNull()); |
+ |
+class _IsNull extends Matcher { |
+ bool matches(item) => (item == null); |
Bob Nystrom
2012/05/30 23:23:51
Remove parens:
bool matches(item) => item == nul
gram
2012/06/01 17:33:15
Done.
|
+ IDescription describe(IDescription description) => description.append('null'); |
+} |
+ |
+/** Returns a matcher that matches the Boolean value true. */ |
+IMatcher isTrue() => new _IsTrue(); |
+ |
+/** Returns a matcher that matches anything except the Boolean value true. */ |
+IMatcher isFalse() => isNot(isTrue()); |
+ |
+class _IsTrue extends Matcher { |
+ |
Bob Nystrom
2012/05/30 23:23:51
Delete this blank line.
gram
2012/06/01 17:33:15
Done.
|
+ bool matches(item) => (item == true); |
Bob Nystrom
2012/05/30 23:23:51
Remove unneeded parens.
gram
2012/06/01 17:33:15
Done.
|
+ |
+ IDescription describe(IDescription description) => description.append('true'); |
+} |
+ |
+/** Returns a matches that matches if the value is the same instance |
Bob Nystrom
2012/05/30 23:23:51
Can you format this with the text on the next line
gram
2012/06/01 17:33:15
Done.
|
+ * as [object] (===). |
Bob Nystrom
2012/05/30 23:23:51
Put === in backticks to code format it: as [object
gram
2012/06/01 17:33:15
Done.
|
+ */ |
+IMatcher same(object) => new _IsSame(object); |
Bob Nystrom
2012/05/30 23:23:51
"isSameAs"?
gram
2012/06/01 17:33:15
Done.
|
+ |
+class _IsSame extends Matcher { |
+ |
+ var _object; |
Bob Nystrom
2012/05/30 23:23:51
Other matcher classes use "_val" for this. Should
gram
2012/06/01 17:33:15
Done.
|
+ |
+ _IsSame(this._object); |
+ |
+ bool matches(item) => (item === _object); |
Bob Nystrom
2012/05/30 23:23:51
Unnecessary ().
gram
2012/06/01 17:33:15
Done.
|
+ |
+ // If all types were hashable we could show a hash here |
Bob Nystrom
2012/05/30 23:23:51
True. :(
Add a "." at the end of the sentence.
gram
2012/06/01 17:33:15
Done.
|
+ IDescription describe(IDescription description) => |
+ description.append('same instance as ').appendDescriptionOf(_object); |
+} |
+ |
+/** Returns a matcher that matches if two objects are equal (==). */ |
+IMatcher equals(obj) => new _IsEqual(obj); |
+ |
+class _IsEqual extends Matcher { |
+ var _object; |
+ |
+ _IsEqual(this._object); |
+ |
+ bool matches(item) => (item == _object); |
+ |
+ IDescription describe(IDescription description) { |
+ //if (_object is IMatcher) { |
Bob Nystrom
2012/05/30 23:23:51
Remove commented out code.
gram
2012/06/01 17:33:15
Done.
|
+ // return description.append('<'). |
+ // appendDescriptionOf(_object). |
+ // append('>'); |
+ //} else { |
+ return description.appendDescriptionOf(_object); |
+ //} |
+ } |
+} |
+ |
+/** Returns a matcher that matches any value. */ |
+IMatcher anything([description = 'anything']) => new _IsAnything(description); |
Bob Nystrom
2012/05/30 23:23:51
Can we get rid of description and make this a cons
gram
2012/06/01 17:33:15
Done.
|
+ |
+class _IsAnything extends Matcher { |
+ var _description; |
+ |
+ _IsAnything([this._description = 'anything']); |
+ |
+ bool matches(item) => true; |
+ |
+ IDescription describe(IDescription description) => |
+ description.append(_description); |
+} |
+ |
+/** |
+ * Returns a matcher that matches functions that throw exceptions when called. |
+ * The value passed to expect() should be a reference to the function. |
+ * Note that the function cannot take arguments; to handle this |
+ * a wrapper will have to be created. |
+ * The function will be called once. |
+ */ |
+IMatcher throwsException() => new _Throws(); |
Bob Nystrom
2012/05/30 23:23:51
"throwsException" -> "throws"?
gram
2012/06/01 17:33:15
Done.
|
+ |
+/** |
+ * Returns a matcher that matches a function call against an exception, |
+ * which is in turn constrained by a [matcher]. |
+ * The value passed to expect() should be a reference to the function. |
+ * Note that the function cannot take arguments; to handle this |
+ * a wrapper will have to be created. |
+ * The function will be called once upon success, or twice upon failure |
+ * (the second time to get the failure description). |
+ */ |
+IMatcher throwsExceptionWhich(IMatcher matcher) => new _Throws(matcher); |
+/** |
Bob Nystrom
2012/05/30 23:23:51
Add blank line above comment.
gram
2012/06/01 17:33:15
Done.
|
+ * Returns a matcher that matches a function call against no exception. |
+ * The function will be called once. Any exceptions will be silently swallowed. |
+ * The value passed to expect() should be a reference to the function. |
+ * Note that the function cannot take arguments; to handle this |
+ * a wrapper will have to be created. |
+ */ |
+IMatcher returnsNormally() => new _ReturnsNormally(); |
Bob Nystrom
2012/05/30 23:23:51
Maybe I don't the intent here, but it seems to me
gram
2012/06/01 17:33:15
Yes - this will catch an exception and return fals
Bob Nystrom
2012/06/01 18:22:22
What happens when exceptions are thrown outside of
|
+ |
+class _Throws extends Matcher { |
+ IMatcher _matcher; |
+ |
+ _Throws([IMatcher this._matcher = null]) { |
+ if (_matcher != null && !(_matcher is Matcher)) { |
Bob Nystrom
2012/05/30 23:23:51
You can delete this. Dart will validate this for y
gram
2012/06/01 17:33:15
Done.
gram
2012/06/01 17:33:15
Done.
|
+ throw new IllegalArgumentException('Throws parameter must be matcher'); |
+ } |
+ } |
+ |
+ bool matches(item) { |
+ try { |
+ item(); |
+ return false; |
+ } catch (final e) { |
+ return _matcher == null || _matcher.matches(e); |
+ } |
+ } |
+ |
+ IDescription describe(IDescription description) { |
+ if (_matcher == null) { |
+ return description.append("throws an exception"); |
+ } else { |
+ return description.append('throws an exception which needs to match '). |
Bob Nystrom
2012/05/30 23:23:51
"which needs to match" -> "which matches"?
gram
2012/06/01 17:33:15
Done.
|
+ appendDescriptionOf(_matcher); |
+ } |
+ } |
+ |
+ IDescription describeMismatch(item, IDescription mismatchDescription) { |
+ try { |
+ item(); |
+ return mismatchDescription.append(' no exception'); |
+ } catch (final e) { |
+ return mismatchDescription.append(' exception does not match '). |
+ appendDescriptionOf(_matcher); |
+ } |
+ } |
+} |
+ |
+class _ReturnsNormally extends Matcher { |
+ bool matches(f) { |
+ try { |
+ f(); |
+ return true; |
+ } catch (final e) { |
+ return false; |
+ } |
+ } |
+ IDescription describe(IDescription description) => |
+ description.append("return normally"); |
+ IDescription describeMismatch(item, IDescription mismatchDescription) { |
+ return mismatchDescription.append(' threw exception'); |
+ } |
+} |
+ |
+/** |
+ * Returns a matcher that matches if an object is an instance |
+ * of a (super)[type]. |
Bob Nystrom
2012/05/30 23:23:51
This is worded a bit confusingly. Maybe:
"if an o
gram
2012/06/01 17:33:15
Done.
|
+ * |
+ * As types are not first class objects in Dart we can only |
+ * approximate this test, by using a generic wrapper named Type. |
Bob Nystrom
2012/05/30 23:23:51
Delete ","
gram
2012/06/01 17:33:15
Done.
|
+ * |
+ * For example, to test whether 'bar' is an instance of type |
+ * 'Foo', we would write: |
+ * |
+ * expect(bar, instanceOf(new Type<Foo>())); |
+ * |
+ * To get better error message, supply a name when creating the |
+ * Type wrapper; e.g.: |
+ * |
+ * expect(bar, instanceOf(new Type<Foo>('Foo'))); |
+ */ |
+IMatcher isInstanceOf(Type type) => new _IsInstanceOf(type); |
+ |
+class Type<T> { |
Bob Nystrom
2012/05/30 23:23:51
How about making this implement IMatcher directly
gram
2012/06/01 17:33:15
Done.
|
+ String _name; |
+ Type([this._name = 'specified type']); |
+ bool isInstance(obj) => obj is T; |
+ String toString() => _name; |
+} |
+ |
+class _IsInstanceOf extends Matcher { |
+ |
+ Type _expected_type; |
Bob Nystrom
2012/05/30 23:23:51
"_expectedType"
gram
2012/06/01 17:33:15
No longer applicable.
|
+ |
+ _IsInstanceOf(Type this._expected_type) { |
+ if (!(_expected_type is Type)) { |
+ throw new IllegalArgumentException('IsInstanceOf requires type'); |
+ } |
+ } |
+ |
+ bool matches(item) => _expected_type.isInstance(item); |
+ |
+ // The description here is lame :-( |
Bob Nystrom
2012/05/30 23:23:51
Why?
gram
2012/06/01 17:33:15
Because we don't have the type name unless it is e
|
+ IDescription describe(IDescription description) => |
+ description.append('an instance of ${_expected_type.toString()}'); |
Bob Nystrom
2012/05/30 23:23:51
You don't need .toString() here. Interpolation doe
gram
2012/06/01 17:33:15
Done.
|
+} |
+ |
+/** |
+ * Returns a matcher that matches if an object has a length property |
+ * that matches [matcher]. |
Bob Nystrom
2012/05/30 23:23:51
This doesn't seem to add a lot of value. Why not j
gram
2012/06/01 17:33:15
You can make this a bit shorter, with:
expect(foo
Bob Nystrom
2012/06/01 18:22:22
I like terseness, but I also like simplicity and m
|
+ */ |
+IMatcher hasLength([matcher = null]) => |
+ new _HasLength(matcher == null ? anything() : wrapMatcher(matcher)); |
+ |
+// TODO(gram) - this is intended to fail if the value |
+// does not have a length property but it doesn't. See if this |
+// can be fixed. |
+class _HasLength extends Matcher { |
+ |
+ var _len_matcher; |
Bob Nystrom
2012/05/30 23:23:51
"_lenMatcher"
gram
2012/06/01 17:33:15
Done.
|
+ |
+ _HasLength(Matcher this._len_matcher) { } |
+ bool matches(item) { |
+ try { |
+ // TODO(gram): this hack to throw if no length property is not |
+ // working |
+ if ((item.length * item.length) >= 0) { |
+ return _len_matcher.matches(item.length); |
+ } |
+ } catch(var e) { |
+ return false; |
+ } |
+ } |
+ |
+ IDescription describeMismatch(item, IDescription mismatchDescription) { |
+ super.describeMismatch(item, mismatchDescription); |
+ try { |
+ // TODO(gram): this hack to throw if no length property is not |
+ // working |
+ if ((item.length * item.length) >= 0) { |
+ return mismatchDescription.append(' with length of '). |
+ appendDescriptionOf(item.length); |
+ } |
+ } catch (var e) { |
+ return mismatchDescription.append(' has no length property'); |
+ } |
+ } |
+ |
+ IDescription describe(IDescription description) => |
+ description.append('an object with length of '). |
+ appendDescriptionOf(_len_matcher); |
+} |
+ |
+/** Returns a matcher that matches if value.toString() matches [matcher] */ |
+IMatcher hasString(matcher) => new _HasString(wrapMatcher(matcher)); |
Bob Nystrom
2012/05/30 23:23:51
This name is confusing to me, and it doesn't seem
gram
2012/06/01 17:33:15
Done.
|
+ |
+class _HasString extends Matcher { |
+ var _str_matcher; |
+ |
+ _HasString(this._str_matcher); |
+ |
+ bool matches(item) => _str_matcher.matches(item.toString()); |
+ |
+ IDescription describe(IDescription description) => |
+ description.append('with toString() value '). |
+ appendDescriptionOf(_str_matcher); |
+} |
+ |
+/** |
+ * Returns a matcher that does a deep recursive match. This only works |
+ * with built-in types. Note that cyclic structures will never terminate! |
Bob Nystrom
2012/05/30 23:23:51
Wouldn't this also work with user-defined types th
gram
2012/06/01 17:33:15
I made it a bit more general, and added an item co
|
+ */ |
+IMatcher recursivelyMatches(obj) => new _DeepMatcher(obj); |
+ |
+class _DeepMatcher extends Matcher { |
+ var _obj; |
+ |
+ _DeepMatcher(this._obj) {} |
+ |
+ String _recursiveMatch(expected, actual, String location) { |
+ String reason = null; |
+ if (expected is Collection) { |
+ if (!(actual is Collection)) { |
+ reason = 'expected a collection'; |
+ } else if (expected.length != actual.length) { |
+ reason = 'different collection lengths'; |
+ } else { |
+ for (var i = 0; i < expected.length; i++) { |
+ reason = _recursiveMatch(expected[i], actual[i], |
+ 'at position ${i} ${location}'); |
Bob Nystrom
2012/05/30 23:23:51
Indent another 2 spaces. Continued lines are inden
gram
2012/06/01 17:33:15
Done.
|
+ if (reason != null) { |
+ break; |
+ } |
+ } |
+ } |
+ } else if (expected is Map) { |
+ if (!(actual is Map)) { |
Bob Nystrom
2012/05/30 23:23:51
actual is !Map
gram
2012/06/01 17:33:15
Done.
|
+ reason = 'expected a map'; |
+ } else if (expected.length != actual.length) { |
+ reason = 'different map lengths'; |
+ } else { |
+ for (var key in expected.getKeys()) { |
+ if (!actual.containsKey(key)) { |
+ reason = 'missing map key ${key}'; |
+ break; |
+ } |
+ reason = _recursiveMatch(expected[key], actual[key], |
+ 'with key ${key} ${location}'); |
+ if (reason != null) { |
+ break; |
+ } |
+ } |
+ } |
+ } else { |
+ if (expected != actual) { |
+ reason = 'expected ${expected} but got ${actual}'; |
+ } |
+ } |
+ if (reason == null) { |
+ return null; |
+ } else { |
+ return '${reason} ${location}'; |
+ } |
+ } |
+ |
+ bool matches(item) => (_recursiveMatch(_obj, item, '') == null); |
+ |
+ IDescription describe(IDescription description) => |
+ description.append('recursively matches '). |
+ appendDescriptionOf(_obj); |
+ |
+ |
+ IDescription describeMismatch(item, IDescription mismatchDescription) => |
+ mismatchDescription.append(_recursiveMatch(_obj, item, '')); |
+} |
+ |
+ |
+ |