Line data Source code
1 : // Copyright (c) 2018, 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 'having_matcher.dart'; 6 : import 'interfaces.dart'; 7 : 8 : /// Returns a matcher that matches objects with type [T]. 9 : /// 10 : /// ```dart 11 : /// expect(shouldBeDuration, isA<Duration>()); 12 : /// ``` 13 : /// 14 : /// Expectations can be chained on top of the type using the 15 : /// [TypeMatcher.having] method to add additional constraints. 16 0 : TypeMatcher<T> isA<T>() => TypeMatcher<T>(); 17 : 18 : /// A [Matcher] subclass that supports validating the [Type] of the target 19 : /// object. 20 : /// 21 : /// ```dart 22 : /// expect(shouldBeDuration, TypeMatcher<Duration>()); 23 : /// ``` 24 : /// 25 : /// If you want to further validate attributes of the specified [Type], use the 26 : /// [having] function. 27 : /// 28 : /// ```dart 29 : /// void shouldThrowRangeError(int value) { 30 : /// throw RangeError.range(value, 10, 20); 31 : /// } 32 : /// 33 : /// expect( 34 : /// () => shouldThrowRangeError(5), 35 : /// throwsA(const TypeMatcher<RangeError>() 36 : /// .having((e) => e.start, 'start', greaterThanOrEqualTo(10)) 37 : /// .having((e) => e.end, 'end', lessThanOrEqualTo(20)))); 38 : /// ``` 39 : /// 40 : /// Notice that you can chain multiple calls to [having] to verify multiple 41 : /// aspects of an object. 42 : /// 43 : /// Note: All of the top-level `isType` matchers exposed by this package are 44 : /// instances of [TypeMatcher], so you can use the [having] function without 45 : /// creating your own instance. 46 : /// 47 : /// ```dart 48 : /// expect( 49 : /// () => shouldThrowRangeError(5), 50 : /// throwsA(isRangeError 51 : /// .having((e) => e.start, 'start', greaterThanOrEqualTo(10)) 52 : /// .having((e) => e.end, 'end', lessThanOrEqualTo(20)))); 53 : /// ``` 54 : class TypeMatcher<T> extends Matcher { 55 : final String? _name; 56 : 57 : /// Create a matcher matches instances of type [T]. 58 : /// 59 : /// For a fluent API to create TypeMatchers see [isA]. 60 34 : const TypeMatcher( 61 : [@Deprecated('Provide a type argument to TypeMatcher and omit the name. ' 62 : 'This argument will be removed in the next release.') 63 : String? name]) 64 : : _name = 65 : // ignore: deprecated_member_use_from_same_package 66 : name; 67 : 68 : /// Returns a new [TypeMatcher] that validates the existing type as well as 69 : /// a specific [feature] of the object with the provided [matcher]. 70 : /// 71 : /// Provides a human-readable [description] of the [feature] to make debugging 72 : /// failures easier. 73 : /// 74 : /// ```dart 75 : /// /// Validates that the object is a [RangeError] with a message containing 76 : /// /// the string 'details' and `start` and `end` properties that are `null`. 77 : /// final _rangeMatcher = isRangeError 78 : /// .having((e) => e.message, 'message', contains('details')) 79 : /// .having((e) => e.start, 'start', isNull) 80 : /// .having((e) => e.end, 'end', isNull); 81 : /// ``` 82 0 : TypeMatcher<T> having( 83 : Object? Function(T) feature, String description, dynamic matcher) => 84 0 : HavingMatcher(this, description, feature, matcher); 85 : 86 0 : @override 87 : Description describe(Description description) { 88 0 : var name = _name ?? _stripDynamic(T); 89 0 : return description.add("<Instance of '$name'>"); 90 : } 91 : 92 8 : @override 93 8 : bool matches(Object? item, Map matchState) => item is T; 94 : 95 0 : @override 96 : Description describeMismatch(dynamic item, Description mismatchDescription, 97 : Map matchState, bool verbose) { 98 0 : var name = _name ?? _stripDynamic(T); 99 0 : return mismatchDescription.add("is not an instance of '$name'"); 100 : } 101 : } 102 : 103 0 : final _dart2DynamicArgs = RegExp('<dynamic(, dynamic)*>'); 104 : 105 : /// With this expression `{}.runtimeType.toString`, 106 : /// Dart 1: "<Instance of Map> 107 : /// Dart 2: "<Instance of Map<dynamic, dynamic>>" 108 : /// 109 : /// This functions returns the Dart 1 output, when Dart 2 runtime semantics 110 : /// are enabled. 111 0 : String _stripDynamic(Type type) => 112 0 : type.toString().replaceAll(_dart2DynamicArgs, '');