| // 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, |
| ), |
| ); |
| } |
| } |