range-ts
Version:
RangeMap implementation based on Guava
454 lines (382 loc) • 14.5 kB
text/typescript
import {BoundType} from '../core/bound-type';
import {NumberRange} from './number-range';
describe('Range', () => {
describe('toString', () => {
it('should print open ranges', () => {
expect(NumberRange.open(4, 9).toString()).toBe('(4..9)');
});
it('should print closed ranges', () => {
expect(NumberRange.closed(4, 9).toString()).toBe('[4..9]');
});
it('should print closed open ranges', () => {
expect(NumberRange.closedOpen(4, 9).toString()).toBe('[4..9)');
});
it('should print all', () => {
expect(NumberRange.all().toString()).toBe('(-∞..+∞)');
});
it('should print Dates using toISOString()', () => {
expect(NumberRange.open(new Date(4), new Date(9)).toString()).toBe('(1970-01-01T00:00:00.004Z..1970-01-01T00:00:00.009Z)');
})
});
describe('contains', () => {
describe('closed open', () => {
it('should match on a closed endpoint', () => {
expect(NumberRange.closedOpen(2, 4).contains(2)).toBeTrue();
expect(NumberRange.closedOpen(new Date(2), new Date(4)).contains(2)).toBeTrue();
expect(NumberRange.closedOpen(new Date(2), new Date(4)).contains(new Date(2))).toBeTrue();
});
it('should not match on an open endpoint', () => {
expect(NumberRange.closedOpen(2, 4).contains(4)).toBeFalse();
expect(NumberRange.closedOpen(new Date(2), new Date(4)).contains(new Date(4))).toBeFalse();
});
it('should match on a number inside', () => {
expect(NumberRange.closedOpen(2, 4).contains(3)).toBeTrue();
expect(NumberRange.closedOpen(new Date(2), new Date(4)).contains(new Date(3))).toBeTrue();
});
it('should not match on a number before', () => {
expect(NumberRange.closedOpen(2, 4).contains(1)).toBeFalse();
});
it('should not match on a number after', () => {
expect(NumberRange.closedOpen(2, 4).contains(5)).toBeFalse();
});
});
describe('closed', () => {
it('should match on a closed lower endpoint', () => {
expect(NumberRange.closed(2, 4).contains(2)).toBeTrue();
});
it('should match on a closed upper endpoint', () => {
expect(NumberRange.closed(2, 4).contains(4)).toBeTrue();
});
it('should match on a number inside', () => {
expect(NumberRange.closed(2, 4).contains(3)).toBeTrue();
});
it('should not match on a number before', () => {
expect(NumberRange.closed(2, 4).contains(1)).toBeFalse();
});
it('should not match on a number after', () => {
expect(NumberRange.closed(2, 4).contains(5)).toBeFalse();
});
});
describe('atMost', () => {
it('should match equal to the bound', () => {
expect(NumberRange.atMost(5).contains(5)).toBeTrue();
});
it('should match below', () => {
expect(NumberRange.atMost(5).contains(4)).toBeTrue();
});
it('should not match above', () => {
expect(NumberRange.atMost(5).contains(6)).toBeFalse();
});
});
describe('atLeast', () => {
it('should match equal to the bound', () => {
expect(NumberRange.atLeast(5).contains(5)).toBeTrue();
});
it('should not match below', () => {
expect(NumberRange.atLeast(5).contains(4)).toBeFalse();
});
it('should match above', () => {
expect(NumberRange.atLeast(5).contains(6)).toBeTrue();
});
});
});
describe('isConnected', () => {
it('should be false when there is a gap between two closed open ranges', () => {
expect(
NumberRange.closedOpen(2, 4).isConnected(NumberRange.closedOpen(5, 7))
).toBeFalse();
expect(
NumberRange.closedOpen(5, 7).isConnected(NumberRange.closedOpen(2, 4))
).toBeFalse();
});
it('should be true when two closed open ranges overlap', () => {
expect(
NumberRange.closedOpen(2, 4).isConnected(NumberRange.closedOpen(3, 4))
).toBeTrue();
expect(
NumberRange.closedOpen(3, 4).isConnected(NumberRange.closedOpen(2, 4))
).toBeTrue();
});
it('should be true when a closed and open range touch on the same value', () => {
expect(
NumberRange.closedOpen(2, 4).isConnected(NumberRange.closedOpen(4, 6))
).toBeTrue();
expect(
NumberRange.closedOpen(4, 6).isConnected(NumberRange.closedOpen(2, 4))
).toBeTrue();
});
it('should be false when two open ranges do not touch', () => {
expect(
NumberRange.upTo(4, BoundType.OPEN).isConnected(NumberRange.atLeast(6))
).toBeFalse();
expect(
NumberRange.atLeast(6).isConnected(NumberRange.upTo(4, BoundType.OPEN))
).toBeFalse();
});
it('should be true when one closedOpen range encloses the other', () => {
expect(
NumberRange.closedOpen(2, 10).isConnected(NumberRange.closedOpen(4, 6))
).toBeTrue();
expect(
NumberRange.closedOpen(4, 6).isConnected(NumberRange.closedOpen(2, 10))
).toBeTrue();
});
});
describe('intersecting', () => {
it('should intersect with closedOpen and atLeast', () => {
const result = NumberRange.closedOpen(2, 4).intersection(
NumberRange.atLeast(3)
);
expect(result?.toString()).toBe('[3..4)');
});
it('should intersect with Date closedOpen and Date atLeast', () => {
const result = NumberRange.closedOpen(new Date(2), new Date(4)).intersection(
NumberRange.atLeast(new Date(3))
);
expect(result?.toString()).toBe('[1970-01-01T00:00:00.003Z..1970-01-01T00:00:00.004Z)');
});
it('should intersect with Date closedOpen and number atLeast', () => {
const result = NumberRange.closedOpen(new Date(2), new Date(4)).intersection(
NumberRange.atLeast(3)
);
// Note that the resulting Range is mixed, no conversions are performed.
expect(result?.toString()).toBe('[3..1970-01-01T00:00:00.004Z)');
});
it('should intersect with closedOpen and atMost', () => {
const result = NumberRange.closedOpen(2, 4).intersection(
NumberRange.atMost(3)
);
expect(result?.toString()).toBe('[2..3]');
});
it('should be able to handle an empty range', () => {
const result = NumberRange.closedOpen(2, 4).intersection(
NumberRange.closedOpen(4, 6)
);
expect(result?.toString()).toBe('[4..4)');
});
it('should be able to handle a range containing one value', () => {
const result = NumberRange.closed(2, 4).intersection(
NumberRange.closedOpen(4, 6)
);
expect(result?.toString()).toBe('[4..4]');
});
});
describe('span', () => {
it('should work for two non connecting closedOpen ranges', () => {
const range1 = NumberRange.closedOpen(1, 5);
const range2 = NumberRange.closedOpen(8, 12);
const result = range1.span(range2);
expect(result.toString()).toBe('[1..12)');
});
it('should work for two overlapping closedOpen ranges', () => {
const range1 = NumberRange.closedOpen(1, 10);
const range2 = NumberRange.closedOpen(7, 12);
const result = range1.span(range2);
expect(result.toString()).toBe('[1..12)');
});
it('should work for a closedOpen and open range', () => {
const range1 = NumberRange.closedOpen(1, 5);
const range2 = NumberRange.atLeast(7);
const result = range1.span(range2);
expect(result.toString()).toBe('[1..+∞)');
});
it('should work for with two non connecting open ranges', () => {
const range1 = NumberRange.atMost(4);
const range2 = NumberRange.atLeast(7);
const result = range1.span(range2);
expect(result.toString()).toBe('(-∞..+∞)');
expect(result.lowerBoundType).toBe(BoundType.OPEN);
expect(result.lowerEndpoint).toBe(Number.NEGATIVE_INFINITY);
expect(result.upperBoundType).toBe(BoundType.OPEN);
expect(result.upperEndpoint).toBe(Number.POSITIVE_INFINITY);
});
});
describe('overlaps', () => {
it('should be false when there is no overlap', () => {
// first before second
expect(
NumberRange.closedOpen(2, 4).overlaps(NumberRange.closedOpen(6, 8))
).toBeFalse();
expect(
NumberRange.closedOpen(2, 4).overlaps(NumberRange.closedOpen(4, 8))
).toBeFalse();
expect(
NumberRange.closed(2, 4).overlaps(NumberRange.closedOpen(5, 8))
).toBeFalse();
// first after second
expect(
NumberRange.closedOpen(10, 20).overlaps(
NumberRange.closedOpen(21, 30)
)
).toBeFalse();
expect(
NumberRange.closedOpen(10, 20).overlaps(
NumberRange.closedOpen(25, 30)
)
).toBeFalse();
expect(
NumberRange.closedOpen(10, 20).overlaps(NumberRange.closed(20, 30))
).toBeFalse();
});
it('should be true when the ranges overlap in any way', () => {
// first overlaps start of second
expect(
NumberRange.closedOpen(10, 20).overlaps(
NumberRange.closedOpen(19, 30)
)
).toBeTrue();
expect(
NumberRange.closedOpen(10, 20).overlaps(
NumberRange.closedOpen(18, 30)
)
).toBeTrue();
expect(
NumberRange.closed(10, 20).overlaps(NumberRange.closedOpen(20, 30))
).toBeTrue();
expect(
NumberRange.closed(10, 20).overlaps(NumberRange.closedOpen(19, 30))
).toBeTrue();
// first totally overlaps second
expect(
NumberRange.closedOpen(10, 20).overlaps(
NumberRange.closedOpen(10, 20)
)
).toBeTrue();
expect(
NumberRange.closedOpen(10, 20).overlaps(
NumberRange.closedOpen(11, 19)
)
).toBeTrue();
expect(
NumberRange.closed(10, 20).overlaps(NumberRange.closedOpen(10, 20))
).toBeTrue();
expect(
NumberRange.closed(10, 20).overlaps(NumberRange.closedOpen(11, 20))
).toBeTrue();
expect(
NumberRange.closed(10, 20).overlaps(NumberRange.closedOpen(10, 19))
).toBeTrue();
// first overlaps end of second
expect(
NumberRange.closedOpen(20, 30).overlaps(
NumberRange.closedOpen(10, 21)
)
).toBeTrue();
expect(
NumberRange.closedOpen(20, 30).overlaps(
NumberRange.closedOpen(11, 22)
)
).toBeTrue();
expect(
NumberRange.closed(20, 30).overlaps(NumberRange.closedOpen(10, 21))
).toBeTrue();
expect(
NumberRange.closed(20, 30).overlaps(NumberRange.closedOpen(11, 22))
).toBeTrue();
expect(
NumberRange.closedOpen(20, 30).overlaps(NumberRange.closed(10, 20))
).toBeTrue();
expect(
NumberRange.closedOpen(20, 30).overlaps(NumberRange.closed(11, 21))
).toBeTrue();
expect(
NumberRange.closed(20, 30).overlaps(NumberRange.closed(10, 20))
).toBeTrue();
expect(
NumberRange.closed(20, 30).overlaps(NumberRange.closed(11, 21))
).toBeTrue();
// first inside of second
expect(
NumberRange.closedOpen(20, 30).overlaps(
NumberRange.closedOpen(20, 29)
)
).toBeTrue();
expect(
NumberRange.closedOpen(20, 30).overlaps(
NumberRange.closedOpen(20, 30)
)
).toBeTrue();
expect(
NumberRange.closedOpen(20, 30).overlaps(
NumberRange.closedOpen(20, 31)
)
).toBeTrue();
expect(
NumberRange.closedOpen(20, 30).overlaps(
NumberRange.closedOpen(19, 31)
)
).toBeTrue();
expect(
NumberRange.closed(20, 30).overlaps(NumberRange.closedOpen(20, 30))
).toBeTrue();
expect(
NumberRange.closed(20, 30).overlaps(NumberRange.closedOpen(19, 30))
).toBeTrue();
expect(
NumberRange.closed(20, 30).overlaps(NumberRange.closedOpen(20, 31))
).toBeTrue();
expect(
NumberRange.closed(20, 30).overlaps(NumberRange.closed(20, 30))
).toBeTrue();
expect(
NumberRange.closed(20, 30).overlaps(NumberRange.closed(19, 31))
).toBeTrue();
expect(
NumberRange.closed(20, 30).overlaps(NumberRange.closed(19, 30))
).toBeTrue();
expect(
NumberRange.closed(20, 30).overlaps(NumberRange.closed(20, 31))
).toBeTrue();
});
});
describe('encloses', () => {
it('a closedOpen should enclose itself', () => {
const range = NumberRange.closedOpen(1, 20);
expect(range.encloses(range)).toBeTrue();
});
it('should work on two closedOpen ranges', () => {
const largeRange = NumberRange.closedOpen(3, 10);
const smallRange = NumberRange.closedOpen(4, 6);
expect(largeRange.encloses(smallRange)).toBeTrue();
expect(smallRange.encloses(largeRange)).toBeFalse();
});
describe('docs specifications', () => {
// See https://guava.dev/releases/19.0/api/docs/com/google/common/collect/Range.html#encloses(com.google.common.collect.Range)
it('[3...6] encloses [4..5]', () => {
const r1 = NumberRange.closed(3, 6)
const r2 = NumberRange.closed(4, 5);
expect(r1.encloses(r2)).toBeTrue()
})
it('(3..6) encloses (3..6)', () => {
const r1 = NumberRange.open(3, 6)
const r2 = NumberRange.open(3, 6);
expect(r1.encloses(r2)).toBeTrue()
})
it('[3..6] encloses [4..4)', () => {
const r1 = NumberRange.closed(3, 6)
const r2 = NumberRange.closedOpen(4, 4);
expect(r1.encloses(r2)).toBeTrue()
})
it('(3..6] does not enclose [3..6] ', () => {
const r1 = NumberRange.openClosed(3, 6);
const r2 = NumberRange.closed(3, 6);
expect(r1.encloses(r2)).toBeFalse()
})
it('[4..5] does not enclose (3..6)', () => {
const r1 = NumberRange.closed(4, 5)
const r2 = NumberRange.open(3, 6);
expect(r1.encloses(r2)).toBeFalse()
})
it('[3..6] does not enclose (1..1]', () => {
const r1 = NumberRange.closed(3, 6)
const r2 = NumberRange.openClosed(1, 1);
expect(r1.encloses(r2)).toBeFalse()
})
})
});
describe('isEmpty', () => {
it('should support Dates', () => {
expect(NumberRange.closedOpen(new Date(0), new Date(0)).isEmpty()).toBeTrue();
})
})
});