CustomSnackbarTest.java revision fa0f82f629bf95681c14ed559922f77a3030aa18
1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.design.widget; 18 19import static android.support.design.testutils.TestUtilsActions.setLayoutDirection; 20import static android.support.test.espresso.Espresso.onView; 21import static android.support.test.espresso.action.ViewActions.swipeLeft; 22import static android.support.test.espresso.action.ViewActions.swipeRight; 23import static android.support.test.espresso.assertion.ViewAssertions.matches; 24import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; 25import static android.support.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed; 26import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA; 27import static android.support.test.espresso.matcher.ViewMatchers.withId; 28import static android.support.test.espresso.matcher.ViewMatchers.withText; 29 30import static org.hamcrest.core.AllOf.allOf; 31import static org.junit.Assert.assertFalse; 32import static org.junit.Assert.assertTrue; 33import static org.mockito.Mockito.mock; 34import static org.mockito.Mockito.times; 35import static org.mockito.Mockito.verify; 36import static org.mockito.Mockito.verifyNoMoreInteractions; 37 38import android.support.annotation.Nullable; 39import android.support.design.test.R; 40import android.support.design.testutils.SnackbarUtils; 41import android.support.test.espresso.ViewAction; 42import android.support.test.espresso.ViewInteraction; 43import android.support.test.filters.MediumTest; 44import android.support.test.filters.SmallTest; 45import android.support.v4.view.ViewCompat; 46import android.view.LayoutInflater; 47 48import org.junit.Before; 49import org.junit.Test; 50 51public class CustomSnackbarTest extends BaseInstrumentationTestCase<SnackbarActivity> { 52 private static final String TITLE_TEXT = "Test title"; 53 private static final String SUBTITLE_TEXT = "Test subtitle"; 54 55 private CoordinatorLayout mCoordinatorLayout; 56 57 private interface DismissAction { 58 void dismiss(CustomSnackbar snackbar); 59 } 60 61 public CustomSnackbarTest() { 62 super(SnackbarActivity.class); 63 } 64 65 @Before 66 public void setup() { 67 mCoordinatorLayout = 68 (CoordinatorLayout) mActivityTestRule.getActivity().findViewById(R.id.col); 69 } 70 71 private void verifySnackbarContent(final CustomSnackbar snackbar, final String expectedTitle, 72 final String expectedSubtitle) throws Throwable { 73 // Show the snackbar 74 SnackbarUtils.showTransientBottomBarAndWaitUntilFullyShown(snackbar); 75 76 // Verify that we're showing the title 77 withText(expectedTitle).matches(allOf( 78 isDescendantOfA(isAssignableFrom(Snackbar.SnackbarLayout.class)), 79 isDescendantOfA(isAssignableFrom(CustomSnackbarMainContent.class)), 80 isCompletelyDisplayed())); 81 82 // Verify that we're showing the subtitle 83 withText(expectedSubtitle).matches(allOf( 84 isDescendantOfA(isAssignableFrom(Snackbar.SnackbarLayout.class)), 85 isDescendantOfA(isAssignableFrom(CustomSnackbarMainContent.class)), 86 isCompletelyDisplayed())); 87 88 // Dismiss the snackbar 89 SnackbarUtils.dismissTransientBottomBarAndWaitUntilFullyDismissed(snackbar); 90 } 91 92 private CustomSnackbar makeCustomSnackbar() { 93 final LayoutInflater inflater = LayoutInflater.from(mCoordinatorLayout.getContext()); 94 final CustomSnackbarMainContent content = 95 (CustomSnackbarMainContent) inflater.inflate( 96 R.layout.custom_snackbar_include, mCoordinatorLayout, false); 97 final BaseTransientBottomBar.ContentViewCallback contentViewCallback = 98 new BaseTransientBottomBar.ContentViewCallback() { 99 @Override 100 public void animateContentIn(int delay, int duration) { 101 content.setAlpha(0f); 102 content.animate().alpha(1f).setDuration(duration) 103 .setStartDelay(delay).start(); 104 } 105 106 @Override 107 public void animateContentOut(int delay, int duration) { 108 content.setAlpha(1f); 109 content.animate().alpha(0f).setDuration(duration) 110 .setStartDelay(delay).start(); 111 } 112 }; 113 return new CustomSnackbar(mCoordinatorLayout, content, contentViewCallback); 114 } 115 116 @Test 117 @SmallTest 118 public void testBasicContent() throws Throwable { 119 // Verify different combinations of snackbar content (title / subtitle and action) 120 // and duration 121 122 // Short duration 123 verifySnackbarContent( 124 makeCustomSnackbar().setTitle(TITLE_TEXT) 125 .setSubtitle(SUBTITLE_TEXT).setDuration(Snackbar.LENGTH_SHORT), 126 TITLE_TEXT, SUBTITLE_TEXT); 127 128 // Long duration 129 verifySnackbarContent( 130 makeCustomSnackbar().setTitle(TITLE_TEXT) 131 .setSubtitle(SUBTITLE_TEXT).setDuration(Snackbar.LENGTH_LONG), 132 TITLE_TEXT, SUBTITLE_TEXT); 133 134 // Indefinite duration 135 verifySnackbarContent( 136 makeCustomSnackbar().setTitle(TITLE_TEXT) 137 .setSubtitle(SUBTITLE_TEXT).setDuration(Snackbar.LENGTH_INDEFINITE), 138 TITLE_TEXT, SUBTITLE_TEXT); 139 } 140 141 private void verifyDismissCallback(final ViewInteraction interaction, 142 @Nullable final ViewAction action, @Nullable final DismissAction dismissAction, 143 final int length, @Snackbar.Callback.DismissEvent final int expectedEvent) 144 throws Throwable { 145 final BaseTransientBottomBar.BaseCallback mockCallback = 146 mock(BaseTransientBottomBar.BaseCallback.class); 147 final CustomSnackbar snackbar = makeCustomSnackbar().setTitle(TITLE_TEXT) 148 .setSubtitle(SUBTITLE_TEXT).setDuration(length) 149 .addCallback(mockCallback); 150 151 // Show the snackbar 152 SnackbarUtils.showTransientBottomBarAndWaitUntilFullyShown(snackbar); 153 // Verify that our onShown has been called 154 verify(mockCallback, times(1)).onShown(snackbar); 155 // and that the snackbar is either shown or queued to be shown 156 assertTrue(snackbar.isShownOrQueued()); 157 // and also check that we have the intended title / subtitle displayed somewhere in 158 // our hierarchy 159 onView(withText(TITLE_TEXT)).check(matches(isCompletelyDisplayed())); 160 onView(withText(SUBTITLE_TEXT)).check(matches(isCompletelyDisplayed())); 161 162 // Now perform the UI interaction 163 SnackbarUtils.performActionAndWaitUntilFullyDismissed(snackbar, 164 new SnackbarUtils.TransientBottomBarAction() { 165 @Override 166 public void perform() throws Throwable { 167 if (action != null) { 168 interaction.perform(action); 169 } else if (dismissAction != null) { 170 mActivityTestRule.runOnUiThread(new Runnable() { 171 @Override 172 public void run() { 173 dismissAction.dismiss(snackbar); 174 } 175 }); 176 } 177 } 178 }); 179 180 // Verify that our onDismissed has been called 181 verify(mockCallback, times(1)).onDismissed(snackbar, expectedEvent); 182 verifyNoMoreInteractions(mockCallback); 183 // and that the snackbar is neither shown nor queued to be shown 184 assertFalse(snackbar.isShownOrQueued()); 185 } 186 187 @Test 188 @MediumTest 189 public void testDismissViaSwipe() throws Throwable { 190 verifyDismissCallback( 191 onView(isAssignableFrom(Snackbar.SnackbarLayout.class)), 192 swipeRight(), 193 null, 194 Snackbar.LENGTH_LONG, 195 Snackbar.Callback.DISMISS_EVENT_SWIPE); 196 } 197 198 @Test 199 @MediumTest 200 public void testDismissViaSwipeRtl() throws Throwable { 201 onView(withId(R.id.col)).perform(setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL)); 202 if (ViewCompat.getLayoutDirection(mCoordinatorLayout) == ViewCompat.LAYOUT_DIRECTION_RTL) { 203 // On devices that support RTL layout, the start-to-end dismiss swipe is done 204 // with swipeLeft() action 205 verifyDismissCallback( 206 onView(isAssignableFrom(Snackbar.SnackbarLayout.class)), 207 swipeLeft(), 208 null, 209 Snackbar.LENGTH_LONG, 210 Snackbar.Callback.DISMISS_EVENT_SWIPE); 211 } 212 } 213 214 @Test 215 @MediumTest 216 public void testDismissViaApi() throws Throwable { 217 verifyDismissCallback( 218 onView(isAssignableFrom(Snackbar.SnackbarLayout.class)), 219 null, 220 new DismissAction() { 221 @Override 222 public void dismiss(CustomSnackbar snackbar) { 223 snackbar.dismiss(); 224 } 225 }, 226 Snackbar.LENGTH_LONG, 227 Snackbar.Callback.DISMISS_EVENT_MANUAL); 228 } 229 230 @Test 231 @MediumTest 232 public void testDismissViaTimeout() throws Throwable { 233 verifyDismissCallback( 234 onView(isAssignableFrom(Snackbar.SnackbarLayout.class)), 235 null, 236 null, 237 Snackbar.LENGTH_LONG, 238 Snackbar.Callback.DISMISS_EVENT_TIMEOUT); 239 } 240 241 @Test 242 @MediumTest 243 public void testDismissViaAnotherSnackbar() throws Throwable { 244 final CustomSnackbar anotherSnackbar = 245 makeCustomSnackbar().setTitle("Different title") 246 .setSubtitle("Different subtitle").setDuration(Snackbar.LENGTH_SHORT); 247 248 // Our dismiss action is to show another snackbar (and verify that the original snackbar 249 // is now dismissed with CONSECUTIVE event) 250 verifyDismissCallback( 251 onView(isAssignableFrom(Snackbar.SnackbarLayout.class)), 252 null, 253 new DismissAction() { 254 @Override 255 public void dismiss(CustomSnackbar snackbar) { 256 anotherSnackbar.show(); 257 } 258 }, 259 Snackbar.LENGTH_LONG, 260 Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE); 261 262 // And dismiss the second snackbar to get back to clean state 263 SnackbarUtils.dismissTransientBottomBarAndWaitUntilFullyDismissed(anotherSnackbar); 264 } 265 266 @Test 267 @MediumTest 268 public void testMultipleCallbacks() throws Throwable { 269 final CustomSnackbar snackbar = makeCustomSnackbar().setTitle(TITLE_TEXT) 270 .setSubtitle(SUBTITLE_TEXT).setDuration(Snackbar.LENGTH_INDEFINITE); 271 final BaseTransientBottomBar.BaseCallback mockCallback1 = 272 mock(BaseTransientBottomBar.BaseCallback.class); 273 final BaseTransientBottomBar.BaseCallback mockCallback2 = 274 mock(BaseTransientBottomBar.BaseCallback.class); 275 snackbar.addCallback(mockCallback1); 276 snackbar.addCallback(mockCallback2); 277 278 SnackbarUtils.showTransientBottomBarAndWaitUntilFullyShown(snackbar); 279 verify(mockCallback1, times(1)).onShown(snackbar); 280 verify(mockCallback2, times(1)).onShown(snackbar); 281 282 SnackbarUtils.dismissTransientBottomBarAndWaitUntilFullyDismissed(snackbar); 283 verify(mockCallback1, times(1)).onDismissed(snackbar, 284 BaseTransientBottomBar.BaseCallback.DISMISS_EVENT_MANUAL); 285 verify(mockCallback2, times(1)).onDismissed(snackbar, 286 BaseTransientBottomBar.BaseCallback.DISMISS_EVENT_MANUAL); 287 } 288} 289