Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
7e8f0e57
Unverified
Commit
7e8f0e57
authored
Aug 03, 2022
by
Matej Knopp
Committed by
GitHub
Aug 03, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[macOS] Use editing intents from engine (#105407)
parent
f7c41d09
Changes
11
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
597 additions
and
21 deletions
+597
-21
text_input.dart
packages/flutter/lib/src/services/text_input.dart
+9
-0
default_text_editing_shortcuts.dart
...utter/lib/src/widgets/default_text_editing_shortcuts.dart
+95
-5
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+23
-1
autofill_test.dart
packages/flutter/test/services/autofill_test.dart
+5
-0
delta_text_input_test.dart
packages/flutter/test/services/delta_text_input_test.dart
+5
-0
text_input_test.dart
packages/flutter/test/services/text_input_test.dart
+36
-0
editable_text_test.dart
packages/flutter/test/widgets/editable_text_test.dart
+90
-11
event_simulation.dart
packages/flutter_test/lib/src/event_simulation.dart
+14
-4
test_text_input.dart
packages/flutter_test/lib/src/test_text_input.dart
+20
-0
test_text_input_key_handler.dart
...ges/flutter_test/lib/src/test_text_input_key_handler.dart
+279
-0
test_text_input_test.dart
packages/flutter_test/test/test_text_input_test.dart
+21
-0
No files found.
packages/flutter/lib/src/services/text_input.dart
View file @
7e8f0e57
...
@@ -1165,6 +1165,11 @@ mixin TextInputClient {
...
@@ -1165,6 +1165,11 @@ mixin TextInputClient {
/// Requests that the client remove the text placeholder.
/// Requests that the client remove the text placeholder.
void
removeTextPlaceholder
()
{}
void
removeTextPlaceholder
()
{}
/// Performs the specified MacOS-specific selector from the
/// `NSStandardKeyBindingResponding` protocol or user-specified selector
/// from `DefaultKeyBinding.Dict`.
void
performSelector
(
String
selectorName
)
{}
}
}
/// An interface to receive focus from the engine.
/// An interface to receive focus from the engine.
...
@@ -1819,6 +1824,10 @@ class TextInput {
...
@@ -1819,6 +1824,10 @@ class TextInput {
case
'TextInputClient.performAction'
:
case
'TextInputClient.performAction'
:
_currentConnection
!.
_client
.
performAction
(
_toTextInputAction
(
args
[
1
]
as
String
));
_currentConnection
!.
_client
.
performAction
(
_toTextInputAction
(
args
[
1
]
as
String
));
break
;
break
;
case
'TextInputClient.performSelectors'
:
final
List
<
String
>
selectors
=
(
args
[
1
]
as
List
<
dynamic
>).
cast
<
String
>();
selectors
.
forEach
(
_currentConnection
!.
_client
.
performSelector
);
break
;
case
'TextInputClient.performPrivateCommand'
:
case
'TextInputClient.performPrivateCommand'
:
final
Map
<
String
,
dynamic
>
firstArg
=
args
[
1
]
as
Map
<
String
,
dynamic
>;
final
Map
<
String
,
dynamic
>
firstArg
=
args
[
1
]
as
Map
<
String
,
dynamic
>;
_currentConnection
!.
_client
.
performPrivateCommand
(
_currentConnection
!.
_client
.
performPrivateCommand
(
...
...
packages/flutter/lib/src/widgets/default_text_editing_shortcuts.dart
View file @
7e8f0e57
...
@@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
...
@@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
import
'package:flutter/services.dart'
;
import
'package:flutter/services.dart'
;
import
'actions.dart'
;
import
'actions.dart'
;
import
'focus_traversal.dart'
;
import
'framework.dart'
;
import
'framework.dart'
;
import
'shortcuts.dart'
;
import
'shortcuts.dart'
;
import
'text_editing_intents.dart'
;
import
'text_editing_intents.dart'
;
...
@@ -258,6 +259,34 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
...
@@ -258,6 +259,34 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
// The macOS shortcuts uses different word/line modifiers than most other
// The macOS shortcuts uses different word/line modifiers than most other
// platforms.
// platforms.
static
final
Map
<
ShortcutActivator
,
Intent
>
_macShortcuts
=
<
ShortcutActivator
,
Intent
>{
static
final
Map
<
ShortcutActivator
,
Intent
>
_macShortcuts
=
<
ShortcutActivator
,
Intent
>{
const
SingleActivator
(
LogicalKeyboardKey
.
keyX
,
meta:
true
):
const
CopySelectionTextIntent
.
cut
(
SelectionChangedCause
.
keyboard
),
const
SingleActivator
(
LogicalKeyboardKey
.
keyC
,
meta:
true
):
CopySelectionTextIntent
.
copy
,
const
SingleActivator
(
LogicalKeyboardKey
.
keyV
,
meta:
true
):
const
PasteTextIntent
(
SelectionChangedCause
.
keyboard
),
const
SingleActivator
(
LogicalKeyboardKey
.
keyA
,
meta:
true
):
const
SelectAllTextIntent
(
SelectionChangedCause
.
keyboard
),
const
SingleActivator
(
LogicalKeyboardKey
.
keyZ
,
meta:
true
):
const
UndoTextIntent
(
SelectionChangedCause
.
keyboard
),
const
SingleActivator
(
LogicalKeyboardKey
.
keyZ
,
shift:
true
,
meta:
true
):
const
RedoTextIntent
(
SelectionChangedCause
.
keyboard
),
// On desktop these keys should go to the IME when a field is focused, not to other
// Shortcuts.
if
(!
kIsWeb
)
...<
ShortcutActivator
,
Intent
>{
const
SingleActivator
(
LogicalKeyboardKey
.
arrowLeft
):
const
DoNothingAndStopPropagationTextIntent
(),
const
SingleActivator
(
LogicalKeyboardKey
.
arrowRight
):
const
DoNothingAndStopPropagationTextIntent
(),
const
SingleActivator
(
LogicalKeyboardKey
.
arrowUp
):
const
DoNothingAndStopPropagationTextIntent
(),
const
SingleActivator
(
LogicalKeyboardKey
.
arrowDown
):
const
DoNothingAndStopPropagationTextIntent
(),
const
SingleActivator
(
LogicalKeyboardKey
.
arrowLeft
,
meta:
true
):
const
DoNothingAndStopPropagationTextIntent
(),
const
SingleActivator
(
LogicalKeyboardKey
.
arrowRight
,
meta:
true
):
const
DoNothingAndStopPropagationTextIntent
(),
const
SingleActivator
(
LogicalKeyboardKey
.
arrowUp
,
meta:
true
):
const
DoNothingAndStopPropagationTextIntent
(),
const
SingleActivator
(
LogicalKeyboardKey
.
arrowDown
,
meta:
true
):
const
DoNothingAndStopPropagationTextIntent
(),
const
SingleActivator
(
LogicalKeyboardKey
.
escape
):
const
DoNothingAndStopPropagationTextIntent
(),
const
SingleActivator
(
LogicalKeyboardKey
.
space
):
const
DoNothingAndStopPropagationTextIntent
(),
const
SingleActivator
(
LogicalKeyboardKey
.
enter
):
const
DoNothingAndStopPropagationTextIntent
(),
const
SingleActivator
(
LogicalKeyboardKey
.
tab
):
const
DoNothingAndStopPropagationTextIntent
(),
const
SingleActivator
(
LogicalKeyboardKey
.
tab
,
shift:
true
):
const
DoNothingAndStopPropagationTextIntent
(),
},
};
// There is no complete documentation of iOS shortcuts.
static
final
Map
<
ShortcutActivator
,
Intent
>
_iOSShortcuts
=
<
ShortcutActivator
,
Intent
>{
for
(
final
bool
pressShift
in
const
<
bool
>[
true
,
false
])
for
(
final
bool
pressShift
in
const
<
bool
>[
true
,
false
])
...<
SingleActivator
,
Intent
>{
...<
SingleActivator
,
Intent
>{
SingleActivator
(
LogicalKeyboardKey
.
backspace
,
shift:
pressShift
):
const
DeleteCharacterIntent
(
forward:
false
),
SingleActivator
(
LogicalKeyboardKey
.
backspace
,
shift:
pressShift
):
const
DeleteCharacterIntent
(
forward:
false
),
...
@@ -296,8 +325,8 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
...
@@ -296,8 +325,8 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
const
SingleActivator
(
LogicalKeyboardKey
.
arrowLeft
,
shift:
true
,
meta:
true
):
const
ExpandSelectionToLineBreakIntent
(
forward:
false
),
const
SingleActivator
(
LogicalKeyboardKey
.
arrowLeft
,
shift:
true
,
meta:
true
):
const
ExpandSelectionToLineBreakIntent
(
forward:
false
),
const
SingleActivator
(
LogicalKeyboardKey
.
arrowRight
,
shift:
true
,
meta:
true
):
const
ExpandSelectionToLineBreakIntent
(
forward:
true
),
const
SingleActivator
(
LogicalKeyboardKey
.
arrowRight
,
shift:
true
,
meta:
true
):
const
ExpandSelectionToLineBreakIntent
(
forward:
true
),
const
SingleActivator
(
LogicalKeyboardKey
.
arrowUp
,
shift:
true
,
meta:
true
):
const
Ex
tendSelectionToDocumentBoundaryIntent
(
forward:
false
,
collapseSelection
:
false
),
const
SingleActivator
(
LogicalKeyboardKey
.
arrowUp
,
shift:
true
,
meta:
true
):
const
Ex
pandSelectionToDocumentBoundaryIntent
(
forward
:
false
),
const
SingleActivator
(
LogicalKeyboardKey
.
arrowDown
,
shift:
true
,
meta:
true
):
const
Ex
tendSelectionToDocumentBoundaryIntent
(
forward:
true
,
collapseSelection:
fals
e
),
const
SingleActivator
(
LogicalKeyboardKey
.
arrowDown
,
shift:
true
,
meta:
true
):
const
Ex
pandSelectionToDocumentBoundaryIntent
(
forward:
tru
e
),
const
SingleActivator
(
LogicalKeyboardKey
.
keyT
,
control:
true
):
const
TransposeCharactersIntent
(),
const
SingleActivator
(
LogicalKeyboardKey
.
keyT
,
control:
true
):
const
TransposeCharactersIntent
(),
...
@@ -331,9 +360,6 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
...
@@ -331,9 +360,6 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
// * Control + shift? + Z
// * Control + shift? + Z
};
};
// There is no complete documentation of iOS shortcuts. Use mac shortcuts for
// now.
static
final
Map
<
ShortcutActivator
,
Intent
>
_iOSShortcuts
=
_macShortcuts
;
// The following key combinations have no effect on text editing on this
// The following key combinations have no effect on text editing on this
// platform:
// platform:
...
@@ -461,3 +487,67 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
...
@@ -461,3 +487,67 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
);
);
}
}
}
}
/// Maps the selector from NSStandardKeyBindingResponding to the Intent if the
/// selector is recognized.
Intent
?
intentForMacOSSelector
(
String
selectorName
)
{
const
Map
<
String
,
Intent
>
selectorToIntent
=
<
String
,
Intent
>{
'deleteBackward:'
:
DeleteCharacterIntent
(
forward:
false
),
'deleteWordBackward:'
:
DeleteToNextWordBoundaryIntent
(
forward:
false
),
'deleteToBeginningOfLine:'
:
DeleteToLineBreakIntent
(
forward:
false
),
'deleteForward:'
:
DeleteCharacterIntent
(
forward:
true
),
'deleteWordForward:'
:
DeleteToNextWordBoundaryIntent
(
forward:
true
),
'deleteToEndOfLine:'
:
DeleteToLineBreakIntent
(
forward:
true
),
'moveLeft:'
:
ExtendSelectionByCharacterIntent
(
forward:
false
,
collapseSelection:
true
),
'moveRight:'
:
ExtendSelectionByCharacterIntent
(
forward:
true
,
collapseSelection:
true
),
'moveForward:'
:
ExtendSelectionByCharacterIntent
(
forward:
true
,
collapseSelection:
true
),
'moveBackward:'
:
ExtendSelectionByCharacterIntent
(
forward:
false
,
collapseSelection:
true
),
'moveUp:'
:
ExtendSelectionVerticallyToAdjacentLineIntent
(
forward:
false
,
collapseSelection:
true
),
'moveDown:'
:
ExtendSelectionVerticallyToAdjacentLineIntent
(
forward:
true
,
collapseSelection:
true
),
'moveLeftAndModifySelection:'
:
ExtendSelectionByCharacterIntent
(
forward:
false
,
collapseSelection:
false
),
'moveRightAndModifySelection:'
:
ExtendSelectionByCharacterIntent
(
forward:
true
,
collapseSelection:
false
),
'moveUpAndModifySelection:'
:
ExtendSelectionVerticallyToAdjacentLineIntent
(
forward:
false
,
collapseSelection:
false
),
'moveDownAndModifySelection:'
:
ExtendSelectionVerticallyToAdjacentLineIntent
(
forward:
true
,
collapseSelection:
false
),
'moveWordLeft:'
:
ExtendSelectionToNextWordBoundaryIntent
(
forward:
false
,
collapseSelection:
true
),
'moveWordRight:'
:
ExtendSelectionToNextWordBoundaryIntent
(
forward:
true
,
collapseSelection:
true
),
'moveToBeginningOfParagraph:'
:
ExtendSelectionToLineBreakIntent
(
forward:
false
,
collapseSelection:
true
),
'moveToEndOfParagraph:'
:
ExtendSelectionToLineBreakIntent
(
forward:
true
,
collapseSelection:
true
),
'moveWordLeftAndModifySelection:'
:
ExtendSelectionToNextWordBoundaryOrCaretLocationIntent
(
forward:
false
),
'moveWordRightAndModifySelection:'
:
ExtendSelectionToNextWordBoundaryOrCaretLocationIntent
(
forward:
true
),
'moveParagraphBackwardAndModifySelection:'
:
ExtendSelectionToLineBreakIntent
(
forward:
false
,
collapseSelection:
false
,
collapseAtReversal:
true
),
'moveParagraphForwardAndModifySelection:'
:
ExtendSelectionToLineBreakIntent
(
forward:
true
,
collapseSelection:
false
,
collapseAtReversal:
true
),
'moveToLeftEndOfLine:'
:
ExtendSelectionToLineBreakIntent
(
forward:
false
,
collapseSelection:
true
),
'moveToRightEndOfLine:'
:
ExtendSelectionToLineBreakIntent
(
forward:
true
,
collapseSelection:
true
),
'moveToBeginningOfDocument:'
:
ExtendSelectionToDocumentBoundaryIntent
(
forward:
false
,
collapseSelection:
true
),
'moveToEndOfDocument:'
:
ExtendSelectionToDocumentBoundaryIntent
(
forward:
true
,
collapseSelection:
true
),
'moveToLeftEndOfLineAndModifySelection:'
:
ExpandSelectionToLineBreakIntent
(
forward:
false
),
'moveToRightEndOfLineAndModifySelection:'
:
ExpandSelectionToLineBreakIntent
(
forward:
true
),
'moveToBeginningOfDocumentAndModifySelection:'
:
ExpandSelectionToDocumentBoundaryIntent
(
forward:
false
),
'moveToEndOfDocumentAndModifySelection:'
:
ExpandSelectionToDocumentBoundaryIntent
(
forward:
true
),
'transpose:'
:
TransposeCharactersIntent
(),
'scrollToBeginningOfDocument:'
:
ScrollToDocumentBoundaryIntent
(
forward:
false
),
'scrollToEndOfDocument:'
:
ScrollToDocumentBoundaryIntent
(
forward:
true
),
// TODO(knopp): Page Up/Down intents are missing (https://github.com/flutter/flutter/pull/105497)
'scrollPageUp:'
:
ScrollToDocumentBoundaryIntent
(
forward:
false
),
'scrollPageDown:'
:
ScrollToDocumentBoundaryIntent
(
forward:
true
),
'pageUpAndModifySelection'
:
ExpandSelectionToDocumentBoundaryIntent
(
forward:
false
),
'pageDownAndModifySelection:'
:
ExpandSelectionToDocumentBoundaryIntent
(
forward:
true
),
// Escape key when there's no IME selection popup.
'cancelOperation:'
:
DismissIntent
(),
// Tab when there's no IME selection.
'insertTab:'
:
NextFocusIntent
(),
'insertBacktab:'
:
PreviousFocusIntent
(),
};
return
selectorToIntent
[
selectorName
];
}
packages/flutter/lib/src/widgets/editable_text.dart
View file @
7e8f0e57
...
@@ -21,6 +21,7 @@ import 'binding.dart';
...
@@ -21,6 +21,7 @@ import 'binding.dart';
import
'constants.dart'
;
import
'constants.dart'
;
import
'debug.dart'
;
import
'debug.dart'
;
import
'default_selection_style.dart'
;
import
'default_selection_style.dart'
;
import
'default_text_editing_shortcuts.dart'
;
import
'focus_manager.dart'
;
import
'focus_manager.dart'
;
import
'focus_scope.dart'
;
import
'focus_scope.dart'
;
import
'focus_traversal.dart'
;
import
'focus_traversal.dart'
;
...
@@ -3227,6 +3228,18 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
...
@@ -3227,6 +3228,18 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
});
});
}
}
@override
void
performSelector
(
String
selectorName
)
{
final
Intent
?
intent
=
intentForMacOSSelector
(
selectorName
);
if
(
intent
!=
null
)
{
final
BuildContext
?
primaryContext
=
primaryFocus
?.
context
;
if
(
primaryContext
!=
null
)
{
Actions
.
invoke
(
primaryContext
,
intent
);
}
}
}
@override
@override
String
get
autofillId
=>
'EditableText-
$hashCode
'
;
String
get
autofillId
=>
'EditableText-
$hashCode
'
;
...
@@ -4421,7 +4434,16 @@ class _UpdateTextSelectionAction<T extends DirectionalCaretMovementIntent> exten
...
@@ -4421,7 +4434,16 @@ class _UpdateTextSelectionAction<T extends DirectionalCaretMovementIntent> exten
}
}
final
_TextBoundary
textBoundary
=
getTextBoundariesForIntent
(
intent
);
final
_TextBoundary
textBoundary
=
getTextBoundariesForIntent
(
intent
);
final
TextSelection
textBoundarySelection
=
textBoundary
.
textEditingValue
.
selection
;
// "textBoundary's selection is only updated after rebuild; if the text
// is the same, use the selection from state, which is more recent.
// This is necessary on macOS where alt+up sends the moveBackward:
// and moveToBeginningOfParagraph: selectors at the same time.
final
TextSelection
textBoundarySelection
=
textBoundary
.
textEditingValue
.
text
==
state
.
_value
.
text
?
state
.
_value
.
selection
:
textBoundary
.
textEditingValue
.
selection
;
if
(!
textBoundarySelection
.
isValid
)
{
if
(!
textBoundarySelection
.
isValid
)
{
return
null
;
return
null
;
}
}
...
...
packages/flutter/test/services/autofill_test.dart
View file @
7e8f0e57
...
@@ -156,6 +156,11 @@ class FakeAutofillClient implements TextInputClient, AutofillClient {
...
@@ -156,6 +156,11 @@ class FakeAutofillClient implements TextInputClient, AutofillClient {
void
removeTextPlaceholder
()
{
void
removeTextPlaceholder
()
{
latestMethodCall
=
'removeTextPlaceholder'
;
latestMethodCall
=
'removeTextPlaceholder'
;
}
}
@override
void
performSelector
(
String
selectorName
)
{
latestMethodCall
=
'performSelector'
;
}
}
}
class
FakeAutofillScope
with
AutofillScopeMixin
implements
AutofillScope
{
class
FakeAutofillScope
with
AutofillScopeMixin
implements
AutofillScope
{
...
...
packages/flutter/test/services/delta_text_input_test.dart
View file @
7e8f0e57
...
@@ -286,5 +286,10 @@ class FakeDeltaTextInputClient implements DeltaTextInputClient {
...
@@ -286,5 +286,10 @@ class FakeDeltaTextInputClient implements DeltaTextInputClient {
latestMethodCall
=
'showToolbar'
;
latestMethodCall
=
'showToolbar'
;
}
}
@override
void
performSelector
(
String
selectorName
)
{
latestMethodCall
=
'performSelector'
;
}
TextInputConfiguration
get
configuration
=>
const
TextInputConfiguration
(
enableDeltaModel:
true
);
TextInputConfiguration
get
configuration
=>
const
TextInputConfiguration
(
enableDeltaModel:
true
);
}
}
packages/flutter/test/services/text_input_test.dart
View file @
7e8f0e57
...
@@ -379,6 +379,35 @@ void main() {
...
@@ -379,6 +379,35 @@ void main() {
expect
(
client
.
latestMethodCall
,
'connectionClosed'
);
expect
(
client
.
latestMethodCall
,
'connectionClosed'
);
});
});
test
(
'TextInputClient performSelectors method is called'
,
()
async
{
final
FakeTextInputClient
client
=
FakeTextInputClient
(
TextEditingValue
.
empty
);
const
TextInputConfiguration
configuration
=
TextInputConfiguration
();
TextInput
.
attach
(
client
,
configuration
);
expect
(
client
.
performedSelectors
,
isEmpty
);
expect
(
client
.
latestMethodCall
,
isEmpty
);
// Send performSelectors message.
final
ByteData
?
messageBytes
=
const
JSONMessageCodec
().
encodeMessage
(<
String
,
dynamic
>{
'args'
:
<
dynamic
>[
1
,
<
dynamic
>[
'selector1'
,
'selector2'
,
]
],
'method'
:
'TextInputClient.performSelectors'
,
});
await
ServicesBinding
.
instance
.
defaultBinaryMessenger
.
handlePlatformMessage
(
'flutter/textinput'
,
messageBytes
,
(
ByteData
?
_
)
{},
);
expect
(
client
.
latestMethodCall
,
'performSelector'
);
expect
(
client
.
performedSelectors
,
<
String
>[
'selector1'
,
'selector2'
]);
});
test
(
'TextInputClient performPrivateCommand method is called'
,
()
async
{
test
(
'TextInputClient performPrivateCommand method is called'
,
()
async
{
// Assemble a TextInputConnection so we can verify its change in state.
// Assemble a TextInputConnection so we can verify its change in state.
final
FakeTextInputClient
client
=
FakeTextInputClient
(
TextEditingValue
.
empty
);
final
FakeTextInputClient
client
=
FakeTextInputClient
(
TextEditingValue
.
empty
);
...
@@ -704,6 +733,7 @@ class FakeTextInputClient with TextInputClient {
...
@@ -704,6 +733,7 @@ class FakeTextInputClient with TextInputClient {
FakeTextInputClient
(
this
.
currentTextEditingValue
);
FakeTextInputClient
(
this
.
currentTextEditingValue
);
String
latestMethodCall
=
''
;
String
latestMethodCall
=
''
;
final
List
<
String
>
performedSelectors
=
<
String
>[];
@override
@override
TextEditingValue
currentTextEditingValue
;
TextEditingValue
currentTextEditingValue
;
...
@@ -757,4 +787,10 @@ class FakeTextInputClient with TextInputClient {
...
@@ -757,4 +787,10 @@ class FakeTextInputClient with TextInputClient {
void
removeTextPlaceholder
()
{
void
removeTextPlaceholder
()
{
latestMethodCall
=
'removeTextPlaceholder'
;
latestMethodCall
=
'removeTextPlaceholder'
;
}
}
@override
void
performSelector
(
String
selectorName
)
{
latestMethodCall
=
'performSelector'
;
performedSelectors
.
add
(
selectorName
);
}
}
}
packages/flutter/test/widgets/editable_text_test.dart
View file @
7e8f0e57
...
@@ -5870,17 +5870,39 @@ void main() {
...
@@ -5870,17 +5870,39 @@ void main() {
targetPlatform:
defaultTargetPlatform
,
targetPlatform:
defaultTargetPlatform
,
);
);
expect
(
switch
(
defaultTargetPlatform
)
{
selection
,
// Extend selection.
equals
(
case
TargetPlatform
.
android
:
const
TextSelection
(
case
TargetPlatform
.
fuchsia
:
baseOffset:
3
,
case
TargetPlatform
.
linux
:
extentOffset:
0
,
case
TargetPlatform
.
windows
:
affinity:
TextAffinity
.
upstream
,
expect
(
),
selection
,
),
equals
(
reason:
'on
$platform
'
,
const
TextSelection
(
);
baseOffset:
3
,
extentOffset:
0
,
affinity:
TextAffinity
.
upstream
,
),
),
reason:
'on
$platform
'
,
);
break
;
// On macOS/iOS expand selection.
case
TargetPlatform
.
iOS
:
case
TargetPlatform
.
macOS
:
expect
(
selection
,
equals
(
const
TextSelection
(
baseOffset:
72
,
extentOffset:
0
,
),
),
reason:
'on
$platform
'
,
);
break
;
}
// Move to start again.
// Move to start again.
await
sendKeys
(
await
sendKeys
(
...
@@ -12562,6 +12584,63 @@ void main() {
...
@@ -12562,6 +12584,63 @@ void main() {
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
,
TargetPlatform
.
macOS
}),
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
,
TargetPlatform
.
macOS
}),
);
);
});
});
testWidgets
(
'macOS selectors work'
,
(
WidgetTester
tester
)
async
{
controller
.
text
=
'test
\n
line2'
;
controller
.
selection
=
TextSelection
.
collapsed
(
offset:
controller
.
text
.
length
);
final
GlobalKey
<
EditableTextState
>
key
=
GlobalKey
<
EditableTextState
>();
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Align
(
alignment:
Alignment
.
topLeft
,
child:
SizedBox
(
width:
400
,
child:
EditableText
(
key:
key
,
maxLines:
10
,
controller:
controller
,
showSelectionHandles:
true
,
autofocus:
true
,
focusNode:
FocusNode
(),
style:
Typography
.
material2018
().
black
.
subtitle1
!,
cursorColor:
Colors
.
blue
,
backgroundCursorColor:
Colors
.
grey
,
selectionControls:
materialTextSelectionControls
,
keyboardType:
TextInputType
.
text
,
textAlign:
TextAlign
.
right
,
),
),
),
));
key
.
currentState
!.
performSelector
(
'moveLeft:'
);
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
9
),
);
key
.
currentState
!.
performSelector
(
'moveToBeginningOfParagraph:'
);
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
5
),
);
// These both need to be handled, first moves cursor to the end of previous
// paragraph, second moves to the beginning of paragraph.
key
.
currentState
!.
performSelector
(
'moveBackward:'
);
key
.
currentState
!.
performSelector
(
'moveToBeginningOfParagraph:'
);
await
tester
.
pump
();
expect
(
controller
.
selection
,
const
TextSelection
.
collapsed
(
offset:
0
),
);
});
});
});
group
(
'magnifier'
,
()
{
group
(
'magnifier'
,
()
{
...
...
packages/flutter_test/lib/src/event_simulation.dart
View file @
7e8f0e57
...
@@ -902,8 +902,13 @@ Future<bool> simulateKeyDownEvent(
...
@@ -902,8 +902,13 @@ Future<bool> simulateKeyDownEvent(
String
?
platform
,
String
?
platform
,
PhysicalKeyboardKey
?
physicalKey
,
PhysicalKeyboardKey
?
physicalKey
,
String
?
character
,
String
?
character
,
})
{
})
async
{
return
KeyEventSimulator
.
simulateKeyDownEvent
(
key
,
platform:
platform
,
physicalKey:
physicalKey
,
character:
character
);
final
bool
handled
=
await
KeyEventSimulator
.
simulateKeyDownEvent
(
key
,
platform:
platform
,
physicalKey:
physicalKey
,
character:
character
);
final
ServicesBinding
binding
=
ServicesBinding
.
instance
;
if
(!
handled
&&
binding
is
TestWidgetsFlutterBinding
)
{
await
binding
.
testTextInput
.
handleKeyDownEvent
(
key
);
}
return
handled
;
}
}
/// Simulates sending a hardware key up event through the system channel.
/// Simulates sending a hardware key up event through the system channel.
...
@@ -929,8 +934,13 @@ Future<bool> simulateKeyUpEvent(
...
@@ -929,8 +934,13 @@ Future<bool> simulateKeyUpEvent(
LogicalKeyboardKey
key
,
{
LogicalKeyboardKey
key
,
{
String
?
platform
,
String
?
platform
,
PhysicalKeyboardKey
?
physicalKey
,
PhysicalKeyboardKey
?
physicalKey
,
})
{
})
async
{
return
KeyEventSimulator
.
simulateKeyUpEvent
(
key
,
platform:
platform
,
physicalKey:
physicalKey
);
final
bool
handled
=
await
KeyEventSimulator
.
simulateKeyUpEvent
(
key
,
platform:
platform
,
physicalKey:
physicalKey
);
final
ServicesBinding
binding
=
ServicesBinding
.
instance
;
if
(!
handled
&&
binding
is
TestWidgetsFlutterBinding
)
{
await
binding
.
testTextInput
.
handleKeyUpEvent
(
key
);
}
return
handled
;
}
}
/// Simulates sending a hardware key repeat event through the system channel.
/// Simulates sending a hardware key repeat event through the system channel.
...
...
packages/flutter_test/lib/src/test_text_input.dart
View file @
7e8f0e57
...
@@ -4,11 +4,13 @@
...
@@ -4,11 +4,13 @@
import
'dart:async'
;
import
'dart:async'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/services.dart'
;
import
'binding.dart'
;
import
'binding.dart'
;
import
'deprecated.dart'
;
import
'deprecated.dart'
;
import
'test_async_utils.dart'
;
import
'test_async_utils.dart'
;
import
'test_text_input_key_handler.dart'
;
export
'package:flutter/services.dart'
show
TextEditingValue
,
TextInputAction
;
export
'package:flutter/services.dart'
show
TextEditingValue
,
TextInputAction
;
...
@@ -105,6 +107,9 @@ class TestTextInput {
...
@@ -105,6 +107,9 @@ class TestTextInput {
}
}
bool
_isVisible
=
false
;
bool
_isVisible
=
false
;
// Platform specific key handler that can process unhandled keyboard events.
TestTextInputKeyHandler
?
_keyHandler
;
/// Resets any internal state of this object.
/// Resets any internal state of this object.
///
///
/// This method is invoked by the testing framework between tests. It should
/// This method is invoked by the testing framework between tests. It should
...
@@ -131,6 +136,7 @@ class TestTextInput {
...
@@ -131,6 +136,7 @@ class TestTextInput {
case
'TextInput.clearClient'
:
case
'TextInput.clearClient'
:
_client
=
null
;
_client
=
null
;
_isVisible
=
false
;
_isVisible
=
false
;
_keyHandler
=
null
;
onCleared
?.
call
();
onCleared
?.
call
();
break
;
break
;
case
'TextInput.setEditingState'
:
case
'TextInput.setEditingState'
:
...
@@ -138,9 +144,13 @@ class TestTextInput {
...
@@ -138,9 +144,13 @@ class TestTextInput {
break
;
break
;
case
'TextInput.show'
:
case
'TextInput.show'
:
_isVisible
=
true
;
_isVisible
=
true
;
if
(!
kIsWeb
&&
defaultTargetPlatform
==
TargetPlatform
.
macOS
)
{
_keyHandler
??=
MacOSTestTextInputKeyHandler
(
_client
??
-
1
);
}
break
;
break
;
case
'TextInput.hide'
:
case
'TextInput.hide'
:
_isVisible
=
false
;
_isVisible
=
false
;
_keyHandler
=
null
;
break
;
break
;
}
}
}
}
...
@@ -350,4 +360,14 @@ class TestTextInput {
...
@@ -350,4 +360,14 @@ class TestTextInput {
(
ByteData
?
data
)
{
/* response from framework is discarded */
},
(
ByteData
?
data
)
{
/* response from framework is discarded */
},
);
);
}
}
/// Gives text input chance to respond to unhandled key down event.
Future
<
void
>
handleKeyDownEvent
(
LogicalKeyboardKey
key
)
async
{
await
_keyHandler
?.
handleKeyDownEvent
(
key
);
}
/// Gives text input chance to respond to unhandled key up event.
Future
<
void
>
handleKeyUpEvent
(
LogicalKeyboardKey
key
)
async
{
await
_keyHandler
?.
handleKeyUpEvent
(
key
);
}
}
}
packages/flutter_test/lib/src/test_text_input_key_handler.dart
0 → 100644
View file @
7e8f0e57
This diff is collapsed.
Click to expand it.
packages/flutter_test/test/test_text_input_test.dart
View file @
7e8f0e57
...
@@ -8,6 +8,7 @@
...
@@ -8,6 +8,7 @@
// Fails with "flutter test --test-randomize-ordering-seed=20210721"
// Fails with "flutter test --test-randomize-ordering-seed=20210721"
@Tags
(<
String
>[
'no-shuffle'
])
@Tags
(<
String
>[
'no-shuffle'
])
import
'package:flutter/foundation.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
...
@@ -54,4 +55,24 @@ void main() {
...
@@ -54,4 +55,24 @@ void main() {
throwsA
(
isA
<
PlatformException
>()),
throwsA
(
isA
<
PlatformException
>()),
);
);
});
});
testWidgets
(
'selectors are called on macOS'
,
(
WidgetTester
tester
)
async
{
List
<
dynamic
>?
selectorNames
;
await
SystemChannels
.
textInput
.
invokeMethod
(
'TextInput.setClient'
,
<
dynamic
>[
1
,
<
String
,
dynamic
>{}]);
await
SystemChannels
.
textInput
.
invokeMethod
(
'TextInput.show'
);
SystemChannels
.
textInput
.
setMethodCallHandler
((
MethodCall
call
)
async
{
if
(
call
.
method
==
'TextInputClient.performSelectors'
)
{
selectorNames
=
(
call
.
arguments
as
List
<
dynamic
>)[
1
]
as
List
<
dynamic
>;
}
});
await
tester
.
sendKeyDownEvent
(
LogicalKeyboardKey
.
altLeft
);
await
tester
.
sendKeyDownEvent
(
LogicalKeyboardKey
.
arrowUp
);
await
SystemChannels
.
textInput
.
invokeMethod
(
'TextInput.clearClient'
);
if
(
defaultTargetPlatform
==
TargetPlatform
.
macOS
)
{
expect
(
selectorNames
,
<
dynamic
>[
'moveBackward:'
,
'moveToBeginningOfParagraph:'
]);
}
else
{
expect
(
selectorNames
,
isNull
);
}
},
variant:
TargetPlatformVariant
.
all
());
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment