1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.statusbar.notification.people
18 
19 import android.graphics.drawable.Drawable
20 import android.testing.AndroidTestingRunner
21 import android.view.View
22 import androidx.test.filters.SmallTest
23 import com.android.systemui.SysuiTestCase
24 import com.android.systemui.plugins.ActivityStarter
25 import com.google.common.truth.Truth.assertThat
26 import org.junit.Rule
27 import org.junit.Test
28 import org.junit.runner.RunWith
29 import org.mockito.Mock
30 import org.mockito.Mockito
31 import org.mockito.Mockito.mock
32 import org.mockito.Mockito.verify
33 import org.mockito.junit.MockitoJUnit
34 import org.mockito.junit.MockitoRule
35 import kotlin.reflect.KClass
36 import org.mockito.Mockito.`when` as whenever
37 
38 @SmallTest
39 @RunWith(AndroidTestingRunner::class)
40 class PeopleHubViewControllerTest : SysuiTestCase() {
41 
42     @JvmField @Rule val mockito: MockitoRule = MockitoJUnit.rule()
43 
44     @Mock private lateinit var mockViewBoundary: PeopleHubViewBoundary
45     @Mock private lateinit var mockActivityStarter: ActivityStarter
46 
47     @Test
48     fun testBindViewModelToViewBoundary() {
49         val fakePerson1 = fakePersonViewModel("name")
50         val fakeViewModel = PeopleHubViewModel(sequenceOf(fakePerson1), true)
51 
52         val mockFactory = mock(PeopleHubViewModelFactory::class.java)
53         whenever(mockFactory.createWithAssociatedClickView(any())).thenReturn(fakeViewModel)
54 
55         val mockClickView = mock(View::class.java)
56         whenever(mockViewBoundary.associatedViewForClickAnimation).thenReturn(mockClickView)
57 
58         val fakePersonViewAdapter1 = FakeDataListener<PersonViewModel?>()
59         val fakePersonViewAdapter2 = FakeDataListener<PersonViewModel?>()
60         whenever(mockViewBoundary.personViewAdapters)
61                 .thenReturn(sequenceOf(fakePersonViewAdapter1, fakePersonViewAdapter2))
62 
63         val adapter = PeopleHubViewAdapterImpl(FakeDataSource(mockFactory))
64 
65         adapter.bindView(mockViewBoundary)
66 
67         assertThat(fakePersonViewAdapter1.lastSeen).isEqualTo(Maybe.Just(fakePerson1))
68         assertThat(fakePersonViewAdapter2.lastSeen).isEqualTo(Maybe.Just<PersonViewModel?>(null))
69         verify(mockViewBoundary).setVisible(true)
70         verify(mockFactory).createWithAssociatedClickView(mockClickView)
71     }
72 
73     @Test
74     fun testBindViewModelToViewBoundary_moreDataThanCanBeDisplayed_displaysMostRecent() {
75         val fakePerson1 = fakePersonViewModel("person1")
76         val fakePerson2 = fakePersonViewModel("person2")
77         val fakePerson3 = fakePersonViewModel("person3")
78         val fakePeople = sequenceOf(fakePerson3, fakePerson2, fakePerson1)
79         val fakeViewModel = PeopleHubViewModel(fakePeople, true)
80 
81         val mockFactory = mock(PeopleHubViewModelFactory::class.java)
82         whenever(mockFactory.createWithAssociatedClickView(any())).thenReturn(fakeViewModel)
83 
84         whenever(mockViewBoundary.associatedViewForClickAnimation)
85                 .thenReturn(mock(View::class.java))
86 
87         val fakePersonViewAdapter1 = FakeDataListener<PersonViewModel?>()
88         val fakePersonViewAdapter2 = FakeDataListener<PersonViewModel?>()
89         whenever(mockViewBoundary.personViewAdapters)
90                 .thenReturn(sequenceOf(fakePersonViewAdapter1, fakePersonViewAdapter2))
91 
92         val adapter = PeopleHubViewAdapterImpl(FakeDataSource(mockFactory))
93 
94         adapter.bindView(mockViewBoundary)
95 
96         assertThat(fakePersonViewAdapter1.lastSeen).isEqualTo(Maybe.Just(fakePerson3))
97         assertThat(fakePersonViewAdapter2.lastSeen).isEqualTo(Maybe.Just(fakePerson2))
98     }
99 
100     @Test
101     fun testViewModelDataSourceTransformsModel() {
102         val fakeClickRunnable = mock(Runnable::class.java)
103         val fakePerson = fakePersonModel("id", "name", fakeClickRunnable)
104         val fakeModel = PeopleHubModel(listOf(fakePerson))
105         val fakeModelDataSource = FakeDataSource(fakeModel)
106         val factoryDataSource = PeopleHubViewModelFactoryDataSourceImpl(
107                 mockActivityStarter,
108                 fakeModelDataSource
109         )
110         val fakeListener = FakeDataListener<PeopleHubViewModelFactory>()
111         val mockClickView = mock(View::class.java)
112 
113         factoryDataSource.registerListener(fakeListener)
114 
115         val viewModel = (fakeListener.lastSeen as Maybe.Just).value
116                 .createWithAssociatedClickView(mockClickView)
117         assertThat(viewModel.isVisible).isTrue()
118         val people = viewModel.people.toList()
119         assertThat(people.size).isEqualTo(1)
120         assertThat(people[0].name).isEqualTo("name")
121         assertThat(people[0].icon).isSameInstanceAs(fakePerson.avatar)
122 
123         people[0].onClick()
124 
125         verify(fakeClickRunnable).run()
126     }
127 }
128 
129 /** Works around Mockito matchers returning `null` and breaking non-nullable Kotlin code. */
130 private inline fun <reified T : Any> any(): T {
131     return Mockito.any() ?: createInstance(T::class)
132 }
133 
134 /** Works around Mockito matchers returning `null` and breaking non-nullable Kotlin code. */
135 private inline fun <reified T : Any> same(value: T): T {
136     return Mockito.same(value) ?: createInstance(T::class)
137 }
138 
139 /** Creates an instance of the given class. */
140 private fun <T : Any> createInstance(clazz: KClass<T>): T = castNull()
141 
142 /** Tricks the Kotlin compiler into assigning `null` to a non-nullable variable. */
143 @Suppress("UNCHECKED_CAST")
144 private fun <T> castNull(): T = null as T
145 
146 private fun fakePersonModel(
147     id: String,
148     name: CharSequence,
149     clickRunnable: Runnable,
150     userId: Int = 0
151 ): PersonModel =
152         PersonModel(id, userId, name, mock(Drawable::class.java), clickRunnable)
153 
154 private fun fakePersonViewModel(name: CharSequence): PersonViewModel =
155         PersonViewModel(name, mock(Drawable::class.java), mock({}.javaClass))
156 
157 sealed class Maybe<T> {
158     data class Just<T>(val value: T) : Maybe<T>()
159     class Nothing<T> : Maybe<T>() {
160         override fun equals(other: Any?): Boolean {
161             if (this === other) return true
162             if (javaClass != other?.javaClass) return false
163             return true
164         }
165 
166         override fun hashCode(): Int {
167             return javaClass.hashCode()
168         }
169     }
170 }
171 
172 class FakeDataListener<T> : DataListener<T> {
173 
174     var lastSeen: Maybe<T> = Maybe.Nothing()
175 
176     override fun onDataChanged(data: T) {
177         lastSeen = Maybe.Just(data)
178     }
179 }
180