blob: 69d8bf6a06dbdddd6b818584c8ddbfb671bc3baa [file] [log] [blame]
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.fragment.app
import android.graphics.Rect
import android.os.Build
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.annotation.RequiresApi
import androidx.fragment.test.R
import androidx.test.core.app.ActivityScenario
import androidx.test.platform.app.InstrumentationRegistry
import androidx.testutils.runOnUiThreadRethrow
import androidx.testutils.withActivity
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import java.lang.ref.WeakReference
fun FragmentTransaction.setReorderingAllowed(
reorderingAllowed: ReorderingAllowed
) = setReorderingAllowed(reorderingAllowed is Reordered)
sealed class ReorderingAllowed {
override fun toString(): String = this.javaClass.simpleName
}
object Reordered : ReorderingAllowed()
object Ordered : ReorderingAllowed()
@Suppress("DEPRECATION")
fun androidx.test.rule.ActivityTestRule<out FragmentActivity>.executePendingTransactions(
fm: FragmentManager = activity.supportFragmentManager
): Boolean {
var ret = false
runOnUiThreadRethrow { ret = fm.executePendingTransactions() }
return ret
}
inline fun <reified A : FragmentActivity> ActivityScenario<A>.executePendingTransactions(
fm: FragmentManager = withActivity { supportFragmentManager }
) {
onActivity { fm.executePendingTransactions() }
}
@Suppress("DEPRECATION")
fun androidx.test.rule.ActivityTestRule<out FragmentActivity>.popBackStackImmediate(): Boolean {
val instrumentation = InstrumentationRegistry.getInstrumentation()
var ret = false
instrumentation.runOnMainSync {
ret = activity.supportFragmentManager.popBackStackImmediate()
}
return ret
}
inline fun <reified A : FragmentActivity> ActivityScenario<A>.popBackStackImmediate() {
withActivity { supportFragmentManager.popBackStackImmediate() }
}
@Suppress("DEPRECATION")
fun androidx.test.rule.ActivityTestRule<out FragmentActivity>.popBackStackImmediate(
id: Int,
flags: Int = 0
): Boolean {
val instrumentation = InstrumentationRegistry.getInstrumentation()
var ret = false
instrumentation.runOnMainSync {
ret = activity.supportFragmentManager.popBackStackImmediate(id, flags)
}
return ret
}
@Suppress("DEPRECATION")
fun androidx.test.rule.ActivityTestRule<out FragmentActivity>.popBackStackImmediate(
name: String,
flags: Int = 0
): Boolean {
val instrumentation = InstrumentationRegistry.getInstrumentation()
var ret = false
instrumentation.runOnMainSync {
ret = activity.supportFragmentManager.popBackStackImmediate(name, flags)
}
return ret
}
@Suppress("DEPRECATION")
fun androidx.test.rule.ActivityTestRule<out FragmentActivity>.setContentView(
@LayoutRes layoutId: Int
) {
val instrumentation = InstrumentationRegistry.getInstrumentation()
instrumentation.runOnMainSync { activity.setContentView(layoutId) }
}
fun assertChildren(container: ViewGroup, vararg fragments: Fragment) {
val numFragments = fragments.size
assertWithMessage("There aren't the correct number of fragment Views in its container")
.that(container.childCount)
.isEqualTo(numFragments)
fragments.forEachIndexed { index, fragment ->
assertWithMessage("Wrong Fragment View order for [$index]")
.that(fragment.requireView())
.isSameInstanceAs(container.getChildAt(index))
}
}
@Suppress("DEPRECATION")
// Transition test methods start
fun androidx.test.rule.ActivityTestRule<out FragmentActivity>.findGreen(): View {
return activity.findViewById(R.id.greenSquare)
}
inline fun <reified A : FragmentActivity> ActivityScenario<A>.findGreen(): View {
return withActivity { findViewById(R.id.greenSquare) }
}
@Suppress("DEPRECATION")
fun androidx.test.rule.ActivityTestRule<out FragmentActivity>.findBlue(): View {
return activity.findViewById(R.id.blueSquare)
}
inline fun <reified A : FragmentActivity> ActivityScenario<A>.findBlue(): View {
return withActivity { findViewById(R.id.blueSquare) }
}
@Suppress("DEPRECATION")
fun androidx.test.rule.ActivityTestRule<out FragmentActivity>.findRed(): View? {
return activity.findViewById(R.id.redSquare)
}
inline fun <reified A : FragmentActivity> ActivityScenario<A>.findRed(): View {
return withActivity { findViewById(R.id.redSquare) }
}
val View.boundsOnScreen: Rect
get() {
val loc = IntArray(2)
getLocationOnScreen(loc)
return Rect(loc[0], loc[1], loc[0] + width, loc[1] + height)
}
data class TransitionVerificationInfo(
var epicenter: Rect? = null,
val exitingViews: MutableList<View> = mutableListOf(),
val enteringViews: MutableList<View> = mutableListOf()
)
fun TargetTracking.verifyAndClearTransition(block: TransitionVerificationInfo.() -> Unit) {
val (epicenter, exitingViews, enteringViews) = TransitionVerificationInfo().apply { block() }
assertThat(exitingTargets).containsExactlyElementsIn(exitingViews)
assertThat(enteringTargets).containsExactlyElementsIn(enteringViews)
if (epicenter == null) {
assertThat(capturedEpicenter).isNull()
} else {
assertThat(capturedEpicenter).isEqualTo(epicenter)
}
clearTargets()
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
fun verifyNoOtherTransitions(fragment: TransitionFragment) {
assertThat(fragment.enterTransition.enteringTargets).isEmpty()
assertThat(fragment.enterTransition.exitingTargets).isEmpty()
assertThat(fragment.exitTransition.enteringTargets).isEmpty()
assertThat(fragment.exitTransition.exitingTargets).isEmpty()
assertThat(fragment.reenterTransition.enteringTargets).isEmpty()
assertThat(fragment.reenterTransition.exitingTargets).isEmpty()
assertThat(fragment.returnTransition.enteringTargets).isEmpty()
assertThat(fragment.returnTransition.exitingTargets).isEmpty()
assertThat(fragment.sharedElementEnter.enteringTargets).isEmpty()
assertThat(fragment.sharedElementEnter.exitingTargets).isEmpty()
assertThat(fragment.sharedElementReturn.enteringTargets).isEmpty()
assertThat(fragment.sharedElementReturn.exitingTargets).isEmpty()
}
// Transition test methods end
/**
* Allocates until a garbage collection occurs.
*/
fun forceGC() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) {
// The following works on O+
Runtime.getRuntime().gc()
Runtime.getRuntime().gc()
Runtime.getRuntime().runFinalization()
} else {
// The following works on older versions
for (i in 0..1) {
// Use a random index in the list to detect the garbage collection each time because
// .get() may accidentally trigger a strong reference during collection.
val leak = ArrayList<WeakReference<ByteArray>>()
do {
val arr = WeakReference(ByteArray(100))
leak.add(arr)
} while (leak[(Math.random() * leak.size).toInt()].get() != null)
}
}
}