Home | History | Annotate | Download | only in widget
      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