fix(tvos): stabilize remote select handling

This commit is contained in:
edde746
2026-05-15 22:16:24 +02:00
parent 5df3ec0c12
commit 91298294b0
5 changed files with 155 additions and 54 deletions
@@ -134,48 +134,60 @@ void main() {
expect(harness.keys, [LogicalKeyboardKey.arrowLeft]);
});
test('deduplicates touch tap and click fallback select events', () async {
test('click events emit held select key down and up', () async {
final harness = _Harness();
await harness.send('started', x: 500, y: 500);
await harness.send('ended', x: 500, y: 500);
await harness.send('click_s');
await harness.send('click_e');
expect(harness.keys, [LogicalKeyboardKey.enter]);
expect(harness.keyDowns, [LogicalKeyboardKey.enter]);
expect(harness.keyUps, [LogicalKeyboardKey.enter]);
harness.advance(const Duration(milliseconds: 121));
await harness.send('click_s');
await harness.send('click_e');
expect(harness.keys, [LogicalKeyboardKey.enter, LogicalKeyboardKey.enter]);
expect(harness.keyDowns, [LogicalKeyboardKey.enter, LogicalKeyboardKey.enter]);
expect(harness.keyUps, [LogicalKeyboardKey.enter, LogicalKeyboardKey.enter]);
});
test('native select suppresses click fallback from physical remote path', () async {
final harness = _Harness();
harness.service.handleNativeKeyEvent(_keyDown(LogicalKeyboardKey.select));
await harness.send('click_s');
await harness.send('click_e');
expect(harness.keys, isEmpty);
expect(harness.keyDowns, isEmpty);
expect(harness.keyUps, isEmpty);
harness.service.handleNativeKeyEvent(_keyUp(LogicalKeyboardKey.select));
harness.advance(const Duration(milliseconds: 121));
await harness.send('click_s');
await harness.send('click_e');
expect(harness.keys, [LogicalKeyboardKey.enter]);
expect(harness.keyDowns, [LogicalKeyboardKey.enter]);
expect(harness.keyUps, [LogicalKeyboardKey.enter]);
});
test('recent directional input suppresses click fallback', () async {
final harness = _Harness();
harness.service.handleNativeKeyEvent(_keyDown(LogicalKeyboardKey.arrowLeft));
await harness.send('click_s');
await harness.send('click_e');
expect(harness.keys, isEmpty);
expect(harness.keyDowns, isEmpty);
expect(harness.keyUps, isEmpty);
harness.advance(const Duration(milliseconds: 221));
await harness.send('click_s');
await harness.send('click_e');
expect(harness.keys, [LogicalKeyboardKey.enter]);
expect(harness.keyDowns, [LogicalKeyboardKey.enter]);
expect(harness.keyUps, [LogicalKeyboardKey.enter]);
});
test('synthetic swipe suppresses click fallback', () async {
@@ -183,9 +195,12 @@ void main() {
await harness.send('started', x: 500, y: 500);
await harness.send('move', x: 380, y: 500);
await harness.send('click_s');
await harness.send('click_e');
expect(harness.keys, [LogicalKeyboardKey.arrowLeft]);
expect(harness.keyDowns, isEmpty);
expect(harness.keyUps, isEmpty);
});
test('cancelled touch does not emit select on a later ended message', () async {
@@ -204,9 +219,13 @@ void main() {
class _Harness {
DateTime now = DateTime(2026, 5, 5, 12);
final List<LogicalKeyboardKey> keys = [];
final List<LogicalKeyboardKey> keyDowns = [];
final List<LogicalKeyboardKey> keyUps = [];
late final AppleTvRemoteTouchService service = AppleTvRemoteTouchService(
simulateKeyPress: keys.add,
simulateKeyDown: keyDowns.add,
simulateKeyUp: keyUps.add,
scheduleFrame: () {},
now: () => now,
swipeThreshold: 100,
+42 -23
View File
@@ -7,29 +7,7 @@ import 'package:plezy/utils/key_event_simulator.dart';
void main() {
testWidgets('simulateKeyPress dispatches directional pad key events', (tester) async {
final events = <KeyEvent>[];
late BuildContext focusContext;
await tester.pumpWidget(
MaterialApp(
home: Focus(
autofocus: true,
onKeyEvent: (_, event) {
events.add(event);
return KeyEventResult.handled;
},
child: Builder(
builder: (context) {
focusContext = context;
return const SizedBox.shrink();
},
),
),
),
);
Focus.of(focusContext).requestFocus();
await tester.pump();
expect(Focus.of(focusContext).hasPrimaryFocus, isTrue);
final events = await _pumpKeyEventRecorder(tester);
scheduleFrameIfIdle();
simulateKeyPress(LogicalKeyboardKey.enter);
@@ -39,4 +17,45 @@ void main() {
expect(events, hasLength(2));
expect(events.map((event) => event.deviceType), everyElement(ui.KeyEventDeviceType.directionalPad));
});
testWidgets('simulateKeyDown and simulateKeyUp dispatch held directional pad events', (tester) async {
final events = await _pumpKeyEventRecorder(tester);
simulateKeyDown(LogicalKeyboardKey.enter);
simulateKeyUp(LogicalKeyboardKey.enter);
await tester.pump();
await tester.pump();
expect(events, hasLength(2));
expect(events[0], isA<KeyDownEvent>());
expect(events[1], isA<KeyUpEvent>());
expect(events.map((event) => event.deviceType), everyElement(ui.KeyEventDeviceType.directionalPad));
});
}
Future<List<KeyEvent>> _pumpKeyEventRecorder(WidgetTester tester) async {
final events = <KeyEvent>[];
late BuildContext focusContext;
await tester.pumpWidget(
MaterialApp(
home: Focus(
autofocus: true,
onKeyEvent: (_, event) {
events.add(event);
return KeyEventResult.handled;
},
child: Builder(
builder: (context) {
focusContext = context;
return const SizedBox.shrink();
},
),
),
),
);
Focus.of(focusContext).requestFocus();
await tester.pump();
expect(Focus.of(focusContext).hasPrimaryFocus, isTrue);
return events;
}