mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
add within
matcher for comparing metric-space values (#12908)
This commit is contained in:
parent
b04b89ac95
commit
a75f003b9c
@ -73,14 +73,14 @@ void main() {
|
||||
|
||||
MaterialPointArcTween tween = new MaterialPointArcTween(begin: begin, end: end);
|
||||
expect(tween.lerp(0.0), begin);
|
||||
expect((tween.lerp(0.25) - const Offset(126.0, 120.0)).distance, closeTo(0.0, 2.0));
|
||||
expect((tween.lerp(0.75) - const Offset(48.0, 196.0)).distance, closeTo(0.0, 2.0));
|
||||
expect(tween.lerp(0.25), within<Offset>(distance: 2.0, from: const Offset(126.0, 120.0)));
|
||||
expect(tween.lerp(0.75), within<Offset>(distance: 2.0, from: const Offset(48.0, 196.0)));
|
||||
expect(tween.lerp(1.0), end);
|
||||
|
||||
tween = new MaterialPointArcTween(begin: end, end: begin);
|
||||
expect(tween.lerp(0.0), end);
|
||||
expect((tween.lerp(0.25) - const Offset(91.0, 239.0)).distance, closeTo(0.0, 2.0));
|
||||
expect((tween.lerp(0.75) - const Offset(168.3, 163.8)).distance, closeTo(0.0, 2.0));
|
||||
expect(tween.lerp(0.25), within<Offset>(distance: 2.0, from: const Offset(91.0, 239.0)));
|
||||
expect(tween.lerp(0.75), within<Offset>(distance: 2.0, from: const Offset(168.3, 163.8)));
|
||||
expect(tween.lerp(1.0), begin);
|
||||
});
|
||||
|
||||
|
@ -142,7 +142,7 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('Shadow colors animate smoothly', (WidgetTester tester) async {
|
||||
// This code verifies that the PhysicalModel's elevation animates over
|
||||
// This code verifies that the PhysicalModel's shadowColor animates over
|
||||
// a kThemeChangeDuration time interval.
|
||||
|
||||
await tester.pumpWidget(buildMaterial(shadowColor: const Color(0xFF00FF00)));
|
||||
@ -155,17 +155,11 @@ void main() {
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 1));
|
||||
final RenderPhysicalModel modelC = getShadow(tester);
|
||||
expect(modelC.shadowColor.alpha, equals(0xFF));
|
||||
expect(modelC.shadowColor.red, closeTo(0x00, 1));
|
||||
expect(modelC.shadowColor.green, closeTo(0xFF, 1));
|
||||
expect(modelC.shadowColor.blue, equals(0x00));
|
||||
expect(modelC.shadowColor, within<Color>(distance: 1, from: const Color(0xFF00FF00)));
|
||||
|
||||
await tester.pump(kThemeChangeDuration ~/ 2);
|
||||
final RenderPhysicalModel modelD = getShadow(tester);
|
||||
expect(modelD.shadowColor.alpha, equals(0xFF));
|
||||
expect(modelD.shadowColor.red, isNot(closeTo(0x00, 1)));
|
||||
expect(modelD.shadowColor.green, isNot(closeTo(0xFF, 1)));
|
||||
expect(modelD.shadowColor.blue, equals(0x00));
|
||||
expect(modelD.shadowColor, isNot(within<Color>(distance: 1, from: const Color(0xFF00FF00))));
|
||||
|
||||
await tester.pump(kThemeChangeDuration);
|
||||
final RenderPhysicalModel modelE = getShadow(tester);
|
||||
|
@ -1104,17 +1104,17 @@ void main() {
|
||||
await tester.pump(duration * 0.25);
|
||||
Offset actualHeroCenter = tester.getCenter(find.byKey(secondKey));
|
||||
Offset predictedHeroCenter = pushCenterTween.lerp(curve.transform(0.25));
|
||||
expect((actualHeroCenter - predictedHeroCenter).distance, closeTo(0.0, epsilon));
|
||||
expect(actualHeroCenter, within<Offset>(distance: epsilon, from: predictedHeroCenter));
|
||||
|
||||
await tester.pump(duration * 0.25);
|
||||
actualHeroCenter = tester.getCenter(find.byKey(secondKey));
|
||||
predictedHeroCenter = pushCenterTween.lerp(curve.transform(0.5));
|
||||
expect((actualHeroCenter - predictedHeroCenter).distance, closeTo(0.0, epsilon));
|
||||
expect(actualHeroCenter, within<Offset>(distance: epsilon, from: predictedHeroCenter));
|
||||
|
||||
await tester.pump(duration * 0.25);
|
||||
actualHeroCenter = tester.getCenter(find.byKey(secondKey));
|
||||
predictedHeroCenter = pushCenterTween.lerp(curve.transform(0.75));
|
||||
expect((actualHeroCenter - predictedHeroCenter).distance, closeTo(0.0, epsilon));
|
||||
expect(actualHeroCenter, within<Offset>(distance: epsilon, from: predictedHeroCenter));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(tester.getCenter(find.byKey(secondKey)), const Offset(400.0, 300.0));
|
||||
@ -1135,17 +1135,17 @@ void main() {
|
||||
await tester.pump(duration * 0.25);
|
||||
actualHeroCenter = tester.getCenter(find.byKey(firstKey));
|
||||
predictedHeroCenter = popCenterTween.lerp(curve.flipped.transform(0.25));
|
||||
expect((actualHeroCenter - predictedHeroCenter).distance, closeTo(0.0, epsilon));
|
||||
expect(actualHeroCenter, within<Offset>(distance: epsilon, from: predictedHeroCenter));
|
||||
|
||||
await tester.pump(duration * 0.25);
|
||||
actualHeroCenter = tester.getCenter(find.byKey(firstKey));
|
||||
predictedHeroCenter = popCenterTween.lerp(curve.flipped.transform(0.5));
|
||||
expect((actualHeroCenter - predictedHeroCenter).distance, closeTo(0.0, epsilon));
|
||||
expect(actualHeroCenter, within<Offset>(distance: epsilon, from: predictedHeroCenter));
|
||||
|
||||
await tester.pump(duration * 0.25);
|
||||
actualHeroCenter = tester.getCenter(find.byKey(firstKey));
|
||||
predictedHeroCenter = popCenterTween.lerp(curve.flipped.transform(0.75));
|
||||
expect((actualHeroCenter - predictedHeroCenter).distance, closeTo(0.0, epsilon));
|
||||
expect(actualHeroCenter, within<Offset>(distance: epsilon, from: predictedHeroCenter));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(tester.getCenter(find.byKey(firstKey)), const Offset(50.0, 50.0));
|
||||
|
@ -2,7 +2,10 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'finders.dart';
|
||||
@ -565,6 +568,105 @@ class _HasGoodToStringDeep extends Matcher {
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the distance between two values.
|
||||
///
|
||||
/// The distance should be a metric in a metric space (see
|
||||
/// https://en.wikipedia.org/wiki/Metric_space). Specifically, if `f` is a
|
||||
/// distance function then the following conditions should hold:
|
||||
///
|
||||
/// - f(a, b) >= 0
|
||||
/// - f(a, b) == 0 if and only if a == b
|
||||
/// - f(a, b) == f(b, a)
|
||||
/// - f(a, c) <= f(a, b) + f(b, c), known as triangle inequality
|
||||
///
|
||||
/// This makes it useful for comparing numbers, [Color]s, [Offset]s and other
|
||||
/// sets of value for which a metric space is defined.
|
||||
typedef num DistanceFunction<T>(T a, T b);
|
||||
|
||||
const Map<Type, DistanceFunction<dynamic>> _kStandardDistanceFunctions = const <Type, DistanceFunction<dynamic>>{
|
||||
Color: _maxComponentColorDistance,
|
||||
Offset: _offsetDistance,
|
||||
int: _intDistance,
|
||||
double: _doubleDistance,
|
||||
};
|
||||
|
||||
int _intDistance(int a, int b) => (b - a).abs();
|
||||
double _doubleDistance(double a, double b) => (b - a).abs();
|
||||
double _offsetDistance(Offset a, Offset b) => (b - a).distance;
|
||||
|
||||
double _maxComponentColorDistance(Color a, Color b) {
|
||||
int delta = math.max<int>((a.red - b.red).abs(), (a.green - b.green).abs());
|
||||
delta = math.max<int>(delta, (a.blue - b.blue).abs());
|
||||
delta = math.max<int>(delta, (a.alpha - b.alpha).abs());
|
||||
return delta.toDouble();
|
||||
}
|
||||
|
||||
/// Asserts that two values are within a certain distance from each other.
|
||||
///
|
||||
/// The distance is computed by a [DistanceFunction].
|
||||
///
|
||||
/// If `distanceFunction` is null, a standard distance function is used for the
|
||||
/// `runtimeType` of the `from` argument. Standard functions are defined for
|
||||
/// the following types:
|
||||
///
|
||||
/// * [Color], whose distance is the maximum component-wise delta.
|
||||
/// * [Offset], whose distance is the Euclidean distance computed using the
|
||||
/// method [Offset.distance].
|
||||
/// * [int], whose distance is the absolute difference between two integers.
|
||||
/// * [double], whose distance is the absolute difference between two doubles.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [moreOrLessEquals], which is similar to this function, but specializes in
|
||||
/// [double]s and has an optional `epsilon` parameter.
|
||||
/// * [closeTo], which specializes in numbers only.
|
||||
Matcher within<T>({
|
||||
@required num distance,
|
||||
@required T from,
|
||||
DistanceFunction<T> distanceFunction,
|
||||
}) {
|
||||
distanceFunction ??= _kStandardDistanceFunctions[from.runtimeType];
|
||||
|
||||
if (distanceFunction == null) {
|
||||
throw new ArgumentError(
|
||||
'The specified distanceFunction was null, and a standard distance '
|
||||
'function was not found for type ${from.runtimeType} of the provided '
|
||||
'`from` argument.'
|
||||
);
|
||||
}
|
||||
|
||||
return new _IsWithinDistance<T>(distanceFunction, from, distance);
|
||||
}
|
||||
|
||||
class _IsWithinDistance<T> extends Matcher {
|
||||
const _IsWithinDistance(this.distanceFunction, this.value, this.epsilon);
|
||||
|
||||
final DistanceFunction<T> distanceFunction;
|
||||
final T value;
|
||||
final num epsilon;
|
||||
|
||||
@override
|
||||
bool matches(Object object, Map<dynamic, dynamic> matchState) {
|
||||
if (object is! T)
|
||||
return false;
|
||||
if (object == value)
|
||||
return true;
|
||||
final T test = object;
|
||||
final num distance = distanceFunction(test, value);
|
||||
if (distance < 0) {
|
||||
throw new ArgumentError(
|
||||
'Invalid distance function was used to compare a ${value.runtimeType} '
|
||||
'to a ${object.runtimeType}. The function must return a non-negative '
|
||||
'double value, but it returned $distance.'
|
||||
);
|
||||
}
|
||||
return distance <= epsilon;
|
||||
}
|
||||
|
||||
@override
|
||||
Description describe(Description description) => description.add('$value (±$epsilon)');
|
||||
}
|
||||
|
||||
class _MoreOrLessEquals extends Matcher {
|
||||
const _MoreOrLessEquals(this.value, this.epsilon);
|
||||
|
||||
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
/// Class that makes it easy to mock common toStringDeep behavior.
|
||||
@ -179,4 +181,32 @@ void main() {
|
||||
expect(11.0, moreOrLessEquals(-11.0, epsilon: 100.0));
|
||||
expect(-11.0, moreOrLessEquals(11.0, epsilon: 100.0));
|
||||
});
|
||||
|
||||
test('within', () {
|
||||
expect(0.0, within<double>(distance: 0.1, from: 0.05));
|
||||
expect(0.0, isNot(within<double>(distance: 0.1, from: 0.2)));
|
||||
|
||||
expect(0, within<int>(distance: 1, from: 1));
|
||||
expect(0, isNot(within<int>(distance: 1, from: 2)));
|
||||
|
||||
expect(const Color(0x00000000), within<Color>(distance: 1, from: const Color(0x01000000)));
|
||||
expect(const Color(0x00000000), within<Color>(distance: 1, from: const Color(0x00010000)));
|
||||
expect(const Color(0x00000000), within<Color>(distance: 1, from: const Color(0x00000100)));
|
||||
expect(const Color(0x00000000), within<Color>(distance: 1, from: const Color(0x00000001)));
|
||||
expect(const Color(0x00000000), within<Color>(distance: 1, from: const Color(0x01010101)));
|
||||
expect(const Color(0x00000000), isNot(within<Color>(distance: 1, from: const Color(0x02000000))));
|
||||
|
||||
expect(const Offset(1.0, 0.0), within(distance: 1.0, from: const Offset(0.0, 0.0)));
|
||||
expect(const Offset(1.0, 0.0), isNot(within(distance: 1.0, from: const Offset(-1.0, 0.0))));
|
||||
|
||||
expect(
|
||||
() => within<bool>(distance: 1, from: false),
|
||||
throwsArgumentError,
|
||||
);
|
||||
|
||||
expect(
|
||||
() => within<int>(distance: 1, from: 2, distanceFunction: (int a, int b) => -1).matches(1, <dynamic, dynamic>{}),
|
||||
throwsArgumentError,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user