Bug 1842245 - Refactor history fragment item click handlers to lib-state
parent
31b5683a76
commit
7e97ccb6f1
@ -0,0 +1,66 @@
|
||||
/* 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.library.history.state
|
||||
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavOptions
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.lib.state.Middleware
|
||||
import mozilla.components.lib.state.MiddlewareContext
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.library.history.History
|
||||
import org.mozilla.fenix.library.history.HistoryFragmentAction
|
||||
import org.mozilla.fenix.library.history.HistoryFragmentDirections
|
||||
import org.mozilla.fenix.library.history.HistoryFragmentState
|
||||
|
||||
/**
|
||||
* A [Middleware] for initiating navigation events based on [HistoryFragmentAction]s that are
|
||||
* dispatched to the [HistoryFragmentStore].
|
||||
*
|
||||
* @property navController [NavController] for handling navigation events
|
||||
* @property openToBrowser Callback to open history items in a browser window.
|
||||
*/
|
||||
class HistoryNavigationMiddleware(
|
||||
private val navController: NavController,
|
||||
private val openToBrowser: (item: History.Regular) -> Unit,
|
||||
private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main),
|
||||
) : Middleware<HistoryFragmentState, HistoryFragmentAction> {
|
||||
override fun invoke(
|
||||
context: MiddlewareContext<HistoryFragmentState, HistoryFragmentAction>,
|
||||
next: (HistoryFragmentAction) -> Unit,
|
||||
action: HistoryFragmentAction,
|
||||
) {
|
||||
// Read the current state before letting the chain process the action, so that clicks are
|
||||
// treated correctly in reference to the number of selected items.
|
||||
val currentState = context.state
|
||||
next(action)
|
||||
scope.launch {
|
||||
when (action) {
|
||||
is HistoryFragmentAction.HistoryItemClicked -> {
|
||||
if (currentState.mode.selectedItems.isEmpty()) {
|
||||
when (val item = action.item) {
|
||||
is History.Regular -> openToBrowser(item)
|
||||
is History.Group -> {
|
||||
navController.navigate(
|
||||
HistoryFragmentDirections.actionGlobalHistoryMetadataGroup(
|
||||
title = item.title,
|
||||
historyMetadataItems = item.items.toTypedArray(),
|
||||
),
|
||||
NavOptions.Builder()
|
||||
.setPopUpTo(R.id.historyMetadataGroupFragment, true)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/* 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.library.history.state
|
||||
|
||||
import mozilla.components.lib.state.Middleware
|
||||
import mozilla.components.lib.state.MiddlewareContext
|
||||
import mozilla.components.service.glean.private.NoExtras
|
||||
import org.mozilla.fenix.library.history.History
|
||||
import org.mozilla.fenix.library.history.HistoryFragmentAction
|
||||
import org.mozilla.fenix.library.history.HistoryFragmentState
|
||||
import org.mozilla.fenix.GleanMetrics.History as GleanHistory
|
||||
|
||||
/**
|
||||
* A [Middleware] for recording telemetry based on [HistoryFragmentAction]s that are dispatched to
|
||||
* the [HistoryFragmentStore].
|
||||
*
|
||||
* @property isInPrivateMode Whether the app is currently in private browsing mode.
|
||||
*/
|
||||
class HistoryTelemetryMiddleware(
|
||||
private val isInPrivateMode: Boolean,
|
||||
) : Middleware<HistoryFragmentState, HistoryFragmentAction> {
|
||||
override fun invoke(
|
||||
context: MiddlewareContext<HistoryFragmentState, HistoryFragmentAction>,
|
||||
next: (HistoryFragmentAction) -> Unit,
|
||||
action: HistoryFragmentAction,
|
||||
) {
|
||||
val currentState = context.state
|
||||
next(action)
|
||||
when (action) {
|
||||
is HistoryFragmentAction.HistoryItemClicked -> {
|
||||
if (currentState.mode.selectedItems.isEmpty()) {
|
||||
when (val item = action.item) {
|
||||
is History.Regular -> {
|
||||
GleanHistory.openedItem.record(
|
||||
GleanHistory.OpenedItemExtra(
|
||||
isRemote = item.isRemote,
|
||||
timeGroup = item.historyTimeGroup.toString(),
|
||||
isPrivate = isInPrivateMode,
|
||||
),
|
||||
)
|
||||
}
|
||||
is History.Group -> GleanHistory.searchTermGroupTapped.record(NoExtras())
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package org.mozilla.fenix.library.history.state
|
||||
|
||||
import androidx.navigation.NavController
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import mozilla.components.support.test.any
|
||||
import mozilla.components.support.test.ext.joinBlocking
|
||||
import mozilla.components.support.test.mock
|
||||
import mozilla.components.support.test.rule.MainCoroutineRule
|
||||
import mozilla.components.support.test.whenever
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mozilla.fenix.library.history.History
|
||||
import org.mozilla.fenix.library.history.HistoryFragmentAction
|
||||
import org.mozilla.fenix.library.history.HistoryFragmentState
|
||||
import org.mozilla.fenix.library.history.HistoryFragmentStore
|
||||
import org.mozilla.fenix.library.history.HistoryItemTimeGroup
|
||||
|
||||
class HistoryNavigationMiddlewareTest {
|
||||
@get:Rule
|
||||
val coroutinesTestRule = MainCoroutineRule()
|
||||
|
||||
@Test
|
||||
fun `WHEN regular history item clicked THEN item is opened in browser`() = runTest {
|
||||
var openedInBrowser = false
|
||||
val url = "url"
|
||||
val history = History.Regular(0, "title", url, 0, HistoryItemTimeGroup.timeGroupForTimestamp(0))
|
||||
val middleware = HistoryNavigationMiddleware(
|
||||
navController = mock(),
|
||||
openToBrowser = { item ->
|
||||
if (item.url == url) {
|
||||
openedInBrowser = true
|
||||
}
|
||||
},
|
||||
scope = this,
|
||||
)
|
||||
val store =
|
||||
HistoryFragmentStore(HistoryFragmentState.initial, middleware = listOf(middleware))
|
||||
|
||||
store.dispatch(HistoryFragmentAction.HistoryItemClicked(history)).joinBlocking()
|
||||
advanceUntilIdle()
|
||||
|
||||
assertTrue(openedInBrowser)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN selected items WHEN the last selected item is clicked THEN last item is not opened`() = runTest {
|
||||
var openedInBrowser = false
|
||||
val url = "url"
|
||||
val history = History.Regular(0, "title", url, 0, HistoryItemTimeGroup.timeGroupForTimestamp(0))
|
||||
val middleware = HistoryNavigationMiddleware(
|
||||
navController = mock(),
|
||||
openToBrowser = { item ->
|
||||
if (item.url == url) {
|
||||
openedInBrowser = true
|
||||
}
|
||||
},
|
||||
scope = this,
|
||||
)
|
||||
val state = HistoryFragmentState.initial.copy(
|
||||
mode = HistoryFragmentState.Mode.Editing(selectedItems = setOf(history)),
|
||||
)
|
||||
val store =
|
||||
HistoryFragmentStore(initialState = state, middleware = listOf(middleware))
|
||||
|
||||
store.dispatch(HistoryFragmentAction.HistoryItemClicked(history)).joinBlocking()
|
||||
advanceUntilIdle()
|
||||
|
||||
assertFalse(openedInBrowser)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN group history item clicked THEN navigate to history metadata fragment`() = runTest {
|
||||
val title = "title"
|
||||
val history = History.Group(0, title, 0, HistoryItemTimeGroup.timeGroupForTimestamp(0), listOf())
|
||||
val navController = mock<NavController>()
|
||||
whenever(navController.navigate(directions = any(), navOptions = any())).thenAnswer { }
|
||||
val middleware = HistoryNavigationMiddleware(
|
||||
navController = navController,
|
||||
openToBrowser = { },
|
||||
scope = this,
|
||||
)
|
||||
val store =
|
||||
HistoryFragmentStore(HistoryFragmentState.initial, middleware = listOf(middleware))
|
||||
|
||||
store.dispatch(HistoryFragmentAction.HistoryItemClicked(history)).joinBlocking()
|
||||
advanceUntilIdle()
|
||||
|
||||
verify(navController).navigate(
|
||||
directions = any(),
|
||||
navOptions = any(),
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package org.mozilla.fenix.library.history.state
|
||||
|
||||
import mozilla.components.service.glean.testing.GleanTestRule
|
||||
import mozilla.components.support.test.ext.joinBlocking
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.library.history.History
|
||||
import org.mozilla.fenix.library.history.HistoryFragmentAction
|
||||
import org.mozilla.fenix.library.history.HistoryFragmentState
|
||||
import org.mozilla.fenix.library.history.HistoryFragmentStore
|
||||
import org.mozilla.fenix.library.history.HistoryItemTimeGroup
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.mozilla.fenix.GleanMetrics.History as GleanHistory
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class HistoryTelemetryMiddlewareTest {
|
||||
@get:Rule
|
||||
val gleanTestRule = GleanTestRule(testContext)
|
||||
|
||||
private val middleware = HistoryTelemetryMiddleware(isInPrivateMode = false)
|
||||
|
||||
@Test
|
||||
fun `GIVEN no items selected WHEN regular history item clicked THEN telemetry recorded`() {
|
||||
val history = History.Regular(0, "title", "url", 0, HistoryItemTimeGroup.timeGroupForTimestamp(0))
|
||||
val store = HistoryFragmentStore(
|
||||
initialState = HistoryFragmentState.initial,
|
||||
middleware = listOf(middleware),
|
||||
)
|
||||
|
||||
store.dispatch(HistoryFragmentAction.HistoryItemClicked(history)).joinBlocking()
|
||||
|
||||
assertNotNull(GleanHistory.openedItem.testGetValue())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN items selected WHEN regular history item clicked THEN no telemetry recorded`() {
|
||||
val history = History.Regular(0, "title", "url", 0, HistoryItemTimeGroup.timeGroupForTimestamp(0))
|
||||
val state = HistoryFragmentState.initial.copy(
|
||||
mode = HistoryFragmentState.Mode.Editing(selectedItems = setOf(history)),
|
||||
)
|
||||
val store = HistoryFragmentStore(
|
||||
initialState = state,
|
||||
middleware = listOf(middleware),
|
||||
)
|
||||
|
||||
store.dispatch(HistoryFragmentAction.HistoryItemClicked(history)).joinBlocking()
|
||||
|
||||
assertNull(GleanHistory.openedItem.testGetValue())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN group history item clicked THEN record telemetry`() {
|
||||
val history = History.Group(0, "title", 0, HistoryItemTimeGroup.timeGroupForTimestamp(0), listOf())
|
||||
val store =
|
||||
HistoryFragmentStore(HistoryFragmentState.initial, middleware = listOf(middleware))
|
||||
|
||||
store.dispatch(HistoryFragmentAction.HistoryItemClicked(history)).joinBlocking()
|
||||
|
||||
assertNotNull(GleanHistory.searchTermGroupTapped.testGetValue())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue