strange
Version:
Range aka interval object. Supports exclusive and infinite ranges. Comes with an interval tree (augmented binary search tree).
794 lines (652 loc) • 26.3 kB
JavaScript
var Range = require("..")
describe("Range", function() {
describe("new", function() {
it("must return an instance of Range", function() {
new Range().must.be.an.instanceof(Range)
})
it("must set begin and end with default bounds", function() {
var range = new Range(42, 69)
range.begin.must.equal(42)
range.end.must.equal(69)
range.bounds.must.equal("[]")
})
it("must set begin and end to undefined if not given", function() {
var range = new Range
range.must.have.property("begin", undefined)
range.must.have.property("end", undefined)
range.bounds.must.equal("[]")
})
;["[]", "()", "[)", "(]"].forEach(function(bounds) {
it("must set bounds given " + bounds, function() {
var range = new Range(42, 69, bounds)
range.begin.must.equal(42)
range.end.must.equal(69)
range.bounds.must.equal(bounds)
})
})
it("must throw RangeError given invalid bounds", function() {
var err
try { new Range(42, 69, ")(") } catch (ex) { err = ex }
err.must.be.an.error(RangeError, /bounds/i)
})
})
describe("when called as a function", function() {
it("must return an instance of Range", function() {
Range().must.be.an.instanceof(Range)
})
it("must set begin and end with given bounds", function() {
var range = Range(42, 69, "()")
range.begin.must.equal(42)
range.end.must.equal(69)
range.bounds.must.equal("()")
})
})
describe(".prototype", function() {
it("must be a valid range", function() {
Range.prototype.isEmpty().must.be.true()
Range.prototype.contains(new Range(0, 1)).must.be.false()
})
})
describe(".prototype.isEmpty", function() {
it("must return true given a range with undefined endpoints", function() {
new Range(undefined, undefined).isEmpty().must.be.true()
new Range(null, undefined).isEmpty().must.be.true()
new Range(undefined, null).isEmpty().must.be.true()
})
it("must return false given an unbounded range", function() {
new Range(null, null).isEmpty().must.be.false()
})
it("must return false given an unbounded exclusive range", function() {
new Range(null, null, "()").isEmpty().must.be.false()
})
it("must return true if exclusive with equivalent endpoints", function() {
new Range(1, 1, "()").isEmpty().must.be.true()
new Range(1, 1, "[)").isEmpty().must.be.true()
new Range(1, 1, "(]").isEmpty().must.be.true()
})
it("must return false if inclusive with equivalent endpoints", function() {
new Range(1, 1, "[]").isEmpty().must.be.false()
})
it("must return false if exclusive with non-equivalent endpoints",
function() {
new Range(1, 2, "()").isEmpty().must.be.false()
})
})
describe(".prototype.isBounded", function() {
it("must return true given a range with undefined endpoints", function() {
new Range(undefined, undefined).isBounded().must.be.true()
})
it("must return true given a range with undefined endpoints", function() {
new Range(null, undefined).isBounded().must.be.true()
new Range(undefined, null).isBounded().must.be.true()
})
it("must return false given an unbounded range", function() {
new Range(null, null).isBounded().must.be.false()
})
it("must return false given a left unbounded range", function() {
new Range(null, 1).isBounded().must.be.false()
})
it("must return false given a right unbounded range", function() {
new Range(1, null).isBounded().must.be.false()
})
it("must return false given an unbounded range with Infinity", function() {
new Range(-Infinity, Infinity).isBounded().must.be.false()
})
it("must return false given a left unbounded range with Infinity",
function() {
new Range(-Infinity, 1).isBounded().must.be.false()
})
it("must return false given a right unbounded range with Infinity",
function() {
new Range(1, Infinity).isBounded().must.be.false()
})
it("must return true given a bounded range", function() {
new Range(1, 1).isBounded().must.be.true()
})
})
describe(".prototype.isUnbounded", function() {
it("must return false given a range with undefined endpoints", function() {
new Range(undefined, undefined).isUnbounded().must.be.false()
})
it("must return false given a range with undefined endpoints", function() {
new Range(null, undefined).isUnbounded().must.be.false()
new Range(undefined, null).isUnbounded().must.be.false()
})
it("must return true given an unbounded range", function() {
new Range(null, null).isUnbounded().must.be.true()
})
it("must return true given a left unbounded range", function() {
new Range(null, 1).isUnbounded().must.be.true()
})
it("must return true given a right unbounded range", function() {
new Range(1, null).isUnbounded().must.be.true()
})
it("must return true given an unbounded range with Infinity", function() {
new Range(-Infinity, Infinity).isUnbounded().must.be.true()
})
it("must return true given a left unbounded range with Infinity",
function() {
new Range(-Infinity, 1).isUnbounded().must.be.true()
})
it("must return true given a right unbounded range with Infinity",
function() {
new Range(1, Infinity).isUnbounded().must.be.true()
})
it("must return false given a bounded range", function() {
new Range(1, 1).isUnbounded().must.be.false()
})
})
describe(".prototype.isFinite", function() {
it("must be an alias to Range.prototype.isBounded", function() {
Range.prototype.isFinite.must.equal(Range.prototype.isBounded)
})
})
describe(".prototype.isInfinite", function() {
it("must be an alias to Range.prototype.isInfinite", function() {
Range.prototype.isInfinite.must.equal(Range.prototype.isUnbounded)
})
})
describe(".prototype.compareBegin", function() {
describe("with inclusive bound", function() {
it("must return 0 if given endpoint equal", function() {
new Range(0, 10, "[)").compareBegin(0).must.equal(0)
})
it("must return 0 if unbounded and equal", function() {
new Range(null, 10, "[)").compareBegin(null).must.equal(0)
})
it("must return -1 if given endpoint greater than", function() {
new Range(0, 10, "[)").compareBegin(11).must.equal(-1)
})
it("must return -1 if unbounded", function() {
new Range(null, 10, "[)").compareBegin(11).must.equal(-1)
})
it("must return 1 if given endpoint less than", function() {
new Range(0, 10, "[)").compareBegin(-1).must.equal(1)
})
it("must return 1 given null", function() {
new Range(0, 10, "[)").compareBegin(null).must.equal(1)
})
})
describe("with exclusive bound", function() {
it("must return -1 if given endpoint greater than", function() {
new Range(0, 10, "(]").compareBegin(1).must.equal(-1)
})
it("must return -1 if unbounded", function() {
new Range(null, 10, "(]").compareBegin(11).must.equal(-1)
})
it("must return 1 if given endpoint equal", function() {
new Range(0, 10, "(]").compareBegin(0).must.equal(1)
})
it("must return 1 if unbounded and equal", function() {
new Range(null, 10, "(]").compareBegin(null).must.equal(1)
})
it("must return 1 if given endpoint less than", function() {
new Range(0, 10, "(]").compareBegin(-1).must.equal(1)
})
it("must return 1 given null", function() {
new Range(0, 10, "(]").compareBegin(null).must.equal(1)
})
})
})
describe(".prototype.compareEnd", function() {
describe("with inclusive bound", function() {
it("must return 0 if given endpoint equal", function() {
new Range(0, 10, "(]").compareEnd(10).must.equal(0)
})
it("must return 0 if unbounded and equal", function() {
new Range(0, null, "(]").compareEnd(null).must.equal(0)
})
it("must return -1 if given endpoint greater than", function() {
new Range(0, 10, "(]").compareEnd(11).must.equal(-1)
})
it("must return -1 if given null", function() {
new Range(0, 10, "(]").compareEnd(null).must.equal(-1)
})
it("must return 1 if given endpoint less than", function() {
new Range(0, 10, "(]").compareEnd(9).must.equal(1)
})
it("must return 1 if unbounded", function() {
new Range(0, null, "(]").compareEnd(-1).must.equal(1)
})
})
describe("with exclusive bound", function() {
it("must return 1 if given endpoint equal", function() {
new Range(0, 10, "[)").compareEnd(0).must.equal(1)
})
it("must return -1 if given endpoint greater than", function() {
new Range(0, 10, "[)").compareEnd(11).must.equal(-1)
})
it("must return -1 if unbounded and equal", function() {
new Range(10, null, "[)").compareEnd(null).must.equal(-1)
})
it("must return -1 if given null", function() {
new Range(0, 10, "(]").compareEnd(null).must.equal(-1)
})
it("must return 1 if given endpoint less than", function() {
new Range(0, 10, "[)").compareEnd(-1).must.equal(1)
})
it("must return 1 if unbounded", function() {
new Range(0, null, "[)").compareEnd(-1).must.equal(1)
})
})
})
describe(".prototype.contains", function() {
it("must return true when contained", function() {
new Range(10, 20).contains(15).must.be.true()
})
it("must return false when intersecting, but of zero size", function() {
new Range(5, 5, "[)").contains(5).must.be.false()
})
it("must return true when on inclusive boundary", function() {
new Range(0, 10, "(]").contains(10).must.be.true()
new Range(0, 10, "[)").contains(0).must.be.true()
})
it("must return false when on exclusive boundary", function() {
new Range(0, 10, "[)").contains(10).must.be.false()
new Range(0, 10, "(]").contains(0).must.be.false()
})
it("must return false when empty", function() {
new Range().contains(5).must.be.false()
})
it("must return true if one endpoint unbounded", function() {
new Range(0, null).contains(10).must.be.true()
new Range(null, 0).contains(-10).must.be.true()
})
it("must return true if one endpoint unbounded and on inclusive boundary",
function() {
new Range(0, null).contains(0).must.be.true()
new Range(null, 0).contains(0).must.be.true()
})
it("must return false if one endpoint unbounded and on exclusive boundary",
function() {
new Range(0, null, "(]").contains(0).must.be.false()
new Range(null, 0, "[)").contains(0).must.be.false()
})
it("must return false if one endpoint undefined", function() {
new Range(0, undefined).contains(0).must.be.false()
new Range(undefined, 0).contains(0).must.be.false()
})
})
describe(".prototype.intersects", function() {
function intersects(a, b) {
var result = a.intersects(b)
b.intersects(a).must.equal(result)
return result
}
function testWithBounds(bounds) {
it("must return true when equal", function() {
var a = new Range(10, 20, bounds)
var b = new Range(10, 20, bounds)
intersects(a, b).must.be.true()
})
it("must return true when intersecting", function() {
var a = new Range(10, 20, bounds)
var b = new Range(5, 15, bounds)
intersects(a, b).must.be.true()
})
it("must return false when intersecting, but one empty", function() {
var a = new Range(0, 10, bounds)
var b = new Range(undefined, undefined, bounds)
intersects(a, b).must.be.false()
})
it("must return false when intersecting, but one of zero size",
function() {
var a = new Range(0, 10, bounds)
var b = new Range(5, 5, "[)")
intersects(a, b).must.be.false()
})
it("must return false when not intersecting", function() {
var a = new Range(0, 10, bounds)
var b = new Range(30, 40, bounds)
intersects(a, b).must.be.false()
})
it("must return true when intersecting and one unbounded", function() {
var a = new Range(0, 10, bounds)
var b = new Range(5, null, bounds)
intersects(a, b).must.be.true()
})
it("must return true when one encloses the other", function() {
var a = new Range(0, 10, bounds)
var b = new Range(3, 6, bounds)
intersects(a, b).must.be.true()
})
it("must return true when one encloses the other and is unbounded",
function() {
var a = new Range(0, 10, bounds)
var b = new Range(null, null, bounds)
intersects(a, b).must.be.true()
})
}
describe("with inclusive bounds", testWithBounds.bind(null, "[]"))
describe("with exclusive bounds", testWithBounds.bind(null, "()"))
it("must return true when one encloses the other, but one exclusive",
function() {
var a = new Range(0, 10)
var b = new Range(3, 6, "()")
intersects(a, b).must.be.true()
})
it("must return true when consecutive", function() {
var a = new Range(0, 10)
var b = new Range(10, 20)
intersects(a, b).must.be.true()
})
it("must return true when one at the boundary of other", function() {
var a = new Range(0, 10)
var b = new Range(10, 10)
intersects(a, b).must.be.true()
})
it("must return false when consecutive, but end exclusive", function() {
var a = new Range(0, 10, "[)")
var b = new Range(10, 20)
intersects(a, b).must.be.false()
})
it("must return false when consecutive, but begin exclusive", function() {
var a = new Range(0, 10)
var b = new Range(10, 20, "(]")
intersects(a, b).must.be.false()
})
it("must return false when consecutive, but both exclusive",
function() {
var a = new Range(0, 10, "[)")
var b = new Range(10, 20, "(]")
intersects(a, b).must.be.false()
})
})
describe(".prototype.valueOf", function() {
it("must return an array", function() {
new Range(42, 69, "()").valueOf().must.eql([42, 69, "()"])
})
})
describe(".prototype.toString", function() {
it("must stringify range with given bounds", function() {
new Range(42, 69, "()").toString().must.equal("(42,69)")
new Range(42, 69, "[]").toString().must.equal("[42,69]")
new Range(42, 69, "[)").toString().must.equal("[42,69)")
new Range(42, 69, "(]").toString().must.equal("(42,69]")
})
it("must stringify begin", function() {
// Having valueOf too ensures the value is stringied before string
// concatenation.
function Value(value) { this.value = value }
Value.prototype.valueOf = function() { return null }
Value.prototype.toString = function() { return this.value }
var a = new Value(42)
var b = new Value(69)
new Range(a, b).toString().must.equal("[42,69]")
})
it("must stringify null endpoint as empty", function() {
new Range(42, null).toString().must.equal("[42,]")
new Range(null, 42).toString().must.equal("[,42]")
new Range(null, null).toString().must.equal("[,]")
})
it("must stringify Infinity as empty", function() {
new Range(42, Infinity).toString().must.equal("[42,]")
new Range(-Infinity, 42).toString().must.equal("[,42]")
new Range(-Infinity, Infinity).toString().must.equal("[,]")
})
})
describe(".prototype.toJSON", function() {
it("must be an alias to toString", function() {
Range.prototype.toJSON.must.equal(Range.prototype.toString)
})
})
describe(".prototype.inspect", function() {
it("must be an alias to toString", function() {
Range.prototype.inspect.must.equal(Range.prototype.toString)
})
})
describe(".compareBeginToBegin", function() {
function compare(a, b) {
var result = Range.compareBeginToBegin(a, b)
Range.compareBeginToBegin(b, a).must.eql(result * -1)
return result
}
function testWithBounds(bounds) {
it("must return 0 if equal", function() {
var a = new Range(0, 10, bounds)
var b = new Range(0, 5, bounds)
compare(a, b).must.equal(0)
})
it("must return -1 if less", function() {
var a = new Range(-1, 10, bounds)
var b = new Range(0, 10, bounds)
compare(a, b).must.equal(-1)
})
it("must return 0 if equal and unbounded", function() {
var a = new Range(null, 10, bounds)
var b = new Range(null, 5, bounds)
compare(a, b).must.equal(0)
})
it("must return -1 if one unbounded", function() {
var a = new Range(null, 10, bounds)
var b = new Range(0, 10, bounds)
compare(a, b).must.equal(-1)
})
}
describe("with inclusive bounds", testWithBounds.bind(null, "[]"))
describe("with exclusive bounds", testWithBounds.bind(null, "()"))
describe("with different bounds", function() {
it("must return -1 if equal", function() {
compare(new Range(0, 10, "[]"), new Range(0, 10, "()")).must.equal(-1)
})
it("must return -1 if less", function() {
compare(new Range(-1, 10, "[]"), new Range(0, 10, "()")).must.equal(-1)
compare(new Range(-1, 10, "()"), new Range(0, 10, "[]")).must.equal(-1)
})
it("must return -1 if equal and unbounded", function() {
var a = new Range(null, 10, "[]")
var b = new Range(null, 10, "(]")
compare(a, b).must.equal(-1)
})
})
})
describe(".compareBeginToEnd", function() {
var compare = Range.compareBeginToEnd
describe("with inclusive bounds", function() {
it("must return 0 if equal", function() {
var a = new Range(0, 10, "[]")
var b = new Range(-5, 0, "[]")
compare(a, b).must.equal(0)
})
it("must return -1 if less", function() {
var a = new Range(-1, 10, "[]")
var b = new Range(-5, 0, "[]")
compare(a, b).must.equal(-1)
})
it("must return -1 if unbounded", function() {
var a = new Range(null, 10, "[]")
var b = new Range(20, null, "[]")
compare(a, b).must.equal(-1)
})
it("must return -1 if begin unbounded", function() {
var a = new Range(null, 10, "[]")
var b = new Range(0, 5, "[]")
compare(a, b).must.equal(-1)
})
it("must return -1 if end unbounded", function() {
var a = new Range(0, 10, "[]")
var b = new Range(0, null, "[]")
compare(a, b).must.equal(-1)
})
})
describe("with exclusive bounds", function() {
it("must return 1 if equal", function() {
var a = new Range(0, 10, "()")
var b = new Range(-5, 0, "()")
compare(a, b).must.equal(1)
})
it("must return -1 if less", function() {
var a = new Range(-1, 10, "()")
var b = new Range(-5, 0, "()")
compare(a, b).must.equal(-1)
})
it("must return -1 if unbounded", function() {
var a = new Range(null, 10, "()")
var b = new Range(20, null, "()")
compare(a, b).must.equal(-1)
})
it("must return -1 if begin unbounded", function() {
var a = new Range(null, 10, "()")
var b = new Range(0, 5, "()")
compare(a, b).must.equal(-1)
})
it("must return -1 if end unbounded", function() {
var a = new Range(0, 10, "()")
var b = new Range(0, null, "()")
compare(a, b).must.equal(-1)
})
})
describe("with different bounds", function() {
it("must return 1 if begin inclusive and end exclusive", function() {
compare(new Range(0, 10, "[]"), new Range(-10, 0, "()")).must.equal(1)
})
it("must return 1 if begin exclusive and end inclusive", function() {
compare(new Range(0, 10, "()"), new Range(-10, 0, "[]")).must.equal(1)
})
it("must return -1 if less", function() {
compare(new Range(-1, 10, "[]"), new Range(-10, 0, "()")).must.equal(-1)
compare(new Range(-1, 10, "()"), new Range(-10, 0, "[]")).must.equal(-1)
})
})
})
describe(".compareEndToEnd", function() {
function compare(a, b) {
var result = Range.compareEndToEnd(a, b)
Range.compareEndToEnd(b, a).must.eql(result * -1)
return result
}
function testWithBounds(bounds) {
it("must return 0 if equal", function() {
var a = new Range(0, 10, bounds)
var b = new Range(5, 10, bounds)
compare(a, b).must.equal(0)
})
it("must return 0 if equal and unbounded", function() {
var a = new Range(0, null, bounds)
var b = new Range(5, null, bounds)
compare(a, b).must.equal(0)
})
it("must return -1 if less", function() {
var a = new Range(0, 9, bounds)
var b = new Range(0, 10, bounds)
compare(a, b).must.equal(-1)
})
it("must return -1 if one unbounded", function() {
var a = new Range(0, 10, bounds)
var b = new Range(0, null, bounds)
compare(a, b).must.equal(-1)
})
}
describe("with inclusive bounds", testWithBounds.bind(null, "[]"))
describe("with exclusive bounds", testWithBounds.bind(null, "()"))
describe("with different bounds", function() {
it("must return -1 if equal", function() {
compare(new Range(0, 10, "()"), new Range(0, 10, "(]")).must.equal(-1)
})
it("must return -1 if less", function() {
compare(new Range(0, 9, "(]"), new Range(0, 10, "()")).must.equal(-1)
compare(new Range(0, 9, "()"), new Range(0, 10, "(]")).must.equal(-1)
})
it("must return -1 if equal and unbounded", function() {
var a = new Range(0, null, "[)")
var b = new Range(0, null, "[]")
compare(a, b).must.equal(-1)
})
})
})
describe(".parse", function() {
it("must parse string with bounds", function() {
Range.parse("[a,z]").must.eql(new Range("a", "z", "[]"))
Range.parse("(a,z)").must.eql(new Range("a", "z", "()"))
Range.parse("[a,z)").must.eql(new Range("a", "z", "[)"))
Range.parse("(a,z]").must.eql(new Range("a", "z", "(]"))
})
it("must parse endpoint with given function", function() {
Range.parse("[42,69]", Number).must.eql(new Range(42, 69))
})
it("must parse string with infinite bounds", function() {
Range.parse("[a,]").must.eql(new Range("a", null))
Range.parse("[,z]").must.eql(new Range(null, "z"))
})
it("must parse string with infinite bounds given parse function",
function() {
var toUpperCase = Function.call.bind(String.prototype.toUpperCase)
Range.parse("[a,]", toUpperCase).must.eql(new Range("A", null))
Range.parse("[,b]", toUpperCase).must.eql(new Range(null, "B"))
Range.parse("(,)", toUpperCase).must.eql(new Range(null, null, "()"))
})
it("must parse string with infinite bounds given Number", function() {
Range.parse("[42,]", Number).must.eql(new Range(42, Infinity))
Range.parse("[,69]", Number).must.eql(new Range(-Infinity, 69))
Range.parse("(,)", Number).must.eql(new Range(-Infinity, Infinity, "()"))
})
})
describe(".union", function() {
function union(a, b) {
var result = Range.union(a, b)
Range.union(b, a).must.eql(result)
return result
}
it("must return a union given one empty range", function() {
var a = new Range(0, 5)
var b = new Range(10, 10, "[)")
union(a, b).must.eql(new Range(0, 5))
})
it("must return a union given two empty ranges", function() {
var a = new Range(5, 5, "[)")
var b = new Range(10, 10, "[)")
union(a, b).must.eql(new Range)
})
it("must return a union given same range twice", function() {
var a = new Range(0, 10, "[)")
union(a, a).must.eql(a)
})
describe("with inclusive bounds", function() {
it("must return a union given two intersecting ranges", function() {
var a = new Range(0, 11, "[]")
var b = new Range(9, 20, "[]")
union(a, b).must.eql(new Range(0, 20, "[]"))
})
it("must return a union given two consecutive ranges", function() {
var a = new Range(0, 10, "[]")
var b = new Range(10, 20, "[]")
union(a, b).must.eql(new Range(0, 20, "[]"))
})
it("must return a union given two non-consecutive ranges", function() {
var a = new Range(0, 5, "[]")
var b = new Range(15, 20, "[]")
union(a, b).must.eql(new Range(0, 20, "[]"))
})
})
describe("with exclusive bounds", function() {
it("must return a union given two intersecting ranges", function() {
var a = new Range(0, 11, "()")
var b = new Range(9, 20, "()")
union(a, b).must.eql(new Range(0, 20, "()"))
})
it("must return a union given two close, but non-consecutive ranges",
function() {
var a = new Range(0, 10, "()")
var b = new Range(10, 20, "()")
union(a, b).must.eql(new Range(0, 20, "()"))
})
it("must return a union given two non-consecutive ranges", function() {
var a = new Range(0, 5, "()")
var b = new Range(15, 20, "()")
union(a, b).must.eql(new Range(0, 20, "()"))
})
})
describe("with different bounds", function() {
it("must return a union given two consecutive ranges", function() {
var a = new Range(0, 10, "(]")
var b = new Range(10, 20, "[)")
union(a, b).must.eql(new Range(0, 20, "()"))
})
it("must return a union given two non-consecutive ranges", function() {
var a = new Range(0, 5, "[)")
var b = new Range(15, 20, "[)")
union(a, b).must.eql(new Range(0, 20, "[)"))
})
})
})
})