Bug 1876332 - Refactoring out Translations Controller and Interactor

This patch refactors out the translations controller and interactor to
transition fully to a redux pattern.

Leaving the translations use cases for reference browser usage.
fenix/124.1.0
ohall-m 4 months ago committed by mergify[bot]
parent 9aaeacc16f
commit 1dbf0145d5

@ -1,84 +0,0 @@
/* 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.translations
import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.translate.DetectedLanguages
import mozilla.components.concept.engine.translate.TranslationOptions
import mozilla.components.feature.session.SessionUseCases
import mozilla.components.support.base.log.logger.Logger
/**
* Manages requests to complete operations with other components.
*
* @param translationUseCase The use case for performing a translation.
* @param browserStore The browser store to use for this controller.
* @param tabId The tab to perform operations or complete requests for.
*/
class TranslationsController(
private val translationUseCase: SessionUseCases.TranslateUseCase,
private val browserStore: BrowserStore,
private val tabId: String,
) {
private val logger = Logger("TranslationsController")
/**
* Retrieves detected information about the language on the browser page, the user's preferred
* language, and if the detected page language is supported.
*
* @return The [DetectedLanguages] object that contains page and user preference information.
*/
fun getDetectedLanguages(): DetectedLanguages? {
logger.info("Retrieving translations language support from the browser store.")
return browserStore.state.findTab(tabId)?.translationsState?.translationEngineState?.detectedLanguages
}
/**
* Translates the page on the given [tabId]. Will fallback to default expectations if
* [fromLanguage] and [toLanguage] are not provided.
*
* @param tabId The ID of the tab to translate.
* @param fromLanguage The BCP-47 language code to translate from. If null, the default will
* be set to the page language.
* @param toLanguage The BCP-47 language code to translate to. If null, the default will
* be set to the user's preferred language.
* @param options Optional options to specify and additional criteria for the translation.
*/
fun translate(
tabId: String?,
fromLanguage: String?,
toLanguage: String?,
options: TranslationOptions?,
) {
if (fromLanguage != null && toLanguage != null) {
logger.info("Requesting a translation.")
translationUseCase.invoke(tabId, fromLanguage, toLanguage, options)
return
}
// Fallback to find defaults
var defaultFromLanguage = fromLanguage
var defaultToLanguage = toLanguage
val detectedLanguages = getDetectedLanguages()
if (defaultFromLanguage == null) {
defaultFromLanguage = detectedLanguages?.documentLangTag
logger.info("Setting translating to use a default 'from' of $defaultFromLanguage.")
}
if (defaultToLanguage == null) {
defaultToLanguage = detectedLanguages?.userPreferredLangTag
logger.info("Setting translating to use a default 'to' of $defaultToLanguage.")
}
if (defaultFromLanguage != null && defaultToLanguage != null) {
logger.info("Requesting a translation based on defaults.")
translationUseCase.invoke(tabId, defaultFromLanguage, defaultToLanguage, options)
return
}
}
}

