Add instrumentation tests for list items

Fixes: 158782293
Fixes: 149053988
Test: atest CarUILibUnitTests

Change-Id: Id7fc3603bccb32ab68ea59b037d1c91d74c20cf0
diff --git a/car-ui-lib/tests/unit/AndroidManifest.xml b/car-ui-lib/tests/unit/AndroidManifest.xml
index 4848fca..e499db5 100644
--- a/car-ui-lib/tests/unit/AndroidManifest.xml
+++ b/car-ui-lib/tests/unit/AndroidManifest.xml
@@ -19,6 +19,7 @@
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
         <activity android:name="com.android.car.ui.TestActivity" />
+        <activity android:name="com.android.car.ui.recyclerview.CarUiRecyclerViewTestActivity" />
         <activity android:name="com.android.car.ui.FocusAreaTestActivity" />
         <activity android:name="com.android.car.ui.FocusParkingViewTestActivity" />
         <activity
diff --git a/car-ui-lib/tests/unit/src/com/android/car/ui/recyclerview/CarUiListItemTest.java b/car-ui-lib/tests/unit/src/com/android/car/ui/recyclerview/CarUiListItemTest.java
new file mode 100644
index 0000000..d5f5d13
--- /dev/null
+++ b/car-ui-lib/tests/unit/src/com/android/car/ui/recyclerview/CarUiListItemTest.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui.recyclerview;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isChecked;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.isNotChecked;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.view.View;
+
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.car.ui.tests.unit.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentMatchers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Unit tests for {@link CarUiListItem}. */
+public class CarUiListItemTest {
+
+    private CarUiRecyclerView mCarUiRecyclerView;
+
+    @Rule
+    public ActivityTestRule<CarUiRecyclerViewTestActivity> mActivityRule =
+            new ActivityTestRule<>(CarUiRecyclerViewTestActivity.class);
+
+    @Before
+    public void setUp() {
+        mCarUiRecyclerView = mActivityRule.getActivity().requireViewById(R.id.list);
+    }
+
+    @Test
+    public void testItemVisibility_withTitle() {
+        List<CarUiListItem> items = new ArrayList<>();
+
+        CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+        item.setTitle("Test title");
+        items.add(item);
+
+        mCarUiRecyclerView.post(
+                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+
+        onView(withId(R.id.title)).check(matches(isDisplayed()));
+        onView(withId(R.id.body)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.icon_container)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.action_container)).check(matches(not(isDisplayed())));
+    }
+
+    @Test
+    public void testItemVisibility_withBody() {
+        List<CarUiListItem> items = new ArrayList<>();
+
+        CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+        item.setBody("Test body");
+        items.add(item);
+
+        mCarUiRecyclerView.post(
+                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+
+        onView(withId(R.id.body)).check(matches(isDisplayed()));
+        onView(withId(R.id.title)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.icon_container)).check(matches(not(isDisplayed())));
+        onView(withId(R.id.action_container)).check(matches(not(isDisplayed())));
+    }
+
+    @Test
+    public void testItemVisibility_withTitle_withBodyAndIcon() {
+        List<CarUiListItem> items = new ArrayList<>();
+
+        CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE);
+        item.setTitle("Test title");
+        item.setBody("Test body");
+        item.setIcon(mActivityRule.getActivity().getDrawable(R.drawable.car_ui_icon_close));
+        items.add(item);
+
+        mCarUiRecyclerView.post(
+                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+
+        onView(withId(R.id.title)).check(matches(isDisplayed()));
+        onView(withId(R.id.body)).check(matches(isDisplayed()));
+        onView(withId(R.id.icon_container)).check(matches(isDisplayed()));
+        onView(withId(R.id.action_container)).check(matches(not(isDisplayed())));
+    }
+
+    @Test
+    public void testItem_withCheckbox() {
+        List<CarUiListItem> items = new ArrayList<>();
+
+        CarUiContentListItem.OnCheckedChangeListener mockOnCheckedChangeListener = mock(
+                CarUiContentListItem.OnCheckedChangeListener.class);
+
+        CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.CHECK_BOX);
+        item.setTitle("Test item with checkbox");
+        item.setOnCheckedChangeListener(mockOnCheckedChangeListener);
+        items.add(item);
+
+        mCarUiRecyclerView.post(
+                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+
+        onView(withId(R.id.title)).check(matches(isDisplayed()));
+        onView(withId(R.id.checkbox_widget)).check(matches(isDisplayed()));
+        onView(withId(R.id.action_divider)).check(matches(not(isDisplayed())));
+
+        // List item with checkbox should be initially unchecked.
+        onView(withId(R.id.checkbox_widget)).check(matches(isNotChecked()));
+        // Clicks anywhere on the item should toggle the checkbox
+        onView(withId(R.id.title)).perform(click());
+        onView(withId(R.id.checkbox_widget)).check(matches(isChecked()));
+        // Check that onCheckChangedListener was invoked.
+        verify(mockOnCheckedChangeListener, times(1)).onCheckedChanged(item, true);
+
+        // Uncheck checkbox with click on the action container
+        onView(withId(R.id.action_container)).perform(click());
+        onView(withId(R.id.checkbox_widget)).check(matches(isNotChecked()));
+        // Check that onCheckChangedListener was invoked.
+        verify(mockOnCheckedChangeListener, times(1)).onCheckedChanged(item, false);
+    }
+
+    @Test
+    public void testItem_withSwitch() {
+        List<CarUiListItem> items = new ArrayList<>();
+
+        CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.SWITCH);
+        item.setBody("Test item with switch");
+        item.setChecked(true);
+        item.setActionDividerVisible(true);
+        items.add(item);
+
+        mCarUiRecyclerView.post(
+                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+
+        onView(withId(R.id.body)).check(matches(isDisplayed()));
+        onView(withId(R.id.switch_widget)).check(matches(isDisplayed()));
+        onView(withId(R.id.action_divider)).check(matches(isDisplayed()));
+
+        // List item with checkbox should be initially checked.
+        onView(withId(R.id.switch_widget)).check(matches(isChecked()));
+        // Clicks anywhere on the item should toggle the switch
+        onView(withId(R.id.switch_widget)).perform(click());
+        onView(withId(R.id.switch_widget)).check(matches(isNotChecked()));
+        // Uncheck checkbox with click on the action container
+        onView(withId(R.id.body)).perform(click());
+        onView(withId(R.id.switch_widget)).check(matches(isChecked()));
+    }
+
+    @Test
+    public void testItem_withRadioButton() {
+        List<CarUiListItem> items = new ArrayList<>();
+
+        CarUiContentListItem item = new CarUiContentListItem(
+                CarUiContentListItem.Action.RADIO_BUTTON);
+        item.setTitle("Test item with radio button");
+        item.setChecked(false);
+        items.add(item);
+
+        mCarUiRecyclerView.post(
+                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+
+        onView(withId(R.id.title)).check(matches(isDisplayed()));
+        onView(withId(R.id.radio_button_widget)).check(matches(isDisplayed()));
+
+        // List item with checkbox should be initially not checked.
+        onView(withId(R.id.radio_button_widget)).check(matches(isNotChecked()));
+        // Clicks anywhere on the item should toggle the radio button.
+        onView(withId(R.id.radio_button_widget)).perform(click());
+        onView(withId(R.id.radio_button_widget)).check(matches(isChecked()));
+
+        // Repeated clicks on a selected radio button should not toggle the element once checked.
+        onView(withId(R.id.title)).perform(click());
+        onView(withId(R.id.radio_button_widget)).check(matches(isChecked()));
+    }
+
+    @Test
+    public void testItem_withListener() {
+        List<CarUiListItem> items = new ArrayList<>();
+
+        CarUiContentListItem.OnClickListener mockOnCheckedChangeListener = mock(
+                CarUiContentListItem.OnClickListener.class);
+
+        CarUiContentListItem item = new CarUiContentListItem(
+                CarUiContentListItem.Action.NONE);
+        item.setIcon(mActivityRule.getActivity().getDrawable(R.drawable.car_ui_icon_close));
+        item.setTitle("Test item with listener");
+        item.setBody("Body text");
+        item.setOnItemClickedListener(mockOnCheckedChangeListener);
+        items.add(item);
+
+        mCarUiRecyclerView.post(
+                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+
+        onView(withId(R.id.title)).check(matches(isDisplayed()));
+
+        // Clicks anywhere on the item should toggle the listener
+        onView(withId(R.id.title)).perform(click());
+        verify(mockOnCheckedChangeListener, times(1)).onClick(item);
+
+        onView(withId(R.id.body)).perform(click());
+        verify(mockOnCheckedChangeListener, times(2)).onClick(item);
+
+        onView(withId(R.id.icon_container)).perform(click());
+        verify(mockOnCheckedChangeListener, times(3)).onClick(item);
+    }
+
+    @Test
+    public void testItem_withSupplementalIconAndIconOnClickListener() {
+        List<CarUiListItem> items = new ArrayList<>();
+
+        CarUiContentListItem.OnClickListener mockedItemOnClickListener = mock(
+                CarUiContentListItem.OnClickListener.class);
+        View.OnClickListener mockedIconListener = mock(View.OnClickListener.class);
+
+        CarUiContentListItem item = new CarUiContentListItem(
+                CarUiContentListItem.Action.ICON);
+        item.setSupplementalIcon(
+                mActivityRule.getActivity().getDrawable(R.drawable.car_ui_icon_close),
+                mockedIconListener);
+        item.setOnItemClickedListener(mockedItemOnClickListener);
+        item.setTitle("Test item with listeners");
+        items.add(item);
+
+        mCarUiRecyclerView.post(
+                () -> mCarUiRecyclerView.setAdapter(new CarUiListItemAdapter(items)));
+
+        onView(withId(R.id.title)).check(matches(isDisplayed()));
+
+        // Clicks anywhere on the item (outside of the icon) should only invoke the item click
+        // listener.
+        onView(withId(R.id.title)).perform(click());
+        verify(mockedItemOnClickListener, times(1)).onClick(item);
+
+        // Clicks anywhere on the icon should invoke both listeners.
+        onView(withId(R.id.action_container)).perform(click());
+        verify(mockedItemOnClickListener, times(2)).onClick(item);
+        verify(mockedIconListener, times(1)).onClick(ArgumentMatchers.any(View.class));
+    }
+
+    @Test
+    public void testRadioButtonListItemAdapter() {
+        List<CarUiRadioButtonListItem> items = new ArrayList<>();
+
+        CarUiRadioButtonListItem itemOne = new CarUiRadioButtonListItem();
+        String itemOneTitle = "Item 1";
+        itemOne.setTitle(itemOneTitle);
+        items.add(itemOne);
+
+        CarUiRadioButtonListItem itemTwo = new CarUiRadioButtonListItem();
+        String itemTwoTitle = "Item 2";
+        itemTwo.setTitle(itemTwoTitle);
+        items.add(itemTwo);
+
+        CarUiRadioButtonListItem itemThree = new CarUiRadioButtonListItem();
+        String itemThreeTitle = "Item 3";
+        itemThree.setTitle(itemThreeTitle);
+        items.add(itemThree);
+
+        mCarUiRecyclerView.post(
+                () -> mCarUiRecyclerView.setAdapter(new CarUiRadioButtonListItemAdapter(items)));
+
+        onView(withText(itemOneTitle)).check(matches(isDisplayed()));
+        onView(withText(itemTwoTitle)).check(matches(isDisplayed()));
+        onView(withText(itemThreeTitle)).check(matches(isDisplayed()));
+
+        // All items are initially unchecked.
+        assertFalse(itemOne.isChecked());
+        assertFalse(itemTwo.isChecked());
+        assertFalse(itemThree.isChecked());
+
+        // Select first item.
+        onView(withText(itemOneTitle)).perform(click());
+        assertTrue(itemOne.isChecked());
+        assertFalse(itemTwo.isChecked());
+        assertFalse(itemThree.isChecked());
+
+        // Select second item.
+        onView(withText(itemTwoTitle)).perform(click());
+        assertFalse(itemOne.isChecked());
+        assertTrue(itemTwo.isChecked());
+        assertFalse(itemThree.isChecked());
+
+        // Select third item.
+        onView(withText(itemThreeTitle)).perform(click());
+        assertFalse(itemOne.isChecked());
+        assertFalse(itemTwo.isChecked());
+        assertTrue(itemThree.isChecked());
+    }
+}
diff --git a/car-ui-lib/tests/unit/src/com/android/car/ui/recyclerview/CarUiRecyclerViewTestActivity.java b/car-ui-lib/tests/unit/src/com/android/car/ui/recyclerview/CarUiRecyclerViewTestActivity.java
new file mode 100644
index 0000000..6fb5746
--- /dev/null
+++ b/car-ui-lib/tests/unit/src/com/android/car/ui/recyclerview/CarUiRecyclerViewTestActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui.recyclerview;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.car.ui.tests.unit.R;
+
+/**
+ * An {@link Activity} that contains only an empty {@link CarUiRecyclerView}.
+ */
+public class CarUiRecyclerViewTestActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.car_ui_recycler_view_test_activity);
+    }
+}