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
285b4751
Unverified
Commit
285b4751
authored
Apr 22, 2021
by
Ian Hickson
Committed by
GitHub
Apr 22, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor text editing test APIs (Mark III) (#80003)
parent
4f3ec01d
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
222 additions
and
133 deletions
+222
-133
text_editing_integration.dart
...s/web_e2e_tests/test_driver/text_editing_integration.dart
+0
-18
system_channels.dart
packages/flutter/lib/src/services/system_channels.dart
+16
-4
text_input.dart
packages/flutter/lib/src/services/text_input.dart
+19
-4
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+1
-1
binding.dart
packages/flutter_test/lib/src/binding.dart
+34
-7
test_text_input.dart
packages/flutter_test/lib/src/test_text_input.dart
+115
-84
widget_tester.dart
packages/flutter_test/lib/src/widget_tester.dart
+15
-12
bindings_test.dart
packages/flutter_test/test/bindings_test.dart
+22
-3
No files found.
dev/integration_tests/web_e2e_tests/test_driver/text_editing_integration.dart
View file @
285b4751
...
@@ -20,9 +20,6 @@ void main() {
...
@@ -20,9 +20,6 @@ void main() {
app
.
main
();
app
.
main
();
await
tester
.
pumpAndSettle
();
await
tester
.
pumpAndSettle
();
// TODO(nurhan): https://github.com/flutter/flutter/issues/51885
SystemChannels
.
textInput
.
setMockMethodCallHandler
(
null
);
// Focus on a TextFormField.
// Focus on a TextFormField.
final
Finder
finder
=
find
.
byKey
(
const
Key
(
'input'
));
final
Finder
finder
=
find
.
byKey
(
const
Key
(
'input'
));
expect
(
finder
,
findsOneWidget
);
expect
(
finder
,
findsOneWidget
);
...
@@ -48,9 +45,6 @@ void main() {
...
@@ -48,9 +45,6 @@ void main() {
app
.
main
();
app
.
main
();
await
tester
.
pumpAndSettle
();
await
tester
.
pumpAndSettle
();
// TODO(nurhan): https://github.com/flutter/flutter/issues/51885
SystemChannels
.
textInput
.
setMockMethodCallHandler
(
null
);
// Focus on a TextFormField.
// Focus on a TextFormField.
final
Finder
finder
=
find
.
byKey
(
const
Key
(
'empty-input'
));
final
Finder
finder
=
find
.
byKey
(
const
Key
(
'empty-input'
));
expect
(
finder
,
findsOneWidget
);
expect
(
finder
,
findsOneWidget
);
...
@@ -76,9 +70,6 @@ void main() {
...
@@ -76,9 +70,6 @@ void main() {
app
.
main
();
app
.
main
();
await
tester
.
pumpAndSettle
();
await
tester
.
pumpAndSettle
();
// TODO(nurhan): https://github.com/flutter/flutter/issues/51885
SystemChannels
.
textInput
.
setMockMethodCallHandler
(
null
);
// This text will show no-enter initially. It will have 'enter-pressed'
// This text will show no-enter initially. It will have 'enter-pressed'
// after `onFieldSubmitted` of TextField is triggered.
// after `onFieldSubmitted` of TextField is triggered.
final
Finder
textFinder
=
find
.
byKey
(
const
Key
(
'text'
));
final
Finder
textFinder
=
find
.
byKey
(
const
Key
(
'text'
));
...
@@ -112,9 +103,6 @@ void main() {
...
@@ -112,9 +103,6 @@ void main() {
app
.
main
();
app
.
main
();
await
tester
.
pumpAndSettle
();
await
tester
.
pumpAndSettle
();
// TODO(nurhan): https://github.com/flutter/flutter/issues/51885
SystemChannels
.
textInput
.
setMockMethodCallHandler
(
null
);
// Focus on a TextFormField.
// Focus on a TextFormField.
final
Finder
finder
=
find
.
byKey
(
const
Key
(
'input'
));
final
Finder
finder
=
find
.
byKey
(
const
Key
(
'input'
));
expect
(
finder
,
findsOneWidget
);
expect
(
finder
,
findsOneWidget
);
...
@@ -147,9 +135,6 @@ void main() {
...
@@ -147,9 +135,6 @@ void main() {
app
.
main
();
app
.
main
();
await
tester
.
pumpAndSettle
();
await
tester
.
pumpAndSettle
();
// TODO(nurhan): https://github.com/flutter/flutter/issues/51885
SystemChannels
.
textInput
.
setMockMethodCallHandler
(
null
);
// Focus on a TextFormField.
// Focus on a TextFormField.
final
Finder
finder
=
find
.
byKey
(
const
Key
(
'input'
));
final
Finder
finder
=
find
.
byKey
(
const
Key
(
'input'
));
expect
(
finder
,
findsOneWidget
);
expect
(
finder
,
findsOneWidget
);
...
@@ -197,9 +182,6 @@ void main() {
...
@@ -197,9 +182,6 @@ void main() {
app
.
main
();
app
.
main
();
await
tester
.
pumpAndSettle
();
await
tester
.
pumpAndSettle
();
// TODO(nurhan): https://github.com/flutter/flutter/issues/51885
SystemChannels
.
textInput
.
setMockMethodCallHandler
(
null
);
// Select something from the selectable text.
// Select something from the selectable text.
final
Finder
finder
=
find
.
byKey
(
const
Key
(
'selectable'
));
final
Finder
finder
=
find
.
byKey
(
const
Key
(
'selectable'
));
expect
(
finder
,
findsOneWidget
);
expect
(
finder
,
findsOneWidget
);
...
...
packages/flutter/lib/src/services/system_channels.dart
View file @
285b4751
...
@@ -120,6 +120,11 @@ class SystemChannels {
...
@@ -120,6 +120,11 @@ class SystemChannels {
/// they apply, so that stale messages referencing past transactions can be
/// they apply, so that stale messages referencing past transactions can be
/// ignored.
/// ignored.
///
///
/// In debug builds, messages sent with a client ID of -1 are always accepted.
/// This allows tests to smuggle messages without having to mock the engine's
/// text handling (for example, allowing the engine to still handle the text
/// input messages in an integration test).
///
/// The methods described below are wrapped in a more convenient form by the
/// The methods described below are wrapped in a more convenient form by the
/// [TextInput] and [TextInputConnection] class.
/// [TextInput] and [TextInputConnection] class.
///
///
...
@@ -152,9 +157,15 @@ class SystemChannels {
...
@@ -152,9 +157,15 @@ class SystemChannels {
/// is a transaction identifier. Calls for stale transactions should be ignored.
/// is a transaction identifier. Calls for stale transactions should be ignored.
///
///
/// * `TextInputClient.updateEditingState`: The user has changed the contents
/// * `TextInputClient.updateEditingState`: The user has changed the contents
/// of the text control. The second argument is a [String] containing a
/// of the text control. The second argument is an object with seven keys,
/// JSON-encoded object with seven keys, in the form expected by
/// in the form expected by [TextEditingValue.fromJSON].
/// [TextEditingValue.fromJSON].
///
/// * `TextInputClient.updateEditingStateWithTag`: One or more text controls
/// were autofilled by the platform's autofill service. The first argument
/// (the client ID) is ignored, the second argument is a map of tags to
/// objects in the form expected by [TextEditingValue.fromJSON]. See
/// [AutofillScope.getAutofillClient] for details on the interpretation of
/// the tag.
///
///
/// * `TextInputClient.performAction`: The user has triggered an action. The
/// * `TextInputClient.performAction`: The user has triggered an action. The
/// second argument is a [String] consisting of the stringification of one
/// second argument is a [String] consisting of the stringification of one
...
@@ -165,7 +176,8 @@ class SystemChannels {
...
@@ -165,7 +176,8 @@ class SystemChannels {
/// one. The framework should call `TextInput.setClient` and
/// one. The framework should call `TextInput.setClient` and
/// `TextInput.setEditingState` again with its most recent information. If
/// `TextInput.setEditingState` again with its most recent information. If
/// there is no existing state on the framework side, the call should
/// there is no existing state on the framework side, the call should
/// fizzle.
/// fizzle. (This call is made without a client ID; indeed, without any
/// arguments at all.)
///
///
/// * `TextInputClient.onConnectionClosed`: The text input connection closed
/// * `TextInputClient.onConnectionClosed`: The text input connection closed
/// on the platform side. For example the application is moved to
/// on the platform side. For example the application is moved to
...
...
packages/flutter/lib/src/services/text_input.dart
View file @
285b4751
...
@@ -1327,9 +1327,11 @@ class TextInput {
...
@@ -1327,9 +1327,11 @@ class TextInput {
final
List
<
dynamic
>
args
=
methodCall
.
arguments
as
List
<
dynamic
>;
final
List
<
dynamic
>
args
=
methodCall
.
arguments
as
List
<
dynamic
>;
// The updateEditingStateWithTag request (autofill) can come up even to a
// text field that doesn't have a connection.
if
(
method
==
'TextInputClient.updateEditingStateWithTag'
)
{
if
(
method
==
'TextInputClient.updateEditingStateWithTag'
)
{
assert
(
_currentConnection
!.
_client
!=
null
);
final
TextInputClient
client
=
_currentConnection
!.
_client
;
final
TextInputClient
client
=
_currentConnection
!.
_client
;
assert
(
client
!=
null
);
final
AutofillScope
?
scope
=
client
.
currentAutofillScope
;
final
AutofillScope
?
scope
=
client
.
currentAutofillScope
;
final
Map
<
String
,
dynamic
>
editingValue
=
args
[
1
]
as
Map
<
String
,
dynamic
>;
final
Map
<
String
,
dynamic
>
editingValue
=
args
[
1
]
as
Map
<
String
,
dynamic
>;
for
(
final
String
tag
in
editingValue
.
keys
)
{
for
(
final
String
tag
in
editingValue
.
keys
)
{
...
@@ -1343,9 +1345,22 @@ class TextInput {
...
@@ -1343,9 +1345,22 @@ class TextInput {
}
}
final
int
client
=
args
[
0
]
as
int
;
final
int
client
=
args
[
0
]
as
int
;
// The incoming message was for a different client.
if
(
client
!=
_currentConnection
!.
_id
)
{
if
(
client
!=
_currentConnection
!.
_id
)
// If the client IDs don't match, the incoming message was for a different
return
;
// client.
bool
debugAllowAnyway
=
false
;
assert
(()
{
// In debug builds we allow "-1" as a magical client ID that ignores
// this verification step so that tests can always get through, even
// when they are not mocking the engine side of text input.
if
(
client
==
-
1
)
debugAllowAnyway
=
true
;
return
true
;
}());
if
(!
debugAllowAnyway
)
return
;
}
switch
(
method
)
{
switch
(
method
)
{
case
'TextInputClient.updateEditingState'
:
case
'TextInputClient.updateEditingState'
:
_currentConnection
!.
_client
.
updateEditingValue
(
TextEditingValue
.
fromJSON
(
args
[
1
]
as
Map
<
String
,
dynamic
>));
_currentConnection
!.
_client
.
updateEditingValue
(
TextEditingValue
.
fromJSON
(
args
[
1
]
as
Map
<
String
,
dynamic
>));
...
...
packages/flutter/lib/src/widgets/editable_text.dart
View file @
285b4751
...
@@ -2112,7 +2112,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
...
@@ -2112,7 +2112,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
if
(
_hasFocus
)
{
if
(
_hasFocus
)
{
_openInputConnection
();
_openInputConnection
();
}
else
{
}
else
{
widget
.
focusNode
.
requestFocus
();
widget
.
focusNode
.
requestFocus
();
// This eventually calls _openInputConnection also, see _handleFocusChanged.
}
}
}
}
...
...
packages/flutter_test/lib/src/binding.dart
View file @
285b4751
...
@@ -195,9 +195,15 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
...
@@ -195,9 +195,15 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
/// Called by the test framework at the beginning of a widget test to
/// Called by the test framework at the beginning of a widget test to
/// prepare the binding for the next test.
/// prepare the binding for the next test.
///
/// If [registerTestTextInput] returns true when this method is called,
/// the [testTextInput] is configured to simulate the keyboard.
void
reset
()
{
void
reset
()
{
_restorationManager
=
null
;
_restorationManager
=
null
;
resetGestureBinding
();
resetGestureBinding
();
testTextInput
.
reset
();
if
(
registerTestTextInput
)
_testTextInput
.
register
();
}
}
@override
@override
...
@@ -237,7 +243,8 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
...
@@ -237,7 +243,8 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
@protected
@protected
bool
get
overrideHttpClient
=>
true
;
bool
get
overrideHttpClient
=>
true
;
/// Determines whether the binding automatically registers [testTextInput].
/// Determines whether the binding automatically registers [testTextInput] as
/// a fake keyboard implementation.
///
///
/// Unit tests make use of this to mock out text input communication for
/// Unit tests make use of this to mock out text input communication for
/// widgets. An integration test would set this to false, to test real IME
/// widgets. An integration test would set this to false, to test real IME
...
@@ -245,6 +252,19 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
...
@@ -245,6 +252,19 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
///
///
/// [TestTextInput.isRegistered] reports whether the text input mock is
/// [TestTextInput.isRegistered] reports whether the text input mock is
/// registered or not.
/// registered or not.
///
/// Some of the properties and methods on [testTextInput] are only valid if
/// [registerTestTextInput] returns true when a test starts. If those
/// members are accessed when using a binding that sets this flag to false,
/// they will throw.
///
/// If this property returns true when a test ends, the [testTextInput] is
/// unregistered.
///
/// This property should not change the value it returns during the lifetime
/// of the binding. Changing the value of this property risks very confusing
/// behavior as the [TestTextInput] may be inconsistently registered or
/// unregistered.
@protected
@protected
bool
get
registerTestTextInput
=>
true
;
bool
get
registerTestTextInput
=>
true
;
...
@@ -319,9 +339,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
...
@@ -319,9 +339,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
binding
.
setupHttpOverrides
();
binding
.
setupHttpOverrides
();
}
}
_testTextInput
=
TestTextInput
(
onCleared:
_resetFocusedEditable
);
_testTextInput
=
TestTextInput
(
onCleared:
_resetFocusedEditable
);
if
(
registerTestTextInput
)
{
_testTextInput
.
register
();
}
}
}
@override
@override
...
@@ -515,12 +532,20 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
...
@@ -515,12 +532,20 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
TestTextInput
get
testTextInput
=>
_testTextInput
;
TestTextInput
get
testTextInput
=>
_testTextInput
;
late
TestTextInput
_testTextInput
;
late
TestTextInput
_testTextInput
;
/// The current client of the onscreen keyboard. Callers must pump
/// The [State] of the current [EditableText] client of the onscreen keyboard.
/// an additional frame after setting this property to complete the
///
/// focus change.
/// Setting this property to a new value causes the given [EditableTextState]
/// to focus itself and request the keyboard to establish a
/// [TextInputConnection].
///
/// Callers must pump an additional frame after setting this property to
/// complete the focus change.
///
///
/// Instead of setting this directly, consider using
/// Instead of setting this directly, consider using
/// [WidgetTester.showKeyboard].
/// [WidgetTester.showKeyboard].
//
// TODO(ianh): We should just remove this property and move the call to
// requestKeyboard to the WidgetTester.showKeyboard method.
EditableTextState
?
get
focusedEditable
=>
_focusedEditable
;
EditableTextState
?
get
focusedEditable
=>
_focusedEditable
;
EditableTextState
?
_focusedEditable
;
EditableTextState
?
_focusedEditable
;
set
focusedEditable
(
EditableTextState
?
value
)
{
set
focusedEditable
(
EditableTextState
?
value
)
{
...
@@ -799,6 +824,8 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
...
@@ -799,6 +824,8 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
// alone so that we don't cause more spurious errors.
// alone so that we don't cause more spurious errors.
runApp
(
Container
(
key:
UniqueKey
(),
child:
_postTestMessage
));
// Unmount any remaining widgets.
runApp
(
Container
(
key:
UniqueKey
(),
child:
_postTestMessage
));
// Unmount any remaining widgets.
await
pump
();
await
pump
();
if
(
registerTestTextInput
)
_testTextInput
.
unregister
();
invariantTester
();
invariantTester
();
_verifyAutoUpdateGoldensUnset
(
autoUpdateGoldensBeforeTest
&&
!
isBrowser
);
_verifyAutoUpdateGoldensUnset
(
autoUpdateGoldensBeforeTest
&&
!
isBrowser
);
_verifyReportTestExceptionUnset
(
reportTestExceptionBeforeTest
);
_verifyReportTestExceptionUnset
(
reportTestExceptionBeforeTest
);
...
...
packages/flutter_test/lib/src/test_text_input.dart
View file @
285b4751
...
@@ -14,6 +14,18 @@ export 'package:flutter/services.dart' show TextEditingValue, TextInputAction;
...
@@ -14,6 +14,18 @@ export 'package:flutter/services.dart' show TextEditingValue, TextInputAction;
///
///
/// Typical app tests will not need to use this class directly.
/// Typical app tests will not need to use this class directly.
///
///
/// The [TestWidgetsFlutterBinding] class registers a [TestTextInput] instance
/// ([TestWidgetsFlutterBinding.testTextInput]) as a stub keyboard
/// implementation if its [TestWidgetsFlutterBinding.registerTestTextInput]
/// property returns true when a test starts, and unregisters it when the test
/// ends (unless it ends with a failure).
///
/// See [register], [unregister], and [isRegistered] for details.
///
/// The [enterText], [updateEditingValue], [receiveAction], and
/// [closeConnection] methods can be used even when the [TestTextInput] is not
/// registered. All other methods will assert if [isRegistered] is false.
///
/// See also:
/// See also:
///
///
/// * [WidgetTester.enterText], which uses this class to simulate keyboard input.
/// * [WidgetTester.enterText], which uses this class to simulate keyboard input.
...
@@ -36,58 +48,76 @@ class TestTextInput {
...
@@ -36,58 +48,76 @@ class TestTextInput {
/// The messenger which sends the bytes for this channel, not null.
/// The messenger which sends the bytes for this channel, not null.
BinaryMessenger
get
_binaryMessenger
=>
ServicesBinding
.
instance
!.
defaultBinaryMessenger
;
BinaryMessenger
get
_binaryMessenger
=>
ServicesBinding
.
instance
!.
defaultBinaryMessenger
;
///
Resets any internal state of this object and calls [register]
.
///
Log for method calls
.
///
///
/// This method is invoked by the testing framework between tests. It should
/// For all registered channels, handled calls are added to the list. Can
/// not ordinarily be called by tests directly.
/// be cleaned using `log.clear()`.
void
resetAndRegister
()
{
final
List
<
MethodCall
>
log
=
<
MethodCall
>[];
log
.
clear
();
editingState
=
null
;
setClientArgs
=
null
;
_client
=
0
;
_isVisible
=
false
;
register
();
}
/// Installs this object as a mock handler for [SystemChannels.textInput].
/// Installs this object as a mock handler for [SystemChannels.textInput].
///
/// Called by the binding at the top of a test when
/// [TestWidgetsFlutterBinding.registerTestTextInput] is true.
void
register
()
=>
SystemChannels
.
textInput
.
setMockMethodCallHandler
(
_handleTextInputCall
);
void
register
()
=>
SystemChannels
.
textInput
.
setMockMethodCallHandler
(
_handleTextInputCall
);
/// Removes this object as a mock handler for [SystemChannels.textInput].
/// Removes this object as a mock handler for [SystemChannels.textInput].
///
///
/// After calling this method, the channel will exchange messages with the
/// After calling this method, the channel will exchange messages with the
/// Flutter engine. Use this with [FlutterDriver] tests that need to display
/// Flutter engine instead of the stub.
/// on-screen keyboard provided by the operating system.
void
unregister
()
=>
SystemChannels
.
textInput
.
setMockMethodCallHandler
(
null
);
/// Log for method calls.
///
///
///
For all registered channels, handled calls are added to the list. Ca
n
///
Called by the binding at the end of a (successful) test whe
n
///
be cleaned using `log.clear()`
.
///
[TestWidgetsFlutterBinding.registerTestTextInput] is true
.
final
List
<
MethodCall
>
log
=
<
MethodCall
>[]
;
void
unregister
()
=>
SystemChannels
.
textInput
.
setMockMethodCallHandler
(
null
)
;
/// Whether this [TestTextInput] is registered with [SystemChannels.textInput].
/// Whether this [TestTextInput] is registered with [SystemChannels.textInput].
///
///
/// Use [register] and [unregister] methods to control this value.
/// The binding uses the [register] and [unregister] methods to control this
/// value when [TestWidgetsFlutterBinding.registerTestTextInput] is true.
bool
get
isRegistered
=>
SystemChannels
.
textInput
.
checkMockMethodCallHandler
(
_handleTextInputCall
);
bool
get
isRegistered
=>
SystemChannels
.
textInput
.
checkMockMethodCallHandler
(
_handleTextInputCall
);
int
?
_client
;
/// Whether there are any active clients listening to text input.
/// Whether there are any active clients listening to text input.
bool
get
hasAnyClients
{
bool
get
hasAnyClients
{
assert
(
isRegistered
);
assert
(
isRegistered
);
return
_client
>
0
;
return
_client
!=
null
&&
_client
!
>
0
;
}
}
int
_client
=
0
;
/// The last set of arguments supplied to the `TextInput.setClient` and
/// `TextInput.updateConfig` methods of this stub implementation.
/// Arguments supplied to the TextInput.setClient method call.
Map
<
String
,
dynamic
>?
setClientArgs
;
Map
<
String
,
dynamic
>?
setClientArgs
;
/// The last set of arguments that [TextInputConnection.setEditingState] sent
/// The last set of arguments that [TextInputConnection.setEditingState] sent
/// to the embedder.
/// to this stub implementation (i.e. the arguments set to
/// `TextInput.setEditingState`).
///
///
/// This is a map representation of a [TextEditingValue] object. For example,
/// This is a map representation of a [TextEditingValue] object. For example,
/// it will have a `text` entry whose value matches the most recent
/// it will have a `text` entry whose value matches the most recent
/// [TextEditingValue.text] that was sent to the embedder.
/// [TextEditingValue.text] that was sent to the embedder.
Map
<
String
,
dynamic
>?
editingState
;
Map
<
String
,
dynamic
>?
editingState
;
/// Whether the onscreen keyboard is visible to the user.
///
/// Specifically, this reflects the last call to `TextInput.show` or
/// `TextInput.hide` received by the stub implementation.
bool
get
isVisible
{
assert
(
isRegistered
);
return
_isVisible
;
}
bool
_isVisible
=
false
;
/// Resets any internal state of this object.
///
/// This method is invoked by the testing framework between tests. It should
/// not ordinarily be called by tests directly.
void
reset
()
{
log
.
clear
();
_client
=
null
;
setClientArgs
=
null
;
editingState
=
null
;
_isVisible
=
false
;
}
Future
<
dynamic
>
_handleTextInputCall
(
MethodCall
methodCall
)
async
{
Future
<
dynamic
>
_handleTextInputCall
(
MethodCall
methodCall
)
async
{
log
.
add
(
methodCall
);
log
.
add
(
methodCall
);
switch
(
methodCall
.
method
)
{
switch
(
methodCall
.
method
)
{
...
@@ -99,7 +129,7 @@ class TestTextInput {
...
@@ -99,7 +129,7 @@ class TestTextInput {
setClientArgs
=
methodCall
.
arguments
as
Map
<
String
,
dynamic
>;
setClientArgs
=
methodCall
.
arguments
as
Map
<
String
,
dynamic
>;
break
;
break
;
case
'TextInput.clearClient'
:
case
'TextInput.clearClient'
:
_client
=
0
;
_client
=
null
;
_isVisible
=
false
;
_isVisible
=
false
;
onCleared
?.
call
();
onCleared
?.
call
();
break
;
break
;
...
@@ -115,87 +145,69 @@ class TestTextInput {
...
@@ -115,87 +145,69 @@ class TestTextInput {
}
}
}
}
/// Whether the onscreen keyboard is visible to the user.
/// Simulates the user hiding the onscreen keyboard.
bool
get
isVisible
{
///
/// This does nothing but set the internal flag.
void
hide
()
{
assert
(
isRegistered
);
assert
(
isRegistered
);
return
_isVisibl
e
;
_isVisible
=
fals
e
;
}
}
bool
_isVisible
=
false
;
/// Simulates the user changing the [TextEditingValue] to the given value.
/// Simulates the user typing the given text.
void
updateEditingValue
(
TextEditingValue
value
)
{
///
assert
(
isRegistered
);
/// Calling this method replaces the content of the connected input field with
// Not using the `expect` function because in the case of a FlutterDriver
/// `text`, and places the caret at the end of the text.
// test this code does not run in a package:test test zone.
///
if
(
_client
==
0
)
/// This can be called even if the [TestTextInput] has not been [register]ed.
throw
TestFailure
(
'Tried to use TestTextInput with no keyboard attached. You must use WidgetTester.showKeyboard() first.'
);
///
_binaryMessenger
.
handlePlatformMessage
(
/// If this is used to inject text when there is a real IME connection, for
SystemChannels
.
textInput
.
name
,
/// example when using the [integration_test] library, there is a risk that
SystemChannels
.
textInput
.
codec
.
encodeMethodCall
(
/// the real IME will become confused as to the current state of input.
MethodCall
(
void
enterText
(
String
text
)
{
'TextInputClient.updateEditingState'
,
updateEditingValue
(
TextEditingValue
(
<
dynamic
>[
_client
,
value
.
toJSON
()],
text:
text
,
),
selection:
TextSelection
.
collapsed
(
offset:
text
.
length
),
),
));
(
ByteData
?
data
)
{
/* response from framework is discarded */
},
);
}
}
/// Simulates the user c
losing the text input connection
.
/// Simulates the user c
hanging the [TextEditingValue] to the given value
.
///
///
/// For example:
/// This can be called even if the [TestTextInput] has not been [register]ed.
/// - User pressed the home button and sent the application to background.
///
/// - User closed the virtual keyboard.
/// If this is used to inject text when there is a real IME connection, for
void
closeConnection
()
{
/// example when using the [integration_test] library, there is a risk that
assert
(
isRegistered
);
/// the real IME will become confused as to the current state of input.
// Not using the `expect` function because in the case of a FlutterDriver
void
updateEditingValue
(
TextEditingValue
value
)
{
// test this code does not run in a package:test test zone.
if
(
_client
==
0
)
throw
TestFailure
(
'Tried to use TestTextInput with no keyboard attached. You must use WidgetTester.showKeyboard() first.'
);
_binaryMessenger
.
handlePlatformMessage
(
_binaryMessenger
.
handlePlatformMessage
(
SystemChannels
.
textInput
.
name
,
SystemChannels
.
textInput
.
name
,
SystemChannels
.
textInput
.
codec
.
encodeMethodCall
(
SystemChannels
.
textInput
.
codec
.
encodeMethodCall
(
MethodCall
(
MethodCall
(
'TextInputClient.
onConnectionClosed
'
,
'TextInputClient.
updateEditingState
'
,
<
dynamic
>[
_client
,]
<
dynamic
>[
_client
??
-
1
,
value
.
toJSON
()],
),
),
),
),
(
ByteData
?
data
)
{
/* response from framework is discarded */
},
(
ByteData
?
data
)
{
/* response from framework is discarded */
},
);
);
}
}
/// Simulates the user typing the given text.
///
/// Calling this method replaces the content of the connected input field with
/// `text`, and places the caret at the end of the text.
void
enterText
(
String
text
)
{
assert
(
isRegistered
);
updateEditingValue
(
TextEditingValue
(
text:
text
,
selection:
TextSelection
.
collapsed
(
offset:
text
.
length
),
));
}
/// Simulates the user pressing one of the [TextInputAction] buttons.
/// Simulates the user pressing one of the [TextInputAction] buttons.
/// Does not check that the [TextInputAction] performed is an acceptable one
/// Does not check that the [TextInputAction] performed is an acceptable one
/// based on the `inputAction` [setClientArgs].
/// based on the `inputAction` [setClientArgs].
///
/// This can be called even if the [TestTextInput] has not been [register]ed.
///
/// If this is used to inject an action when there is a real IME connection,
/// for example when using the [integration_test] library, there is a risk
/// that the real IME will become confused as to the current state of input.
Future
<
void
>
receiveAction
(
TextInputAction
action
)
async
{
Future
<
void
>
receiveAction
(
TextInputAction
action
)
async
{
assert
(
isRegistered
);
return
TestAsyncUtils
.
guard
(()
{
return
TestAsyncUtils
.
guard
(()
{
// Not using the `expect` function because in the case of a FlutterDriver
// test this code does not run in a package:test test zone.
if
(
_client
==
0
)
{
throw
TestFailure
(
'Tried to use TestTextInput with no keyboard attached. You must use WidgetTester.showKeyboard() first.'
);
}
final
Completer
<
void
>
completer
=
Completer
<
void
>();
final
Completer
<
void
>
completer
=
Completer
<
void
>();
_binaryMessenger
.
handlePlatformMessage
(
_binaryMessenger
.
handlePlatformMessage
(
SystemChannels
.
textInput
.
name
,
SystemChannels
.
textInput
.
name
,
SystemChannels
.
textInput
.
codec
.
encodeMethodCall
(
SystemChannels
.
textInput
.
codec
.
encodeMethodCall
(
MethodCall
(
MethodCall
(
'TextInputClient.performAction'
,
'TextInputClient.performAction'
,
<
dynamic
>[
_client
,
action
.
toString
()],
<
dynamic
>[
_client
??
-
1
,
action
.
toString
()],
),
),
),
),
(
ByteData
?
data
)
{
(
ByteData
?
data
)
{
...
@@ -219,9 +231,28 @@ class TestTextInput {
...
@@ -219,9 +231,28 @@ class TestTextInput {
});
});
}
}
/// Simulates the user hiding the onscreen keyboard.
/// Simulates the user closing the text input connection.
void
hide
()
{
///
assert
(
isRegistered
);
/// For example:
_isVisible
=
false
;
///
/// * User pressed the home button and sent the application to background.
/// * User closed the virtual keyboard.
///
/// This can be called even if the [TestTextInput] has not been [register]ed.
///
/// If this is used to inject text when there is a real IME connection, for
/// example when using the [integration_test] library, there is a risk that
/// the real IME will become confused as to the current state of input.
void
closeConnection
()
{
_binaryMessenger
.
handlePlatformMessage
(
SystemChannels
.
textInput
.
name
,
SystemChannels
.
textInput
.
codec
.
encodeMethodCall
(
MethodCall
(
'TextInputClient.onConnectionClosed'
,
<
dynamic
>[
_client
??
-
1
],
),
),
(
ByteData
?
data
)
{
/* response from framework is discarded */
},
);
}
}
}
}
packages/flutter_test/lib/src/widget_tester.dart
View file @
285b4751
...
@@ -149,7 +149,6 @@ void testWidgets(
...
@@ -149,7 +149,6 @@ void testWidgets(
()
async
{
()
async
{
binding
.
reset
();
binding
.
reset
();
debugResetSemanticsIdCounter
();
debugResetSemanticsIdCounter
();
tester
.
resetTestTextInput
();
Object
?
memento
;
Object
?
memento
;
try
{
try
{
memento
=
await
variant
.
setUp
(
value
);
memento
=
await
variant
.
setUp
(
value
);
...
@@ -1002,18 +1001,13 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
...
@@ -1002,18 +1001,13 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
///
///
/// Typical app tests will not need to use this value. To add text to widgets
/// Typical app tests will not need to use this value. To add text to widgets
/// like [TextField] or [TextFormField], call [enterText].
/// like [TextField] or [TextFormField], call [enterText].
TestTextInput
get
testTextInput
=>
binding
.
testTextInput
;
/// Ensures that [testTextInput] is registered and [TestTextInput.log] is
/// reset.
///
///
/// This is called by the testing framework before test runs, so that if a
/// Some of the properties and methods on this value are only valid if the
/// previous test has set its own handler on [SystemChannels.textInput], the
/// binding's [TestWidgetsFlutterBinding.registerTestTextInput] flag is set to
/// [testTextInput] regains control and the log is fresh for the new test.
/// true as a test is starting (meaning that the keyboard is to be simulated
/// It should not typically need to be called by tests.
/// by the test framework). If those members are accessed when using a binding
void
resetTestTextInput
()
{
/// that sets this flag to false, they will throw.
testTextInput
.
resetAndRegister
();
TestTextInput
get
testTextInput
=>
binding
.
testTextInput
;
}
/// Give the text input widget specified by [finder] the focus, as if the
/// Give the text input widget specified by [finder] the focus, as if the
/// onscreen keyboard had appeared.
/// onscreen keyboard had appeared.
...
@@ -1035,6 +1029,9 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
...
@@ -1035,6 +1029,9 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
matchRoot:
true
,
matchRoot:
true
,
),
),
);
);
// Setting focusedEditable causes the binding to call requestKeyboard()
// on the EditableTextState, which itself eventually calls TextInput.attach
// to establish the connection.
binding
.
focusedEditable
=
editable
;
binding
.
focusedEditable
=
editable
;
await
pump
();
await
pump
();
});
});
...
@@ -1052,6 +1049,12 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
...
@@ -1052,6 +1049,12 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
///
///
/// To just give [finder] the focus without entering any text,
/// To just give [finder] the focus without entering any text,
/// see [showKeyboard].
/// see [showKeyboard].
///
/// To enter text into other widgets (e.g. a custom widget that maintains a
/// TextInputConnection the way that a [EditableText] does), first ensure that
/// that widget has an open connection (e.g. by using [tap] to to focus it),
/// then call `testTextInput.enterText` directly (see
/// [TestTextInput.enterText]).
Future
<
void
>
enterText
(
Finder
finder
,
String
text
)
async
{
Future
<
void
>
enterText
(
Finder
finder
,
String
text
)
async
{
return
TestAsyncUtils
.
guard
<
void
>(()
async
{
return
TestAsyncUtils
.
guard
<
void
>(()
async
{
await
showKeyboard
(
finder
);
await
showKeyboard
(
finder
);
...
...
packages/flutter_test/test/bindings_test.dart
View file @
285b4751
...
@@ -11,6 +11,8 @@ import 'package:flutter_test/flutter_test.dart';
...
@@ -11,6 +11,8 @@ import 'package:flutter_test/flutter_test.dart';
import
'package:test_api/test_api.dart'
as
test_package
;
import
'package:test_api/test_api.dart'
as
test_package
;
void
main
(
)
{
void
main
(
)
{
final
AutomatedTestWidgetsFlutterBinding
binding
=
AutomatedTestWidgetsFlutterBinding
();
group
(
TestViewConfiguration
,
()
{
group
(
TestViewConfiguration
,
()
{
test
(
'is initialized with top-level window if one is not provided'
,
()
{
test
(
'is initialized with top-level window if one is not provided'
,
()
{
// The code below will throw without the default.
// The code below will throw without the default.
...
@@ -20,15 +22,32 @@ void main() {
...
@@ -20,15 +22,32 @@ void main() {
group
(
AutomatedTestWidgetsFlutterBinding
,
()
{
group
(
AutomatedTestWidgetsFlutterBinding
,
()
{
test
(
'allows setting defaultTestTimeout to 5 minutes'
,
()
{
test
(
'allows setting defaultTestTimeout to 5 minutes'
,
()
{
final
AutomatedTestWidgetsFlutterBinding
binding
=
AutomatedTestWidgetsFlutterBinding
();
binding
.
defaultTestTimeout
=
const
test_package
.
Timeout
(
Duration
(
minutes:
5
));
binding
.
defaultTestTimeout
=
const
test_package
.
Timeout
(
Duration
(
minutes:
5
));
expect
(
binding
.
defaultTestTimeout
.
duration
,
const
Duration
(
minutes:
5
));
expect
(
binding
.
defaultTestTimeout
.
duration
,
const
Duration
(
minutes:
5
));
});
});
});
});
// The next three tests must run in order -- first using `test`, then `testWidgets`, then `test` again.
int
order
=
0
;
test
(
'Initializes httpOverrides and testTextInput'
,
()
async
{
test
(
'Initializes httpOverrides and testTextInput'
,
()
async
{
final
TestWidgetsFlutterBinding
binding
=
TestWidgetsFlutterBinding
.
ensureInitialized
()
as
TestWidgetsFlutterBinding
;
assert
(
order
==
0
);
expect
(
binding
.
testTextInput
.
isRegistered
,
true
);
expect
(
binding
.
testTextInput
,
isNotNull
);
expect
(
binding
.
testTextInput
.
isRegistered
,
isFalse
);
expect
(
HttpOverrides
.
current
,
isNotNull
);
expect
(
HttpOverrides
.
current
,
isNotNull
);
order
+=
1
;
});
testWidgets
(
'Registers testTextInput'
,
(
WidgetTester
tester
)
async
{
assert
(
order
==
1
);
expect
(
tester
.
testTextInput
.
isRegistered
,
isTrue
);
order
+=
1
;
});
test
(
'Unregisters testTextInput'
,
()
async
{
assert
(
order
==
2
);
expect
(
binding
.
testTextInput
.
isRegistered
,
isFalse
);
order
+=
1
;
});
});
}
}
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