Changed Robolectric's ShadowAccessibilityNodeInfo.equals() test to use a unique source ID.
This is simpler and more efficient, and more accurately mimics the behavior of AccessibilityNodeInfo.equals() which matches instances based upon their origins.
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityNodeInfoTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityNodeInfoTest.java
index 487fd6e..2b42469 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityNodeInfoTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityNodeInfoTest.java
@@ -2,12 +2,12 @@
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.assertEquals;
import static org.robolectric.Shadows.shadowOf;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcel;
+import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.AccessibilityWindowInfo;
@@ -16,6 +16,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
@@ -45,12 +46,13 @@
}
@Test
- public void ShouldHaveClonedCorrectly() {
+ public void shouldHaveClonedCorrectly() {
node.setAccessibilityFocused(true);
node.setBoundsInParent(new Rect(0, 0, 100, 100));
node.setContentDescription("test");
AccessibilityNodeInfo anotherNode = AccessibilityNodeInfo.obtain(node);
- assertEquals(node, anotherNode);
+ assertThat(anotherNode).isEqualTo(node);
+ assertThat(anotherNode.getContentDescription().toString()).isEqualTo("test");
}
@Test
@@ -71,7 +73,7 @@
shadowOf(node).addChild(child);
shadowOf(child).addChild(node);
AccessibilityNodeInfo anotherNode = AccessibilityNodeInfo.obtain(node);
- assertThat(node.equals(anotherNode)).isEqualTo(true);
+ assertThat(node).isEqualTo(anotherNode);
}
@Test
@@ -82,14 +84,14 @@
shadow.addChild(child1);
ShadowAccessibilityNodeInfo child1Shadow = shadowOf(child1);
child1Shadow.addChild(node);
- AccessibilityNodeInfo anotherNode = ShadowAccessibilityNodeInfo.obtain(node);
+ AccessibilityNodeInfo anotherNode = ShadowAccessibilityNodeInfo.obtain();
AccessibilityNodeInfo child2 = ShadowAccessibilityNodeInfo.obtain();
child2.setText("test");
ShadowAccessibilityNodeInfo child2Shadow = shadowOf(child2);
ShadowAccessibilityNodeInfo anotherNodeShadow = shadowOf(anotherNode);
anotherNodeShadow.addChild(child2);
child2Shadow.addChild(anotherNode);
- assertThat(node.equals(anotherNode)).isEqualTo(false);
+ assertThat(node).isNotEqualTo(anotherNode);
}
@Test
@@ -156,28 +158,45 @@
}
@Test
- public void equalsTest_avoidsNullPointerDuringParentComparison() {
- AccessibilityNodeInfo grandparentInfo = AccessibilityNodeInfo.obtain();
- AccessibilityNodeInfo childInfo = AccessibilityNodeInfo.obtain();
- AccessibilityNodeInfo parentInfo = AccessibilityNodeInfo.obtain();
- shadowOf(grandparentInfo).addChild(parentInfo);
- shadowOf(parentInfo).addChild(childInfo);
+ public void equalsTest_unrelatedNodesAreUnequal() {
+ AccessibilityNodeInfo nodeA = AccessibilityNodeInfo.obtain();
+ AccessibilityNodeInfo nodeB = AccessibilityNodeInfo.obtain();
+ shadowOf(nodeA).setText("test");
+ shadowOf(nodeB).setText("test");
- assertThat(parentInfo.equals(childInfo)).isFalse();
- assertThat(childInfo.equals(parentInfo)).isFalse();
- assertThat(grandparentInfo.equals(parentInfo)).isFalse();
- assertThat(parentInfo.equals(grandparentInfo)).isFalse();
+ assertThat(nodeA).isNotEqualTo(nodeB);
}
@Test
- public void equalsTest_avoidsNullPointerDuringChildrenComparison() {
- node.setVisibleToUser(true);
- AccessibilityNodeInfo child1 = AccessibilityNodeInfo.obtain();
- AccessibilityNodeInfo child2 = AccessibilityNodeInfo.obtain();
- shadowOf(node).addChild(child1);
- shadowOf(node).addChild(child2);
+ public void equalsTest_nodesFromTheSameViewAreEqual() {
+ View view = new View(RuntimeEnvironment.application);
+ AccessibilityNodeInfo nodeA = AccessibilityNodeInfo.obtain(view);
+ AccessibilityNodeInfo nodeB = AccessibilityNodeInfo.obtain(view);
+ shadowOf(nodeA).setText("tomato");
+ shadowOf(nodeB).setText("tomatoe");
- assertThat(node).isEqualTo(node);
+ assertThat(nodeA).isEqualTo(nodeB);
+ }
+
+ @Test
+ public void equalsTest_nodesFromDifferentViewsAreNotEqual() {
+ View viewA = new View(RuntimeEnvironment.application);
+ View viewB = new View(RuntimeEnvironment.application);
+ AccessibilityNodeInfo nodeA = AccessibilityNodeInfo.obtain(viewA);
+ AccessibilityNodeInfo nodeB = AccessibilityNodeInfo.obtain(viewB);
+ shadowOf(nodeA).setText("test");
+ shadowOf(nodeB).setText("test");
+
+ assertThat(nodeA).isNotEqualTo(nodeB);
+ }
+
+ @Test
+ public void equalsTest_nodeIsEqualToItsClone_evenWhenModified() {
+ node = AccessibilityNodeInfo.obtain();
+ AccessibilityNodeInfo clone = AccessibilityNodeInfo.obtain(node);
+ shadowOf(clone).setText("test");
+
+ assertThat(node).isEqualTo(clone);
}
@After
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityNodeInfo.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityNodeInfo.java
index e5f3ce1..82b2d7f 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityNodeInfo.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityNodeInfo.java
@@ -11,7 +11,6 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import android.text.TextUtils;
import android.util.Pair;
import android.util.SparseArray;
import android.view.View;
@@ -104,6 +103,13 @@
private static final int CAN_OPEN_POPUP_MASK = 0x00100000; //19
+ /**
+ * Uniquely identifies the origin of the AccessibilityNodeInfo for equality
+ * testing. Two instances that come from the same node info should have the
+ * same ID.
+ */
+ private long mOriginNodeId;
+
private List<AccessibilityNodeInfo> children;
private Rect boundsInScreen = new Rect();
@@ -167,8 +173,6 @@
private AccessibilityNodeInfo traversalBefore; //22
private OnPerformActionListener actionListener;
-
- private boolean visitedWhenCheckingChildren = false;
@RealObject
private AccessibilityNodeInfo realAccessibilityNodeInfo;
@@ -184,6 +188,9 @@
final AccessibilityNodeInfo obtainedInstance = shadowInfo.getClone();
sAllocationCount++;
+ if (shadowInfo.mOriginNodeId == 0) {
+ shadowInfo.mOriginNodeId = sAllocationCount;
+ }
StrictEqualityNodeWrapper wrapper = new StrictEqualityNodeWrapper(obtainedInstance);
obtainedInstances.put(wrapper, Thread.currentThread().getStackTrace());
orderedInstances.put(sAllocationCount, wrapper);
@@ -212,6 +219,9 @@
shadowObtained.view = view;
sAllocationCount++;
+ if (shadowObtained.mOriginNodeId == 0) {
+ shadowObtained.mOriginNodeId = sAllocationCount;
+ }
StrictEqualityNodeWrapper wrapper = new StrictEqualityNodeWrapper(obtainedInstance);
obtainedInstances.put(wrapper, Thread.currentThread().getStackTrace());
orderedInstances.put(sAllocationCount, wrapper);
@@ -924,35 +934,9 @@
return actionListener == null || actionListener.onPerformAccessibilityAction(action, arguments);
}
- private boolean childrenEqualityCheck(
- ShadowAccessibilityNodeInfo otherShadow,
- LinkedList<ShadowAccessibilityNodeInfo> visitedNodes) {
- if (children == null) {
- return otherShadow.getChildCount() == 0;
- } else if (getChildCount() != otherShadow.getChildCount()) {
- return false;
- }
- boolean childrenEquality = true;
- for (int i = 0; i < children.size(); i++) {
- AccessibilityNodeInfo child = children.get(i);
- ShadowAccessibilityNodeInfo childShadow = shadowOf(child);
- if (!childShadow.visitedWhenCheckingChildren) {
- AccessibilityNodeInfo otherChild = otherShadow.children.get(i);
- visitedNodes.add(childShadow);
- childShadow.visitedWhenCheckingChildren = true;
- if (!child.equals(otherChild)) {
- childrenEquality = false;
- break;
- }
- childrenEquality = childShadow.childrenEqualityCheck(shadowOf(otherChild), visitedNodes);
- }
- }
- return childrenEquality;
- }
-
/**
- * Equality check based on reference equality for mParent and mView and
- * value equality for other fields.
+ * Equality check based on reference equality of the Views from which these instances were
+ * created, or the equality of their assigned IDs.
*/
@Implementation
@Override
@@ -964,119 +948,13 @@
final AccessibilityNodeInfo info = (AccessibilityNodeInfo) object;
final ShadowAccessibilityNodeInfo otherShadow = shadowOf(info);
- boolean areEqual = true;
- if (children == null) {
- areEqual &= (otherShadow.children == null);
- } else {
- LinkedList<ShadowAccessibilityNodeInfo> visitedNodes = new LinkedList<>();
- areEqual &=
- (otherShadow.children != null) && childrenEqualityCheck(otherShadow, visitedNodes);
- if (parent == null) {
- areEqual &= (otherShadow.parent == null);
- } else if (!shadowOf(parent).visitedWhenCheckingChildren){
- areEqual &=
- ((otherShadow.parent != null) && shadowOf(parent).equals(shadowOf(otherShadow.parent)));
- }
-
- while (!visitedNodes.isEmpty()) {
- ShadowAccessibilityNodeInfo visitedNode = visitedNodes.remove();
- visitedNode.visitedWhenCheckingChildren = false;
- }
+ if (this.view != null) {
+ return this.view == otherShadow.view;
}
- areEqual &= (propertyFlags == otherShadow.propertyFlags);
-
- if (getApiLevel() >= LOLLIPOP) {
- boolean actionsArrayEquality = false;
- if (actionsArray == null && otherShadow.actionsArray == null) {
- actionsArrayEquality = true;
- } else if (actionsArray == null || otherShadow.actionsArray == null) {
- actionsArrayEquality = false;
- } else {
- actionsArrayEquality = actionsArray.equals(otherShadow.actionsArray);
- }
- areEqual &= actionsArrayEquality;
- if (accessibilityWindowInfo == null) {
- areEqual &= (otherShadow.accessibilityWindowInfo == null);
- } else {
- areEqual &= (otherShadow.accessibilityWindowInfo != null)
- && accessibilityWindowInfo.equals(otherShadow.accessibilityWindowInfo);
- }
- } else {
- areEqual &= (actionsMask == otherShadow.actionsMask);
+ if (this.mOriginNodeId != 0) {
+ return this.mOriginNodeId == otherShadow.mOriginNodeId;
}
-
- /*
- * These checks have the potential to become infinite loops if there are
- * loops in the labelFor or labeledBy logic. Rather than deal with this
- * complexity, allow the failure since it will indicate a problem that
- * needs addressing.
- */
- if (labelFor == null) {
- areEqual &= (otherShadow.labelFor == null);
- } else {
- areEqual &= (labelFor.equals(otherShadow.labelFor));
- }
-
- if (labeledBy == null) {
- areEqual &= (otherShadow.labeledBy == null);
- } else {
- areEqual &= (labeledBy.equals(otherShadow.labeledBy));
- }
-
- areEqual &= boundsInScreen.equals(otherShadow.boundsInScreen);
- areEqual &= (TextUtils.equals(contentDescription, otherShadow.contentDescription));
- areEqual &= (TextUtils.equals(text, otherShadow.text));
-
- areEqual &= TextUtils.equals(className, otherShadow.className);
- areEqual &= (view == otherShadow.view);
- areEqual &= (textSelectionStart == otherShadow.textSelectionStart);
- areEqual &= (textSelectionEnd == otherShadow.textSelectionEnd);
-
- areEqual &= (refreshReturnValue == otherShadow.refreshReturnValue);
- areEqual &= (movementGranularities == otherShadow.movementGranularities);
- areEqual &= (TextUtils.isEmpty(packageName) == TextUtils.isEmpty(otherShadow.packageName));
- if (!TextUtils.isEmpty(packageName)) {
- areEqual &= (packageName.toString().equals(otherShadow.packageName.toString()));
- }
- if (getApiLevel() >= JELLY_BEAN_MR2) {
- areEqual &= TextUtils.equals(viewIdResourceName, otherShadow.viewIdResourceName);
- }
- if (getApiLevel() >= KITKAT) {
- if (collectionInfo == null) {
- areEqual &= (otherShadow.collectionInfo == null);
- } else {
- areEqual &= (collectionInfo.equals(otherShadow.collectionInfo));
- }
- if (collectionItemInfo == null) {
- areEqual &= (otherShadow.collectionItemInfo == null);
- } else {
- areEqual &= (collectionItemInfo.equals(otherShadow.collectionItemInfo));
- }
- areEqual &= (inputType == otherShadow.inputType);
- areEqual &= (liveRegion == otherShadow.liveRegion);
- if (rangeInfo == null) {
- areEqual &= (otherShadow.rangeInfo == null);
- } else {
- areEqual &= (rangeInfo.equals(otherShadow.rangeInfo));
- }
- }
- if (getApiLevel() >= LOLLIPOP) {
- areEqual &= (maxTextLength == otherShadow.maxTextLength);
- areEqual &= TextUtils.equals(error, otherShadow.error);
- }
- if (getApiLevel() >= LOLLIPOP_MR1) {
- if (traversalAfter == null) {
- areEqual &= (otherShadow.traversalAfter == null);
- } else {
- areEqual &= (traversalAfter.equals(otherShadow.traversalAfter));
- }
- if (traversalBefore == null) {
- areEqual &= (otherShadow.traversalBefore == null);
- } else {
- areEqual &= (traversalBefore.equals(otherShadow.traversalBefore));
- }
- }
- return areEqual;
+ throw new IllegalStateException("Node has neither an ID nor View");
}
@Implementation
@@ -1155,6 +1033,7 @@
ReflectionHelpers.callConstructor(AccessibilityNodeInfo.class);
final ShadowAccessibilityNodeInfo newShadow = shadowOf(newInfo);
+ newShadow.mOriginNodeId = mOriginNodeId;
newShadow.boundsInScreen = new Rect(boundsInScreen);
newShadow.propertyFlags = propertyFlags;
newShadow.contentDescription = contentDescription;
@@ -1162,6 +1041,7 @@
newShadow.performedActionAndArgsList = performedActionAndArgsList;
newShadow.parent = parent;
newShadow.className = className;
+ newShadow.labelFor = labelFor;
newShadow.labeledBy = labeledBy;
newShadow.view = view;
newShadow.textSelectionStart = textSelectionStart;