From 67ce5ad5d967aae8cd26c0b5ecf4467a73b772ee Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Wed, 8 Jul 2020 07:53:22 -0700 Subject: [PATCH] Add tests for onboarding viewholders (#11522) --- .../OnboardingAutomaticSignInViewHolder.kt | 68 ++++++------ .../org/mozilla/fenix/components/TestCore.kt | 2 +- ...OnboardingAutomaticSignInViewHolderTest.kt | 105 ++++++++++++++++++ .../OnboardingFinishViewHolderTest.kt | 39 +++++++ .../OnboardingHeaderViewHolderTest.kt | 34 ++++++ .../OnboardingManualSignInViewHolderTest.kt | 61 ++++++++++ .../OnboardingSectionHeaderViewHolderTest.kt | 38 +++++++ ...dingToolbarPositionPickerViewHolderTest.kt | 65 +++++++++++ 8 files changed, 380 insertions(+), 32 deletions(-) create mode 100644 app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolderTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingFinishViewHolderTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingHeaderViewHolderTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingManualSignInViewHolderTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingSectionHeaderViewHolderTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingToolbarPositionPickerViewHolderTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolder.kt index 76d8e9e6f..39c49a414 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolder.kt @@ -5,12 +5,14 @@ package org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding import android.view.View +import android.widget.Button +import androidx.annotation.VisibleForTesting import androidx.appcompat.content.res.AppCompatResources.getDrawable import androidx.recyclerview.widget.RecyclerView import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.onboarding_automatic_signin.view.* import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch import mozilla.components.service.fxa.manager.SignInWithShareableAccountResult import mozilla.components.service.fxa.sharing.ShareableAccount @@ -20,42 +22,18 @@ import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.ext.components -class OnboardingAutomaticSignInViewHolder(view: View) : RecyclerView.ViewHolder(view) { +class OnboardingAutomaticSignInViewHolder( + view: View, + private val scope: CoroutineScope = MainScope() +) : RecyclerView.ViewHolder(view) { private lateinit var shareableAccount: ShareableAccount private val headerText = view.header_text init { view.turn_on_sync_button.setOnClickListener { - it.context.components.analytics.metrics.track(Event.OnboardingAutoSignIn) - - it.turn_on_sync_button.text = it.context.getString( - R.string.onboarding_firefox_account_signing_in - ) - it.turn_on_sync_button.isEnabled = false - - CoroutineScope(Dispatchers.Main).launch { - val result = view.context.components.backgroundServices.accountManager - .signInWithShareableAccountAsync(shareableAccount).await() - when (result) { - SignInWithShareableAccountResult.Failure -> { - // Failed to sign-in (e.g. bad credentials). Allow to try again. - it.turn_on_sync_button.text = it.context.getString( - R.string.onboarding_firefox_account_auto_signin_confirm - ) - it.turn_on_sync_button.isEnabled = true - FenixSnackbar.make( - view = it, - duration = Snackbar.LENGTH_SHORT, - isDisplayedWithBrowserToolbar = false - ).setText( - it.context.getString(R.string.onboarding_firefox_account_automatic_signin_failed) - ).show() - } - SignInWithShareableAccountResult.WillRetry, SignInWithShareableAccountResult.Success -> { - // We consider both of these as a 'success'. - } - } + scope.launch { + onClick(it.turn_on_sync_button) } } } @@ -69,6 +47,34 @@ class OnboardingAutomaticSignInViewHolder(view: View) : RecyclerView.ViewHolder( headerText.putCompoundDrawablesRelativeWithIntrinsicBounds(start = icon) } + @VisibleForTesting + internal suspend fun onClick(button: Button) { + val context = button.context + context.components.analytics.metrics.track(Event.OnboardingAutoSignIn) + + button.text = context.getString(R.string.onboarding_firefox_account_signing_in) + button.isEnabled = false + + val accountManager = context.components.backgroundServices.accountManager + when (accountManager.signInWithShareableAccountAsync(shareableAccount).await()) { + SignInWithShareableAccountResult.Failure -> { + // Failed to sign-in (e.g. bad credentials). Allow to try again. + button.text = context.getString(R.string.onboarding_firefox_account_auto_signin_confirm) + button.isEnabled = true + FenixSnackbar.make( + view = button, + duration = Snackbar.LENGTH_SHORT, + isDisplayedWithBrowserToolbar = false + ).setText( + context.getString(R.string.onboarding_firefox_account_automatic_signin_failed) + ).show() + } + SignInWithShareableAccountResult.WillRetry, SignInWithShareableAccountResult.Success -> { + // We consider both of these as a 'success'. + } + } + } + companion object { const val LAYOUT_ID = R.layout.onboarding_automatic_signin } diff --git a/app/src/test/java/org/mozilla/fenix/components/TestCore.kt b/app/src/test/java/org/mozilla/fenix/components/TestCore.kt index 8764a7d68..cfe3ba62d 100644 --- a/app/src/test/java/org/mozilla/fenix/components/TestCore.kt +++ b/app/src/test/java/org/mozilla/fenix/components/TestCore.kt @@ -18,7 +18,7 @@ import mozilla.components.feature.pwa.WebAppShortcutManager class TestCore(context: Context) : Core(context) { override val engine = mockk(relaxed = true) { - every { this@mockk getProperty "settings" } returns mockk() + every { this@mockk getProperty "settings" } returns mockk(relaxed = true) } override val sessionManager = SessionManager(engine) override val store = mockk() diff --git a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolderTest.kt new file mode 100644 index 000000000..51464285a --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolderTest.kt @@ -0,0 +1,105 @@ +/* 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.home.sessioncontrol.viewholders.onboarding + +import android.view.LayoutInflater +import android.view.View +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.unmockkObject +import io.mockk.verify +import kotlinx.android.synthetic.main.onboarding_automatic_signin.view.* +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runBlockingTest +import mozilla.components.service.fxa.manager.SignInWithShareableAccountResult +import mozilla.components.service.fxa.sharing.ShareableAccount +import mozilla.components.support.test.robolectric.testContext +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.components.BackgroundServices +import org.mozilla.fenix.components.FenixSnackbar +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner + +@ExperimentalCoroutinesApi +@RunWith(FenixRobolectricTestRunner::class) +class OnboardingAutomaticSignInViewHolderTest { + + private lateinit var view: View + private lateinit var backgroundServices: BackgroundServices + private lateinit var snackbar: FenixSnackbar + + @Before + fun setup() { + view = LayoutInflater.from(testContext) + .inflate(OnboardingAutomaticSignInViewHolder.LAYOUT_ID, null) + snackbar = mockk(relaxed = true) + mockkObject(FenixSnackbar.Companion) + + backgroundServices = testContext.components.backgroundServices + every { FenixSnackbar.make(any(), any(), any(), any()) } returns snackbar + } + + @After + fun teardown() { + unmockkObject(FenixSnackbar.Companion) + } + + @Test + fun `bind updates header text`() { + val holder = OnboardingAutomaticSignInViewHolder(view) + holder.bind(mockk { + every { email } returns "email@example.com" + }) + assertEquals( + "You are signed in as email@example.com on another Firefox browser on this phone. Would you like to sign in with this account?", + view.header_text.text + ) + assertTrue(view.turn_on_sync_button.isEnabled) + } + + @Test + fun `sign in on click`() = runBlocking { + val account = mockk { + every { email } returns "email@example.com" + } + every { + backgroundServices.accountManager.signInWithShareableAccountAsync(account) + } returns CompletableDeferred(SignInWithShareableAccountResult.Success) + + val holder = OnboardingAutomaticSignInViewHolder(view, scope = this) + holder.bind(account) + holder.onClick(view.turn_on_sync_button) + + assertEquals("Signing in…", view.turn_on_sync_button.text) + assertFalse(view.turn_on_sync_button.isEnabled) + } + + @Test + fun `show error if sign in fails`() = runBlockingTest { + val account = mockk { + every { email } returns "email@example.com" + } + every { + backgroundServices.accountManager.signInWithShareableAccountAsync(account) + } returns CompletableDeferred(SignInWithShareableAccountResult.Failure) + + val holder = OnboardingAutomaticSignInViewHolder(view, scope = this) + holder.bind(account) + holder.onClick(view.turn_on_sync_button) + + assertEquals("Yes, sign me in", view.turn_on_sync_button.text) + assertTrue(view.turn_on_sync_button.isEnabled) + verify { snackbar.setText("Failed to sign-in") } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingFinishViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingFinishViewHolderTest.kt new file mode 100644 index 000000000..80616898b --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingFinishViewHolderTest.kt @@ -0,0 +1,39 @@ +/* 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.home.sessioncontrol.viewholders.onboarding + +import android.view.LayoutInflater +import android.view.View +import io.mockk.mockk +import io.mockk.verify +import kotlinx.android.synthetic.main.onboarding_finish.view.* +import mozilla.components.support.test.robolectric.testContext +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner +import org.mozilla.fenix.home.sessioncontrol.OnboardingInteractor + +@RunWith(FenixRobolectricTestRunner::class) +class OnboardingFinishViewHolderTest { + + private lateinit var view: View + private lateinit var interactor: OnboardingInteractor + + @Before + fun setup() { + view = LayoutInflater.from(testContext) + .inflate(OnboardingFinishViewHolder.LAYOUT_ID, null) + interactor = mockk(relaxed = true) + } + + @Test + fun `call interactor on click`() { + OnboardingFinishViewHolder(view, interactor) + + view.finish_button.performClick() + verify { interactor.onStartBrowsingClicked() } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingHeaderViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingHeaderViewHolderTest.kt new file mode 100644 index 000000000..f29821142 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingHeaderViewHolderTest.kt @@ -0,0 +1,34 @@ +/* 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.home.sessioncontrol.viewholders.onboarding + +import android.view.LayoutInflater +import android.view.View +import kotlinx.android.synthetic.main.onboarding_header.view.* +import mozilla.components.support.test.robolectric.testContext +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner + +@RunWith(FenixRobolectricTestRunner::class) +class OnboardingHeaderViewHolderTest { + + private lateinit var view: View + + @Before + fun setup() { + view = LayoutInflater.from(testContext) + .inflate(OnboardingHeaderViewHolder.LAYOUT_ID, null) + } + + @Test + fun `bind header text`() { + OnboardingHeaderViewHolder(view) + + assertEquals("Welcome to Firefox Preview!", view.header_text.text) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingManualSignInViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingManualSignInViewHolderTest.kt new file mode 100644 index 000000000..5fc02b4e7 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingManualSignInViewHolderTest.kt @@ -0,0 +1,61 @@ +/* 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.home.sessioncontrol.viewholders.onboarding + +import android.view.LayoutInflater +import android.view.View +import androidx.navigation.NavController +import androidx.navigation.Navigation +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import io.mockk.verify +import kotlinx.android.synthetic.main.onboarding_manual_signin.view.* +import mozilla.components.support.test.robolectric.testContext +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner +import org.mozilla.fenix.home.HomeFragmentDirections + +@RunWith(FenixRobolectricTestRunner::class) +class OnboardingManualSignInViewHolderTest { + + private lateinit var view: View + private lateinit var navController: NavController + + @Before + fun setup() { + view = LayoutInflater.from(testContext) + .inflate(OnboardingManualSignInViewHolder.LAYOUT_ID, null) + navController = mockk(relaxed = true) + + mockkStatic(Navigation::class) + every { Navigation.findNavController(view) } returns navController + } + + @After + fun teardown() { + unmockkStatic(Navigation::class) + } + + @Test + fun `bind header text`() { + OnboardingManualSignInViewHolder(view).bind() + + assertEquals("Get the most out of Firefox Preview.", view.header_text.text) + } + + @Test + fun `navigate on click`() { + OnboardingManualSignInViewHolder(view) + view.turn_on_sync_button.performClick() + + verify { navController.navigate(HomeFragmentDirections.actionGlobalTurnOnSync()) } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingSectionHeaderViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingSectionHeaderViewHolderTest.kt new file mode 100644 index 000000000..bd74bc6b4 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingSectionHeaderViewHolderTest.kt @@ -0,0 +1,38 @@ +/* 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.home.sessioncontrol.viewholders.onboarding + +import android.view.LayoutInflater +import android.view.View +import kotlinx.android.synthetic.main.onboarding_section_header.view.* +import mozilla.components.support.test.robolectric.testContext +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner + +@RunWith(FenixRobolectricTestRunner::class) +class OnboardingSectionHeaderViewHolderTest { + + private lateinit var view: View + + @Before + fun setup() { + view = LayoutInflater.from(testContext) + .inflate(OnboardingSectionHeaderViewHolder.LAYOUT_ID, null) + } + + @Test + fun `bind text`() { + val holder = OnboardingSectionHeaderViewHolder(view) + holder.bind { "Hello world" } + + assertEquals( + "Hello world", + view.section_header_text.text + ) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingToolbarPositionPickerViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingToolbarPositionPickerViewHolderTest.kt new file mode 100644 index 000000000..ef4361de2 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingToolbarPositionPickerViewHolderTest.kt @@ -0,0 +1,65 @@ +/* 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.home.sessioncontrol.viewholders.onboarding + +import android.view.LayoutInflater +import android.view.View +import io.mockk.every +import io.mockk.mockk +import kotlinx.android.synthetic.main.onboarding_toolbar_position_picker.view.* +import mozilla.components.support.test.robolectric.testContext +import org.junit.After +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner +import org.mozilla.fenix.utils.Settings + +@RunWith(FenixRobolectricTestRunner::class) +class OnboardingToolbarPositionPickerViewHolderTest { + + private lateinit var view: View + private lateinit var settings: Settings + + @Before + fun setup() { + view = LayoutInflater.from(testContext) + .inflate(OnboardingToolbarPositionPickerViewHolder.LAYOUT_ID, null) + settings = mockk(relaxed = true) + + Settings.instance = settings + } + + @After + fun teardown() { + Settings.instance = null + } + + @Test + fun `bottom illustration should select corresponding radio button`() { + every { settings.shouldUseBottomToolbar } returns false + OnboardingToolbarPositionPickerViewHolder(view) + assertTrue(view.toolbar_top_radio_button.isChecked) + assertFalse(view.toolbar_bottom_radio_button.isChecked) + + view.toolbar_bottom_image.performClick() + assertFalse(view.toolbar_top_radio_button.isChecked) + assertTrue(view.toolbar_bottom_radio_button.isChecked) + } + + @Test + fun `top illustration should select corresponding radio button`() { + every { settings.shouldUseBottomToolbar } returns true + OnboardingToolbarPositionPickerViewHolder(view) + assertFalse(view.toolbar_top_radio_button.isChecked) + assertTrue(view.toolbar_bottom_radio_button.isChecked) + + view.toolbar_top_image.performClick() + assertTrue(view.toolbar_top_radio_button.isChecked) + assertFalse(view.toolbar_bottom_radio_button.isChecked) + } +}