1 /*
<lambda>null2  * 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.bouncer.ui.composable
18 
19 import android.platform.test.annotations.MotionTest
20 import androidx.compose.foundation.layout.size
21 import androidx.compose.runtime.Composable
22 import androidx.compose.ui.Modifier
23 import androidx.compose.ui.unit.dp
24 import androidx.test.ext.junit.runners.AndroidJUnit4
25 import androidx.test.filters.LargeTest
26 import com.android.systemui.SysuiTestCase
27 import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
28 import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
29 import com.android.systemui.kosmos.testScope
30 import com.android.systemui.motion.createSysUiComposeMotionTestRule
31 import com.android.systemui.testKosmos
32 import kotlinx.coroutines.flow.MutableStateFlow
33 import kotlinx.coroutines.flow.asStateFlow
34 import kotlinx.coroutines.flow.takeWhile
35 import org.junit.Rule
36 import org.junit.Test
37 import org.junit.runner.RunWith
38 import platform.test.motion.compose.ComposeRecordingSpec
39 import platform.test.motion.compose.MotionControl
40 import platform.test.motion.compose.feature
41 import platform.test.motion.compose.motionTestValueOfNode
42 import platform.test.motion.compose.recordMotion
43 import platform.test.motion.compose.runTest
44 import platform.test.motion.golden.DataPointTypes
45 
46 @RunWith(AndroidJUnit4::class)
47 @LargeTest
48 @MotionTest
49 class PatternBouncerTest : SysuiTestCase() {
50     private val kosmos = testKosmos()
51 
52     @get:Rule val motionTestRule = createSysUiComposeMotionTestRule(kosmos)
53 
54     private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
55     private val viewModel by lazy {
56         PatternBouncerViewModel(
57             applicationContext = context,
58             viewModelScope = kosmos.testScope.backgroundScope,
59             interactor = bouncerInteractor,
60             isInputEnabled = MutableStateFlow(true).asStateFlow(),
61             onIntentionalUserInput = {},
62         )
63     }
64 
65     @Composable
66     private fun PatternBouncerUnderTest() {
67         PatternBouncer(viewModel, centerDotsVertically = true, modifier = Modifier.size(400.dp))
68     }
69 
70     @Test
71     fun entryAnimation() =
72         motionTestRule.runTest {
73             val motion =
74                 recordMotion(
75                     content = { play -> if (play) PatternBouncerUnderTest() },
76                     ComposeRecordingSpec.until(
77                         recordBefore = false,
78                         checkDone = { motionTestValueOfNode(MotionTestKeys.entryCompleted) }
79                     ) {
80                         feature(MotionTestKeys.dotAppearFadeIn, floatArray)
81                         feature(MotionTestKeys.dotAppearMoveUp, floatArray)
82                     }
83                 )
84 
85             assertThat(motion).timeSeriesMatchesGolden()
86         }
87 
88     @Test
89     fun animateFailure() =
90         motionTestRule.runTest {
91             val failureAnimationMotionControl =
92                 MotionControl(
93                     delayReadyToPlay = {
94                         // Skip entry animation.
95                         awaitCondition { motionTestValueOfNode(MotionTestKeys.entryCompleted) }
96                     },
97                     delayRecording = {
98                         // Trigger failure animation by calling onDragEnd without having recorded a
99                         // pattern  before.
100                         viewModel.onDragEnd()
101                         // Failure animation starts when animateFailure flips to true...
102                         viewModel.animateFailure.takeWhile { !it }.collect {}
103                     }
104                 ) {
105                     // ... and ends when the composable flips it back to false.
106                     viewModel.animateFailure.takeWhile { it }.collect {}
107                 }
108 
109             val motion =
110                 recordMotion(
111                     content = { PatternBouncerUnderTest() },
112                     ComposeRecordingSpec(failureAnimationMotionControl) {
113                         feature(MotionTestKeys.dotScaling, floatArray)
114                     }
115                 )
116             assertThat(motion).timeSeriesMatchesGolden()
117         }
118 
119     companion object {
120         val floatArray = DataPointTypes.listOf(DataPointTypes.float)
121     }
122 }
123