This reverts commit 0d1a8a7798133bbb363bad53ecd0fc282aa2e92f.
This commit is contained in:
parent
745950c2e6
commit
094a75e655
@ -23,18 +23,18 @@ void main() {
|
|||||||
|
|
||||||
const MethodChannel kSemanticsChannel = MethodChannel('semantics');
|
const MethodChannel kSemanticsChannel = MethodChannel('semantics');
|
||||||
|
|
||||||
Future<String> dataHandler(String? message) async {
|
Future<String> dataHandler(String message) async {
|
||||||
if (message!.contains('getSemanticsNode')) {
|
if (message.contains('getSemanticsNode')) {
|
||||||
final Completer<String> completer = Completer<String>();
|
final Completer<String> completer = Completer<String>();
|
||||||
final int id = int.tryParse(message.split('#')[1]) ?? 0;
|
final int id = int.tryParse(message.split('#')[1]) ?? 0;
|
||||||
Future<void> completeSemantics([Object? _]) async {
|
Future<void> completeSemantics([Object _]) async {
|
||||||
final dynamic result = await kSemanticsChannel.invokeMethod<dynamic>('getSemanticsNode', <String, dynamic>{
|
final dynamic result = await kSemanticsChannel.invokeMethod<dynamic>('getSemanticsNode', <String, dynamic>{
|
||||||
'id': id,
|
'id': id,
|
||||||
});
|
});
|
||||||
completer.complete(json.encode(result));
|
completer.complete(json.encode(result));
|
||||||
}
|
}
|
||||||
if (SchedulerBinding.instance!.hasScheduledFrame)
|
if (SchedulerBinding.instance.hasScheduledFrame)
|
||||||
SchedulerBinding.instance!.addPostFrameCallback(completeSemantics);
|
SchedulerBinding.instance.addPostFrameCallback(completeSemantics);
|
||||||
else
|
else
|
||||||
completeSemantics();
|
completeSemantics();
|
||||||
return completer.future;
|
return completer.future;
|
||||||
@ -50,7 +50,7 @@ Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
|||||||
};
|
};
|
||||||
|
|
||||||
class TestApp extends StatelessWidget {
|
class TestApp extends StatelessWidget {
|
||||||
const TestApp({Key? key}) : super(key: key);
|
const TestApp({Key key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -53,19 +53,19 @@ class AndroidSemanticsNode {
|
|||||||
/// ]
|
/// ]
|
||||||
/// }
|
/// }
|
||||||
factory AndroidSemanticsNode.deserialize(String value) {
|
factory AndroidSemanticsNode.deserialize(String value) {
|
||||||
return AndroidSemanticsNode._(json.decode(value) as Map<String, Object?>);
|
return AndroidSemanticsNode._(json.decode(value) as Map<String, Object>);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Map<String, Object?> _values;
|
final Map<String, Object> _values;
|
||||||
final List<AndroidSemanticsNode> _children = <AndroidSemanticsNode>[];
|
final List<AndroidSemanticsNode> _children = <AndroidSemanticsNode>[];
|
||||||
|
|
||||||
Map<String, Object?> get _flags => _values['flags']! as Map<String, Object?>;
|
Map<String, Object> get _flags => _values['flags'] as Map<String, Object>;
|
||||||
|
|
||||||
/// The text value of the semantics node.
|
/// The text value of the semantics node.
|
||||||
///
|
///
|
||||||
/// This is produced by combining the value, label, and hint fields from
|
/// This is produced by combining the value, label, and hint fields from
|
||||||
/// the Flutter [SemanticsNode].
|
/// the Flutter [SemanticsNode].
|
||||||
String get text => _values['text']! as String;
|
String get text => _values['text'] as String;
|
||||||
|
|
||||||
/// The contentDescription of the semantics node.
|
/// The contentDescription of the semantics node.
|
||||||
///
|
///
|
||||||
@ -75,7 +75,7 @@ class AndroidSemanticsNode {
|
|||||||
///
|
///
|
||||||
/// This is produced by combining the value, label, and hint fields from
|
/// This is produced by combining the value, label, and hint fields from
|
||||||
/// the Flutter [SemanticsNode].
|
/// the Flutter [SemanticsNode].
|
||||||
String get contentDescription => _values['contentDescription']! as String;
|
String get contentDescription => _values['contentDescription'] as String;
|
||||||
|
|
||||||
/// The className of the semantics node.
|
/// The className of the semantics node.
|
||||||
///
|
///
|
||||||
@ -84,10 +84,10 @@ class AndroidSemanticsNode {
|
|||||||
///
|
///
|
||||||
/// If a more specific value isn't provided, it defaults to
|
/// If a more specific value isn't provided, it defaults to
|
||||||
/// "android.view.View".
|
/// "android.view.View".
|
||||||
String get className => _values['className']! as String;
|
String get className => _values['className'] as String;
|
||||||
|
|
||||||
/// The identifier for this semantics node.
|
/// The identifier for this semantics node.
|
||||||
int get id => _values['id']! as int;
|
int get id => _values['id'] as int;
|
||||||
|
|
||||||
/// The children of this semantics node.
|
/// The children of this semantics node.
|
||||||
List<AndroidSemanticsNode> get children => _children;
|
List<AndroidSemanticsNode> get children => _children;
|
||||||
@ -95,50 +95,50 @@ class AndroidSemanticsNode {
|
|||||||
/// Whether the node is currently in a checked state.
|
/// Whether the node is currently in a checked state.
|
||||||
///
|
///
|
||||||
/// Equivalent to [SemanticsFlag.isChecked].
|
/// Equivalent to [SemanticsFlag.isChecked].
|
||||||
bool get isChecked => _flags['isChecked']! as bool;
|
bool get isChecked => _flags['isChecked'] as bool;
|
||||||
|
|
||||||
/// Whether the node can be in a checked state.
|
/// Whether the node can be in a checked state.
|
||||||
///
|
///
|
||||||
/// Equivalent to [SemanticsFlag.hasCheckedState]
|
/// Equivalent to [SemanticsFlag.hasCheckedState]
|
||||||
bool get isCheckable => _flags['isCheckable']! as bool;
|
bool get isCheckable => _flags['isCheckable'] as bool;
|
||||||
|
|
||||||
/// Whether the node is editable.
|
/// Whether the node is editable.
|
||||||
///
|
///
|
||||||
/// This is usually only applied to text fields, which map
|
/// This is usually only applied to text fields, which map
|
||||||
/// to "android.widget.EditText".
|
/// to "android.widget.EditText".
|
||||||
bool get isEditable => _flags['isEditable']! as bool;
|
bool get isEditable => _flags['isEditable'] as bool;
|
||||||
|
|
||||||
/// Whether the node is enabled.
|
/// Whether the node is enabled.
|
||||||
bool get isEnabled => _flags['isEnabled']! as bool;
|
bool get isEnabled => _flags['isEnabled'] as bool;
|
||||||
|
|
||||||
/// Whether the node is focusable.
|
/// Whether the node is focusable.
|
||||||
bool get isFocusable => _flags['isFocusable']! as bool;
|
bool get isFocusable => _flags['isFocusable'] as bool;
|
||||||
|
|
||||||
/// Whether the node is focused.
|
/// Whether the node is focused.
|
||||||
bool get isFocused => _flags['isFocused']! as bool;
|
bool get isFocused => _flags['isFocused'] as bool;
|
||||||
|
|
||||||
/// Whether the node is considered a heading.
|
/// Whether the node is considered a heading.
|
||||||
bool get isHeading => _flags['isHeading']! as bool;
|
bool get isHeading => _flags['isHeading'] as bool;
|
||||||
|
|
||||||
/// Whether the node represents a password field.
|
/// Whether the node represents a password field.
|
||||||
///
|
///
|
||||||
/// Equivalent to [SemanticsFlag.isObscured].
|
/// Equivalent to [SemanticsFlag.isObscured].
|
||||||
bool get isPassword => _flags['isPassword']! as bool;
|
bool get isPassword => _flags['isPassword'] as bool;
|
||||||
|
|
||||||
/// Whether the node is long clickable.
|
/// Whether the node is long clickable.
|
||||||
///
|
///
|
||||||
/// Equivalent to having [SemanticsAction.longPress].
|
/// Equivalent to having [SemanticsAction.longPress].
|
||||||
bool get isLongClickable => _flags['isLongClickable']! as bool;
|
bool get isLongClickable => _flags['isLongClickable'] as bool;
|
||||||
|
|
||||||
/// Gets a [Rect] which defines the position and size of the semantics node.
|
/// Gets a [Rect] which defines the position and size of the semantics node.
|
||||||
Rect getRect() {
|
Rect getRect() {
|
||||||
final Map<String, Object?> rawRect = _values['rect']! as Map<String, Object?>;
|
final Map<String, Object> rawRect = _values['rect'] as Map<String, Object>;
|
||||||
final Map<String, int> rect = rawRect.cast<String, int>();
|
final Map<String, int> rect = rawRect.cast<String, int>();
|
||||||
return Rect.fromLTRB(
|
return Rect.fromLTRB(
|
||||||
rect['left']!.toDouble(),
|
rect['left'].toDouble(),
|
||||||
rect['top']!.toDouble(),
|
rect['top'].toDouble(),
|
||||||
rect['right']!.toDouble(),
|
rect['right'].toDouble(),
|
||||||
rect['bottom']!.toDouble(),
|
rect['bottom'].toDouble(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,7 +150,7 @@ class AndroidSemanticsNode {
|
|||||||
|
|
||||||
/// Gets a list of [AndroidSemanticsActions] which are defined for the node.
|
/// Gets a list of [AndroidSemanticsActions] which are defined for the node.
|
||||||
List<AndroidSemanticsAction> getActions() => <AndroidSemanticsAction>[
|
List<AndroidSemanticsAction> getActions() => <AndroidSemanticsAction>[
|
||||||
for (final int id in (_values['actions']! as List<dynamic>).cast<int>()) AndroidSemanticsAction.deserialize(id)!,
|
for (final int id in (_values['actions'] as List<dynamic>).cast<int>()) AndroidSemanticsAction.deserialize(id),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -168,7 +168,7 @@ class AndroidSemanticsAction {
|
|||||||
case _kSetText:
|
case _kSetText:
|
||||||
return 'AndroidSemanticsAction.setText';
|
return 'AndroidSemanticsAction.setText';
|
||||||
default:
|
default:
|
||||||
return 'INVALID';
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,7 +210,7 @@ class AndroidSemanticsAction {
|
|||||||
/// Creates a new [AndroidSemanticsAction] from an integer `value`.
|
/// Creates a new [AndroidSemanticsAction] from an integer `value`.
|
||||||
///
|
///
|
||||||
/// Returns `null` if the id is not a known Android accessibility action.
|
/// Returns `null` if the id is not a known Android accessibility action.
|
||||||
static AndroidSemanticsAction? deserialize(int value) {
|
static AndroidSemanticsAction deserialize(int value) {
|
||||||
return _kActionById[value];
|
return _kActionById[value];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,23 +18,23 @@ import 'constants.dart';
|
|||||||
/// the Android accessibility bridge, and not the semantics object created by
|
/// the Android accessibility bridge, and not the semantics object created by
|
||||||
/// the Flutter framework.
|
/// the Flutter framework.
|
||||||
Matcher hasAndroidSemantics({
|
Matcher hasAndroidSemantics({
|
||||||
String? text,
|
String text,
|
||||||
String? contentDescription,
|
String contentDescription,
|
||||||
String? className,
|
String className,
|
||||||
int? id,
|
int id,
|
||||||
Rect? rect,
|
Rect rect,
|
||||||
Size? size,
|
Size size,
|
||||||
List<AndroidSemanticsAction>? actions,
|
List<AndroidSemanticsAction> actions,
|
||||||
List<AndroidSemanticsNode>? children,
|
List<AndroidSemanticsNode> children,
|
||||||
bool? isChecked,
|
bool isChecked,
|
||||||
bool? isCheckable,
|
bool isCheckable,
|
||||||
bool? isEditable,
|
bool isEditable,
|
||||||
bool? isEnabled,
|
bool isEnabled,
|
||||||
bool? isFocusable,
|
bool isFocusable,
|
||||||
bool? isFocused,
|
bool isFocused,
|
||||||
bool? isHeading,
|
bool isHeading,
|
||||||
bool? isPassword,
|
bool isPassword,
|
||||||
bool? isLongClickable,
|
bool isLongClickable,
|
||||||
}) {
|
}) {
|
||||||
return _AndroidSemanticsMatcher(
|
return _AndroidSemanticsMatcher(
|
||||||
text: text,
|
text: text,
|
||||||
@ -76,22 +76,22 @@ class _AndroidSemanticsMatcher extends Matcher {
|
|||||||
this.isLongClickable,
|
this.isLongClickable,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String? text;
|
final String text;
|
||||||
final String? className;
|
final String className;
|
||||||
final String? contentDescription;
|
final String contentDescription;
|
||||||
final int? id;
|
final int id;
|
||||||
final List<AndroidSemanticsAction>? actions;
|
final List<AndroidSemanticsAction> actions;
|
||||||
final Rect? rect;
|
final Rect rect;
|
||||||
final Size? size;
|
final Size size;
|
||||||
final bool? isChecked;
|
final bool isChecked;
|
||||||
final bool? isCheckable;
|
final bool isCheckable;
|
||||||
final bool? isEditable;
|
final bool isEditable;
|
||||||
final bool? isEnabled;
|
final bool isEnabled;
|
||||||
final bool? isFocusable;
|
final bool isFocusable;
|
||||||
final bool? isFocused;
|
final bool isFocused;
|
||||||
final bool? isHeading;
|
final bool isHeading;
|
||||||
final bool? isPassword;
|
final bool isPassword;
|
||||||
final bool? isLongClickable;
|
final bool isLongClickable;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Description describe(Description description) {
|
Description describe(Description description) {
|
||||||
@ -130,10 +130,7 @@ class _AndroidSemanticsMatcher extends Matcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool matches(covariant AndroidSemanticsNode? item, Map<Object?, Object?> matchState) {
|
bool matches(covariant AndroidSemanticsNode item, Map<Object, Object> matchState) {
|
||||||
if (item == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (text != null && text != item.text)
|
if (text != null && text != item.text)
|
||||||
return _failWithMessage('Expected text: $text', matchState);
|
return _failWithMessage('Expected text: $text', matchState);
|
||||||
if (contentDescription != null && contentDescription != item.contentDescription)
|
if (contentDescription != null && contentDescription != item.contentDescription)
|
||||||
@ -148,8 +145,8 @@ class _AndroidSemanticsMatcher extends Matcher {
|
|||||||
return _failWithMessage('Expected size: $size', matchState);
|
return _failWithMessage('Expected size: $size', matchState);
|
||||||
if (actions != null) {
|
if (actions != null) {
|
||||||
final List<AndroidSemanticsAction> itemActions = item.getActions();
|
final List<AndroidSemanticsAction> itemActions = item.getActions();
|
||||||
if (!unorderedEquals(actions!).matches(itemActions, matchState)) {
|
if (!unorderedEquals(actions).matches(itemActions, matchState)) {
|
||||||
final List<String> actionsString = actions!.map<String>((AndroidSemanticsAction action) => action.toString()).toList()..sort();
|
final List<String> actionsString = actions.map<String>((AndroidSemanticsAction action) => action.toString()).toList()..sort();
|
||||||
final List<String> itemActionsString = itemActions.map<String>((AndroidSemanticsAction action) => action.toString()).toList()..sort();
|
final List<String> itemActionsString = itemActions.map<String>((AndroidSemanticsAction action) => action.toString()).toList()..sort();
|
||||||
final Set<String> unexpected = itemActionsString.toSet().difference(actionsString.toSet());
|
final Set<String> unexpected = itemActionsString.toSet().difference(actionsString.toSet());
|
||||||
final Set<String> missing = actionsString.toSet().difference(itemActionsString.toSet());
|
final Set<String> missing = actionsString.toSet().difference(itemActionsString.toSet());
|
||||||
@ -179,14 +176,9 @@ class _AndroidSemanticsMatcher extends Matcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Description describeMismatch(
|
Description describeMismatch(Object item, Description mismatchDescription,
|
||||||
Object? item,
|
Map<Object, Object> matchState, bool verbose) {
|
||||||
Description mismatchDescription,
|
return mismatchDescription.add(matchState['failure'] as String);
|
||||||
Map<Object?, Object?> matchState,
|
|
||||||
bool verbose,
|
|
||||||
) {
|
|
||||||
print(matchState);
|
|
||||||
return mismatchDescription.add(matchState['failure']! as String);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _failWithMessage(String value, Map<dynamic, dynamic> matchState) {
|
bool _failWithMessage(String value, Map<dynamic, dynamic> matchState) {
|
||||||
|
@ -9,7 +9,7 @@ export 'controls_constants.dart';
|
|||||||
|
|
||||||
/// A test page with a checkbox, three radio buttons, and a switch.
|
/// A test page with a checkbox, three radio buttons, and a switch.
|
||||||
class SelectionControlsPage extends StatefulWidget {
|
class SelectionControlsPage extends StatefulWidget {
|
||||||
const SelectionControlsPage({Key? key}) : super(key: key);
|
const SelectionControlsPage({Key key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => _SelectionControlsPageState();
|
State<StatefulWidget> createState() => _SelectionControlsPageState();
|
||||||
@ -28,15 +28,15 @@ class _SelectionControlsPageState extends State<SelectionControlsPage> {
|
|||||||
bool _isLabeledOn = false;
|
bool _isLabeledOn = false;
|
||||||
int _radio = 0;
|
int _radio = 0;
|
||||||
|
|
||||||
void _updateCheckbox(bool? newValue) {
|
void _updateCheckbox(bool newValue) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isChecked = newValue!;
|
_isChecked = newValue;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateRadio(int? newValue) {
|
void _updateRadio(int newValue) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_radio = newValue!;
|
_radio = newValue;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ export 'headings_constants.dart';
|
|||||||
|
|
||||||
/// A test page with an app bar and some body text for testing heading flags.
|
/// A test page with an app bar and some body text for testing heading flags.
|
||||||
class HeadingsPage extends StatelessWidget {
|
class HeadingsPage extends StatelessWidget {
|
||||||
const HeadingsPage({Key? key}) : super(key: key);
|
const HeadingsPage({Key key}) : super(key: key);
|
||||||
|
|
||||||
static const ValueKey<String> _appBarTitleKey = ValueKey<String>(appBarTitleKeyValue);
|
static const ValueKey<String> _appBarTitleKey = ValueKey<String>(appBarTitleKeyValue);
|
||||||
static const ValueKey<String> _bodyTextKey = ValueKey<String>(bodyTextKeyValue);
|
static const ValueKey<String> _bodyTextKey = ValueKey<String>(bodyTextKeyValue);
|
||||||
|
@ -10,7 +10,7 @@ export 'popup_constants.dart';
|
|||||||
|
|
||||||
/// A page with a popup menu, a dropdown menu, and a modal alert.
|
/// A page with a popup menu, a dropdown menu, and a modal alert.
|
||||||
class PopupControlsPage extends StatefulWidget {
|
class PopupControlsPage extends StatefulWidget {
|
||||||
const PopupControlsPage({Key? key}) : super(key: key);
|
const PopupControlsPage({Key key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => _PopupControlsPageState();
|
State<StatefulWidget> createState() => _PopupControlsPageState();
|
||||||
@ -22,7 +22,7 @@ class _PopupControlsPageState extends State<PopupControlsPage> {
|
|||||||
final Key alertKey = const ValueKey<String>(alertKeyValue);
|
final Key alertKey = const ValueKey<String>(alertKeyValue);
|
||||||
|
|
||||||
String popupValue = popupItems.first;
|
String popupValue = popupItems.first;
|
||||||
String? dropdownValue = popupItems.first;
|
String dropdownValue = popupItems.first;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -60,7 +60,7 @@ class _PopupControlsPageState extends State<PopupControlsPage> {
|
|||||||
child: Text(item),
|
child: Text(item),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
onChanged: (String? value) {
|
onChanged: (String value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
dropdownValue = value;
|
dropdownValue = value;
|
||||||
});
|
});
|
||||||
|
@ -10,7 +10,7 @@ export 'text_field_constants.dart';
|
|||||||
|
|
||||||
/// A page with a normal text field and a password field.
|
/// A page with a normal text field and a password field.
|
||||||
class TextFieldPage extends StatefulWidget {
|
class TextFieldPage extends StatefulWidget {
|
||||||
const TextFieldPage({Key? key}) : super(key: key);
|
const TextFieldPage({Key key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => _TextFieldPageState();
|
State<StatefulWidget> createState() => _TextFieldPageState();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
name: android_semantics_testing
|
name: android_semantics_testing
|
||||||
description: Integration testing library for Android semantics
|
description: Integration testing library for Android semantics
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.12.0 <3.0.0'
|
sdk: '>=2.9.0 <3.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
|
@ -11,7 +11,7 @@ import 'package:path/path.dart' as path;
|
|||||||
import 'package:test/test.dart' hide isInstanceOf;
|
import 'package:test/test.dart' hide isInstanceOf;
|
||||||
|
|
||||||
String adbPath() {
|
String adbPath() {
|
||||||
final String? androidHome = io.Platform.environment['ANDROID_HOME'] ?? io.Platform.environment['ANDROID_SDK_ROOT'];
|
final String androidHome = io.Platform.environment['ANDROID_HOME'] ?? io.Platform.environment['ANDROID_SDK_ROOT'];
|
||||||
if (androidHome == null) {
|
if (androidHome == null) {
|
||||||
return 'adb';
|
return 'adb';
|
||||||
} else {
|
} else {
|
||||||
@ -21,7 +21,7 @@ String adbPath() {
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
group('AccessibilityBridge', () {
|
group('AccessibilityBridge', () {
|
||||||
late FlutterDriver driver;
|
FlutterDriver driver;
|
||||||
Future<AndroidSemanticsNode> getSemantics(SerializableFinder finder) async {
|
Future<AndroidSemanticsNode> getSemantics(SerializableFinder finder) async {
|
||||||
final int id = await driver.getSemanticsId(finder);
|
final int id = await driver.getSemanticsId(finder);
|
||||||
final String data = await driver.requestData('getSemanticsNode#$id');
|
final String data = await driver.requestData('getSemanticsNode#$id');
|
||||||
@ -53,7 +53,7 @@ void main() {
|
|||||||
'null',
|
'null',
|
||||||
]);
|
]);
|
||||||
await run.exitCode;
|
await run.exitCode;
|
||||||
driver.close();
|
driver?.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
group('TextField', () {
|
group('TextField', () {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user