Bug 1856979 - Translations UI Post-Translate Popup
parent
3b1aa89e19
commit
0505682776
@ -0,0 +1,142 @@
|
||||
/* 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 kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import mozilla.components.browser.state.selector.findTab
|
||||
import mozilla.components.browser.state.state.BrowserState
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.concept.engine.translate.initialFromLanguage
|
||||
import mozilla.components.concept.engine.translate.initialToLanguage
|
||||
import mozilla.components.lib.state.helpers.AbstractBinding
|
||||
|
||||
/**
|
||||
* Helper for observing Translation state from [BrowserStore].
|
||||
*/
|
||||
class TranslationsDialogBinding(
|
||||
browserStore: BrowserStore,
|
||||
private val translationsDialogStore: TranslationsDialogStore,
|
||||
private val sessionId: String,
|
||||
private val getTranslatedPageTitle: (localizedFrom: String?, localizedTo: String?) -> String,
|
||||
) : AbstractBinding<BrowserState>(browserStore) {
|
||||
|
||||
override suspend fun onState(flow: Flow<BrowserState>) {
|
||||
flow.mapNotNull { state -> state.findTab(sessionId) }
|
||||
.distinctUntilChangedBy {
|
||||
it.translationsState
|
||||
}
|
||||
.collect { sessionState ->
|
||||
val translationsState = sessionState.translationsState
|
||||
|
||||
val fromSelected =
|
||||
translationsState.translationEngineState?.initialFromLanguage(
|
||||
translationsState.supportedLanguages?.fromLanguages,
|
||||
)
|
||||
|
||||
fromSelected?.let {
|
||||
translationsDialogStore.dispatch(
|
||||
TranslationsDialogAction.UpdateFromSelectedLanguage(
|
||||
fromSelected,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val toSelected =
|
||||
translationsState.translationEngineState?.initialToLanguage(
|
||||
translationsState.supportedLanguages?.toLanguages,
|
||||
)
|
||||
toSelected?.let {
|
||||
translationsDialogStore.dispatch(
|
||||
TranslationsDialogAction.UpdateToSelectedLanguage(
|
||||
toSelected,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
toSelected != null && fromSelected != null &&
|
||||
translationsDialogStore.state.translatedPageTitle == null
|
||||
) {
|
||||
translationsDialogStore.dispatch(
|
||||
TranslationsDialogAction.UpdateTranslatedPageTitle(
|
||||
getTranslatedPageTitle(
|
||||
fromSelected.localizedDisplayName,
|
||||
toSelected.localizedDisplayName,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val translateFromLanguages = translationsState.supportedLanguages?.fromLanguages
|
||||
translateFromLanguages?.let {
|
||||
translationsDialogStore.dispatch(
|
||||
TranslationsDialogAction.UpdateTranslateFromLanguages(
|
||||
translateFromLanguages,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val translateToLanguages = translationsState.supportedLanguages?.toLanguages
|
||||
translateToLanguages?.let {
|
||||
translationsDialogStore.dispatch(
|
||||
TranslationsDialogAction.UpdateTranslateToLanguages(
|
||||
translateToLanguages,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
if (translationsState.isTranslateProcessing) {
|
||||
updateStoreIfIsTranslateProcessing()
|
||||
}
|
||||
|
||||
if (translationsState.isTranslated && !translationsState.isTranslateProcessing) {
|
||||
updateStoreIfTranslated()
|
||||
}
|
||||
|
||||
if (translationsState.translationError != null) {
|
||||
translationsDialogStore.dispatch(
|
||||
TranslationsDialogAction.UpdateTranslationError(translationsState.translationError),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateStoreIfIsTranslateProcessing() {
|
||||
translationsDialogStore.dispatch(
|
||||
TranslationsDialogAction.UpdateTranslationInProgress(
|
||||
true,
|
||||
),
|
||||
)
|
||||
translationsDialogStore.dispatch(
|
||||
TranslationsDialogAction.DismissDialog(
|
||||
dismissDialogState = DismissDialogState.WaitingToBeDismissed,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateStoreIfTranslated() {
|
||||
translationsDialogStore.dispatch(
|
||||
TranslationsDialogAction.UpdateTranslationInProgress(
|
||||
false,
|
||||
),
|
||||
)
|
||||
|
||||
translationsDialogStore.dispatch(
|
||||
TranslationsDialogAction.UpdateTranslated(
|
||||
true,
|
||||
),
|
||||
)
|
||||
|
||||
if (translationsDialogStore.state.dismissDialogState == DismissDialogState.WaitingToBeDismissed) {
|
||||
translationsDialogStore.dispatch(
|
||||
TranslationsDialogAction.DismissDialog(
|
||||
dismissDialogState = DismissDialogState.Dismiss,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/* 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.action.TranslationsAction
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.concept.engine.translate.TranslationOperation
|
||||
import mozilla.components.lib.state.Middleware
|
||||
import mozilla.components.lib.state.MiddlewareContext
|
||||
|
||||
/**
|
||||
* [Middleware] implementation for updating [BrowserStore] based on translation actions.
|
||||
*/
|
||||
class TranslationsDialogMiddleware(
|
||||
private val browserStore: BrowserStore,
|
||||
private val sessionId: String,
|
||||
) : Middleware<TranslationsDialogState, TranslationsDialogAction> {
|
||||
|
||||
override fun invoke(
|
||||
context: MiddlewareContext<TranslationsDialogState, TranslationsDialogAction>,
|
||||
next: (TranslationsDialogAction) -> Unit,
|
||||
action: TranslationsDialogAction,
|
||||
) {
|
||||
when (action) {
|
||||
is TranslationsDialogAction.FetchSupportedLanguages -> {
|
||||
browserStore.dispatch(
|
||||
TranslationsAction.OperationRequestedAction(
|
||||
tabId = sessionId,
|
||||
operation = TranslationOperation.FETCH_SUPPORTED_LANGUAGES,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
is TranslationsDialogAction.TranslateAction -> {
|
||||
context.state.initialFrom?.code?.let { fromLanguage ->
|
||||
context.state.initialTo?.code?.let { toLanguage ->
|
||||
TranslationsAction.TranslateAction(
|
||||
tabId = sessionId,
|
||||
fromLanguage = fromLanguage,
|
||||
toLanguage = toLanguage,
|
||||
options = null,
|
||||
)
|
||||
}
|
||||
}?.let {
|
||||
browserStore.dispatch(
|
||||
it,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is TranslationsDialogAction.RestoreTranslation -> {
|
||||
browserStore.dispatch(TranslationsAction.TranslateRestoreAction(sessionId))
|
||||
}
|
||||
|
||||
else -> {
|
||||
next(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,285 @@
|
||||
/* 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.action.TranslationsAction
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.concept.engine.translate.Language
|
||||
import mozilla.components.concept.engine.translate.TranslationError
|
||||
import mozilla.components.lib.state.Action
|
||||
import mozilla.components.lib.state.Middleware
|
||||
import mozilla.components.lib.state.State
|
||||
import mozilla.components.lib.state.Store
|
||||
|
||||
/**
|
||||
* The [TranslationsDialogStore] holds the [TranslationsDialogState] (state tree).
|
||||
*
|
||||
* The only way to change the [TranslationsDialogState] inside
|
||||
* [TranslationsDialogStore] is to dispatch an [Action] on it.
|
||||
*/
|
||||
class TranslationsDialogStore(
|
||||
initialState: TranslationsDialogState,
|
||||
middlewares: List<Middleware<TranslationsDialogState, TranslationsDialogAction>> = emptyList(),
|
||||
) : Store<TranslationsDialogState, TranslationsDialogAction>(
|
||||
initialState,
|
||||
TranslationsDialogReducer::reduce,
|
||||
middlewares,
|
||||
) {
|
||||
init {
|
||||
dispatch(TranslationsDialogAction.FetchSupportedLanguages)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The current state of the Translations bottom sheet dialog.
|
||||
*
|
||||
* @property isTranslated The page is currently translated.
|
||||
* @property isTranslationInProgress The page is currently attempting a translation.
|
||||
* @property positiveButtonType Can be enabled,disabled or in progress.
|
||||
* @property error An error that can occur during the translation process.
|
||||
* @property dismissDialogState Whether the dialog bottom sheet should be dismissed.
|
||||
* @property initialFrom Initial "from" language, based on the translation state and page state.
|
||||
* @property initialTo Initial "to" language, based on the translation state and page state.
|
||||
* @property fromLanguages Translation menu items to be shown in the translate from dropdown.
|
||||
* @property toLanguages Translation menu items to be shown in the translate to dropdown.
|
||||
* @property translatedPageTitle Title of the bottom sheet dialogue if the page was translated.
|
||||
*/
|
||||
data class TranslationsDialogState(
|
||||
var isTranslated: Boolean = false,
|
||||
val isTranslationInProgress: Boolean = false,
|
||||
val positiveButtonType: PositiveButtonType? = null,
|
||||
val error: TranslationError? = null,
|
||||
val dismissDialogState: DismissDialogState? = null,
|
||||
val initialFrom: Language? = null,
|
||||
val initialTo: Language? = null,
|
||||
val fromLanguages: List<Language>? = null,
|
||||
val toLanguages: List<Language>? = null,
|
||||
val translatedPageTitle: String? = null,
|
||||
) : State
|
||||
|
||||
/**
|
||||
* Action to dispatch through the `TranslationsStore` to modify `TranslationsDialogState` through the reducer.
|
||||
*/
|
||||
sealed class TranslationsDialogAction : Action {
|
||||
/**
|
||||
* Invoked when the [TranslationsDialogStore] is added to the fragment.
|
||||
*/
|
||||
object InitTranslationsDialog : TranslationsDialogAction()
|
||||
|
||||
/**
|
||||
* When FetchSupportedLanguages is dispatched, an [TranslationsAction.OperationRequestedAction]
|
||||
* will be dispatched to the [BrowserStore]
|
||||
*/
|
||||
object FetchSupportedLanguages : TranslationsDialogAction()
|
||||
|
||||
/**
|
||||
* Invoked when the user wants to translate a website.
|
||||
*/
|
||||
object TranslateAction : TranslationsDialogAction()
|
||||
|
||||
/**
|
||||
* Invoked when the user wants to restore the website to its original pre-translated content.
|
||||
*/
|
||||
object RestoreTranslation : TranslationsDialogAction()
|
||||
|
||||
/**
|
||||
* Invoked when a translation error occurs during the translation process.
|
||||
*/
|
||||
data class UpdateTranslationError(
|
||||
val translationError: TranslationError? = null,
|
||||
) : TranslationsDialogAction()
|
||||
|
||||
/**
|
||||
* Updates translate from languages list.
|
||||
*/
|
||||
data class UpdateTranslateFromLanguages(
|
||||
val translateFromLanguages: List<Language>,
|
||||
) : TranslationsDialogAction()
|
||||
|
||||
/**
|
||||
* Updates translate to languages list.
|
||||
*/
|
||||
data class UpdateTranslateToLanguages(
|
||||
val translateToLanguages: List<Language>,
|
||||
) : TranslationsDialogAction()
|
||||
|
||||
/**
|
||||
* Updates to the current selected language from the "translateFromLanguages" list.
|
||||
*/
|
||||
data class UpdateFromSelectedLanguage(
|
||||
val language: Language,
|
||||
) : TranslationsDialogAction()
|
||||
|
||||
/**
|
||||
* Updates to the current selected language from the "translateToLanguages" list.
|
||||
*/
|
||||
data class UpdateToSelectedLanguage(
|
||||
val language: Language,
|
||||
) : TranslationsDialogAction()
|
||||
|
||||
/**
|
||||
* Dismiss the translation dialog fragment.
|
||||
*/
|
||||
data class DismissDialog(
|
||||
val dismissDialogState: DismissDialogState,
|
||||
) : TranslationsDialogAction()
|
||||
|
||||
/**
|
||||
* Updates the dialog content to translation in progress status.
|
||||
*/
|
||||
data class UpdateTranslationInProgress(
|
||||
val inProgress: Boolean,
|
||||
) : TranslationsDialogAction()
|
||||
|
||||
/**
|
||||
* Updates the dialog content to translated status.
|
||||
*/
|
||||
data class UpdateTranslated(
|
||||
val isTranslated: Boolean,
|
||||
) : TranslationsDialogAction()
|
||||
|
||||
/**
|
||||
* Updates the dialog title if the page was translated.
|
||||
*/
|
||||
data class UpdateTranslatedPageTitle(val title: String) : TranslationsDialogAction()
|
||||
}
|
||||
|
||||
/**
|
||||
* Positive button type from the translation bottom sheet.
|
||||
*/
|
||||
sealed class PositiveButtonType {
|
||||
/**
|
||||
* The translating indicator will appear.
|
||||
*/
|
||||
object InProgress : PositiveButtonType()
|
||||
|
||||
/**
|
||||
* The button is in a disabled state.
|
||||
*/
|
||||
object Disabled : PositiveButtonType()
|
||||
|
||||
/**
|
||||
* The button is in a enabled state.
|
||||
*/
|
||||
object Enabled : PositiveButtonType()
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss translation bottom sheet type.
|
||||
*/
|
||||
sealed class DismissDialogState {
|
||||
/**
|
||||
* The dialog should be dismissed.
|
||||
*/
|
||||
object Dismiss : DismissDialogState()
|
||||
|
||||
/**
|
||||
* This is the step when translation is in progress and the dialog is waiting to be dismissed.
|
||||
*/
|
||||
object WaitingToBeDismissed : DismissDialogState()
|
||||
}
|
||||
|
||||
internal object TranslationsDialogReducer {
|
||||
/**
|
||||
* Reduces the translations dialog state from the current state and an action performed on it.
|
||||
*
|
||||
* @param state The current translations dialog state.
|
||||
* @param action The action to perform.
|
||||
* @return The new [TranslationsDialogState].
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
fun reduce(
|
||||
state: TranslationsDialogState,
|
||||
action: TranslationsDialogAction,
|
||||
): TranslationsDialogState {
|
||||
return when (action) {
|
||||
is TranslationsDialogAction.UpdateFromSelectedLanguage -> {
|
||||
state.copy(
|
||||
initialFrom = action.language,
|
||||
positiveButtonType = if (state.initialTo != null && action.language != state.initialTo) {
|
||||
PositiveButtonType.Enabled
|
||||
} else {
|
||||
PositiveButtonType.Disabled
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
is TranslationsDialogAction.UpdateToSelectedLanguage -> {
|
||||
state.copy(
|
||||
initialTo = action.language,
|
||||
positiveButtonType = if (state.initialFrom != null && action.language != state.initialFrom) {
|
||||
PositiveButtonType.Enabled
|
||||
} else {
|
||||
PositiveButtonType.Disabled
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
is TranslationsDialogAction.UpdateTranslateToLanguages -> {
|
||||
state.copy(toLanguages = action.translateToLanguages)
|
||||
}
|
||||
|
||||
is TranslationsDialogAction.UpdateTranslateFromLanguages -> {
|
||||
state.copy(fromLanguages = action.translateFromLanguages)
|
||||
}
|
||||
|
||||
is TranslationsDialogAction.DismissDialog -> {
|
||||
state.copy(dismissDialogState = action.dismissDialogState)
|
||||
}
|
||||
|
||||
is TranslationsDialogAction.UpdateTranslationInProgress -> {
|
||||
state.copy(
|
||||
isTranslationInProgress = action.inProgress,
|
||||
positiveButtonType = if (action.inProgress) {
|
||||
PositiveButtonType.InProgress
|
||||
} else {
|
||||
state.positiveButtonType
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
is TranslationsDialogAction.InitTranslationsDialog -> {
|
||||
state.copy(
|
||||
positiveButtonType = if (state.initialTo == null || state.initialFrom == null) {
|
||||
PositiveButtonType.Disabled
|
||||
} else {
|
||||
state.positiveButtonType
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
is TranslationsDialogAction.UpdateTranslationError -> {
|
||||
state.copy(
|
||||
error = action.translationError,
|
||||
positiveButtonType = if (
|
||||
action.translationError is TranslationError.LanguageNotSupportedError
|
||||
) {
|
||||
PositiveButtonType.Disabled
|
||||
} else {
|
||||
PositiveButtonType.Enabled
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
is TranslationsDialogAction.UpdateTranslated -> {
|
||||
state.copy(
|
||||
isTranslated = action.isTranslated,
|
||||
positiveButtonType = PositiveButtonType.Disabled,
|
||||
)
|
||||
}
|
||||
|
||||
is TranslationsDialogAction.UpdateTranslatedPageTitle -> {
|
||||
state.copy(translatedPageTitle = action.title)
|
||||
}
|
||||
|
||||
is TranslationsDialogAction.TranslateAction,
|
||||
TranslationsDialogAction.FetchSupportedLanguages,
|
||||
TranslationsDialogAction.RestoreTranslation,
|
||||
-> {
|
||||
// handled by [TranslationsDialogMiddleware]
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,308 @@
|
||||
/* 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 androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import mozilla.components.browser.state.action.TranslationsAction
|
||||
import mozilla.components.browser.state.state.BrowserState
|
||||
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.Language
|
||||
import mozilla.components.concept.engine.translate.TranslationEngineState
|
||||
import mozilla.components.concept.engine.translate.TranslationError
|
||||
import mozilla.components.concept.engine.translate.TranslationOperation
|
||||
import mozilla.components.concept.engine.translate.TranslationPair
|
||||
import mozilla.components.concept.engine.translate.TranslationSupport
|
||||
import mozilla.components.support.test.ext.joinBlocking
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import mozilla.components.support.test.rule.MainCoroutineRule
|
||||
import mozilla.components.support.test.rule.runTestOnMain
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mockito.spy
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mozilla.fenix.R
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class TranslationsDialogBindingTest {
|
||||
@get:Rule
|
||||
val coroutineRule = MainCoroutineRule()
|
||||
|
||||
lateinit var browserStore: BrowserStore
|
||||
private lateinit var translationsDialogStore: TranslationsDialogStore
|
||||
|
||||
private val tabId = "1"
|
||||
private val tab = createTab(url = tabId, id = tabId)
|
||||
|
||||
@Test
|
||||
fun `WHEN fromLanguage and toLanguage get updated in the browserStore THEN translations dialog actions dispatched with the update`() =
|
||||
runTestOnMain {
|
||||
val englishLanguage = Language("en", "English")
|
||||
val spanishLanguage = Language("es", "Spanish")
|
||||
translationsDialogStore = spy(TranslationsDialogStore(TranslationsDialogState()))
|
||||
browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(tab),
|
||||
selectedTabId = tabId,
|
||||
),
|
||||
)
|
||||
|
||||
val binding = TranslationsDialogBinding(
|
||||
browserStore = browserStore,
|
||||
translationsDialogStore = translationsDialogStore,
|
||||
sessionId = tabId,
|
||||
getTranslatedPageTitle = { localizedFrom, localizedTo ->
|
||||
testContext.getString(
|
||||
R.string.translations_bottom_sheet_title_translation_completed,
|
||||
localizedFrom,
|
||||
localizedTo,
|
||||
)
|
||||
},
|
||||
)
|
||||
binding.start()
|
||||
|
||||
val detectedLanguages = DetectedLanguages(
|
||||
documentLangTag = englishLanguage.code,
|
||||
supportedDocumentLang = true,
|
||||
userPreferredLangTag = spanishLanguage.code,
|
||||
)
|
||||
|
||||
val translationEngineState = TranslationEngineState(
|
||||
detectedLanguages = detectedLanguages,
|
||||
error = null,
|
||||
isEngineReady = true,
|
||||
requestedTranslationPair = TranslationPair(
|
||||
fromLanguage = englishLanguage.code,
|
||||
toLanguage = spanishLanguage.code,
|
||||
),
|
||||
)
|
||||
|
||||
val supportLanguages = TranslationSupport(
|
||||
fromLanguages = listOf(englishLanguage),
|
||||
toLanguages = listOf(spanishLanguage),
|
||||
)
|
||||
|
||||
browserStore.dispatch(
|
||||
TranslationsAction.SetSupportedLanguagesAction(
|
||||
tabId = tab.id,
|
||||
supportedLanguages = supportLanguages,
|
||||
),
|
||||
).joinBlocking()
|
||||
|
||||
browserStore.dispatch(
|
||||
TranslationsAction.TranslateStateChangeAction(
|
||||
tabId = tabId,
|
||||
translationEngineState = translationEngineState,
|
||||
),
|
||||
).joinBlocking()
|
||||
|
||||
verify(translationsDialogStore).dispatch(
|
||||
TranslationsDialogAction.UpdateFromSelectedLanguage(
|
||||
englishLanguage,
|
||||
),
|
||||
)
|
||||
verify(translationsDialogStore).dispatch(
|
||||
TranslationsDialogAction.UpdateToSelectedLanguage(
|
||||
spanishLanguage,
|
||||
),
|
||||
)
|
||||
verify(translationsDialogStore).dispatch(
|
||||
TranslationsDialogAction.UpdateTranslatedPageTitle(
|
||||
testContext.getString(
|
||||
R.string.translations_bottom_sheet_title_translation_completed,
|
||||
englishLanguage.localizedDisplayName,
|
||||
spanishLanguage.localizedDisplayName,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN translate action is sent to the browserStore THEN update translation dialog store based on operation`() =
|
||||
runTestOnMain {
|
||||
val englishLanguage = Language("en", "English")
|
||||
val spanishLanguage = Language("es", "Spanish")
|
||||
translationsDialogStore = spy(TranslationsDialogStore(TranslationsDialogState()))
|
||||
browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(tab),
|
||||
selectedTabId = tabId,
|
||||
),
|
||||
)
|
||||
|
||||
val binding = TranslationsDialogBinding(
|
||||
browserStore = browserStore,
|
||||
translationsDialogStore = translationsDialogStore,
|
||||
sessionId = tabId,
|
||||
getTranslatedPageTitle = { localizedFrom, localizedTo ->
|
||||
testContext.getString(
|
||||
R.string.translations_bottom_sheet_title_translation_completed,
|
||||
localizedFrom,
|
||||
localizedTo,
|
||||
)
|
||||
},
|
||||
)
|
||||
binding.start()
|
||||
|
||||
browserStore.dispatch(
|
||||
TranslationsAction.TranslateAction(
|
||||
tabId = tabId,
|
||||
fromLanguage = englishLanguage.code,
|
||||
toLanguage = spanishLanguage.code,
|
||||
null,
|
||||
),
|
||||
).joinBlocking()
|
||||
|
||||
verify(translationsDialogStore).dispatch(
|
||||
TranslationsDialogAction.UpdateTranslationInProgress(
|
||||
true,
|
||||
),
|
||||
)
|
||||
verify(translationsDialogStore).dispatch(
|
||||
TranslationsDialogAction.DismissDialog(
|
||||
dismissDialogState = DismissDialogState.WaitingToBeDismissed,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN translate from languages list and translate to languages list are sent to the browserStore THEN update translation dialog store based on operation`() =
|
||||
runTestOnMain {
|
||||
translationsDialogStore = spy(TranslationsDialogStore(TranslationsDialogState()))
|
||||
browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(tab),
|
||||
selectedTabId = tabId,
|
||||
),
|
||||
)
|
||||
|
||||
val binding = TranslationsDialogBinding(
|
||||
browserStore = browserStore,
|
||||
translationsDialogStore = translationsDialogStore,
|
||||
sessionId = tabId,
|
||||
getTranslatedPageTitle = { localizedFrom, localizedTo ->
|
||||
testContext.getString(
|
||||
R.string.translations_bottom_sheet_title_translation_completed,
|
||||
localizedFrom,
|
||||
localizedTo,
|
||||
)
|
||||
},
|
||||
)
|
||||
binding.start()
|
||||
|
||||
val toLanguage = Language("de", "German")
|
||||
val fromLanguage = Language("es", "Spanish")
|
||||
val supportedLanguages = TranslationSupport(listOf(fromLanguage), listOf(toLanguage))
|
||||
browserStore.dispatch(
|
||||
TranslationsAction.SetSupportedLanguagesAction(
|
||||
tabId = tab.id,
|
||||
supportedLanguages = supportedLanguages,
|
||||
),
|
||||
).joinBlocking()
|
||||
|
||||
verify(translationsDialogStore).dispatch(
|
||||
TranslationsDialogAction.UpdateTranslateFromLanguages(
|
||||
listOf(fromLanguage),
|
||||
),
|
||||
)
|
||||
verify(translationsDialogStore).dispatch(
|
||||
TranslationsDialogAction.UpdateTranslateToLanguages(
|
||||
listOf(toLanguage),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN translate action success is sent to the browserStore THEN update translation dialog store based on operation`() =
|
||||
runTestOnMain {
|
||||
translationsDialogStore =
|
||||
spy(TranslationsDialogStore(TranslationsDialogState(dismissDialogState = DismissDialogState.WaitingToBeDismissed)))
|
||||
browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(tab),
|
||||
selectedTabId = tabId,
|
||||
),
|
||||
)
|
||||
|
||||
val binding = TranslationsDialogBinding(
|
||||
browserStore = browserStore,
|
||||
translationsDialogStore = translationsDialogStore,
|
||||
sessionId = tabId,
|
||||
getTranslatedPageTitle = { localizedFrom, localizedTo ->
|
||||
testContext.getString(
|
||||
R.string.translations_bottom_sheet_title_translation_completed,
|
||||
localizedFrom,
|
||||
localizedTo,
|
||||
)
|
||||
},
|
||||
)
|
||||
binding.start()
|
||||
|
||||
browserStore.dispatch(
|
||||
TranslationsAction.TranslateSuccessAction(
|
||||
tabId = tab.id,
|
||||
operation = TranslationOperation.TRANSLATE,
|
||||
),
|
||||
).joinBlocking()
|
||||
|
||||
verify(translationsDialogStore).dispatch(
|
||||
TranslationsDialogAction.UpdateTranslated(
|
||||
true,
|
||||
),
|
||||
)
|
||||
verify(translationsDialogStore).dispatch(
|
||||
TranslationsDialogAction.UpdateTranslationInProgress(
|
||||
false,
|
||||
),
|
||||
)
|
||||
verify(translationsDialogStore).dispatch(
|
||||
TranslationsDialogAction.DismissDialog(
|
||||
dismissDialogState = DismissDialogState.Dismiss,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN translate fetch error is sent to the browserStore THEN update translation dialog store based on operation`() =
|
||||
runTestOnMain {
|
||||
translationsDialogStore =
|
||||
spy(TranslationsDialogStore(TranslationsDialogState()))
|
||||
browserStore = BrowserStore(
|
||||
BrowserState(
|
||||
tabs = listOf(tab),
|
||||
selectedTabId = tabId,
|
||||
),
|
||||
)
|
||||
|
||||
val binding = TranslationsDialogBinding(
|
||||
browserStore = browserStore,
|
||||
translationsDialogStore = translationsDialogStore,
|
||||
sessionId = tabId,
|
||||
getTranslatedPageTitle = { localizedFrom, localizedTo ->
|
||||
testContext.getString(
|
||||
R.string.translations_bottom_sheet_title_translation_completed,
|
||||
localizedFrom,
|
||||
localizedTo,
|
||||
)
|
||||
},
|
||||
)
|
||||
binding.start()
|
||||
|
||||
val fetchError = TranslationError.CouldNotLoadLanguagesError(null)
|
||||
browserStore.dispatch(
|
||||
TranslationsAction.TranslateExceptionAction(
|
||||
tabId = tab.id,
|
||||
operation = TranslationOperation.FETCH_SUPPORTED_LANGUAGES,
|
||||
translationError = fetchError,
|
||||
),
|
||||
).joinBlocking()
|
||||
|
||||
verify(translationsDialogStore).dispatch(
|
||||
TranslationsDialogAction.UpdateTranslationError(fetchError),
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
/* 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 io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import mozilla.components.browser.state.action.TranslationsAction
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.concept.engine.translate.Language
|
||||
import mozilla.components.concept.engine.translate.TranslationOperation
|
||||
import mozilla.components.support.test.ext.joinBlocking
|
||||
import mozilla.components.support.test.libstate.ext.waitUntilIdle
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class TranslationsDialogMiddlewareTest {
|
||||
|
||||
@Test
|
||||
fun `GIVEN translationState WHEN FetchSupportedLanguages action is called THEN call OperationRequestedAction from BrowserStore`() =
|
||||
runTest {
|
||||
val browserStore = mockk<BrowserStore>(relaxed = true)
|
||||
val translationsDialogMiddleware =
|
||||
TranslationsDialogMiddleware(browserStore = browserStore, sessionId = "tab1")
|
||||
|
||||
val translationStore = TranslationsDialogStore(
|
||||
initialState = TranslationsDialogState(),
|
||||
middlewares = listOf(translationsDialogMiddleware),
|
||||
)
|
||||
translationStore.dispatch(TranslationsDialogAction.FetchSupportedLanguages).joinBlocking()
|
||||
|
||||
translationStore.waitUntilIdle()
|
||||
|
||||
verify {
|
||||
browserStore.dispatch(
|
||||
TranslationsAction.OperationRequestedAction(
|
||||
tabId = "tab1",
|
||||
operation = TranslationOperation.FETCH_SUPPORTED_LANGUAGES,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN translationState WHEN TranslateAction from TranslationDialogStore is called THEN call TranslateAction from BrowserStore`() =
|
||||
runTest {
|
||||
val browserStore = mockk<BrowserStore>(relaxed = true)
|
||||
val translationsDialogMiddleware =
|
||||
TranslationsDialogMiddleware(browserStore = browserStore, sessionId = "tab1")
|
||||
|
||||
val translationStore = TranslationsDialogStore(
|
||||
initialState = TranslationsDialogState(
|
||||
initialFrom = Language("en", "English"),
|
||||
initialTo = Language("fr", "France"),
|
||||
),
|
||||
middlewares = listOf(translationsDialogMiddleware),
|
||||
)
|
||||
translationStore.dispatch(TranslationsDialogAction.TranslateAction).joinBlocking()
|
||||
|
||||
translationStore.waitUntilIdle()
|
||||
|
||||
verify {
|
||||
browserStore.dispatch(
|
||||
TranslationsAction.TranslateAction(
|
||||
tabId = "tab1",
|
||||
fromLanguage = "en",
|
||||
toLanguage = "fr",
|
||||
options = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN translationState WHEN RestoreTranslation from TranslationDialogStore is called THEN call TranslateRestoreAction from BrowserStore`() =
|
||||
runTest {
|
||||
val browserStore = mockk<BrowserStore>(relaxed = true)
|
||||
val translationsDialogMiddleware =
|
||||
TranslationsDialogMiddleware(browserStore = browserStore, sessionId = "tab1")
|
||||
|
||||
val translationStore = TranslationsDialogStore(
|
||||
initialState = TranslationsDialogState(),
|
||||
middlewares = listOf(translationsDialogMiddleware),
|
||||
)
|
||||
translationStore.dispatch(TranslationsDialogAction.RestoreTranslation).joinBlocking()
|
||||
|
||||
translationStore.waitUntilIdle()
|
||||
|
||||
verify {
|
||||
browserStore.dispatch(
|
||||
TranslationsAction.TranslateRestoreAction(
|
||||
tabId = "tab1",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,229 @@
|
||||
/* 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.Language
|
||||
import mozilla.components.concept.engine.translate.TranslationError
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class TranslationsDialogReducerTest {
|
||||
|
||||
@Test
|
||||
fun `WHEN the reducer is called for UpdateFromSelectedLanguage THEN a new state with updated fromSelectedLanguage is returned`() {
|
||||
val spanishLanguage = Language("es", "Spanish")
|
||||
val englishLanguage = Language("en", "English")
|
||||
|
||||
val translationsDialogState = TranslationsDialogState(initialTo = spanishLanguage)
|
||||
|
||||
val updatedState = TranslationsDialogReducer.reduce(
|
||||
translationsDialogState,
|
||||
TranslationsDialogAction.UpdateFromSelectedLanguage(englishLanguage),
|
||||
)
|
||||
|
||||
assertEquals(englishLanguage, updatedState.initialFrom)
|
||||
assertEquals(PositiveButtonType.Enabled, updatedState.positiveButtonType)
|
||||
|
||||
val updatedStateTwo = TranslationsDialogReducer.reduce(
|
||||
translationsDialogState,
|
||||
TranslationsDialogAction.UpdateFromSelectedLanguage(spanishLanguage),
|
||||
)
|
||||
|
||||
assertEquals(PositiveButtonType.Disabled, updatedStateTwo.positiveButtonType)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN the reducer is called for UpdateToSelectedLanguage THEN a new state with updated toSelectedLanguage is returned`() {
|
||||
val spanishLanguage = Language("es", "Spanish")
|
||||
val englishLanguage = Language("en", "English")
|
||||
|
||||
val translationsDialogState = TranslationsDialogState(initialFrom = spanishLanguage)
|
||||
|
||||
val updatedState = TranslationsDialogReducer.reduce(
|
||||
translationsDialogState,
|
||||
TranslationsDialogAction.UpdateToSelectedLanguage(englishLanguage),
|
||||
)
|
||||
|
||||
assertEquals(englishLanguage, updatedState.initialTo)
|
||||
assertEquals(PositiveButtonType.Enabled, updatedState.positiveButtonType)
|
||||
|
||||
val updatedStateTwo = TranslationsDialogReducer.reduce(
|
||||
translationsDialogState,
|
||||
TranslationsDialogAction.UpdateToSelectedLanguage(spanishLanguage),
|
||||
)
|
||||
|
||||
assertEquals(PositiveButtonType.Disabled, updatedStateTwo.positiveButtonType)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN the reducer is called for UpdateTranslateToLanguages THEN a new state with updated translateToLanguages is returned`() {
|
||||
val spanishLanguage = Language("es", "Spanish")
|
||||
val englishLanguage = Language("en", "English")
|
||||
|
||||
val translationsDialogState = TranslationsDialogState()
|
||||
|
||||
val updatedState = TranslationsDialogReducer.reduce(
|
||||
translationsDialogState,
|
||||
TranslationsDialogAction.UpdateTranslateToLanguages(
|
||||
listOf(
|
||||
spanishLanguage,
|
||||
englishLanguage,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(listOf(spanishLanguage, englishLanguage), updatedState.toLanguages)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN the reducer is called for UpdateTranslateFromLanguages THEN a new state with updated translatefromLanguages is returned`() {
|
||||
val spanishLanguage = Language("es", "Spanish")
|
||||
val englishLanguage = Language("en", "English")
|
||||
|
||||
val translationsDialogState = TranslationsDialogState()
|
||||
|
||||
val updatedState = TranslationsDialogReducer.reduce(
|
||||
translationsDialogState,
|
||||
TranslationsDialogAction.UpdateTranslateFromLanguages(
|
||||
listOf(
|
||||
spanishLanguage,
|
||||
englishLanguage,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(listOf(spanishLanguage, englishLanguage), updatedState.fromLanguages)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN the reducer is called for DismissDialog THEN a new state with updated dismiss dialog is returned`() {
|
||||
val translationsDialogState = TranslationsDialogState()
|
||||
|
||||
val updatedState = TranslationsDialogReducer.reduce(
|
||||
translationsDialogState,
|
||||
TranslationsDialogAction.DismissDialog(DismissDialogState.Dismiss),
|
||||
)
|
||||
|
||||
assertEquals(DismissDialogState.Dismiss, updatedState.dismissDialogState)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN the reducer is called for UpdateInProgress THEN a new state with translation in progress is returned`() {
|
||||
val translationsDialogState = TranslationsDialogState()
|
||||
|
||||
val updatedState = TranslationsDialogReducer.reduce(
|
||||
translationsDialogState,
|
||||
TranslationsDialogAction.UpdateTranslationInProgress(true),
|
||||
)
|
||||
|
||||
assertEquals(true, updatedState.isTranslationInProgress)
|
||||
assertEquals(PositiveButtonType.InProgress, updatedState.positiveButtonType)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN the reducer is called for InitTranslationsDialog THEN a new state for PositiveButtonType is returned`() {
|
||||
val translationsDialogState = TranslationsDialogState()
|
||||
|
||||
val updatedState = TranslationsDialogReducer.reduce(
|
||||
translationsDialogState,
|
||||
TranslationsDialogAction.InitTranslationsDialog,
|
||||
)
|
||||
|
||||
assertEquals(PositiveButtonType.Disabled, updatedState.positiveButtonType)
|
||||
|
||||
val spanishLanguage = Language("es", "Spanish")
|
||||
val englishLanguage = Language("en", "English")
|
||||
val translationsDialogStateTwo = TranslationsDialogState(
|
||||
initialFrom = spanishLanguage,
|
||||
initialTo = englishLanguage,
|
||||
positiveButtonType = PositiveButtonType.Enabled,
|
||||
)
|
||||
|
||||
val updatedStateTwo = TranslationsDialogReducer.reduce(
|
||||
translationsDialogStateTwo,
|
||||
TranslationsDialogAction.InitTranslationsDialog,
|
||||
)
|
||||
|
||||
assertEquals(PositiveButtonType.Enabled, updatedStateTwo.positiveButtonType)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN the reducer is called for UpdateTranslationError THEN a new state with translation error is returned`() {
|
||||
val translationsDialogState = TranslationsDialogState()
|
||||
|
||||
val updatedState = TranslationsDialogReducer.reduce(
|
||||
translationsDialogState,
|
||||
TranslationsDialogAction.UpdateTranslationError(
|
||||
TranslationError.LanguageNotSupportedError(
|
||||
null,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assertTrue(updatedState.error is TranslationError.LanguageNotSupportedError)
|
||||
assertEquals(PositiveButtonType.Disabled, updatedState.positiveButtonType)
|
||||
|
||||
val updatedStateTwo = TranslationsDialogReducer.reduce(
|
||||
translationsDialogState,
|
||||
TranslationsDialogAction.UpdateTranslationError(
|
||||
TranslationError.CouldNotLoadLanguagesError(
|
||||
null,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assertTrue(updatedStateTwo.error is TranslationError.CouldNotLoadLanguagesError)
|
||||
assertEquals(PositiveButtonType.Enabled, updatedStateTwo.positiveButtonType)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN the reducer is called for UpdateTranslated THEN a new state with translation translated is returned`() {
|
||||
val translationsDialogState = TranslationsDialogState()
|
||||
|
||||
val updatedState = TranslationsDialogReducer.reduce(
|
||||
translationsDialogState,
|
||||
TranslationsDialogAction.UpdateTranslated(
|
||||
true,
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(PositiveButtonType.Disabled, updatedState.positiveButtonType)
|
||||
assertEquals(true, updatedState.isTranslated)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN the reducer is called for UpdateTranslatedPageTitle THEN a new state with translation title is returned`() {
|
||||
val spanishLanguage = Language("es", "Spanish")
|
||||
val englishLanguage = Language("en", "English")
|
||||
val translationsDialogState =
|
||||
TranslationsDialogState(initialTo = englishLanguage, initialFrom = spanishLanguage)
|
||||
|
||||
val updatedState = TranslationsDialogReducer.reduce(
|
||||
translationsDialogState,
|
||||
TranslationsDialogAction.UpdateTranslatedPageTitle(
|
||||
testContext.getString(
|
||||
R.string.translations_bottom_sheet_title_translation_completed,
|
||||
spanishLanguage.localizedDisplayName,
|
||||
englishLanguage.localizedDisplayName,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
testContext.getString(
|
||||
R.string.translations_bottom_sheet_title_translation_completed,
|
||||
spanishLanguage.localizedDisplayName,
|
||||
englishLanguage.localizedDisplayName,
|
||||
),
|
||||
updatedState.translatedPageTitle,
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue