import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hbb/models/terminal_mouse_handler.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:xterm/xterm.dart'; const _captureMouse = '\x1b[?1007l\x1b[?1000h\x1b[?1002h\x1b[?1006h\x1b[?1003h'; void main() { late Terminal terminal; late TerminalController controller; late List output; setUp(() { output = []; terminal = Terminal()..onOutput = output.add; controller = TerminalController(); }); Future mount(WidgetTester tester) async { await tester.pumpWidget(MaterialApp( home: Align( alignment: Alignment.topLeft, child: Padding( padding: const EdgeInsets.only(left: 30, top: 50), child: SizedBox( width: 400, height: 120, child: TerminalMouseInteraction(terminal, controller: controller), ), ), ), )); terminal.write(List.generate(80, (i) => 'line $i\r\n').join()); await tester.pump(); final view = tester.state(find.byType(TerminalView)); expect(view.widget.scrollController!.offset, greaterThan(0)); return view; } Future scrollTrackpad( WidgetTester tester, Offset point, double distance) async { final pointer = TestPointer(2, PointerDeviceKind.trackpad); await tester.sendEventToBinding(pointer.panZoomStart(point)); await tester.sendEventToBinding( pointer.panZoomUpdate(point, pan: Offset(0, distance))); await tester.sendEventToBinding( pointer.panZoomUpdate(point, pan: Offset(0, distance * 2))); await tester.sendEventToBinding(pointer.panZoomEnd()); await tester.pump(); } testWidgets('alternate screen reports trackpad after main-screen history', (tester) async { final view = await mount(tester); terminal.write('\x1b[?1049h$_captureMouse'); await tester.pump(); final render = view.renderTerminal; final point = render.localToGlobal( Offset(render.cellSize.width * 2.5, render.lineHeight * 1.5)); await scrollTrackpad(tester, point, render.lineHeight * 3); expect(output, isNotEmpty); expect(output, everyElement('\x1b[<64;3;2M')); expect(view.widget.scrollController!.offset, 0); expect(controller.selection, isNull); }, variant: TargetPlatformVariant.only(TargetPlatform.macOS)); testWidgets('leaving alternate screen restores local trackpad scrollback', (tester) async { final view = await mount(tester); terminal.write('\x1b[?1049h$_captureMouse'); await tester.pump(); terminal.write('\x1b[?1003l\x1b[?1002l\x1b[?1000l\x1b[?1006l\x1b[?1049l'); await tester.pump(); final before = view.widget.scrollController!.offset; await scrollTrackpad( tester, view.renderTerminal.localToGlobal(const Offset(40, 40)), 48); expect(output, isEmpty); expect(view.widget.scrollController!.offset, lessThan(before)); }, variant: TargetPlatformVariant.only(TargetPlatform.macOS)); testWidgets('alternate screen without mouse capture still simulates arrows', (tester) async { final view = await mount(tester); terminal.write('\x1b[?1049h'); await tester.pump(); await scrollTrackpad( tester, view.renderTerminal.localToGlobal(const Offset(40, 40)), -48); expect(output, isNotEmpty); expect(output, everyElement('\x1b[B')); expect(view.widget.scrollController!.offset, 0); }, variant: TargetPlatformVariant.only(TargetPlatform.macOS)); }