test sweep: cover BufferRange family (T-91)
Adds test/terminal/buffer/range_test.dart — 38 unit tests covering the small pure-Dart files in lib/src/terminal/src/core/buffer/: - cell_offset.dart 23 / 23 (was 0 / 23) - range.dart 13 / 13 (was 0 / 13) - segment.dart 13 / 13 (was 0 / 13) - range_line.dart 30 / 30 (was 0 / 30) - range_block.dart 48 / 48 (was 0 / 48) Total project line coverage 34.90% → 36.39%; coverage_floor in pubspec.yaml bumped 34 → 36 in lockstep. Tests exercise the abstract BufferRange operator==/hashCode/toString via a local _StubRange (BufferRangeLine and BufferRangeBlock both override those, so the base versions are otherwise unreachable — worth a stub rather than carving the lines out of coverage). The denormalized-input branches in Block contain/toSegments/extend get explicit cases too. Pure Dart, no Flutter dependency — uses package:test/test.dart and runs in <100ms. First batch under T-91; line.dart and buffer.dart land in subsequent commits with their own floor bumps. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,354 @@
|
||||
/// Pure-Dart tests for the BufferRange family (CellOffset, BufferSegment,
|
||||
/// BufferRangeLine, BufferRangeBlock). No Flutter dependency.
|
||||
library;
|
||||
|
||||
import 'package:clide/src/terminal/src/core/buffer/cell_offset.dart';
|
||||
import 'package:clide/src/terminal/src/core/buffer/range.dart';
|
||||
import 'package:clide/src/terminal/src/core/buffer/range_block.dart';
|
||||
import 'package:clide/src/terminal/src/core/buffer/range_line.dart';
|
||||
import 'package:clide/src/terminal/src/core/buffer/segment.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('CellOffset', () {
|
||||
test('equality + hashCode for same coordinates', () {
|
||||
const a = CellOffset(3, 5);
|
||||
const b = CellOffset(3, 5);
|
||||
expect(a, b);
|
||||
expect(a.hashCode, b.hashCode);
|
||||
expect(a.isEqual(b), isTrue);
|
||||
expect(identical(a, a), isTrue); // hits the identity branch in ==
|
||||
expect(a == 'CellOffset(3, 5)', isFalse); // hits the type-mismatch branch
|
||||
});
|
||||
|
||||
test('inequality for different coordinates', () {
|
||||
expect(const CellOffset(3, 5), isNot(const CellOffset(4, 5)));
|
||||
expect(const CellOffset(3, 5).isEqual(const CellOffset(3, 6)), isFalse);
|
||||
});
|
||||
|
||||
test('isBefore / isAfter — different rows', () {
|
||||
const top = CellOffset(9, 1);
|
||||
const bottom = CellOffset(0, 2);
|
||||
expect(top.isBefore(bottom), isTrue);
|
||||
expect(bottom.isAfter(top), isTrue);
|
||||
expect(top.isAfter(bottom), isFalse);
|
||||
expect(bottom.isBefore(top), isFalse);
|
||||
});
|
||||
|
||||
test('isBefore / isAfter — same row, different columns', () {
|
||||
const left = CellOffset(2, 4);
|
||||
const right = CellOffset(7, 4);
|
||||
expect(left.isBefore(right), isTrue);
|
||||
expect(right.isAfter(left), isTrue);
|
||||
expect(left.isAfter(right), isFalse);
|
||||
expect(right.isBefore(left), isFalse);
|
||||
});
|
||||
|
||||
test('isBefore / isAfter — equal positions are neither', () {
|
||||
const a = CellOffset(2, 2);
|
||||
const b = CellOffset(2, 2);
|
||||
expect(a.isBefore(b), isFalse);
|
||||
expect(a.isAfter(b), isFalse);
|
||||
});
|
||||
|
||||
test('isBeforeOrSame / isAfterOrSame include equal positions', () {
|
||||
const a = CellOffset(3, 3);
|
||||
const b = CellOffset(3, 3);
|
||||
const earlier = CellOffset(2, 3);
|
||||
const later = CellOffset(4, 3);
|
||||
expect(a.isBeforeOrSame(b), isTrue);
|
||||
expect(a.isAfterOrSame(b), isTrue);
|
||||
expect(earlier.isBeforeOrSame(a), isTrue);
|
||||
expect(later.isAfterOrSame(a), isTrue);
|
||||
expect(later.isBeforeOrSame(a), isFalse);
|
||||
expect(earlier.isAfterOrSame(a), isFalse);
|
||||
});
|
||||
|
||||
test('isAtSameRow / isAtSameColumn', () {
|
||||
expect(const CellOffset(2, 5).isAtSameRow(const CellOffset(9, 5)), isTrue);
|
||||
expect(const CellOffset(2, 5).isAtSameRow(const CellOffset(2, 6)), isFalse);
|
||||
expect(const CellOffset(4, 1).isAtSameColumn(const CellOffset(4, 9)), isTrue);
|
||||
expect(const CellOffset(4, 1).isAtSameColumn(const CellOffset(5, 9)), isFalse);
|
||||
});
|
||||
|
||||
test('isWithin delegates to BufferRange.contains', () {
|
||||
final range = BufferRangeLine(const CellOffset(0, 0), const CellOffset(5, 0));
|
||||
expect(const CellOffset(3, 0).isWithin(range), isTrue);
|
||||
expect(const CellOffset(3, 1).isWithin(range), isFalse);
|
||||
});
|
||||
|
||||
test('toString shape', () {
|
||||
expect(const CellOffset(2, 3).toString(), 'CellOffset(2, 3)');
|
||||
});
|
||||
});
|
||||
|
||||
group('BufferSegment', () {
|
||||
test('isWithin — wrong line is never within', () {
|
||||
const seg = BufferSegment(_dummyRange, 4, 2, 8);
|
||||
expect(seg.isWithin(const CellOffset(5, 3)), isFalse);
|
||||
expect(seg.isWithin(const CellOffset(5, 5)), isFalse);
|
||||
});
|
||||
|
||||
test('isWithin — bounded segment respects start and end', () {
|
||||
const seg = BufferSegment(_dummyRange, 4, 2, 8);
|
||||
expect(seg.isWithin(const CellOffset(2, 4)), isTrue); // at start
|
||||
expect(seg.isWithin(const CellOffset(5, 4)), isTrue); // middle
|
||||
expect(seg.isWithin(const CellOffset(8, 4)), isTrue); // at end
|
||||
expect(seg.isWithin(const CellOffset(1, 4)), isFalse); // before start
|
||||
expect(seg.isWithin(const CellOffset(9, 4)), isFalse); // after end
|
||||
});
|
||||
|
||||
test('isWithin — null start means line beginning', () {
|
||||
const seg = BufferSegment(_dummyRange, 0, null, 5);
|
||||
expect(seg.isWithin(const CellOffset(0, 0)), isTrue);
|
||||
expect(seg.isWithin(const CellOffset(5, 0)), isTrue);
|
||||
expect(seg.isWithin(const CellOffset(6, 0)), isFalse);
|
||||
});
|
||||
|
||||
test('isWithin — null end means line end', () {
|
||||
const seg = BufferSegment(_dummyRange, 0, 3, null);
|
||||
expect(seg.isWithin(const CellOffset(3, 0)), isTrue);
|
||||
expect(seg.isWithin(const CellOffset(999, 0)), isTrue);
|
||||
expect(seg.isWithin(const CellOffset(2, 0)), isFalse);
|
||||
});
|
||||
|
||||
test('equality + hashCode', () {
|
||||
const a = BufferSegment(_dummyRange, 1, 2, 3);
|
||||
const b = BufferSegment(_dummyRange, 1, 2, 3);
|
||||
const c = BufferSegment(_dummyRange, 1, 2, 4);
|
||||
expect(a, b);
|
||||
expect(a.hashCode, b.hashCode);
|
||||
expect(a, isNot(c));
|
||||
expect(a == 'segment', isFalse);
|
||||
expect(identical(a, a), isTrue);
|
||||
});
|
||||
|
||||
test('toString shape — bounded and unbounded', () {
|
||||
expect(
|
||||
const BufferSegment(_dummyRange, 4, 2, 8).toString(),
|
||||
'Segment(4, 2 -> 8)',
|
||||
);
|
||||
expect(
|
||||
const BufferSegment(_dummyRange, 0, null, null).toString(),
|
||||
'Segment(0, start -> end)',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('BufferRangeLine', () {
|
||||
test('collapsed range has equal begin and end', () {
|
||||
final r = BufferRangeLine.collapsed(const CellOffset(2, 3));
|
||||
expect(r.isCollapsed, isTrue);
|
||||
expect(r.begin, const CellOffset(2, 3));
|
||||
expect(r.end, const CellOffset(2, 3));
|
||||
});
|
||||
|
||||
test('isNormalized true when begin <= end', () {
|
||||
final r = BufferRangeLine(const CellOffset(0, 0), const CellOffset(3, 1));
|
||||
expect(r.isNormalized, isTrue);
|
||||
});
|
||||
|
||||
test('normalized swaps begin/end when reversed', () {
|
||||
final r = BufferRangeLine(const CellOffset(3, 1), const CellOffset(0, 0));
|
||||
expect(r.isNormalized, isFalse);
|
||||
final n = r.normalized;
|
||||
expect(n.begin, const CellOffset(0, 0));
|
||||
expect(n.end, const CellOffset(3, 1));
|
||||
expect(n.normalized, n); // already normalized → identity
|
||||
});
|
||||
|
||||
test('toSegments — single line returns one bounded segment', () {
|
||||
final r = BufferRangeLine(const CellOffset(2, 0), const CellOffset(5, 0));
|
||||
final segs = r.toSegments().toList();
|
||||
expect(segs, hasLength(1));
|
||||
expect(segs[0].line, 0);
|
||||
expect(segs[0].start, 2);
|
||||
expect(segs[0].end, 5);
|
||||
});
|
||||
|
||||
test('toSegments — multiple lines: first bounded-left, last bounded-right, middle unbounded', () {
|
||||
final r = BufferRangeLine(const CellOffset(2, 0), const CellOffset(5, 2));
|
||||
final segs = r.toSegments().toList();
|
||||
expect(segs.map((s) => s.line), [0, 1, 2]);
|
||||
expect(segs[0].start, 2);
|
||||
expect(segs[0].end, isNull);
|
||||
expect(segs[1].start, isNull);
|
||||
expect(segs[1].end, isNull);
|
||||
expect(segs[2].start, isNull);
|
||||
expect(segs[2].end, 5);
|
||||
});
|
||||
|
||||
test('contains positions inside the range, including endpoints', () {
|
||||
final r = BufferRangeLine(const CellOffset(2, 0), const CellOffset(5, 2));
|
||||
expect(r.contains(const CellOffset(2, 0)), isTrue);
|
||||
expect(r.contains(const CellOffset(0, 1)), isTrue); // mid line
|
||||
expect(r.contains(const CellOffset(5, 2)), isTrue);
|
||||
expect(r.contains(const CellOffset(1, 0)), isFalse);
|
||||
expect(r.contains(const CellOffset(6, 2)), isFalse);
|
||||
});
|
||||
|
||||
test('contains works on a denormalized range too (normalizes internally)', () {
|
||||
final r = BufferRangeLine(const CellOffset(5, 2), const CellOffset(2, 0));
|
||||
expect(r.contains(const CellOffset(0, 1)), isTrue);
|
||||
});
|
||||
|
||||
test('merge picks earliest begin and latest end', () {
|
||||
final a = BufferRangeLine(const CellOffset(2, 0), const CellOffset(5, 0));
|
||||
final b = BufferRangeLine(const CellOffset(0, 0), const CellOffset(9, 0));
|
||||
final m = a.merge(b);
|
||||
expect(m.begin, const CellOffset(0, 0));
|
||||
expect(m.end, const CellOffset(9, 0));
|
||||
});
|
||||
|
||||
test('extend grows the range to cover an outside position', () {
|
||||
final r = BufferRangeLine(const CellOffset(2, 1), const CellOffset(5, 1));
|
||||
final earlier = r.extend(const CellOffset(0, 0));
|
||||
expect(earlier.begin, const CellOffset(0, 0));
|
||||
expect(earlier.end, const CellOffset(5, 1));
|
||||
final later = r.extend(const CellOffset(9, 3));
|
||||
expect(later.begin, const CellOffset(2, 1));
|
||||
expect(later.end, const CellOffset(9, 3));
|
||||
});
|
||||
|
||||
test('equality + hashCode + toString', () {
|
||||
final a = BufferRangeLine(const CellOffset(0, 0), const CellOffset(1, 1));
|
||||
final b = BufferRangeLine(const CellOffset(0, 0), const CellOffset(1, 1));
|
||||
final c = BufferRangeLine(const CellOffset(0, 0), const CellOffset(1, 2));
|
||||
expect(a, b);
|
||||
expect(a.hashCode, b.hashCode);
|
||||
expect(a, isNot(c));
|
||||
expect(a == 'line range', isFalse);
|
||||
expect(identical(a, a), isTrue);
|
||||
expect(a.toString(), startsWith('Line Range('));
|
||||
});
|
||||
});
|
||||
|
||||
group('BufferRangeBlock', () {
|
||||
test('isNormalized requires top-left + bottom-right corners', () {
|
||||
final tlBr = BufferRangeBlock(const CellOffset(2, 1), const CellOffset(5, 4));
|
||||
expect(tlBr.isNormalized, isTrue);
|
||||
|
||||
final trBl = BufferRangeBlock(const CellOffset(5, 1), const CellOffset(2, 4));
|
||||
expect(trBl.isNormalized, isFalse);
|
||||
});
|
||||
|
||||
test('normalized produces top-left / bottom-right corners', () {
|
||||
final r = BufferRangeBlock(const CellOffset(5, 1), const CellOffset(2, 4));
|
||||
final n = r.normalized;
|
||||
expect(n.begin, const CellOffset(2, 1));
|
||||
expect(n.end, const CellOffset(5, 4));
|
||||
expect(n.normalized, n); // already normalized branch
|
||||
});
|
||||
|
||||
test('toSegments — bounded on every line', () {
|
||||
final r = BufferRangeBlock(const CellOffset(2, 0), const CellOffset(5, 2));
|
||||
final segs = r.toSegments().toList();
|
||||
expect(segs.map((s) => s.line), [0, 1, 2]);
|
||||
for (final s in segs) {
|
||||
expect(s.start, 2);
|
||||
expect(s.end, 5);
|
||||
}
|
||||
});
|
||||
|
||||
test('toSegments — denormalized input still yields normalized output', () {
|
||||
final r = BufferRangeBlock(const CellOffset(5, 2), const CellOffset(2, 0));
|
||||
final segs = r.toSegments().toList();
|
||||
// After internal normalization the segment range is x:[2..5] over y:[0..2].
|
||||
expect(segs.map((s) => s.line), [0, 1, 2]);
|
||||
for (final s in segs) {
|
||||
expect(s.start, 2);
|
||||
expect(s.end, 5);
|
||||
}
|
||||
});
|
||||
|
||||
test('contains — inside, on edge, outside', () {
|
||||
final r = BufferRangeBlock(const CellOffset(2, 0), const CellOffset(5, 2));
|
||||
expect(r.contains(const CellOffset(2, 0)), isTrue); // tl
|
||||
expect(r.contains(const CellOffset(5, 2)), isTrue); // br
|
||||
expect(r.contains(const CellOffset(3, 1)), isTrue); // inside
|
||||
expect(r.contains(const CellOffset(1, 1)), isFalse); // left of block
|
||||
expect(r.contains(const CellOffset(6, 1)), isFalse); // right of block
|
||||
expect(r.contains(const CellOffset(3, 3)), isFalse); // below block
|
||||
});
|
||||
|
||||
test('contains works on denormalized blocks', () {
|
||||
final r = BufferRangeBlock(const CellOffset(5, 2), const CellOffset(2, 0));
|
||||
expect(r.contains(const CellOffset(3, 1)), isTrue);
|
||||
});
|
||||
|
||||
test('extend — position inside is identity', () {
|
||||
final r = BufferRangeBlock(const CellOffset(2, 0), const CellOffset(5, 2));
|
||||
final same = r.extend(const CellOffset(3, 1));
|
||||
expect(same, r);
|
||||
});
|
||||
|
||||
test('extend — position outside grows the block', () {
|
||||
final r = BufferRangeBlock(const CellOffset(2, 1), const CellOffset(5, 3));
|
||||
final out = r.extend(const CellOffset(0, 0));
|
||||
expect(out.begin, const CellOffset(0, 0));
|
||||
expect(out.end, const CellOffset(5, 3));
|
||||
});
|
||||
|
||||
test('merge — combines two blocks into the smallest enclosing block', () {
|
||||
final a = BufferRangeBlock(const CellOffset(0, 0), const CellOffset(2, 2));
|
||||
final b = BufferRangeBlock(const CellOffset(5, 5), const CellOffset(7, 7));
|
||||
final m = a.merge(b);
|
||||
expect(m.begin, const CellOffset(0, 0));
|
||||
expect(m.end, const CellOffset(7, 7));
|
||||
});
|
||||
|
||||
test('equality + hashCode + toString', () {
|
||||
final a = BufferRangeBlock(const CellOffset(0, 0), const CellOffset(1, 1));
|
||||
final b = BufferRangeBlock(const CellOffset(0, 0), const CellOffset(1, 1));
|
||||
final c = BufferRangeBlock(const CellOffset(0, 0), const CellOffset(1, 2));
|
||||
expect(a, b);
|
||||
expect(a.hashCode, b.hashCode);
|
||||
expect(a, isNot(c));
|
||||
expect(a == 'block range', isFalse);
|
||||
expect(identical(a, a), isTrue);
|
||||
expect(a.toString(), startsWith('Block Range('));
|
||||
});
|
||||
});
|
||||
|
||||
group('BufferRange (abstract base) — collapsed + isCollapsed + isNormalized', () {
|
||||
test('collapsed range is normalized and collapsed', () {
|
||||
final r = BufferRangeLine.collapsed(const CellOffset(3, 3));
|
||||
expect(r.isCollapsed, isTrue);
|
||||
expect(r.isNormalized, isTrue);
|
||||
});
|
||||
|
||||
test('block collapsed constructor', () {
|
||||
final r = BufferRangeBlock.collapsed(const CellOffset(2, 2));
|
||||
expect(r.isCollapsed, isTrue);
|
||||
expect(r.begin, r.end);
|
||||
});
|
||||
|
||||
test('base ==/hashCode/toString via direct subclass that does not override them', () {
|
||||
const a = _StubRange(CellOffset(0, 0), CellOffset(1, 1));
|
||||
const b = _StubRange(CellOffset(0, 0), CellOffset(1, 1));
|
||||
const c = _StubRange(CellOffset(0, 0), CellOffset(2, 2));
|
||||
expect(a, b); // same begin + end via base ==
|
||||
expect(a.hashCode, b.hashCode);
|
||||
expect(a, isNot(c));
|
||||
expect(a == 'not a range', isFalse); // type-mismatch branch
|
||||
expect(identical(a, a), isTrue);
|
||||
expect(a.toString(), 'Range(CellOffset(0, 0), CellOffset(1, 1))');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const _dummyRange = _StubRange(CellOffset(0, 0), CellOffset(0, 0));
|
||||
|
||||
class _StubRange extends BufferRange {
|
||||
const _StubRange(super.begin, super.end);
|
||||
@override
|
||||
BufferRange get normalized => this;
|
||||
@override
|
||||
Iterable<BufferSegment> toSegments() => const [];
|
||||
@override
|
||||
bool contains(CellOffset position) => false;
|
||||
@override
|
||||
BufferRange merge(BufferRange range) => this;
|
||||
@override
|
||||
BufferRange extend(CellOffset position) => this;
|
||||
}
|
||||
Reference in New Issue
Block a user