Unit tests for PeopleHubViewController.kt

Specifically, this tests the glue code between the data source
(currently implemented as an in-flux prototype) and the UI (abstracted
behind a boundary interface, which simply forward to View methods).

Test: atest
Change-Id: Iee308af4680bfc43427c66c88926f64ee9f812c6
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt
index 4570989..4f03003 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubModule.kt
@@ -23,7 +23,7 @@
 abstract class PeopleHubModule {
 
     @Binds
-    abstract fun peopleHubSectionFooterViewController(
+    abstract fun peopleHubSectionFooterViewAdapter(
         impl: PeopleHubSectionFooterViewAdapterImpl
     ): PeopleHubSectionFooterViewAdapter
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
index fe257d9..9091d94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
@@ -180,8 +180,7 @@
     if (!isMessagingNotification()) {
         return null
     }
-    val clickIntent = sbn.notification.contentIntent
-            ?: return null
+    val clickIntent = sbn.notification.contentIntent ?: return null
     val extras = sbn.notification.extras
     val name = extras.getString(Notification.EXTRA_CONVERSATION_TITLE)
             ?: extras.getString(Notification.EXTRA_TITLE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
new file mode 100644
index 0000000..a1822c7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2019 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.systemui.statusbar.notification.people
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.graphics.drawable.Drawable
+import android.testing.AndroidTestingRunner
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.ActivityStarter
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import kotlin.reflect.KClass
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class PeopleHubViewControllerTest : SysuiTestCase() {
+
+    @JvmField @Rule val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Mock private lateinit var mockViewBoundary: PeopleHubSectionFooterViewBoundary
+    @Mock private lateinit var mockActivityStarter: ActivityStarter
+
+    @Test
+    fun testBindViewModelToViewBoundary() {
+        val fakePerson1 = fakePersonViewModel("name")
+        val fakeViewModel = PeopleHubViewModel(sequenceOf(fakePerson1), true)
+
+        val fakePersonViewAdapter1 = FakeDataListener<PersonViewModel?>()
+        val fakePersonViewAdapter2 = FakeDataListener<PersonViewModel?>()
+
+        val mockClickView = mock(View::class.java)
+
+        `when`(mockViewBoundary.associatedViewForClickAnimation).thenReturn(mockClickView)
+        `when`(mockViewBoundary.personViewAdapters)
+                .thenReturn(sequenceOf(fakePersonViewAdapter1, fakePersonViewAdapter2))
+
+        val mockFactory = mock(PeopleHubViewModelFactory::class.java)
+        `when`(mockFactory.createWithAssociatedClickView(any())).thenReturn(fakeViewModel)
+
+        val mockSubscription = mock(Subscription::class.java)
+        val fakeFactoryDataSource = object : DataSource<PeopleHubViewModelFactory> {
+            override fun registerListener(
+                listener: DataListener<PeopleHubViewModelFactory>
+            ): Subscription {
+                listener.onDataChanged(mockFactory)
+                return mockSubscription
+            }
+        }
+        val adapter = PeopleHubSectionFooterViewAdapterImpl(fakeFactoryDataSource)
+
+        adapter.bindView(mockViewBoundary)
+
+        assertThat(fakePersonViewAdapter1.lastSeen).isEqualTo(Maybe.Just(fakePerson1))
+        assertThat(fakePersonViewAdapter2.lastSeen).isEqualTo(Maybe.Just<PersonViewModel?>(null))
+        verify(mockViewBoundary).setVisible(true)
+        verify(mockFactory).createWithAssociatedClickView(mockClickView)
+    }
+
+    fun testViewModelDataSourceTransformsModel() {
+        val fakeClickIntent = PendingIntent.getActivity(context, 0, Intent("action"), 0)
+        val fakePerson = fakePersonModel("id", "name", fakeClickIntent)
+        val fakeModel = PeopleHubModel(listOf(fakePerson))
+        val mockSubscription = mock(Subscription::class.java)
+        val fakeModelDataSource = object : DataSource<PeopleHubModel> {
+            override fun registerListener(listener: DataListener<PeopleHubModel>): Subscription {
+                listener.onDataChanged(fakeModel)
+                return mockSubscription
+            }
+        }
+        val factoryDataSource = PeopleHubViewModelFactoryDataSourceImpl(
+                mockActivityStarter, fakeModelDataSource)
+        val fakeListener = FakeDataListener<PeopleHubViewModelFactory>()
+        val mockClickView = mock(View::class.java)
+
+        factoryDataSource.registerListener(fakeListener)
+        val viewModel = (fakeListener.lastSeen as Maybe.Just).value
+                .createWithAssociatedClickView(mockClickView)
+        assertThat(viewModel.isVisible).isTrue()
+
+        val people = viewModel.people.toList()
+        assertThat(people.size).isEqualTo(1)
+        assertThat(people[0].name).isEqualTo("name")
+        assertThat(people[0].icon).isSameAs(fakePerson.avatar)
+
+        people[0].onClick()
+        verify(mockActivityStarter).startPendingIntentDismissingKeyguard(
+                same(fakeClickIntent),
+                any(),
+                same(mockClickView)
+        )
+    }
+}
+
+private inline fun <reified T : Any> any(): T {
+    return Mockito.any() ?: createInstance(T::class)
+}
+
+private inline fun <reified T : Any> same(value: T): T {
+    return Mockito.same(value) ?: createInstance(T::class)
+}
+
+private fun <T : Any> createInstance(clazz: KClass<T>): T = castNull()
+
+@Suppress("UNCHECKED_CAST")
+private fun <T> castNull(): T = null as T
+
+private fun fakePersonModel(
+    id: String,
+    name: CharSequence,
+    clickIntent: PendingIntent
+): PersonModel =
+        PersonModel(id, name, mock(Drawable::class.java), clickIntent)
+
+private fun fakePersonViewModel(name: CharSequence): PersonViewModel =
+        PersonViewModel(name, mock(Drawable::class.java), mock({}.javaClass))
+
+sealed class Maybe<T> {
+    data class Just<T>(val value: T) : Maybe<T>()
+    class Nothing<T> : Maybe<T>() {
+        override fun equals(other: Any?): Boolean {
+            if (this === other) return true
+            if (javaClass != other?.javaClass) return false
+            return true
+        }
+
+        override fun hashCode(): Int {
+            return javaClass.hashCode()
+        }
+    }
+}
+
+class FakeDataListener<T> : DataListener<T> {
+
+    var lastSeen: Maybe<T> = Maybe.Nothing()
+
+    override fun onDataChanged(data: T) {
+        lastSeen = Maybe.Just(data)
+    }
+}
\ No newline at end of file