Files
clide/test/terminal/buffer/range_test.dart
T
jpmschweitzerandClaude Opus 4.8 6d0ebab721 chore: adopt Dart 3.9 toolchain — honest floor + tall-style reformat (T-353)
Raise the declared minimums in pubspec.yaml to what our deps already
require: Flutter >=3.35.0 / Dart >=3.9.0 (was 3.19.0 / 3.5.0). alchemist
0.12 needs Flutter 3.32; Dart 3.9 first ships in Flutter 3.35, so 3.35 is
the binding floor. Pin the exact build toolchain in .fvmrc (Flutter
3.44.1).

Moving to the Dart 3.9 language level switches `dart format` to the new
"tall" style and enables two new lints. This commit is the resulting
mechanical churn, isolated from any behaviour change:
  - whole-tree `dart format` reformat (tall style)
  - `dart fix` for unnecessary_underscores + use_null_aware_elements

No runtime behaviour change; `make test` green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 12:11:53 +02:00

354 lines
14 KiB
Dart

/// 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 ==
const Object notACellOffset = 'CellOffset(3, 5)';
expect(a == notACellOffset, 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));
const Object notASegment = 'segment';
expect(a == notASegment, 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));
const Object notALineRange = 'line range';
expect(a == notALineRange, 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));
const Object notABlockRange = 'block range';
expect(a == notABlockRange, 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));
const Object notARange = 'not a range';
expect(a == notARange, 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;
}