Merge "Second pass for AlertDialog tests." into mnc-ub-dev
diff --git a/v7/appcompat/tests/res/values/strings.xml b/v7/appcompat/tests/res/values/strings.xml
index 3780f1b..2cbf27e 100644
--- a/v7/appcompat/tests/res/values/strings.xml
+++ b/v7/appcompat/tests/res/values/strings.xml
@@ -28,4 +28,10 @@
     <string name="alert_dialog_show">Show alert dialog</string>
     <string name="alert_dialog_title">Dialog title</string>
     <string name="alert_dialog_content">Dialog content</string>
+    <string-array name="alert_dialog_items">
+        <item>Albania</item>
+        <item>Belize</item>
+        <item>Chad</item>
+        <item>Djibouti</item>
+    </string-array>
 </resources>
diff --git a/v7/appcompat/tests/src/android/support/v7/app/AlertDialogTest.java b/v7/appcompat/tests/src/android/support/v7/app/AlertDialogTest.java
index db92d76..4213d0e 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/AlertDialogTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/AlertDialogTest.java
@@ -26,7 +26,11 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.View;
 import android.widget.Button;
+import android.widget.CheckedTextView;
 import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import org.hamcrest.Matcher;
 
 import static android.support.test.espresso.Espresso.onData;
 import static android.support.test.espresso.Espresso.onView;
@@ -40,6 +44,22 @@
 import static org.hamcrest.core.AllOf.allOf;
 import static org.hamcrest.core.Is.is;
 
+/**
+ * Tests in this class make a few assumptions about the underlying implementation of
+ * <code>AlertDialog</code>. While the assumptions don't go all the way down to individual
+ * <code>R.id</code> references or very specific layout arrangements, internal refactoring
+ * of <code>AlertDialog</code> might require corresponding restructuring of the matching
+ * tests. Specifically:
+ *
+ * <ul>
+ *     <li>Testing <code>setIcon</code> API assumes that the icon is displayed by a separate
+ *     <code>ImageView</code> which is a sibling of a title view.</li>
+ *     <li>Testing <code>setMultiChoiceItems</code> API assumes that each item in the list
+ *     is rendered by a single <code></code>CheckedTextView</code>.</li>
+ *     <li>Testing <code>setSingleChoiceItems</code> API assumes that each item in the list
+ *     is rendered by a single <code></code>CheckedTextView</code>.</li>
+ * </ul>
+ */
 public class AlertDialogTest extends ActivityInstrumentationTestCase2<AlertDialogTestActivity> {
     private Button mButton;
 
@@ -86,6 +106,9 @@
         onView(withText("Dialog content")).inRoot(isDialog()).check(matches(isDisplayed()));
         onView(withText("Dialog content")).inRoot(isDialog()).check(
                 isBelow(withText("Dialog title")));
+
+        ListView listView = mAlertDialog.getListView();
+        assertNull("No list view", listView);
     }
 
     @SmallTest
@@ -134,12 +157,42 @@
         assertFalse("Dialog is not canceled", mIsCanceledCalled);
     }
 
+    private void verifySimpleItemsContent(String[] expectedContent) {
+        final int expectedCount = expectedContent.length;
+
+        onView(withId(R.id.test_button)).perform(click());
+
+        final ListView listView = mAlertDialog.getListView();
+        assertNotNull("List view is shown", listView);
+
+        final ListAdapter listAdapter = listView.getAdapter();
+        assertEquals("List has " + expectedCount + " entries",
+                expectedCount, listAdapter.getCount());
+        for (int i = 0; i < expectedCount; i++) {
+            assertEquals("List entry #" + i, expectedContent[i], listAdapter.getItem(i));
+        }
+
+        // Test that all items are showing
+        onView(withText("Dialog title")).inRoot(isDialog()).check(matches(isDisplayed()));
+        for (int i = 0; i < expectedCount; i++) {
+            onData(allOf(is(instanceOf(String.class)), is(expectedContent[i]))).inRoot(isDialog()).
+                    check(matches(isDisplayed()));
+        }
+
+        // Test that a click on an item invokes the registered listener
+        assertEquals("Before list item click", -1, mClickedItemIndex);
+        int indexToClick = expectedCount - 2;
+        onData(allOf(is(instanceOf(String.class)), is(expectedContent[indexToClick]))).
+                inRoot(isDialog()).perform(click());
+        assertEquals("List item clicked", indexToClick, mClickedItemIndex);
+    }
 
     @SmallTest
