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:
2026-05-06 22:55:15 +02:00
co-authored by Claude
parent f204e91f29
commit eb32396bd4
3 changed files with 493 additions and 108 deletions
+354
View File
@@ -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;
}