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