fix(tvos): stabilize remote select handling
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user