Skip to content
This repository has been archived by the owner on Jun 17, 2024. It is now read-only.

Commit

Permalink
Bug 1877278 - AC Translations Check for if the Engine is Supported
Browse files Browse the repository at this point in the history
This patch supports the workflow for checking if the device architecture supports translations.

This patch adds:
* New `TranslationsBrowserState` to hold global translations engine state
* New `SetEngineSupportedAction` to set the isEngineSupported value on `TranslationsBrowserState`
* New `EngineExceptionAction` to set errors on `TranslationsBrowserState`
  • Loading branch information
ohall-m committed Feb 13, 2024
1 parent ab76672 commit 1322455
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,16 @@ sealed class TranslationsAction : BrowserAction() {
val translationError: TranslationError,
) : TranslationsAction(), ActionWithTab

/**
* Indicates an app level translations error occurred and to set the error on
* [BrowserState.translationEngine].
*
* @property error The error that occurred.
*/
data class EngineExceptionAction(
val error: TranslationError,
) : TranslationsAction()

/**
* Indicates that the given [operation] data should be fetched for the given [tabId].
*
Expand All @@ -948,6 +958,16 @@ sealed class TranslationsAction : BrowserAction() {
val operation: TranslationOperation,
) : TranslationsAction(), ActionWithTab

/**
* Sets whether the device architecture supports translations or not on
* [BrowserState.translationEngine].
*
* @property isEngineSupported If the engine supports translations on this device.
*/
data class SetEngineSupportedAction(
val isEngineSupported: Boolean,
) : TranslationsAction()

/**
* Sets the languages that are supported by the translations engine.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package mozilla.components.browser.state.engine.middleware
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import mozilla.components.browser.state.action.BrowserAction
import mozilla.components.browser.state.action.InitAction
import mozilla.components.browser.state.action.TranslationsAction
import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.state.BrowserState
Expand Down Expand Up @@ -39,6 +40,11 @@ class TranslationsMiddleware(
) {
// Pre process actions
when (action) {
is InitAction ->
scope.launch {
requestEngineSupport(context)
}

is TranslationsAction.OperationRequestedAction -> {
when (action.operation) {
TranslationOperation.FETCH_SUPPORTED_LANGUAGES -> {
Expand Down Expand Up @@ -70,6 +76,35 @@ class TranslationsMiddleware(
next(action)
}

/**
* Checks if the translations engine supports the device architecture and updates the state.
*
* @param context Context to use to dispatch to the store.
*/
private fun requestEngineSupport(
context: MiddlewareContext<BrowserState, BrowserAction>,
) {
engine.isTranslationsEngineSupported(
onSuccess = { isEngineSupported ->
context.store.dispatch(
TranslationsAction.SetEngineSupportedAction(
isEngineSupported = isEngineSupported,
),
)
logger.info("Success requesting engine support.")
},

onError = { error ->
context.store.dispatch(
TranslationsAction.EngineExceptionAction(
error = TranslationError.UnknownEngineSupportError(error),
),
)
logger.error("Error requesting engine support: ", error)
},
)
}

