1/* 2 * Copyright (C) 2015 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 */ 16package android.support.v7.app; 17 18import static android.support.test.espresso.Espresso.onData; 19import static android.support.test.espresso.Espresso.onView; 20import static android.support.test.espresso.action.ViewActions.click; 21import static android.support.test.espresso.assertion.PositionAssertions.isBelow; 22import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist; 23import static android.support.test.espresso.assertion.ViewAssertions.matches; 24import static android.support.test.espresso.matcher.LayoutMatchers.hasEllipsizedText; 25import static android.support.test.espresso.matcher.RootMatchers.isDialog; 26import static android.support.test.espresso.matcher.ViewMatchers.hasSibling; 27import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; 28import static android.support.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed; 29import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA; 30import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; 31import static android.support.test.espresso.matcher.ViewMatchers.withId; 32import static android.support.test.espresso.matcher.ViewMatchers.withText; 33 34import static org.hamcrest.Matchers.instanceOf; 35import static org.hamcrest.Matchers.not; 36import static org.hamcrest.core.AllOf.allOf; 37import static org.hamcrest.core.Is.is; 38import static org.junit.Assert.assertEquals; 39import static org.junit.Assert.assertFalse; 40import static org.junit.Assert.assertNotNull; 41import static org.junit.Assert.assertNull; 42import static org.junit.Assert.assertTrue; 43import static org.mockito.ArgumentMatchers.anyLong; 44import static org.mockito.Mockito.any; 45import static org.mockito.Mockito.mock; 46import static org.mockito.Mockito.never; 47import static org.mockito.Mockito.times; 48import static org.mockito.Mockito.verify; 49 50import android.content.Context; 51import android.content.DialogInterface; 52import android.graphics.drawable.ColorDrawable; 53import android.os.Handler; 54import android.os.Message; 55import android.support.annotation.ColorInt; 56import android.support.annotation.StringRes; 57import android.support.test.annotation.UiThreadTest; 58import android.support.test.espresso.Espresso; 59import android.support.test.espresso.ViewInteraction; 60import android.support.test.filters.LargeTest; 61import android.support.test.filters.MediumTest; 62import android.support.test.filters.SmallTest; 63import android.support.test.rule.ActivityTestRule; 64import android.support.v7.appcompat.test.R; 65import android.support.v7.testutils.TestUtilsMatchers; 66import android.text.TextUtils; 67import android.util.TypedValue; 68import android.view.LayoutInflater; 69import android.view.View; 70import android.widget.ArrayAdapter; 71import android.widget.Button; 72import android.widget.CheckedTextView; 73import android.widget.ImageView; 74import android.widget.ListAdapter; 75import android.widget.ListView; 76 77import org.hamcrest.Matcher; 78import org.junit.After; 79import org.junit.Before; 80import org.junit.Rule; 81import org.junit.Test; 82import org.mockito.ArgumentCaptor; 83 84/** 85 * Tests in this class make a few assumptions about the underlying implementation of 86 * <code>AlertDialog</code>. While the assumptions don't go all the way down to individual 87 * <code>R.id</code> references or very specific layout arrangements, internal refactoring 88 * of <code>AlertDialog</code> might require corresponding restructuring of the matching 89 * tests. Specifically: 90 * 91 * <ul> 92 * <li>Testing <code>setIcon</code> API assumes that the icon is displayed by a separate 93 * <code>ImageView</code> which is a sibling of a title view.</li> 94 * <li>Testing <code>setMultiChoiceItems</code> API assumes that each item in the list 95 * is rendered by a single <code>CheckedTextView</code>.</li> 96 * <li>Testing <code>setSingleChoiceItems</code> API assumes that each item in the list 97 * is rendered by a single <code>CheckedTextView</code>.</li> 98 * </ul> 99 */ 100public class AlertDialogTest { 101 @Rule 102 public final ActivityTestRule<AlertDialogTestActivity> mActivityTestRule; 103 104 private Button mButton; 105 106 private AlertDialog mAlertDialog; 107 108 public AlertDialogTest() { 109 mActivityTestRule = new ActivityTestRule<>(AlertDialogTestActivity.class); 110 } 111 112 @Before 113 public void setUp() { 114 final AlertDialogTestActivity activity = mActivityTestRule.getActivity(); 115 mButton = (Button) activity.findViewById(R.id.test_button); 116 } 117 118 @After 119 public void tearDown() throws Throwable { 120 if ((mAlertDialog != null) && mAlertDialog.isShowing()) { 121 mActivityTestRule.runOnUiThread(new Runnable() { 122 @Override 123 public void run() { 124 mAlertDialog.hide(); 125 } 126 }); 127 } 128 } 129 130 private void wireBuilder(final AlertDialog.Builder builder) { 131 mButton.setOnClickListener(new View.OnClickListener() { 132 @Override 133 public void onClick(View v) { 134 mAlertDialog = builder.show(); 135 } 136 }); 137 } 138 139 @Test 140 @SmallTest 141 @UiThreadTest 142 public void testBuilderTheme() { 143 final Context context = mActivityTestRule.getActivity(); 144 final AlertDialog dialog = new AlertDialog.Builder(context, R.style.Theme_TextColors) 145 .setTitle(R.string.alert_dialog_title) 146 .setMessage(R.string.alert_dialog_content) 147 .create(); 148 149 final TypedValue tv = new TypedValue(); 150 dialog.getContext().getTheme().resolveAttribute(android.R.attr.textColorPrimary, tv, true); 151 assertEquals(0xFF0000FF, tv.data); 152 } 153 154 @Test 155 @MediumTest 156 public void testBasicContent() { 157 final Context context = mActivityTestRule.getActivity(); 158 AlertDialog.Builder builder = new AlertDialog.Builder(context) 159 .setTitle(R.string.alert_dialog_title) 160 .setMessage(R.string.alert_dialog_content); 161 wireBuilder(builder); 162 163 onView(withId(R.id.test_button)).perform(click()); 164 165 // Test that we're showing a dialog with vertically stacked title and content 166 final String expectedTitle = context.getString(R.string.alert_dialog_title); 167 final String expectedMessage = context.getString(R.string.alert_dialog_content); 168 onView(withText(expectedTitle)).inRoot(isDialog()).check(matches(isDisplayed())); 169 onView(withText(expectedMessage)).inRoot(isDialog()).check(matches(isDisplayed())); 170 onView(withText(expectedMessage)).inRoot(isDialog()).check( 171 isBelow(withText(expectedTitle))); 172 173 assertNull("No list view", mAlertDialog.getListView()); 174 175 assertEquals("Positive button not shown", View.GONE, 176 mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE).getVisibility()); 177 assertEquals("Negative button not shown", View.GONE, 178 mAlertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).getVisibility()); 179 assertEquals("Neutral button not shown", View.GONE, 180 mAlertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).getVisibility()); 181 } 182 183 // Tests for message logic 184 185 @Test 186 @MediumTest 187 public void testMessageString() { 188 final String dialogMessage = "Dialog message"; 189 AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 190 .setTitle(R.string.alert_dialog_title) 191 .setMessage(dialogMessage); 192 wireBuilder(builder); 193 194 onView(withId(R.id.test_button)).perform(click()); 195 onView(withText(dialogMessage)).inRoot(isDialog()).check(matches(isDisplayed())); 196 } 197 198 @Test 199 @MediumTest 200 public void testMessageStringPostCreation() throws Throwable { 201 final String dialogInitialMessage = "Initial message"; 202 final String dialogUpdatedMessage = "Updated message"; 203 AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 204 .setTitle(R.string.alert_dialog_title) 205 .setMessage(dialogInitialMessage); 206 wireBuilder(builder); 207 208 // Click the button to show the dialog and check that it shows the initial message 209 onView(withId(R.id.test_button)).perform(click()); 210 onView(withText(dialogInitialMessage)).inRoot(isDialog()).check(matches(isDisplayed())); 211 212 // Update the dialog message 213 mActivityTestRule.runOnUiThread(new Runnable() { 214 @Override 215 public void run() { 216 mAlertDialog.setMessage(dialogUpdatedMessage); 217 } 218 }); 219 // Check that the old message is not showing 220 onView(withText(dialogInitialMessage)).inRoot(isDialog()).check(doesNotExist()); 221 // and that the new message is showing 222 onView(withText(dialogUpdatedMessage)).inRoot(isDialog()).check(matches(isDisplayed())); 223 } 224 225 // Tests for custom title logic 226 227 /** 228 * Helper method to verify that setting custom title hides the default title and shows 229 * the custom title above the dialog message. 230 */ 231 private void verifyCustomTitle() { 232 final Context context = mActivityTestRule.getActivity(); 233 234 // Test that we're showing a dialog with vertically stacked custom title and content 235 final String title = context.getString(R.string.alert_dialog_title); 236 final String expectedCustomTitle = context.getString(R.string.alert_dialog_custom_title); 237 final String expectedMessage = context.getString(R.string.alert_dialog_content); 238 239 // Check that the default title is not showing 240 onView(withText(title)).inRoot(isDialog()).check(doesNotExist()); 241 // Check that the custom title is fully displayed with no text eliding and is 242 // stacked above the message 243 onView(withText(expectedCustomTitle)).inRoot(isDialog()).check( 244 matches(isCompletelyDisplayed())); 245 onView(withText(expectedCustomTitle)).inRoot(isDialog()).check( 246 matches(not(hasEllipsizedText()))); 247 onView(withText(expectedMessage)).inRoot(isDialog()).check(matches(isDisplayed())); 248 onView(withText(expectedMessage)).inRoot(isDialog()).check( 249 isBelow(withText(expectedCustomTitle))); 250 } 251 252 @Test 253 @MediumTest 254 public void testCustomTitle() { 255 final Context context = mActivityTestRule.getActivity(); 256 final LayoutInflater inflater = LayoutInflater.from(context); 257 AlertDialog.Builder builder = new AlertDialog.Builder(context) 258 .setTitle(R.string.alert_dialog_title) 259 .setMessage(R.string.alert_dialog_content) 260 .setCustomTitle(inflater.inflate(R.layout.alert_dialog_custom_title, null, false)); 261 wireBuilder(builder); 262 263 onView(withId(R.id.test_button)).perform(click()); 264 265 verifyCustomTitle(); 266 } 267 268 @Test 269 @MediumTest 270 public void testCustomTitlePostCreation() { 271 final Context context = mActivityTestRule.getActivity(); 272 final LayoutInflater inflater = LayoutInflater.from(context); 273 final AlertDialog.Builder builder = new AlertDialog.Builder(context) 274 .setTitle(R.string.alert_dialog_title) 275 .setMessage(R.string.alert_dialog_content); 276 277 mButton.setOnClickListener(new View.OnClickListener() { 278 @Override 279 public void onClick(View v) { 280 mAlertDialog = builder.create(); 281 282 // Configure custom title 283 mAlertDialog.setCustomTitle(inflater.inflate( 284 R.layout.alert_dialog_custom_title, null, false)); 285 286 mAlertDialog.show(); 287 } 288 }); 289 290 // Click the button to create the dialog, configure custom title and show the dialog 291 onView(withId(R.id.test_button)).perform(click()); 292 293 verifyCustomTitle(); 294 } 295 296 // Tests for custom view logic 297 298 /** 299 * Helper method to verify that setting custom view shows the content of that view. 300 */ 301 private void verifyCustomView() { 302 final Context context = mActivityTestRule.getActivity(); 303 304 // Test that we're showing a dialog with vertically stacked custom title and content 305 final String expectedCustomText1 = context.getString(R.string.alert_dialog_custom_text1); 306 final String expectedCustomText2 = context.getString(R.string.alert_dialog_custom_text2); 307 308 // Check that we're showing the content of our custom view 309 onView(withId(R.id.alert_dialog_custom_view)).inRoot(isDialog()).check( 310 matches(isCompletelyDisplayed())); 311 onView(withText(expectedCustomText1)).inRoot(isDialog()).check( 312 matches(isCompletelyDisplayed())); 313 onView(withText(expectedCustomText1)).inRoot(isDialog()).check( 314 matches(not(hasEllipsizedText()))); 315 onView(withText(expectedCustomText2)).inRoot(isDialog()).check( 316 matches(isCompletelyDisplayed())); 317 onView(withText(expectedCustomText2)).inRoot(isDialog()).check( 318 matches(not(hasEllipsizedText()))); 319 } 320 321 @Test 322 @MediumTest 323 public void testCustomView() { 324 final Context context = mActivityTestRule.getActivity(); 325 final LayoutInflater inflater = LayoutInflater.from(context); 326 AlertDialog.Builder builder = new AlertDialog.Builder(context) 327 .setTitle(R.string.alert_dialog_title) 328 .setMessage(R.string.alert_dialog_content) 329 .setView(inflater.inflate(R.layout.alert_dialog_custom_view, null, false)); 330 wireBuilder(builder); 331 332 onView(withId(R.id.test_button)).perform(click()); 333 334 verifyCustomView(); 335 } 336 337 @Test 338 @MediumTest 339 public void testCustomViewById() { 340 final Context context = mActivityTestRule.getActivity(); 341 AlertDialog.Builder builder = new AlertDialog.Builder(context) 342 .setTitle(R.string.alert_dialog_title) 343 .setMessage(R.string.alert_dialog_content) 344 .setView(R.layout.alert_dialog_custom_view); 345 wireBuilder(builder); 346 347 onView(withId(R.id.test_button)).perform(click()); 348 349 verifyCustomView(); 350 } 351 352 @Test 353 @MediumTest 354 public void testCustomViewPostCreation() { 355 final Context context = mActivityTestRule.getActivity(); 356 final LayoutInflater inflater = LayoutInflater.from(context); 357 final AlertDialog.Builder builder = new AlertDialog.Builder(context) 358 .setTitle(R.string.alert_dialog_title) 359 .setMessage(R.string.alert_dialog_content); 360 361 mButton.setOnClickListener(new View.OnClickListener() { 362 @Override 363 public void onClick(View v) { 364 mAlertDialog = builder.create(); 365 366 // Configure custom view 367 mAlertDialog.setView(inflater.inflate( 368 R.layout.alert_dialog_custom_view, null, false)); 369 370 mAlertDialog.show(); 371 } 372 }); 373 374 // Click the button to create the dialog, configure custom view and show the dialog 375 onView(withId(R.id.test_button)).perform(click()); 376 377 verifyCustomView(); 378 } 379 380 // Tests for cancel logic 381 382 @Test 383 @MediumTest 384 public void testCancelCancelableDialog() { 385 DialogInterface.OnCancelListener mockCancelListener = 386 mock(DialogInterface.OnCancelListener.class); 387 AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 388 .setTitle(R.string.alert_dialog_title) 389 .setMessage(R.string.alert_dialog_content) 390 .setCancelable(true) 391 .setOnCancelListener(mockCancelListener); 392 wireBuilder(builder); 393 394 onView(withId(R.id.test_button)).perform(click()); 395 396 // Emulate a tap on the device BACK button 397 Espresso.pressBack(); 398 399 // Since our dialog is cancelable, check that the cancel listener has been invoked 400 verify(mockCancelListener, times(1)).onCancel(mAlertDialog); 401 } 402 403 @Test 404 @MediumTest 405 public void testCancelNonCancelableDialog() { 406 DialogInterface.OnCancelListener mockCancelListener = 407 mock(DialogInterface.OnCancelListener.class); 408 AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 409 .setTitle(R.string.alert_dialog_title) 410 .setMessage(R.string.alert_dialog_content) 411 .setCancelable(false) 412 .setOnCancelListener(mockCancelListener); 413 wireBuilder(builder); 414 415 onView(withId(R.id.test_button)).perform(click()); 416 417 // Emulate a tap on the device BACK button 418 Espresso.pressBack(); 419 420 // Since our dialog is not cancelable, check that the cancel listener has not been invoked 421 verify(mockCancelListener, never()).onCancel(mAlertDialog); 422 } 423 424 // Tests for items content logic (simple, single-choice, multi-choice) 425 426 private void verifySimpleItemsContent(String[] expectedContent, 427 DialogInterface.OnClickListener onClickListener) { 428 final int expectedCount = expectedContent.length; 429 430 onView(withId(R.id.test_button)).perform(click()); 431 432 final ListView listView = mAlertDialog.getListView(); 433 assertNotNull("List view is shown", listView); 434 435 final ListAdapter listAdapter = listView.getAdapter(); 436 assertEquals("List has " + expectedCount + " entries", 437 expectedCount, listAdapter.getCount()); 438 for (int i = 0; i < expectedCount; i++) { 439 assertEquals("List entry #" + i, expectedContent[i], listAdapter.getItem(i)); 440 } 441 442 // Test that all items are showing 443 onView(withText("Dialog title")).inRoot(isDialog()).check(matches(isDisplayed())); 444 for (int i = 0; i < expectedCount; i++) { 445 onData(allOf(is(instanceOf(String.class)), is(expectedContent[i]))).inRoot(isDialog()). 446 check(matches(isDisplayed())); 447 } 448 449 // Verify that our click listener hasn't been called yet 450 verify(onClickListener, never()).onClick(any(DialogInterface.class), any(int.class)); 451 // Test that a click on an item invokes the registered listener 452 int indexToClick = expectedCount - 2; 453 onData(allOf(is(instanceOf(String.class)), is(expectedContent[indexToClick]))). 454 inRoot(isDialog()).perform(click()); 455 verify(onClickListener, times(1)).onClick(mAlertDialog, indexToClick); 456 } 457 458 @Test 459 @MediumTest 460 public void testCustomAdapter() { 461 final Context context = mActivityTestRule.getActivity(); 462 final String[] content = context.getResources().getStringArray(R.array.alert_dialog_items); 463 final DialogInterface.OnClickListener mockClickListener = 464 mock(DialogInterface.OnClickListener.class); 465 AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 466 .setTitle(R.string.alert_dialog_title) 467 .setAdapter( 468 new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, content), 469 mockClickListener); 470 wireBuilder(builder); 471 472 verifySimpleItemsContent(content, mockClickListener); 473 } 474 475 @Test 476 @MediumTest 477 public void testSimpleItemsFromRuntimeArray() { 478 final String[] content = new String[] { "Alice", "Bob", "Charlie", "Delta" }; 479 final DialogInterface.OnClickListener mockClickListener = 480 mock(DialogInterface.OnClickListener.class); 481 AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 482 .setTitle(R.string.alert_dialog_title) 483 .setItems(content, mockClickListener); 484 wireBuilder(builder); 485 486 verifySimpleItemsContent(content, mockClickListener); 487 } 488 489 @Test 490 @MediumTest 491 public void testSimpleItemsFromResourcesArray() { 492 final DialogInterface.OnClickListener mockClickListener = 493 mock(DialogInterface.OnClickListener.class); 494 AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 495 .setTitle(R.string.alert_dialog_title) 496 .setItems(R.array.alert_dialog_items, mockClickListener); 497 wireBuilder(builder); 498 499 verifySimpleItemsContent(mActivityTestRule.getActivity().getResources().getStringArray( 500 R.array.alert_dialog_items), mockClickListener); 501 } 502 503 /** 504 * Helper method to verify the state of the multi-choice items list. It gets the String 505 * array of content and verifies that: 506 * 507 * 1. The items in the array are rendered as CheckedTextViews inside a ListView 508 * 2. Each item in the array is displayed 509 * 3. Checked state of each row in the ListView corresponds to the matching entry in the 510 * passed boolean array 511 */ 512 private void verifyMultiChoiceItemsState(String[] expectedContent, 513 boolean[] checkedTracker) { 514 final int expectedCount = expectedContent.length; 515 516 final ListView listView = mAlertDialog.getListView(); 517 assertNotNull("List view is shown", listView); 518 519 final ListAdapter listAdapter = listView.getAdapter(); 520 assertEquals("List has " + expectedCount + " entries", 521 expectedCount, listAdapter.getCount()); 522 for (int i = 0; i < expectedCount; i++) { 523 assertEquals("List entry #" + i, expectedContent[i], listAdapter.getItem(i)); 524 } 525 526 for (int i = 0; i < expectedCount; i++) { 527 Matcher checkedStateMatcher = checkedTracker[i] ? TestUtilsMatchers.isCheckedTextView() : 528 TestUtilsMatchers.isNonCheckedTextView(); 529 // Check that the corresponding row is rendered as CheckedTextView with expected 530 // checked state. 531 onData(allOf(is(instanceOf(String.class)), is(expectedContent[i]))).inRoot(isDialog()). 532 check(matches(allOf( 533 isDisplayed(), 534 isAssignableFrom(CheckedTextView.class), 535 isDescendantOfA(isAssignableFrom(ListView.class)), 536 checkedStateMatcher))); 537 } 538 } 539 540 private void verifyMultiChoiceItemsContent(String[] expectedContent, 541 final boolean[] checkedTracker) { 542 final int expectedCount = expectedContent.length; 543 544 onView(withId(R.id.test_button)).perform(click()); 545 546 final ListView listView = mAlertDialog.getListView(); 547 assertNotNull("List view is shown", listView); 548 549 final ListAdapter listAdapter = listView.getAdapter(); 550 assertEquals("List has " + expectedCount + " entries", 551 expectedCount, listAdapter.getCount()); 552 for (int i = 0; i < expectedCount; i++) { 553 assertEquals("List entry #" + i, expectedContent[i], listAdapter.getItem(i)); 554 } 555 556 // Test that all items are showing 557 onView(withText("Dialog title")).inRoot(isDialog()).check(matches(isDisplayed())); 558 verifyMultiChoiceItemsState(expectedContent, checkedTracker); 559 560 // We're going to click item #1 and test that the click listener has been invoked to 561 // update the original state array 562 boolean[] expectedAfterClick1 = checkedTracker.clone(); 563 expectedAfterClick1[1] = !expectedAfterClick1[1]; 564 onData(allOf(is(instanceOf(String.class)), is(expectedContent[1]))). 565 inRoot(isDialog()).perform(click()); 566 verifyMultiChoiceItemsState(expectedContent, expectedAfterClick1); 567 568 // Now click item #1 again and test that the click listener has been invoked to update the 569 // original state array again 570 expectedAfterClick1[1] = !expectedAfterClick1[1]; 571 onData(allOf(is(instanceOf(String.class)), is(expectedContent[1]))). 572 inRoot(isDialog()).perform(click()); 573 verifyMultiChoiceItemsState(expectedContent, expectedAfterClick1); 574 575 // Now we're going to click the last item and test that the click listener has been invoked 576 // to update the original state array 577 boolean[] expectedAfterClickLast = checkedTracker.clone(); 578 expectedAfterClickLast[expectedCount - 1] = !expectedAfterClickLast[expectedCount - 1]; 579 onData(allOf(is(instanceOf(String.class)), is(expectedContent[expectedCount - 1]))). 580 inRoot(isDialog()).perform(click()); 581 verifyMultiChoiceItemsState(expectedContent, expectedAfterClickLast); 582 } 583 584 @Test 585 @MediumTest 586 public void testMultiChoiceItemsFromRuntimeArray() { 587 final String[] content = new String[] { "Alice", "Bob", "Charlie", "Delta" }; 588 final boolean[] checkedTracker = new boolean[] { false, true, false, false }; 589 AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 590 .setTitle(R.string.alert_dialog_title) 591 .setMultiChoiceItems( 592 content, checkedTracker, 593 new DialogInterface.OnMultiChoiceClickListener() { 594 @Override 595 public void onClick(DialogInterface dialog, int which, 596 boolean isChecked) { 597 checkedTracker[which] = isChecked; 598 } 599 }); 600 wireBuilder(builder); 601 602 // Pass the same boolean[] array as used for initialization since our click listener 603 // will be updating its content. 604 verifyMultiChoiceItemsContent(content, checkedTracker); 605 } 606 607 @Test 608 @MediumTest 609 public void testMultiChoiceItemsFromResourcesArray() { 610 final boolean[] checkedTracker = new boolean[] { true, false, true, false }; 611 AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 612 .setTitle(R.string.alert_dialog_title) 613 .setMultiChoiceItems(R.array.alert_dialog_items, checkedTracker, 614 new DialogInterface.OnMultiChoiceClickListener() { 615 @Override 616 public void onClick(DialogInterface dialog, int which, 617 boolean isChecked) { 618 checkedTracker[which] = isChecked; 619 } 620 }); 621 wireBuilder(builder); 622 623 verifyMultiChoiceItemsContent( 624 mActivityTestRule.getActivity().getResources().getStringArray( 625 R.array.alert_dialog_items), 626 checkedTracker); 627 } 628 629 /** 630 * Helper method to verify the state of the single-choice items list. It gets the String 631 * array of content and verifies that: 632 * 633 * 1. The items in the array are rendered as CheckedTextViews inside a ListView 634 * 2. Each item in the array is displayed 635 * 3. Only one row in the ListView is checked, and that corresponds to the passed 636 * integer index. 637 */ 638 private void verifySingleChoiceItemsState(String[] expectedContent, 639 int currentlyExpectedSelectionIndex) { 640 final int expectedCount = expectedContent.length; 641 642 final ListView listView = mAlertDialog.getListView(); 643 assertNotNull("List view is shown", listView); 644 645 final ListAdapter listAdapter = listView.getAdapter(); 646 assertEquals("List has " + expectedCount + " entries", 647 expectedCount, listAdapter.getCount()); 648 for (int i = 0; i < expectedCount; i++) { 649 assertEquals("List entry #" + i, expectedContent[i], listAdapter.getItem(i)); 650 } 651 652 for (int i = 0; i < expectedCount; i++) { 653 Matcher checkedStateMatcher = (i == currentlyExpectedSelectionIndex) ? 654 TestUtilsMatchers.isCheckedTextView() : 655 TestUtilsMatchers.isNonCheckedTextView(); 656 // Check that the corresponding row is rendered as CheckedTextView with expected 657 // checked state. 658 onData(allOf(is(instanceOf(String.class)), is(expectedContent[i]))).inRoot(isDialog()). 659 check(matches(allOf( 660 isDisplayed(), 661 isAssignableFrom(CheckedTextView.class), 662 isDescendantOfA(isAssignableFrom(ListView.class)), 663 checkedStateMatcher))); 664 } 665 } 666 667 private void verifySingleChoiceItemsContent(String[] expectedContent, 668 int initialSelectionIndex, DialogInterface.OnClickListener onClickListener) { 669 final int expectedCount = expectedContent.length; 670 int currentlyExpectedSelectionIndex = initialSelectionIndex; 671 672 onView(withId(R.id.test_button)).perform(click()); 673 674 // Test that all items are showing 675 onView(withText("Dialog title")).inRoot(isDialog()).check(matches(isDisplayed())); 676 verifySingleChoiceItemsState(expectedContent, currentlyExpectedSelectionIndex); 677 678 // We're going to click the first unselected item and test that the click listener has 679 // been invoked. 680 currentlyExpectedSelectionIndex = (currentlyExpectedSelectionIndex == 0) ? 1 : 0; 681 onData(allOf(is(instanceOf(String.class)), 682 is(expectedContent[currentlyExpectedSelectionIndex]))). 683 inRoot(isDialog()).perform(click()); 684 verify(onClickListener, times(1)).onClick(mAlertDialog, currentlyExpectedSelectionIndex); 685 verifySingleChoiceItemsState(expectedContent, currentlyExpectedSelectionIndex); 686 687 // Now click the same item again and test that the selection has not changed 688 onData(allOf(is(instanceOf(String.class)), 689 is(expectedContent[currentlyExpectedSelectionIndex]))). 690 inRoot(isDialog()).perform(click()); 691 verify(onClickListener, times(2)).onClick(mAlertDialog, currentlyExpectedSelectionIndex); 692 verifySingleChoiceItemsState(expectedContent, currentlyExpectedSelectionIndex); 693 694 // Now we're going to click the last item and test that the click listener has been invoked 695 // to update the original state array 696 currentlyExpectedSelectionIndex = expectedCount - 1; 697 onData(allOf(is(instanceOf(String.class)), 698 is(expectedContent[currentlyExpectedSelectionIndex]))). 699 inRoot(isDialog()).perform(click()); 700 verify(onClickListener, times(1)).onClick(mAlertDialog, currentlyExpectedSelectionIndex); 701 verifySingleChoiceItemsState(expectedContent, currentlyExpectedSelectionIndex); 702 } 703 704 @Test 705 @LargeTest 706 public void testSingleChoiceItemsFromRuntimeArray() { 707 final String[] content = new String[] { "Alice", "Bob", "Charlie", "Delta" }; 708 final DialogInterface.OnClickListener mockClickListener = 709 mock(DialogInterface.OnClickListener.class); 710 AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 711 .setTitle(R.string.alert_dialog_title) 712 .setSingleChoiceItems(content, 2, mockClickListener); 713 wireBuilder(builder); 714 715 verifySingleChoiceItemsContent(content, 2, mockClickListener); 716 } 717 718 @Test 719 @LargeTest 720 public void testSingleChoiceItemsFromResourcesArray() { 721 final DialogInterface.OnClickListener mockClickListener = 722 mock(DialogInterface.OnClickListener.class); 723 AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 724 .setTitle(R.string.alert_dialog_title) 725 .setSingleChoiceItems(R.array.alert_dialog_items, 1, mockClickListener); 726 wireBuilder(builder); 727 728 verifySingleChoiceItemsContent(new String[] { "Albania", "Belize", "Chad", "Djibouti" }, 1, 729 mockClickListener); 730 } 731 732 // Tests for icon logic 733 734 @Test 735 @MediumTest 736 public void testIconResource() { 737 AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 738 .setTitle(R.string.alert_dialog_title) 739 .setMessage(R.string.alert_dialog_content) 740 .setIcon(R.drawable.test_drawable_red); 741 742 wireBuilder(builder); 743 744 onView(withId(R.id.test_button)).perform(click()); 745 746 // Find the title icon as a visible view that is the sibling of our title 747 ViewInteraction titleIconInteraction = onView(allOf( 748 isAssignableFrom(ImageView.class), 749 isDisplayed(), 750 hasSibling(withText("Dialog title")))); 751 // And check that it's the expected red color 752 titleIconInteraction.check(matches(TestUtilsMatchers.drawable(0xFFFF6030))); 753 } 754 755 @Test 756 @MediumTest 757 public void testIconResourceChangeAfterInitialSetup() throws Throwable { 758 AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 759 .setTitle(R.string.alert_dialog_title) 760 .setMessage(R.string.alert_dialog_content) 761 .setIcon(R.drawable.test_drawable_red); 762 763 wireBuilder(builder); 764 765 onView(withId(R.id.test_button)).perform(click()); 766 767 // Emulate background loading of the new icon 768 Thread.sleep(1000); 769 770 // Change the icon 771 mActivityTestRule.runOnUiThread(new Runnable() { 772 @Override 773 public void run() { 774 mAlertDialog.setIcon(R.drawable.test_drawable_green); 775 } 776 }); 777 778 // Find the title icon as a visible view that is the sibling of our title 779 ViewInteraction titleIconInteraction = onView(allOf( 780 isAssignableFrom(ImageView.class), 781 isDisplayed(), 782 hasSibling(withText("Dialog title")))); 783 // And check that it's the expected (newly set) green color 784 titleIconInteraction.check(matches(TestUtilsMatchers.drawable(0xFF50E080))); 785 } 786 787 @Test 788 @MediumTest 789 public void testIconResourceChangeWithNoInitialSetup() throws Throwable { 790 AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 791 .setTitle(R.string.alert_dialog_title) 792 .setMessage(R.string.alert_dialog_content); 793 794 wireBuilder(builder); 795 796 onView(withId(R.id.test_button)).perform(click()); 797 798 // Emulate background loading of the new icon 799 Thread.sleep(1000); 800 801 // Change the icon 802 mActivityTestRule.runOnUiThread(new Runnable() { 803 @Override 804 public void run() { 805 mAlertDialog.setIcon(R.drawable.test_drawable_green); 806 } 807 }); 808 809 // Find the title icon as a visible view that is the sibling of our title 810 ViewInteraction titleIconInteraction = onView(allOf( 811 isAssignableFrom(ImageView.class), 812 isDisplayed(), 813 hasSibling(withText("Dialog title")))); 814 // And check that it's the expected (newly set) green color 815 titleIconInteraction.check(matches(TestUtilsMatchers.drawable(0xFF50E080))); 816 } 817 818 @Test 819 @MediumTest 820 public void testIconResourceRemoveAfterInitialSetup() throws Throwable { 821 AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 822 .setTitle(R.string.alert_dialog_title) 823 .setMessage(R.string.alert_dialog_content) 824 .setIcon(R.drawable.test_drawable_red); 825 826 wireBuilder(builder); 827 828 onView(withId(R.id.test_button)).perform(click()); 829 830 // Emulate background resetting of the icon 831 Thread.sleep(1000); 832 833 // Change the icon 834 mActivityTestRule.runOnUiThread(new Runnable() { 835 @Override 836 public void run() { 837 mAlertDialog.setIcon(0); 838 } 839 }); 840 841 // Find the title icon as a visible view that is the sibling of our title 842 ViewInteraction titleIconInteraction = onView(allOf( 843 isAssignableFrom(ImageView.class), 844 isDisplayed(), 845 hasSibling(withText("Dialog title")))); 846 // And check that we couldn't find the title icon (since it's expected to be GONE) 847 titleIconInteraction.check(doesNotExist()); 848 } 849 850 @Test 851 @MediumTest 852 public void testIconDrawable() { 853 AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 854 .setTitle(R.string.alert_dialog_title) 855 .setMessage(R.string.alert_dialog_content) 856 .setIcon(new TestDrawable(0xFF807060, 40, 40)); 857 858 wireBuilder(builder); 859 860 onView(withId(R.id.test_button)).perform(click()); 861 862 // Find the title icon as a visible view that is the sibling of our title 863 ViewInteraction titleIconInteraction = onView(allOf( 864 isAssignableFrom(ImageView.class), 865 isDisplayed(), 866 hasSibling(withText("Dialog title")))); 867 // And check that it's the expected red color 868 titleIconInteraction.check(matches(TestUtilsMatchers.drawable(0xFF807060))); 869 } 870 871 @Test 872 @MediumTest 873 public void testIconResourceDrawableAfterInitialSetup() throws Throwable { 874 AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 875 .setTitle(R.string.alert_dialog_title) 876 .setMessage(R.string.alert_dialog_content) 877 .setIcon(new TestDrawable(0xFF807060, 40, 40)); 878 879 wireBuilder(builder); 880 881 onView(withId(R.id.test_button)).perform(click()); 882 883 // Emulate background loading of the new icon 884 Thread.sleep(1000); 885 886 // Change the icon 887 mActivityTestRule.runOnUiThread(new Runnable() { 888 @Override 889 public void run() { 890 mAlertDialog.setIcon(new TestDrawable(0xFF503090, 40, 40)); 891 } 892 }); 893 894 // Find the title icon as a visible view that is the sibling of our title 895 ViewInteraction titleIconInteraction = onView(allOf( 896 isAssignableFrom(ImageView.class), 897 isDisplayed(), 898 hasSibling(withText("Dialog title")))); 899 // And check that it's the expected (newly set) green color 900 titleIconInteraction.check(matches(TestUtilsMatchers.drawable(0xFF503090))); 901 } 902 903 @Test 904 @MediumTest 905 public void testIconDrawableChangeWithNoInitialSetup() throws Throwable { 906 AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 907 .setTitle(R.string.alert_dialog_title) 908 .setMessage(R.string.alert_dialog_content); 909 910 wireBuilder(builder); 911 912 onView(withId(R.id.test_button)).perform(click()); 913 914 // Emulate background loading of the new icon 915 Thread.sleep(1000); 916 917 // Change the icon 918 mActivityTestRule.runOnUiThread(new Runnable() { 919 @Override 920 public void run() { 921 mAlertDialog.setIcon(new TestDrawable(0xFF503090, 40, 40)); 922 } 923 }); 924 925 // Find the title icon as a visible view that is the sibling of our title 926 ViewInteraction titleIconInteraction = onView(allOf( 927 isAssignableFrom(ImageView.class), 928 isDisplayed(), 929 hasSibling(withText("Dialog title")))); 930 // And check that it's the expected (newly set) green color 931 titleIconInteraction.check(matches(TestUtilsMatchers.drawable(0xFF503090))); 932 } 933 934 @Test 935 @MediumTest 936 public void testIconDrawableRemoveAfterInitialSetup() throws Throwable { 937 AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 938 .setTitle(R.string.alert_dialog_title) 939 .setMessage(R.string.alert_dialog_content) 940 .setIcon(new TestDrawable(0xFF807060, 40, 40)); 941 942 wireBuilder(builder); 943 944 onView(withId(R.id.test_button)).perform(click()); 945 946 // Emulate background resetting of the icon 947 Thread.sleep(1000); 948 949 // Change the icon 950 mActivityTestRule.runOnUiThread(new Runnable() { 951 @Override 952 public void run() { 953 mAlertDialog.setIcon(null); 954 } 955 }); 956 957 // Find the title icon as a visible view that is the sibling of our title 958 ViewInteraction titleIconInteraction = onView(allOf( 959 isAssignableFrom(ImageView.class), 960 isDisplayed(), 961 hasSibling(withText("Dialog title")))); 962 // And check that we couldn't find the title icon (since it's expected to be GONE) 963 titleIconInteraction.check(doesNotExist()); 964 } 965 966 // Tests for buttons logic 967 968 /** 969 * Helper method to verify visibility and text content of dialog buttons. Gets expected texts 970 * for three buttons (positive, negative and neutral) and for each button verifies that: 971 * 972 * If the text is null or empty, that the button is GONE 973 * If the text is not empty, that the button is VISIBLE and shows the corresponding text 974 */ 975 private void verifyButtonContent(String expectedPositiveButtonText, 976 String expectedNegativeButtonText, String expectedNeutralButtonText) { 977 assertTrue("Dialog is showing", mAlertDialog.isShowing()); 978 979 final Button positiveButton = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE); 980 final Button negativeButton = mAlertDialog.getButton(AlertDialog.BUTTON_NEGATIVE); 981 final Button neutralButton = mAlertDialog.getButton(AlertDialog.BUTTON_NEUTRAL); 982 983 if (TextUtils.isEmpty(expectedPositiveButtonText)) { 984 assertEquals("Positive button not shown", View.GONE, positiveButton.getVisibility()); 985 } else { 986 assertEquals("Positive button shown", View.VISIBLE, positiveButton.getVisibility()); 987 assertEquals("Positive button text", expectedPositiveButtonText, 988 positiveButton.getText()); 989 } 990 991 if (TextUtils.isEmpty(expectedNegativeButtonText)) { 992 assertEquals("Negative button not shown", View.GONE, negativeButton.getVisibility()); 993 } else { 994 assertEquals("Negative button shown", View.VISIBLE, negativeButton.getVisibility()); 995 assertEquals("Negative button text", expectedNegativeButtonText, 996 negativeButton.getText()); 997 } 998 999 if (TextUtils.isEmpty(expectedNeutralButtonText)) { 1000 assertEquals("Neutral button not shown", View.GONE, neutralButton.getVisibility()); 1001 } else { 1002 assertEquals("Neutral button shown", View.VISIBLE, neutralButton.getVisibility()); 1003 assertEquals("Neutral button text", expectedNeutralButtonText, 1004 neutralButton.getText()); 1005 } 1006 } 1007 1008 /** 1009 * Helper method to verify dialog state after a button has been clicked. 1010 */ 1011 private void verifyPostButtonClickState(int whichButtonClicked, 1012 DialogInterface.OnDismissListener onDismissListener, 1013 Handler messageHandler) { 1014 // Verify that a Message with expected 'what' field has been posted on our mock handler 1015 ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); 1016 verify(messageHandler, times(1)).sendMessageDelayed( 1017 messageArgumentCaptor.capture(), anyLong()); 1018 assertEquals("Button clicked", whichButtonClicked, messageArgumentCaptor.getValue().what); 1019 // Verify that the dialog is no longer showing 1020 assertFalse("Dialog is not showing", mAlertDialog.isShowing()); 1021 if (onDismissListener != null) { 1022 // And that our mock listener has been called when the dialog was dismissed 1023 verify(onDismissListener, times(1)).onDismiss(mAlertDialog); 1024 } 1025 } 1026 1027 /** 1028 * Helper method to verify dialog state after a button has been clicked. 1029 */ 1030 private void verifyPostButtonClickState(int whichButtonClicked, 1031 DialogInterface.OnClickListener onClickListener, 1032 DialogInterface.OnDismissListener onDismissListener) { 1033 if (onClickListener != null) { 1034 verify(onClickListener, times(1)).onClick(mAlertDialog, whichButtonClicked); 1035 } 1036 assertFalse("Dialog is not showing", mAlertDialog.isShowing()); 1037 if (onDismissListener != null) { 1038 verify(onDismissListener, times(1)).onDismiss(mAlertDialog); 1039 } 1040 } 1041 1042 /** 1043 * Helper method to verify button-related logic for setXXXButton on AlertDialog.Builder 1044 * that gets CharSequence parameter. This method configures the dialog buttons based 1045 * on the passed texts (some of which may be null or empty, in which case the corresponding 1046 * button is not configured), tests the buttons visibility and texts, simulates a click 1047 * on the specified button and then tests the post-click dialog state. 1048 */ 1049 private void verifyDialogButtons(String positiveButtonText, String negativeButtonText, 1050 String neutralButtonText, int whichButtonToClick) { 1051 final AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 1052 .setTitle(R.string.alert_dialog_title); 1053 // Configure buttons with non-empty texts 1054 DialogInterface.OnClickListener mockClickListener = 1055 mock(DialogInterface.OnClickListener.class); 1056 if (!TextUtils.isEmpty(positiveButtonText)) { 1057 builder.setPositiveButton(positiveButtonText, mockClickListener); 1058 } 1059 if (!TextUtils.isEmpty(negativeButtonText)) { 1060 builder.setNegativeButton(negativeButtonText, mockClickListener); 1061 } 1062 if (!TextUtils.isEmpty(neutralButtonText)) { 1063 builder.setNeutralButton(neutralButtonText, mockClickListener); 1064 } 1065 // Set a dismiss listener to verify that the dialog is dismissed on clicking any button 1066 DialogInterface.OnDismissListener mockDismissListener = 1067 mock(DialogInterface.OnDismissListener.class); 1068 builder.setOnDismissListener(mockDismissListener); 1069 1070 // Wire the builder to the button click and click that button to show the dialog 1071 wireBuilder(builder); 1072 onView(withId(R.id.test_button)).perform(click()); 1073 1074 // Check that the dialog is showing the configured buttons 1075 verifyButtonContent(positiveButtonText, negativeButtonText, neutralButtonText); 1076 1077 // Click the specified button and verify the post-click state 1078 String textOfButtonToClick = null; 1079 switch (whichButtonToClick) { 1080 case DialogInterface.BUTTON_POSITIVE: 1081 textOfButtonToClick = positiveButtonText; 1082 break; 1083 case DialogInterface.BUTTON_NEGATIVE: 1084 textOfButtonToClick = negativeButtonText; 1085 break; 1086 case DialogInterface.BUTTON_NEUTRAL: 1087 textOfButtonToClick = neutralButtonText; 1088 break; 1089 } 1090 onView(withText(textOfButtonToClick)).inRoot(isDialog()).perform(click()); 1091 verifyPostButtonClickState(whichButtonToClick, mockClickListener, mockDismissListener); 1092 } 1093 1094 /** 1095 * Helper method to verify button-related logic for setXXXButton on AlertDialog.Builder 1096 * that gets string resource ID parameter. This method configures the dialog buttons based 1097 * on the passed texts (some of which may be null or empty, in which case the corresponding 1098 * button is not configured), tests the buttons visibility and texts, simulates a click 1099 * on the specified button and then tests the post-click dialog state. 1100 */ 1101 private void verifyDialogButtons(@StringRes int positiveButtonTextResId, 1102 @StringRes int negativeButtonTextResId, 1103 @StringRes int neutralButtonTextResId, int whichButtonToClick) { 1104 Context context = mActivityTestRule.getActivity(); 1105 String positiveButtonText = null; 1106 String negativeButtonText = null; 1107 String neutralButtonText = null; 1108 1109 final AlertDialog.Builder builder = new AlertDialog.Builder(context) 1110 .setTitle(R.string.alert_dialog_title); 1111 DialogInterface.OnClickListener mockClickListener = 1112 mock(DialogInterface.OnClickListener.class); 1113 // Configure buttons with non-zero text resource IDs 1114 if (positiveButtonTextResId != 0) { 1115 positiveButtonText = context.getString(positiveButtonTextResId); 1116 builder.setPositiveButton(positiveButtonTextResId, mockClickListener); 1117 } 1118 if (negativeButtonTextResId != 0) { 1119 negativeButtonText = context.getString(negativeButtonTextResId); 1120 builder.setNegativeButton(negativeButtonTextResId, mockClickListener); 1121 } 1122 if (neutralButtonTextResId != 0) { 1123 neutralButtonText = context.getString(neutralButtonTextResId); 1124 builder.setNeutralButton(neutralButtonTextResId, mockClickListener); 1125 } 1126 // Set a dismiss listener to verify that the dialog is dismissed on clicking any button 1127 DialogInterface.OnDismissListener mockDismissListener = 1128 mock(DialogInterface.OnDismissListener.class); 1129 builder.setOnDismissListener(mockDismissListener); 1130 1131 // Wire the builder to the button click and click that button to show the dialog 1132 wireBuilder(builder); 1133 onView(withId(R.id.test_button)).perform(click()); 1134 1135 // Check that the dialog is showing the configured buttons 1136 verifyButtonContent(positiveButtonText, negativeButtonText, neutralButtonText); 1137 1138 // Click the specified button and verify the post-click state 1139 String textOfButtonToClick = null; 1140 switch (whichButtonToClick) { 1141 case DialogInterface.BUTTON_POSITIVE: 1142 textOfButtonToClick = positiveButtonText; 1143 break; 1144 case DialogInterface.BUTTON_NEGATIVE: 1145 textOfButtonToClick = negativeButtonText; 1146 break; 1147 case DialogInterface.BUTTON_NEUTRAL: 1148 textOfButtonToClick = neutralButtonText; 1149 break; 1150 } 1151 onView(withText(textOfButtonToClick)).inRoot(isDialog()).perform(click()); 1152 verifyPostButtonClickState(whichButtonToClick, mockClickListener, mockDismissListener); 1153 } 1154 1155 /** 1156 * Helper method to verify button-related logic for setButton on AlertDialog after the 1157 * dialog has been create()'d. This method configures the dialog buttons based 1158 * on the passed texts (some of which may be null or empty, in which case the corresponding 1159 * button is not configured), tests the buttons visibility and texts, simulates a click 1160 * on the specified button and then tests the post-click dialog state. 1161 */ 1162 private void verifyDialogButtonsPostCreation(final String positiveButtonText, 1163 final String negativeButtonText, final String neutralButtonText, 1164 int whichButtonToClick) { 1165 final AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 1166 .setTitle(R.string.alert_dialog_title); 1167 // Set a dismiss listener to verify that the dialog is dismissed on clicking any button 1168 DialogInterface.OnDismissListener mockDismissListener = 1169 mock(DialogInterface.OnDismissListener.class); 1170 builder.setOnDismissListener(mockDismissListener); 1171 1172 final DialogInterface.OnClickListener mockClickListener = 1173 mock(DialogInterface.OnClickListener.class); 1174 1175 mButton.setOnClickListener(new View.OnClickListener() { 1176 @Override 1177 public void onClick(View v) { 1178 mAlertDialog = builder.create(); 1179 // Configure buttons with non-empty texts 1180 if (!TextUtils.isEmpty(positiveButtonText)) { 1181 mAlertDialog.setButton(DialogInterface.BUTTON_POSITIVE, positiveButtonText, 1182 mockClickListener); 1183 } 1184 if (!TextUtils.isEmpty(negativeButtonText)) { 1185 mAlertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, negativeButtonText, 1186 mockClickListener); 1187 } 1188 if (!TextUtils.isEmpty(neutralButtonText)) { 1189 mAlertDialog.setButton(DialogInterface.BUTTON_NEUTRAL, neutralButtonText, 1190 mockClickListener); 1191 } 1192 1193 mAlertDialog.show(); 1194 } 1195 }); 1196 1197 // Click the button to create the dialog, configure the buttons and show the dialog 1198 onView(withId(R.id.test_button)).perform(click()); 1199 1200 // Check that the dialog is showing the configured buttons 1201 verifyButtonContent(positiveButtonText, negativeButtonText, neutralButtonText); 1202 1203 // Click the specified button and verify the post-click state 1204 String textOfButtonToClick = null; 1205 switch (whichButtonToClick) { 1206 case DialogInterface.BUTTON_POSITIVE: 1207 textOfButtonToClick = positiveButtonText; 1208 break; 1209 case DialogInterface.BUTTON_NEGATIVE: 1210 textOfButtonToClick = negativeButtonText; 1211 break; 1212 case DialogInterface.BUTTON_NEUTRAL: 1213 textOfButtonToClick = neutralButtonText; 1214 break; 1215 } 1216 onView(withText(textOfButtonToClick)).inRoot(isDialog()).perform(click()); 1217 verifyPostButtonClickState(whichButtonToClick, mockClickListener, null); 1218 } 1219 1220 /** 1221 * Helper method to verify button-related logic for setButton on AlertDialog after the 1222 * dialog has been create()'d. This method configures the dialog buttons based 1223 * on the passed texts (some of which may be null or empty, in which case the corresponding 1224 * button is not configured), tests the buttons visibility and texts, simulates a click 1225 * on the specified button and then tests the post-click dialog state. 1226 */ 1227 private void verifyDialogButtonsPostCreationMessage(final String positiveButtonText, 1228 final String negativeButtonText, final String neutralButtonText, 1229 int whichButtonToClick) { 1230 final AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 1231 .setTitle(R.string.alert_dialog_title); 1232 // Set a dismiss listener to verify that the dialog is dismissed on clicking any button 1233 DialogInterface.OnDismissListener mockDismissListener = 1234 mock(DialogInterface.OnDismissListener.class); 1235 builder.setOnDismissListener(mockDismissListener); 1236 1237 final Handler mockMessageHandler = mock(Handler.class); 1238 mButton.setOnClickListener(new View.OnClickListener() { 1239 @Override 1240 public void onClick(View v) { 1241 mAlertDialog = builder.create(); 1242 // Configure buttons with non-empty texts 1243 if (!TextUtils.isEmpty(positiveButtonText)) { 1244 mAlertDialog.setButton(DialogInterface.BUTTON_POSITIVE, positiveButtonText, 1245 Message.obtain(mockMessageHandler, DialogInterface.BUTTON_POSITIVE)); 1246 } 1247 if (!TextUtils.isEmpty(negativeButtonText)) { 1248 mAlertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, negativeButtonText, 1249 Message.obtain(mockMessageHandler, DialogInterface.BUTTON_NEGATIVE)); 1250 } 1251 if (!TextUtils.isEmpty(neutralButtonText)) { 1252 mAlertDialog.setButton(DialogInterface.BUTTON_NEUTRAL, neutralButtonText, 1253 Message.obtain(mockMessageHandler, DialogInterface.BUTTON_NEUTRAL)); 1254 } 1255 1256 mAlertDialog.show(); 1257 } 1258 }); 1259 1260 // Click the button to create the dialog, configure the buttons and show the dialog 1261 onView(withId(R.id.test_button)).perform(click()); 1262 1263 // Check that the dialog is showing the configured buttons 1264 verifyButtonContent(positiveButtonText, negativeButtonText, neutralButtonText); 1265 1266 // Click the specified button and verify the post-click state 1267 String textOfButtonToClick = null; 1268 switch (whichButtonToClick) { 1269 case DialogInterface.BUTTON_POSITIVE: 1270 textOfButtonToClick = positiveButtonText; 1271 break; 1272 case DialogInterface.BUTTON_NEGATIVE: 1273 textOfButtonToClick = negativeButtonText; 1274 break; 1275 case DialogInterface.BUTTON_NEUTRAL: 1276 textOfButtonToClick = neutralButtonText; 1277 break; 1278 } 1279 onView(withText(textOfButtonToClick)).inRoot(isDialog()).perform(click()); 1280 verifyPostButtonClickState(whichButtonToClick, mockDismissListener, mockMessageHandler); 1281 } 1282 1283 @Test 1284 @MediumTest 1285 public void testButtonVisibility() { 1286 final String positiveButtonText = "Positive button"; 1287 final String negativeButtonText = "Negative button"; 1288 final String neutralButtonText = "Neutral button"; 1289 AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity()) 1290 .setTitle(R.string.alert_dialog_title) 1291 .setPositiveButton(positiveButtonText, null) 1292 .setNegativeButton(negativeButtonText, null) 1293 .setNeutralButton(neutralButtonText, null); 1294 wireBuilder(builder); 1295 1296 onView(withId(R.id.test_button)).perform(click()); 1297 1298 // Positive button should be fully displayed with no text eliding 1299 onView(withText(positiveButtonText)).inRoot(isDialog()).check( 1300 matches(isCompletelyDisplayed())); 1301 onView(withText(positiveButtonText)).inRoot(isDialog()).check( 1302 matches(not(hasEllipsizedText()))); 1303 1304 // Negative button should be fully displayed with no text eliding 1305 onView(withText(negativeButtonText)).inRoot(isDialog()).check( 1306 matches(isCompletelyDisplayed())); 1307 onView(withText(negativeButtonText)).inRoot(isDialog()).check( 1308 matches(not(hasEllipsizedText()))); 1309 1310 // Neutral button should be fully displayed with no text eliding 1311 onView(withText(neutralButtonText)).inRoot(isDialog()).check( 1312 matches(isCompletelyDisplayed())); 1313 onView(withText(neutralButtonText)).inRoot(isDialog()).check( 1314 matches(not(hasEllipsizedText()))); 1315 } 1316 1317 @Test 1318 @LargeTest 1319 public void testButtons() { 1320 // Positive-only button 1321 verifyDialogButtons("Positive", null, null, AlertDialog.BUTTON_POSITIVE); 1322 verifyDialogButtons(R.string.alert_dialog_positive_button, 0, 0, 1323 AlertDialog.BUTTON_POSITIVE); 1324 verifyDialogButtonsPostCreation("Post positive", null, null, AlertDialog.BUTTON_POSITIVE); 1325 verifyDialogButtonsPostCreationMessage("Message positive", null, null, 1326 AlertDialog.BUTTON_POSITIVE); 1327 1328 // Negative-only button 1329 verifyDialogButtons(null, "Negative", null, AlertDialog.BUTTON_NEGATIVE); 1330 verifyDialogButtons(0, R.string.alert_dialog_negative_button, 0, 1331 AlertDialog.BUTTON_NEGATIVE); 1332 verifyDialogButtonsPostCreation(null, "Post negative", null, AlertDialog.BUTTON_NEGATIVE); 1333 verifyDialogButtonsPostCreationMessage(null, "Message negative", null, 1334 AlertDialog.BUTTON_NEGATIVE); 1335 1336 // Neutral-only button 1337 verifyDialogButtons(null, null, "Neutral", AlertDialog.BUTTON_NEUTRAL); 1338 verifyDialogButtons(0, 0, R.string.alert_dialog_neutral_button, AlertDialog.BUTTON_NEUTRAL); 1339 verifyDialogButtonsPostCreation(null, null, "Post neutral", AlertDialog.BUTTON_NEUTRAL); 1340 verifyDialogButtonsPostCreationMessage(null, null, "Message neutral", 1341 AlertDialog.BUTTON_NEUTRAL); 1342 1343 // Show positive and negative, click positive 1344 verifyDialogButtons(R.string.alert_dialog_positive_button, 1345 R.string.alert_dialog_negative_button, 0, AlertDialog.BUTTON_POSITIVE); 1346 1347 // Show positive and neutral, click neutral 1348 verifyDialogButtons("Positive", null, "Neutral", AlertDialog.BUTTON_NEUTRAL); 1349 1350 // Show negative and neutral, click negative 1351 verifyDialogButtonsPostCreationMessage(null, "Message negative", 1352 "Message neutral", AlertDialog.BUTTON_NEGATIVE); 1353 1354 // Show all, click positive 1355 verifyDialogButtonsPostCreation("Post positive", "Post negative", "Post neutral", 1356 AlertDialog.BUTTON_POSITIVE); 1357 } 1358 1359 private static class TestDrawable extends ColorDrawable { 1360 private int mWidth; 1361 private int mHeight; 1362 1363 public TestDrawable(@ColorInt int color, int width, int height) { 1364 super(color); 1365 mWidth = width; 1366 mHeight = height; 1367 } 1368 1369 @Override 1370 public int getIntrinsicWidth() { 1371 return mWidth; 1372 } 1373 1374 @Override 1375 public int getIntrinsicHeight() { 1376 return mHeight; 1377 } 1378 } 1379} 1380