mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
WIP Commits separated as follows: - Update lints in analysis_options files - Run `dart fix --apply` - Clean up leftover analysis issues - Run `dart format .` in the right places. Local analysis and testing passes. Checking CI now. Part of https://github.com/flutter/flutter/issues/178827 - Adoption of flutter_lints in examples/api coming in a separate change (cc @loic-sharma) ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
621 lines
22 KiB
Dart
621 lines
22 KiB
Dart
// 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 'dart:ui' as ui;
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
testWidgets(
|
|
'HardwareKeyboard records pressed keys and enabled locks',
|
|
(WidgetTester tester) async {
|
|
await simulateKeyDownEvent(LogicalKeyboardKey.numLock, platform: 'windows');
|
|
expect(
|
|
HardwareKeyboard.instance.physicalKeysPressed,
|
|
equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numLock}),
|
|
);
|
|
expect(
|
|
HardwareKeyboard.instance.logicalKeysPressed,
|
|
equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numLock}),
|
|
);
|
|
expect(
|
|
HardwareKeyboard.instance.lockModesEnabled,
|
|
equals(<KeyboardLockMode>{KeyboardLockMode.numLock}),
|
|
);
|
|
|
|
await simulateKeyDownEvent(LogicalKeyboardKey.numpad1, platform: 'windows');
|
|
expect(
|
|
HardwareKeyboard.instance.physicalKeysPressed,
|
|
equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numLock, PhysicalKeyboardKey.numpad1}),
|
|
);
|
|
expect(
|
|
HardwareKeyboard.instance.logicalKeysPressed,
|
|
equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numLock, LogicalKeyboardKey.numpad1}),
|
|
);
|
|
expect(
|
|
HardwareKeyboard.instance.lockModesEnabled,
|
|
equals(<KeyboardLockMode>{KeyboardLockMode.numLock}),
|
|
);
|
|
|
|
await simulateKeyRepeatEvent(LogicalKeyboardKey.numpad1, platform: 'windows');
|
|
expect(
|
|
HardwareKeyboard.instance.physicalKeysPressed,
|
|
equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numLock, PhysicalKeyboardKey.numpad1}),
|
|
);
|
|
expect(
|
|
HardwareKeyboard.instance.logicalKeysPressed,
|
|
equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numLock, LogicalKeyboardKey.numpad1}),
|
|
);
|
|
expect(
|
|
HardwareKeyboard.instance.lockModesEnabled,
|
|
equals(<KeyboardLockMode>{KeyboardLockMode.numLock}),
|
|
);
|
|
|
|
await simulateKeyUpEvent(LogicalKeyboardKey.numLock);
|
|
expect(
|
|
HardwareKeyboard.instance.physicalKeysPressed,
|
|
equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numpad1}),
|
|
);
|
|
expect(
|
|
HardwareKeyboard.instance.logicalKeysPressed,
|
|
equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numpad1}),
|
|
);
|
|
expect(
|
|
HardwareKeyboard.instance.lockModesEnabled,
|
|
equals(<KeyboardLockMode>{KeyboardLockMode.numLock}),
|
|
);
|
|
|
|
await simulateKeyDownEvent(LogicalKeyboardKey.numLock, platform: 'windows');
|
|
expect(
|
|
HardwareKeyboard.instance.physicalKeysPressed,
|
|
equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numLock, PhysicalKeyboardKey.numpad1}),
|
|
);
|
|
expect(
|
|
HardwareKeyboard.instance.logicalKeysPressed,
|
|
equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numLock, LogicalKeyboardKey.numpad1}),
|
|
);
|
|
expect(HardwareKeyboard.instance.lockModesEnabled, equals(<KeyboardLockMode>{}));
|
|
|
|
await simulateKeyUpEvent(LogicalKeyboardKey.numpad1, platform: 'windows');
|
|
expect(
|
|
HardwareKeyboard.instance.physicalKeysPressed,
|
|
equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.numLock}),
|
|
);
|
|
expect(
|
|
HardwareKeyboard.instance.logicalKeysPressed,
|
|
equals(<LogicalKeyboardKey>{LogicalKeyboardKey.numLock}),
|
|
);
|
|
expect(HardwareKeyboard.instance.lockModesEnabled, equals(<KeyboardLockMode>{}));
|
|
|
|
await simulateKeyUpEvent(LogicalKeyboardKey.numLock, platform: 'windows');
|
|
expect(HardwareKeyboard.instance.physicalKeysPressed, equals(<PhysicalKeyboardKey>{}));
|
|
expect(HardwareKeyboard.instance.logicalKeysPressed, equals(<LogicalKeyboardKey>{}));
|
|
expect(HardwareKeyboard.instance.lockModesEnabled, equals(<KeyboardLockMode>{}));
|
|
},
|
|
variant: KeySimulatorTransitModeVariant.keyDataThenRawKeyData(),
|
|
);
|
|
|
|
testWidgets(
|
|
'KeyEvent can tell which keys are pressed',
|
|
(WidgetTester tester) async {
|
|
await tester.pumpWidget(const Focus(autofocus: true, child: SizedBox()));
|
|
await tester.pump();
|
|
|
|
await simulateKeyDownEvent(LogicalKeyboardKey.numLock, platform: 'windows');
|
|
|
|
expect(HardwareKeyboard.instance.isPhysicalKeyPressed(PhysicalKeyboardKey.numLock), isTrue);
|
|
expect(HardwareKeyboard.instance.isLogicalKeyPressed(LogicalKeyboardKey.numLock), isTrue);
|
|
|
|
await simulateKeyDownEvent(LogicalKeyboardKey.numpad1, platform: 'windows');
|
|
expect(HardwareKeyboard.instance.isPhysicalKeyPressed(PhysicalKeyboardKey.numpad1), isTrue);
|
|
expect(HardwareKeyboard.instance.isLogicalKeyPressed(LogicalKeyboardKey.numpad1), isTrue);
|
|
|
|
await simulateKeyRepeatEvent(LogicalKeyboardKey.numpad1, platform: 'windows');
|
|
expect(HardwareKeyboard.instance.isPhysicalKeyPressed(PhysicalKeyboardKey.numpad1), isTrue);
|
|
expect(HardwareKeyboard.instance.isLogicalKeyPressed(LogicalKeyboardKey.numpad1), isTrue);
|
|
|
|
await simulateKeyUpEvent(LogicalKeyboardKey.numLock);
|
|
expect(HardwareKeyboard.instance.isPhysicalKeyPressed(PhysicalKeyboardKey.numpad1), isTrue);
|
|
expect(HardwareKeyboard.instance.isLogicalKeyPressed(LogicalKeyboardKey.numpad1), isTrue);
|
|
|
|
await simulateKeyDownEvent(LogicalKeyboardKey.numLock, platform: 'windows');
|
|
expect(HardwareKeyboard.instance.isPhysicalKeyPressed(PhysicalKeyboardKey.numLock), isTrue);
|
|
expect(HardwareKeyboard.instance.isLogicalKeyPressed(LogicalKeyboardKey.numLock), isTrue);
|
|
|
|
await simulateKeyUpEvent(LogicalKeyboardKey.numpad1, platform: 'windows');
|
|
expect(HardwareKeyboard.instance.isPhysicalKeyPressed(PhysicalKeyboardKey.numpad1), isFalse);
|
|
expect(HardwareKeyboard.instance.isLogicalKeyPressed(LogicalKeyboardKey.numpad1), isFalse);
|
|
|
|
await simulateKeyUpEvent(LogicalKeyboardKey.numLock, platform: 'windows');
|
|
expect(HardwareKeyboard.instance.isPhysicalKeyPressed(PhysicalKeyboardKey.numLock), isFalse);
|
|
expect(HardwareKeyboard.instance.isLogicalKeyPressed(LogicalKeyboardKey.numLock), isFalse);
|
|
},
|
|
variant: KeySimulatorTransitModeVariant.keyDataThenRawKeyData(),
|
|
);
|
|
|
|
testWidgets('KeyboardManager synthesizes modifier keys in rawKeyData mode', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final events = <KeyEvent>[];
|
|
HardwareKeyboard.instance.addHandler((KeyEvent event) {
|
|
events.add(event);
|
|
return false;
|
|
});
|
|
// While ShiftLeft is held (the event of which was skipped), press keyA.
|
|
final Map<String, dynamic> rawMessage = kIsWeb
|
|
? (KeyEventSimulator.getKeyData(LogicalKeyboardKey.keyA, platform: 'web')
|
|
..['metaState'] = RawKeyEventDataWeb.modifierShift)
|
|
: (KeyEventSimulator.getKeyData(LogicalKeyboardKey.keyA, platform: 'android')
|
|
..['metaState'] =
|
|
RawKeyEventDataAndroid.modifierLeftShift | RawKeyEventDataAndroid.modifierShift);
|
|
tester.binding.keyEventManager.handleRawKeyMessage(rawMessage);
|
|
expect(events, hasLength(2));
|
|
expect(events[0].physicalKey, PhysicalKeyboardKey.shiftLeft);
|
|
expect(events[0].logicalKey, LogicalKeyboardKey.shiftLeft);
|
|
expect(events[0].synthesized, true);
|
|
expect(events[1].physicalKey, PhysicalKeyboardKey.keyA);
|
|
expect(events[1].logicalKey, LogicalKeyboardKey.keyA);
|
|
expect(events[1].synthesized, false);
|
|
});
|
|
|
|
testWidgets('Dispatch events to all handlers', (WidgetTester tester) async {
|
|
final focusNode = FocusNode();
|
|
addTearDown(focusNode.dispose);
|
|
final logs = <int>[];
|
|
|
|
await tester.pumpWidget(
|
|
KeyboardListener(
|
|
autofocus: true,
|
|
focusNode: focusNode,
|
|
child: Container(),
|
|
onKeyEvent: (KeyEvent event) {
|
|
logs.add(1);
|
|
},
|
|
),
|
|
);
|
|
|
|
// Only the Service binding handler.
|
|
|
|
expect(await simulateKeyDownEvent(LogicalKeyboardKey.keyA), false);
|
|
expect(logs, <int>[1]);
|
|
logs.clear();
|
|
|
|
// Add a handler.
|
|
|
|
var handler2Result = false;
|
|
bool handler2(KeyEvent event) {
|
|
logs.add(2);
|
|
return handler2Result;
|
|
}
|
|
|
|
HardwareKeyboard.instance.addHandler(handler2);
|
|
|
|
expect(await simulateKeyUpEvent(LogicalKeyboardKey.keyA), false);
|
|
expect(logs, <int>[2, 1]);
|
|
logs.clear();
|
|
|
|
handler2Result = true;
|
|
|
|
expect(await simulateKeyDownEvent(LogicalKeyboardKey.keyA), true);
|
|
expect(logs, <int>[2, 1]);
|
|
logs.clear();
|
|
|
|
// Add another handler.
|
|
|
|
handler2Result = false;
|
|
var handler3Result = false;
|
|
bool handler3(KeyEvent event) {
|
|
logs.add(3);
|
|
return handler3Result;
|
|
}
|
|
|
|
HardwareKeyboard.instance.addHandler(handler3);
|
|
|
|
expect(await simulateKeyUpEvent(LogicalKeyboardKey.keyA), false);
|
|
expect(logs, <int>[2, 3, 1]);
|
|
logs.clear();
|
|
|
|
handler2Result = true;
|
|
|
|
expect(await simulateKeyDownEvent(LogicalKeyboardKey.keyA), true);
|
|
expect(logs, <int>[2, 3, 1]);
|
|
logs.clear();
|
|
|
|
handler3Result = true;
|
|
|
|
expect(await simulateKeyUpEvent(LogicalKeyboardKey.keyA), true);
|
|
expect(logs, <int>[2, 3, 1]);
|
|
logs.clear();
|
|
|
|
// Add handler2 again.
|
|
|
|
HardwareKeyboard.instance.addHandler(handler2);
|
|
|
|
handler3Result = false;
|
|
handler2Result = false;
|
|
expect(await simulateKeyDownEvent(LogicalKeyboardKey.keyA), false);
|
|
expect(logs, <int>[2, 3, 2, 1]);
|
|
logs.clear();
|
|
|
|
handler2Result = true;
|
|
expect(await simulateKeyUpEvent(LogicalKeyboardKey.keyA), true);
|
|
expect(logs, <int>[2, 3, 2, 1]);
|
|
logs.clear();
|
|
|
|
// Remove handler2 once.
|
|
|
|
HardwareKeyboard.instance.removeHandler(handler2);
|
|
expect(await simulateKeyDownEvent(LogicalKeyboardKey.keyA), true);
|
|
expect(logs, <int>[3, 2, 1]);
|
|
logs.clear();
|
|
}, variant: KeySimulatorTransitModeVariant.all());
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/99196 .
|
|
//
|
|
// In rawKeyData mode, if a key down event is dispatched but immediately
|
|
// synthesized to be released, the old logic would trigger a Null check
|
|
// _CastError on _hardwareKeyboard.lookUpLayout(key). The original scenario
|
|
// that this is triggered on Android is unknown. Here we make up a scenario
|
|
// where a ShiftLeft key down is dispatched but the modifier bit is not set.
|
|
testWidgets(
|
|
'Correctly convert down events that are synthesized released',
|
|
(WidgetTester tester) async {
|
|
final focusNode = FocusNode();
|
|
addTearDown(focusNode.dispose);
|
|
final events = <KeyEvent>[];
|
|
|
|
await tester.pumpWidget(
|
|
KeyboardListener(
|
|
autofocus: true,
|
|
focusNode: focusNode,
|
|
child: Container(),
|
|
onKeyEvent: (KeyEvent event) {
|
|
events.add(event);
|
|
},
|
|
),
|
|
);
|
|
|
|
// Dispatch an arbitrary event to bypass the pressedKeys check.
|
|
await simulateKeyDownEvent(LogicalKeyboardKey.keyA, platform: 'web');
|
|
|
|
// Dispatch an
|
|
final Map<String, dynamic> data2 = KeyEventSimulator.getKeyData(
|
|
LogicalKeyboardKey.shiftLeft,
|
|
platform: 'web',
|
|
)..['metaState'] = 0;
|
|
await tester.binding.defaultBinaryMessenger.handlePlatformMessage(
|
|
SystemChannels.keyEvent.name,
|
|
SystemChannels.keyEvent.codec.encodeMessage(data2),
|
|
(ByteData? data) {},
|
|
);
|
|
|
|
expect(events, hasLength(3));
|
|
expect(events[1], isA<KeyDownEvent>());
|
|
expect(events[1].logicalKey, LogicalKeyboardKey.shiftLeft);
|
|
expect(events[1].synthesized, false);
|
|
expect(events[2], isA<KeyUpEvent>());
|
|
expect(events[2].logicalKey, LogicalKeyboardKey.shiftLeft);
|
|
expect(events[2].synthesized, true);
|
|
expect(
|
|
ServicesBinding.instance.keyboard.physicalKeysPressed,
|
|
equals(<PhysicalKeyboardKey>{PhysicalKeyboardKey.keyA}),
|
|
);
|
|
},
|
|
variant: const KeySimulatorTransitModeVariant(<KeyDataTransitMode>{
|
|
KeyDataTransitMode.rawKeyData,
|
|
}),
|
|
);
|
|
|
|
testWidgets(
|
|
'Instantly dispatch synthesized key events when the queue is empty',
|
|
(WidgetTester tester) async {
|
|
final focusNode = FocusNode();
|
|
addTearDown(focusNode.dispose);
|
|
final logs = <int>[];
|
|
|
|
await tester.pumpWidget(
|
|
KeyboardListener(
|
|
autofocus: true,
|
|
focusNode: focusNode,
|
|
child: Container(),
|
|
onKeyEvent: (KeyEvent event) {
|
|
logs.add(1);
|
|
},
|
|
),
|
|
);
|
|
ServicesBinding.instance.keyboard.addHandler((KeyEvent event) {
|
|
logs.add(2);
|
|
return false;
|
|
});
|
|
|
|
// Dispatch a solitary synthesized event.
|
|
expect(
|
|
ServicesBinding.instance.keyEventManager.handleKeyData(
|
|
ui.KeyData(
|
|
timeStamp: Duration.zero,
|
|
type: ui.KeyEventType.down,
|
|
logical: LogicalKeyboardKey.keyA.keyId,
|
|
physical: PhysicalKeyboardKey.keyA.usbHidUsage,
|
|
character: null,
|
|
synthesized: true,
|
|
),
|
|
),
|
|
false,
|
|
);
|
|
expect(logs, <int>[2, 1]);
|
|
logs.clear();
|
|
},
|
|
variant: KeySimulatorTransitModeVariant.keyDataThenRawKeyData(),
|
|
);
|
|
|
|
testWidgets(
|
|
'Postpone synthesized key events when the queue is not empty',
|
|
(WidgetTester tester) async {
|
|
final keyboardListenerFocusNode = FocusNode();
|
|
addTearDown(keyboardListenerFocusNode.dispose);
|
|
final rawKeyboardListenerFocusNode = FocusNode();
|
|
addTearDown(rawKeyboardListenerFocusNode.dispose);
|
|
final logs = <String>[];
|
|
|
|
await tester.pumpWidget(
|
|
RawKeyboardListener(
|
|
focusNode: rawKeyboardListenerFocusNode,
|
|
onKey: (RawKeyEvent event) {
|
|
logs.add('${event.runtimeType}');
|
|
},
|
|
child: KeyboardListener(
|
|
autofocus: true,
|
|
focusNode: keyboardListenerFocusNode,
|
|
child: Container(),
|
|
onKeyEvent: (KeyEvent event) {
|
|
logs.add('${event.runtimeType}');
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
// On macOS, a CapsLock tap yields a down event and a synthesized up event.
|
|
expect(
|
|
ServicesBinding.instance.keyEventManager.handleKeyData(
|
|
ui.KeyData(
|
|
timeStamp: Duration.zero,
|
|
type: ui.KeyEventType.down,
|
|
logical: LogicalKeyboardKey.capsLock.keyId,
|
|
physical: PhysicalKeyboardKey.capsLock.usbHidUsage,
|
|
character: null,
|
|
synthesized: false,
|
|
),
|
|
),
|
|
false,
|
|
);
|
|
expect(
|
|
ServicesBinding.instance.keyEventManager.handleKeyData(
|
|
ui.KeyData(
|
|
timeStamp: Duration.zero,
|
|
type: ui.KeyEventType.up,
|
|
logical: LogicalKeyboardKey.capsLock.keyId,
|
|
physical: PhysicalKeyboardKey.capsLock.usbHidUsage,
|
|
character: null,
|
|
synthesized: true,
|
|
),
|
|
),
|
|
false,
|
|
);
|
|
expect(
|
|
await ServicesBinding.instance.keyEventManager.handleRawKeyMessage(<String, dynamic>{
|
|
'type': 'keydown',
|
|
'keymap': 'macos',
|
|
'keyCode': 0x00000039,
|
|
'characters': '',
|
|
'charactersIgnoringModifiers': '',
|
|
'modifiers': 0x10000,
|
|
}),
|
|
equals(<String, dynamic>{'handled': false}),
|
|
);
|
|
|
|
expect(logs, <String>['RawKeyDownEvent', 'KeyDownEvent', 'KeyUpEvent']);
|
|
logs.clear();
|
|
},
|
|
variant: KeySimulatorTransitModeVariant.keyDataThenRawKeyData(),
|
|
);
|
|
|
|
// The first key data received from the engine might be an empty key data.
|
|
// In that case, the key data should not be converted to any [KeyEvent]s,
|
|
// but is only used so that *a* key data comes before the raw key message
|
|
// and makes [KeyEventManager] infer [KeyDataTransitMode.keyDataThenRawKeyData].
|
|
testWidgets('Empty keyData yields no event but triggers inference', (WidgetTester tester) async {
|
|
final events = <KeyEvent>[];
|
|
final rawEvents = <RawKeyEvent>[];
|
|
tester.binding.keyboard.addHandler((KeyEvent event) {
|
|
events.add(event);
|
|
return true;
|
|
});
|
|
RawKeyboard.instance.addListener((RawKeyEvent event) {
|
|
rawEvents.add(event);
|
|
});
|
|
tester.binding.keyEventManager.handleKeyData(
|
|
const ui.KeyData(
|
|
type: ui.KeyEventType.down,
|
|
timeStamp: Duration.zero,
|
|
logical: 0,
|
|
physical: 0,
|
|
character: 'a',
|
|
synthesized: false,
|
|
),
|
|
);
|
|
tester.binding.keyEventManager.handleRawKeyMessage(<String, dynamic>{
|
|
'type': 'keydown',
|
|
'keymap': 'windows',
|
|
'keyCode': 0x04,
|
|
'scanCode': 0x04,
|
|
'characterCodePoint': 0,
|
|
'modifiers': 0,
|
|
});
|
|
expect(events.length, 0);
|
|
expect(rawEvents.length, 1);
|
|
|
|
// Dispatch another key data to ensure it's in
|
|
// [KeyDataTransitMode.keyDataThenRawKeyData] mode (otherwise assertion
|
|
// will be thrown upon a KeyData).
|
|
tester.binding.keyEventManager.handleKeyData(
|
|
const ui.KeyData(
|
|
type: ui.KeyEventType.down,
|
|
timeStamp: Duration.zero,
|
|
logical: 0x22,
|
|
physical: 0x70034,
|
|
character: '"',
|
|
synthesized: false,
|
|
),
|
|
);
|
|
tester.binding.keyEventManager.handleRawKeyMessage(<String, dynamic>{
|
|
'type': 'keydown',
|
|
'keymap': 'windows',
|
|
'keyCode': 0x04,
|
|
'scanCode': 0x04,
|
|
'characterCodePoint': 0,
|
|
'modifiers': 0,
|
|
});
|
|
expect(events.length, 1);
|
|
expect(rawEvents.length, 2);
|
|
});
|
|
|
|
testWidgets('Exceptions from keyMessageHandler are caught and reported', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final KeyMessageHandler? oldKeyMessageHandler =
|
|
tester.binding.keyEventManager.keyMessageHandler;
|
|
addTearDown(() {
|
|
tester.binding.keyEventManager.keyMessageHandler = oldKeyMessageHandler;
|
|
});
|
|
|
|
// When keyMessageHandler throws an error...
|
|
tester.binding.keyEventManager.keyMessageHandler = (KeyMessage message) {
|
|
throw 1;
|
|
};
|
|
|
|
// Simulate a key down event.
|
|
FlutterErrorDetails? record;
|
|
await _runWhileOverridingOnError(
|
|
() => simulateKeyDownEvent(LogicalKeyboardKey.keyA),
|
|
onError: (FlutterErrorDetails details) {
|
|
record = details;
|
|
},
|
|
);
|
|
|
|
// ... the error should be caught.
|
|
expect(record, isNotNull);
|
|
expect(record!.exception, 1);
|
|
final Map<String, DiagnosticsNode> infos = _groupDiagnosticsByName(
|
|
record!.informationCollector!(),
|
|
);
|
|
expect(infos['KeyMessage'], isA<DiagnosticsProperty<KeyMessage>>());
|
|
|
|
// But the exception should not interrupt recording the state.
|
|
// Now the keyMessageHandler no longer throws an error.
|
|
tester.binding.keyEventManager.keyMessageHandler = null;
|
|
record = null;
|
|
|
|
// Simulate a key up event.
|
|
await _runWhileOverridingOnError(
|
|
() => simulateKeyUpEvent(LogicalKeyboardKey.keyA),
|
|
onError: (FlutterErrorDetails details) {
|
|
record = details;
|
|
},
|
|
);
|
|
// If the previous state (key down) wasn't recorded, this key up event will
|
|
// trigger assertions.
|
|
expect(record, isNull);
|
|
});
|
|
|
|
testWidgets(
|
|
'Exceptions from HardwareKeyboard handlers are caught and reported',
|
|
(WidgetTester tester) async {
|
|
bool throwingCallback(KeyEvent event) {
|
|
throw 1;
|
|
}
|
|
|
|
// When the handler throws an error...
|
|
HardwareKeyboard.instance.addHandler(throwingCallback);
|
|
|
|
// Simulate a key down event.
|
|
FlutterErrorDetails? record;
|
|
await _runWhileOverridingOnError(
|
|
() => simulateKeyDownEvent(LogicalKeyboardKey.keyA),
|
|
onError: (FlutterErrorDetails details) {
|
|
record = details;
|
|
},
|
|
);
|
|
|
|
// ... the error should be caught.
|
|
expect(record, isNotNull);
|
|
expect(record!.exception, 1);
|
|
final Map<String, DiagnosticsNode> infos = _groupDiagnosticsByName(
|
|
record!.informationCollector!(),
|
|
);
|
|
expect(infos['Event'], isA<DiagnosticsProperty<KeyEvent>>());
|
|
|
|
// But the exception should not interrupt recording the state.
|
|
// Now the key handler no longer throws an error.
|
|
HardwareKeyboard.instance.removeHandler(throwingCallback);
|
|
record = null;
|
|
|
|
// Simulate a key up event.
|
|
await _runWhileOverridingOnError(
|
|
() => simulateKeyUpEvent(LogicalKeyboardKey.keyA),
|
|
onError: (FlutterErrorDetails details) {
|
|
record = details;
|
|
},
|
|
);
|
|
// If the previous state (key down) wasn't recorded, this key up event will
|
|
// trigger assertions.
|
|
expect(record, isNull);
|
|
},
|
|
variant: KeySimulatorTransitModeVariant.all(),
|
|
);
|
|
|
|
testWidgets('debugPrintKeyboardEvents causes logging of key events', (WidgetTester tester) async {
|
|
final bool oldDebugPrintKeyboardEvents = debugPrintKeyboardEvents;
|
|
final DebugPrintCallback oldDebugPrint = debugPrint;
|
|
final messages = StringBuffer();
|
|
debugPrint = (String? message, {int? wrapWidth}) {
|
|
messages.writeln(message ?? '');
|
|
};
|
|
debugPrintKeyboardEvents = true;
|
|
try {
|
|
await simulateKeyDownEvent(LogicalKeyboardKey.keyA);
|
|
} finally {
|
|
debugPrintKeyboardEvents = oldDebugPrintKeyboardEvents;
|
|
debugPrint = oldDebugPrint;
|
|
}
|
|
final messagesStr = messages.toString();
|
|
expect(messagesStr, contains('KEYBOARD: Key event received: '));
|
|
expect(messagesStr, contains('KEYBOARD: Pressed state before processing the event:'));
|
|
expect(messagesStr, contains('KEYBOARD: Pressed state after processing the event:'));
|
|
});
|
|
}
|
|
|
|
Future<void> _runWhileOverridingOnError(
|
|
AsyncCallback body, {
|
|
required FlutterExceptionHandler onError,
|
|
}) async {
|
|
final FlutterExceptionHandler? oldFlutterErrorOnError = FlutterError.onError;
|
|
FlutterError.onError = onError;
|
|
|
|
try {
|
|
await body();
|
|
} finally {
|
|
FlutterError.onError = oldFlutterErrorOnError;
|
|
}
|
|
}
|
|
|
|
Map<String, DiagnosticsNode> _groupDiagnosticsByName(Iterable<DiagnosticsNode> infos) {
|
|
return Map<String, DiagnosticsNode>.fromIterable(
|
|
infos,
|
|
key: (dynamic node) => (node as DiagnosticsNode).name ?? '',
|
|
);
|
|
}
|