/**
* Retrieves the list of supported languages using [scope] and dispatches the result to the
* store via [TranslationsAction.TranslateSetLanguagesAction] or else dispatches the failure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ package mozilla.components.browser.state.reducer

import mozilla.components.browser.state.action.TranslationsAction
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.state.TranslationsState
import mozilla.components.concept.engine.translate.TranslationOperation

internal object TranslationsStateReducer {

/**
* Reducer for [BrowserState.translationEngine] and [SessionState.translationsState]
*/
@Suppress("LongMethod")
fun reduce(state: BrowserState, action: TranslationsAction): BrowserState = when (action) {
is TranslationsAction.TranslateExpectedAction -> {
Expand Down Expand Up @@ -167,6 +171,10 @@ internal object TranslationsStateReducer {
}
}

is TranslationsAction.EngineExceptionAction -> {
state.copy(translationEngine = state.translationEngine.copy(engineError = action.error))
}

is TranslationsAction.SetSupportedLanguagesAction ->
state.copyWithTranslationsState(action.tabId) {
it.copy(
Expand Down Expand Up @@ -218,6 +226,15 @@ internal object TranslationsStateReducer {
state
}
}

is TranslationsAction.SetEngineSupportedAction -> {
state.copy(
translationEngine = state.translationEngine.copy(
isEngineSupported = action.isEngineSupported,
engineError = null,
),
)
}
}

private inline fun BrowserState.copyWithTranslationsState(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import java.util.Locale
* on application startup e.g. as an indicator that tabs have been restored.
* @property locale The current locale of the app. Will be null when following the system default.
* @property awesomeBarState Holds state for interactions with the [AwesomeBar].
* @property translationEngine Holds state for translations that apply to the entire browser.
*/
data class BrowserState(
val tabs: List<TabSessionState> = emptyList(),
Expand All @@ -54,4 +55,5 @@ data class BrowserState(
val showExtensionsProcessDisabledPrompt: Boolean = false,
val extensionsProcessDisabled: Boolean = false,
val awesomeBarState: AwesomeBarState = AwesomeBarState(),
val translationEngine: TranslationsBrowserState = TranslationsBrowserState(),
) : State
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* 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 mozilla.components.browser.state.state

import mozilla.components.concept.engine.translate.TranslationError

/**
* Value type that represents the state of the translations engine within a [BrowserState].
*
* @property isEngineSupported The translations engine supports the device architecture.
* @property engineError If the translations engine has a global error state.
* See [TranslationsState.translationError] for session level errors.
*/
data class TranslationsBrowserState(
val isEngineSupported: Boolean? = null,
val engineError: TranslationError? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -387,4 +387,37 @@ class TranslationsActionTest {
// Action success
assertNull(tabState().translationsState.supportedLanguages)
}

@Test
fun `WHEN a SetEngineSupportAction is dispatched THEN the browser store is updated to match`() {
// Initial state
assertNull(store.state.translationEngine.isEngineSupported)

// Dispatch
store.dispatch(
TranslationsAction.SetEngineSupportedAction(
isEngineSupported = true,
),
).joinBlocking()

// Final state
assertTrue(store.state.translationEngine.isEngineSupported!!)
}

@Test
fun `WHEN an EngineExceptionAction is dispatched THEN the browser store is updated to match`() {
// Initial state
assertNull(store.state.translationEngine.engineError)

// Dispatch
val error = TranslationError.UnknownError(Throwable())
store.dispatch(
TranslationsAction.EngineExceptionAction(
error = error,
),
).joinBlocking()

// Final state
assertEquals(store.state.translationEngine.engineError!!, error)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package mozilla.components.browser.state.engine.middleware

import kotlinx.coroutines.test.runTest
import mozilla.components.browser.state.action.BrowserAction
import mozilla.components.browser.state.action.InitAction
import mozilla.components.browser.state.action.TranslationsAction
import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.state.BrowserState
Expand Down Expand Up @@ -34,6 +35,7 @@ import mozilla.components.support.test.whenever
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify

Expand Down Expand Up @@ -281,4 +283,51 @@ class TranslationsMiddlewareTest {
)
waitForIdle()
}

@Test
fun `WHEN InitAction is dispatched THEN SetEngineSupportAction is dispatched`() = runTest {
// Send Action
// Note: Will cause a double InitAction
translationsMiddleware.invoke(context, {}, InitAction)
waitForIdle()

// Check expectations
val engineSupportedCallback = argumentCaptor<((Boolean) -> Unit)>()
verify(engine, atLeastOnce()).isTranslationsEngineSupported(
onSuccess = engineSupportedCallback.capture(),
onError = any(),
)
engineSupportedCallback.value.invoke(true)
waitForIdle()

verify(store, atLeastOnce()).dispatch(
TranslationsAction.SetEngineSupportedAction(
isEngineSupported = true,
),
)
waitForIdle()
}

@Test
fun `WHEN InitAction is dispatched AND has an issue THEN TranslateExceptionAction is dispatched`() = runTest() {
// Send Action
// Note: Will cause a double InitAction
translationsMiddleware.invoke(context, {}, InitAction)
waitForIdle()

// Check expectations
val errorCallback = argumentCaptor<((Throwable) -> Unit)>()
verify(engine, atLeastOnce()).isTranslationsEngineSupported(
onSuccess = any(),
onError = errorCallback.capture(),
)
errorCallback.value.invoke(IllegalStateException())
waitForIdle()

verify(store, atLeastOnce()).dispatch(
TranslationsAction.EngineExceptionAction(
error = TranslationError.UnknownEngineSupportError(any()),
),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ sealed class TranslationError(
class EngineNotSupportedError(override val cause: Throwable?) :
TranslationError(errorName = "engine-not-supported", displayError = false, cause = cause)

/**
* Could not determine if the translations engine works on the device architecture.
*
* @param cause The original throwable before it was converted into this error state.
*/
class UnknownEngineSupportError(override val cause: Throwable?) :
TranslationError(errorName = "unknown-engine-support", displayError = false, cause = cause)

/**
* Generic could not compete a translation error.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ class TelemetryMiddlewareTest {
val engine: Engine = mockk()
every { engine.enableExtensionProcessSpawning() } just runs
every { engine.disableExtensionProcessSpawning() } just runs
every { engine.isTranslationsEngineSupported(any(), any()) } just runs

store = BrowserStore(
middleware = listOf(telemetryMiddleware) + EngineMiddleware.create(engine),
initialState = BrowserState(),
Expand Down

0 comments on commit 1322455

Please sign in to comment.