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