blob: d7f9023db24485d8bc5a1a4d1d2e797b12846da2 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.appcompat.testutils;
import android.database.sqlite.SQLiteCursor;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.support.test.espresso.matcher.BoundedMatcher;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckedTextView;
import android.widget.ImageView;
import androidx.annotation.ColorInt;
import androidx.core.view.TintableBackgroundView;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import java.util.List;
public class TestUtilsMatchers {
/**
* Returns a matcher that matches <code>ImageView</code>s which have drawable flat-filled
* with the specific color.
*/
public static Matcher drawable(@ColorInt final int color) {
return new BoundedMatcher<View, ImageView>(ImageView.class) {
private String failedComparisonDescription;
@Override
public void describeTo(final Description description) {
description.appendText("with drawable of color: ");
description.appendText(failedComparisonDescription);
}
@Override
public boolean matchesSafely(final ImageView view) {
Drawable drawable = view.getDrawable();
if (drawable == null) {
return false;
}
// One option is to check if we have a ColorDrawable and then call getColor
// but that API is v11+. Instead, we call our helper method that checks whether
// all pixels in a Drawable are of the same specified color.
try {
TestUtils.assertAllPixelsOfColor("", drawable, view.getWidth(),
view.getHeight(), true, color, 0, true);
// If we are here, the color comparison has passed.
failedComparisonDescription = null;
return true;
} catch (Throwable t) {
// If we are here, the color comparison has failed.
failedComparisonDescription = t.getMessage();
return false;
}
}
};
}
/**
* Returns a matcher that matches <code>View</code>s which have background flat-filled
* with the specific color.
*/
public static Matcher isBackground(@ColorInt final int color) {
return isBackground(color, false);
}
/**
* Returns a matcher that matches <code>View</code>s which have background flat-filled
* with the specific color.
*/
public static Matcher isBackground(@ColorInt final int color, final boolean onlyTestCenter) {
return new BoundedMatcher<View, View>(View.class) {
private String failedComparisonDescription;
@Override
public void describeTo(final Description description) {
description.appendText("with background of color: ");
description.appendText(failedComparisonDescription);
}
@Override
public boolean matchesSafely(final View view) {
Drawable drawable = view.getBackground();
if (drawable == null) {
return false;
}
try {
if (onlyTestCenter) {
TestUtils.assertCenterPixelOfColor("", drawable, view.getWidth(),
view.getHeight(), false, color, 0, true);
} else {
TestUtils.assertAllPixelsOfColor("", drawable, view.getWidth(),
view.getHeight(), false, color, 0, true);
}
// If we are here, the color comparison has passed.
failedComparisonDescription = null;
return true;
} catch (Throwable t) {
// If we are here, the color comparison has failed.
failedComparisonDescription = t.getMessage();
return false;
}
}
};
}
/**
* Returns a matcher that matches <code>View</code>s whose combined background starting
* from the view and up its ancestor chain matches the specified color.
*/
public static Matcher isCombinedBackground(@ColorInt final int color,
final boolean onlyTestCenterPixel) {
return new BoundedMatcher<View, View>(View.class) {
private String failedComparisonDescription;
@Override
public void describeTo(final Description description) {
description.appendText("with ascendant background of color: ");
description.appendText(failedComparisonDescription);
}
@Override
public boolean matchesSafely(View view) {
// Create a bitmap with combined backgrounds of the view and its ancestors.
Bitmap combinedBackgroundBitmap = TestUtils.getCombinedBackgroundBitmap(view);
try {
if (onlyTestCenterPixel) {
TestUtils.assertCenterPixelOfColor("", combinedBackgroundBitmap,
color, 0, true);
} else {
TestUtils.assertAllPixelsOfColor("", combinedBackgroundBitmap,
combinedBackgroundBitmap.getWidth(),
combinedBackgroundBitmap.getHeight(), color, 0, true);
}
// If we are here, the color comparison has passed.
failedComparisonDescription = null;
return true;
} catch (Throwable t) {
failedComparisonDescription = t.getMessage();
return false;
} finally {
combinedBackgroundBitmap.recycle();
}
}
};
}
/**
* Returns a matcher that matches <code>CheckedTextView</code>s which are in checked state.
*/
public static Matcher isCheckedTextView() {
return new BoundedMatcher<View, CheckedTextView>(CheckedTextView.class) {
private String failedDescription;
@Override
public void describeTo(final Description description) {
description.appendText("checked text view: ");
description.appendText(failedDescription);
}
@Override
public boolean matchesSafely(final CheckedTextView view) {
if (view.isChecked()) {
return true;
}
failedDescription = "not checked";
return false;
}
};
}
/**
* Returns a matcher that matches <code>CheckedTextView</code>s which are in checked state.
*/
public static Matcher isNonCheckedTextView() {
return new BoundedMatcher<View, CheckedTextView>(CheckedTextView.class) {
private String failedDescription;
@Override
public void describeTo(final Description description) {
description.appendText("non checked text view: ");
description.appendText(failedDescription);
}
@Override
public boolean matchesSafely(final CheckedTextView view) {
if (!view.isChecked()) {
return true;
}
failedDescription = "checked";
return false;
}
};
}
/**
* Returns a matcher that matches data entry in <code>SQLiteCursor</code> that has
* the specified text in the specified column.
*/
public static Matcher<Object> withCursorItemContent(final String columnName,
final String expectedText) {
return new BoundedMatcher<Object, SQLiteCursor>(SQLiteCursor.class) {
@Override
public void describeTo(Description description) {
description.appendText("doesn't match " + expectedText);
}
@Override
protected boolean matchesSafely(SQLiteCursor cursor) {
return TextUtils.equals(expectedText,
cursor.getString(cursor.getColumnIndex(columnName)));
}
};
}
/**
* Returns a matcher that matches Views which implement TintableBackgroundView.
*/
public static Matcher<View> isTintableBackgroundView() {
return new TypeSafeMatcher<View>() {
@Override
public void describeTo(Description description) {
description.appendText("is TintableBackgroundView");
}
@Override
public boolean matchesSafely(View view) {
return TintableBackgroundView.class.isAssignableFrom(view.getClass());
}
};
}
/**
* Returns a matcher that matches lists of float values that fall into the specified range.
*/
public static Matcher<List<Float>> inRange(final float from, final float to) {
return new TypeSafeMatcher<List<Float>>() {
private String mFailedDescription;
@Override
public void describeTo(Description description) {
description.appendText(mFailedDescription);
}
@Override
protected boolean matchesSafely(List<Float> item) {
int itemCount = item.size();
for (int i = 0; i < itemCount; i++) {
float curr = item.get(i);
if ((curr < from) || (curr > to)) {
mFailedDescription = "Value #" + i + ":" + curr + " should be between " +
from + " and " + to;
return false;
}
}
return true;
}
};
}
/**
* Returns a matcher that matches lists of float values that are in ascending order.
*/
public static Matcher<List<Float>> inAscendingOrder() {
return new TypeSafeMatcher<List<Float>>() {
private String mFailedDescription;
@Override
public void describeTo(Description description) {
description.appendText(mFailedDescription);
}
@Override
protected boolean matchesSafely(List<Float> item) {
int itemCount = item.size();
if (itemCount >= 2) {
for (int i = 0; i < itemCount - 1; i++) {
float curr = item.get(i);
float next = item.get(i + 1);
if (curr > next) {
mFailedDescription = "Values should increase between #" + i +
":" + curr + " and #" + (i + 1) + ":" + next;
return false;
}
}
}
return true;
}
};
}
/**
* Returns a matcher that matches lists of float values that are in descending order.
*/
public static Matcher<List<Float>> inDescendingOrder() {
return new TypeSafeMatcher<List<Float>>() {
private String mFailedDescription;
@Override
public void describeTo(Description description) {
description.appendText(mFailedDescription);
}
@Override
protected boolean matchesSafely(List<Float> item) {
int itemCount = item.size();
if (itemCount >= 2) {
for (int i = 0; i < itemCount - 1; i++) {
float curr = item.get(i);
float next = item.get(i + 1);
if (curr < next) {
mFailedDescription = "Values should decrease between #" + i +
":" + curr + " and #" + (i + 1) + ":" + next;
return false;
}
}
}
return true;
}
};
}
/**
* Returns a matcher that matches {@link View}s based on the given child type.
*
* @param childMatcher the type of the child to match on
*/
public static Matcher<ViewGroup> hasChild(final Matcher<View> childMatcher) {
return new TypeSafeMatcher<ViewGroup>() {
@Override
public void describeTo(Description description) {
description.appendText("has child: ");
childMatcher.describeTo(description);
}
@Override
public boolean matchesSafely(ViewGroup view) {
final int childCount = view.getChildCount();
for (int i = 0; i < childCount; i++) {
if (childMatcher.matches(view.getChildAt(i))) {
return true;
}
}
return false;
}
};
}
}