1 /* 2 * Copyright (C) 2023 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.intentresolver 18 19 import android.content.res.Resources 20 import android.view.View 21 import android.view.Window 22 import androidx.activity.ComponentActivity 23 import androidx.lifecycle.Lifecycle 24 import kotlinx.coroutines.Dispatchers 25 import kotlinx.coroutines.ExperimentalCoroutinesApi 26 import kotlinx.coroutines.test.StandardTestDispatcher 27 import kotlinx.coroutines.test.TestCoroutineScheduler 28 import kotlinx.coroutines.test.resetMain 29 import kotlinx.coroutines.test.setMain 30 import org.junit.After 31 import org.junit.Before 32 import org.junit.Test 33 import org.mockito.Mockito.anyInt 34 import org.mockito.Mockito.never 35 import org.mockito.Mockito.times 36 import org.mockito.Mockito.verify 37 38 private const val TIMEOUT_MS = 200 39 40 @OptIn(ExperimentalCoroutinesApi::class) 41 class EnterTransitionAnimationDelegateTest { 42 private val elementName = "shared-element" 43 private val scheduler = TestCoroutineScheduler() 44 private val dispatcher = StandardTestDispatcher(scheduler) 45 private val lifecycleOwner = TestLifecycleOwner() 46 <lambda>null47 private val transitionTargetView = mock<View> { 48 // avoid the request-layout path in the delegate 49 whenever(isInLayout).thenReturn(true) 50 } 51 52 private val windowMock = mock<Window>() <lambda>null53 private val resourcesMock = mock<Resources> { 54 whenever(getInteger(anyInt())).thenReturn(TIMEOUT_MS) 55 } <lambda>null56 private val activity = mock<ComponentActivity> { 57 whenever(lifecycle).thenReturn(lifecycleOwner.lifecycle) 58 whenever(resources).thenReturn(resourcesMock) 59 whenever(isActivityTransitionRunning).thenReturn(true) 60 whenever(window).thenReturn(windowMock) 61 } 62 <lambda>null63 private val testSubject = EnterTransitionAnimationDelegate(activity) { 64 transitionTargetView 65 } 66 67 @Before setupnull68 fun setup() { 69 Dispatchers.setMain(dispatcher) 70 lifecycleOwner.state = Lifecycle.State.CREATED 71 } 72 73 @After cleanupnull74 fun cleanup() { 75 lifecycleOwner.state = Lifecycle.State.DESTROYED 76 Dispatchers.resetMain() 77 } 78 79 @Test test_postponeTransition_timeoutnull80 fun test_postponeTransition_timeout() { 81 testSubject.postponeTransition() 82 testSubject.markOffsetCalculated() 83 84 scheduler.advanceTimeBy(TIMEOUT_MS + 1L) 85 verify(activity, times(1)).startPostponedEnterTransition() 86 verify(windowMock, never()).setWindowAnimations(anyInt()) 87 } 88 89 @Test test_postponeTransition_animation_resumes_only_oncenull90 fun test_postponeTransition_animation_resumes_only_once() { 91 testSubject.postponeTransition() 92 testSubject.markOffsetCalculated() 93 testSubject.onTransitionElementReady(elementName) 94 testSubject.markOffsetCalculated() 95 testSubject.onTransitionElementReady(elementName) 96 97 scheduler.advanceTimeBy(TIMEOUT_MS + 1L) 98 verify(activity, times(1)).startPostponedEnterTransition() 99 } 100 101 @Test test_postponeTransition_resume_animation_conditionsnull102 fun test_postponeTransition_resume_animation_conditions() { 103 testSubject.postponeTransition() 104 verify(activity, never()).startPostponedEnterTransition() 105 106 testSubject.markOffsetCalculated() 107 verify(activity, never()).startPostponedEnterTransition() 108 109 testSubject.onAllTransitionElementsReady() 110 verify(activity, times(1)).startPostponedEnterTransition() 111 } 112 } 113