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