Unverified Commit 99b38295 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Create flutter_driver infra for testing the Android AccessibilityNodeInfo...

Create flutter_driver infra for testing the Android AccessibilityNodeInfo generated by Flutter (#19700)
parent 676a3358
...@@ -309,6 +309,7 @@ Future<Null> _runTests() async { ...@@ -309,6 +309,7 @@ Future<Null> _runTests() async {
await _runFlutterTest(path.join(flutterRoot, 'packages', 'fuchsia_remote_debug_protocol')); await _runFlutterTest(path.join(flutterRoot, 'packages', 'fuchsia_remote_debug_protocol'));
await _pubRunTest(path.join(flutterRoot, 'dev', 'bots')); await _pubRunTest(path.join(flutterRoot, 'dev', 'bots'));
await _pubRunTest(path.join(flutterRoot, 'dev', 'devicelab')); await _pubRunTest(path.join(flutterRoot, 'dev', 'devicelab'));
await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'android_semantics_testing'));
await _runFlutterTest(path.join(flutterRoot, 'dev', 'manual_tests')); await _runFlutterTest(path.join(flutterRoot, 'dev', 'manual_tests'));
await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'vitool')); await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'vitool'));
await _runFlutterTest(path.join(flutterRoot, 'examples', 'hello_world')); await _runFlutterTest(path.join(flutterRoot, 'examples', 'hello_world'));
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// This library provides constants and matchers for testing the Android
/// accessibility implementation in a flutter driver environment.
library android_semantics_testing;
export 'src/common.dart';
export 'src/constants.dart';
export 'src/matcher.dart';
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'package:meta/meta.dart';
import 'constants.dart';
/// A semantics node created from Android accessibility information.
///
/// This object represents Android accessibility information derived from an
/// [AccessibilityNodeInfo](https://developer.android.com/reference/android/view/accessibility/AccessibilityNodeInfo)
/// object. The purpose is to verify in integration
/// tests that our semantics framework produces the correct accessibility info
/// on Android.
///
/// See also:
///
/// * [AccessibilityNodeInfo](https://developer.android.com/reference/android/view/accessibility/AccessibilityNodeInfo)
class AndroidSemanticsNode {
AndroidSemanticsNode._(this._values);
/// Deserializes a new [AndroidSemanticsNode] from a json map.
///
/// The structure of the JSON:
///
/// {
/// "flags": {
/// "isChecked": bool,
/// "isCheckable": bool,
/// "isEditable": bool,
/// "isEnabled": bool,
/// "isFocusable": bool,
/// "isFocused": bool,
/// "isPassword": bool,
/// "isLongClickable": bool,
/// },
/// "text": String,
/// "className": String,
/// "id": int,
/// "rect": {
/// left: int,
/// top: int,
/// right: int,
/// bottom: int,
/// },
/// actions: [
/// int,
/// ]
/// }
factory AndroidSemanticsNode.deserialize(String value) {
return new AndroidSemanticsNode._(json.decode(value));
}
final Map<String, Object> _values;
final List<AndroidSemanticsNode> _children = <AndroidSemanticsNode>[];
Map<String, Object> get _flags => _values['flags'];
/// 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'];
/// The className of the semantics node.
///
/// Certain kinds of Flutter semantics are mapped to Android classes to
/// use their default semantic behavior, such as checkboxes and images.
///
/// If a more specific value isn't provided, it defaults to
/// "android.view.View".
String get className => _values['className'];
/// The identifier for this semantics node.
int get id => _values['id'];
/// The children of this semantics node.
List<AndroidSemanticsNode> get children => _children;
/// Whether the node is currently in a checked state.
///
/// Equivalent to [SemanticsFlag.isChecked].
bool get isChecked => _flags['isChecked'];
/// Whether the node can be in a checked state.
///
/// Equivalent to [SemanticsFlag.hasCheckedState]
bool get isCheckable => _flags['isCheckable'];
/// Whether the node is editable.
///
/// This is usually only applied to text fields, which map
/// to "android.widget.EditText".
bool get isEditable => _flags['isEditable'];
/// Whether the node is enabled.
bool get isEnabled => _flags['isEnabled'];
/// Whether the node is focusable.
bool get isFocusable => _flags['isFocusable'];
/// Whether the node is focused.
bool get isFocused => _flags['isFocused'];
/// Whether the node represents a password field.
///
/// Equivalent to [SemanticsFlag.isObscured].
bool get isPassword => _flags['isPassword'];
/// Whether the node is long clickable.
///
/// Equivalent to having [SemanticsAction.longPress].
bool get isLongClickable => _flags['isLongClickable'];
/// Gets a [Rect] which defines the position and size of the semantics node.
Rect getRect() {
final Map<String, Object> rawRect = _values['rect'];
final Map<String, int> rect = rawRect.cast<String, int>();
return new Rect.fromLTRB(
rect['left'].toDouble(),
rect['top'].toDouble(),
rect['right'].toDouble(),
rect['bottom'].toDouble(),
);
}
/// Gets a [Size] which defines the size of the semantics node.
Size getSize() {
final Rect rect = getRect();
return new Size(rect.bottom - rect.top, rect.right - rect.left);
}
/// Gets a list of [AndroidSemanticsActions] which are defined for the node.
List<AndroidSemanticsAction> getActions() {
final List<AndroidSemanticsAction> result = <AndroidSemanticsAction>[];
for (int id in _values['actions']) {
result.add(AndroidSemanticsAction.deserialize(id));
}
return result;
}
@override
String toString() {
return _values.toString();
}
}
/// A Dart VM implementation of a rectangle.
///
/// Created to mirror the implementation of [ui.Rect].
@immutable
class Rect {
/// Creates a new rectangle.
///
/// All values are required.
const Rect.fromLTRB(this.left, this.top, this.right, this.bottom);
/// The top side of the rectangle.
final double top;
/// The left side of the rectangle.
final double left;
/// The right side of the rectangle.
final double right;
/// The bottom side of the rectangle.
final double bottom;
@override
int get hashCode =>
top.hashCode ^ left.hashCode ^ right.hashCode ^ bottom.hashCode;
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
final Rect typedOther = other;
return typedOther.top == top &&
typedOther.left == left &&
typedOther.right == right &&
typedOther.bottom == bottom;
}
@override
String toString() => 'Rect.fromLTRB($left, $top, $right, $bottom)';
}
/// A Dart VM implementation of a Size.
///
/// Created to mirror the implementation [ui.Size].
@immutable
class Size {
/// Creates a new [Size] object.
const Size(this.width, this.height);
/// The width of some object.
final double width;
/// The height of some object.
final double height;
@override
int get hashCode => width.hashCode ^ height.hashCode;
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
final Size typedOther = other;
return typedOther.width == width && typedOther.height == height;
}
@override
String toString() => 'Size{$width, $height}';
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:meta/meta.dart';
/// Class name constants which correspond to the class names used by the
/// Android accessibility bridge.
class AndroidClassName {
/// The class name used for checkboxes.
static const String checkBox = 'android.widget.CheckBox';
/// The default className if none is provided by flutter.
static const String view = 'android.view.View';
/// The class name used for radio buttons.
static const String radio = 'android.widget.RadioButton';
/// The class name used for editable text fields.
static const String editText = 'android.widget.EditText';
/// The class name used for read only text fields.
static const String textView = 'android.widget.TextView';
}
/// Action constants which correspond to `AccessibilityAction` in Android.
@immutable
class AndroidSemanticsAction {
const AndroidSemanticsAction._(this.id);
/// The Android id of the action.
final int id;
static const int _kFocusIndex = 1 << 0;
static const int _kClearFocusIndex = 1 << 1;
static const int _kSelectIndex = 1 << 2;
static const int _kClearSelectionIndex = 1 << 3;
static const int _kClickIndex = 1 << 4;
static const int _kLongClickIndex = 1 << 5;
static const int _kAccessibilityFocusIndex = 1 << 6;
static const int _kClearAccessibilityFocusIndex = 1 << 7;
static const int _kNextAtMovementGranularityIndex = 1 << 8;
static const int _kPreviousAtMovementGranularityIndex = 1 << 9;
static const int _kNextHtmlElementIndex = 1 << 10;
static const int _kPreviousHtmlElementIndex = 1 << 11;
static const int _kScrollForwardIndex = 1 << 12;
static const int _kScrollBackwardIndex = 1 << 13;
static const int _kCutIndex = 1 << 14;
static const int _kCopyIndex = 1 << 15;
static const int _kPasteIndex = 1 << 16;
static const int _kSetSelectionIndex = 1 << 17;
static const int _kExpandIndex = 1 << 18;
static const int _kCollapseIndex = 1 << 19;
/// Matches `AccessibilityAction.ACTION_FOCUS`.
static const AndroidSemanticsAction focus = AndroidSemanticsAction._(_kFocusIndex);
/// Matches `AccessibilityAction.ACTION_CLEAR_FOCUS`.
static const AndroidSemanticsAction clearFocus = AndroidSemanticsAction._(_kClearFocusIndex);
/// Matches `AccessibilityAction.ACTION_SELECT`.
static const AndroidSemanticsAction select = AndroidSemanticsAction._(_kSelectIndex);
/// Matches `AccessibilityAction.ACTION_CLEAR_SELECTION`.
static const AndroidSemanticsAction clearSelection = AndroidSemanticsAction._(_kClearSelectionIndex);
/// Matches `AccessibilityAction.ACTION_CLICK`.
static const AndroidSemanticsAction click = AndroidSemanticsAction._(_kClickIndex);
/// Matches `AccessibilityAction.ACTION_LONG_CLICK`.
static const AndroidSemanticsAction longClick = AndroidSemanticsAction._(_kLongClickIndex);
/// Matches `AccessibilityAction.ACTION_ACCESSIBILITY_FOCUS`.
static const AndroidSemanticsAction accessibilityFocus = AndroidSemanticsAction._(_kAccessibilityFocusIndex);
/// Matches `AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS`.
static const AndroidSemanticsAction clearAccessibilityFocus = AndroidSemanticsAction._(_kClearAccessibilityFocusIndex);
/// Matches `AccessibilityAction.ACTION_NEXT_AT_MOVEMENT_GRANULARITY`.
static const AndroidSemanticsAction nextAtMovementGranularity = AndroidSemanticsAction._(_kNextAtMovementGranularityIndex);
/// Matches `AccessibilityAction.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY`.
static const AndroidSemanticsAction previousAtMovementGranularity = AndroidSemanticsAction._(_kPreviousAtMovementGranularityIndex);
/// Matches `AccessibilityAction.ACTION_NEXT_HTML_ELEMENT`.
static const AndroidSemanticsAction nextHtmlElement = AndroidSemanticsAction._(_kNextHtmlElementIndex);
/// Matches `AccessibilityAction.ACTION_PREVIOUS_HTML_ELEMENT`.
static const AndroidSemanticsAction previousHtmlElement = AndroidSemanticsAction._(_kPreviousHtmlElementIndex);
/// Matches `AccessibilityAction.ACTION_SCROLL_FORWARD`.
static const AndroidSemanticsAction scrollForward = AndroidSemanticsAction._(_kScrollForwardIndex);
/// Matches `AccessibilityAction.ACTION_SCROLL_BACKWARD`.
static const AndroidSemanticsAction scrollBackward = AndroidSemanticsAction._(_kScrollBackwardIndex);
/// Matches `AccessibilityAction.ACTION_CUT`.
static const AndroidSemanticsAction cut = AndroidSemanticsAction._(_kCutIndex);
/// Matches `AccessibilityAction.ACTION_COPY`.
static const AndroidSemanticsAction copy = AndroidSemanticsAction._(_kCopyIndex);
/// Matches `AccessibilityAction.ACTION_PASTE`.
static const AndroidSemanticsAction paste = AndroidSemanticsAction._(_kPasteIndex);
/// Matches `AccessibilityAction.ACTION_SET_SELECTION`.
static const AndroidSemanticsAction setSelection = AndroidSemanticsAction._(_kSetSelectionIndex);
/// Matches `AccessibilityAction.ACTION_EXPAND`.
static const AndroidSemanticsAction expand = AndroidSemanticsAction._(_kExpandIndex);
/// Matches `AccessibilityAction.ACTION_COLLAPSE`.
static const AndroidSemanticsAction collapse = AndroidSemanticsAction._(_kCollapseIndex);
@override
String toString() {
switch (id) {
case _kFocusIndex:
return 'AndroidSemanticsAction.focus';
case _kClearFocusIndex:
return 'AndroidSemanticsAction.clearFocus';
case _kSelectIndex:
return 'AndroidSemanticsAction.select';
case _kClearSelectionIndex:
return 'AndroidSemanticsAction.clearSelection';
case _kClickIndex:
return 'AndroidSemanticsAction.click';
case _kLongClickIndex:
return 'AndroidSemanticsAction.longClick';
case _kAccessibilityFocusIndex:
return 'AndroidSemanticsAction.accessibilityFocus';
case _kClearAccessibilityFocusIndex:
return 'AndroidSemanticsAction.clearAccessibilityFocus';
case _kNextAtMovementGranularityIndex:
return 'AndroidSemanticsAction.nextAtMovementGranularity';
case _kPreviousAtMovementGranularityIndex:
return 'AndroidSemanticsAction.nextAtMovementGranularity';
case _kNextHtmlElementIndex:
return 'AndroidSemanticsAction.nextHtmlElement';
case _kPreviousHtmlElementIndex:
return 'AndroidSemanticsAction.previousHtmlElement';
case _kScrollForwardIndex:
return 'AndroidSemanticsAction.scrollForward';
case _kScrollBackwardIndex:
return 'AndroidSemanticsAction.scrollBackward';
case _kCutIndex:
return 'AndroidSemanticsAction.cut';
case _kCopyIndex:
return 'AndroidSemanticsAction.copy';
case _kPasteIndex:
return 'AndroidSemanticsAction.paste';
case _kSetSelectionIndex:
return 'AndroidSemanticsAction.setSelection';
case _kExpandIndex:
return 'AndroidSemanticsAction.expand';
case _kCollapseIndex:
return 'AndroidSemanticsAction.collapse';
default:
return null;
}
}
static const Map<int, AndroidSemanticsAction> _kactionById = <int, AndroidSemanticsAction>{
_kFocusIndex: focus,
_kClearFocusIndex: clearFocus,
_kSelectIndex: select,
_kClearSelectionIndex: clearSelection,
_kClickIndex: click,
_kLongClickIndex: longClick,
_kAccessibilityFocusIndex: accessibilityFocus,
_kClearAccessibilityFocusIndex: clearAccessibilityFocus,
_kNextAtMovementGranularityIndex: nextAtMovementGranularity,
_kPreviousAtMovementGranularityIndex: nextAtMovementGranularity,
_kNextHtmlElementIndex: nextHtmlElement,
_kPreviousHtmlElementIndex: previousHtmlElement,
_kScrollForwardIndex: scrollForward,
_kScrollBackwardIndex: scrollBackward,
_kCutIndex: cut,
_kCopyIndex: copy,
_kPasteIndex: paste,
_kSetSelectionIndex: setSelection,
_kExpandIndex: expand,
_kCollapseIndex: collapse,
};
@override
int get hashCode => id.hashCode;
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
final AndroidSemanticsAction typedOther = other;
return id == typedOther.id;
}
/// 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) {
return _kactionById[value];
}
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart';
import 'common.dart';
import 'constants.dart';
/// Matches an [AndroidSemanticsNode].
///
/// Any properties which aren't supplied are ignored during the comparison.
///
/// This matcher is intended to compare the accessibility values generated by
/// the Android accessibility bridge, and not the semantics object created by
/// the Flutter framework.
Matcher hasAndroidSemantics({
String text,
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 isPassword,
bool isLongClickable,
}) {
return new _AndroidSemanticsMatcher(
text: text,
className: className,
rect: rect,
size: size,
id: id,
actions: actions,
isChecked: isChecked,
isCheckable: isCheckable,
isEditable: isEditable,
isEnabled: isEnabled,
isFocusable: isFocusable,
isFocused: isFocused,
isPassword: isPassword,
isLongClickable: isLongClickable,
);
}
class _AndroidSemanticsMatcher extends Matcher {
_AndroidSemanticsMatcher({
this.text,
this.className,
this.id,
this.actions,
this.rect,
this.size,
this.isChecked,
this.isCheckable,
this.isEnabled,
this.isEditable,
this.isFocusable,
this.isFocused,
this.isPassword,
this.isLongClickable,
});
final String text;
final String className;
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 isPassword;
final bool isLongClickable;
@override
Description describe(Description description) {
description.add('AndroidSemanticsNode');
if (text != null)
description.add(' with text: $text');
if (className != null)
description.add(' with className: $className');
if (id != null)
description.add(' with id: $id');
if (actions != null)
description.add(' with actions: $actions');
if (rect != null)
description.add(' with rect: $rect');
if (size != null)
description.add(' with size: $size');
if (isChecked != null)
description.add(' with flag isChecked: $isChecked');
if (isEditable != null)
description.add(' with flag isEditable: $isEditable');
if (isEnabled != null)
description.add(' with flag isEnabled: $isEnabled');
if (isFocusable != null)
description.add(' with flag isFocusable: $isFocusable');
if (isFocused != null)
description.add(' with flag isFocused: $isFocused');
if (isPassword != null)
description.add(' with flag isPassword: $isPassword');
if (isLongClickable != null)
description.add(' with flag isLongClickable: $isLongClickable');
return description;
}
@override
bool matches(covariant AndroidSemanticsNode item, Map<Object, Object> matchState) {
if (text != null && text != item.text)
return _failWithMessage('Expected text: $text', matchState);
if (className != null && className != item.className)
return _failWithMessage('Expected className: $className', matchState);
if (id != null && id != item.id)
return _failWithMessage('Expected id: $id', matchState);
if (rect != null && rect != item.getRect())
return _failWithMessage('Expected rect: $rect', matchState);
if (size != null && size != item.getSize())
return _failWithMessage('Expected size: $size', matchState);
if (actions != null) {
final List<AndroidSemanticsAction> itemActions = item.getActions();
if (!unorderedEquals(actions).matches(itemActions, matchState))
return _failWithMessage('Expected actions: $actions', matchState);
}
if (isChecked != null && isChecked != item.isChecked)
return _failWithMessage('Expected isChecked: $isChecked', matchState);
if (isCheckable != null && isCheckable != item.isCheckable)
return _failWithMessage('Expected isCheckable: $isCheckable', matchState);
if (isEditable != null && isEditable != item.isEditable)
return _failWithMessage('Expected isEditable: $isEditable', matchState);
if (isEnabled != null && isEnabled != item.isEnabled)
return _failWithMessage('Expected isEnabled: $isEnabled', matchState);
if (isFocusable != null && isFocusable != item.isFocusable)
return _failWithMessage('Expected isFocusable: $isFocusable', matchState);
if (isFocused != null && isFocused != item.isFocused)
return _failWithMessage('Expected isFocused: $isFocused', matchState);
if (isPassword != null && isPassword != item.isPassword)
return _failWithMessage('Expected isPassword: $isPassword', matchState);
if (isLongClickable != null && isLongClickable != item.isLongClickable)
return _failWithMessage('Expected longClickable: $isLongClickable', matchState);
return true;
}
@override
Description describeMismatch(Object item, Description mismatchDescription,
Map<Object, Object> matchState, bool verbose) {
return mismatchDescription.add(matchState['failure']);
}
bool _failWithMessage(String value, Map<dynamic, dynamic> matchState) {
matchState['failure'] = value;
return false;
}
}
name: android_semantics_testing
description: Integration testing library for Android semantics
dependencies:
flutter:
sdk: flutter
flutter_driver:
sdk: flutter
async: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
collection: 1.14.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
convert: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
crypto: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
file: 5.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
intl: 0.15.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
json_rpc_2: 2.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
meta: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
path: 1.6.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
pub_semver: 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
source_span: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
stack_trace: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
stream_channel: 1.6.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
vm_service_client: 0.2.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
web_socket_channel: 1.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
# PUBSPEC CHECKSUM: d684
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:android_semantics_testing/android_semantics_testing.dart';
import 'package:flutter_test/flutter_test.dart';
// JSON matching a serialized Android AccessibilityNodeInfo.
const String source = r'''
{
"id": 23,
"flags": {
"isChecked": false,
"isCheckable": false,
"isEditable": false,
"isFocusable": false,
"isFocused": false,
"isPassword": false,
"isLongClickable": false
},
"text": "hello",
"className": "android.view.View",
"rect": {
"left": 0,
"top": 0,
"right": 10,
"bottom": 10
},
"actions": [1, 2, 4]
}
''';
void main() {
group(AndroidSemanticsNode, () {
test('can be parsed from json data', () {
final AndroidSemanticsNode node = AndroidSemanticsNode.deserialize(source);
expect(node.isChecked, false);
expect(node.isCheckable, false);
expect(node.isEditable, false);
expect(node.isFocusable, false);
expect(node.isFocused, false);
expect(node.isPassword, false);
expect(node.isLongClickable, false);
expect(node.text, 'hello');
expect(node.id, 23);
expect(node.getRect(), const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0));
expect(node.getActions(), <AndroidSemanticsAction>[
AndroidSemanticsAction.focus,
AndroidSemanticsAction.clearFocus,
AndroidSemanticsAction.select,
]);
expect(node.className, 'android.view.View');
expect(node.getSize(), const Size(10.0, 10.0));
});
});
group(AndroidSemanticsAction, () {
test('can be parsed from correct constant id', () {
expect(AndroidSemanticsAction.deserialize(0x1), AndroidSemanticsAction.focus);
});
test('returns null passed a bogus id', () {
expect(AndroidSemanticsAction.deserialize(23), isNull);
});
});
group('hasAndroidSemantics', () {
test('matches all android semantics properties', () {
final AndroidSemanticsNode node = AndroidSemanticsNode.deserialize(source);
expect(node, hasAndroidSemantics(
isChecked: false,
isCheckable: false,
isEditable: false,
isFocusable: false,
isFocused: false,
isPassword: false,
isLongClickable: false,
text: 'hello',
className: 'android.view.View',
id: 23,
rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
actions: <AndroidSemanticsAction>[
AndroidSemanticsAction.focus,
AndroidSemanticsAction.clearFocus,
AndroidSemanticsAction.select,
],
size: const Size(10.0, 10.0),
));
});
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment