Testing appearance-related APIs on NavigationView.
Also fix a bug where setting item background wouldn't update
the navigation view content. In addition, mark a few method
signatures with @Nullable where appropriate.
Bug: 26793013
Change-Id: Ie91c376d73beb7acb317510ac8322771ca17b66a
diff --git a/design/src/android/support/design/internal/NavigationMenuPresenter.java b/design/src/android/support/design/internal/NavigationMenuPresenter.java
index cb95f4f..609795f 100644
--- a/design/src/android/support/design/internal/NavigationMenuPresenter.java
+++ b/design/src/android/support/design/internal/NavigationMenuPresenter.java
@@ -238,12 +238,14 @@
updateMenuView(false);
}
+ @Nullable
public Drawable getItemBackground() {
return mItemBackground;
}
- public void setItemBackground(Drawable itemBackground) {
+ public void setItemBackground(@Nullable Drawable itemBackground) {
mItemBackground = itemBackground;
+ updateMenuView(false);
}
public void setUpdateSuspended(boolean updateSuspended) {
diff --git a/design/src/android/support/design/widget/NavigationView.java b/design/src/android/support/design/widget/NavigationView.java
index a73dc65..42f4eed 100644
--- a/design/src/android/support/design/widget/NavigationView.java
+++ b/design/src/android/support/design/widget/NavigationView.java
@@ -297,7 +297,7 @@
}
/**
- * Returns the tint which is applied to our item's icons.
+ * Returns the tint which is applied to our menu items' icons.
*
* @see #setItemIconTintList(ColorStateList)
*
@@ -309,7 +309,7 @@
}
/**
- * Set the tint which is applied to our item's icons.
+ * Set the tint which is applied to our menu items' icons.
*
* @param tint the tint to apply.
*
@@ -320,7 +320,7 @@
}
/**
- * Returns the tint which is applied to our item's icons.
+ * Returns the tint which is applied to our menu items' icons.
*
* @see #setItemTextColor(ColorStateList)
*
@@ -332,7 +332,7 @@
}
/**
- * Set the text color which is text to our items.
+ * Set the text color to be used on our menu items.
*
* @see #getItemTextColor()
*
@@ -343,18 +343,19 @@
}
/**
- * Returns the background drawable for the menu items.
+ * Returns the background drawable for our menu items.
*
* @see #setItemBackgroundResource(int)
*
* @attr ref R.styleable#NavigationView_itemBackground
*/
+ @Nullable
public Drawable getItemBackground() {
return mPresenter.getItemBackground();
}
/**
- * Set the background of the menu items to the given resource.
+ * Set the background of our menu items to the given resource.
*
* @param resId The identifier of the resource.
*
@@ -365,12 +366,12 @@
}
/**
- * Set the background of the menu items to a given resource. The resource should refer to
- * a Drawable object or 0 to use the background background.
+ * Set the background of our menu items to a given resource. The resource should refer to
+ * a Drawable object or null to use the default background set on this navigation menu.
*
* @attr ref R.styleable#NavigationView_itemBackground
*/
- public void setItemBackground(Drawable itemBackground) {
+ public void setItemBackground(@Nullable Drawable itemBackground) {
mPresenter.setItemBackground(itemBackground);
}
diff --git a/design/tests/res/color/color_state_list_lilac.xml b/design/tests/res/color/color_state_list_lilac.xml
new file mode 100644
index 0000000..f0b2791
--- /dev/null
+++ b/design/tests/res/color/color_state_list_lilac.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false" android:color="@color/lilac_disabled" />
+ <item android:color="@color/lilac_default"/>
+</selector>
+
diff --git a/design/tests/res/color/color_state_list_red_translucent.xml b/design/tests/res/color/color_state_list_red_translucent.xml
new file mode 100644
index 0000000..fdf8b2b
--- /dev/null
+++ b/design/tests/res/color/color_state_list_red_translucent.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@color/red_translucent"/>
+</selector>
+
diff --git a/design/tests/res/drawable/test_background_blue.xml b/design/tests/res/drawable/test_background_blue.xml
new file mode 100644
index 0000000..fe4bca2
--- /dev/null
+++ b/design/tests/res/drawable/test_background_blue.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid
+ android:color="@color/test_blue" />
+</shape>
\ No newline at end of file
diff --git a/design/tests/res/drawable/test_background_green.xml b/design/tests/res/drawable/test_background_green.xml
new file mode 100644
index 0000000..b90d9bc
--- /dev/null
+++ b/design/tests/res/drawable/test_background_green.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid
+ android:color="@color/test_green" />
+</shape>
\ No newline at end of file
diff --git a/design/tests/res/drawable/test_drawable_blue.xml b/design/tests/res/drawable/test_drawable_blue.xml
new file mode 100644
index 0000000..f228e34
--- /dev/null
+++ b/design/tests/res/drawable/test_drawable_blue.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <size
+ android:width="@dimen/drawable_large_size"
+ android:height="@dimen/drawable_small_size" />
+ <solid
+ android:color="@color/test_blue" />
+</shape>
\ No newline at end of file
diff --git a/design/tests/res/drawable/test_drawable_green.xml b/design/tests/res/drawable/test_drawable_green.xml
new file mode 100644
index 0000000..1d83f0f
--- /dev/null
+++ b/design/tests/res/drawable/test_drawable_green.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <size
+ android:width="@dimen/drawable_medium_size"
+ android:height="@dimen/drawable_large_size" />
+ <solid
+ android:color="@color/test_green" />
+</shape>
\ No newline at end of file
diff --git a/design/tests/res/drawable/test_drawable_red.xml b/design/tests/res/drawable/test_drawable_red.xml
new file mode 100644
index 0000000..58dbe73
--- /dev/null
+++ b/design/tests/res/drawable/test_drawable_red.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <size
+ android:width="@dimen/drawable_small_size"
+ android:height="@dimen/drawable_medium_size" />
+ <solid
+ android:color="@color/test_red" />
+</shape>
\ No newline at end of file
diff --git a/design/tests/res/layout/design_navigation_view.xml b/design/tests/res/layout/design_navigation_view.xml
index 3f4e03b..1789843 100644
--- a/design/tests/res/layout/design_navigation_view.xml
+++ b/design/tests/res/layout/design_navigation_view.xml
@@ -47,7 +47,7 @@
app:menu="@menu/navigation_view_content"
app:itemIconTint="@color/emerald_translucent"
app:itemTextColor="@color/emerald_text"
- app:itemBackground="@color/sand_fill"
+ app:itemBackground="@color/sand_default"
app:itemTextAppearance="@style/TextMediumStyle" />
</android.support.v4.widget.DrawerLayout>
diff --git a/design/tests/res/menu/navigation_view_content.xml b/design/tests/res/menu/navigation_view_content.xml
index 24fd830..748b79a 100644
--- a/design/tests/res/menu/navigation_view_content.xml
+++ b/design/tests/res/menu/navigation_view_content.xml
@@ -15,11 +15,14 @@
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/destination_home"
- android:title="@string/navigate_home" />
+ android:title="@string/navigate_home"
+ android:icon="@drawable/test_drawable_red" />
<item android:id="@+id/destination_profile"
- android:title="@string/navigate_profile" />
+ android:title="@string/navigate_profile"
+ android:icon="@drawable/test_drawable_green" />
<item android:id="@+id/destination_people"
- android:title="@string/navigate_people" />
+ android:title="@string/navigate_people"
+ android:icon="@drawable/test_drawable_blue" />
<item android:id="@+id/destination_settings"
android:title="@string/navigate_settings" />
</menu>
diff --git a/design/tests/res/values/colors.xml b/design/tests/res/values/colors.xml
index d1a6a1c..c7fa603 100644
--- a/design/tests/res/values/colors.xml
+++ b/design/tests/res/values/colors.xml
@@ -17,5 +17,16 @@
<resources>
<color name="emerald_translucent">#8020A060</color>
<color name="emerald_text">#30B050</color>
- <color name="sand_fill">#F0E020</color>
+ <color name="red_translucent">#90FF2040</color>
+
+ <color name="lilac_default">#F080F0</color>
+ <color name="lilac_disabled">#F0A0FF</color>
+ <color name="sand_default">#F0B000</color>
+ <color name="sand_disabled">#FFC080</color>
+ <color name="ocean_default">#50C0FF</color>
+ <color name="ocean_disabled">#90F0FF</color>
+
+ <color name="test_red">#FF6030</color>
+ <color name="test_green">#50E080</color>
+ <color name="test_blue">#3050CF</color>
</resources>
diff --git a/design/tests/res/values/dimens.xml b/design/tests/res/values/dimens.xml
index c402941..94c2734 100644
--- a/design/tests/res/values/dimens.xml
+++ b/design/tests/res/values/dimens.xml
@@ -19,5 +19,10 @@
<dimen name="tab_width_limit_large">120dip</dimen>
<dimen name="bottom_sheet_peek_height">128dp</dimen>
+ <dimen name="text_small_size">16sp</dimen>
<dimen name="text_medium_size">20sp</dimen>
+
+ <dimen name="drawable_small_size">12dip</dimen>
+ <dimen name="drawable_medium_size">16dip</dimen>
+ <dimen name="drawable_large_size">20dip</dimen>
</resources>
\ No newline at end of file
diff --git a/design/tests/res/values/styles.xml b/design/tests/res/values/styles.xml
index 1fda2e6..4fc946b 100644
--- a/design/tests/res/values/styles.xml
+++ b/design/tests/res/values/styles.xml
@@ -14,6 +14,10 @@
limitations under the License.
-->
<resources>
+ <style name="TextSmallStyle" parent="@android:style/TextAppearance">
+ <item name="android:textSize">@dimen/text_small_size</item>
+ </style>
+
<style name="TextMediumStyle" parent="@android:style/TextAppearance.Medium">
<item name="android:textSize">@dimen/text_medium_size</item>
</style>
diff --git a/design/tests/src/android/support/design/testutils/NavigationViewActions.java b/design/tests/src/android/support/design/testutils/NavigationViewActions.java
new file mode 100644
index 0000000..c2dd02f
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/NavigationViewActions.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2016 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 android.support.design.testutils;
+
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.Nullable;
+import android.support.annotation.StyleRes;
+import android.support.design.widget.NavigationView;
+import android.support.design.widget.TabLayout;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.view.View;
+import org.hamcrest.Matcher;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast;
+
+public class NavigationViewActions {
+ /**
+ * Sets item text appearance on the content of the navigation view.
+ */
+ public static ViewAction setItemTextAppearance(final @StyleRes int resId) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Set item text appearance";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ NavigationView navigationView = (NavigationView) view;
+ navigationView.setItemTextAppearance(resId);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets item text color on the content of the navigation view.
+ */
+ public static ViewAction setItemTextColor(final ColorStateList textColor) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Set item text color";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ NavigationView navigationView = (NavigationView) view;
+ navigationView.setItemTextColor(textColor);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets item background on the content of the navigation view.
+ */
+ public static ViewAction setItemBackground(final Drawable itemBackground) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Set item background";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ NavigationView navigationView = (NavigationView) view;
+ navigationView.setItemBackground(itemBackground);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets item background on the content of the navigation view.
+ */
+ public static ViewAction setItemBackgroundResource(final @DrawableRes int resId) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Set item background";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ NavigationView navigationView = (NavigationView) view;
+ navigationView.setItemBackgroundResource(resId);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets item icon tint list on the content of the navigation view.
+ */
+ public static ViewAction setItemIconTintList(final @Nullable ColorStateList tint) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Set item icon tint list";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ NavigationView navigationView = (NavigationView) view;
+ navigationView.setItemIconTintList(tint);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+}
diff --git a/design/tests/src/android/support/design/testutils/TestUtils.java b/design/tests/src/android/support/design/testutils/TestUtils.java
new file mode 100644
index 0000000..3255fb1
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/TestUtils.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 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 android.support.design.testutils;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import junit.framework.Assert;
+
+public class TestUtils {
+ /**
+ * Checks whether all the pixels in the specified drawable are of the same specified color.
+ *
+ * In case there is a color mismatch, the behavior of this method depends on the
+ * <code>throwExceptionIfFails</code> parameter. If it is <code>true</code>, this method will
+ * throw an <code>Exception</code> describing the mismatch. Otherwise this method will call
+ * <code>Assert.fail</code> with detailed description of the mismatch.
+ */
+ public static void assertAllPixelsOfColor(String failMessagePrefix, @NonNull Drawable drawable,
+ int drawableWidth, int drawableHeight, boolean callSetBounds, @ColorInt int color,
+ int allowedComponentVariance, boolean throwExceptionIfFails) {
+ // Create a bitmap
+ Bitmap bitmap = Bitmap.createBitmap(drawableWidth, drawableHeight, Bitmap.Config.ARGB_8888);
+ // Create a canvas that wraps the bitmap
+ Canvas canvas = new Canvas(bitmap);
+ if (callSetBounds) {
+ // Configure the drawable to have bounds that match the passed size
+ drawable.setBounds(0, 0, drawableWidth, drawableHeight);
+ }
+ // And ask the drawable to draw itself to the canvas / bitmap
+ drawable.draw(canvas);
+
+ try {
+ int[] rowPixels = new int[drawableWidth];
+ for (int row = 0; row < drawableHeight; row++) {
+ bitmap.getPixels(rowPixels, 0, drawableWidth, 0, row, drawableWidth, 1);
+ for (int column = 0; column < drawableWidth; column++) {
+ int sourceAlpha = Color.alpha(rowPixels[column]);
+ int sourceRed = Color.red(rowPixels[column]);
+ int sourceGreen = Color.green(rowPixels[column]);
+ int sourceBlue = Color.blue(rowPixels[column]);
+
+ int expectedAlpha = Color.alpha(color);
+ int expectedRed = Color.red(color);
+ int expectedGreen = Color.green(color);
+ int expectedBlue = Color.blue(color);
+
+ int varianceAlpha = Math.abs(sourceAlpha - expectedAlpha);
+ int varianceRed = Math.abs(sourceRed - expectedRed);
+ int varianceGreen = Math.abs(sourceGreen - expectedGreen);
+ int varianceBlue = Math.abs(sourceBlue - expectedBlue);
+
+ boolean isColorMatch = (varianceAlpha <= allowedComponentVariance)
+ && (varianceRed <= allowedComponentVariance)
+ && (varianceGreen <= allowedComponentVariance)
+ && (varianceBlue <= allowedComponentVariance);
+
+ if (!isColorMatch) {
+ String mismatchDescription = failMessagePrefix
+ + ": expected all drawable colors to be ["
+ + expectedAlpha + "," + expectedRed + ","
+ + expectedGreen + "," + expectedBlue
+ + "] but at position (" + row + "," + column + ") found ["
+ + sourceAlpha + "," + sourceRed + ","
+ + sourceGreen + "," + sourceBlue + "]";
+ if (throwExceptionIfFails) {
+ throw new RuntimeException(mismatchDescription);
+ } else {
+ Assert.fail(mismatchDescription);
+ }
+ }
+ }
+ }
+ } finally {
+ bitmap.recycle();
+ }
+ }
+}
\ No newline at end of file
diff --git a/design/tests/src/android/support/design/testutils/TestUtilsMatchers.java b/design/tests/src/android/support/design/testutils/TestUtilsMatchers.java
index 049e7a5..a44caff 100644
--- a/design/tests/src/android/support/design/testutils/TestUtilsMatchers.java
+++ b/design/tests/src/android/support/design/testutils/TestUtilsMatchers.java
@@ -16,10 +16,18 @@
package android.support.design.testutils;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorInt;
import android.support.test.espresso.matcher.BoundedMatcher;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.TextViewCompat;
import android.view.View;
+import android.view.ViewParent;
+import android.widget.TextView;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
public class TestUtilsMatchers {
/**
@@ -71,4 +79,163 @@
}
};
}
+
+ /**
+ * Returns a matcher that matches TextViews with the specified text size.
+ */
+ public static Matcher withTextSize(final float textSize) {
+ return new BoundedMatcher<View, TextView>(TextView.class) {
+ private String failedCheckDescription;
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText(failedCheckDescription);
+ }
+
+ @Override
+ public boolean matchesSafely(final TextView view) {
+ final float ourTextSize = view.getTextSize();
+ if (Math.abs(textSize - ourTextSize) > 1.0f) {
+ failedCheckDescription =
+ "text size " + ourTextSize + " is different than expected " + textSize;
+ return false;
+ }
+ return true;
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches TextViews with the specified text color.
+ */
+ public static Matcher withTextColor(final @ColorInt int textColor) {
+ return new BoundedMatcher<View, TextView>(TextView.class) {
+ private String failedCheckDescription;
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText(failedCheckDescription);
+ }
+
+ @Override
+ public boolean matchesSafely(final TextView view) {
+ final @ColorInt int ourTextColor = view.getTextColors().getDefaultColor();
+ if (ourTextColor != textColor) {
+ int ourAlpha = Color.alpha(ourTextColor);
+ int ourRed = Color.red(ourTextColor);
+ int ourGreen = Color.green(ourTextColor);
+ int ourBlue = Color.blue(ourTextColor);
+
+ int expectedAlpha = Color.alpha(textColor);
+ int expectedRed = Color.red(textColor);
+ int expectedGreen = Color.green(textColor);
+ int expectedBlue = Color.blue(textColor);
+
+ failedCheckDescription =
+ "expected color to be ["
+ + expectedAlpha + "," + expectedRed + ","
+ + expectedGreen + "," + expectedBlue
+ + "] but found ["
+ + ourAlpha + "," + ourRed + ","
+ + ourGreen + "," + ourBlue + "]";
+ return false;
+ }
+ return true;
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches TextViews whose start drawable is filled with the specified
+ * fill color.
+ */
+ public static Matcher withStartDrawableFilledWith(final @ColorInt int fillColor,
+ final int allowedComponentVariance) {
+ return new BoundedMatcher<View, TextView>(TextView.class) {
+ private String failedCheckDescription;
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText(failedCheckDescription);
+ }
+
+ @Override
+ public boolean matchesSafely(final TextView view) {
+ Drawable[] compoundDrawables = view.getCompoundDrawables();
+ boolean isRtl =
+ (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_RTL);
+ Drawable startDrawable = isRtl ? compoundDrawables[2] : compoundDrawables[0];
+ if (startDrawable == null) {
+ failedCheckDescription = "no start drawable";
+ return false;
+ }
+ try {
+ TestUtils.assertAllPixelsOfColor("",
+ startDrawable, startDrawable.getIntrinsicWidth(),
+ startDrawable.getIntrinsicHeight(), true,
+ fillColor, allowedComponentVariance, true);
+ } catch (Throwable t) {
+ failedCheckDescription = t.getMessage();
+ return false;
+ }
+ return true;
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches Views with the specified background fill color.
+ */
+ public static Matcher withBackgroundFill(final @ColorInt int fillColor) {
+ return new BoundedMatcher<View, View>(View.class) {
+ private String failedCheckDescription;
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText(failedCheckDescription);
+ }
+
+ @Override
+ public boolean matchesSafely(final View view) {
+ Drawable background = view.getBackground();
+ try {
+ TestUtils.assertAllPixelsOfColor("",
+ background, view.getWidth(), view.getHeight(), true,
+ fillColor, 0, true);
+ } catch (Throwable t) {
+ failedCheckDescription = t.getMessage();
+ return false;
+ }
+ return true;
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches {@link View}s based on the given parent type.
+ *
+ * @param parentMatcher the type of the parent to match on
+ */
+ public static Matcher<View> isChildOfA(final Matcher<View> parentMatcher) {
+ return new TypeSafeMatcher<View>() {
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("is child of a: ");
+ parentMatcher.describeTo(description);
+ }
+
+ @Override
+ public boolean matchesSafely(View view) {
+ final ViewParent viewParent = view.getParent();
+ if (!(viewParent instanceof View)) {
+ return false;
+ }
+ if (parentMatcher.matches(viewParent)) {
+ return true;
+ }
+ return false;
+ }
+ };
+ }
+
}
diff --git a/design/tests/src/android/support/design/widget/NavigationViewTest.java b/design/tests/src/android/support/design/widget/NavigationViewTest.java
index 0f99cb5..5238e02 100755
--- a/design/tests/src/android/support/design/widget/NavigationViewTest.java
+++ b/design/tests/src/android/support/design/widget/NavigationViewTest.java
@@ -16,15 +16,24 @@
package android.support.design.widget;
import android.content.res.Resources;
+import android.support.annotation.ColorInt;
import android.support.design.test.R;
+import android.support.v4.content.res.ResourcesCompat;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
+import android.support.v7.widget.RecyclerView;
import android.test.suitebuilder.annotation.SmallTest;
+import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.Test;
+import java.util.HashMap;
+import java.util.Map;
+
import static android.support.design.testutils.DrawerLayoutActions.closeDrawer;
import static android.support.design.testutils.DrawerLayoutActions.openDrawer;
+import static android.support.design.testutils.NavigationViewActions.*;
+import static android.support.design.testutils.TestUtilsMatchers.*;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.*;
@@ -32,6 +41,10 @@
public class NavigationViewTest
extends BaseInstrumentationTestCase<NavigationViewActivity> {
+ private static final int[] MENU_CONTENT_ITEM_IDS = { R.id.destination_home,
+ R.id.destination_profile, R.id.destination_people, R.id.destination_settings };
+ private Map<Integer, String> mMenuStringContent;
+
private DrawerLayout mDrawerLayout;
private NavigationView mNavigationView;
@@ -48,6 +61,14 @@
// Close the drawer to reset the state for the next test
onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START));
+
+ final Resources res = activity.getResources();
+ mMenuStringContent = new HashMap<>(MENU_CONTENT_ITEM_IDS.length);
+ mMenuStringContent.put(R.id.destination_home, res.getString(R.string.navigate_home));
+ mMenuStringContent.put(R.id.destination_profile, res.getString(R.string.navigate_profile));
+ mMenuStringContent.put(R.id.destination_people, res.getString(R.string.navigate_people));
+ mMenuStringContent.put(R.id.destination_settings,
+ res.getString(R.string.navigate_settings));
}
@Test
@@ -56,16 +77,198 @@
// Open our drawer
onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
- final Resources res = mActivityTestRule.getActivity().getResources();
-
// Check that we have the expected menu items in our NavigationView
- onView(allOf(withText(res.getString(R.string.navigate_home)),
- isDescendantOfA(withId(R.id.start_drawer)))).check(matches(isDisplayed()));
- onView(allOf(withText(res.getString(R.string.navigate_profile)),
- isDescendantOfA(withId(R.id.start_drawer)))).check(matches(isDisplayed()));
- onView(allOf(withText(res.getString(R.string.navigate_people)),
- isDescendantOfA(withId(R.id.start_drawer)))).check(matches(isDisplayed()));
- onView(allOf(withText(res.getString(R.string.navigate_settings)),
- isDescendantOfA(withId(R.id.start_drawer)))).check(matches(isDisplayed()));
+ for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+ onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(matches(isDisplayed()));
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testTextAppearance() {
+ // Open our drawer
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+ final Resources res = mActivityTestRule.getActivity().getResources();
+ final int defaultTextSize = res.getDimensionPixelSize(R.dimen.text_medium_size);
+
+ // Check the default style of the menu items in our NavigationView
+ for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+ onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(
+ matches(withTextSize(defaultTextSize)));
+ }
+
+ // Set a new text appearance on our NavigationView
+ onView(withId(R.id.start_drawer)).perform(setItemTextAppearance(R.style.TextSmallStyle));
+
+ // And check that all the menu items have the new style
+ final int newTextSize = res.getDimensionPixelSize(R.dimen.text_small_size);
+ for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+ onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(
+ matches(withTextSize(newTextSize)));
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testTextColor() {
+ // Open our drawer
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+ final Resources res = mActivityTestRule.getActivity().getResources();
+ final @ColorInt int defaultTextColor = ResourcesCompat.getColor(res,
+ R.color.emerald_text, null);
+
+ // Check the default text color of the menu items in our NavigationView
+ for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+ onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(
+ matches(withTextColor(defaultTextColor)));
+ }
+
+ // Set a new text color on our NavigationView
+ onView(withId(R.id.start_drawer)).perform(setItemTextColor(
+ ResourcesCompat.getColorStateList(res, R.color.color_state_list_lilac, null)));
+
+ // And check that all the menu items have the new color
+ final @ColorInt int newTextColor = ResourcesCompat.getColor(res,
+ R.color.lilac_default, null);
+ for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+ onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(
+ matches(withTextColor(newTextColor)));
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testBackground() {
+ // Open our drawer
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+ final Resources res = mActivityTestRule.getActivity().getResources();
+ final @ColorInt int defaultFillColor = ResourcesCompat.getColor(res,
+ R.color.sand_default, null);
+
+ // Check the default fill color of the menu items in our NavigationView
+ for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+ // Note that here we're tying ourselves to the implementation details of the
+ // internal structure of the NavigationView. Specifically, we're looking at the
+ // direct child of RecyclerView which is expected to have the background set
+ // on it. If the internal implementation of NavigationView changes, the second
+ // Matcher below will need to be tweaked.
+ Matcher menuItemMatcher = allOf(
+ hasDescendant(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i]))),
+ isChildOfA(isAssignableFrom(RecyclerView.class)),
+ isDescendantOfA(withId(R.id.start_drawer)));
+
+ onView(menuItemMatcher).check(matches(withBackgroundFill(defaultFillColor)));
+ }
+
+ // Set a new background (flat fill color) on our NavigationView
+ onView(withId(R.id.start_drawer)).perform(setItemBackgroundResource(
+ R.drawable.test_background_blue));
+
+ // And check that all the menu items have the new fill
+ final @ColorInt int newFillColorBlue = ResourcesCompat.getColor(res,
+ R.color.test_blue, null);
+ for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+ Matcher menuItemMatcher = allOf(
+ hasDescendant(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i]))),
+ isChildOfA(isAssignableFrom(RecyclerView.class)),
+ isDescendantOfA(withId(R.id.start_drawer)));
+
+ onView(menuItemMatcher).check(matches(withBackgroundFill(newFillColorBlue)));
+ }
+
+ // Set another new background on our NavigationView
+ onView(withId(R.id.start_drawer)).perform(setItemBackground(
+ ResourcesCompat.getDrawable(res, R.drawable.test_background_green, null)));
+
+ // And check that all the menu items have the new fill
+ final @ColorInt int newFillColorGreen = ResourcesCompat.getColor(res,
+ R.color.test_green, null);
+ for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+ Matcher menuItemMatcher = allOf(
+ hasDescendant(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i]))),
+ isChildOfA(isAssignableFrom(RecyclerView.class)),
+ isDescendantOfA(withId(R.id.start_drawer)));
+
+ onView(menuItemMatcher).check(matches(withBackgroundFill(newFillColorGreen)));
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testIconTinting() {
+ // Open our drawer
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+ final Resources res = mActivityTestRule.getActivity().getResources();
+ final @ColorInt int defaultTintColor = ResourcesCompat.getColor(res,
+ R.color.emerald_translucent, null);
+
+ // We're allowing a margin of error in checking the color of the items' icons.
+ // This is due to the translucent color being used in the icon tinting
+ // and off-by-one discrepancies of SRC_IN when it's compositing
+ // translucent color. Note that all the checks below are written for the current
+ // logic on NavigationView that uses the default SRC_IN tint mode - effectively
+ // replacing all non-transparent pixels in the destination (original icon) with
+ // our translucent tint color.
+ final int allowedComponentVariance = 1;
+
+ // Note that here we're tying ourselves to the implementation details of the
+ // internal structure of the NavigationView. Specifically, we're checking the
+ // start drawable of the text view with the specific text. If the internal
+ // implementation of NavigationView changes, the second Matcher in the lookups
+ // below will need to be tweaked.
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_home)),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+ withStartDrawableFilledWith(defaultTintColor, allowedComponentVariance)));
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_profile)),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+ withStartDrawableFilledWith(defaultTintColor, allowedComponentVariance)));
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+ withStartDrawableFilledWith(defaultTintColor, allowedComponentVariance)));
+
+ final @ColorInt int newTintColor = ResourcesCompat.getColor(res,
+ R.color.red_translucent, null);
+
+ onView(withId(R.id.start_drawer)).perform(setItemIconTintList(
+ ResourcesCompat.getColorStateList(res, R.color.color_state_list_red_translucent,
+ null)));
+ // Check that all menu items with icons now have icons tinted with the newly set color
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_home)),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+ withStartDrawableFilledWith(newTintColor, allowedComponentVariance)));
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_profile)),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+ withStartDrawableFilledWith(newTintColor, allowedComponentVariance)));
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+ withStartDrawableFilledWith(newTintColor, allowedComponentVariance)));
+
+ // And now remove all icon tinting
+ onView(withId(R.id.start_drawer)).perform(setItemIconTintList(null));
+ // And verify that all menu items with icons now have the original colors for their icons.
+ // Note that since there is no tinting at this point, we don't allow any color variance
+ // in these checks.
+ final @ColorInt int redIconColor = ResourcesCompat.getColor(res, R.color.test_red, null);
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_home)),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+ withStartDrawableFilledWith(redIconColor, 0)));
+ final @ColorInt int greenIconColor = ResourcesCompat.getColor(res, R.color.test_green,
+ null);
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_profile)),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+ withStartDrawableFilledWith(greenIconColor, 0)));
+ final @ColorInt int blueIconColor = ResourcesCompat.getColor(res, R.color.test_blue, null);
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+ withStartDrawableFilledWith(blueIconColor, 0)));
}
}