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
Expand all
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() {
app
.
main
();
await
tester
.
pumpAndSettle
();
// TODO(nurhan): https://github.com/flutter/flutter/issues/51885
SystemChannels
.
textInput
.
setMockMethodCallHandler
(
null
);
// Focus on a TextFormField.
final
Finder
finder
=
find
.
byKey
(
const
Key
(
'input'
));
expect
(
finder
,
findsOneWidget
);
...
...
@@ -48,9 +45,6 @@ void main() {
app
.
main
();
await
tester
.
pumpAndSettle
();
// TODO(nurhan): https://github.com/flutter/flutter/issues/51885
SystemChannels
.
textInput
.
setMockMethodCallHandler
(
null
);
// Focus on a TextFormField.
final
Finder
finder
=
find
.
byKey
(
const
Key
(
'empty-input'
));
expect
(
finder
,
findsOneWidget
);
...
...
@@ -76,9 +70,6 @@ void main() {
app
.
main
();
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'
// after `onFieldSubmitted` of TextField is triggered.
final
Finder
textFinder
=
find
.
byKey
(
const
Key
(
'text'
));
...
...
@@ -112,9 +103,6 @@ void main() {
app
.
main
();
await
tester
.
pumpAndSettle
();
// TODO(nurhan): https://github.com/flutter/flutter/issues/51885
SystemChannels
.
textInput
.
setMockMethodCallHandler
(
null
);
// Focus on a TextFormField.
final
Finder
finder
=
find
.
byKey
(
const
Key
(
'input'
));
expect
(
finder
,
findsOneWidget
);
...
...
@@ -147,9 +135,6 @@ void main() {
app
.
main
();
await
tester
.
pumpAndSettle
();
// TODO(nurhan): https://github.com/flutter/flutter/issues/51885
SystemChannels
.
textInput
.
setMockMethodCallHandler
(
null
);
// Focus on a TextFormField.
final
Finder
finder
=
find
.
byKey
(
const
Key
(
'input'
));
expect
(
finder
,
findsOneWidget
);
...
...
@@ -197,9 +182,6 @@ void main() {
app
.
main
();
await
tester
.
pumpAndSettle
();
// TODO(nurhan): https://github.com/flutter/flutter/issues/51885
SystemChannels
.
textInput
.
setMockMethodCallHandler
(
null
);
// Select something from the selectable text.
final
Finder
finder
=
find
.
byKey
(
const
Key
(
'selectable'
));
expect
(
finder
,
findsOneWidget
);
...
...
packages/flutter/lib/src/services/system_channels.dart
View file @
285b4751
...
...
@@ -120,6 +120,11 @@ class SystemChannels {
/// they apply, so that stale messages referencing past transactions can be
/// 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
/// [TextInput] and [TextInputConnection] class.
///
...
...
@@ -152,9 +157,15 @@ class SystemChannels {
/// is a transaction identifier. Calls for stale transactions should be ignored.
///
/// * `TextInputClient.updateEditingState`: The user has changed the contents
/// of the text control. The second argument is a [String] containing a
/// JSON-encoded object with seven keys, in the form expected by
/// [TextEditingValue.fromJSON].
/// of the text control. The second argument is an object with seven keys,
/// in the form expected by [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
/// second argument is a [String] consisting of the stringification of one
...
...
@@ -165,7 +176,8 @@ class SystemChannels {
/// one. The framework should call `TextInput.setClient` and
/// `TextInput.setEditingState` again with its most recent information. If
/// 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
/// 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 {
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'
)
{
assert
(
_currentConnection
!.
_client
!=
null
);
final
TextInputClient
client
=
_currentConnection
!.
_client
;
assert
(
client
!=
null
);
final
AutofillScope
?
scope
=
client
.
currentAutofillScope
;
final
Map
<
String
,
dynamic
>
editingValue
=
args
[
1
]
as
Map
<
String
,
dynamic
>;
for
(
final
String
tag
in
editingValue
.
keys
)
{
...
...
@@ -1343,9 +1345,22 @@ class TextInput {
}
final
int
client
=
args
[
0
]
as
int
;
// The incoming message was for a different client.
if
(
client
!=
_currentConnection
!.
_id
)
return
;
if
(
client
!=
_currentConnection
!.
_id
)
{
// If the client IDs don't match, the incoming message was for a different
// 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
)
{
case
'TextInputClient.updateEditingState'
:
_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
if
(
_hasFocus
)
{
_openInputConnection
();
}
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
/// Called by the test framework at the beginning of a widget test to
/// 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
()
{
_restorationManager
=
null
;
resetGestureBinding
();
testTextInput
.
reset
();
if
(
registerTestTextInput
)
_testTextInput
.
register
();
}
@override
...
...
@@ -237,7 +243,8 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
@protected
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
/// widgets. An integration test would set this to false, to test real IME
...
...
@@ -245,6 +252,19 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
///
/// [TestTextInput.isRegistered] reports whether the text input mock is
/// 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
bool
get
registerTestTextInput
=>
true
;
...
...
@@ -319,9 +339,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
binding
.
setupHttpOverrides
();
}
_testTextInput
=
TestTextInput
(
onCleared:
_resetFocusedEditable
);
if
(
registerTestTextInput
)
{
_testTextInput
.
register
();
}
}
@override
...
...
@@ -515,12 +532,20 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
TestTextInput
get
testTextInput
=>
_testTextInput
;
late
TestTextInput
_testTextInput
;
/// The current client of the onscreen keyboard. Callers must pump
/// an additional frame after setting this property to complete the
/// focus change.
/// The [State] of the current [EditableText] client of the onscreen keyboard.
///
/// 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
/// [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
?
_focusedEditable
;
set
focusedEditable
(
EditableTextState
?
value
)
{
...
...
@@ -799,6 +824,8 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
// alone so that we don't cause more spurious errors.
runApp
(
Container
(
key:
UniqueKey
(),
child:
_postTestMessage
));
// Unmount any remaining widgets.
await
pump
();
if
(
registerTestTextInput
)
_testTextInput
.
unregister
();
invariantTester
();
_verifyAutoUpdateGoldensUnset
(
autoUpdateGoldensBeforeTest
&&
!
isBrowser
);
_verifyReportTestExceptionUnset
(
reportTestExceptionBeforeTest
);
...
...
packages/flutter_test/lib/src/test_text_input.dart
View file @
285b4751
This diff is collapsed.
Click to expand it.
packages/flutter_test/lib/src/widget_tester.dart
View file @
285b4751
...
...
@@ -149,7 +149,6 @@ void testWidgets(
()
async
{
binding
.
reset
();
debugResetSemanticsIdCounter
();
tester
.
resetTestTextInput
();
Object
?
memento
;
try
{
memento
=
await
variant
.
setUp
(
value
);
...
...
@@ -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
/// 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
/// previous test has set its own handler on [SystemChannels.textInput], the
/// [testTextInput] regains control and the log is fresh for the new test.
/// It should not typically need to be called by tests.
void
resetTestTextInput
()
{
testTextInput
.
resetAndRegister
();
}
/// Some of the properties and methods on this value are only valid if the
/// binding's [TestWidgetsFlutterBinding.registerTestTextInput] flag is set to
/// true as a test is starting (meaning that the keyboard is to be simulated
/// by the test framework). If those members are accessed when using a binding
/// that sets this flag to false, they will throw.
TestTextInput
get
testTextInput
=>
binding
.
testTextInput
;
/// Give the text input widget specified by [finder] the focus, as if the
/// onscreen keyboard had appeared.
...
...
@@ -1035,6 +1029,9 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
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
;
await
pump
();
});
...
...
@@ -1052,6 +1049,12 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
///
/// To just give [finder] the focus without entering any text,
/// 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
{
return
TestAsyncUtils
.
guard
<
void
>(()
async
{
await
showKeyboard
(
finder
);
...
...
packages/flutter_test/test/bindings_test.dart
View file @
285b4751
...
...
@@ -11,6 +11,8 @@ import 'package:flutter_test/flutter_test.dart';
import
'package:test_api/test_api.dart'
as
test_package
;
void
main
(
)
{
final
AutomatedTestWidgetsFlutterBinding
binding
=
AutomatedTestWidgetsFlutterBinding
();
group
(
TestViewConfiguration
,
()
{
test
(
'is initialized with top-level window if one is not provided'
,
()
{
// The code below will throw without the default.
...
...
@@ -20,15 +22,32 @@ void main() {
group
(
AutomatedTestWidgetsFlutterBinding
,
()
{
test
(
'allows setting defaultTestTimeout to 5 minutes'
,
()
{
final
AutomatedTestWidgetsFlutterBinding
binding
=
AutomatedTestWidgetsFlutterBinding
();
binding
.
defaultTestTimeout
=
const
test_package
.
Timeout
(
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
{
final
TestWidgetsFlutterBinding
binding
=
TestWidgetsFlutterBinding
.
ensureInitialized
()
as
TestWidgetsFlutterBinding
;
expect
(
binding
.
testTextInput
.
isRegistered
,
true
);
assert
(
order
==
0
);
expect
(
binding
.
testTextInput
,
isNotNull
);
expect
(
binding
.
testTextInput
.
isRegistered
,
isFalse
);
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