| // Copyright 2014 The Flutter 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 'common.dart'; |
| import 'constants.dart'; |
| import 'flutter_test_alternative.dart'; |
| |
| /// Matches an [AndroidSemanticsNode]. |
| /// |
| /// Any properties which aren't supplied are ignored during the comparison, |
| /// with the exception of `isHeading`. The heading property is not available |
| /// on all versions of Android. If it is not available on the tested version, |
| /// it will be match whatever it is compared against. |
| /// |
| /// 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 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, |
| contentDescription: contentDescription, |
| className: className, |
| rect: rect, |
| size: size, |
| id: id, |
| actions: actions, |
| isChecked: isChecked, |
| isCheckable: isCheckable, |
| isEditable: isEditable, |
| isEnabled: isEnabled, |
| isFocusable: isFocusable, |
| isFocused: isFocused, |
| isHeading: isHeading, |
| isPassword: isPassword, |
| isLongClickable: isLongClickable, |
| ); |
| } |
| |
| class _AndroidSemanticsMatcher extends Matcher { |
| _AndroidSemanticsMatcher({ |
| this.text, |
| this.contentDescription, |
| this.className, |
| this.id, |
| this.actions, |
| this.rect, |
| this.size, |
| this.isChecked, |
| this.isCheckable, |
| this.isEnabled, |
| this.isEditable, |
| this.isFocusable, |
| this.isFocused, |
| this.isHeading, |
| this.isPassword, |
| 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; |
| |
| @override |
| Description describe(Description description) { |
| description.add('AndroidSemanticsNode'); |
| if (text != null) |
| description.add(' with text: $text'); |
| if (contentDescription != null) |
| description.add( 'with contentDescription $contentDescription'); |
| 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 (isHeading != null) |
| description.add(' with flag isHeading: $isHeading'); |
| 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 (contentDescription != null && contentDescription != item.contentDescription) |
| return _failWithMessage('Expected contentDescription: $contentDescription', 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)) { |
| 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()); |
| return _failWithMessage('Expected actions: $actionsString\nActual actions: $itemActionsString\nUnexpected: $unexpected\nMissing: $missing', 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); |
| // Heading is not available in all Android versions, so match anything if it is not set by the platform |
| if (isHeading != null && isHeading != item.isHeading && item.isHeading != null) |
| return _failWithMessage('Expected isHeading: $isHeading', 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'] as String); |
| } |
| |
| bool _failWithMessage(String value, Map<dynamic, dynamic> matchState) { |
| matchState['failure'] = value; |
| return false; |
| } |
| } |