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