1 /*
2  * Copyright (C) 2022 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.settingslib.spaprivileged.model.app
18 
19 import android.app.Application
20 import android.content.pm.ApplicationInfo
21 import androidx.compose.runtime.Composable
22 import androidx.test.ext.junit.runners.AndroidJUnit4
23 import com.android.settingslib.spa.framework.compose.stateOf
24 import com.android.settingslib.spa.framework.util.mapItem
25 import com.android.settingslib.spa.testutils.waitUntil
26 import com.android.settingslib.spaprivileged.template.app.AppListConfig
27 import com.google.common.truth.Truth.assertThat
28 import kotlinx.coroutines.ExperimentalCoroutinesApi
29 import kotlinx.coroutines.flow.Flow
30 import kotlinx.coroutines.flow.first
31 import kotlinx.coroutines.flow.flowOf
32 import kotlinx.coroutines.test.runTest
33 import org.junit.Rule
34 import org.junit.Test
35 import org.junit.runner.RunWith
36 import org.mockito.Mock
37 import org.mockito.junit.MockitoJUnit
38 import org.mockito.junit.MockitoRule
39 
40 @OptIn(ExperimentalCoroutinesApi::class)
41 @RunWith(AndroidJUnit4::class)
42 class AppListViewModelTest {
43     @get:Rule
44     val mockito: MockitoRule = MockitoJUnit.rule()
45 
46     @Mock
47     private lateinit var application: Application
48 
49     private val listModel = TestAppListModel()
50 
51     private fun createViewModel(): AppListViewModelImpl<TestAppRecord> {
52         val viewModel = AppListViewModelImpl<TestAppRecord>(
53             application = application,
54             appListRepositoryFactory = { FakeAppListRepository },
55             appRepositoryFactory = { FakeAppRepository },
56         )
57         viewModel.appListConfig.setIfAbsent(CONFIG)
58         viewModel.listModel.setIfAbsent(listModel)
59         viewModel.showSystem.setIfAbsent(false)
60         viewModel.optionFlow.value = 0
61         viewModel.searchQuery.setIfAbsent("")
62         viewModel.reloadApps()
63         return viewModel
64     }
65 
66     @Test
67     fun appListDataFlow() = runTest {
68         val viewModel = createViewModel()
69 
70         val (appEntries, option) = viewModel.appListDataFlow.first()
71 
72         assertThat(appEntries).hasSize(1)
73         assertThat(appEntries[0].record.app).isSameInstanceAs(APP)
74         assertThat(appEntries[0].label).isEqualTo(LABEL)
75         assertThat(option).isEqualTo(0)
76     }
77 
78     @Test
79     fun onFirstLoaded_calledWhenLoaded() = runTest {
80         val viewModel = createViewModel()
81 
82         viewModel.appListDataFlow.first()
83 
84         waitUntil { listModel.onFirstLoadedCalled }
85     }
86 
87     private object FakeAppListRepository : AppListRepository {
88         override suspend fun loadApps(
89             userId: Int,
90             loadInstantApps: Boolean,
91             matchAnyUserForAdmin: Boolean,
92         ) = listOf(APP)
93 
94         override fun showSystemPredicate(
95             userIdFlow: Flow<Int>,
96             showSystemFlow: Flow<Boolean>,
97         ): Flow<(app: ApplicationInfo) -> Boolean> = flowOf { true }
98 
99         override fun getSystemPackageNamesBlocking(userId: Int): Set<String> = emptySet()
100 
101         override suspend fun loadAndFilterApps(userId: Int, isSystemApp: Boolean) =
102             emptyList<ApplicationInfo>()
103     }
104 
105     private object FakeAppRepository : AppRepository {
106         override fun loadLabel(app: ApplicationInfo) = LABEL
107 
108         @Composable
109         override fun produceIcon(app: ApplicationInfo) = stateOf(null)
110 
111         @Composable
112         override fun produceIconContentDescription(app: ApplicationInfo) = stateOf(null)
113     }
114 
115     private companion object {
116         const val USER_ID = 0
117         const val PACKAGE_NAME = "package.name"
118         const val LABEL = "Label"
119         val CONFIG = AppListConfig(
120             userIds = listOf(USER_ID),
121             showInstantApps = false,
122             matchAnyUserForAdmin = false,
123         )
124         val APP = ApplicationInfo().apply {
125             packageName = PACKAGE_NAME
126         }
127     }
128 }
129 
130 private data class TestAppRecord(override val app: ApplicationInfo) : AppRecord
131 
132 private class TestAppListModel : AppListModel<TestAppRecord> {
133     var onFirstLoadedCalled = false
134 
135     override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
136         appListFlow.mapItem(::TestAppRecord)
137 
138     override suspend fun onFirstLoaded(recordList: List<TestAppRecord>): Boolean {
139         onFirstLoadedCalled = true
140         return false
141     }
142 }
143