-
Notifications
You must be signed in to change notification settings - Fork 218
Return a Future from expect(). #529
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// Copyright (c) 2017, 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 'dart:async'; | ||
|
||
import 'package:matcher/matcher.dart'; | ||
|
||
import '../backend/invoker.dart'; | ||
import 'expect.dart'; | ||
|
||
/// A matcher that does asynchronous computation. | ||
/// | ||
/// Rather than implementing [matches], subclasses implement [matchAsync]. | ||
/// [AsyncMatcher.matches] ensures that the test doesn't complete until the | ||
/// returned future completes, and [expect] returns a future that completes when | ||
/// the returned future completes so that tests can wait for it. | ||
abstract class AsyncMatcher extends Matcher { | ||
const AsyncMatcher(); | ||
|
||
/// Returns `null` if this matches [item], or a [String] description of the | ||
/// failure if it doesn't match. | ||
/// | ||
/// This can return a [Future] or a synchronous value. If it returns a | ||
/// [Future], neither [expect] nor the test will complete until that [Future] | ||
/// completes. | ||
/// | ||
/// If this returns a [String] synchronously, [expect] will synchronously | ||
/// throw a [TestFailure] and [matches] will synchronusly return `false`. | ||
/*FutureOr<String>*/ matchAsync(item); | ||
|
||
bool matches(item, Map matchState) { | ||
var result = matchAsync(item); | ||
expect(result, anyOf([ | ||
equals(null), | ||
new isInstanceOf<Future>(), | ||
new isInstanceOf<String>() | ||
]), reason: "matchAsync() may only return a String, a Future, or null."); | ||
|
||
if (result is Future) { | ||
Invoker.current.addOutstandingCallback(); | ||
result.then((realResult) { | ||
if (realResult != null) fail(formatFailure(this, item, realResult)); | ||
Invoker.current.removeOutstandingCallback(); | ||
}); | ||
} else if (result is String) { | ||
matchState[this] = result; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
Description describeMismatch( | ||
item, Description description, Map matchState, bool verbose) => | ||
new StringDescription(matchState[this]); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,7 +6,8 @@ import 'dart:async'; | |
|
||
import 'package:matcher/matcher.dart'; | ||
|
||
import '../backend/invoker.dart'; | ||
import '../utils.dart'; | ||
import 'async_matcher.dart'; | ||
import 'expect.dart'; | ||
|
||
/// Matches a [Future] that completes successfully with a value. | ||
|
@@ -17,6 +18,9 @@ import 'expect.dart'; | |
/// | ||
/// To test that a Future completes with an exception, you can use [throws] and | ||
/// [throwsA]. | ||
/// | ||
/// This returns an [AsyncMatcher], so [expect] won't complete until the matched | ||
/// future does. | ||
final Matcher completes = const _Completes(null); | ||
|
||
/// Matches a [Future] that completes succesfully with a value that matches | ||
|
@@ -30,24 +34,41 @@ final Matcher completes = const _Completes(null); | |
/// [throwsA]. | ||
/// | ||
/// The [description] parameter is deprecated and shouldn't be used. | ||
/// | ||
/// This returns an [AsyncMatcher], so [expect] won't complete until the matched | ||
/// future does. | ||
Matcher completion(matcher, [@deprecated String description]) => | ||
new _Completes(wrapMatcher(matcher)); | ||
|
||
class _Completes extends Matcher { | ||
class _Completes extends AsyncMatcher { | ||
final Matcher _matcher; | ||
|
||
const _Completes(this._matcher); | ||
|
||
bool matches(item, Map matchState) { | ||
if (item is! Future) return false; | ||
Invoker.current.addOutstandingCallback(); | ||
// Avoid async/await so we synchronously start listening to [item]. | ||
/*FutureOr<String>*/ matchAsync(item) { | ||
if (item is! Future) return "was not a Future"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why this string here but null below? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A string means failure, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. gotcha. can you add that to the comments? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh. right. I can read. Carry on then! |
||
|
||
item.then((value) { | ||
if (_matcher != null) expect(value, _matcher); | ||
Invoker.current.removeOutstandingCallback(); | ||
}); | ||
return item.then((value) async { | ||
if (_matcher == null) return null; | ||
|
||
return true; | ||
String result; | ||
if (_matcher is AsyncMatcher) { | ||
result = await (_matcher as AsyncMatcher).matchAsync(value); | ||
if (result == null) return null; | ||
} else { | ||
var matchState = {}; | ||
if (_matcher.matches(value, matchState)) return null; | ||
result = _matcher | ||
.describeMismatch(value, new StringDescription(), matchState, false) | ||
.toString(); | ||
} | ||
|
||
var buffer = new StringBuffer(); | ||
buffer.writeln(indent(prettyPrint(value), first: 'emitted ')); | ||
if (result.isNotEmpty) buffer.writeln(indent(result, first: ' which ')); | ||
return buffer.toString().trimRight(); | ||
}); | ||
} | ||
|
||
Description describe(Description description) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
did you want to indent the "reason" line too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, the extra whitespace is just to visually verify that the
first
prefixes are all the same width.