Files
rustdesk/flutter/test/terminal_alt_screen_scroll_test.dart
T
RustDeskandClaude Opus 5.5 9997b7a3b8 test: remove tests that do not pin user-visible behavior (#16539)
* test: remove tests that do not pin user-visible behavior

Apply the AGENTS.md test rules to the existing Rust and Flutter suites.
A test is kept when it is the only coverage of user- or API-visible
behavior, the regression test of a reported or fixed bug, a security
boundary (one test per rejection class), or a check against an external
reference. Tests that assert private state or the order of internal
steps, pin review-round corner cases, or repeat what a kept test already
covers are removed.

Rust: 679 -> 298 tests. Dart: 118 -> 45 tests.

Test-only helpers left without callers go with them
(applyLastOnlineStateForTesting, terminalKeyboardRowWidth,
debugBufferedInputCount, tokio's test-util dev feature), and cfg gates
and visibility that existed only for the deleted tests are narrowed.
Runtime behavior is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WS1HTjp6UQuG43KEcQPq1R

* test: keep tests that pin security and user-visible behavior

Restore six tests that review showed are the only coverage of their
behavior:

- update metadata rejects an artifact file name with path components;
- ICE candidates from another session are dropped;
- KCP delivers every multi-segment frame queued before a close;
- set_files rejects an empty name in a multi-file transfer;
- a lopsided elongated cursor keeps its centre hotspot (#16242);
- toggling Show hidden during a pending same-path read issues a new read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WS1HTjp6UQuG43KEcQPq1R

* test: keep offline update and key-exchange downgrade rejections

Restore three tests that are the only coverage of their rejection class:

- offline update metadata rejects a signed artifact with an http or
  credential-bearing URL;
- offline verification rejects a renamed local file and a version that
  does not match the release;
- a key-exchange pick lowered in transit leaves both sides unable to
  decrypt, so a v0 fallback after a failed v1 decrypt cannot pass
  unnoticed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WS1HTjp6UQuG43KEcQPq1R

* test: keep the decomposed-grapheme and reconnect-cancel tests

Each catches a plausible wrong change that passes every kept test:
mapping the first scalar under a locked Ctrl turns é into \x05,
and a reconnect that does not fail pending reads leaves them hanging
until the 30 s timeout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WS1HTjp6UQuG43KEcQPq1R

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 14:45:22 +08:00

97 lines
3.5 KiB
Dart

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<String> output;
setUp(() {
output = [];
terminal = Terminal()..onOutput = output.add;
controller = TerminalController();
});
Future<TerminalViewState> 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<TerminalViewState>(find.byType(TerminalView));
expect(view.widget.scrollController!.offset, greaterThan(0));
return view;
}
Future<void> 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));
}