blob: 2ecb88010d1ae6d2ddd056771d4ee38dc1552d92 [file] [log] [blame]
// 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';
import 'clipboard_utils.dart';
import 'keyboard_utils.dart';
Iterable<SingleActivator> allModifierVariants(LogicalKeyboardKey trigger) {
const Iterable<bool> trueFalse = <bool>[false, true];
return trueFalse.expand((bool shift) {
return trueFalse.expand((bool control) {
return trueFalse.expand((bool alt) {
return trueFalse.map((bool meta) => SingleActivator(trigger, shift: shift, control: control, alt: alt, meta: meta));
});
});
});
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final MockClipboard mockClipboard = MockClipboard();
setUp(() async {
TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall);
await Clipboard.setData(const ClipboardData(text: 'empty'));
});
tearDown(() {
TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, null);
});
const String testText =
'Now is the time for\n' // 20
'all good people\n' // 20 + 16 => 36
'to come to the aid\n' // 36 + 19 => 55
'of their country.'; // 55 + 17 => 72
const String testCluster = 'πŸ‘¨β€πŸ‘©β€πŸ‘¦πŸ‘¨β€πŸ‘©β€πŸ‘¦πŸ‘¨β€πŸ‘©β€πŸ‘¦'; // 8 * 3
// Exactly 20 characters each line.
const String testSoftwrapText =
'0123456789ABCDEFGHIJ'
'0123456789ABCDEFGHIJ'
'0123456789ABCDEFGHIJ'
'0123456789ABCDEFGHIJ';
const String testVerticalText = '1\n2\n3\n4\n5\n6\n7\n8\n9';
final TextEditingController controller = TextEditingController(text: testText);
final FocusNode focusNode = FocusNode();
Widget buildEditableText({
TextAlign textAlign = TextAlign.left,
bool readOnly = false,
bool obscured = false,
TextStyle style = const TextStyle(fontSize: 10.0),
bool enableInteractiveSelection = true
}) {
return MaterialApp(
home: Align(
alignment: Alignment.topLeft,
child: SizedBox(
// Softwrap at exactly 20 characters.
width: 201,
height: 200,
child: EditableText(
controller: controller,
showSelectionHandles: true,
autofocus: true,
focusNode: focusNode,
style: style,
textScaleFactor: 1,
// Avoid the cursor from taking up width.
cursorWidth: 0,
cursorColor: Colors.blue,
backgroundCursorColor: Colors.grey,
selectionControls: materialTextSelectionControls,
keyboardType: TextInputType.text,
maxLines: obscured ? 1 : null,
readOnly: readOnly,
textAlign: textAlign,
obscureText: obscured,
enableInteractiveSelection: enableInteractiveSelection,
),
),
),
);
}
testWidgets(
'Movement/Deletion shortcuts do nothing when the selection is invalid',
(WidgetTester tester) async {
await tester.pumpWidget(buildEditableText());
controller.text = testText;
controller.selection = const TextSelection.collapsed(offset: -1);
await tester.pump();
const List<LogicalKeyboardKey> triggers = <LogicalKeyboardKey>[
LogicalKeyboardKey.backspace,
LogicalKeyboardKey.delete,
LogicalKeyboardKey.arrowLeft,
LogicalKeyboardKey.arrowRight,
LogicalKeyboardKey.arrowUp,
LogicalKeyboardKey.arrowDown,
LogicalKeyboardKey.pageUp,
LogicalKeyboardKey.pageDown,
LogicalKeyboardKey.home,
LogicalKeyboardKey.end,
];
for (final SingleActivator activator in triggers.expand(allModifierVariants)) {
await sendKeyCombination(tester, activator);
await tester.pump();
expect(controller.text, testText, reason: activator.toString());
expect(controller.selection, const TextSelection.collapsed(offset: -1), reason: activator.toString());
}
},
skip: kIsWeb, // [intended] on web these keys are handled by the browser.
variant: TargetPlatformVariant.all(),
);
group('Common text editing shortcuts: ',
() {
final TargetPlatformVariant allExceptApple = TargetPlatformVariant.all(excluding: <TargetPlatform>{TargetPlatform.macOS, TargetPlatform.iOS});
group('backspace', () {
const LogicalKeyboardKey trigger = LogicalKeyboardKey.backspace;
testWidgets('backspace', (WidgetTester tester) async {
controller.text = testText;
// Move the selection to the beginning of the 2nd line (after the newline
// character).
controller.selection = const TextSelection.collapsed(
offset: 20,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(trigger));
expect(
controller.text,
'Now is the time forall good people\n'
'to come to the aid\n'
'of their country.',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 19),
);
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS }));
testWidgets('backspace readonly', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 20,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText(readOnly: true));
await sendKeyCombination(tester, const SingleActivator(trigger));
expect(controller.text, testText);
expect(
controller.selection,
const TextSelection.collapsed(offset: 20, affinity: TextAffinity.upstream),
);
}, variant: TargetPlatformVariant.all());
testWidgets('backspace at start', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 0,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(trigger));
expect(
controller.text,
'Now is the time for\n'
'all good people\n'
'to come to the aid\n'
'of their country.',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 0),
);
}, variant: TargetPlatformVariant.all());
testWidgets('backspace at end', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 72,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(trigger));
expect(
controller.text,
'Now is the time for\n'
'all good people\n'
'to come to the aid\n'
'of their country',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 71),
);
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS }));
testWidgets('backspace inside of a cluster', (WidgetTester tester) async {
controller.text = testCluster;
controller.selection = const TextSelection.collapsed(
offset: 1,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(trigger));
expect(
controller.text,
'πŸ‘¨β€πŸ‘©β€πŸ‘¦πŸ‘¨β€πŸ‘©β€πŸ‘¦',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 0),
);
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS }));
testWidgets('backspace at cluster boundary', (WidgetTester tester) async {
controller.text = testCluster;
controller.selection = const TextSelection.collapsed(
offset: 8,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(trigger));
expect(
controller.text,
'πŸ‘¨β€πŸ‘©β€πŸ‘¦πŸ‘¨β€πŸ‘©β€πŸ‘¦',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 0),
);
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS }));
});
group('delete: ', () {
const LogicalKeyboardKey trigger = LogicalKeyboardKey.delete;
testWidgets('delete', (WidgetTester tester) async {
controller.text = testText;
// Move the selection to the beginning of the 2nd line (after the newline
// character).
controller.selection = const TextSelection.collapsed(
offset: 20,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(trigger));
expect(
controller.text,
'Now is the time for\n'
'll good people\n'
'to come to the aid\n'
'of their country.',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 20),
);
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS }));
testWidgets('delete readonly', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 20,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText(readOnly: true));
await sendKeyCombination(tester, const SingleActivator(trigger));
expect(controller.text, testText);
expect(
controller.selection,
const TextSelection.collapsed(offset: 20, affinity: TextAffinity.upstream),
);
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS }));
testWidgets('delete at start', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 0,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(trigger));
expect(
controller.text,
'ow is the time for\n'
'all good people\n'
'to come to the aid\n'
'of their country.',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 0),
);
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS }));
testWidgets('delete at end', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 72,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(trigger));
expect(
controller.text,
'Now is the time for\n'
'all good people\n'
'to come to the aid\n'
'of their country.',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 72, affinity: TextAffinity.upstream),
);
}, variant: TargetPlatformVariant.all());
testWidgets('delete inside of a cluster', (WidgetTester tester) async {
controller.text = testCluster;
controller.selection = const TextSelection.collapsed(
offset: 1,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(trigger));
expect(
controller.text,
'πŸ‘¨β€πŸ‘©β€πŸ‘¦πŸ‘¨β€πŸ‘©β€πŸ‘¦',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 0),
);
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS }));
testWidgets('delete at cluster boundary', (WidgetTester tester) async {
controller.text = testCluster;
controller.selection = const TextSelection.collapsed(
offset: 8,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(trigger));
expect(
controller.text,
'πŸ‘¨β€πŸ‘©β€πŸ‘¦πŸ‘¨β€πŸ‘©β€πŸ‘¦',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 8),
);
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS }));
});
group('Non-collapsed delete', () {
// This shares the same logic as backspace.
const LogicalKeyboardKey trigger = LogicalKeyboardKey.delete;
testWidgets('inside of a cluster', (WidgetTester tester) async {
controller.text = testCluster;
controller.selection = const TextSelection(
baseOffset: 9,
extentOffset: 12,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(trigger));
expect(
controller.text,
'πŸ‘¨β€πŸ‘©β€πŸ‘¦πŸ‘¨β€πŸ‘©β€πŸ‘¦',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 8),
);
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS }));
testWidgets('at the boundaries of a cluster', (WidgetTester tester) async {
controller.text = testCluster;
controller.selection = const TextSelection(
baseOffset: 8,
extentOffset: 16,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(trigger));
expect(
controller.text,
'πŸ‘¨β€πŸ‘©β€πŸ‘¦πŸ‘¨β€πŸ‘©β€πŸ‘¦',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 8),
);
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS }));
testWidgets('cross-cluster', (WidgetTester tester) async {
controller.text = testCluster;
controller.selection = const TextSelection(
baseOffset: 1,
extentOffset: 9,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(trigger));
expect(
controller.text,
'πŸ‘¨β€πŸ‘©β€πŸ‘¦',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 0),
);
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS }));
testWidgets('cross-cluster obscured text', (WidgetTester tester) async {
controller.text = testCluster;
controller.selection = const TextSelection(
baseOffset: 1,
extentOffset: 9,
);
await tester.pumpWidget(buildEditableText(obscured: true));
await sendKeyCombination(tester, const SingleActivator(trigger));
expect(
controller.text,
'πŸ‘¨β€πŸ‘©β€πŸ‘¦πŸ‘¨β€πŸ‘©β€πŸ‘¦',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 1),
);
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS }));
});
group('word modifier + backspace', () {
const LogicalKeyboardKey trigger = LogicalKeyboardKey.backspace;
SingleActivator wordModifierBackspace() {
final bool isApple = defaultTargetPlatform == TargetPlatform.macOS || defaultTargetPlatform == TargetPlatform.iOS;
return SingleActivator(trigger, control: !isApple, alt: isApple);
}
testWidgets('WordModifier-backspace', (WidgetTester tester) async {
controller.text = testText;
// Place the caret before "people".
controller.selection = const TextSelection.collapsed(
offset: 29,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, wordModifierBackspace());
expect(
controller.text,
'Now is the time for\n'
'all people\n'
'to come to the aid\n'
'of their country.',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 24),
);
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS }));
testWidgets('readonly', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 29,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText(readOnly: true));
await sendKeyCombination(tester, wordModifierBackspace());
expect(controller.text, testText);
expect(
controller.selection,
const TextSelection.collapsed(offset: 29, affinity: TextAffinity.upstream),
);
}, variant: TargetPlatformVariant.all());
testWidgets('at start', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 0,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, wordModifierBackspace());
expect(
controller.text,
'Now is the time for\n'
'all good people\n'
'to come to the aid\n'
'of their country.',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 0),
);
}, variant: TargetPlatformVariant.all());
testWidgets('at end', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 72,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, wordModifierBackspace());
expect(
controller.text,
'Now is the time for\n'
'all good people\n'
'to come to the aid\n'
'of their country',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 71),
);
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS }));
testWidgets('inside of a cluster', (WidgetTester tester) async {
controller.text = testCluster;
controller.selection = const TextSelection.collapsed(
offset: 1,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, wordModifierBackspace());
expect(
controller.text,
'πŸ‘¨β€πŸ‘©β€πŸ‘¦πŸ‘¨β€πŸ‘©β€πŸ‘¦',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 0),
);
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS }));
testWidgets('at cluster boundary', (WidgetTester tester) async {
controller.text = testCluster;
controller.selection = const TextSelection.collapsed(
offset: 8,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, wordModifierBackspace());
expect(
controller.text,
'πŸ‘¨β€πŸ‘©β€πŸ‘¦πŸ‘¨β€πŸ‘©β€πŸ‘¦',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 0),
);
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS }));
});
group('word modifier + delete', () {
const LogicalKeyboardKey trigger = LogicalKeyboardKey.delete;
SingleActivator wordModifierDelete() {
final bool isApple = defaultTargetPlatform == TargetPlatform.macOS || defaultTargetPlatform == TargetPlatform.iOS;
return SingleActivator(trigger, control: !isApple, alt: isApple);
}
testWidgets('WordModifier-delete', (WidgetTester tester) async {
controller.text = testText;
// Place the caret after "all".
controller.selection = const TextSelection.collapsed(
offset: 23,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, wordModifierDelete());
expect(
controller.text,
'Now is the time for\n'
'all people\n'
'to come to the aid\n'
'of their country.',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 23),
);
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS }));
testWidgets('readonly', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 23,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText(readOnly: true));
await sendKeyCombination(tester, wordModifierDelete());
expect(controller.text, testText);
expect(
controller.selection,
const TextSelection.collapsed(offset: 23, affinity: TextAffinity.upstream),
);
}, variant: TargetPlatformVariant.all());
testWidgets('at start', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 0,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, wordModifierDelete());
expect(
controller.text,
' is the time for\n'
'all good people\n'
'to come to the aid\n'
'of their country.',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 0),
);
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS }));
testWidgets('at end', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 72,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, wordModifierDelete());
expect(controller.text, testText);
expect(
controller.selection,
const TextSelection.collapsed(offset: 72, affinity: TextAffinity.upstream),
);
}, variant: TargetPlatformVariant.all());
testWidgets('inside of a cluster', (WidgetTester tester) async {
controller.text = testCluster;
controller.selection = const TextSelection.collapsed(
offset: 1,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, wordModifierDelete());
expect(
controller.text,
'πŸ‘¨β€πŸ‘©β€πŸ‘¦πŸ‘¨β€πŸ‘©β€πŸ‘¦',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 0),
);
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS }));
testWidgets('at cluster boundary', (WidgetTester tester) async {
controller.text = testCluster;
controller.selection = const TextSelection.collapsed(
offset: 8,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, wordModifierDelete());
expect(
controller.text,
'πŸ‘¨β€πŸ‘©β€πŸ‘¦πŸ‘¨β€πŸ‘©β€πŸ‘¦',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 8),
);
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS }));
});
group('line modifier + backspace', () {
const LogicalKeyboardKey trigger = LogicalKeyboardKey.backspace;
SingleActivator lineModifierBackspace() {
final bool isApple = defaultTargetPlatform == TargetPlatform.macOS || defaultTargetPlatform == TargetPlatform.iOS;
return SingleActivator(trigger, meta: isApple, alt: !isApple);
}
testWidgets('alt-backspace', (WidgetTester tester) async {
controller.text = testText;
// Place the caret before "people".
controller.selection = const TextSelection.collapsed(
offset: 29,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, lineModifierBackspace());
expect(
controller.text,
'Now is the time for\n'
'people\n'
'to come to the aid\n'
'of their country.',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 20),
);
}, variant: TargetPlatformVariant.all());
testWidgets('softwrap line boundary, upstream', (WidgetTester tester) async {
controller.text = testSoftwrapText;
// Place the caret at the beginning of the 3rd line.
controller.selection = const TextSelection.collapsed(
offset: 40,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, lineModifierBackspace());
expect(
controller.text,
'0123456789ABCDEFGHIJ'
'0123456789ABCDEFGHIJ'
'0123456789ABCDEFGHIJ'
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 20),
);
}, variant: TargetPlatformVariant.all());
testWidgets('softwrap line boundary, downstream', (WidgetTester tester) async {
controller.text = testSoftwrapText;
// Place the caret at the beginning of the 3rd line.
controller.selection = const TextSelection.collapsed(
offset: 40,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, lineModifierBackspace());
expect(controller.text, testSoftwrapText);
expect(
controller.selection,
const TextSelection.collapsed(offset: 40),
);
}, variant: TargetPlatformVariant.all());
testWidgets('readonly', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 29,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText(readOnly: true));
await sendKeyCombination(tester, lineModifierBackspace());
expect(controller.text, testText);
expect(
controller.selection,
const TextSelection.collapsed(offset: 29, affinity: TextAffinity.upstream),
);
}, variant: TargetPlatformVariant.all());
testWidgets('at start', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 0,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, lineModifierBackspace());
expect(
controller.text,
'Now is the time for\n'
'all good people\n'
'to come to the aid\n'
'of their country.',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 0),
);
}, variant: TargetPlatformVariant.all());
testWidgets('at end', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 72,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, lineModifierBackspace());
expect(
controller.text,
'Now is the time for\n'
'all good people\n'
'to come to the aid\n'
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 55),
);
}, variant: TargetPlatformVariant.all());
testWidgets('inside of a cluster', (WidgetTester tester) async {
controller.text = testCluster;
controller.selection = const TextSelection.collapsed(
offset: 1,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, lineModifierBackspace());
expect(
controller.text,
'πŸ‘¨β€πŸ‘©β€πŸ‘¦πŸ‘¨β€πŸ‘©β€πŸ‘¦',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 0),
);
}, variant: TargetPlatformVariant.all());
testWidgets('at cluster boundary', (WidgetTester tester) async {
controller.text = testCluster;
controller.selection = const TextSelection.collapsed(
offset: 8,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, lineModifierBackspace());
expect(
controller.text,
'πŸ‘¨β€πŸ‘©β€πŸ‘¦πŸ‘¨β€πŸ‘©β€πŸ‘¦',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 0),
);
}, variant: TargetPlatformVariant.all());
});
group('line modifier + delete', () {
const LogicalKeyboardKey trigger = LogicalKeyboardKey.delete;
SingleActivator lineModifierDelete() {
final bool isApple = defaultTargetPlatform == TargetPlatform.macOS || defaultTargetPlatform == TargetPlatform.iOS;
return SingleActivator(trigger, meta: isApple, alt: !isApple);
}
testWidgets('alt-delete', (WidgetTester tester) async {
controller.text = testText;
// Place the caret after "all".
controller.selection = const TextSelection.collapsed(
offset: 23,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, lineModifierDelete());
expect(
controller.text,
'Now is the time for\n'
'all\n'
'to come to the aid\n'
'of their country.',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 23),
);
}, variant: TargetPlatformVariant.all());
testWidgets('softwrap line boundary, upstream', (WidgetTester tester) async {
controller.text = testSoftwrapText;
// Place the caret at the beginning of the 3rd line.
controller.selection = const TextSelection.collapsed(
offset: 40,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, lineModifierDelete());
expect(controller.text, testSoftwrapText);
expect(
controller.selection,
const TextSelection.collapsed(offset: 40, affinity: TextAffinity.upstream),
);
}, variant: TargetPlatformVariant.all());
testWidgets('softwrap line boundary, downstream', (WidgetTester tester) async {
controller.text = testSoftwrapText;
// Place the caret at the beginning of the 3rd line.
controller.selection = const TextSelection.collapsed(
offset: 40,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, lineModifierDelete());
expect(
controller.text,
'0123456789ABCDEFGHIJ'
'0123456789ABCDEFGHIJ'
'0123456789ABCDEFGHIJ'
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 40),
);
}, variant: TargetPlatformVariant.all());
testWidgets('readonly', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 23,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText(readOnly: true));
await sendKeyCombination(tester, lineModifierDelete());
expect(controller.text, testText);
expect(
controller.selection,
const TextSelection.collapsed(offset: 23, affinity: TextAffinity.upstream),
);
}, variant: TargetPlatformVariant.all());
testWidgets('at start', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 0,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, lineModifierDelete());
expect(
controller.text,
'\n'
'all good people\n'
'to come to the aid\n'
'of their country.',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 0),
);
}, variant: TargetPlatformVariant.all());
testWidgets('at end', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 72,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, lineModifierDelete());
expect(controller.text, testText);
expect(
controller.selection,
const TextSelection.collapsed(offset: 72, affinity: TextAffinity.upstream),
);
}, variant: TargetPlatformVariant.all());
testWidgets('inside of a cluster', (WidgetTester tester) async {
controller.text = testCluster;
controller.selection = const TextSelection.collapsed(
offset: 1,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, lineModifierDelete());
expect(
controller.text,
isEmpty,
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 0),
);
}, variant: TargetPlatformVariant.all());
testWidgets('at cluster boundary', (WidgetTester tester) async {
controller.text = testCluster;
controller.selection = const TextSelection.collapsed(
offset: 8,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, lineModifierDelete());
expect(
controller.text,
'πŸ‘¨β€πŸ‘©β€πŸ‘¦',
);
expect(
controller.selection,
const TextSelection.collapsed(offset: 8),
);
}, variant: TargetPlatformVariant.all());
});
group('Arrow Movement', () {
group('left', () {
const LogicalKeyboardKey trigger = LogicalKeyboardKey.arrowLeft;
testWidgets('at start', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 0,
);
await tester.pumpWidget(buildEditableText());
for (final SingleActivator activator in allModifierVariants(trigger)) {
await sendKeyCombination(tester, activator);
await tester.pump();
expect(
controller.selection,
const TextSelection.collapsed(offset: 0),
reason: activator.toString(),
);
}
}, variant: TargetPlatformVariant.all());
testWidgets('base arrow key movement', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 20,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(trigger));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 19,
));
}, variant: TargetPlatformVariant.all());
testWidgets('word modifier + arrow key movement', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 7, // Before the first "the"
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(trigger, control: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 4,
));
}, variant: allExceptApple);
testWidgets('line modifier + arrow key movement', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 24, // Before the "good".
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(trigger, alt: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 20,
));
}, variant: allExceptApple);
});
group('right', () {
const LogicalKeyboardKey trigger = LogicalKeyboardKey.arrowRight;
testWidgets('at end', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 72,
);
await tester.pumpWidget(buildEditableText());
for (final SingleActivator activator in allModifierVariants(trigger)) {
await sendKeyCombination(tester, activator);
await tester.pump();
expect(controller.selection.isCollapsed, isTrue, reason: activator.toString());
expect(controller.selection.baseOffset, 72, reason: activator.toString());
}
}, variant: TargetPlatformVariant.all());
testWidgets('base arrow key movement', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 20,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(trigger));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 21,
affinity: TextAffinity.upstream,
));
}, variant: TargetPlatformVariant.all());
testWidgets('word modifier + arrow key movement', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 7, // Before the first "the"
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(trigger, control: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 10,
affinity: TextAffinity.upstream,
));
}, variant: allExceptApple);
testWidgets('line modifier + arrow key movement', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 24, // Before the "good".
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(trigger, alt: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 35, // Before the newline character.
affinity: TextAffinity.upstream,
));
}, variant: allExceptApple);
});
group('With initial non-collapsed selection', () {
testWidgets('base arrow key movement', (WidgetTester tester) async {
controller.text = testText;
// The word "all" is selected.
controller.selection = const TextSelection(
baseOffset: 20,
extentOffset: 23,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 20,
));
// The word "all" is selected.
controller.selection = const TextSelection(
baseOffset: 23,
extentOffset: 20,
);
await tester.pump();
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 20,
));
// The word "all" is selected.
controller.selection = const TextSelection(
baseOffset: 20,
extentOffset: 23,
);
await tester.pump();
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 23,
));
// The word "all" is selected.
controller.selection = const TextSelection(
baseOffset: 23,
extentOffset: 20,
);
await tester.pump();
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 23,
));
}, variant: TargetPlatformVariant.all());
testWidgets('word modifier + arrow key movement', (WidgetTester tester) async {
controller.text = testText;
// "good" to "come" is selected.
controller.selection = const TextSelection(
baseOffset: 24,
extentOffset: 43,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, control: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 39, // Before "come".
));
// "good" to "come" is selected.
controller.selection = const TextSelection(
baseOffset: 43,
extentOffset: 24,
);
await tester.pump();
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, control: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 20, // Before "all".
//offset: 39, // Before "come".
));
// "good" to "come" is selected.
controller.selection = const TextSelection(
baseOffset: 24,
extentOffset: 43,
);
await tester.pump();
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, control: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 46, // After "to".
affinity: TextAffinity.upstream,
));
// "good" to "come" is selected.
controller.selection = const TextSelection(
baseOffset: 43,
extentOffset: 24,
);
await tester.pump();
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, control: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 28, // After "good".
affinity: TextAffinity.upstream,
));
}, variant: allExceptApple);
testWidgets('line modifier + arrow key movement', (WidgetTester tester) async {
controller.text = testText;
// "good" to "come" is selected.
controller.selection = const TextSelection(
baseOffset: 24,
extentOffset: 43,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 36, // Before "to".
));
// "good" to "come" is selected.
controller.selection = const TextSelection(
baseOffset: 43,
extentOffset: 24,
);
await tester.pump();
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 20, // Before "all".
));
// "good" to "come" is selected.
controller.selection = const TextSelection(
baseOffset: 24,
extentOffset: 43,
);
await tester.pump();
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, alt: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 54, // After "aid".
affinity: TextAffinity.upstream,
));
// "good" to "come" is selected.
controller.selection = const TextSelection(
baseOffset: 43,
extentOffset: 24,
);
await tester.pump();
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, alt: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 35, // After "people".
affinity: TextAffinity.upstream,
));
}, variant: allExceptApple);
});
group('vertical movement', () {
testWidgets('at start', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 0,
);
await tester.pumpWidget(buildEditableText());
for (final SingleActivator activator in allModifierVariants(LogicalKeyboardKey.arrowUp)) {
await sendKeyCombination(tester, activator);
await tester.pump();
expect(controller.text, testText);
expect(
controller.selection,
const TextSelection.collapsed(offset: 0),
reason: activator.toString(),
);
}
for (final SingleActivator activator in allModifierVariants(LogicalKeyboardKey.pageUp)) {
await sendKeyCombination(tester, activator);
await tester.pump();
expect(controller.text, testText);
expect(
controller.selection,
const TextSelection.collapsed(offset: 0),
reason: activator.toString(),
);
}
}, variant: TargetPlatformVariant.all());
testWidgets('at end', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 72,
);
await tester.pumpWidget(buildEditableText());
for (final SingleActivator activator in allModifierVariants(LogicalKeyboardKey.arrowDown)) {
await sendKeyCombination(tester, activator);
await tester.pump();
expect(controller.text, testText);
expect(controller.selection.baseOffset, 72, reason: activator.toString());
expect(controller.selection.extentOffset, 72, reason: activator.toString());
}
for (final SingleActivator activator in allModifierVariants(LogicalKeyboardKey.pageDown)) {
await sendKeyCombination(tester, activator);
await tester.pump();
expect(controller.text, testText);
expect(controller.selection.baseOffset, 72, reason: activator.toString());
expect(controller.selection.extentOffset, 72, reason: activator.toString());
}
}, variant: TargetPlatformVariant.all());
testWidgets('run', (WidgetTester tester) async {
controller.text =
'aa\n' // 3
'a\n' // 3 + 2 = 5
'aa\n' // 5 + 3 = 8
'aaa\n' // 8 + 4 = 12
'aaaa'; // 12 + 4 = 16
controller.selection = const TextSelection.collapsed(
offset: 2,
);
await tester.pumpWidget(buildEditableText());
await tester.pump(); // Wait for autofocus to take effect.
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowDown));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 4,
affinity: TextAffinity.upstream,
));
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowDown));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 7,
affinity: TextAffinity.upstream,
));
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowDown));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 10,
));
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowDown));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 14,
));
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowDown));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 16,
));
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowUp));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 10,
));
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowUp));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 7,
affinity: TextAffinity.upstream,
));
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowUp));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 4,
affinity: TextAffinity.upstream,
));
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowUp));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 2,
affinity: TextAffinity.upstream,
));
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowUp));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 0,
));
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowDown));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 4,
affinity: TextAffinity.upstream,
));
}, variant: TargetPlatformVariant.all());
testWidgets('run with page down/up', (WidgetTester tester) async {
controller.text =
'aa\n' // 3
'a\n' // 3 + 2 = 5
'aa\n' // 5 + 3 = 8
'aaa\n' // 8 + 4 = 12
'${"aaa\n" * 50}'
'aaaa';
controller.selection = const TextSelection.collapsed(offset: 2);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowDown));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 4,
affinity: TextAffinity.upstream,
));
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.pageDown));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(offset: 82));
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowUp));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(offset: 78));
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.pageUp));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 2,
affinity: TextAffinity.upstream,
));
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{TargetPlatform.iOS, TargetPlatform.macOS})); // intended: on macOS Page Up/Down only scrolls
testWidgets('run can be interrupted by layout changes', (WidgetTester tester) async {
controller.text =
'aa\n' // 3
'a\n' // 3 + 2 = 5
'aa\n' // 5 + 3 = 8
'aaa\n' // 8 + 4 = 12
'aaaa'; // 12 + 4 = 16
controller.selection = const TextSelection.collapsed(
offset: 2,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowUp));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 0,
));
// Layout changes.
await tester.pumpWidget(buildEditableText(textAlign: TextAlign.right));
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowDown));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 3,
));
}, variant: TargetPlatformVariant.all());
testWidgets('run can be interrupted by selection changes', (WidgetTester tester) async {
controller.text =
'aa\n' // 3
'a\n' // 3 + 2 = 5
'aa\n' // 5 + 3 = 8
'aaa\n' // 8 + 4 = 12
'aaaa'; // 12 + 4 = 16
controller.selection = const TextSelection.collapsed(
offset: 2,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowUp));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 0,
));
controller.selection = const TextSelection.collapsed(
offset: 1,
);
await tester.pump();
controller.selection = const TextSelection.collapsed(
offset: 0,
);
await tester.pump();
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowDown));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 3, // Would have been 4 if the run wasn't interrupted.
));
}, variant: TargetPlatformVariant.all());
testWidgets('long run with fractional text height', (WidgetTester tester) async {
controller.text = "${'ΕΊdΕΊbΕ‚o\n' * 49}ΕΊdΕΊbΕ‚o";
controller.selection = const TextSelection.collapsed(offset: 2);
await tester.pumpWidget(buildEditableText(style: const TextStyle(fontSize: 13.0, height: 1.17)));
for (int i = 1; i <= 49; i++) {
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowDown));
await tester.pump();
expect(
controller.selection,
TextSelection.collapsed(offset: 2 + i * 7),
reason: 'line $i',
);
}
for (int i = 49; i >= 1; i--) {
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowUp));
await tester.pump();
expect(
controller.selection,
TextSelection.collapsed(offset: 2 + (i - 1) * 7),
reason: 'line $i',
);
}
}, variant: TargetPlatformVariant.all());
});
});
},
skip: kIsWeb, // [intended] on web these keys are handled by the browser.
);
group('macOS shortcuts', () {
final TargetPlatformVariant macOSOnly = TargetPlatformVariant.only(TargetPlatform.macOS);
testWidgets('word modifier + arrowLeft', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 7, // Before the first "the"
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 4,
));
}, variant: macOSOnly);
testWidgets('word modifier + arrowRight', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 7, // Before the first "the"
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, alt: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 10,
affinity: TextAffinity.upstream,
));
}, variant: macOSOnly);
testWidgets('line modifier + arrowLeft', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 24, // Before the "good".
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 20,
));
}, variant: macOSOnly);
testWidgets('line modifier + arrowRight', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 24, // Before the "good".
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, meta: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 35, // Before the newline character.
affinity: TextAffinity.upstream,
));
}, variant: macOSOnly);
testWidgets('word modifier + arrow key movement', (WidgetTester tester) async {
controller.text = testText;
// "good" to "come" is selected.
controller.selection = const TextSelection(
baseOffset: 24,
extentOffset: 43,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 39, // Before "come".
));
// "good" to "come" is selected.
controller.selection = const TextSelection(
baseOffset: 43,
extentOffset: 24,
);
await tester.pump();
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 20, // Before "all".
//offset: 39, // Before "come".
));
// "good" to "come" is selected.
controller.selection = const TextSelection(
baseOffset: 24,
extentOffset: 43,
);
await tester.pump();
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, alt: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 46, // After "to".
affinity: TextAffinity.upstream,
));
// "good" to "come" is selected.
controller.selection = const TextSelection(
baseOffset: 43,
extentOffset: 24,
);
await tester.pump();
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, alt: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 28, // After "good".
affinity: TextAffinity.upstream,
));
}, variant: macOSOnly);
testWidgets('line modifier + arrow key movement', (WidgetTester tester) async {
controller.text = testText;
// "good" to "come" is selected.
controller.selection = const TextSelection(
baseOffset: 24,
extentOffset: 43,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 36, // Before "to".
));
// "good" to "come" is selected.
controller.selection = const TextSelection(
baseOffset: 43,
extentOffset: 24,
);
await tester.pump();
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 20, // Before "all".
));
// "good" to "come" is selected.
controller.selection = const TextSelection(
baseOffset: 24,
extentOffset: 43,
);
await tester.pump();
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, meta: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 54, // After "aid".
affinity: TextAffinity.upstream,
));
// "good" to "come" is selected.
controller.selection = const TextSelection(
baseOffset: 43,
extentOffset: 24,
);
await tester.pump();
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, meta: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 35, // After "people".
affinity: TextAffinity.upstream,
));
}, variant: macOSOnly);
}, skip: kIsWeb); // [intended] on web these keys are handled by the browser.
group('Web does not accept', () {
final TargetPlatformVariant allExceptApple = TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS });
const TargetPlatformVariant appleOnly = TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.macOS, TargetPlatform.iOS });
group('macOS shortcuts', () {
testWidgets('word modifier + arrowLeft', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 7, // Before the first "the"
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(offset: 7));
}, variant: appleOnly);
testWidgets('word modifier + arrowRight', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 7, // Before the first "the"
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, alt: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(offset: 7));
}, variant: appleOnly);
testWidgets('line modifier + arrowLeft', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 24, // Before the "good".
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(offset: 24,));
}, variant: appleOnly);
testWidgets('line modifier + arrowRight', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 24, // Before the "good".
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, meta: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 24, // Before the newline character.
));
}, variant: appleOnly);
testWidgets('word modifier + arrow key movement', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection(
baseOffset: 24,
extentOffset: 43,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true));
await tester.pump();
expect(controller.selection, const TextSelection(
baseOffset: 24,
extentOffset: 43,
));
controller.selection = const TextSelection(
baseOffset: 43,
extentOffset: 24,
);
await tester.pump();
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true));
await tester.pump();
expect(controller.selection, const TextSelection(
baseOffset: 43,
extentOffset: 24,
));
controller.selection = const TextSelection(
baseOffset: 24,
extentOffset: 43,
);
await tester.pump();
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, alt: true));
await tester.pump();
expect(controller.selection, const TextSelection(
baseOffset: 24,
extentOffset: 43,
));
// "good" to "come" is selected.
controller.selection = const TextSelection(
baseOffset: 43,
extentOffset: 24,
);
await tester.pump();
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, alt: true));
await tester.pump();
expect(controller.selection, const TextSelection(
baseOffset: 43,
extentOffset: 24,
));
}, variant: appleOnly);
testWidgets('line modifier + arrow key movement', (WidgetTester tester) async {
controller.text = testText;
// "good" to "come" is selected.
controller.selection = const TextSelection(
baseOffset: 24,
extentOffset: 43,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true));
await tester.pump();
expect(controller.selection, const TextSelection(
baseOffset: 24,
extentOffset: 43,
));
// "good" to "come" is selected.
controller.selection = const TextSelection(
baseOffset: 43,
extentOffset: 24,
);
await tester.pump();
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true));
await tester.pump();
expect(controller.selection, const TextSelection(
baseOffset: 43,
extentOffset: 24,
));
// "good" to "come" is selected.
controller.selection = const TextSelection(
baseOffset: 24,
extentOffset: 43,
);
await tester.pump();
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, meta: true));
await tester.pump();
expect(controller.selection, const TextSelection(
baseOffset: 24,
extentOffset: 43,
));
// "good" to "come" is selected.
controller.selection = const TextSelection(
baseOffset: 43,
extentOffset: 24,
);
await tester.pump();
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowRight, meta: true));
await tester.pump();
expect(controller.selection, const TextSelection(
baseOffset: 43,
extentOffset: 24,
));
}, variant: appleOnly);
});
testWidgets('vertical movement outside of selection',
(WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 0,
);
await tester.pumpWidget(buildEditableText());
for (final SingleActivator activator in allModifierVariants(LogicalKeyboardKey.arrowDown)) {
// Skip for the shift shortcut since web accepts it.
if (activator.shift) {
continue;
}
await sendKeyCombination(tester, activator);
await tester.pump();
expect(controller.text, testText);
expect(
controller.selection,
const TextSelection.collapsed(offset: 0),
reason: activator.toString(),
);
}
}, variant: TargetPlatformVariant.all());
testWidgets('select all non apple', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 0,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.keyA, control: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(offset: 0));
}, variant: allExceptApple);
testWidgets('select all apple', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(
offset: 0,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.keyA, meta: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(offset: 0));
}, variant: appleOnly);
testWidgets('copy non apple', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection(
baseOffset: 0,
extentOffset: 4,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.keyC, control: true));
await tester.pump();
final Map<String, dynamic> clipboardData = mockClipboard.clipboardData as Map<String, dynamic>;
expect(clipboardData['text'], 'empty');
}, variant: allExceptApple);
testWidgets('copy apple', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection(
baseOffset: 0,
extentOffset: 4,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.keyC, meta: true));
await tester.pump();
final Map<String, dynamic> clipboardData = mockClipboard.clipboardData as Map<String, dynamic>;
expect(clipboardData['text'], 'empty');
}, variant: appleOnly);
testWidgets('cut non apple', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection(
baseOffset: 0,
extentOffset: 4,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.keyX, control: true));
await tester.pump();
final Map<String, dynamic> clipboardData = mockClipboard.clipboardData as Map<String, dynamic>;
expect(clipboardData['text'], 'empty');
expect(controller.selection, const TextSelection(
baseOffset: 0,
extentOffset: 4,
));
}, variant: allExceptApple);
testWidgets('cut apple', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection(
baseOffset: 0,
extentOffset: 4,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.keyX, meta: true));
await tester.pump();
final Map<String, dynamic> clipboardData = mockClipboard.clipboardData as Map<String, dynamic>;
expect(clipboardData['text'], 'empty');
expect(controller.selection, const TextSelection(
baseOffset: 0,
extentOffset: 4,
));
}, variant: appleOnly);
testWidgets('paste non apple', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(offset: 0);
mockClipboard.clipboardData = <String, dynamic>{
'text': 'some text',
};
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.keyV, control: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(offset: 0));
expect(controller.text, testText);
}, variant: allExceptApple);
testWidgets('paste apple', (WidgetTester tester) async {
controller.text = testText;
controller.selection = const TextSelection.collapsed(offset: 0);
mockClipboard.clipboardData = <String, dynamic>{
'text': 'some text',
};
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.keyV, meta: true));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(offset: 0));
expect(controller.text, testText);
}, variant: appleOnly);
}, skip: !kIsWeb);// [intended] specific tests target web.
group('Web does accept', () {
testWidgets('select up', (WidgetTester tester) async {
const SingleActivator selectUp =
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true);
controller.text = testVerticalText;
controller.selection = const TextSelection.collapsed(
offset: 5,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, selectUp);
await tester.pump();
expect(controller.text, testVerticalText);
expect(
controller.selection,
const TextSelection(
baseOffset: 5,
extentOffset: 3), // selection extends upwards from 5
reason: selectUp.toString(),
);
}, variant: TargetPlatformVariant.desktop());
testWidgets('select down', (WidgetTester tester) async {
const SingleActivator selectDown =
SingleActivator(LogicalKeyboardKey.arrowDown, shift: true);
controller.text = testVerticalText;
controller.selection = const TextSelection.collapsed(
offset: 5,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, selectDown);
await tester.pump();
expect(controller.text, testVerticalText);
expect(
controller.selection,
const TextSelection(
baseOffset: 5,
extentOffset: 7), // selection extends downwards from 5
reason: selectDown.toString(),
);
}, variant: TargetPlatformVariant.desktop());
testWidgets('select all up', (WidgetTester tester) async {
final bool isMacOS = defaultTargetPlatform == TargetPlatform.macOS;
final SingleActivator selectAllUp = isMacOS
? const SingleActivator(LogicalKeyboardKey.arrowUp,
shift: true, meta: true)
: const SingleActivator(LogicalKeyboardKey.arrowUp,
shift: true, alt: true);
controller.text = testVerticalText;
controller.selection = const TextSelection.collapsed(
offset: 5,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, selectAllUp);
await tester.pump();
expect(controller.text, testVerticalText);
expect(
controller.selection,
const TextSelection(
baseOffset: 5,
extentOffset: 0), // selection extends all the way up
reason: selectAllUp.toString(),
);
}, variant: TargetPlatformVariant.desktop());
testWidgets('select all down', (WidgetTester tester) async {
final bool isMacOS = defaultTargetPlatform == TargetPlatform.macOS;
final SingleActivator selectAllDown = isMacOS
? const SingleActivator(LogicalKeyboardKey.arrowDown,
shift: true, meta: true)
: const SingleActivator(LogicalKeyboardKey.arrowDown,
shift: true, alt: true);
controller.text = testVerticalText;
controller.selection = const TextSelection.collapsed(
offset: 5,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, selectAllDown);
await tester.pump();
expect(controller.text, testVerticalText);
expect(
controller.selection,
const TextSelection(
baseOffset: 5,
extentOffset: 17), // selection extends all the way down
reason: selectAllDown.toString(),
);
}, variant: TargetPlatformVariant.desktop());
testWidgets('select left', (WidgetTester tester) async {
const SingleActivator selectLeft =
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true);
controller.text = 'testing';
controller.selection = const TextSelection.collapsed(
offset: 5,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, selectLeft);
await tester.pump();
expect(controller.text, 'testing');
expect(
controller.selection,
const TextSelection(baseOffset: 5, extentOffset: 4),
reason: selectLeft.toString(),
);
}, variant: TargetPlatformVariant.desktop());
testWidgets('select right', (WidgetTester tester) async {
const SingleActivator selectRight =
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true);
controller.text = 'testing';
controller.selection = const TextSelection.collapsed(
offset: 5,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, selectRight);
await tester.pump();
expect(controller.text, 'testing');
expect(
controller.selection,
const TextSelection(baseOffset: 5, extentOffset: 6),
reason: selectRight.toString(),
);
}, variant: TargetPlatformVariant.desktop());
testWidgets(
'select left should not expand selection if selection is disabled',
(WidgetTester tester) async {
const SingleActivator selectLeft =
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true);
controller.text = 'testing';
controller.selection = const TextSelection.collapsed(
offset: 5,
);
await tester
.pumpWidget(buildEditableText(enableInteractiveSelection: false));
await sendKeyCombination(tester, selectLeft);
await tester.pump();
expect(controller.text, 'testing');
expect(
controller.selection,
const TextSelection.collapsed(offset: 4), // should not expand selection
reason: selectLeft.toString(),
);
}, variant: TargetPlatformVariant.desktop());
testWidgets(
'select right should not expand selection if selection is disabled',
(WidgetTester tester) async {
const SingleActivator selectRight =
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true);
controller.text = 'testing';
controller.selection = const TextSelection.collapsed(offset: 5);
await tester
.pumpWidget(buildEditableText(enableInteractiveSelection: false));
await sendKeyCombination(tester, selectRight);
await tester.pump();
expect(controller.text, 'testing');
expect(
controller.selection,
const TextSelection.collapsed(
offset: 6,
affinity: TextAffinity.upstream), // should not expand selection
reason: selectRight.toString(),
);
}, variant: TargetPlatformVariant.desktop());
testWidgets('select all left', (WidgetTester tester) async {
final bool isMacOS = defaultTargetPlatform == TargetPlatform.macOS;
final SingleActivator selectAllLeft = isMacOS
? const SingleActivator(LogicalKeyboardKey.arrowLeft,
shift: true, meta: true)
: const SingleActivator(LogicalKeyboardKey.arrowLeft,
shift: true, alt: true);
controller.text = 'testing';
controller.selection = const TextSelection.collapsed(
offset: 5,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, selectAllLeft);
await tester.pump();
expect(controller.text, 'testing');
expect(
controller.selection,
const TextSelection(baseOffset: 5, extentOffset: 0),
reason: selectAllLeft.toString(),
);
}, variant: TargetPlatformVariant.desktop());
testWidgets('select all right', (WidgetTester tester) async {
final bool isMacOS = defaultTargetPlatform == TargetPlatform.macOS;
final SingleActivator selectAllRight = isMacOS
? const SingleActivator(LogicalKeyboardKey.arrowRight,
shift: true, meta: true)
: const SingleActivator(LogicalKeyboardKey.arrowRight,
shift: true, alt: true);
controller.text = 'testing';
controller.selection = const TextSelection.collapsed(
offset: 5,
);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, selectAllRight);
await tester.pump();
expect(controller.text, 'testing');
expect(
controller.selection,
const TextSelection(baseOffset: 5, extentOffset: 7),
reason: selectAllRight.toString(),
);
}, variant: TargetPlatformVariant.desktop());
}, skip: !kIsWeb); // [intended] specific tests target web.
}