/* * Copyright (C) 2014 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.test.espresso.matcher; import static androidx.test.internal.util.Checks.checkArgument; import static androidx.test.internal.util.Checks.checkNotNull; import static org.hamcrest.Matchers.is; import android.database.Cursor; import android.database.CursorIndexOutOfBoundsException; import android.widget.AdapterView; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.SelfDescribing; import org.hamcrest.StringDescription; /** * A collection of Hamcrest matchers that matches a data row in a {@link Cursor}.
* *

{@link AdapterView}s that are backed by a {@link Cursor} are very common. This class contains * Matcher * s that can be used together with {@link * androidx.test.espresso.Espresso#onData(Matcher)} to match a data row in a {@link Cursor}. * The * Matchers can only operate on a single data row of the cursor and Espresso internally * moves the {@link Cursor} to the correct adapter position. */ public final class CursorMatchers { private static final int COLUMN_NOT_FOUND = -1; private static final int MULTIPLE_COLUMNS_FOUND = -2; private static final int USE_COLUMN_PICKER = -3; private CursorMatchers() { // no instance } /** A {@link Matcher} that matches {@link Cursor}s based on values in their columns. */ @SuppressWarnings("Extended from BoundedMatcher to be binary compatible") public static class CursorMatcher extends BoundedMatcher { private final int columnIndex; private final Matcher columnNameMatcher; private final Matcher valueMatcher; private final CursorDataRetriever cursorDataRetriever; private boolean checkColumns = false; private CursorMatcher( int columnIndex, Matcher valueMatcher, CursorDataRetriever cursorDataRetriever) { super(Cursor.class); checkArgument(columnIndex >= 0); this.columnIndex = columnIndex; this.valueMatcher = checkNotNull(valueMatcher); this.cursorDataRetriever = checkNotNull(cursorDataRetriever); this.columnNameMatcher = null; } private CursorMatcher( Matcher columnPicker, Matcher valueMatcher, CursorDataRetriever cursorDataRetriever) { super(Cursor.class); this.columnNameMatcher = checkNotNull(columnPicker); this.valueMatcher = checkNotNull(valueMatcher); this.cursorDataRetriever = checkNotNull(cursorDataRetriever); this.columnIndex = USE_COLUMN_PICKER; } @Override public boolean matchesSafely(Cursor cursor) { int chosenColumn = columnIndex; StringDescription mismatchDescription = new StringDescription(); if (chosenColumn < 0) { chosenColumn = findColumnIndex(columnNameMatcher, cursor); if (chosenColumn < 0) { if (chosenColumn == MULTIPLE_COLUMNS_FOUND) { mismatchDescription .appendText("Multiple columns in ") .appendValue(cursor.getColumnNames()) .appendText(" match ") .appendDescriptionOf(columnNameMatcher); } else { mismatchDescription .appendText("Couldn't find column in ") .appendValue(cursor.getColumnNames()) .appendText(" matching ") .appendDescriptionOf(columnNameMatcher); } if (checkColumns) { throw new IllegalArgumentException(mismatchDescription.toString()); } return false; } } try { Object data = cursorDataRetriever.getData(cursor, chosenColumn); boolean result = valueMatcher.matches(data); if (!result) { mismatchDescription .appendText("value at column ") .appendValue(chosenColumn) .appendText(" "); valueMatcher.describeMismatch(data, mismatchDescription); } return result; } catch (CursorIndexOutOfBoundsException e) { mismatchDescription .appendText("Column index ") .appendValue(chosenColumn) .appendText(" is invalid"); if (checkColumns) { throw new IllegalArgumentException(mismatchDescription.toString(), e); } // this cursor position doesn't have a column with this index, but other ones might // (e.g. in case of MergeCursor), so if columnChecks are off continue the search. return false; } } @Override public void describeTo(Description description) { description.appendText("an instance of android.database.Cursor and Rows with column: "); if (columnIndex < 0) { columnNameMatcher.describeTo(description); } else { description.appendText("index = " + columnIndex); } description .appendText(" ") .appendDescriptionOf(cursorDataRetriever) .appendText(" matching ") .appendDescriptionOf(valueMatcher); } /** * Allows test authors to override whether the the matcher should throw an {@link * IllegalArgumentException} if the column name/count is not valid. This is useful in the case * where a cursor may iterates over a data set with variable columns. By default, the checks are * off. */ public CursorMatcher withStrictColumnChecks(boolean checkColumns) { this.checkColumns = checkColumns; return this; } } private static int findColumnIndex(Matcher nameMatcher, Cursor cursor) { int result = COLUMN_NOT_FOUND; String[] columnNames = cursor.getColumnNames(); for (int i = 0; i < columnNames.length; i++) { String column = columnNames[i]; if (nameMatcher.matches(column)) { if (result == COLUMN_NOT_FOUND) { result = i; } else { result = MULTIPLE_COLUMNS_FOUND; break; } } } return result; } private interface CursorDataRetriever extends SelfDescribing { T getData(Cursor cursor, int chosenColumn); } private static final CursorDataRetriever BLOB_MATCHER_APPLIER = new CursorDataRetriever() { @Override public void describeTo(Description description) { description.appendText("with Blob"); } @Override public byte[] getData(Cursor cursor, int chosenColumn) { return cursor.getBlob(chosenColumn); } }; private static final CursorDataRetriever LONG_MATCHER_APPLIER = new CursorDataRetriever() { @Override public void describeTo(Description description) { description.appendText("with Long"); } @Override public Long getData(Cursor cursor, int chosenColumn) { return cursor.getLong(chosenColumn); } }; private static final CursorDataRetriever SHORT_MATCHER_APPLIER = new CursorDataRetriever() { @Override public void describeTo(Description description) { description.appendText("with Short"); } @Override public Short getData(Cursor cursor, int chosenColumn) { return cursor.getShort(chosenColumn); } }; private static final CursorDataRetriever INT_MATCHER_APPLIER = new CursorDataRetriever() { @Override public void describeTo(Description description) { description.appendText("with Int"); } @Override public Integer getData(Cursor cursor, int chosenColumn) { return cursor.getInt(chosenColumn); } }; private static final CursorDataRetriever FLOAT_MATCHER_APPLIER = new CursorDataRetriever() { @Override public void describeTo(Description description) { description.appendText("with Float"); } @Override public Float getData(Cursor cursor, int chosenColumn) { return cursor.getFloat(chosenColumn); } }; private static final CursorDataRetriever DOUBLE_MATCHER_APPLIER = new CursorDataRetriever() { @Override public void describeTo(Description description) { description.appendText("with Double"); } @Override public Double getData(Cursor cursor, int chosenColumn) { return cursor.getDouble(chosenColumn); } }; private static final CursorDataRetriever STRING_MATCHER_APPLIER = new CursorDataRetriever() { @Override public void describeTo(Description description) { description.appendText("with String"); } @Override public String getData(Cursor cursor, int chosenColumn) { return cursor.getString(chosenColumn); } }; /** * Returns a matcher that matches a {@link Short} value at a given column index in a {@link * Cursor}s data row.
* * @param columnIndex int column index * @param value a short value to match */ public static CursorMatcher withRowShort(int columnIndex, short value) { return withRowShort(columnIndex, is(value)); } /** * Returns a matcher that matches a {@link Short} value at a given column index in a {@link * Cursor}s data row.
* * @param columnIndex int column index * @param valueMatcher a Matcher * that matches a {@link Short} value */ public static CursorMatcher withRowShort(int columnIndex, Matcher valueMatcher) { return new CursorMatcher(columnIndex, valueMatcher, SHORT_MATCHER_APPLIER); } /** * Returns a matcher that matches a {@link Short} value at a given column index in a {@link * Cursor}s data row.
* * @param columnName as a {@link String} * @param value a short value to match */ public static CursorMatcher withRowShort(String columnName, short value) { return withRowShort(columnName, is(value)); } /** * Returns a matcher that matches a {@link Short} value at a given column index in a {@link * Cursor}s data row.
* * @param columnName as a {@link String} * @param valueMatcher a Matcher * that matches a {@link Short} value */ public static CursorMatcher withRowShort(String columnName, Matcher valueMatcher) { return withRowShort(is(columnName), valueMatcher); } /** * Returns a matcher that matches a {@link Short} value at a given column index in a {@link * Cursor}s data row.
* * @param columnNameMatcher as a Matcher * of {@link String} * @param valueMatcher a Matcher * that matches a {@link Short} value */ public static CursorMatcher withRowShort( Matcher columnNameMatcher, Matcher valueMatcher) { return new CursorMatcher(columnNameMatcher, valueMatcher, SHORT_MATCHER_APPLIER); } /** * Returns a matcher that matches a {@link Integer} value at a given column index in a {@link * Cursor}s data row.
* * @param columnIndex int column index * @param value a int value to match */ public static CursorMatcher withRowInt(int columnIndex, int value) { return withRowInt(columnIndex, is(value)); } /** * Returns a matcher that matches a {@link Integer} value at a given column index in a {@link * Cursor}s data row.
* * @param columnIndex int column index * @param valueMatcher a Matcher * that matches a {@link Integer} value */ public static CursorMatcher withRowInt(int columnIndex, Matcher valueMatcher) { return new CursorMatcher(columnIndex, valueMatcher, INT_MATCHER_APPLIER); } /** * Returns a matcher that matches a {@link Integer} value at a given column index in a {@link * Cursor}s data row.
* * @param columnName as a {@link String} * @param value a int value to match */ public static CursorMatcher withRowInt(String columnName, int value) { return withRowInt(columnName, is(value)); } /** * Returns a matcher that matches a {@link Integer} value at a given column index in a {@link * Cursor}s data row.
* * @param columnName as a {@link String} * @param valueMatcher a Matcher * that matches a {@link Integer} value */ public static CursorMatcher withRowInt(String columnName, Matcher valueMatcher) { return withRowInt(is(columnName), valueMatcher); } /** * Returns a matcher that matches a {@link Integer} value at a given column index in a {@link * Cursor}s data row.
* * @param columnNameMatcher as a Matcher * of {@link String} * @param valueMatcher a Matcher * that matches a {@link Integer} value */ public static CursorMatcher withRowInt( Matcher columnNameMatcher, final Matcher valueMatcher) { return new CursorMatcher(columnNameMatcher, valueMatcher, INT_MATCHER_APPLIER); } /** * Returns a matcher that matches a {@link Long} value at a given column index in a {@link * Cursor}s data row.
* * @param columnIndex int column index * @param value a long value to match */ public static CursorMatcher withRowLong(int columnIndex, long value) { return withRowLong(columnIndex, is(value)); } /** * Returns a matcher that matches a {@link Long} value at a given column index in a {@link * Cursor}s data row.
* * @param columnIndex int column index * @param valueMatcher a Matcher * that matches a {@link Long} value */ public static CursorMatcher withRowLong(int columnIndex, Matcher valueMatcher) { return new CursorMatcher(columnIndex, valueMatcher, LONG_MATCHER_APPLIER); } /** * Returns a matcher that matches a {@link Long} value at a given column index in a {@link * Cursor}s data row.
* * @param columnName as a {@link String} * @param value a long value to match */ public static CursorMatcher withRowLong(String columnName, long value) { return withRowLong(columnName, is(value)); } /** * Returns a matcher that matches a {@link Long} value at a given column index in a {@link * Cursor}s data row.
* * @param columnName as a {@link String} * @param valueMatcher a Matcher * that matches a {@link Long} value */ public static CursorMatcher withRowLong(String columnName, Matcher valueMatcher) { return withRowLong(is(columnName), valueMatcher); } /** * Returns a matcher that matches a {@link Long} value at a given column index in a {@link * Cursor}s data row.
* * @param columnNameMatcher as a Matcher * of {@link String} * @param valueMatcher a Matcher * that matches a {@link Long} value */ public static CursorMatcher withRowLong( Matcher columnNameMatcher, Matcher valueMatcher) { return new CursorMatcher(columnNameMatcher, valueMatcher, LONG_MATCHER_APPLIER); } /** * Returns a matcher that matches a {@link Float} value at a given column index in a {@link * Cursor}s data row.
* * @param columnIndex int column index * @param value a float value to match */ public static CursorMatcher withRowFloat(int columnIndex, float value) { return withRowFloat(columnIndex, is(value)); } /** * Returns a matcher that matches a {@link Float} value at a given column index in a {@link * Cursor}s data row.
* * @param columnIndex int column index * @param valueMatcher a Matcher * that matches a {@link Float} value */ public static CursorMatcher withRowFloat(int columnIndex, Matcher valueMatcher) { return new CursorMatcher(columnIndex, valueMatcher, FLOAT_MATCHER_APPLIER); } /** * Returns a matcher that matches a {@link Float} value at a given column index in a {@link * Cursor}s data row.
* * @param columnName as a {@link String} * @param value a float value to match */ public static CursorMatcher withRowFloat(String columnName, float value) { return withRowFloat(columnName, is(value)); } /** * Returns a matcher that matches a {@link Float} value at a given column index in a {@link * Cursor}s data row.
* * @param columnName as a {@link String} * @param valueMatcher a Matcher * that matches a {@link Float} value */ public static CursorMatcher withRowFloat(String columnName, Matcher valueMatcher) { return withRowFloat(is(columnName), valueMatcher); } /** * Returns a matcher that matches a {@link Float} value at a given column index in a {@link * Cursor}s data row.
* * @param columnNameMatcher as a Matcher * of {@link String} * @param valueMatcher a Matcher * that matches a {@link Float} value */ public static CursorMatcher withRowFloat( Matcher columnNameMatcher, Matcher valueMatcher) { return new CursorMatcher(columnNameMatcher, valueMatcher, FLOAT_MATCHER_APPLIER); } /** * Returns a matcher that matches a {@link Double} value at a given column index in a {@link * Cursor}s data row.
* * @param columnIndex int column index * @param value a double value to match */ public static CursorMatcher withRowDouble(int columnIndex, double value) { return withRowDouble(columnIndex, is(value)); } /** * Returns a matcher that matches a {@link Double} value at a given column index in a {@link * Cursor}s data row.
* * @param columnIndex int column index * @param valueMatcher a Matcher * that matches a {@link Double} value */ public static CursorMatcher withRowDouble(int columnIndex, Matcher valueMatcher) { return new CursorMatcher(columnIndex, valueMatcher, DOUBLE_MATCHER_APPLIER); } /** * Returns a matcher that matches a {@link Double} value at a given column index in a {@link * Cursor}s data row.
* * @param columnName as a {@link String} * @param value a double value to match */ public static CursorMatcher withRowDouble(String columnName, double value) { return withRowDouble(columnName, is(value)); } /** * Returns a matcher that matches a {@link Double} value at a given column index in a {@link * Cursor}s data row.
* * @param columnName as a {@link String} * @param valueMatcher a Matcher * that matches a {@link Double} value */ public static CursorMatcher withRowDouble(String columnName, Matcher valueMatcher) { return withRowDouble(is(columnName), valueMatcher); } /** * Returns a matcher that matches a {@link Double} value at a given column index in a {@link * Cursor}s data row.
* * @param columnNameMatcher as a Matcher * of {@link String} * @param valueMatcher a Matcher * that matches a {@link Double} value */ public static CursorMatcher withRowDouble( Matcher columnNameMatcher, Matcher valueMatcher) { return new CursorMatcher(columnNameMatcher, valueMatcher, DOUBLE_MATCHER_APPLIER); } /** * Returns a matcher that matches a {@link String} value at a given column index in a {@link * Cursor}s data row.
* * @param columnIndex int column index * @param value a {@link String} value to match */ public static CursorMatcher withRowString(int columnIndex, String value) { return withRowString(columnIndex, is(value)); } /** * Returns a matcher that matches a {@link String} value at a given column index in a {@link * Cursor}s data row.
* * @param columnIndex int column index * @param valueMatcher a Matcher * that matches a {@link String} value */ public static CursorMatcher withRowString(int columnIndex, Matcher valueMatcher) { return new CursorMatcher(columnIndex, valueMatcher, STRING_MATCHER_APPLIER); } /** * Returns a matcher that matches a {@link String} value at a given column index in a {@link * Cursor}s data row.
* * @param columnName as a {@link String} * @param value a {@link String} value to match */ public static CursorMatcher withRowString(String columnName, String value) { return withRowString(is(columnName), is(value)); } /** * Returns a matcher that matches a {@link String} value at a given column index in a {@link * Cursor}s data row.
* * @param columnName as a {@link String} * @param valueMatcher a Matcher * that matches a {@link String} value */ public static CursorMatcher withRowString(String columnName, Matcher valueMatcher) { return withRowString(is(columnName), valueMatcher); } /** * Returns a matcher that matches a {@link String} value at a given column index in a {@link * Cursor}s data row.
* * @param columnPicker as a Matcher * of {@link String} * @param valueMatcher a Matcher * that matches a {@link String} value */ public static CursorMatcher withRowString( final Matcher columnPicker, final Matcher valueMatcher) { return new CursorMatcher(columnPicker, valueMatcher, STRING_MATCHER_APPLIER); } /** * Returns a matcher that matches a byte[] at a given column index in a {@link Cursor}s data row. *
* * @param columnIndex int column index * @param value byte[] to match */ public static CursorMatcher withRowBlob(int columnIndex, byte[] value) { return withRowBlob(columnIndex, is(value)); } /** * Returns a matcher that matches a byte[] at a given column index in a {@link Cursor}s data row. *
* * @param columnIndex int column index * @param valueMatcher a Matcher * that matches a byte[] */ public static CursorMatcher withRowBlob(int columnIndex, Matcher valueMatcher) { return new CursorMatcher(columnIndex, valueMatcher, BLOB_MATCHER_APPLIER); } /** * Returns a matcher that matches a byte[] at a given column index in a {@link Cursor}s data row. *
* * @param columnName as a {@link String} * @param value byte[] to match */ public static CursorMatcher withRowBlob(String columnName, byte[] value) { return withRowBlob(is(columnName), is(value)); } /** * Returns a matcher that matches a byte[] at a given column index in a {@link Cursor}s data row. *
* * @param columnName as a {@link String} * @param valueMatcher a Matcher * that matches a byte[] */ public static CursorMatcher withRowBlob(String columnName, Matcher valueMatcher) { return withRowBlob(is(columnName), valueMatcher); } /** * Returns a matcher that matches a byte[] at a given column index in a {@link Cursor}s data row. *
* * @param columnPicker as a Matcher * of {@link String} * @param valueMatcher a Matcher * that matches a byte[] */ public static CursorMatcher withRowBlob( Matcher columnPicker, Matcher valueMatcher) { return new CursorMatcher(columnPicker, valueMatcher, BLOB_MATCHER_APPLIER); } }