mirror of
https://github.com/flutter/flutter
synced 2024-10-12 11:12:54 +00:00
Migrate android_semantics_testing to null safety (#111420)
This commit is contained in:
parent
e3b9223a74
commit
0df0e2ea1f
|
@ -23,11 +23,11 @@ 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 != null && 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,
|
||||
});
|
||||
|
@ -40,10 +40,10 @@ Future<String> dataHandler(String message) async {
|
|||
}
|
||||
return completer.future;
|
||||
}
|
||||
if (message.contains('setClipboard')) {
|
||||
if (message != null && message.contains('setClipboard')) {
|
||||
final Completer<String> completer = Completer<String>();
|
||||
final String str = message.split('#')[1];
|
||||
Future<void> completeSetClipboard([Object _]) async {
|
||||
Future<void> completeSetClipboard([Object? _]) async {
|
||||
await kSemanticsChannel.invokeMethod<dynamic>('setClipboard', <String, dynamic>{
|
||||
'message': str,
|
||||
});
|
||||
|
@ -67,7 +67,7 @@ Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
|||
};
|
||||
|
||||
class TestApp extends StatelessWidget {
|
||||
const TestApp({Key key}) : super(key: key);
|
||||
const TestApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// ignore_for_file: avoid_dynamic_calls
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
@ -53,13 +55,13 @@ class AndroidSemanticsNode {
|
|||
/// ]
|
||||
/// }
|
||||
factory AndroidSemanticsNode.deserialize(String value) {
|
||||
return AndroidSemanticsNode._(json.decode(value) as Map<String, Object>);
|
||||
return AndroidSemanticsNode._(json.decode(value));
|
||||
}
|
||||
|
||||
final Map<String, Object> _values;
|
||||
final dynamic _values;
|
||||
final List<AndroidSemanticsNode> _children = <AndroidSemanticsNode>[];
|
||||
|
||||
Map<String, Object> get _flags => _values['flags'] as Map<String, Object>;
|
||||
dynamic get _flags => _values['flags'];
|
||||
|
||||
/// The text value of the semantics node.
|
||||
///
|
||||
|
@ -132,13 +134,12 @@ class AndroidSemanticsNode {
|
|||
|
||||
/// 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, int> rect = rawRect.cast<String, int>();
|
||||
final dynamic rawRect = _values['rect'];
|
||||
return Rect.fromLTRB(
|
||||
rect['left'].toDouble(),
|
||||
rect['top'].toDouble(),
|
||||
rect['right'].toDouble(),
|
||||
rect['bottom'].toDouble(),
|
||||
(rawRect['left']! as int).toDouble(),
|
||||
(rawRect['top']! as int).toDouble(),
|
||||
(rawRect['right']! as int).toDouble(),
|
||||
(rawRect['bottom']! as int).toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -150,7 +151,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
|
||||
|
|
|
@ -168,7 +168,7 @@ class AndroidSemanticsAction {
|
|||
case _kSetText:
|
||||
return 'AndroidSemanticsAction.setText';
|
||||
default:
|
||||
return null;
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -211,7 +211,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];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,24 +18,24 @@ 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<AndroidSemanticsAction> ignoredActions,
|
||||
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<AndroidSemanticsAction>? ignoredActions,
|
||||
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,
|
||||
|
@ -79,23 +79,23 @@ class _AndroidSemanticsMatcher extends Matcher {
|
|||
this.isLongClickable,
|
||||
});
|
||||
|
||||
final String text;
|
||||
final String className;
|
||||
final String contentDescription;
|
||||
final int id;
|
||||
final List<AndroidSemanticsAction> actions;
|
||||
final List<AndroidSemanticsAction> ignoredActions;
|
||||
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 List<AndroidSemanticsAction>? ignoredActions;
|
||||
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) {
|
||||
|
@ -149,7 +149,7 @@ class _AndroidSemanticsMatcher extends Matcher {
|
|||
}
|
||||
|
||||
@override
|
||||
bool matches(covariant AndroidSemanticsNode item, Map<Object, Object> matchState) {
|
||||
bool matches(covariant AndroidSemanticsNode item, Map<dynamic, dynamic> matchState) {
|
||||
if (text != null && text != item.text) {
|
||||
return _failWithMessage('Expected text: $text', matchState);
|
||||
}
|
||||
|
@ -170,13 +170,13 @@ class _AndroidSemanticsMatcher extends Matcher {
|
|||
}
|
||||
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<AndroidSemanticsAction> unexpected = itemActions.toSet().difference(actions.toSet());
|
||||
final Set<AndroidSemanticsAction> unexpected = itemActions.toSet().difference(actions!.toSet());
|
||||
final Set<String> unexpectedInString = itemActionsString.toSet().difference(actionsString.toSet());
|
||||
final Set<String> missingInString = actionsString.toSet().difference(itemActionsString.toSet());
|
||||
if (missingInString.isEmpty && ignoredActions != null && unexpected.every(ignoredActions.contains)) {
|
||||
if (missingInString.isEmpty && ignoredActions != null && unexpected.every(ignoredActions!.contains)) {
|
||||
return true;
|
||||
}
|
||||
return _failWithMessage('Expected actions: $actionsString\nActual actions: $itemActionsString\nUnexpected: $unexpectedInString\nMissing: $missingInString', matchState);
|
||||
|
@ -214,8 +214,8 @@ class _AndroidSemanticsMatcher extends Matcher {
|
|||
}
|
||||
|
||||
@override
|
||||
Description describeMismatch(Object item, Description mismatchDescription,
|
||||
Map<Object, Object> matchState, bool verbose) {
|
||||
Description describeMismatch(dynamic item, Description mismatchDescription,
|
||||
Map<dynamic, dynamic> matchState, bool verbose) {
|
||||
return mismatchDescription.add(matchState['failure'] as String);
|
||||
}
|
||||
|
||||
|
|
|
@ -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({super.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!;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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({super.key});
|
||||
|
||||
static const ValueKey<String> _appBarTitleKey = ValueKey<String>(appBarTitleKeyValue);
|
||||
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.
|
||||
class PopupControlsPage extends StatefulWidget {
|
||||
const PopupControlsPage({Key key}) : super(key: key);
|
||||
const PopupControlsPage({super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _PopupControlsPageState();
|
||||
|
@ -60,9 +60,9 @@ class _PopupControlsPageState extends State<PopupControlsPage> {
|
|||
child: Text(item),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (String value) {
|
||||
onChanged: (String? value) {
|
||||
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.
|
||||
class TextFieldPage extends StatefulWidget {
|
||||
const TextFieldPage({Key key}) : super(key: key);
|
||||
const TextFieldPage({super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _TextFieldPageState();
|
||||
|
|
|
@ -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.17.0-0 <3.0.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
|
|
|
@ -20,7 +20,7 @@ const List<AndroidSemanticsAction> ignoredAccessibilityFocusActions = <AndroidSe
|
|||
];
|
||||
|
||||
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 {
|
||||
|
@ -30,7 +30,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');
|
||||
|
@ -38,7 +38,7 @@ void main() {
|
|||
}
|
||||
|
||||
// The version of TalkBack running on the device.
|
||||
Version talkbackVersion;
|
||||
Version? talkbackVersion;
|
||||
|
||||
Future<Version> getTalkbackVersion() async {
|
||||
final io.ProcessResult result = await io.Process.run(adbPath(), const <String>[
|
||||
|
@ -51,7 +51,7 @@ void main() {
|
|||
throw Exception('Failed to get TalkBack version: ${result.stdout as String}\n${result.stderr as String}');
|
||||
}
|
||||
final List<String> lines = (result.stdout as String).split('\n');
|
||||
String version;
|
||||
String? version;
|
||||
for (final String line in lines) {
|
||||
if (line.contains('versionName')) {
|
||||
version = line.replaceAll(RegExp(r'\s*versionName='), '');
|
||||
|
@ -64,14 +64,14 @@ void main() {
|
|||
|
||||
// Android doesn't quite use semver, so convert the version string to semver form.
|
||||
final RegExp startVersion = RegExp(r'(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(\.(?<build>\d+))?');
|
||||
final RegExpMatch match = startVersion.firstMatch(version);
|
||||
final RegExpMatch? match = startVersion.firstMatch(version);
|
||||
if (match == null) {
|
||||
return Version(0, 0, 0);
|
||||
}
|
||||
return Version(
|
||||
int.parse(match.namedGroup('major')),
|
||||
int.parse(match.namedGroup('minor')),
|
||||
int.parse(match.namedGroup('patch')),
|
||||
int.parse(match.namedGroup('major')!),
|
||||
int.parse(match.namedGroup('minor')!),
|
||||
int.parse(match.namedGroup('patch')!),
|
||||
build: match.namedGroup('build'),
|
||||
);
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ void main() {
|
|||
'null',
|
||||
]);
|
||||
await run.exitCode;
|
||||
driver?.close();
|
||||
driver.close();
|
||||
});
|
||||
|
||||
group('TextField', () {
|
||||
|
|
Loading…
Reference in a new issue