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