Re-land "Migrate android_semantics_testing to null safety (#84136)" (#84156)

This commit is contained in:
Michael Goderbauer 2021-06-07 16:24:03 -07:00 committed by GitHub
parent b3bde1d9da
commit 0100285e6f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 91 additions and 84 deletions

View file

@ -23,18 +23,18 @@ void main() {
const MethodChannel kSemanticsChannel = MethodChannel('semantics');
Future<String> dataHandler(String message) async {
if (message.contains('getSemanticsNode')) {
Future<String> dataHandler(String? message) async {
if (message!.contains('getSemanticsNode')) {
final Completer<String> completer = Completer<String>();
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>{
'id': id,
});
completer.complete(json.encode(result));
}
if (SchedulerBinding.instance.hasScheduledFrame)
SchedulerBinding.instance.addPostFrameCallback(completeSemantics);
if (SchedulerBinding.instance!.hasScheduledFrame)
SchedulerBinding.instance!.addPostFrameCallback(completeSemantics);
else
completeSemantics();
return completer.future;
@ -50,7 +50,7 @@ Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
};
class TestApp extends StatelessWidget {
const TestApp({Key key}) : super(key: key);
const TestApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {

View file

@ -53,19 +53,19 @@ class AndroidSemanticsNode {
/// ]
/// }
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>[];
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.
///
/// This is produced by combining the value, label, and hint fields from
/// the Flutter [SemanticsNode].
String get text => _values['text'] as String;
String get text => _values['text']! as String;
/// The contentDescription of the semantics node.
///
@ -75,7 +75,7 @@ class AndroidSemanticsNode {
///
/// This is produced by combining the value, label, and hint fields from
/// the Flutter [SemanticsNode].
String get contentDescription => _values['contentDescription'] as String;
String get contentDescription => _values['contentDescription']! as String;
/// The className of the semantics node.
///
@ -84,10 +84,10 @@ class AndroidSemanticsNode {
///
/// If a more specific value isn't provided, it defaults to
/// "android.view.View".
String get className => _values['className'] as String;
String get className => _values['className']! as String;
/// 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.
List<AndroidSemanticsNode> get children => _children;
@ -95,50 +95,50 @@ class AndroidSemanticsNode {
/// Whether the node is currently in a checked state.
///
/// 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.
///
/// Equivalent to [SemanticsFlag.hasCheckedState]
bool get isCheckable => _flags['isCheckable'] as bool;
bool get isCheckable => _flags['isCheckable']! as bool;
/// Whether the node is editable.
///
/// This is usually only applied to text fields, which map
/// to "android.widget.EditText".
bool get isEditable => _flags['isEditable'] as bool;
bool get isEditable => _flags['isEditable']! as bool;
/// Whether the node is enabled.
bool get isEnabled => _flags['isEnabled'] as bool;
bool get isEnabled => _flags['isEnabled']! as bool;
/// Whether the node is focusable.
bool get isFocusable => _flags['isFocusable'] as bool;
bool get isFocusable => _flags['isFocusable']! as bool;
/// 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.
bool get isHeading => _flags['isHeading'] as bool;
bool get isHeading => _flags['isHeading']! as bool;
/// Whether the node represents a password field.
///
/// Equivalent to [SemanticsFlag.isObscured].
bool get isPassword => _flags['isPassword'] as bool;
bool get isPassword => _flags['isPassword']! as bool;
/// Whether the node is long clickable.
///
/// 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.
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>();
return Rect.fromLTRB(
rect['left'].toDouble(),
rect['top'].toDouble(),
rect['right'].toDouble(),
rect['bottom'].toDouble(),
rect['left']!.toDouble(),
rect['top']!.toDouble(),
rect['right']!.toDouble(),
rect['bottom']!.toDouble(),
);
}
@ -150,7 +150,7 @@ class AndroidSemanticsNode {
/// Gets a list of [AndroidSemanticsActions] which are defined for the node.
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

View file

@ -168,7 +168,7 @@ class AndroidSemanticsAction {
case _kSetText:
return 'AndroidSemanticsAction.setText';
default:
return null;
return 'INVALID';
}
}
@ -210,7 +210,7 @@ class AndroidSemanticsAction {
/// Creates a new [AndroidSemanticsAction] from an integer `value`.
///
/// 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];
}
}

View file

@ -18,23 +18,23 @@ import 'constants.dart';
/// the Android accessibility bridge, and not the semantics object created by
/// the Flutter framework.
Matcher hasAndroidSemantics({
String text,
String contentDescription,
String className,
int id,
Rect rect,
Size size,
List<AndroidSemanticsAction> actions,
List<AndroidSemanticsNode> children,
bool isChecked,
bool isCheckable,
bool isEditable,
bool isEnabled,
bool isFocusable,
bool isFocused,
bool isHeading,
bool isPassword,
bool isLongClickable,
String? text,
String? contentDescription,
String? className,
int? id,
Rect? rect,
Size? size,
List<AndroidSemanticsAction>? actions,
List<AndroidSemanticsNode>? children,
bool? isChecked,
bool? isCheckable,
bool? isEditable,
bool? isEnabled,
bool? isFocusable,
bool? isFocused,
bool? isHeading,
bool? isPassword,
bool? isLongClickable,
}) {
return _AndroidSemanticsMatcher(
text: text,
@ -76,22 +76,22 @@ class _AndroidSemanticsMatcher extends Matcher {
this.isLongClickable,
});
final String text;
final String className;
final String contentDescription;
final int id;
final List<AndroidSemanticsAction> actions;
final Rect rect;
final Size size;
final bool isChecked;
final bool isCheckable;
final bool isEditable;
final bool isEnabled;
final bool isFocusable;
final bool isFocused;
final bool isHeading;
final bool isPassword;
final bool isLongClickable;
final String? text;
final String? className;
final String? contentDescription;
final int? id;
final List<AndroidSemanticsAction>? actions;
final Rect? rect;
final Size? size;
final bool? isChecked;
final bool? isCheckable;
final bool? isEditable;
final bool? isEnabled;
final bool? isFocusable;
final bool? isFocused;
final bool? isHeading;
final bool? isPassword;
final bool? isLongClickable;
@override
Description describe(Description description) {
@ -130,7 +130,9 @@ class _AndroidSemanticsMatcher extends Matcher {
}
@override
bool matches(covariant AndroidSemanticsNode item, Map<Object, Object> matchState) {
bool matches(covariant AndroidSemanticsNode? item, Map<Object?, Object?> matchState) {
if (item is! AndroidSemanticsNode)
return _failWithMessage('Expected an AndroidSemanticsNode, but got: $item', matchState);
if (text != null && text != item.text)
return _failWithMessage('Expected text: $text', matchState);
if (contentDescription != null && contentDescription != item.contentDescription)
@ -145,8 +147,8 @@ class _AndroidSemanticsMatcher extends Matcher {
return _failWithMessage('Expected size: $size', matchState);
if (actions != null) {
final List<AndroidSemanticsAction> itemActions = item.getActions();
if (!unorderedEquals(actions).matches(itemActions, matchState)) {
final List<String> actionsString = actions.map<String>((AndroidSemanticsAction action) => action.toString()).toList()..sort();
if (!unorderedEquals(actions!).matches(itemActions, matchState)) {
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 Set<String> unexpected = itemActionsString.toSet().difference(actionsString.toSet());
final Set<String> missing = actionsString.toSet().difference(itemActionsString.toSet());
@ -176,12 +178,16 @@ class _AndroidSemanticsMatcher extends Matcher {
}
@override
Description describeMismatch(Object item, Description mismatchDescription,
Map<Object, Object> matchState, bool verbose) {
return mismatchDescription.add(matchState['failure'] as String);
Description describeMismatch(
Object? item,
Description mismatchDescription,
Map<Object?, Object?> matchState,
bool verbose,
) {
return mismatchDescription.add(matchState['failure']! as String);
}
bool _failWithMessage(String value, Map<dynamic, dynamic> matchState) {
bool _failWithMessage(String value, Map<Object?, Object?> matchState) {
matchState['failure'] = value;
return false;
}

View file

@ -9,7 +9,7 @@ export 'controls_constants.dart';
/// A test page with a checkbox, three radio buttons, and a switch.
class SelectionControlsPage extends StatefulWidget {
const SelectionControlsPage({Key key}) : super(key: key);
const SelectionControlsPage({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _SelectionControlsPageState();
@ -28,15 +28,15 @@ class _SelectionControlsPageState extends State<SelectionControlsPage> {
bool _isLabeledOn = false;
int _radio = 0;
void _updateCheckbox(bool newValue) {
void _updateCheckbox(bool? newValue) {
setState(() {
_isChecked = newValue;
_isChecked = newValue!;
});
}
void _updateRadio(int newValue) {
void _updateRadio(int? newValue) {
setState(() {
_radio = newValue;
_radio = newValue!;
});
}

View file

@ -9,7 +9,7 @@ export 'headings_constants.dart';
/// A test page with an app bar and some body text for testing heading flags.
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> _bodyTextKey = ValueKey<String>(bodyTextKeyValue);

View file

@ -10,7 +10,7 @@ export 'popup_constants.dart';
/// A page with a popup menu, a dropdown menu, and a modal alert.
class PopupControlsPage extends StatefulWidget {
const PopupControlsPage({Key key}) : super(key: key);
const PopupControlsPage({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _PopupControlsPageState();
@ -22,7 +22,7 @@ class _PopupControlsPageState extends State<PopupControlsPage> {
final Key alertKey = const ValueKey<String>(alertKeyValue);
String popupValue = popupItems.first;
String dropdownValue = popupItems.first;
String? dropdownValue = popupItems.first;
@override
Widget build(BuildContext context) {
@ -60,7 +60,7 @@ class _PopupControlsPageState extends State<PopupControlsPage> {
child: Text(item),
);
}).toList(),
onChanged: (String value) {
onChanged: (String? value) {
setState(() {
dropdownValue = value;
});

View file

@ -10,7 +10,7 @@ export 'text_field_constants.dart';
/// A page with a normal text field and a password field.
class TextFieldPage extends StatefulWidget {
const TextFieldPage({Key key}) : super(key: key);
const TextFieldPage({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _TextFieldPageState();

View file

@ -1,7 +1,7 @@
name: android_semantics_testing
description: Integration testing library for Android semantics
environment:
sdk: '>=2.9.0 <3.0.0'
sdk: '>=2.12.0 <3.0.0'
dependencies:
flutter:

View file

@ -11,7 +11,7 @@ import 'package:path/path.dart' as path;
import 'package:test/test.dart' hide isInstanceOf;
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) {
return 'adb';
} else {
@ -21,7 +21,7 @@ String adbPath() {
void main() {
group('AccessibilityBridge', () {
FlutterDriver driver;
late FlutterDriver driver;
Future<AndroidSemanticsNode> getSemantics(SerializableFinder finder) async {
final int id = await driver.getSemanticsId(finder);
final String data = await driver.requestData('getSemanticsNode#$id');
@ -53,7 +53,7 @@ void main() {
'null',
]);
await run.exitCode;
driver?.close();
driver.close();
});
group('TextField', () {
@ -658,6 +658,7 @@ void main() {
group('Headings', () {
setUpAll(() async {
await driver.tap(find.text(headingsRoute));
await driver.waitFor(find.byValueKey(appBarTitleKeyValue));
});
test('AppBar title has correct Android heading semantics', () async {