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 'package:stack_trace/stack_trace.dart'; 6 : 7 : import 'description.dart'; 8 : import 'interfaces.dart'; 9 : import 'util.dart'; 10 : 11 : /// A useful utility class for implementing other matchers through inheritance. 12 : /// Derived classes should call the base constructor with a feature name and 13 : /// description, and an instance matcher, and should implement the 14 : /// [featureValueOf] abstract method. 15 : /// 16 : /// The feature description will typically describe the item and the feature, 17 : /// while the feature name will just name the feature. For example, we may 18 : /// have a Widget class where each Widget has a price; we could make a 19 : /// [CustomMatcher] that can make assertions about prices with: 20 : /// 21 : /// ```dart 22 : /// class HasPrice extends CustomMatcher { 23 : /// HasPrice(matcher) : super("Widget with price that is", "price", matcher); 24 : /// featureValueOf(actual) => (actual as Widget).price; 25 : /// } 26 : /// ``` 27 : /// 28 : /// and then use this for example like: 29 : /// 30 : /// ```dart 31 : /// expect(inventoryItem, HasPrice(greaterThan(0))); 32 : /// ``` 33 : class CustomMatcher extends Matcher { 34 : final String _featureDescription; 35 : final String _featureName; 36 : final Matcher _matcher; 37 : 38 0 : CustomMatcher( 39 : this._featureDescription, this._featureName, Object? valueOrMatcher) 40 0 : : _matcher = wrapMatcher(valueOrMatcher); 41 : 42 : /// Override this to extract the interesting feature. 43 0 : Object? featureValueOf(dynamic actual) => actual; 44 : 45 0 : @override 46 : bool matches(Object? item, Map matchState) { 47 : try { 48 0 : var f = featureValueOf(item); 49 0 : if (_matcher.matches(f, matchState)) return true; 50 0 : addStateInfo(matchState, {'custom.feature': f}); 51 : } catch (exception, stack) { 52 0 : addStateInfo(matchState, { 53 0 : 'custom.exception': exception.toString(), 54 0 : 'custom.stack': Chain.forTrace(stack) 55 0 : .foldFrames( 56 0 : (frame) => 57 0 : frame.package == 'test' || 58 0 : frame.package == 'stream_channel' || 59 0 : frame.package == 'matcher', 60 : terse: true) 61 0 : .toString() 62 : }); 63 : } 64 : return false; 65 : } 66 : 67 0 : @override 68 : Description describe(Description description) => 69 0 : description.add(_featureDescription).add(' ').addDescriptionOf(_matcher); 70 : 71 0 : @override 72 : Description describeMismatch(Object? item, Description mismatchDescription, 73 : Map matchState, bool verbose) { 74 0 : if (matchState['custom.exception'] != null) { 75 : mismatchDescription 76 0 : .add('threw ') 77 0 : .addDescriptionOf(matchState['custom.exception']) 78 0 : .add('\n') 79 0 : .add(matchState['custom.stack'].toString()); 80 : return mismatchDescription; 81 : } 82 : 83 : mismatchDescription 84 0 : .add('has ') 85 0 : .add(_featureName) 86 0 : .add(' with value ') 87 0 : .addDescriptionOf(matchState['custom.feature']); 88 0 : var innerDescription = StringDescription(); 89 : 90 0 : _matcher.describeMismatch(matchState['custom.feature'], innerDescription, 91 0 : matchState['state'] as Map, verbose); 92 : 93 0 : if (innerDescription.length > 0) { 94 0 : mismatchDescription.add(' which ').add(innerDescription.toString()); 95 : } 96 : return mismatchDescription; 97 : } 98 : }