@ -26,7 +26,6 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.theme.FirefoxTheme
@ -45,7 +44,6 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() {
private var behavior: BottomSheetBehavior<View>? = null
private val args by navArgs<TranslationsDialogFragmentArgs>()
private lateinit var interactor: TranslationsInteractor
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
super.onCreateDialog(savedInstanceState).apply {
@ -63,14 +61,6 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() {
container: ViewGroup?,
savedInstanceState: Bundle?,
): View = ComposeView(requireContext()).apply {
interactor = TranslationsInteractor(
translationsController = TranslationsController(
translationUseCase = requireComponents.useCases.sessionUseCases.translate,
browserStore = requireComponents.core.store,
tabId = args.sessionId,
),
)
setContent {
FirefoxTheme {
var translationsVisibility by remember {
@ -125,14 +115,7 @@ class TranslationsDialogFragment : BottomSheetDialogFragment() {
from = BrowserDirection.FromTranslationsDialogFragment,
)
},
onTranslateButtonClick = {
interactor.onTranslate(
tabId = args.sessionId,
fromLanguage = null,
toLanguage = null,
null,
)
},
onTranslateButtonClick = {},
onNotNowButtonClick = { dismiss() },
)
}

@ -1,54 +0,0 @@
/* 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.translations
import mozilla.components.concept.engine.translate.DetectedLanguages
import mozilla.components.concept.engine.translate.TranslationOptions
import mozilla.components.support.base.log.logger.Logger
/**
* Manages coordinating the functionality interactions for use in the views.
*
* @param translationsController The translations controller that requests data and
* interactions.
*/
class TranslationsInteractor(
private val translationsController: TranslationsController,
) {
private val logger = Logger("TranslationsInteractor")
/**
* Retrieves the [DetectedLanguages] applicable to the page.
*
* @return The [DetectedLanguages] object that contains page and user preference information.
*/
fun detectedLanguages(): DetectedLanguages? {
logger.info("Requesting translations language support from the controller.")
return translationsController.getDetectedLanguages()
}
/**
* Translates the page on the given [tabId].
* If null is provided for [fromLanguage] or [toLanguage], the engine will attempt to
* find a sensible default.
*
* @param tabId The ID of the tab to translate.
* @param fromLanguage The BCP-47 language code to translate from. Usually will be the detected
* page language. If set as null, will revert to a default page language, if known.
* @param toLanguage The BCP-47 language code to translate to. Usually will be the user's
* preferred language. If set as null, will revert to a default of the user's preferred
* language, if known.
* @param options Optional options to specify and additional criteria for the translation.
*/
fun onTranslate(
tabId: String?,
fromLanguage: String?,
toLanguage: String?,
options: TranslationOptions?,
) {
logger.info("Requesting a translation from the controller.")
translationsController.translate(tabId, fromLanguage, toLanguage, options)
}
}

@ -1,94 +0,0 @@
/* 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.components.translations
import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.TranslationsState
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.translate.DetectedLanguages
import mozilla.components.concept.engine.translate.TranslationEngineState
import mozilla.components.concept.engine.translate.TranslationOptions
import mozilla.components.feature.session.SessionUseCases
import mozilla.components.support.test.mock
import mozilla.components.support.test.whenever
import org.junit.Assert.assertNotNull
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.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.translations.TranslationsController
@RunWith(FenixRobolectricTestRunner::class)
class TranslationsControllerTest {
val tab: TabSessionState = spy(
createTab(
url = "https://www.firefox.com",
title = "Firefox",
id = "1",
),
)
private val tabs = spy(listOf(tab))
private val browserState = spy(BrowserState(tabs = tabs))
private val browserStore = spy(BrowserStore(browserState))
private val translationsUseCase: SessionUseCases.TranslateUseCase = mock()
private val translationsController = spy(TranslationsController(translationUseCase = translationsUseCase, browserStore = browserStore, tabId = tab.id))
@Test
fun `Controller translate called the translate use case as expected`() {
val from = "en"
val to = "es"
val options = TranslationOptions(false)
translationsController.translate(tab.id, from, to, options)
verify(translationsUseCase).invoke(tab.id, from, to, options)
}
@Test
fun `Controller translate called the translate use case as expected when languages were null`() {
val mockFrom = "es"
val mockTo = "en"
val mockDetectedLanguages = DetectedLanguages(
documentLangTag = mockFrom,
supportedDocumentLang = true,
userPreferredLangTag = mockTo,
)
whenever(translationsController.getDetectedLanguages()).thenReturn(mockDetectedLanguages)
val from = null
val to = null
val options = TranslationOptions(false)
translationsController.translate(tab.id, from, to, options)
verify(translationsUseCase).invoke(tab.id, mockFrom, mockTo, options)
}
@Test
fun `Controller detectedLanguages retrieved the languages as expected`() {
val mockFrom = "es"
val mockTo = "en"
val mockDetectedLanguages = DetectedLanguages(
documentLangTag = mockFrom,
supportedDocumentLang = true,
userPreferredLangTag = mockTo,
)
val mockState = TranslationsState(
translationEngineState = TranslationEngineState(mockDetectedLanguages),
)
whenever(browserState.findTab(tab.id)?.translationsState).thenReturn(mockState)
val test = translationsController.getDetectedLanguages()
assertNotNull(test)
assertTrue(test?.documentLangTag == mockDetectedLanguages.documentLangTag)
assertTrue(test?.userPreferredLangTag == mockDetectedLanguages.userPreferredLangTag)
}
}

@ -1,78 +0,0 @@
/* 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.components.translations
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.translate.DetectedLanguages
import mozilla.components.concept.engine.translate.TranslationOptions
import mozilla.components.feature.session.SessionUseCases
import mozilla.components.support.test.mock
import mozilla.components.support.test.whenever
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.translations.TranslationsController
import org.mozilla.fenix.translations.TranslationsInteractor
@RunWith(FenixRobolectricTestRunner::class)
class TranslationsInteractorTest {
val tab: TabSessionState = spy(
createTab(
url = "https://www.firefox.com",
title = "Firefox",
id = "1",
),
)
private val tabs = spy(listOf(tab))
private val browserState = spy(BrowserState(tabs = tabs))
private val browserStore = spy(BrowserStore(browserState))
private val translationsUseCase: SessionUseCases.TranslateUseCase = mock()
private val translationsController = spy(TranslationsController(translationUseCase = translationsUseCase, browserStore = browserStore, tabId = tab.id))
private val interactor = TranslationsInteractor(translationsController)
@Test
fun `Interactor onTranslate called the translate controller as expected`() {
val from = "en"
val to = "es"
val options = TranslationOptions(false)
interactor.onTranslate(tab.id, from, to, options)
verify(translationsController).translate(tab.id, from, to, options)
verify(translationsController, never()).getDetectedLanguages()
}
@Test
fun `Interactor onTranslate called the translate controller as expected when languages are null`() {
val mockFrom = "es"
val mockTo = "en"
val mockDetectedLanguages = DetectedLanguages(
documentLangTag = mockFrom,
supportedDocumentLang = true,
userPreferredLangTag = mockTo,
)
whenever(translationsController.getDetectedLanguages()).thenReturn(mockDetectedLanguages)
val from = null
val to = null
val options = TranslationOptions(false)
interactor.onTranslate(tab.id, from, to, options)
verify(translationsController).translate(tab.id, from, to, options)
verify(translationsController).getDetectedLanguages()
verify(translationsUseCase).invoke(tab.id, mockFrom, mockTo, options)
}
@Test
fun `Interactor detectedLanguages called the controller to pull detected languages`() {
interactor.detectedLanguages()
verify(translationsController).getDetectedLanguages()
}
}
Loading…
Cancel
Save