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 androidx.lifecycle.testing.TestLifecycleOwner 25 import kotlinx.coroutines.Dispatchers 26 import kotlinx.coroutines.ExperimentalCoroutinesApi 27 import kotlinx.coroutines.test.StandardTestDispatcher 28 import kotlinx.coroutines.test.TestCoroutineScheduler 29 import kotlinx.coroutines.test.resetMain 30 import kotlinx.coroutines.test.setMain 31 import org.junit.After 32 import org.junit.Before 33 import org.junit.Test 34 import org.mockito.kotlin.any 35 import org.mockito.kotlin.doReturn 36 import org.mockito.kotlin.mock 37 import org.mockito.kotlin.times 38 import org.mockito.kotlin.verify 39 40 private const val TIMEOUT_MS = 200 41 42 @OptIn(ExperimentalCoroutinesApi::class) 43 class EnterTransitionAnimationDelegateTest { 44 private val elementName = "shared-element" 45 private val scheduler = TestCoroutineScheduler() 46 private val dispatcher = StandardTestDispatcher(scheduler) 47 private val lifecycleOwner = TestLifecycleOwner() 48 49 private val transitionTargetView = <lambda>null50 mock<View> { 51 // avoid the request-layout path in the delegate 52 on { isInLayout } doReturn true 53 } 54 55 private val windowMock = mock<Window>() 56 private val resourcesMock = <lambda>null57 mock<Resources> { on { getInteger(any<Int>()) } doReturn TIMEOUT_MS } 58 private val activity = <lambda>null59 mock<ComponentActivity> { 60 on { lifecycle } doReturn lifecycleOwner.lifecycle 61 on { resources } doReturn resourcesMock 62 on { isActivityTransitionRunning } doReturn true 63 on { window } doReturn windowMock 64 } 65 <lambda>null66 private val testSubject = EnterTransitionAnimationDelegate(activity) { transitionTargetView } 67 68 @Before setupnull69 fun setup() { 70 Dispatchers.setMain(dispatcher) 71 lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) 72 } 73 74 @After cleanupnull75 fun cleanup() { 76 lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) 77 Dispatchers.resetMain() 78 } 79 80 @Test test_postponeTransition_timeoutnull81 fun test_postponeTransition_timeout() { 82 testSubject.postponeTransition() 83 testSubject.markOffsetCalculated() 84 85 scheduler.advanceTimeBy(TIMEOUT_MS + 1L) 86 verify(activity) { 1 * { mock.startPostponedEnterTransition() } } 87 verify(windowMock) { 0 * { setWindowAnimations(any<Int>()) } } 88 } 89 90 @Test test_postponeTransition_animation_resumes_only_oncenull91 fun test_postponeTransition_animation_resumes_only_once() { 92 testSubject.postponeTransition() 93 testSubject.markOffsetCalculated() 94 testSubject.onTransitionElementReady(elementName) 95 testSubject.markOffsetCalculated() 96 testSubject.onTransitionElementReady(elementName) 97 98 scheduler.advanceTimeBy(TIMEOUT_MS + 1L) 99 verify(activity, times(1)).startPostponedEnterTransition() 100 } 101 102 @Test test_postponeTransition_resume_animation_conditionsnull103 fun test_postponeTransition_resume_animation_conditions() { 104 testSubject.postponeTransition() 105 verify(activity) { 0 * { startPostponedEnterTransition() } } 106 107 testSubject.markOffsetCalculated() 108 verify(activity) { 0 * { startPostponedEnterTransition() } } 109 110 testSubject.onAllTransitionElementsReady() 111 verify(activity) { 1 * { startPostponedEnterTransition() } } 112 } 113 } 114