mirror of
https://github.com/flutter/flutter
synced 2024-09-13 21:32:11 +00:00
Refactor macOS text editing shortcuts (#110289)
This commit is contained in:
parent
6ce01ed8c9
commit
1dd6a23358
|
@ -258,32 +258,7 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
|
|||
// macOS document shortcuts: https://support.apple.com/en-us/HT201236.
|
||||
// The macOS shortcuts uses different word/line modifiers than most other
|
||||
// platforms.
|
||||
static final Map<ShortcutActivator, Intent> _macShortcuts = <ShortcutActivator, Intent>{
|
||||
const SingleActivator(LogicalKeyboardKey.keyX, meta: true): const CopySelectionTextIntent.cut(SelectionChangedCause.keyboard),
|
||||
const SingleActivator(LogicalKeyboardKey.keyC, meta: true): CopySelectionTextIntent.copy,
|
||||
const SingleActivator(LogicalKeyboardKey.keyV, meta: true): const PasteTextIntent(SelectionChangedCause.keyboard),
|
||||
const SingleActivator(LogicalKeyboardKey.keyA, meta: true): const SelectAllTextIntent(SelectionChangedCause.keyboard),
|
||||
const SingleActivator(LogicalKeyboardKey.keyZ, meta: true): const UndoTextIntent(SelectionChangedCause.keyboard),
|
||||
const SingleActivator(LogicalKeyboardKey.keyZ, shift: true, meta: true): const RedoTextIntent(SelectionChangedCause.keyboard),
|
||||
|
||||
// On desktop these keys should go to the IME when a field is focused, not to other
|
||||
// Shortcuts.
|
||||
if (!kIsWeb) ...<ShortcutActivator, Intent>{
|
||||
const SingleActivator(LogicalKeyboardKey.arrowLeft): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowRight): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowUp): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowDown): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowRight, meta: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowUp, meta: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowDown, meta: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.escape): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.space): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.enter): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.tab): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.tab, shift: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
},
|
||||
};
|
||||
static final Map<ShortcutActivator, Intent> _macShortcuts = _iOSShortcuts;
|
||||
|
||||
// There is no complete documentation of iOS shortcuts.
|
||||
static final Map<ShortcutActivator, Intent> _iOSShortcuts = <ShortcutActivator, Intent>{
|
||||
|
@ -399,42 +374,7 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
|
|||
SingleActivator(LogicalKeyboardKey.backspace, meta: true, shift: pressShift): const DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.delete, meta: true, shift: pressShift): const DoNothingAndStopPropagationTextIntent(),
|
||||
},
|
||||
const SingleActivator(LogicalKeyboardKey.arrowDown, alt: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowRight, alt: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowUp, alt: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowDown, shift: true, alt: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, alt: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, alt: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowUp, shift: true, alt: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowDown): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowLeft): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowRight): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowUp): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowLeft, control: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowRight, control: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, control: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, control: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.end): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.home): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowDown, meta: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowRight, meta: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowUp, meta: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowDown, shift: true, meta: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, meta: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, meta: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowUp, shift: true, meta: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowDown, shift: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.end, shift: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.home, shift: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.end, control: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.home, control: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.space): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.enter): const DoNothingAndStopPropagationTextIntent(),
|
||||
..._commonDisablingTextShortcuts,
|
||||
const SingleActivator(LogicalKeyboardKey.keyX, control: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.keyX, meta: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.keyC, control: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
|
@ -445,6 +385,53 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
|
|||
const SingleActivator(LogicalKeyboardKey.keyA, meta: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
};
|
||||
|
||||
|
||||
static const Map<ShortcutActivator, Intent> _commonDisablingTextShortcuts = <ShortcutActivator, Intent>{
|
||||
SingleActivator(LogicalKeyboardKey.arrowDown, alt: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowRight, alt: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowUp, alt: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowDown, shift: true, alt: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, alt: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, alt: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true, alt: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowDown, meta: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowRight, meta: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowUp, meta: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowDown, shift: true, meta: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, meta: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, meta: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true, meta: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowDown, shift: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.end, shift: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.home, shift: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowDown): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowLeft): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowRight): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowUp): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowLeft, control: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowRight, control: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, control: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, control: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.end): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.home): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.end, control: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.home, control: true): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.space): DoNothingAndStopPropagationTextIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.enter): DoNothingAndStopPropagationTextIntent(),
|
||||
};
|
||||
|
||||
static final Map<ShortcutActivator, Intent> _macDisablingTextShortcuts = <ShortcutActivator, Intent>{
|
||||
..._commonDisablingTextShortcuts,
|
||||
const SingleActivator(LogicalKeyboardKey.escape): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.tab): const DoNothingAndStopPropagationTextIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.tab, shift: true): const DoNothingAndStopPropagationTextIntent(),
|
||||
};
|
||||
|
||||
static Map<ShortcutActivator, Intent> get _shortcuts {
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
|
@ -462,21 +449,39 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
Map<ShortcutActivator, Intent>? _getDisablingShortcut() {
|
||||
if (kIsWeb) {
|
||||
return _webDisablingTextShortcuts;
|
||||
}
|
||||
switch (defaultTargetPlatform) {
|
||||
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
return null;
|
||||
case TargetPlatform.macOS:
|
||||
return _macDisablingTextShortcuts;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget result = child;
|
||||
if (kIsWeb) {
|
||||
// On the web, these shortcuts make sure of the following:
|
||||
final Map<ShortcutActivator, Intent>? disablingShortcut = _getDisablingShortcut();
|
||||
if (disablingShortcut != null) {
|
||||
// These shortcuts make sure of the following:
|
||||
//
|
||||
// 1. Shortcuts fired when an EditableText is focused are ignored and
|
||||
// forwarded to the browser by the EditableText's Actions, because it
|
||||
// forwarded to the platform by the EditableText's Actions, because it
|
||||
// maps DoNothingAndStopPropagationTextIntent to DoNothingAction.
|
||||
// 2. Shortcuts fired when no EditableText is focused will still trigger
|
||||
// _shortcuts assuming DoNothingAndStopPropagationTextIntent is
|
||||
// unhandled elsewhere.
|
||||
result = Shortcuts(
|
||||
debugLabel: '<Web Disabling Text Editing Shortcuts>',
|
||||
shortcuts: _webDisablingTextShortcuts,
|
||||
shortcuts: disablingShortcut,
|
||||
child: result
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,439 @@
|
|||
// 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/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
Future<void> sendKeyCombination(
|
||||
WidgetTester tester,
|
||||
SingleActivator activator,
|
||||
) async {
|
||||
final List<LogicalKeyboardKey> modifiers = <LogicalKeyboardKey>[
|
||||
if (activator.control) LogicalKeyboardKey.control,
|
||||
if (activator.shift) LogicalKeyboardKey.shift,
|
||||
if (activator.alt) LogicalKeyboardKey.alt,
|
||||
if (activator.meta) LogicalKeyboardKey.meta,
|
||||
];
|
||||
for (final LogicalKeyboardKey modifier in modifiers) {
|
||||
await tester.sendKeyDownEvent(modifier);
|
||||
}
|
||||
await tester.sendKeyDownEvent(activator.trigger);
|
||||
await tester.sendKeyUpEvent(activator.trigger);
|
||||
await tester.pump();
|
||||
for (final LogicalKeyboardKey modifier in modifiers.reversed) {
|
||||
await tester.sendKeyUpEvent(modifier);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
Widget buildSpyAboveEditableText({
|
||||
required FocusNode editableFocusNode,
|
||||
required FocusNode spyFocusNode,
|
||||
}) {
|
||||
return MaterialApp(
|
||||
home: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: SizedBox(
|
||||
// Softwrap at exactly 20 characters.
|
||||
width: 201,
|
||||
height: 200,
|
||||
child: ActionSpy(
|
||||
focusNode: spyFocusNode,
|
||||
child: EditableText(
|
||||
controller: TextEditingController(text: 'dummy text'),
|
||||
showSelectionHandles: true,
|
||||
autofocus: true,
|
||||
focusNode: editableFocusNode,
|
||||
style: const TextStyle(fontSize: 10.0),
|
||||
textScaleFactor: 1,
|
||||
// Avoid the cursor from taking up width.
|
||||
cursorWidth: 0,
|
||||
cursorColor: Colors.blue,
|
||||
backgroundCursorColor: Colors.grey,
|
||||
selectionControls: materialTextSelectionControls,
|
||||
keyboardType: TextInputType.text,
|
||||
maxLines: null,
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
group('macOS does not accept shortcuts if focus under EditableText', () {
|
||||
final TargetPlatformVariant macOSOnly = TargetPlatformVariant.only(TargetPlatform.macOS);
|
||||
|
||||
testWidgets('word modifier + arrowLeft', (WidgetTester tester) async {
|
||||
tester.binding.testTextInput.unregister();
|
||||
addTearDown((){
|
||||
tester.binding.testTextInput.register();
|
||||
});
|
||||
final FocusNode editable = FocusNode();
|
||||
final FocusNode spy = FocusNode();
|
||||
await tester.pumpWidget(
|
||||
buildSpyAboveEditableText(
|
||||
editableFocusNode: editable,
|
||||
spyFocusNode: spy,
|
||||
),
|
||||
);
|
||||
editable.requestFocus();
|
||||
await tester.pump();
|
||||
final ActionSpyState state = tester.state<ActionSpyState>(find.byType(ActionSpy));
|
||||
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true));
|
||||
await tester.pump();
|
||||
|
||||
expect(state.lastIntent, isNull);
|
||||
}, variant: macOSOnly);
|
||||
|
||||
testWidgets('word modifier + arrowRight', (WidgetTester tester) async {
|
||||
tester.binding.testTextInput.unregister();
|
||||
addTearDown((){
|
||||
tester.binding.testTextInput.register();
|
||||
});
|
||||
final FocusNode editable = FocusNode();
|
||||
final FocusNode spy = FocusNode();
|
||||
await tester.pumpWidget(
|
||||
buildSpyAboveEditableText(
|
||||
editableFocusNode: editable,
|
||||
spyFocusNode: spy,
|
||||
),
|
||||
);
|
||||
editable.requestFocus();
|
||||
await tester.pump();
|
||||
final ActionSpyState state = tester.state<ActionSpyState>(find.byType(ActionSpy));
|
||||
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, alt: true));
|
||||
await tester.pump();
|
||||
|
||||
expect(state.lastIntent, isNull);
|
||||
}, variant: macOSOnly);
|
||||
|
||||
testWidgets('line modifier + arrowLeft', (WidgetTester tester) async {
|
||||
tester.binding.testTextInput.unregister();
|
||||
addTearDown((){
|
||||
tester.binding.testTextInput.register();
|
||||
});
|
||||
final FocusNode editable = FocusNode();
|
||||
final FocusNode spy = FocusNode();
|
||||
await tester.pumpWidget(
|
||||
buildSpyAboveEditableText(
|
||||
editableFocusNode: editable,
|
||||
spyFocusNode: spy,
|
||||
),
|
||||
);
|
||||
editable.requestFocus();
|
||||
await tester.pump();
|
||||
final ActionSpyState state = tester.state<ActionSpyState>(find.byType(ActionSpy));
|
||||
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true));
|
||||
await tester.pump();
|
||||
|
||||
expect(state.lastIntent, isNull);
|
||||
}, variant: macOSOnly);
|
||||
|
||||
testWidgets('line modifier + arrowRight', (WidgetTester tester) async {
|
||||
tester.binding.testTextInput.unregister();
|
||||
addTearDown((){
|
||||
tester.binding.testTextInput.register();
|
||||
});
|
||||
final FocusNode editable = FocusNode();
|
||||
final FocusNode spy = FocusNode();
|
||||
await tester.pumpWidget(
|
||||
buildSpyAboveEditableText(
|
||||
editableFocusNode: editable,
|
||||
spyFocusNode: spy,
|
||||
),
|
||||
);
|
||||
editable.requestFocus();
|
||||
await tester.pump();
|
||||
final ActionSpyState state = tester.state<ActionSpyState>(find.byType(ActionSpy));
|
||||
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, meta: true));
|
||||
await tester.pump();
|
||||
|
||||
expect(state.lastIntent, isNull);
|
||||
}, variant: macOSOnly);
|
||||
|
||||
testWidgets('word modifier + arrow key movement', (WidgetTester tester) async {
|
||||
tester.binding.testTextInput.unregister();
|
||||
addTearDown((){
|
||||
tester.binding.testTextInput.register();
|
||||
});
|
||||
final FocusNode editable = FocusNode();
|
||||
final FocusNode spy = FocusNode();
|
||||
await tester.pumpWidget(
|
||||
buildSpyAboveEditableText(
|
||||
editableFocusNode: editable,
|
||||
spyFocusNode: spy,
|
||||
),
|
||||
);
|
||||
editable.requestFocus();
|
||||
await tester.pump();
|
||||
final ActionSpyState state = tester.state<ActionSpyState>(find.byType(ActionSpy));
|
||||
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true));
|
||||
await tester.pump();
|
||||
|
||||
expect(state.lastIntent, isNull);
|
||||
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true));
|
||||
await tester.pump();
|
||||
expect(state.lastIntent, isNull);
|
||||
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, alt: true));
|
||||
await tester.pump();
|
||||
expect(state.lastIntent, isNull);
|
||||
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, alt: true));
|
||||
await tester.pump();
|
||||
expect(state.lastIntent, isNull);
|
||||
}, variant: macOSOnly);
|
||||
|
||||
testWidgets('line modifier + arrow key movement', (WidgetTester tester) async {
|
||||
tester.binding.testTextInput.unregister();
|
||||
addTearDown((){
|
||||
tester.binding.testTextInput.register();
|
||||
});
|
||||
final FocusNode editable = FocusNode();
|
||||
final FocusNode spy = FocusNode();
|
||||
await tester.pumpWidget(
|
||||
buildSpyAboveEditableText(
|
||||
editableFocusNode: editable,
|
||||
spyFocusNode: spy,
|
||||
),
|
||||
);
|
||||
editable.requestFocus();
|
||||
await tester.pump();
|
||||
final ActionSpyState state = tester.state<ActionSpyState>(find.byType(ActionSpy));
|
||||
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true));
|
||||
await tester.pump();
|
||||
expect(state.lastIntent, isNull);
|
||||
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true));
|
||||
await tester.pump();
|
||||
expect(state.lastIntent, isNull);
|
||||
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, meta: true));
|
||||
await tester.pump();
|
||||
expect(state.lastIntent, isNull);
|
||||
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, meta: true));
|
||||
await tester.pump();
|
||||
expect(state.lastIntent, isNull);
|
||||
}, variant: macOSOnly);
|
||||
});
|
||||
|
||||
group('macOS does accept shortcuts if focus above EditableText', () {
|
||||
final TargetPlatformVariant macOSOnly = TargetPlatformVariant.only(TargetPlatform.macOS);
|
||||
|
||||
testWidgets('word modifier + arrowLeft', (WidgetTester tester) async {
|
||||
tester.binding.testTextInput.unregister();
|
||||
addTearDown((){
|
||||
tester.binding.testTextInput.register();
|
||||
});
|
||||
final FocusNode editable = FocusNode();
|
||||
final FocusNode spy = FocusNode();
|
||||
await tester.pumpWidget(
|
||||
buildSpyAboveEditableText(
|
||||
editableFocusNode: editable,
|
||||
spyFocusNode: spy,
|
||||
),
|
||||
);
|
||||
spy.requestFocus();
|
||||
await tester.pump();
|
||||
final ActionSpyState state = tester.state<ActionSpyState>(find.byType(ActionSpy));
|
||||
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true));
|
||||
await tester.pump();
|
||||
|
||||
expect(state.lastIntent, isA<ExtendSelectionToNextWordBoundaryIntent>());
|
||||
}, variant: macOSOnly);
|
||||
|
||||
testWidgets('word modifier + arrowRight', (WidgetTester tester) async {
|
||||
tester.binding.testTextInput.unregister();
|
||||
addTearDown((){
|
||||
tester.binding.testTextInput.register();
|
||||
});
|
||||
final FocusNode editable = FocusNode();
|
||||
final FocusNode spy = FocusNode();
|
||||
await tester.pumpWidget(
|
||||
buildSpyAboveEditableText(
|
||||
editableFocusNode: editable,
|
||||
spyFocusNode: spy,
|
||||
),
|
||||
);
|
||||
spy.requestFocus();
|
||||
await tester.pump();
|
||||
final ActionSpyState state = tester.state<ActionSpyState>(find.byType(ActionSpy));
|
||||
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, alt: true));
|
||||
await tester.pump();
|
||||
|
||||
expect(state.lastIntent, isA<ExtendSelectionToNextWordBoundaryIntent>());
|
||||
}, variant: macOSOnly);
|
||||
|
||||
testWidgets('line modifier + arrowLeft', (WidgetTester tester) async {
|
||||
tester.binding.testTextInput.unregister();
|
||||
addTearDown((){
|
||||
tester.binding.testTextInput.register();
|
||||
});
|
||||
final FocusNode editable = FocusNode();
|
||||
final FocusNode spy = FocusNode();
|
||||
await tester.pumpWidget(
|
||||
buildSpyAboveEditableText(
|
||||
editableFocusNode: editable,
|
||||
spyFocusNode: spy,
|
||||
),
|
||||
);
|
||||
spy.requestFocus();
|
||||
await tester.pump();
|
||||
final ActionSpyState state = tester.state<ActionSpyState>(find.byType(ActionSpy));
|
||||
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true));
|
||||
await tester.pump();
|
||||
|
||||
expect(state.lastIntent, isA<ExtendSelectionToLineBreakIntent>());
|
||||
}, variant: macOSOnly);
|
||||
|
||||
testWidgets('line modifier + arrowRight', (WidgetTester tester) async {
|
||||
tester.binding.testTextInput.unregister();
|
||||
addTearDown((){
|
||||
tester.binding.testTextInput.register();
|
||||
});
|
||||
final FocusNode editable = FocusNode();
|
||||
final FocusNode spy = FocusNode();
|
||||
await tester.pumpWidget(
|
||||
buildSpyAboveEditableText(
|
||||
editableFocusNode: editable,
|
||||
spyFocusNode: spy,
|
||||
),
|
||||
);
|
||||
spy.requestFocus();
|
||||
await tester.pump();
|
||||
final ActionSpyState state = tester.state<ActionSpyState>(find.byType(ActionSpy));
|
||||
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, meta: true));
|
||||
await tester.pump();
|
||||
|
||||
expect(state.lastIntent, isA<ExtendSelectionToLineBreakIntent>());
|
||||
}, variant: macOSOnly);
|
||||
|
||||
testWidgets('word modifier + arrow key movement', (WidgetTester tester) async {
|
||||
tester.binding.testTextInput.unregister();
|
||||
addTearDown((){
|
||||
tester.binding.testTextInput.register();
|
||||
});
|
||||
final FocusNode editable = FocusNode();
|
||||
final FocusNode spy = FocusNode();
|
||||
await tester.pumpWidget(
|
||||
buildSpyAboveEditableText(
|
||||
editableFocusNode: editable,
|
||||
spyFocusNode: spy,
|
||||
),
|
||||
);
|
||||
spy.requestFocus();
|
||||
await tester.pump();
|
||||
final ActionSpyState state = tester.state<ActionSpyState>(find.byType(ActionSpy));
|
||||
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true));
|
||||
await tester.pump();
|
||||
expect(state.lastIntent, isA<ExtendSelectionToNextWordBoundaryIntent>());
|
||||
|
||||
state.lastIntent = null;
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true));
|
||||
await tester.pump();
|
||||
expect(state.lastIntent, isA<ExtendSelectionToNextWordBoundaryIntent>());
|
||||
|
||||
state.lastIntent = null;
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, alt: true));
|
||||
await tester.pump();
|
||||
expect(state.lastIntent, isA<ExtendSelectionToNextWordBoundaryIntent>());
|
||||
|
||||
state.lastIntent = null;
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, alt: true));
|
||||
await tester.pump();
|
||||
expect(state.lastIntent, isA<ExtendSelectionToNextWordBoundaryIntent>());
|
||||
}, variant: macOSOnly);
|
||||
|
||||
testWidgets('line modifier + arrow key movement', (WidgetTester tester) async {
|
||||
tester.binding.testTextInput.unregister();
|
||||
addTearDown((){
|
||||
tester.binding.testTextInput.register();
|
||||
});
|
||||
final FocusNode editable = FocusNode();
|
||||
final FocusNode spy = FocusNode();
|
||||
await tester.pumpWidget(
|
||||
buildSpyAboveEditableText(
|
||||
editableFocusNode: editable,
|
||||
spyFocusNode: spy,
|
||||
),
|
||||
);
|
||||
spy.requestFocus();
|
||||
await tester.pump();
|
||||
final ActionSpyState state = tester.state<ActionSpyState>(find.byType(ActionSpy));
|
||||
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true));
|
||||
await tester.pump();
|
||||
expect(state.lastIntent, isA<ExtendSelectionToLineBreakIntent>());
|
||||
|
||||
state.lastIntent = null;
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true));
|
||||
await tester.pump();
|
||||
expect(state.lastIntent, isA<ExtendSelectionToLineBreakIntent>());
|
||||
|
||||
state.lastIntent = null;
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, meta: true));
|
||||
await tester.pump();
|
||||
expect(state.lastIntent, isA<ExtendSelectionToLineBreakIntent>());
|
||||
|
||||
state.lastIntent = null;
|
||||
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, meta: true));
|
||||
await tester.pump();
|
||||
expect(state.lastIntent, isA<ExtendSelectionToLineBreakIntent>());
|
||||
}, variant: macOSOnly);
|
||||
}, skip: kIsWeb); // [intended] specific tests target non-web.
|
||||
}
|
||||
|
||||
class ActionSpy extends StatefulWidget {
|
||||
const ActionSpy({super.key, required this.focusNode, required this.child});
|
||||
final FocusNode focusNode;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
State<ActionSpy> createState() => ActionSpyState();
|
||||
}
|
||||
|
||||
class ActionSpyState extends State<ActionSpy> {
|
||||
Intent? lastIntent;
|
||||
late final Map<Type, Action<Intent>> _actions = <Type, Action<Intent>>{
|
||||
ExtendSelectionByCharacterIntent: CallbackAction<ExtendSelectionByCharacterIntent>(onInvoke: _captureIntent),
|
||||
ExtendSelectionToNextWordBoundaryIntent: CallbackAction<ExtendSelectionToNextWordBoundaryIntent>(onInvoke: _captureIntent),
|
||||
ExtendSelectionToLineBreakIntent: CallbackAction<ExtendSelectionToLineBreakIntent>(onInvoke: _captureIntent),
|
||||
ExpandSelectionToLineBreakIntent: CallbackAction<ExpandSelectionToLineBreakIntent>(onInvoke: _captureIntent),
|
||||
ExpandSelectionToDocumentBoundaryIntent: CallbackAction<ExpandSelectionToDocumentBoundaryIntent>(onInvoke: _captureIntent),
|
||||
ExtendSelectionVerticallyToAdjacentLineIntent: CallbackAction<ExtendSelectionVerticallyToAdjacentLineIntent>(onInvoke: _captureIntent),
|
||||
ExtendSelectionToDocumentBoundaryIntent: CallbackAction<ExtendSelectionToDocumentBoundaryIntent>(onInvoke: _captureIntent),
|
||||
ExtendSelectionToNextWordBoundaryOrCaretLocationIntent: CallbackAction<ExtendSelectionToNextWordBoundaryOrCaretLocationIntent>(onInvoke: _captureIntent),
|
||||
};
|
||||
|
||||
// ignore: use_setters_to_change_properties
|
||||
void _captureIntent(Intent intent) {
|
||||
lastIntent = intent;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Actions(
|
||||
actions: _actions,
|
||||
child: Focus(
|
||||
focusNode: widget.focusNode,
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue