/// Coverage closeouts for `lib/src/terminal/src/core/escape/parser.dart` /// (CUF/CUB/CPL with explicit param, insertLines with param, DEC mouse /// mode 1001) and `lib/src/terminal/src/ui/render.dart` (the _onScroll /// listener body, driven via a test ViewportOffset that exposes /// notifyListeners). library; import 'package:clide/src/terminal/src/core/mouse/mode.dart'; import 'package:clide/src/terminal/src/terminal.dart'; import 'package:clide/src/terminal/src/terminal_view.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; /// Minimal ViewportOffset that exposes `notifyListeners` so we can fire /// it from a test. The real ScrollPositionWithSingleContext is hidden /// inside Scrollable; this stand-in lets us drive the offset setter /// path on RenderTerminal directly. class _TestViewportOffset extends ViewportOffset { @override double get pixels => 0; @override bool get hasPixels => true; @override bool applyViewportDimension(double viewportDimension) => true; @override bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) => true; @override void correctBy(double correction) {} @override void jumpTo(double pixels) {} @override Future animateTo(double to, {required Duration duration, required Curve curve}) async {} @override ScrollDirection get userScrollDirection => ScrollDirection.idle; @override bool get allowImplicitScrolling => false; void fire() => notifyListeners(); } Widget _host(Widget child) => Directionality( textDirection: TextDirection.ltr, child: MediaQuery( data: const MediaQueryData(), child: Center(child: SizedBox(width: 400, height: 200, child: child)), ), ); void main() { group('EscapeParser — explicit-param CSI cursor moves', () { test('CUF (ESC [ Ps C) reads params[0] and normalises 0 → 1', () { final t = Terminal(maxLines: 100, onOutput: (_) {}); t.write('A'); // Cursor at (1,0). Move forward 3 cells, write 'B'. t.write('\x1b[3C'); t.write('B'); expect(t.buffer.cursorX, greaterThanOrEqualTo(4)); // Param of 0 normalises to 1. t.write('\x1b[0C'); t.write('C'); expect(t.buffer.cursorX, greaterThanOrEqualTo(5)); }); test('CUB (ESC [ Ps D) reads params[0] and normalises 0 → 1', () { final t = Terminal(maxLines: 100, onOutput: (_) {}); t.write('ABCDEFG'); // Now at column 7. Step back 3. t.write('\x1b[3D'); // Then 0-normalised step back 1. t.write('\x1b[0D'); expect(t.buffer.cursorX, lessThan(7)); }); test('CPL (ESC [ Ps F) reads params[0] and normalises 0 → 1', () { final t = Terminal(maxLines: 100, onOutput: (_) {}); t.write('line1\r\nline2\r\nline3'); // Cursor on row 2. Go up 2 lines. t.write('\x1b[2F'); // Then 0-normalised up 1. t.write('\x1b[0F'); // Just verify the cursor moved upward — exact row is implementation // detail, but it shouldn't still be at row 2. expect(t.buffer.cursorY, lessThan(2)); }); test('insertLines (ESC [ Ps L) reads params[0]', () { final t = Terminal(maxLines: 100, onOutput: (_) {}); t.write('row0\r\nrow1\r\nrow2'); // Move cursor to home and insert 2 blank lines — just verifies the // path runs without throwing. t.write('\x1b[H\x1b[2L'); }); test('DEC private mode 1001 routes to MouseMode.upDownScroll', () { final t = Terminal(maxLines: 100, onOutput: (_) {}); // Enable mode 1001. t.write('\x1b[?1001h'); expect(t.mouseMode, MouseMode.upDownScroll); // Disable returns to none. t.write('\x1b[?1001l'); expect(t.mouseMode, MouseMode.none); }); }); group('RenderTerminal — _onScroll listener', () { testWidgets('viewport-offset notifyListeners fires _onScroll without throwing', (tester) async { final t = Terminal(maxLines: 100, onOutput: (_) {}); await tester.pumpWidget(_host(TerminalView(t))); await tester.pump(); final r = tester.state(find.byType(TerminalView)).renderTerminal; // Swap in a viewport offset we can fire on. The offset setter // attaches _onScroll as a listener. final off = _TestViewportOffset(); r.offset = off; // Force a layout so attach() has wired up listeners. await tester.pump(); // Drive the listener — runs the body of _onScroll (markNeedsLayout // + _notifyEditableRect). off.fire(); await tester.pump(); }); }); }