// 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; } }