/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.focus import android.content.Context import android.graphics.Bitmap import android.util.AttributeSet import android.view.LayoutInflater import android.view.View import kotlinx.coroutines.flow.flowOf import mozilla.components.browser.state.state.ContentState import mozilla.components.browser.state.state.CustomTabConfig import mozilla.components.browser.state.state.CustomTabSessionState import mozilla.components.browser.state.state.ExternalAppType import mozilla.components.concept.engine.EngineSession import mozilla.components.concept.engine.EngineView import mozilla.components.concept.engine.selection.SelectionActionDelegate import mozilla.components.support.test.robolectric.testContext import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mozilla.focus.browser.BrowserCoordinatorLayout import org.mozilla.focus.databinding.FragmentBrowserBinding import org.mozilla.focus.fragment.BrowserFragment import org.robolectric.RobolectricTestRunner import kotlin.test.assertNotNull @RunWith(RobolectricTestRunner::class) class BrowserFragmentTest { @Test fun testEngineViewInflationAndParentInteraction() { val layoutInflater = LayoutInflater.from(testContext) // Intercept the inflation process layoutInflater.factory2 = object : LayoutInflater.Factory2 { override fun onCreateView( parent: View?, name: String, context: Context, attrs: AttributeSet, ): View? { // Inflate a DummyEngineView when trying to inflate an EngineView if (name == EngineView::class.java.name) { return DummyEngineView(testContext) } // For other types of views, let the system handle it return null } override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? { return onCreateView(null, name, context, attrs) } } val binding = FragmentBrowserBinding.inflate(LayoutInflater.from(testContext)) val engineView: EngineView = binding.engineView assertNotNull(engineView) // Get the layout parent of the EngineView val engineViewParent = spy( (engineView as View).parent as BrowserCoordinatorLayout, ) assertNotNull(engineViewParent) engineViewParent.requestDisallowInterceptTouchEvent(true) // Verify that the EngineView's parent does not propagate requestDisallowInterceptTouchEvent verify(engineViewParent).requestDisallowInterceptTouchEvent(true) // If propagated, an additional ViewGroup.requestDisallowInterceptTouchEvent would have been registered. verifyNoMoreInteractions(engineViewParent) } @Test fun `test isOnboardingTab returns the expected value for each external app type`() { ExternalAppType.entries.forEach { val sessionState = testCustomTabSessionState(it) when (it) { ExternalAppType.CUSTOM_TAB, ExternalAppType.PROGRESSIVE_WEB_APP, ExternalAppType.TRUSTED_WEB_ACTIVITY, -> assertFalse(BrowserFragment().isOnboardingTab(sessionState)) ExternalAppType.ONBOARDING_CUSTOM_TAB -> assertTrue( BrowserFragment().isOnboardingTab(sessionState), ) } } } private fun testCustomTabSessionState(externalAppType: ExternalAppType) = CustomTabSessionState( content = ContentState(""), config = CustomTabConfig(externalAppType = externalAppType), ) } /** * Dummy implementation of the EngineView interface. */ class DummyEngineView(context: Context) : View(context), EngineView { init { id = R.id.engineView } override val verticalScrollPosition = flowOf(0f) override val verticalScrollDelta = flowOf(0f) override fun render(session: EngineSession) { // no-op } override fun release() { // no-op } override fun captureThumbnail(onFinish: (Bitmap?) -> Unit) { // no-op } override fun setVerticalClipping(clippingHeight: Int) { // no-op } override fun setDynamicToolbarMaxHeight(height: Int) { // no-op } override fun setActivityContext(context: Context?) { // no-op } override var selectionActionDelegate: SelectionActionDelegate? = null override fun addWindowInsetsListener( key: String, listener: androidx.core.view.OnApplyWindowInsetsListener?, ) { // no-op } override fun removeWindowInsetsListener(key: String) { // no-op } }