-    public void testListContent() {
+    public void testSimpleItemsFromRuntimeArray() {
+        final String[] content = new String[] { "Alice", "Bob", "Charlie", "Delta" };
         AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
                 .setTitle(R.string.alert_dialog_title)
-                .setItems(new String[]{"Alice", "Bob", "Charlie", "Delta"},
+                .setItems(content,
                         new DialogInterface.OnClickListener() {
                             @Override
                             public void onClick(DialogInterface dialog, int which) {
@@ -148,23 +201,259 @@
                         });
         wireBuilder(builder);
 
+        verifySimpleItemsContent(content);
+    }
+
+    @SmallTest
+    public void testSimpleItemsFromResourcesArray() {
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+                .setTitle(R.string.alert_dialog_title)
+                .setItems(R.array.alert_dialog_items,
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                mClickedItemIndex = which;
+                            }
+                        });
+        wireBuilder(builder);
+
+        verifySimpleItemsContent(
+                getActivity().getResources().getStringArray(R.array.alert_dialog_items));
+    }
+
+    /**
+     * Helper method to verify the state of the multi-choice items list. It gets the String
+     * array of content and verifies that:
+     *
+     * 1. The items in the array are rendered as CheckedTextViews inside a ListView
+     * 2. Each item in the array is displayed
+     * 3. Checked state of each row in the ListView corresponds to the matching entry in the
+     *    passed boolean array
+     */
+    private void verifyMultiChoiceItemsState(String[] expectedContent,
+            boolean[] checkedTracker) {
+        final int expectedCount = expectedContent.length;
+
+        final ListView listView = mAlertDialog.getListView();
+        assertNotNull("List view is shown", listView);
+
+        final ListAdapter listAdapter = listView.getAdapter();
+        assertEquals("List has " + expectedCount + " entries",
+                expectedCount, listAdapter.getCount());
+        for (int i = 0; i < expectedCount; i++) {
+            assertEquals("List entry #" + i, expectedContent[i], listAdapter.getItem(i));
+        }
+
+        for (int i = 0; i < expectedCount; i++) {
+            Matcher checkedStateMatcher = checkedTracker[i] ? TestUtilsMatchers.isCheckedTextView() :
+                    TestUtilsMatchers.isNonCheckedTextView();
+            // Check that the corresponding row is rendered as CheckedTextView with expected
+            // checked state.
+            onData(allOf(is(instanceOf(String.class)), is(expectedContent[i]))).inRoot(isDialog()).
+                    check(matches(allOf(
+                            isDisplayed(),
+                            isAssignableFrom(CheckedTextView.class),
+                            isDescendantOfA(isAssignableFrom(ListView.class)),
+                            checkedStateMatcher)));
+        }
+    }
+
+    private void verifyMultiChoiceItemsContent(String[] expectedContent,
+            final boolean[] checkedTracker) {
+        final int expectedCount = expectedContent.length;
+
+        onView(withId(R.id.test_button)).perform(click());
+
+        final ListView listView = mAlertDialog.getListView();
+        assertNotNull("List view is shown", listView);
+
+        final ListAdapter listAdapter = listView.getAdapter();
+        assertEquals("List has " + expectedCount + " entries",
+                expectedCount, listAdapter.getCount());
+        for (int i = 0; i < expectedCount; i++) {
+            assertEquals("List entry #" + i, expectedContent[i], listAdapter.getItem(i));
+        }
+
+        // Test that all items are showing
+        onView(withText("Dialog title")).inRoot(isDialog()).check(matches(isDisplayed()));
+        verifyMultiChoiceItemsState(expectedContent, checkedTracker);
+
+        // We're going to click item #1 and test that the click listener has been invoked to
+        // update the original state array
+        boolean[] expectedAfterClick1 = checkedTracker.clone();
+        expectedAfterClick1[1] = !expectedAfterClick1[1];
+        onData(allOf(is(instanceOf(String.class)), is(expectedContent[1]))).
+                inRoot(isDialog()).perform(click());
+        verifyMultiChoiceItemsState(expectedContent, expectedAfterClick1);
+
+        // Now click item #1 again and test that the click listener has been invoked to update the
+        // original state array again
+        expectedAfterClick1[1] = !expectedAfterClick1[1];
+        onData(allOf(is(instanceOf(String.class)), is(expectedContent[1]))).
+                inRoot(isDialog()).perform(click());
+        verifyMultiChoiceItemsState(expectedContent, expectedAfterClick1);
+
+        // Now we're going to click the last item and test that the click listener has been invoked
+        // to update the original state array
+        boolean[] expectedAfterClickLast = checkedTracker.clone();
+        expectedAfterClickLast[expectedCount - 1] = !expectedAfterClickLast[expectedCount - 1];
+        onData(allOf(is(instanceOf(String.class)), is(expectedContent[expectedCount - 1]))).
+                inRoot(isDialog()).perform(click());
+        verifyMultiChoiceItemsState(expectedContent, expectedAfterClickLast);
+    }
+
+    @SmallTest
+    public void testMultiChoiceItemsFromRuntimeArray() {
+        final String[] content = new String[] { "Alice", "Bob", "Charlie", "Delta" };
+        final boolean[] checkedTracker = new boolean[] { false, true, false, false };
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+                .setTitle(R.string.alert_dialog_title)
+                .setMultiChoiceItems(
+                        content, checkedTracker,
+                        new DialogInterface.OnMultiChoiceClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which,
+                                    boolean isChecked) {
+                                checkedTracker[which] = isChecked;
+                            }
+                        });
+        wireBuilder(builder);
+
+        // Pass the same boolean[] array as used for initialization since our click listener
+        // will be updating its content.
+        verifyMultiChoiceItemsContent(content, checkedTracker);
+    }
+
+    @SmallTest
+    public void testMultiChoiceItemsFromResourcesArray() {
+        final boolean[] checkedTracker = new boolean[] { true, false, true, false };
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+                .setTitle(R.string.alert_dialog_title)
+                .setMultiChoiceItems(R.array.alert_dialog_items, checkedTracker,
+                        new DialogInterface.OnMultiChoiceClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which,
+                                    boolean isChecked) {
+                                checkedTracker[which] = isChecked;
+                            }
+                        });
+        wireBuilder(builder);
+
+        verifyMultiChoiceItemsContent(
+                getActivity().getResources().getStringArray(R.array.alert_dialog_items),
+                checkedTracker);
+    }
+
+    /**
+     * Helper method to verify the state of the single-choice items list. It gets the String
+     * array of content and verifies that:
+     *
+     * 1. The items in the array are rendered as CheckedTextViews inside a ListView
+     * 2. Each item in the array is displayed
+     * 3. Only one row in the ListView is checked, and that corresponds to the passed
+     *    integer index.
+     */
+    private void verifySingleChoiceItemsState(String[] expectedContent,
+            int currentlyExpectedSelectionIndex) {
+        final int expectedCount = expectedContent.length;
+
+        final ListView listView = mAlertDialog.getListView();
+        assertNotNull("List view is shown", listView);
+
+        final ListAdapter listAdapter = listView.getAdapter();
+        assertEquals("List has " + expectedCount + " entries",
+                expectedCount, listAdapter.getCount());
+        for (int i = 0; i < expectedCount; i++) {
+            assertEquals("List entry #" + i, expectedContent[i], listAdapter.getItem(i));
+        }
+
+        for (int i = 0; i < expectedCount; i++) {
+            Matcher checkedStateMatcher = (i == currentlyExpectedSelectionIndex) ?
+                    TestUtilsMatchers.isCheckedTextView() :
+                    TestUtilsMatchers.isNonCheckedTextView();
+            // Check that the corresponding row is rendered as CheckedTextView with expected
+            // checked state.
+            onData(allOf(is(instanceOf(String.class)), is(expectedContent[i]))).inRoot(isDialog()).
+                    check(matches(allOf(
+                            isDisplayed(),
+                            isAssignableFrom(CheckedTextView.class),
+                            isDescendantOfA(isAssignableFrom(ListView.class)),
+                            checkedStateMatcher)));
+        }
+    }
+
+    private void verifySingleChoiceItemsContent(String[] expectedContent,
+            int initialSelectionIndex) {
+        final int expectedCount = expectedContent.length;
+        int currentlyExpectedSelectionIndex = initialSelectionIndex;
+
         onView(withId(R.id.test_button)).perform(click());
 
         // Test that all items are showing
         onView(withText("Dialog title")).inRoot(isDialog()).check(matches(isDisplayed()));
-        onData(allOf(is(instanceOf(String.class)), is("Alice"))).inRoot(isDialog()).
-                check(matches(isDisplayed()));
-        onData(allOf(is(instanceOf(String.class)), is("Bob"))).inRoot(isDialog()).
-                check(matches(isDisplayed()));
-        onData(allOf(is(instanceOf(String.class)), is("Charlie"))).inRoot(isDialog()).
-                check(matches(isDisplayed()));
-        onData(allOf(is(instanceOf(String.class)), is("Delta"))).inRoot(isDialog()).
-                check(matches(isDisplayed()));
+        verifySingleChoiceItemsState(expectedContent, currentlyExpectedSelectionIndex);
 
-        // Test that a click on an item invokes the registered listener
-        onData(allOf(is(instanceOf(String.class)), is("Charlie"))).inRoot(isDialog()).
-                perform(click());
-        assertEquals("List item clicked", 2, mClickedItemIndex);
+        // We're going to click the first unselected item and test that the click listener has
+        // been invoked.
+        currentlyExpectedSelectionIndex = (currentlyExpectedSelectionIndex == 0) ? 1 : 0;
+        onData(allOf(is(instanceOf(String.class)),
+                is(expectedContent[currentlyExpectedSelectionIndex]))).
+                    inRoot(isDialog()).perform(click());
+        assertEquals("Selected first single-choice item",
+                currentlyExpectedSelectionIndex, mClickedItemIndex);
+        verifySingleChoiceItemsState(expectedContent, currentlyExpectedSelectionIndex);
+
+        // Now click the same item again and test that the selection has not changed
+        onData(allOf(is(instanceOf(String.class)),
+                is(expectedContent[currentlyExpectedSelectionIndex]))).
+                inRoot(isDialog()).perform(click());
+        assertEquals("Selected first single-choice item again",
+                currentlyExpectedSelectionIndex, mClickedItemIndex);
+        verifySingleChoiceItemsState(expectedContent, currentlyExpectedSelectionIndex);
+
+        // Now we're going to click the last item and test that the click listener has been invoked
+        // to update the original state array
+        currentlyExpectedSelectionIndex = expectedCount - 1;
+        onData(allOf(is(instanceOf(String.class)),
+                is(expectedContent[currentlyExpectedSelectionIndex]))).
+                inRoot(isDialog()).perform(click());
+        assertEquals("Selected last single-choice item",
+                currentlyExpectedSelectionIndex, mClickedItemIndex);
+        verifySingleChoiceItemsState(expectedContent, currentlyExpectedSelectionIndex);
+    }
+
+    @SmallTest
+    public void testSingleChoiceItemsFromRuntimeArray() {
+        final String[] content = new String[] { "Alice", "Bob", "Charlie", "Delta" };
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+                .setTitle(R.string.alert_dialog_title)
+                .setSingleChoiceItems(
+                        content, 2,
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                mClickedItemIndex = which;
+                            }
+                        });
+        wireBuilder(builder);
+
+        verifySingleChoiceItemsContent(content, 2);
+    }
+
+    @SmallTest
+    public void testSingleChoiceItemsFromResourcesArray() {
+        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+                .setTitle(R.string.alert_dialog_title)
+                .setSingleChoiceItems(R.array.alert_dialog_items, 1,
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                mClickedItemIndex = which;
+                            }
+                        });
+        wireBuilder(builder);
+
+        verifySingleChoiceItemsContent(new String[] { "Albania", "Belize", "Chad", "Djibouti" }, 1);
     }
 
     @SmallTest
diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java
index 6aa8ec2..319583e 100644
--- a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java
+++ b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java
@@ -20,6 +20,7 @@
 import android.support.annotation.ColorInt;
 import android.support.test.espresso.matcher.BoundedMatcher;
 import android.view.View;
+import android.widget.CheckedTextView;
 import android.widget.ImageView;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
@@ -64,4 +65,56 @@
             }
         };
     }
+
+    /**
+     * Returns a matcher that matches <code>CheckedTextView</code>s which are in checked state.
+     */
+    public static Matcher isCheckedTextView() {
+        return new BoundedMatcher<View, CheckedTextView>(CheckedTextView.class) {
+            private String failedDescription;
+
+            @Override
+            public void describeTo(final Description description) {
+                description.appendText("checked text view: ");
+
+                description.appendText(failedDescription);
+            }
+
+            @Override
+            public boolean matchesSafely(final CheckedTextView view) {
+                if (view.isChecked()) {
+                    return true;
+                }
+
+                failedDescription = "not checked";
+                return false;
+            }
+        };
+    }
+
+    /**
+     * Returns a matcher that matches <code>CheckedTextView</code>s which are in checked state.
+     */
+    public static Matcher isNonCheckedTextView() {
+        return new BoundedMatcher<View, CheckedTextView>(CheckedTextView.class) {
+            private String failedDescription;
+
+            @Override
+            public void describeTo(final Description description) {
+                description.appendText("non checked text view: ");
+
+                description.appendText(failedDescription);
+            }
+
+            @Override
+            public boolean matchesSafely(final CheckedTextView view) {
+                if (!view.isChecked()) {
+                    return true;
+                }
+
+                failedDescription = "checked";
+                return false;
+            }
+        };
+    }
 }