blob: ae0b0ad72af204b2b60e4bdeb36ef64deb36a1d4 [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.
library matcher.numeric_matchers;
import 'interfaces.dart';
/// Returns a matcher which matches if the match argument is greater
/// than the given [value].
Matcher greaterThan(value) =>
new _OrderingComparison(value, false, false, true, 'a value greater than');
/// Returns a matcher which matches if the match argument is greater
/// than or equal to the given [value].
Matcher greaterThanOrEqualTo(value) =>
new _OrderingComparison(value, true, false, true,
'a value greater than or equal to');
/// Returns a matcher which matches if the match argument is less
/// than the given [value].
Matcher lessThan(value) =>
new _OrderingComparison(value, false, true, false, 'a value less than');
/// Returns a matcher which matches if the match argument is less
/// than or equal to the given [value].
Matcher lessThanOrEqualTo(value) =>
new _OrderingComparison(value, true, true, false,
'a value less than or equal to');
/// A matcher which matches if the match argument is zero.
const Matcher isZero =
const _OrderingComparison(0, true, false, false, 'a value equal to');
/// A matcher which matches if the match argument is non-zero.
const Matcher isNonZero =
const _OrderingComparison(0, false, true, true, 'a value not equal to');
/// A matcher which matches if the match argument is positive.
const Matcher isPositive =
const _OrderingComparison(0, false, false, true, 'a positive value', false);
/// A matcher which matches if the match argument is zero or negative.
const Matcher isNonPositive =
const _OrderingComparison(0, true, true, false,
'a non-positive value', false);
/// A matcher which matches if the match argument is negative.
const Matcher isNegative =
const _OrderingComparison(0, false, true, false, 'a negative value', false);
/// A matcher which matches if the match argument is zero or positive.
const Matcher isNonNegative =
const _OrderingComparison(0, true, false, true,
'a non-negative value', false);
bool _isNumeric(value) {
return value is num;
}
// TODO(kevmoo) Note that matchers that use _OrderingComparison only use
// `==` and `<` operators to evaluate the match. Or change the matcher.
class _OrderingComparison extends Matcher {
/// Expected value.
final _value;
/// What to return if actual == expected
final bool _equalValue;
/// What to return if actual < expected
final bool _lessThanValue;
/// What to return if actual > expected
final bool _greaterThanValue;
/// Textual name of the inequality
final String _comparisonDescription;
/// Whether to include the expected value in the description
final bool _valueInDescription;
const _OrderingComparison(
this._value,
this._equalValue,
this._lessThanValue,
this._greaterThanValue,
this._comparisonDescription,
[bool valueInDescription = true]) :
this._valueInDescription = valueInDescription;
bool matches(item, Map matchState) {
if (item == _value) {
return _equalValue;
} else if (item < _value) {
return _lessThanValue;
} else {
return _greaterThanValue;
}
}
Description describe(Description description) {
if (_valueInDescription) {
return description.add(_comparisonDescription).add(' ').
addDescriptionOf(_value);
} else {
return description.add(_comparisonDescription);
}
}
Description describeMismatch(item, Description mismatchDescription,
Map matchState, bool verbose) {
mismatchDescription.add('is not ');
return describe(mismatchDescription);
}
}
/// Returns a matcher which matches if the match argument is within [delta]
/// of some [value]; i.e. if the match argument is greater than
/// than or equal [value]-[delta] and less than or equal to [value]+[delta].
Matcher closeTo(num value, num delta) => new _IsCloseTo(value, delta);
class _IsCloseTo extends Matcher {
final num _value, _delta;
const _IsCloseTo(this._value, this._delta);
bool matches(item, Map matchState) {
if (!_isNumeric(item)) {
return false;
}
var diff = item - _value;
if (diff < 0) diff = -diff;
return (diff <= _delta);
}
Description describe(Description description) =>
description.add('a numeric value within ').
addDescriptionOf(_delta).
add(' of ').
addDescriptionOf(_value);
Description describeMismatch(item, Description mismatchDescription,
Map matchState, bool verbose) {
if (item is !num) {
return mismatchDescription.add(' not numeric');
} else {
var diff = item - _value;
if (diff < 0) diff = -diff;
return mismatchDescription.
add(' differs by ').
addDescriptionOf(diff);
}
}
}
/// Returns a matcher which matches if the match argument is greater
/// than or equal to [low] and less than or equal to [high].
Matcher inInclusiveRange(num low, num high) =>
new _InRange(low, high, true, true);
/// Returns a matcher which matches if the match argument is greater
/// than [low] and less than [high].
Matcher inExclusiveRange(num low, num high) =>
new _InRange(low, high, false, false);
/// Returns a matcher which matches if the match argument is greater
/// than [low] and less than or equal to [high].
Matcher inOpenClosedRange(num low, num high) =>
new _InRange(low, high, false, true);
/// Returns a matcher which matches if the match argument is greater
/// than or equal to a [low] and less than [high].
Matcher inClosedOpenRange(num low, num high) =>
new _InRange(low, high, true, false);
class _InRange extends Matcher {
final num _low, _high;
final bool _lowMatchValue, _highMatchValue;
const _InRange(this._low, this._high,
this._lowMatchValue, this._highMatchValue);
bool matches(value, Map matchState) {
if (value is! num) {
return false;
}
if (value < _low || value > _high) {
return false;
}
if (value == _low) {
return _lowMatchValue;
}
if (value == _high) {
return _highMatchValue;
}
return true;
}
Description describe(Description description) =>
description.add("be in range from "
"$_low (${_lowMatchValue ? 'inclusive' : 'exclusive'}) to "
"$_high (${_highMatchValue ? 'inclusive' : 'exclusive'})");
Description describeMismatch(item, Description mismatchDescription,
Map matchState, bool verbose) {
if (item is !num) {
return mismatchDescription.
addDescriptionOf(item).
add(' not numeric');
} else {
return super.describeMismatch(item, mismatchDescription,
matchState, verbose);
}
}
}