fenix/app/src/test/java/org/mozilla/fenix/browser/BrowserFragmentTest.kt

386 lines
15 KiB
Kotlin

/* 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.fenix.browser
import android.content.Context
import android.view.View
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.navigation.NavController
import io.mockk.every
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import kotlinx.coroutines.test.TestCoroutineDispatcher
import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.action.RestoreCompleteAction
import mozilla.components.browser.state.action.TabListAction
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.LoadRequestState
import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.browser.toolbar.BrowserToolbar
import mozilla.components.support.test.ext.joinBlocking
import mozilla.components.support.test.rule.MainCoroutineRule
import org.junit.After
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.FenixApplication
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.components.toolbar.BrowserToolbarView
import org.mozilla.fenix.components.toolbar.ToolbarIntegration
import org.mozilla.fenix.ext.application
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.onboarding.FenixOnboarding
import org.mozilla.fenix.utils.Settings
import java.lang.Exception
@RunWith(FenixRobolectricTestRunner::class)
class BrowserFragmentTest {
private lateinit var store: BrowserStore
private lateinit var testTab: TabSessionState
private lateinit var browserFragment: BrowserFragment
private lateinit var view: View
private lateinit var homeActivity: HomeActivity
private lateinit var fenixApplication: FenixApplication
private lateinit var context: Context
private lateinit var lifecycleOwner: MockedLifecycleOwner
private lateinit var navController: NavController
private lateinit var onboarding: FenixOnboarding
private val testDispatcher = TestCoroutineDispatcher()
@get:Rule
val coroutinesTestRule = MainCoroutineRule(testDispatcher)
@Before
fun setup() {
context = mockk(relaxed = true)
fenixApplication = mockk(relaxed = true)
every { context.application } returns fenixApplication
homeActivity = mockk(relaxed = true)
view = mockk(relaxed = true)
lifecycleOwner = MockedLifecycleOwner(Lifecycle.State.STARTED)
navController = mockk(relaxed = true)
onboarding = mockk(relaxed = true)
browserFragment = spyk(BrowserFragment())
every { browserFragment.view } returns view
every { browserFragment.isAdded } returns true
every { browserFragment.browserToolbarView } returns mockk(relaxed = true)
every { browserFragment.activity } returns homeActivity
every { browserFragment.lifecycle } returns lifecycleOwner.lifecycle
every { browserFragment.onboarding } returns onboarding
every { browserFragment.requireContext() } returns context
every { browserFragment.initializeUI(any(), any()) } returns mockk()
every { browserFragment.fullScreenChanged(any()) } returns Unit
every { browserFragment.resumeDownloadDialogState(any(), any(), any(), any()) } returns Unit
testTab = createTab(url = "https://mozilla.org")
store = BrowserStore()
every { context.components.core.store } returns store
}
@After
fun cleanUp() {
testDispatcher.cleanupTestCoroutines()
}
@Test
fun `GIVEN fragment is added WHEN selected tab changes THEN theme is updated`() {
browserFragment.observeTabSelection(store)
verify(exactly = 0) { browserFragment.updateThemeForSession(testTab) }
addAndSelectTab(testTab)
verify(exactly = 1) { browserFragment.updateThemeForSession(testTab) }
}
@Test
fun `GIVEN fragment is removing WHEN selected tab changes THEN theme is not updated`() {
every { browserFragment.isRemoving } returns true
browserFragment.observeTabSelection(store)
addAndSelectTab(testTab)
verify(exactly = 0) { browserFragment.updateThemeForSession(testTab) }
}
@Test
fun `GIVEN browser UI is not initialized WHEN selected tab changes THEN browser UI is initialized`() {
browserFragment.observeTabSelection(store)
verify(exactly = 0) { browserFragment.initializeUI(view, testTab) }
addAndSelectTab(testTab)
verify(exactly = 1) { browserFragment.initializeUI(view, testTab) }
}
@Test
fun `GIVEN browser UI is initialized WHEN selected tab changes THEN toolbar is expanded`() {
browserFragment.browserInitialized = true
browserFragment.observeTabSelection(store)
val toolbar: BrowserToolbarView = mockk(relaxed = true)
every { browserFragment.browserToolbarView } returns toolbar
val newSelectedTab = createTab("https://firefox.com")
addAndSelectTab(newSelectedTab)
verify(exactly = 1) { toolbar.expand() }
}
@Test
fun `GIVEN browser UI is initialized WHEN selected tab changes THEN full screen mode is exited`() {
browserFragment.browserInitialized = true
browserFragment.observeTabSelection(store)
val newSelectedTab = createTab("https://firefox.com")
addAndSelectTab(newSelectedTab)
verify(exactly = 1) { browserFragment.fullScreenChanged(false) }
}
@Test
fun `GIVEN browser UI is initialized WHEN selected tab changes THEN download dialog is resumed`() {
browserFragment.browserInitialized = true
browserFragment.observeTabSelection(store)
val newSelectedTab = createTab("https://firefox.com")
addAndSelectTab(newSelectedTab)
verify(exactly = 1) {
browserFragment.resumeDownloadDialogState(newSelectedTab.id, store, context, any())
}
}
@Test
fun `WHEN url changes THEN toolbar is expanded`() {
addAndSelectTab(testTab)
browserFragment.expandToolbarOnNavigation(store)
val toolbar: BrowserToolbarView = mockk(relaxed = true)
every { browserFragment.browserToolbarView } returns toolbar
store.dispatch(ContentAction.UpdateUrlAction(testTab.id, "https://firefox.com")).joinBlocking()
verify(exactly = 1) { toolbar.expand() }
}
@Test
fun `WHEN load request is triggered THEN toolbar is expanded`() {
addAndSelectTab(testTab)
browserFragment.expandToolbarOnNavigation(store)
val toolbar: BrowserToolbarView = mockk(relaxed = true)
every { browserFragment.browserToolbarView } returns toolbar
store.dispatch(
ContentAction.UpdateLoadRequestAction(
testTab.id,
LoadRequestState("https://firefox.com", false, true)
)
).joinBlocking()
verify(exactly = 1) { toolbar.expand() }
}
@Test
fun `GIVEN tabs are restored WHEN there are no tabs THEN navigate to home`() {
browserFragment.observeRestoreComplete(store, navController)
store.dispatch(RestoreCompleteAction).joinBlocking()
verify(exactly = 1) { navController.popBackStack(R.id.homeFragment, false) }
}
@Test
fun `GIVEN tabs are restored WHEN there are tabs THEN do not navigate`() {
addAndSelectTab(testTab)
browserFragment.observeRestoreComplete(store, navController)
store.dispatch(RestoreCompleteAction).joinBlocking()
verify(exactly = 0) { navController.popBackStack(R.id.homeFragment, false) }
}
@Test
fun `GIVEN tabs are restored WHEN there is no selected tab THEN navigate to home`() {
val store = BrowserStore(initialState = BrowserState(tabs = listOf(testTab)))
browserFragment.observeRestoreComplete(store, navController)
store.dispatch(RestoreCompleteAction).joinBlocking()
verify(exactly = 1) { navController.popBackStack(R.id.homeFragment, false) }
}
@Test
fun `GIVEN the onboarding is finished WHEN visiting any link THEN the onboarding is not dismissed `() {
every { onboarding.userHasBeenOnboarded() } returns true
browserFragment.observeTabSource(store)
val newSelectedTab = createTab("any-tab.org")
addAndSelectTab(newSelectedTab)
verify(exactly = 0) { onboarding.finish() }
}
@Test
fun `GIVEN the onboarding is not finished WHEN visiting a link THEN the onboarding is dismissed `() {
every { onboarding.userHasBeenOnboarded() } returns false
browserFragment.observeTabSource(store)
val newSelectedTab = createTab("any-tab.org")
addAndSelectTab(newSelectedTab)
verify(exactly = 1) { onboarding.finish() }
}
@Test
fun `GIVEN the onboarding is not finished WHEN visiting an onboarding link THEN the onboarding is not dismissed `() {
every { onboarding.userHasBeenOnboarded() } returns false
browserFragment.observeTabSource(store)
val newSelectedTab = createTab(BaseBrowserFragment.onboardingLinksList[0])
addAndSelectTab(newSelectedTab)
verify(exactly = 0) { onboarding.finish() }
}
@Test
fun `GIVEN the onboarding is not finished WHEN opening a page from another app THEN the onboarding is not dismissed `() {
every { onboarding.userHasBeenOnboarded() } returns false
browserFragment.observeTabSource(store)
val newSelectedTab1 = createTab("any-tab-1.org", source = SessionState.Source.External.ActionSearch(mockk()))
val newSelectedTab2 = createTab("any-tab-2.org", source = SessionState.Source.External.ActionView(mockk()))
val newSelectedTab3 = createTab("any-tab-3.org", source = SessionState.Source.External.ActionSend(mockk()))
val newSelectedTab4 = createTab("any-tab-4.org", source = SessionState.Source.External.CustomTab(mockk()))
addAndSelectTab(newSelectedTab1)
verify(exactly = 0) { onboarding.finish() }
addAndSelectTab(newSelectedTab2)
verify(exactly = 0) { onboarding.finish() }
addAndSelectTab(newSelectedTab3)
verify(exactly = 0) { onboarding.finish() }
addAndSelectTab(newSelectedTab4)
verify(exactly = 0) { onboarding.finish() }
}
@Test
fun `GIVEN the onboarding is not finished WHEN visiting an link after redirect THEN the onboarding is not dismissed `() {
every { onboarding.userHasBeenOnboarded() } returns false
val newSelectedTab: TabSessionState = mockk(relaxed = true)
every { newSelectedTab.content.loadRequest?.triggeredByRedirect } returns true
browserFragment.observeTabSource(store)
addAndSelectTab(newSelectedTab)
verify(exactly = 0) { onboarding.finish() }
}
@Test
fun `WHEN isPullToRefreshEnabledInBrowser is disabled THEN pull down refresh is disabled`() {
every { context.settings().isPullToRefreshEnabledInBrowser } returns true
assert(browserFragment.shouldPullToRefreshBeEnabled(false))
every { context.settings().isPullToRefreshEnabledInBrowser } returns false
assert(!browserFragment.shouldPullToRefreshBeEnabled(false))
}
@Test
fun `WHEN in fullscreen THEN pull down refresh is disabled`() {
every { context.settings().isPullToRefreshEnabledInBrowser } returns true
assert(browserFragment.shouldPullToRefreshBeEnabled(false))
assert(!browserFragment.shouldPullToRefreshBeEnabled(true))
}
@Test
fun `WHEN fragment is not attached THEN toolbar invalidation does nothing`() {
val browserToolbarView: BrowserToolbarView = mockk(relaxed = true)
val browserToolbar: BrowserToolbar = mockk(relaxed = true)
val toolbarIntegration: ToolbarIntegration = mockk(relaxed = true)
every { browserToolbarView.view } returns browserToolbar
every { browserToolbarView.toolbarIntegration } returns toolbarIntegration
every { browserFragment.context } returns null
browserFragment._browserToolbarView = browserToolbarView
browserFragment.safeInvalidateBrowserToolbarView()
verify(exactly = 0) { browserToolbar.invalidateActions() }
verify(exactly = 0) { toolbarIntegration.invalidateMenu() }
}
@Test
@Suppress("TooGenericExceptionCaught")
fun `WHEN fragment is attached and toolbar view is null THEN toolbar invalidation is safe`() {
every { browserFragment.context } returns mockk(relaxed = true)
try {
browserFragment.safeInvalidateBrowserToolbarView()
} catch (e: Exception) {
fail("Exception thrown when invalidating toolbar")
}
}
@Test
fun `WHEN fragment and view are attached THEN toolbar invalidation is triggered`() {
val browserToolbarView: BrowserToolbarView = mockk(relaxed = true)
val browserToolbar: BrowserToolbar = mockk(relaxed = true)
val toolbarIntegration: ToolbarIntegration = mockk(relaxed = true)
every { browserToolbarView.view } returns browserToolbar
every { browserToolbarView.toolbarIntegration } returns toolbarIntegration
every { browserFragment.context } returns mockk(relaxed = true)
browserFragment._browserToolbarView = browserToolbarView
browserFragment.safeInvalidateBrowserToolbarView()
verify(exactly = 1) { browserToolbar.invalidateActions() }
verify(exactly = 1) { toolbarIntegration.invalidateMenu() }
}
@Test
fun `WHEN fragment configuration changed THEN menu is dismissed`() {
val browserToolbarView: BrowserToolbarView = mockk(relaxed = true)
every { browserFragment.context } returns null
browserFragment._browserToolbarView = browserToolbarView
browserFragment.onConfigurationChanged(mockk(relaxed = true))
verify(exactly = 1) { browserToolbarView.dismissMenu() }
}
private fun addAndSelectTab(tab: TabSessionState) {
store.dispatch(TabListAction.AddTabAction(tab)).joinBlocking()
store.dispatch(TabListAction.SelectTabAction(tab.id)).joinBlocking()
}
internal class MockedLifecycleOwner(initialState: Lifecycle.State) : LifecycleOwner {
val lifecycleRegistry = LifecycleRegistry(this).apply {
currentState = initialState
}
override fun getLifecycle(): Lifecycle = lifecycleRegistry
}
@Test
fun `WHEN updating the last browse activity THEN update the associated preference`() {
val settings: Settings = mockk(relaxed = true)
every { browserFragment.context } returns context
every { context.settings() } returns settings
browserFragment.updateLastBrowseActivity()
verify(exactly = 1) { settings.lastBrowseActivity = any() }
}
}