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

Commit

Permalink
This patch supports the workflow for checking if the device architect…
Browse files Browse the repository at this point in the history
…ure 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 a0e4e64
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 a0e4e64

Please sign in to comment.