| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/services.dart'; |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| const List<String> platforms = <String>['linux', 'macos', 'android', 'fuchsia']; |
| |
| void _verifyKeyEvent<T extends KeyEvent>(KeyEvent event, PhysicalKeyboardKey physical, LogicalKeyboardKey logical, String? character) { |
| expect(event, isA<T>()); |
| expect(event.physicalKey, physical); |
| expect(event.logicalKey, logical); |
| expect(event.character, character); |
| expect(event.synthesized, false); |
| } |
| |
| void _verifyRawKeyEvent<T extends RawKeyEvent>(RawKeyEvent event, PhysicalKeyboardKey physical, LogicalKeyboardKey logical, String? character) { |
| expect(event, isA<T>()); |
| expect(event.physicalKey, physical); |
| expect(event.logicalKey, logical); |
| expect(event.character, character); |
| } |
| |
| Future<void> _shouldThrow<T extends Error>(AsyncValueGetter<void> func) async { |
| bool hasError = false; |
| try { |
| await func(); |
| } catch (e) { |
| expect(e, isA<T>()); |
| hasError = true; |
| } finally { |
| expect(hasError, true); |
| } |
| } |
| |
| void main() { |
| testWidgets('simulates keyboard events (RawEvent)', (WidgetTester tester) async { |
| debugKeyEventSimulatorTransitModeOverride = KeyDataTransitMode.rawKeyData; |
| |
| final List<RawKeyEvent> events = <RawKeyEvent>[]; |
| |
| final FocusNode focusNode = FocusNode(); |
| |
| await tester.pumpWidget( |
| RawKeyboardListener( |
| focusNode: focusNode, |
| onKey: events.add, |
| child: Container(), |
| ), |
| ); |
| |
| focusNode.requestFocus(); |
| await tester.idle(); |
| |
| for (final String platform in platforms) { |
| await tester.sendKeyEvent(LogicalKeyboardKey.shiftLeft, platform: platform); |
| await tester.sendKeyEvent(LogicalKeyboardKey.shift, platform: platform); |
| await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA, platform: platform); |
| await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA, platform: platform); |
| await tester.sendKeyDownEvent(LogicalKeyboardKey.numpad1, platform: platform); |
| await tester.sendKeyUpEvent(LogicalKeyboardKey.numpad1, platform: platform); |
| await tester.idle(); |
| |
| expect(events.length, 8); |
| for (int i = 0; i < events.length; ++i) { |
| final bool isEven = i.isEven; |
| if (isEven) { |
| expect(events[i].runtimeType, equals(RawKeyDownEvent)); |
| } else { |
| expect(events[i].runtimeType, equals(RawKeyUpEvent)); |
| } |
| if (i < 4) { |
| expect(events[i].data.isModifierPressed(ModifierKey.shiftModifier, side: KeyboardSide.left), equals(isEven)); |
| } |
| } |
| events.clear(); |
| } |
| |
| await tester.pumpWidget(Container()); |
| focusNode.dispose(); |
| |
| debugKeyEventSimulatorTransitModeOverride = null; |
| }); |
| |
| testWidgets('simulates keyboard events (KeyData then RawKeyEvent)', (WidgetTester tester) async { |
| debugKeyEventSimulatorTransitModeOverride = KeyDataTransitMode.keyDataThenRawKeyData; |
| |
| final List<KeyEvent> events = <KeyEvent>[]; |
| |
| final FocusNode focusNode = FocusNode(); |
| |
| await tester.pumpWidget( |
| KeyboardListener( |
| focusNode: focusNode, |
| onKeyEvent: events.add, |
| child: Container(), |
| ), |
| ); |
| |
| focusNode.requestFocus(); |
| await tester.idle(); |
| |
| // Key press shiftLeft |
| await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft); |
| expect(events.length, 1); |
| _verifyKeyEvent<KeyDownEvent>(events[0], PhysicalKeyboardKey.shiftLeft, LogicalKeyboardKey.shiftLeft, null); |
| expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.shiftLeft})); |
| expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft})); |
| expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); |
| events.clear(); |
| |
| await tester.sendKeyRepeatEvent(LogicalKeyboardKey.shiftLeft); |
| expect(events.length, 1); |
| _verifyKeyEvent<KeyRepeatEvent>(events[0], PhysicalKeyboardKey.shiftLeft, LogicalKeyboardKey.shiftLeft, null); |
| expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.shiftLeft})); |
| expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft})); |
| expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); |
| events.clear(); |
| |
| await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft); |
| expect(events.length, 1); |
| _verifyKeyEvent<KeyUpEvent>(events[0], PhysicalKeyboardKey.shiftLeft, LogicalKeyboardKey.shiftLeft, null); |
| expect(HardwareKeyboard.instance.physicalKeysPressed, isEmpty); |
| expect(HardwareKeyboard.instance.logicalKeysPressed, isEmpty); |
| expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); |
| events.clear(); |
| |
| // Key press keyA |
| await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA); |
| expect(events.length, 1); |
| _verifyKeyEvent<KeyDownEvent>(events[0], PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, 'a'); |
| expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.keyA})); |
| expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.keyA})); |
| expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); |
| events.clear(); |
| |
| await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyA); |
| _verifyKeyEvent<KeyRepeatEvent>(events[0], PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, 'a'); |
| expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.keyA})); |
| expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.keyA})); |
| expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); |
| events.clear(); |
| |
| await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA); |
| _verifyKeyEvent<KeyUpEvent>(events[0], PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, null); |
| expect(HardwareKeyboard.instance.physicalKeysPressed, isEmpty); |
| expect(HardwareKeyboard.instance.logicalKeysPressed, isEmpty); |
| expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); |
| events.clear(); |
| |
| // Key press keyA with physical keyQ |
| await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA, physicalKey: PhysicalKeyboardKey.keyQ); |
| expect(events.length, 1); |
| _verifyKeyEvent<KeyDownEvent>(events[0], PhysicalKeyboardKey.keyQ, LogicalKeyboardKey.keyA, 'a'); |
| expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.keyQ})); |
| expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.keyA})); |
| expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); |
| events.clear(); |
| |
| await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyA, physicalKey: PhysicalKeyboardKey.keyQ); |
| _verifyKeyEvent<KeyRepeatEvent>(events[0], PhysicalKeyboardKey.keyQ, LogicalKeyboardKey.keyA, 'a'); |
| expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.keyQ})); |
| expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.keyA})); |
| expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); |
| events.clear(); |
| |
| await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA, physicalKey: PhysicalKeyboardKey.keyQ); |
| _verifyKeyEvent<KeyUpEvent>(events[0], PhysicalKeyboardKey.keyQ, LogicalKeyboardKey.keyA, null); |
| expect(HardwareKeyboard.instance.physicalKeysPressed, isEmpty); |
| expect(HardwareKeyboard.instance.logicalKeysPressed, isEmpty); |
| expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); |
| events.clear(); |
| |
| // Key press numpad1 |
| await tester.sendKeyDownEvent(LogicalKeyboardKey.numpad1); |
| _verifyKeyEvent<KeyDownEvent>(events[0], PhysicalKeyboardKey.numpad1, LogicalKeyboardKey.numpad1, null); |
| expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numpad1})); |
| expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numpad1})); |
| expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); |
| events.clear(); |
| |
| await tester.sendKeyRepeatEvent(LogicalKeyboardKey.numpad1); |
| _verifyKeyEvent<KeyRepeatEvent>(events[0], PhysicalKeyboardKey.numpad1, LogicalKeyboardKey.numpad1, null); |
| expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numpad1})); |
| expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numpad1})); |
| expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); |
| events.clear(); |
| |
| await tester.sendKeyUpEvent(LogicalKeyboardKey.numpad1); |
| _verifyKeyEvent<KeyUpEvent>(events[0], PhysicalKeyboardKey.numpad1, LogicalKeyboardKey.numpad1, null); |
| expect(HardwareKeyboard.instance.physicalKeysPressed, isEmpty); |
| expect(HardwareKeyboard.instance.logicalKeysPressed, isEmpty); |
| expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); |
| events.clear(); |
| |
| // Key press numLock (1st time) |
| await tester.sendKeyDownEvent(LogicalKeyboardKey.numLock); |
| _verifyKeyEvent<KeyDownEvent>(events[0], PhysicalKeyboardKey.numLock, LogicalKeyboardKey.numLock, null); |
| expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numLock})); |
| expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numLock})); |
| expect(HardwareKeyboard.instance.lockModesEnabled, equals(<KeyboardLockMode>{KeyboardLockMode.numLock})); |
| events.clear(); |
| |
| await tester.sendKeyRepeatEvent(LogicalKeyboardKey.numLock); |
| _verifyKeyEvent<KeyRepeatEvent>(events[0], PhysicalKeyboardKey.numLock, LogicalKeyboardKey.numLock, null); |
| expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numLock})); |
| expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numLock})); |
| expect(HardwareKeyboard.instance.lockModesEnabled, equals(<KeyboardLockMode>{KeyboardLockMode.numLock})); |
| events.clear(); |
| |
| await tester.sendKeyUpEvent(LogicalKeyboardKey.numLock); |
| _verifyKeyEvent<KeyUpEvent>(events[0], PhysicalKeyboardKey.numLock, LogicalKeyboardKey.numLock, null); |
| expect(HardwareKeyboard.instance.physicalKeysPressed, isEmpty); |
| expect(HardwareKeyboard.instance.logicalKeysPressed, isEmpty); |
| expect(HardwareKeyboard.instance.lockModesEnabled, equals(<KeyboardLockMode>{KeyboardLockMode.numLock})); |
| events.clear(); |
| |
| // Key press numLock (2nd time) |
| await tester.sendKeyDownEvent(LogicalKeyboardKey.numLock); |
| _verifyKeyEvent<KeyDownEvent>(events[0], PhysicalKeyboardKey.numLock, LogicalKeyboardKey.numLock, null); |
| expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numLock})); |
| expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numLock})); |
| expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); |
| events.clear(); |
| |
| await tester.sendKeyRepeatEvent(LogicalKeyboardKey.numLock); |
| _verifyKeyEvent<KeyRepeatEvent>(events[0], PhysicalKeyboardKey.numLock, LogicalKeyboardKey.numLock, null); |
| expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numLock})); |
| expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numLock})); |
| expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); |
| events.clear(); |
| |
| await tester.sendKeyUpEvent(LogicalKeyboardKey.numLock); |
| _verifyKeyEvent<KeyUpEvent>(events[0], PhysicalKeyboardKey.numLock, LogicalKeyboardKey.numLock, null); |
| expect(HardwareKeyboard.instance.physicalKeysPressed, isEmpty); |
| expect(HardwareKeyboard.instance.logicalKeysPressed, isEmpty); |
| expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); |
| events.clear(); |
| |
| await tester.idle(); |
| |
| await tester.pumpWidget(Container()); |
| focusNode.dispose(); |
| |
| debugKeyEventSimulatorTransitModeOverride = null; |
| }); |
| |
| testWidgets('simulates using the correct transit mode: rawKeyData', (WidgetTester tester) async { |
| debugKeyEventSimulatorTransitModeOverride = KeyDataTransitMode.rawKeyData; |
| |
| final List<Object> events = <Object>[]; |
| |
| final FocusNode focusNode = FocusNode(); |
| await tester.pumpWidget( |
| Focus( |
| focusNode: focusNode, |
| onKey: (FocusNode node, RawKeyEvent event) { |
| events.add(event); |
| return KeyEventResult.ignored; |
| }, |
| onKeyEvent: (FocusNode node, KeyEvent event) { |
| events.add(event); |
| return KeyEventResult.ignored; |
| }, |
| child: Container(), |
| ), |
| ); |
| |
| focusNode.requestFocus(); |
| await tester.idle(); |
| |
| // A (physical keyA, logical keyA) is pressed. |
| await simulateKeyDownEvent(LogicalKeyboardKey.keyA); |
| expect(events.length, 2); |
| expect(events[0], isA<KeyEvent>()); |
| _verifyKeyEvent<KeyDownEvent>(events[0] as KeyEvent, PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, 'a'); |
| expect(events[1], isA<RawKeyEvent>()); |
| _verifyRawKeyEvent<RawKeyDownEvent>(events[1] as RawKeyEvent, PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, 'a'); |
| events.clear(); |
| |
| // A (physical keyA, logical keyB) is released. |
| // |
| // Since this event was synthesized and regularized before being sent to |
| // HardwareKeyboard, this event will be accepted. |
| await simulateKeyUpEvent(LogicalKeyboardKey.keyB, physicalKey: PhysicalKeyboardKey.keyA); |
| expect(events.length, 2); |
| expect(events[0], isA<KeyEvent>()); |
| _verifyKeyEvent<KeyUpEvent>(events[0] as KeyEvent, PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, null); |
| expect(events[1], isA<RawKeyEvent>()); |
| _verifyRawKeyEvent<RawKeyUpEvent>(events[1] as RawKeyEvent, PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyB, null); |
| events.clear(); |
| |
| // Manually switch the transit mode to `keyDataThenRawKeyData`. This will |
| // never happen in real applications so the assertion error can verify that |
| // the transit mode is correctly applied. |
| debugKeyEventSimulatorTransitModeOverride = KeyDataTransitMode.keyDataThenRawKeyData; |
| |
| await _shouldThrow<AssertionError>(() => |
| simulateKeyUpEvent(LogicalKeyboardKey.keyB, physicalKey: PhysicalKeyboardKey.keyA)); |
| |
| debugKeyEventSimulatorTransitModeOverride = null; |
| }); |
| |
| testWidgets('simulates using the correct transit mode: keyDataThenRawKeyData', (WidgetTester tester) async { |
| debugKeyEventSimulatorTransitModeOverride = KeyDataTransitMode.keyDataThenRawKeyData; |
| |
| final List<Object> events = <Object>[]; |
| |
| final FocusNode focusNode = FocusNode(); |
| await tester.pumpWidget( |
| Focus( |
| focusNode: focusNode, |
| onKey: (FocusNode node, RawKeyEvent event) { |
| events.add(event); |
| return KeyEventResult.ignored; |
| }, |
| onKeyEvent: (FocusNode node, KeyEvent event) { |
| events.add(event); |
| return KeyEventResult.ignored; |
| }, |
| child: Container(), |
| ), |
| ); |
| |
| focusNode.requestFocus(); |
| await tester.idle(); |
| |
| // A (physical keyA, logical keyA) is pressed. |
| await simulateKeyDownEvent(LogicalKeyboardKey.keyA); |
| expect(events.length, 2); |
| expect(events[0], isA<KeyEvent>()); |
| _verifyKeyEvent<KeyDownEvent>(events[0] as KeyEvent, PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, 'a'); |
| expect(events[1], isA<RawKeyEvent>()); |
| _verifyRawKeyEvent<RawKeyDownEvent>(events[1] as RawKeyEvent, PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, 'a'); |
| events.clear(); |
| |
| // A (physical keyA, logical keyB) is released. |
| // |
| // Since this event is transmitted to HardwareKeyboard as-is, it will be rejected due to |
| // inconsistent logical key. This does not indicate behaviral difference, |
| // since KeyData is will never send malformed data sequence in real applications. |
| await _shouldThrow<AssertionError>(() => |
| simulateKeyUpEvent(LogicalKeyboardKey.keyB, physicalKey: PhysicalKeyboardKey.keyA)); |
| |
| debugKeyEventSimulatorTransitModeOverride = null; |
| }); |
| } |