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 17 package android.support.design.widget; 18 19 import android.content.res.Resources; 20 import android.os.SystemClock; 21 import android.support.annotation.Nullable; 22 import android.support.annotation.StringRes; 23 import android.support.design.test.R; 24 import android.support.design.testutils.SnackbarUtils; 25 import android.support.test.InstrumentationRegistry; 26 import android.support.test.espresso.ViewAction; 27 import android.support.test.espresso.ViewInteraction; 28 import android.support.v4.view.ViewCompat; 29 import android.test.suitebuilder.annotation.MediumTest; 30 import android.test.suitebuilder.annotation.SmallTest; 31 import android.text.TextUtils; 32 import android.view.View; 33 import org.junit.Before; 34 import org.junit.Test; 35 36 import static android.support.design.testutils.TestUtilsActions.setLayoutDirection; 37 import static android.support.test.espresso.Espresso.onView; 38 import static android.support.test.espresso.action.ViewActions.*; 39 import static android.support.test.espresso.matcher.ViewMatchers.*; 40 import static org.hamcrest.core.AllOf.allOf; 41 import static org.mockito.Matchers.any; 42 import static org.mockito.Mockito.*; 43 44 public class SnackbarTest extends BaseInstrumentationTestCase<SnackbarActivity> { 45 private static final String MESSAGE_TEXT = "Test Message"; 46 private static final @StringRes int MESSAGE_ID = R.string.snackbar_text; 47 private static final String ACTION_TEXT = "Action"; 48 private static final @StringRes int ACTION_ID = R.string.snackbar_action; 49 50 private CoordinatorLayout mCoordinatorLayout; 51 52 private interface DismissAction { 53 void dismiss(Snackbar snackbar); 54 } 55 56 public SnackbarTest() { 57 super(SnackbarActivity.class); 58 } 59 60 @Before 61 public void setup() { 62 mCoordinatorLayout = 63 (CoordinatorLayout) mActivityTestRule.getActivity().findViewById(R.id.col); 64 } 65 66 private void verifySnackbarContent(final Snackbar snackbar, final String expectedMessage, 67 final String expectedAction) { 68 // Show the snackbar 69 SnackbarUtils.showSnackbarAndWaitUntilFullyShown(snackbar); 70 71 // Verify that we're showing the message 72 withText(expectedMessage).matches(allOf( 73 isDescendantOfA(isAssignableFrom(Snackbar.SnackbarLayout.class)), 74 isCompletelyDisplayed())); 75 76 // If the action is not empty, verify that we're showing it 77 if (!TextUtils.isEmpty(expectedAction)) { 78 withText(expectedAction).matches(allOf( 79 isDescendantOfA(isAssignableFrom(Snackbar.SnackbarLayout.class)), 80 isCompletelyDisplayed())); 81 } 82 83 // Dismiss the snackbar 84 SnackbarUtils.dismissSnackbarAndWaitUntilFullyDismissed(snackbar); 85 } 86 87 @Test 88 @SmallTest 89 public void testBasicContent() { 90 // Verify different combinations of snackbar content (message and action) and duration 91 92 final Resources res = mActivityTestRule.getActivity().getResources(); 93 final String resolvedMessage = res.getString(MESSAGE_ID); 94 final String resolvedAction = res.getString(ACTION_ID); 95 96 // String message and no action 97 verifySnackbarContent( 98 Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_SHORT), 99 MESSAGE_TEXT, null); 100 101 // Resource message and no action 102 verifySnackbarContent( 103 Snackbar.make(mCoordinatorLayout, MESSAGE_ID, Snackbar.LENGTH_LONG), 104 resolvedMessage, null); 105 106 // String message and string action 107 verifySnackbarContent( 108 Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_INDEFINITE) 109 .setAction(ACTION_TEXT, mock(View.OnClickListener.class)), 110 MESSAGE_TEXT, ACTION_TEXT); 111 112 // String message and resource action 113 verifySnackbarContent( 114 Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_SHORT) 115 .setAction(ACTION_ID, mock(View.OnClickListener.class)), 116 MESSAGE_TEXT, resolvedAction); 117 118 // Resource message and resource action 119 verifySnackbarContent( 120 Snackbar.make(mCoordinatorLayout, MESSAGE_ID, Snackbar.LENGTH_LONG) 121 .setAction(ACTION_ID, mock(View.OnClickListener.class)), 122 resolvedMessage, resolvedAction); 123 } 124 125 private void verifyDismissCallback(final ViewInteraction interaction, 126 final @Nullable ViewAction action, final @Nullable DismissAction dismissAction, 127 final int length, @Snackbar.Callback.DismissEvent final int expectedEvent) { 128 final Snackbar.Callback mockCallback = mock(Snackbar.Callback.class); 129 final Snackbar snackbar = Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, length) 130 .setAction(ACTION_TEXT, mock(View.OnClickListener.class)) 131 .setCallback(mockCallback); 132 133 // Note that unlike other tests around Snackbar that use Espresso's IdlingResources 134 // to wait until the snackbar is shown (SnackbarUtils.showSnackbarAndWaitUntilFullyShown), 135 // here we want to verify our callback has been called with onShown after snackbar is shown 136 // and with onDismissed after snackbar is dismissed. 137 138 // Now show the Snackbar 139 snackbar.show(); 140 // sleep for the animation 141 SystemClock.sleep(Snackbar.ANIMATION_DURATION + 50); 142 143 // Now perform the UI interaction 144 if (action != null) { 145 interaction.perform(action); 146 } else if (dismissAction != null) { 147 InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { 148 @Override 149 public void run() { 150 dismissAction.dismiss(snackbar); 151 } 152 }); 153 } 154 // wait until the Snackbar has been removed from the view hierarchy 155 while (snackbar.isShownOrQueued()) { 156 SystemClock.sleep(20); 157 } 158 // and verify that our callback was invoked with onShown and onDismissed 159 verify(mockCallback, times(1)).onShown(snackbar); 160 verify(mockCallback, times(1)).onDismissed(snackbar, expectedEvent); 161 verifyNoMoreInteractions(mockCallback); 162 } 163 164 @Test 165 @MediumTest 166 public void testDismissViaActionClick() { 167 verifyDismissCallback( 168 onView(withId(R.id.snackbar_action)), 169 click(), 170 null, 171 Snackbar.LENGTH_LONG, 172 Snackbar.Callback.DISMISS_EVENT_ACTION); 173 } 174 175 @Test 176 @MediumTest 177 public void testDismissViaSwipe() { 178 verifyDismissCallback( 179 onView(isAssignableFrom(Snackbar.SnackbarLayout.class)), 180 swipeRight(), 181 null, 182 Snackbar.LENGTH_LONG, 183 Snackbar.Callback.DISMISS_EVENT_SWIPE); 184 } 185 186 @Test 187 @MediumTest 188 public void testDismissViaSwipeRtl() { 189 onView(withId(R.id.col)).perform(setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL)); 190 if (ViewCompat.getLayoutDirection(mCoordinatorLayout) == ViewCompat.LAYOUT_DIRECTION_RTL) { 191 // On devices that support RTL layout, the start-to-end dismiss swipe is done 192 // with swipeLeft() action 193 verifyDismissCallback( 194 onView(isAssignableFrom(Snackbar.SnackbarLayout.class)), 195 swipeLeft(), 196 null, 197 Snackbar.LENGTH_LONG, 198 Snackbar.Callback.DISMISS_EVENT_SWIPE); 199 } 200 } 201 202 @Test 203 @MediumTest 204 public void testDismissViaApi() { 205 verifyDismissCallback( 206 onView(isAssignableFrom(Snackbar.SnackbarLayout.class)), 207 null, 208 new DismissAction() { 209 @Override 210 public void dismiss(Snackbar snackbar) { 211 snackbar.dismiss(); 212 } 213 }, 214 Snackbar.LENGTH_LONG, 215 Snackbar.Callback.DISMISS_EVENT_MANUAL); 216 } 217 218 @Test 219 @MediumTest 220 public void testDismissViaTimeout() { 221 verifyDismissCallback( 222 onView(isAssignableFrom(Snackbar.SnackbarLayout.class)), 223 null, 224 null, 225 Snackbar.LENGTH_LONG, 226 Snackbar.Callback.DISMISS_EVENT_TIMEOUT); 227 } 228 229 @Test 230 @MediumTest 231 public void testDismissViaAnotherSnackbar() { 232 final Snackbar anotherSnackbar = 233 Snackbar.make(mCoordinatorLayout, "A different message", Snackbar.LENGTH_SHORT); 234 235 // Our dismiss action is to show another snackbar (and verify that the original snackbar 236 // is now dismissed with CONSECUTIVE event) 237 verifyDismissCallback( 238 onView(isAssignableFrom(Snackbar.SnackbarLayout.class)), 239 null, 240 new DismissAction() { 241 @Override 242 public void dismiss(Snackbar snackbar) { 243 anotherSnackbar.show(); 244 } 245 }, 246 Snackbar.LENGTH_LONG, 247 Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE); 248 249 // And dismiss the second snackbar to get back to clean state 250 SnackbarUtils.dismissSnackbarAndWaitUntilFullyDismissed(anotherSnackbar); 251 } 252 253 @Test 254 @MediumTest 255 public void testActionClickListener() { 256 final View.OnClickListener mockClickListener = mock(View.OnClickListener.class); 257 final Snackbar snackbar = 258 Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_SHORT) 259 .setAction(ACTION_TEXT, mockClickListener); 260 261 // Show the snackbar 262 SnackbarUtils.showSnackbarAndWaitUntilFullyShown(snackbar); 263 // perform the action click 264 onView(withId(R.id.snackbar_action)).perform(click()); 265 // and verify that our click listener has been called 266 verify(mockClickListener, times(1)).onClick(any(View.class)); 267 } 268 } 269