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