blob: a37933dd77ffed4ab38c803cb4f212979de85468 [file] [log] [blame]
// 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.
import 'package:stack_trace/stack_trace.dart';
import 'description.dart';
import 'interfaces.dart';
import 'util.dart';
/// A useful utility class for implementing other matchers through inheritance.
/// Derived classes should call the base constructor with a feature name and
/// description, and an instance matcher, and should implement the
/// [featureValueOf] abstract method.
///
/// The feature description will typically describe the item and the feature,
/// while the feature name will just name the feature. For example, we may
/// have a Widget class where each Widget has a price; we could make a
/// [CustomMatcher] that can make assertions about prices with:
///
/// ```dart
/// class HasPrice extends CustomMatcher {
/// HasPrice(matcher) : super("Widget with price that is", "price", matcher);
/// featureValueOf(actual) => (actual as Widget).price;
/// }
/// ```
///
/// and then use this for example like:
///
/// ```dart
/// expect(inventoryItem, HasPrice(greaterThan(0)));
/// ```
class CustomMatcher extends Matcher {
final String _featureDescription;
final String _featureName;
final Matcher _matcher;
CustomMatcher(this._featureDescription, this._featureName, matcher)
: _matcher = wrapMatcher(matcher);
/// Override this to extract the interesting feature.
Object featureValueOf(actual) => actual;
@override
bool matches(item, Map matchState) {
try {
var f = featureValueOf(item);
if (_matcher.matches(f, matchState)) return true;
addStateInfo(matchState, {'custom.feature': f});
} catch (exception, stack) {
addStateInfo(matchState, {
'custom.exception': exception.toString(),
'custom.stack': Chain.forTrace(stack)
.foldFrames(
(frame) =>
frame.package == 'test' ||
frame.package == 'stream_channel' ||
frame.package == 'matcher',
terse: true)
.toString()
});
}
return false;
}
@override
Description describe(Description description) =>
description.add(_featureDescription).add(' ').addDescriptionOf(_matcher);
@override
Description describeMismatch(
item, Description mismatchDescription, Map matchState, bool verbose) {
if (matchState['custom.exception'] != null) {
mismatchDescription
.add('threw ')
.addDescriptionOf(matchState['custom.exception'])
.add('\n')
.add(matchState['custom.stack'].toString());
return mismatchDescription;
}
mismatchDescription
.add('has ')
.add(_featureName)
.add(' with value ')
.addDescriptionOf(matchState['custom.feature']);
var innerDescription = StringDescription();
_matcher.describeMismatch(matchState['custom.feature'], innerDescription,
matchState['state'] as Map, verbose);
if (innerDescription.length > 0) {
mismatchDescription.add(' which ').add(innerDescription.toString());
}
return mismatchDescription;
}
}