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 static org.hamcrest.CoreMatchers.is;
     20 import static org.hamcrest.CoreMatchers.notNullValue;
     21 import static org.hamcrest.MatcherAssert.assertThat;
     22 import static org.hamcrest.Matchers.lessThan;
     23 import static org.junit.Assert.fail;
     24 
     25 import android.content.Context;
     26 import android.content.DialogInterface;
     27 import android.os.SystemClock;
     28 import android.support.design.test.R;
     29 import android.support.test.InstrumentationRegistry;
     30 import android.support.test.espresso.Espresso;
     31 import android.support.test.espresso.UiController;
     32 import android.support.test.espresso.ViewAction;
     33 import android.support.test.espresso.action.ViewActions;
     34 import android.support.test.espresso.assertion.ViewAssertions;
     35 import android.support.test.espresso.matcher.ViewMatchers;
     36 import android.support.v7.widget.AppCompatTextView;
     37 import android.test.suitebuilder.annotation.MediumTest;
     38 import android.view.View;
     39 import android.widget.FrameLayout;
     40 
     41 import org.hamcrest.Description;
     42 import org.hamcrest.Matcher;
     43 import org.hamcrest.TypeSafeMatcher;
     44 import org.junit.Test;
     45 
     46 import java.util.concurrent.atomic.AtomicBoolean;
     47 
     48 public class BottomSheetDialogTest extends
     49         BaseInstrumentationTestCase<BottomSheetDialogActivity> {
     50 
     51     private BottomSheetDialog mDialog;
     52 
     53     public BottomSheetDialogTest() {
     54         super(BottomSheetDialogActivity.class);
     55     }
     56 
     57     @Test
     58     @MediumTest
     59     public void testBasicDialogSetup() {
     60         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
     61             @Override
     62             public void run() {
     63                 showDialog();
     64                 // Confirms that the dialog is shown
     65                 assertThat(mDialog.isShowing(), is(true));
     66                 FrameLayout bottomSheet = (FrameLayout) mDialog
     67                         .findViewById(R.id.design_bottom_sheet);
     68                 assertThat(bottomSheet, is(notNullValue()));
     69                 BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet);
     70                 assertThat(behavior.isHideable(), is(true));
     71                 assertThat(behavior, is(notNullValue()));
     72                 // Modal bottom sheets have auto peek height by default.
     73                 assertThat(behavior.getPeekHeight(), is(BottomSheetBehavior.PEEK_HEIGHT_AUTO));
     74             }
     75         });
     76         // Click outside the bottom sheet
     77         Espresso.onView(ViewMatchers.withId(R.id.touch_outside))
     78                 .perform(ViewActions.click());
     79         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
     80             @Override
     81             public void run() {
     82                 // Confirm that the dialog is no longer shown
     83                 assertThat(mDialog.isShowing(), is(false));
     84             }
     85         });
     86     }
     87 
     88     @Test
     89     @MediumTest
     90     public void testShortDialog() {
     91         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
     92             @Override
     93             public void run() {
     94                 showDialog();
     95             }
     96         });
     97         // This ensures that the views are laid out before assertions below
     98         Espresso.onView(ViewMatchers.withId(R.id.design_bottom_sheet))
     99                 .perform(setTallPeekHeight())
    100                 .check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
    101         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    102             @Override
    103             public void run() {
    104                 FrameLayout bottomSheet = (FrameLayout) mDialog
    105                         .findViewById(R.id.design_bottom_sheet);
    106                 CoordinatorLayout coordinator = (CoordinatorLayout) bottomSheet.getParent();
    107                 BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet);
    108                 assertThat(bottomSheet, is(notNullValue()));
    109                 assertThat(coordinator, is(notNullValue()));
    110                 assertThat(behavior, is(notNullValue()));
    111                 // This bottom sheet is shorter than the peek height
    112                 assertThat(bottomSheet.getHeight(), is(lessThan(behavior.getPeekHeight())));
    113                 // Confirm that the bottom sheet is bottom-aligned
    114                 assertThat(bottomSheet.getTop(),
    115                         is(coordinator.getHeight() - bottomSheet.getHeight()));
    116             }
    117         });
    118     }
    119 
    120     @Test
    121     @MediumTest
    122     public void testNonCancelableDialog() {
    123         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    124             @Override
    125             public void run() {
    126                 showDialog();
    127                 mDialog.setCancelable(false);
    128             }
    129         });
    130         // Click outside the bottom sheet
    131         Espresso.onView(ViewMatchers.withId(R.id.touch_outside))
    132                 .perform(ViewActions.click());
    133         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    134             @Override
    135             public void run() {
    136                 FrameLayout bottomSheet = (FrameLayout) mDialog
    137                         .findViewById(R.id.design_bottom_sheet);
    138                 BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet);
    139                 assertThat(behavior.isHideable(), is(false));
    140                 assertThat(mDialog.isShowing(), is(true));
    141                 mDialog.cancel();
    142                 assertThat(mDialog.isShowing(), is(false));
    143             }
    144         });
    145     }
    146 
    147     @Test
    148     @MediumTest
    149     public void testHideBottomSheet() {
    150         final AtomicBoolean canceled = new AtomicBoolean(false);
    151         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
    152             @Override
    153             public void run() {
    154                 showDialog();
    155                 mDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
    156                     @Override
    157                     public void onCancel(DialogInterface dialogInterface) {
    158                         canceled.set(true);
    159                     }
    160                 });
    161             }
    162         });
    163         Espresso.onView(ViewMatchers.withId(R.id.design_bottom_sheet))
    164                 .perform(setState(BottomSheetBehavior.STATE_HIDDEN));
    165         // The dialog should be canceled
    166         long start = System.currentTimeMillis();
    167         while (!canceled.get()) {
    168             SystemClock.sleep(31);
    169             if (System.currentTimeMillis() - start > 3000) {
    170                 fail("Timed out while waiting for the dialog to be canceled.");
    171             }
    172         }
    173     }
    174 
    175     private void showDialog() {
    176         Context context = mActivityTestRule.getActivity();
    177         mDialog = new BottomSheetDialog(context);
    178         AppCompatTextView text = new AppCompatTextView(context);
    179         StringBuilder builder = new StringBuilder();
    180         builder.append("It is fine today. ");
    181         text.setText(builder);
    182         mDialog.setContentView(text);
    183         mDialog.show();
    184     }
    185 
    186     private static ViewAction setTallPeekHeight() {
    187         return new ViewAction() {
    188             @Override
    189             public Matcher<View> getConstraints() {
    190                 return ViewMatchers.isDisplayed();
    191             }
    192 
    193             @Override
    194             public String getDescription() {
    195                 return "set tall peek height";
    196             }
    197 
    198             @Override
    199             public void perform(UiController uiController, View view) {
    200                 BottomSheetBehavior behavior = BottomSheetBehavior.from(view);
    201                 behavior.setPeekHeight(view.getHeight() + 100);
    202             }
    203         };
    204     }
    205 
    206     private static ViewAction setState(@BottomSheetBehavior.State final int state) {
    207         return new ViewAction() {
    208             @Override
    209             public Matcher<View> getConstraints() {
    210                 return isBottomSheet();
    211             }
    212 
    213             @Override
    214             public String getDescription() {
    215                 return "set state to " + state;
    216             }
    217 
    218             @Override
    219             public void perform(UiController uiController, View view) {
    220                 BottomSheetBehavior.from(view).setState(state);
    221             }
    222         };
    223     }
    224 
    225     private static Matcher<View> isBottomSheet() {
    226         return new TypeSafeMatcher<View>() {
    227             @Override
    228             protected boolean matchesSafely(View view) {
    229                 return BottomSheetBehavior.from(view) != null;
    230             }
    231 
    232             @Override
    233             public void describeTo(Description description) {
    234                 description.appendText("is a bottom sheet");
    235             }
    236         };
    237     }
    238 
    239 }
    240 
    241