Другой слой косвенности:
for i in $(eval echo {1..$END}); do
∶
public static Matcher<View> nthChildOf(final Matcher<View> parentMatcher, final int childPosition) {
return new TypeSafeMatcher<View>() {
@Override
public void describeTo(Description description) {
description.appendText("with "+childPosition+" child view of type parentMatcher");
}
@Override
public boolean matchesSafely(View view) {
if (!(view.getParent() instanceof ViewGroup)) {
return parentMatcher.matches(view.getParent());
}
ViewGroup group = (ViewGroup) view.getParent();
return parentMatcher.matches(view.getParent()) && group.getChildAt(childPosition).equals(view);
}
};
}
Чтобы использовать его
onView(nthChildOf(withId(R.id.parent_container), 0)).check(matches(withText("I am the first child")));
Чтобы попытаться немного улучшить решение Maragues, я внес несколько изменений.
Решение состоит в том, чтобы создать пользовательский Matcher < View > , который оборачивает другой Matcher < View > для родительского представления и принимает индекс дочернего представления для сопоставления.
public static Matcher<View> nthChildOf(final Matcher<View> parentMatcher, final int childPosition) {
return new TypeSafeMatcher<View>() {
@Override
public void describeTo(Description description) {
description.appendText("position " + childPosition + " of parent ");
parentMatcher.describeTo(description);
}
@Override
public boolean matchesSafely(View view) {
if (!(view.getParent() instanceof ViewGroup)) return false;
ViewGroup parent = (ViewGroup) view.getParent();
return parentMatcher.matches(parent)
&& parent.getChildCount() > childPosition
&& parent.getChildAt(childPosition).equals(view);
}
};
}
Подробное объяснение
Вы можете переопределить метод attributeTo , чтобы дать простое для понимания описание сопоставителя, добавив к Описание Аргумент. Вы также захотите передать вызов descriptionTo родительскому сопоставителю, чтобы его описание также было добавлено.
@Override
public void describeTo(Description description) {
description.appendText("position " + childPosition + " of parent "); // Add this matcher's description.
parentMatcher.describeTo(description); // Add the parentMatcher description.
}
Далее следует переопределить matchSafely , который будет определять, когда найдено совпадение в иерархии представлений. При вызове с представлением, чей родитель соответствует предоставленному сопоставлению родителей, убедитесь, что представление равно потомку в предоставленной позиции.
Если родитель не имеет childCount больше, чем дочерняя позиция, getChildAt вернет ноль и вызовет сбой теста. Лучше избегать сбоев и позволить тесту провалиться, чтобы мы получили надлежащий отчет о тестировании и сообщение об ошибке.
@Override
public boolean matchesSafely(View view) {
if (!(view.getParent() instanceof ViewGroup)) return false; // If it's not a ViewGroup we know it doesn't match.
ViewGroup parent = (ViewGroup) view.getParent();
return parentMatcher.matches(parent) // Check that the parent matches.
&& parent.getChildCount() > childPosition // Make sure there's enough children.
&& parent.getChildAt(childPosition).equals(view); // Check that this is the right child.
}
Если вы можете получить родительский вид. Может быть, эта ссылка , в которой определено совпадение для получения первого потомка представления, может дать вам некоторую подсказку.
public static Matcher<View> firstChildOf(final Matcher<View> parentMatcher) {
return new TypeSafeMatcher<View>() {
@Override
public void describeTo(Description description) {
description.appendText("with first child view of type parentMatcher");
}
@Override
public boolean matchesSafely(View view) {
if (!(view.getParent() instanceof ViewGroup)) {
return parentMatcher.matches(view.getParent());
}
ViewGroup group = (ViewGroup) view.getParent();
return parentMatcher.matches(view.getParent()) && group.getChildAt(0).equals(view);
}
};
}