xref: /aosp_15_r20/frameworks/base/packages/SystemUI/tests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1 /*
2  * Copyright (C) 2024 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.lifecycle
18 
19 import android.view.View
20 import androidx.compose.runtime.getValue
21 import androidx.compose.runtime.mutableStateOf
22 import androidx.compose.ui.test.junit4.createComposeRule
23 import androidx.test.ext.junit.runners.AndroidJUnit4
24 import androidx.test.filters.SmallTest
25 import com.android.systemui.SysuiTestCase
26 import com.android.systemui.util.Assert
27 import com.google.common.truth.Truth.assertThat
28 import kotlinx.coroutines.awaitCancellation
29 import kotlinx.coroutines.launch
30 import kotlinx.coroutines.test.runCurrent
31 import kotlinx.coroutines.test.runTest
32 import org.junit.Rule
33 import org.junit.Test
34 import org.junit.runner.RunWith
35 import org.mockito.kotlin.argumentCaptor
36 import org.mockito.kotlin.doReturn
37 import org.mockito.kotlin.mock
38 import org.mockito.kotlin.stub
39 import org.mockito.kotlin.verify
40 
41 @SmallTest
42 @RunWith(AndroidJUnit4::class)
43 class SysUiViewModelTest : SysuiTestCase() {
44 
45     @get:Rule val composeRule = createComposeRule()
46 
47     @Test
rememberActivatednull48     fun rememberActivated() {
49         val keepAliveMutable = mutableStateOf(true)
50         var isActive = false
51         composeRule.setContent {
52             val keepAlive by keepAliveMutable
53             if (keepAlive) {
54                 rememberViewModel("test") {
55                     FakeSysUiViewModel(
56                         onActivation = { isActive = true },
57                         onDeactivation = { isActive = false },
58                     )
59                 }
60             }
61         }
62         assertThat(isActive).isTrue()
63     }
64 
65     @Test
rememberActivated_withKeynull66     fun rememberActivated_withKey() {
67         val keyMutable = mutableStateOf(1)
68         var isActive1 = false
69         var isActive2 = false
70         composeRule.setContent {
71             val key by keyMutable
72             // Need to explicitly state the type to avoid a weird issue where the factory seems to
73             // return Unit instead of FakeSysUiViewModel. It might be an issue with the compose
74             // compiler.
75             val unused: FakeSysUiViewModel =
76                 rememberViewModel("test", key) {
77                     when (key) {
78                         1 ->
79                             FakeSysUiViewModel(
80                                 onActivation = { isActive1 = true },
81                                 onDeactivation = { isActive1 = false },
82                             )
83                         2 ->
84                             FakeSysUiViewModel(
85                                 onActivation = { isActive2 = true },
86                                 onDeactivation = { isActive2 = false },
87                             )
88                         else -> error("unsupported key $key")
89                     }
90                 }
91         }
92         assertThat(isActive1).isTrue()
93         assertThat(isActive2).isFalse()
94 
95         composeRule.runOnUiThread { keyMutable.value = 2 }
96         composeRule.waitForIdle()
97         assertThat(isActive1).isFalse()
98         assertThat(isActive2).isTrue()
99 
100         composeRule.runOnUiThread { keyMutable.value = 1 }
101         composeRule.waitForIdle()
102         assertThat(isActive1).isTrue()
103         assertThat(isActive2).isFalse()
104     }
105 
106     @Test
rememberActivated_leavingTheCompositionnull107     fun rememberActivated_leavingTheComposition() {
108         val keepAliveMutable = mutableStateOf(true)
109         var isActive = false
110         composeRule.setContent {
111             val keepAlive by keepAliveMutable
112             if (keepAlive) {
113                 rememberViewModel("test") {
114                     FakeSysUiViewModel(
115                         onActivation = { isActive = true },
116                         onDeactivation = { isActive = false },
117                     )
118                 }
119             }
120         }
121 
122         // Tear down the composable.
123         composeRule.runOnUiThread { keepAliveMutable.value = false }
124         composeRule.waitForIdle()
125 
126         assertThat(isActive).isFalse()
127     }
128 
129     @Test
<lambda>null130     fun viewModel_viewBinder() = runTest {
131         Assert.setTestThread(Thread.currentThread())
132 
133         val view: View = mock { on { isAttachedToWindow } doReturn false }
134         val viewModel = FakeViewModel()
135         backgroundScope.launch {
136             view.viewModel(
137                 traceName = "test",
138                 minWindowLifecycleState = WindowLifecycleState.ATTACHED,
139                 factory = { viewModel },
140             ) {
141                 awaitCancellation()
142             }
143         }
144         runCurrent()
145 
146         assertThat(viewModel.isActivated).isFalse()
147 
148         view.stub { on { isAttachedToWindow } doReturn true }
149         argumentCaptor<View.OnAttachStateChangeListener>()
150             .apply { verify(view).addOnAttachStateChangeListener(capture()) }
151             .allValues
152             .forEach { it.onViewAttachedToWindow(view) }
153         runCurrent()
154 
155         assertThat(viewModel.isActivated).isTrue()
156     }
157 }
158 
159 private class FakeViewModel : ExclusiveActivatable() {
160     var isActivated = false
161 
onActivatednull162     override suspend fun onActivated(): Nothing {
163         isActivated = true
164         try {
165             awaitCancellation()
166         } finally {
167             isActivated = false
168         }
169     }
170 }
171