| // Copyright 2019 The Chromium 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/material.dart'; |
| import 'package:flutter/services.dart'; |
| import 'package:flutter/src/services/keyboard_key.dart'; |
| import 'package:flutter/src/services/keyboard_maps.dart'; |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| void sendFakeKeyEvent(Map<String, dynamic> data) { |
| defaultBinaryMessenger.handlePlatformMessage( |
| SystemChannels.keyEvent.name, |
| SystemChannels.keyEvent.codec.encodeMessage(data), |
| (ByteData data) {}, |
| ); |
| } |
| |
| typedef PostInvokeCallback = void Function({Action action, Intent intent, FocusNode focusNode, ActionDispatcher dispatcher}); |
| |
| class TestAction extends CallbackAction { |
| const TestAction({ |
| @required OnInvokeCallback onInvoke, |
| }) : assert(onInvoke != null), |
| super(key, onInvoke: onInvoke); |
| |
| static const LocalKey key = ValueKey<Type>(TestAction); |
| } |
| |
| class TestDispatcher extends ActionDispatcher { |
| const TestDispatcher({this.postInvoke}); |
| |
| final PostInvokeCallback postInvoke; |
| |
| @override |
| bool invokeAction(Action action, Intent intent, {FocusNode focusNode}) { |
| final bool result = super.invokeAction(action, intent, focusNode: focusNode); |
| postInvoke?.call(action: action, intent: intent, focusNode: focusNode, dispatcher: this); |
| return result; |
| } |
| } |
| |
| class TestIntent extends Intent { |
| const TestIntent() : super(TestAction.key); |
| } |
| |
| class DoNothingAction extends Action { |
| const DoNothingAction({ |
| @required OnInvokeCallback onInvoke, |
| }) : assert(onInvoke != null), |
| super(key); |
| |
| static const LocalKey key = ValueKey<Type>(DoNothingAction); |
| |
| @override |
| void invoke(FocusNode node, Intent invocation) {} |
| } |
| |
| class DoNothingIntent extends Intent { |
| const DoNothingIntent() : super(DoNothingAction.key); |
| } |
| |
| class TestShortcutManager extends ShortcutManager { |
| TestShortcutManager(this.keys); |
| |
| List<LogicalKeyboardKey> keys; |
| |
| @override |
| bool handleKeypress(BuildContext context, RawKeyEvent event, {LogicalKeySet keysPressed}) { |
| keys.add(event.logicalKey); |
| return super.handleKeypress(context, event, keysPressed: keysPressed); |
| } |
| } |
| |
| void testKeypress(LogicalKeyboardKey key) { |
| assert(key.debugName != null); |
| int keyCode; |
| kAndroidToLogicalKey.forEach((int code, LogicalKeyboardKey codeKey) { |
| if (key == codeKey) { |
| keyCode = code; |
| } |
| }); |
| assert(keyCode != null, 'Key $key not found in Android key map'); |
| int scanCode; |
| kAndroidToPhysicalKey.forEach((int code, PhysicalKeyboardKey codeKey) { |
| if (key.debugName == codeKey.debugName) { |
| scanCode = code; |
| } |
| }); |
| assert(scanCode != null, 'Physical key for $key not found in Android key map'); |
| sendFakeKeyEvent(<String, dynamic>{ |
| 'type': 'keydown', |
| 'keymap': 'android', |
| 'keyCode': keyCode, |
| 'plainCodePoint': 0, |
| 'codePoint': 0, |
| 'character': null, |
| 'scanCode': scanCode, |
| 'metaState': 0, |
| }); |
| } |
| |
| void main() { |
| group(LogicalKeySet, () { |
| test('$LogicalKeySet passes parameters correctly.', () { |
| final LogicalKeySet set1 = LogicalKeySet(LogicalKeyboardKey.keyA); |
| final LogicalKeySet set2 = LogicalKeySet( |
| LogicalKeyboardKey.keyA, |
| LogicalKeyboardKey.keyB, |
| ); |
| final LogicalKeySet set3 = LogicalKeySet( |
| LogicalKeyboardKey.keyA, |
| LogicalKeyboardKey.keyB, |
| LogicalKeyboardKey.keyC, |
| ); |
| final LogicalKeySet set4 = LogicalKeySet( |
| LogicalKeyboardKey.keyA, |
| LogicalKeyboardKey.keyB, |
| LogicalKeyboardKey.keyC, |
| LogicalKeyboardKey.keyD, |
| ); |
| final LogicalKeySet setFromSet = LogicalKeySet.fromSet(<LogicalKeyboardKey>{ |
| LogicalKeyboardKey.keyA, |
| LogicalKeyboardKey.keyB, |
| LogicalKeyboardKey.keyC, |
| LogicalKeyboardKey.keyD, |
| }); |
| expect( |
| set1.keys, |
| equals(<LogicalKeyboardKey>{ |
| LogicalKeyboardKey.keyA, |
| })); |
| expect( |
| set2.keys, |
| equals(<LogicalKeyboardKey>{ |
| LogicalKeyboardKey.keyA, |
| LogicalKeyboardKey.keyB, |
| })); |
| expect( |
| set3.keys, |
| equals(<LogicalKeyboardKey>{ |
| LogicalKeyboardKey.keyA, |
| LogicalKeyboardKey.keyB, |
| LogicalKeyboardKey.keyC, |
| })); |
| expect( |
| set4.keys, |
| equals(<LogicalKeyboardKey>{ |
| LogicalKeyboardKey.keyA, |
| LogicalKeyboardKey.keyB, |
| LogicalKeyboardKey.keyC, |
| LogicalKeyboardKey.keyD, |
| })); |
| expect( |
| setFromSet.keys, |
| equals(<LogicalKeyboardKey>{ |
| LogicalKeyboardKey.keyA, |
| LogicalKeyboardKey.keyB, |
| LogicalKeyboardKey.keyC, |
| LogicalKeyboardKey.keyD, |
| })); |
| }); |
| test('$LogicalKeySet works as a map key.', () { |
| final LogicalKeySet set1 = LogicalKeySet(LogicalKeyboardKey.keyA); |
| final LogicalKeySet set2 = LogicalKeySet( |
| LogicalKeyboardKey.keyA, |
| LogicalKeyboardKey.keyB, |
| ); |
| final Map<LogicalKeySet, String> map = <LogicalKeySet, String>{set1: 'one'}; |
| expect(map.containsKey(set1), isTrue); |
| expect(map.containsKey(LogicalKeySet(LogicalKeyboardKey.keyA)), isTrue); |
| expect( |
| set2, |
| equals(LogicalKeySet.fromSet(<LogicalKeyboardKey>{ |
| LogicalKeyboardKey.keyA, |
| LogicalKeyboardKey.keyB, |
| }))); |
| }); |
| test('$KeySet diagnostics work.', () { |
| final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); |
| |
| LogicalKeySet( |
| LogicalKeyboardKey.keyA, |
| LogicalKeyboardKey.keyB, |
| ).debugFillProperties(builder); |
| |
| final List<String> description = builder.properties |
| .where((DiagnosticsNode node) { |
| return !node.isFiltered(DiagnosticLevel.info); |
| }) |
| .map((DiagnosticsNode node) => node.toString()) |
| .toList(); |
| |
| expect(description.length, equals(1)); |
| expect( |
| description[0], |
| equalsIgnoringHashCodes( |
| 'keys: {LogicalKeyboardKey#00000(keyId: "0x00000061", keyLabel: "a", debugName: "Key A"), LogicalKeyboardKey#00000(keyId: "0x00000062", keyLabel: "b", debugName: "Key B")}')); |
| }); |
| }); |
| group(Shortcuts, () { |
| testWidgets('$ShortcutManager handles shortcuts', (WidgetTester tester) async { |
| final GlobalKey containerKey = GlobalKey(); |
| final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[]; |
| final TestShortcutManager testManager = TestShortcutManager(pressedKeys); |
| bool invoked = false; |
| await tester.pumpWidget( |
| Actions( |
| actions: <LocalKey, ActionFactory>{ |
| TestAction.key: () => TestAction(onInvoke: (FocusNode node, Intent intent) { |
| invoked = true; |
| return true; |
| }), |
| }, |
| child: Shortcuts( |
| manager: testManager, |
| shortcuts: <LogicalKeySet, Intent>{ |
| LogicalKeySet(LogicalKeyboardKey.shift): const TestIntent(), |
| }, |
| child: Focus( |
| autofocus: true, |
| child: Container(key: containerKey, width: 100, height: 100), |
| ), |
| ), |
| ), |
| ); |
| await tester.pump(); |
| expect(Shortcuts.of(containerKey.currentContext), isNotNull); |
| testKeypress(LogicalKeyboardKey.shiftLeft); |
| expect(invoked, isTrue); |
| expect(pressedKeys, equals(<LogicalKeyboardKey>[LogicalKeyboardKey.shiftLeft])); |
| }); |
| testWidgets("$Shortcuts passes to the next $Shortcuts widget if it doesn't map the key", (WidgetTester tester) async { |
| final GlobalKey containerKey = GlobalKey(); |
| final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[]; |
| final TestShortcutManager testManager = TestShortcutManager(pressedKeys); |
| bool invoked = false; |
| await tester.pumpWidget( |
| Shortcuts( |
| manager: testManager, |
| shortcuts: <LogicalKeySet, Intent>{ |
| LogicalKeySet(LogicalKeyboardKey.shift): const TestIntent(), |
| }, |
| child: Actions( |
| actions: <LocalKey, ActionFactory>{ |
| TestAction.key: () => TestAction(onInvoke: (FocusNode node, Intent intent) { |
| invoked = true; |
| return true; |
| }), |
| }, |
| child: Shortcuts( |
| shortcuts: <LogicalKeySet, Intent>{ |
| LogicalKeySet(LogicalKeyboardKey.keyA): const DoNothingIntent(), |
| }, |
| child: Focus( |
| autofocus: true, |
| child: Container(key: containerKey, width: 100, height: 100), |
| ), |
| ), |
| ), |
| ), |
| ); |
| await tester.pump(); |
| expect(Shortcuts.of(containerKey.currentContext), isNotNull); |
| testKeypress(LogicalKeyboardKey.shiftLeft); |
| expect(invoked, isTrue); |
| expect(pressedKeys, equals(<LogicalKeyboardKey>[LogicalKeyboardKey.shiftLeft])); |
| }); |
| }); |
| } |