blob: 96b4c9c5353766a430377b2745b614eca7e4bb53 [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 'keyboard_utils.dart';
void main() {
Widget buildSpyAboveEditableText({
required FocusNode editableFocusNode,
required FocusNode spyFocusNode,
}) {
final TextEditingController controller = TextEditingController(text: 'dummy text');
addTearDown(controller.dispose);
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: controller,
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('iOS: do not handle delete/backspace events', () {
final TargetPlatformVariant iOS = TargetPlatformVariant.only(TargetPlatform.iOS);
final FocusNode editable = FocusNode();
final FocusNode spy = FocusNode();
testWidgets('backspace with and without word modifier', (WidgetTester tester) async {
tester.binding.testTextInput.unregister();
addTearDown(tester.binding.testTextInput.register);
await tester.pumpWidget(
buildSpyAboveEditableText(
editableFocusNode: editable,
spyFocusNode: spy,
),
);
editable.requestFocus();
await tester.pump();
final ActionSpyState state = tester.state<ActionSpyState>(find.byType(ActionSpy));
for (int altShiftState = 0; altShiftState < 1 << 2; altShiftState += 1) {
final bool alt = altShiftState & 0x1 != 0;
final bool shift = altShiftState & 0x2 != 0;
await sendKeyCombination(tester, SingleActivator(LogicalKeyboardKey.backspace, alt: alt, shift: shift));
}
await tester.pump();
expect(state.lastIntent, isNull);
}, variant: iOS);
testWidgets('delete with and without word modifier', (WidgetTester tester) async {
tester.binding.testTextInput.unregister();
addTearDown(tester.binding.testTextInput.register);
await tester.pumpWidget(
buildSpyAboveEditableText(
editableFocusNode: editable,
spyFocusNode: spy,
),
);
editable.requestFocus();
await tester.pump();
final ActionSpyState state = tester.state<ActionSpyState>(find.byType(ActionSpy));
for (int altShiftState = 0; altShiftState < 1 << 2; altShiftState += 1) {
final bool alt = altShiftState & 0x1 != 0;
final bool shift = altShiftState & 0x2 != 0;
await sendKeyCombination(tester, SingleActivator(LogicalKeyboardKey.delete, alt: alt, shift: shift));
}
await tester.pump();
expect(state.lastIntent, isNull);
}, variant: iOS);
testWidgets('Exception: deleting to line boundary is handled by the framework', (WidgetTester tester) async {
tester.binding.testTextInput.unregister();
addTearDown(tester.binding.testTextInput.register);
await tester.pumpWidget(
buildSpyAboveEditableText(
editableFocusNode: editable,
spyFocusNode: spy,
),
);
editable.requestFocus();
await tester.pump();
final ActionSpyState state = tester.state<ActionSpyState>(find.byType(ActionSpy));
for (int keyState = 0; keyState < 1 << 2; keyState += 1) {
final bool shift = keyState & 0x1 != 0;
final LogicalKeyboardKey key = keyState & 0x2 != 0 ? LogicalKeyboardKey.delete : LogicalKeyboardKey.backspace;
state.lastIntent = null;
final SingleActivator activator = SingleActivator(key, meta: true, shift: shift);
await sendKeyCombination(tester, activator);
await tester.pump();
expect(state.lastIntent, isA<DeleteToLineBreakIntent>(), reason: '$activator');
}
}, variant: iOS);
}, skip: kIsWeb); // [intended] specific tests target non-web.
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();
addTearDown(editable.dispose);
final FocusNode spy = FocusNode();
addTearDown(spy.dispose);
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();
addTearDown(editable.dispose);
final FocusNode spy = FocusNode();
addTearDown(spy.dispose);
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();
addTearDown(editable.dispose);
final FocusNode spy = FocusNode();
addTearDown(spy.dispose);
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();
addTearDown(editable.dispose);
final FocusNode spy = FocusNode();
addTearDown(spy.dispose);
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();
addTearDown(editable.dispose);
final FocusNode spy = FocusNode();
addTearDown(spy.dispose);
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();
addTearDown(editable.dispose);
final FocusNode spy = FocusNode();
addTearDown(spy.dispose);
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();
addTearDown(editable.dispose);
final FocusNode spy = FocusNode();
addTearDown(spy.dispose);
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();
addTearDown(editable.dispose);
final FocusNode spy = FocusNode();
addTearDown(spy.dispose);
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();
addTearDown(editable.dispose);
final FocusNode spy = FocusNode();
addTearDown(spy.dispose);
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();
addTearDown(editable.dispose);
final FocusNode spy = FocusNode();
addTearDown(spy.dispose);
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();
addTearDown(editable.dispose);
final FocusNode spy = FocusNode();
addTearDown(spy.dispose);
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();
addTearDown(editable.dispose);
final FocusNode spy = FocusNode();
addTearDown(spy.dispose);
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.
group('Linux does accept numpad shortcuts', () {
testWidgets('when numlock is locked', (WidgetTester tester) async {
final FocusNode editable = FocusNode();
addTearDown(editable.dispose);
final FocusNode spy = FocusNode();
addTearDown(spy.dispose);
await tester.pumpWidget(
buildSpyAboveEditableText(
editableFocusNode: editable,
spyFocusNode: spy,
),
);
spy.requestFocus();
await tester.pump();
final ActionSpyState state = tester.state<ActionSpyState>(find.byType(ActionSpy));
// Lock NumLock.
await tester.sendKeyEvent(LogicalKeyboardKey.numLock);
expect(HardwareKeyboard.instance.lockModesEnabled.contains(KeyboardLockMode.numLock), isTrue);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad6, shift: true));
expect(state.lastIntent, isA<ExtendSelectionByCharacterIntent>());
expect((state.lastIntent! as ExtendSelectionByCharacterIntent).forward, true);
expect((state.lastIntent! as ExtendSelectionByCharacterIntent).collapseSelection, false);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad4, shift: true));
expect(state.lastIntent, isA<ExtendSelectionByCharacterIntent>());
expect((state.lastIntent! as ExtendSelectionByCharacterIntent).forward, false);
expect((state.lastIntent! as ExtendSelectionByCharacterIntent).collapseSelection, false);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad8, shift: true));
expect(state.lastIntent, isA<ExtendSelectionVerticallyToAdjacentLineIntent>());
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentLineIntent).forward, false);
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentLineIntent).collapseSelection, false);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad2, shift: true));
expect(state.lastIntent, isA<ExtendSelectionVerticallyToAdjacentLineIntent>());
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentLineIntent).forward, true);
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentLineIntent).collapseSelection, false);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad9, shift: true));
expect(state.lastIntent, isA<ExtendSelectionVerticallyToAdjacentPageIntent>());
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentPageIntent).forward, false);
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentPageIntent).collapseSelection, false);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad3, shift: true));
expect(state.lastIntent, isA<ExtendSelectionVerticallyToAdjacentPageIntent>());
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentPageIntent).forward, true);
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentPageIntent).collapseSelection, false);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad7, shift: true));
expect(state.lastIntent, isA<ExtendSelectionVerticallyToAdjacentLineIntent>());
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentLineIntent).forward, false);
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentLineIntent).collapseSelection, false);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad1, shift: true));
expect(state.lastIntent, isA<ExtendSelectionVerticallyToAdjacentLineIntent>());
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentLineIntent).forward, true);
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentLineIntent).collapseSelection, false);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpadDecimal, shift: true));
expect(state.lastIntent, isA<DeleteCharacterIntent>());
expect((state.lastIntent! as DeleteCharacterIntent).forward, true);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad6, shift: true, control: true));
expect(state.lastIntent, isA<ExtendSelectionToNextWordBoundaryIntent>());
expect((state.lastIntent! as ExtendSelectionToNextWordBoundaryIntent).forward, true);
expect((state.lastIntent! as ExtendSelectionToNextWordBoundaryIntent).collapseSelection, false);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad4, shift: true, control: true));
expect(state.lastIntent, isA<ExtendSelectionToNextWordBoundaryIntent>());
expect((state.lastIntent! as ExtendSelectionToNextWordBoundaryIntent).forward, false);
expect((state.lastIntent! as ExtendSelectionToNextWordBoundaryIntent).collapseSelection, false);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad8, shift: true, control: true));
expect(state.lastIntent, isA<ExtendSelectionToNextParagraphBoundaryIntent>());
expect((state.lastIntent! as ExtendSelectionToNextParagraphBoundaryIntent).forward, false);
expect((state.lastIntent! as ExtendSelectionToNextParagraphBoundaryIntent).collapseSelection, false);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad2, shift: true, control: true));
expect(state.lastIntent, isA<ExtendSelectionToNextParagraphBoundaryIntent>());
expect((state.lastIntent! as ExtendSelectionToNextParagraphBoundaryIntent).forward, true);
expect((state.lastIntent! as ExtendSelectionToNextParagraphBoundaryIntent).collapseSelection, false);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpadDecimal, shift: true, control: true));
expect(state.lastIntent, isA<DeleteToNextWordBoundaryIntent>());
expect((state.lastIntent! as DeleteToNextWordBoundaryIntent).forward, true);
}, variant: TargetPlatformVariant.only(TargetPlatform.linux));
testWidgets('when numlock is unlocked', (WidgetTester tester) async {
final FocusNode editable = FocusNode();
addTearDown(editable.dispose);
final FocusNode spy = FocusNode();
addTearDown(spy.dispose);
await tester.pumpWidget(
buildSpyAboveEditableText(
editableFocusNode: editable,
spyFocusNode: spy,
),
);
spy.requestFocus();
await tester.pump();
final ActionSpyState state = tester.state<ActionSpyState>(find.byType(ActionSpy));
// Verify that NumLock is unlocked.
expect(HardwareKeyboard.instance.lockModesEnabled.contains(KeyboardLockMode.numLock), isFalse);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad6));
expect(state.lastIntent, isA<ExtendSelectionByCharacterIntent>());
expect((state.lastIntent! as ExtendSelectionByCharacterIntent).forward, true);
expect((state.lastIntent! as ExtendSelectionByCharacterIntent).collapseSelection, true);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad4));
expect(state.lastIntent, isA<ExtendSelectionByCharacterIntent>());
expect((state.lastIntent! as ExtendSelectionByCharacterIntent).forward, false);
expect((state.lastIntent! as ExtendSelectionByCharacterIntent).collapseSelection, true);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad8));
expect(state.lastIntent, isA<ExtendSelectionVerticallyToAdjacentLineIntent>());
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentLineIntent).forward, false);
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentLineIntent).collapseSelection, true);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad2));
expect(state.lastIntent, isA<ExtendSelectionVerticallyToAdjacentLineIntent>());
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentLineIntent).forward, true);
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentLineIntent).collapseSelection, true);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad9));
expect(state.lastIntent, isA<ExtendSelectionVerticallyToAdjacentPageIntent>());
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentPageIntent).forward, false);
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentPageIntent).collapseSelection, true);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad3));
expect(state.lastIntent, isA<ExtendSelectionVerticallyToAdjacentPageIntent>());
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentPageIntent).forward, true);
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentPageIntent).collapseSelection, true);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad7));
expect(state.lastIntent, isA<ExtendSelectionVerticallyToAdjacentLineIntent>());
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentLineIntent).forward, false);
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentLineIntent).collapseSelection, true);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad1));
expect(state.lastIntent, isA<ExtendSelectionVerticallyToAdjacentLineIntent>());
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentLineIntent).forward, true);
expect((state.lastIntent! as ExtendSelectionVerticallyToAdjacentLineIntent).collapseSelection, true);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpadDecimal));
expect(state.lastIntent, isA<DeleteCharacterIntent>());
expect((state.lastIntent! as DeleteCharacterIntent).forward, true);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad6, control: true));
expect(state.lastIntent, isA<ExtendSelectionToNextWordBoundaryIntent>());
expect((state.lastIntent! as ExtendSelectionToNextWordBoundaryIntent).forward, true);
expect((state.lastIntent! as ExtendSelectionToNextWordBoundaryIntent).collapseSelection, true);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad4, control: true));
expect(state.lastIntent, isA<ExtendSelectionToNextWordBoundaryIntent>());
expect((state.lastIntent! as ExtendSelectionToNextWordBoundaryIntent).forward, false);
expect((state.lastIntent! as ExtendSelectionToNextWordBoundaryIntent).collapseSelection, true);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad8, control: true));
expect(state.lastIntent, isA<ExtendSelectionToNextParagraphBoundaryIntent>());
expect((state.lastIntent! as ExtendSelectionToNextParagraphBoundaryIntent).forward, false);
expect((state.lastIntent! as ExtendSelectionToNextParagraphBoundaryIntent).collapseSelection, true);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpad2, control: true));
expect(state.lastIntent, isA<ExtendSelectionToNextParagraphBoundaryIntent>());
expect((state.lastIntent! as ExtendSelectionToNextParagraphBoundaryIntent).forward, true);
expect((state.lastIntent! as ExtendSelectionToNextParagraphBoundaryIntent).collapseSelection, true);
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.numpadDecimal, control: true));
expect(state.lastIntent, isA<DeleteToNextWordBoundaryIntent>());
expect((state.lastIntent! as DeleteToNextWordBoundaryIntent).forward, true);
}, variant: TargetPlatformVariant.only(TargetPlatform.linux));
}, 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),
ExtendSelectionToNextParagraphBoundaryIntent: CallbackAction<ExtendSelectionToNextParagraphBoundaryIntent>(onInvoke: _captureIntent),
ExtendSelectionVerticallyToAdjacentPageIntent: CallbackAction<ExtendSelectionVerticallyToAdjacentPageIntent>(onInvoke: _captureIntent),
DeleteToLineBreakIntent: CallbackAction<DeleteToLineBreakIntent>(onInvoke: _captureIntent),
DeleteToNextWordBoundaryIntent: CallbackAction<DeleteToNextWordBoundaryIntent>(onInvoke: _captureIntent),
DeleteCharacterIntent: CallbackAction<DeleteCharacterIntent>(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,
),
);
}
}