// 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 behavioral difference, // since KeyData is will never send malformed data sequence in real applications. await _shouldThrow<AssertionError>(() => simulateKeyUpEvent(LogicalKeyboardKey.keyB, physicalKey: PhysicalKeyboardKey.keyA)); debugKeyEventSimulatorTransitModeOverride = null; }); }