diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index 5cf4e4e3c..a03672372 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -67,6 +67,7 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager import org.mozilla.fenix.components.metrics.BreadcrumbsRecorder import org.mozilla.fenix.components.metrics.Event +import org.mozilla.fenix.exceptions.trackingprotection.TrackingProtectionExceptionsFragmentDirections import org.mozilla.fenix.ext.alreadyOnDestination import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.metrics @@ -98,7 +99,6 @@ import org.mozilla.fenix.sync.SyncedTabsFragmentDirections import org.mozilla.fenix.tabtray.TabTrayDialogFragment import org.mozilla.fenix.theme.DefaultThemeManager import org.mozilla.fenix.theme.ThemeManager -import org.mozilla.fenix.trackingprotectionexceptions.TrackingProtectionExceptionsFragmentDirections import org.mozilla.fenix.utils.BrowsersCache /** diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsAdapter.kt b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsAdapter.kt new file mode 100644 index 000000000..b0050ecf8 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsAdapter.kt @@ -0,0 +1,100 @@ +/* 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.exceptions + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.annotation.StringRes +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import org.mozilla.fenix.exceptions.viewholders.ExceptionsDeleteButtonViewHolder +import org.mozilla.fenix.exceptions.viewholders.ExceptionsHeaderViewHolder +import org.mozilla.fenix.exceptions.viewholders.ExceptionsListItemViewHolder + +/** + * Adapter for a list of sites that are exempted from saving logins or tracking protection, + * along with controls to remove the exception. + */ +abstract class ExceptionsAdapter( + private val interactor: ExceptionsInteractor, + diffCallback: DiffUtil.ItemCallback +) : ListAdapter(diffCallback) { + + /** + * Change the list of items that are displayed. + * Header and footer items are added to the list as well. + */ + fun updateData(exceptions: List) { + val adapterItems: List = listOf(AdapterItem.Header) + + exceptions.map { wrapAdapterItem(it) } + + listOf(AdapterItem.DeleteButton) + submitList(adapterItems) + } + + /** + * Layout to use for the delete button. + */ + @get:LayoutRes + abstract val deleteButtonLayoutId: Int + + /** + * String to use for the exceptions list header. + */ + @get:StringRes + abstract val headerDescriptionResource: Int + + /** + * Converts an item from [updateData] into an adapter item. + */ + abstract fun wrapAdapterItem(item: T): AdapterItem.Item + + final override fun getItemViewType(position: Int) = when (getItem(position)) { + AdapterItem.DeleteButton -> deleteButtonLayoutId + AdapterItem.Header -> ExceptionsHeaderViewHolder.LAYOUT_ID + is AdapterItem.Item<*> -> ExceptionsListItemViewHolder.LAYOUT_ID + } + + final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false) + + return when (viewType) { + deleteButtonLayoutId -> + ExceptionsDeleteButtonViewHolder(view, interactor) + ExceptionsHeaderViewHolder.LAYOUT_ID -> + ExceptionsHeaderViewHolder(view, headerDescriptionResource) + ExceptionsListItemViewHolder.LAYOUT_ID -> + ExceptionsListItemViewHolder(view, interactor) + else -> throw IllegalStateException() + } + } + + @Suppress("Unchecked_Cast") + final override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + if (holder is ExceptionsListItemViewHolder<*>) { + holder as ExceptionsListItemViewHolder + val adapterItem = getItem(position) as AdapterItem.Item + holder.bind(adapterItem.item, adapterItem.url) + } + } + + /** + * Internal items for [ExceptionsAdapter] + */ + sealed class AdapterItem { + object DeleteButton : AdapterItem() + object Header : AdapterItem() + + /** + * Represents an item to display in [ExceptionsAdapter]. + * [T] should refer to the same value as in the [ExceptionsAdapter] and [ExceptionsInteractor]. + */ + abstract class Item : AdapterItem() { + abstract val item: T + abstract val url: String + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsInteractor.kt b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsInteractor.kt new file mode 100644 index 000000000..a1638052d --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsInteractor.kt @@ -0,0 +1,21 @@ +/* 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.exceptions + +/** + * Interface for exceptions view interactors. This interface is implemented by objects that want + * to respond to user interaction on the [ExceptionsView]. + */ +interface ExceptionsInteractor { + /** + * Called whenever all exception items are deleted + */ + fun onDeleteAll() + + /** + * Called whenever one exception item is deleted + */ + fun onDeleteOne(item: T) +} diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsView.kt b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsView.kt new file mode 100644 index 000000000..f42fe66ac --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsView.kt @@ -0,0 +1,41 @@ +/* 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.exceptions + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.core.view.isVisible +import androidx.recyclerview.widget.LinearLayoutManager +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.component_exceptions.* +import org.mozilla.fenix.R + +/** + * View that contains and configures the Exceptions List + */ +abstract class ExceptionsView( + container: ViewGroup, + protected val interactor: ExceptionsInteractor +) : LayoutContainer { + + override val containerView: FrameLayout = LayoutInflater.from(container.context) + .inflate(R.layout.component_exceptions, container, true) + .findViewById(R.id.exceptions_wrapper) + + protected abstract val exceptionsAdapter: ExceptionsAdapter + + init { + exceptions_list.apply { + layoutManager = LinearLayoutManager(containerView.context) + } + } + + fun update(items: List) { + exceptions_empty_view.isVisible = items.isEmpty() + exceptions_list.isVisible = items.isNotEmpty() + exceptionsAdapter.updateData(items) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/loginexceptions/ExceptionsFragmentStore.kt b/app/src/main/java/org/mozilla/fenix/exceptions/login/ExceptionsFragmentStore.kt similarity index 95% rename from app/src/main/java/org/mozilla/fenix/loginexceptions/ExceptionsFragmentStore.kt rename to app/src/main/java/org/mozilla/fenix/exceptions/login/ExceptionsFragmentStore.kt index 2493d3f68..f669f2b4a 100644 --- a/app/src/main/java/org/mozilla/fenix/loginexceptions/ExceptionsFragmentStore.kt +++ b/app/src/main/java/org/mozilla/fenix/exceptions/login/ExceptionsFragmentStore.kt @@ -2,7 +2,7 @@ * 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.loginexceptions +package org.mozilla.fenix.exceptions.login import mozilla.components.feature.logins.exceptions.LoginException import mozilla.components.lib.state.Action @@ -26,7 +26,7 @@ sealed class ExceptionsFragmentAction : Action { * The state for the Exceptions Screen * @property items List of exceptions to display */ -data class ExceptionsFragmentState(val items: List) : State +data class ExceptionsFragmentState(val items: List = emptyList()) : State /** * The ExceptionsState Reducer. diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/login/LoginExceptionsAdapter.kt b/app/src/main/java/org/mozilla/fenix/exceptions/login/LoginExceptionsAdapter.kt new file mode 100644 index 000000000..ca94186f1 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/exceptions/login/LoginExceptionsAdapter.kt @@ -0,0 +1,44 @@ +/* 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.exceptions.login + +import androidx.recyclerview.widget.DiffUtil +import mozilla.components.feature.logins.exceptions.LoginException +import org.mozilla.fenix.R +import org.mozilla.fenix.exceptions.ExceptionsAdapter + +/** + * Adapter for a list of sites that are exempted from saving logins, + * along with controls to remove the exception. + */ +class LoginExceptionsAdapter( + interactor: LoginExceptionsInteractor +) : ExceptionsAdapter(interactor, DiffCallback) { + + override val deleteButtonLayoutId = R.layout.delete_logins_exceptions_button + override val headerDescriptionResource = R.string.preferences_passwords_exceptions_description + + override fun wrapAdapterItem(item: LoginException) = + LoginAdapterItem(item) + + data class LoginAdapterItem( + override val item: LoginException + ) : AdapterItem.Item() { + override val url get() = item.origin + } + + internal object DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: AdapterItem, newItem: AdapterItem) = + when (oldItem) { + AdapterItem.DeleteButton, AdapterItem.Header -> oldItem === newItem + is LoginAdapterItem -> newItem is LoginAdapterItem && oldItem.item.id == newItem.item.id + else -> false + } + + @Suppress("DiffUtilEquals") + override fun areContentsTheSame(oldItem: AdapterItem, newItem: AdapterItem) = + oldItem == newItem + } +} diff --git a/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsFragment.kt b/app/src/main/java/org/mozilla/fenix/exceptions/login/LoginExceptionsFragment.kt similarity index 70% rename from app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsFragment.kt rename to app/src/main/java/org/mozilla/fenix/exceptions/login/LoginExceptionsFragment.kt index c1519bb00..c0f7b4dec 100644 --- a/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/exceptions/login/LoginExceptionsFragment.kt @@ -2,7 +2,7 @@ * 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.loginexceptions +package org.mozilla.fenix.exceptions.login import android.os.Bundle import android.view.LayoutInflater @@ -13,10 +13,9 @@ import androidx.lifecycle.asLiveData import androidx.lifecycle.lifecycleScope import androidx.lifecycle.observe import kotlinx.android.synthetic.main.fragment_exceptions.view.* -import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.launch -import mozilla.components.feature.logins.exceptions.LoginException +import kotlinx.coroutines.plus import mozilla.components.lib.state.ext.consumeFrom import org.mozilla.fenix.R import org.mozilla.fenix.components.StoreProvider @@ -45,14 +44,17 @@ class LoginExceptionsFragment : Fragment() { val view = inflater.inflate(R.layout.fragment_exceptions, container, false) exceptionsStore = StoreProvider.get(this) { ExceptionsFragmentStore( - ExceptionsFragmentState( - items = listOf() - ) + ExceptionsFragmentState(items = emptyList()) ) } - exceptionsInteractor = - LoginExceptionsInteractor(::deleteOneItem, ::deleteAllItems) - exceptionsView = LoginExceptionsView(view.exceptionsLayout, exceptionsInteractor) + exceptionsInteractor = DefaultLoginExceptionsInteractor( + ioScope = viewLifecycleOwner.lifecycleScope + Dispatchers.IO, + loginExceptionStorage = requireComponents.core.loginExceptionStorage + ) + exceptionsView = LoginExceptionsView( + view.exceptionsLayout, + exceptionsInteractor + ) subscribeToLoginExceptions() return view } @@ -67,19 +69,7 @@ class LoginExceptionsFragment : Fragment() { @ExperimentalCoroutinesApi override fun onViewCreated(view: View, savedInstanceState: Bundle?) { consumeFrom(exceptionsStore) { - exceptionsView.update(it) - } - } - - private fun deleteAllItems() { - viewLifecycleOwner.lifecycleScope.launch(IO) { - requireComponents.core.loginExceptionStorage.deleteAllLoginExceptions() - } - } - - private fun deleteOneItem(item: LoginException) { - viewLifecycleOwner.lifecycleScope.launch(IO) { - requireComponents.core.loginExceptionStorage.removeLoginException(item) + exceptionsView.update(it.items) } } } diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/login/LoginExceptionsInteractor.kt b/app/src/main/java/org/mozilla/fenix/exceptions/login/LoginExceptionsInteractor.kt new file mode 100644 index 000000000..0360dd614 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/exceptions/login/LoginExceptionsInteractor.kt @@ -0,0 +1,31 @@ +/* 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.exceptions.login + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import mozilla.components.feature.logins.exceptions.LoginException +import mozilla.components.feature.logins.exceptions.LoginExceptionStorage +import org.mozilla.fenix.exceptions.ExceptionsInteractor + +interface LoginExceptionsInteractor : ExceptionsInteractor + +class DefaultLoginExceptionsInteractor( + private val ioScope: CoroutineScope, + private val loginExceptionStorage: LoginExceptionStorage +) : LoginExceptionsInteractor { + + override fun onDeleteAll() { + ioScope.launch { + loginExceptionStorage.deleteAllLoginExceptions() + } + } + + override fun onDeleteOne(item: LoginException) { + ioScope.launch { + loginExceptionStorage.removeLoginException(item) + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/login/LoginExceptionsView.kt b/app/src/main/java/org/mozilla/fenix/exceptions/login/LoginExceptionsView.kt new file mode 100644 index 000000000..7d83cd40b --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/exceptions/login/LoginExceptionsView.kt @@ -0,0 +1,29 @@ +/* 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.exceptions.login + +import android.view.ViewGroup +import androidx.core.view.isVisible +import kotlinx.android.synthetic.main.component_exceptions.* +import mozilla.components.feature.logins.exceptions.LoginException +import org.mozilla.fenix.R +import org.mozilla.fenix.exceptions.ExceptionsView + +class LoginExceptionsView( + container: ViewGroup, + interactor: LoginExceptionsInteractor +) : ExceptionsView(container, interactor) { + + override val exceptionsAdapter = LoginExceptionsAdapter(interactor) + + init { + exceptions_learn_more.isVisible = false + exceptions_empty_message.text = + containerView.context.getString(R.string.preferences_passwords_exceptions_description_empty) + exceptions_list.apply { + adapter = exceptionsAdapter + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsFragmentStore.kt b/app/src/main/java/org/mozilla/fenix/exceptions/trackingprotection/ExceptionsFragmentStore.kt similarity index 85% rename from app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsFragmentStore.kt rename to app/src/main/java/org/mozilla/fenix/exceptions/trackingprotection/ExceptionsFragmentStore.kt index 1c2f5e982..6fa287e6d 100644 --- a/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsFragmentStore.kt +++ b/app/src/main/java/org/mozilla/fenix/exceptions/trackingprotection/ExceptionsFragmentStore.kt @@ -2,19 +2,13 @@ * 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.trackingprotectionexceptions +package org.mozilla.fenix.exceptions.trackingprotection import mozilla.components.concept.engine.content.blocking.TrackingProtectionException import mozilla.components.lib.state.Action import mozilla.components.lib.state.State import mozilla.components.lib.state.Store -/** - * Class representing an exception item - * @property url Host of the exception - */ -data class ExceptionItem(override val url: String) : TrackingProtectionException - /** * The [Store] for holding the [ExceptionsFragmentState] and applying [ExceptionsFragmentAction]s. */ @@ -32,7 +26,7 @@ sealed class ExceptionsFragmentAction : Action { * The state for the Exceptions Screen * @property items List of exceptions to display */ -data class ExceptionsFragmentState(val items: List) : State +data class ExceptionsFragmentState(val items: List = emptyList()) : State /** * The ExceptionsState Reducer. diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsAdapter.kt b/app/src/main/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsAdapter.kt new file mode 100644 index 000000000..c69fdd620 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsAdapter.kt @@ -0,0 +1,45 @@ +/* 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.exceptions.trackingprotection + +import androidx.recyclerview.widget.DiffUtil +import mozilla.components.concept.engine.content.blocking.TrackingProtectionException +import org.mozilla.fenix.R +import org.mozilla.fenix.exceptions.ExceptionsAdapter + +/** + * Adapter for a list of sites that are exempted from Tracking Protection, + * along with controls to remove the exception. + */ +class TrackingProtectionExceptionsAdapter( + interactor: TrackingProtectionExceptionsInteractor +) : ExceptionsAdapter(interactor, DiffCallback) { + + override val deleteButtonLayoutId = R.layout.delete_exceptions_button + override val headerDescriptionResource = R.string.enhanced_tracking_protection_exceptions + + override fun wrapAdapterItem(item: TrackingProtectionException) = + TrackingProtectionAdapterItem(item) + + data class TrackingProtectionAdapterItem( + override val item: TrackingProtectionException + ) : AdapterItem.Item() { + override val url get() = item.url + } + + internal object DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: AdapterItem, newItem: AdapterItem) = + when (oldItem) { + AdapterItem.DeleteButton, AdapterItem.Header -> oldItem === newItem + is TrackingProtectionAdapterItem -> + newItem is TrackingProtectionAdapterItem && oldItem.item.url == newItem.item.url + else -> false + } + + @Suppress("DiffUtilEquals") + override fun areContentsTheSame(oldItem: AdapterItem, newItem: AdapterItem) = + oldItem == newItem + } +} diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsFragment.kt b/app/src/main/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsFragment.kt new file mode 100644 index 000000000..eef5c4eb5 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsFragment.kt @@ -0,0 +1,66 @@ +/* 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.exceptions.trackingprotection + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import kotlinx.android.synthetic.main.fragment_exceptions.view.* +import kotlinx.coroutines.ExperimentalCoroutinesApi +import mozilla.components.lib.state.ext.consumeFrom +import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.R +import org.mozilla.fenix.components.StoreProvider +import org.mozilla.fenix.ext.requireComponents +import org.mozilla.fenix.ext.showToolbar + +/** + * Displays a list of sites that are exempted from Tracking Protection, + * along with controls to remove the exception. + */ +class TrackingProtectionExceptionsFragment : Fragment() { + + private lateinit var exceptionsStore: ExceptionsFragmentStore + private lateinit var exceptionsView: TrackingProtectionExceptionsView + private lateinit var exceptionsInteractor: DefaultTrackingProtectionExceptionsInteractor + + override fun onResume() { + super.onResume() + showToolbar(getString(R.string.preference_exceptions)) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val view = inflater.inflate(R.layout.fragment_exceptions, container, false) + exceptionsStore = StoreProvider.get(this) { + ExceptionsFragmentStore( + ExceptionsFragmentState(items = emptyList()) + ) + } + exceptionsInteractor = DefaultTrackingProtectionExceptionsInteractor( + activity = activity as HomeActivity, + exceptionsStore = exceptionsStore, + trackingProtectionUseCases = requireComponents.useCases.trackingProtectionUseCases + ) + exceptionsView = TrackingProtectionExceptionsView( + view.exceptionsLayout, + exceptionsInteractor + ) + exceptionsInteractor.reloadExceptions() + return view + } + + @ExperimentalCoroutinesApi + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + consumeFrom(exceptionsStore) { + exceptionsView.update(it.items) + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsInteractor.kt b/app/src/main/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsInteractor.kt new file mode 100644 index 000000000..f02a32b7e --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsInteractor.kt @@ -0,0 +1,54 @@ +/* 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.exceptions.trackingprotection + +import mozilla.components.concept.engine.content.blocking.TrackingProtectionException +import mozilla.components.feature.session.TrackingProtectionUseCases +import org.mozilla.fenix.BrowserDirection +import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.exceptions.ExceptionsInteractor +import org.mozilla.fenix.settings.SupportUtils + +interface TrackingProtectionExceptionsInteractor : ExceptionsInteractor { + /** + * Called whenever learn more about tracking protection is tapped + */ + fun onLearnMore() +} + +class DefaultTrackingProtectionExceptionsInteractor( + private val activity: HomeActivity, + private val exceptionsStore: ExceptionsFragmentStore, + private val trackingProtectionUseCases: TrackingProtectionUseCases +) : TrackingProtectionExceptionsInteractor { + + override fun onLearnMore() { + activity.openToBrowserAndLoad( + searchTermOrURL = SupportUtils.getGenericSumoURLForTopic( + SupportUtils.SumoTopic.TRACKING_PROTECTION + ), + newTab = true, + from = BrowserDirection.FromTrackingProtectionExceptions + ) + } + + override fun onDeleteAll() { + trackingProtectionUseCases.removeAllExceptions() + reloadExceptions() + } + + override fun onDeleteOne(item: TrackingProtectionException) { + trackingProtectionUseCases.removeException(item) + reloadExceptions() + } + + fun reloadExceptions() { + trackingProtectionUseCases.fetchExceptions { resultList -> + exceptionsStore.dispatch( + ExceptionsFragmentAction.Change(resultList) + ) + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsView.kt b/app/src/main/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsView.kt new file mode 100644 index 000000000..b312e939e --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsView.kt @@ -0,0 +1,33 @@ +/* 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.exceptions.trackingprotection + +import android.text.method.LinkMovementMethod +import android.view.ViewGroup +import kotlinx.android.synthetic.main.component_exceptions.* +import mozilla.components.concept.engine.content.blocking.TrackingProtectionException +import org.mozilla.fenix.exceptions.ExceptionsView +import org.mozilla.fenix.ext.addUnderline + +class TrackingProtectionExceptionsView( + container: ViewGroup, + interactor: TrackingProtectionExceptionsInteractor +) : ExceptionsView(container, interactor) { + + override val exceptionsAdapter = TrackingProtectionExceptionsAdapter(interactor) + + init { + exceptions_list.apply { + adapter = exceptionsAdapter + } + + with(exceptions_learn_more) { + addUnderline() + + movementMethod = LinkMovementMethod.getInstance() + setOnClickListener { interactor.onLearnMore() } + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsDeleteButtonViewHolder.kt b/app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsDeleteButtonViewHolder.kt similarity index 55% rename from app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsDeleteButtonViewHolder.kt rename to app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsDeleteButtonViewHolder.kt index d25754bdf..2c8af4f1f 100644 --- a/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsDeleteButtonViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsDeleteButtonViewHolder.kt @@ -2,27 +2,23 @@ * 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.trackingprotectionexceptions.viewholders +package org.mozilla.fenix.exceptions.viewholders import android.view.View import androidx.recyclerview.widget.RecyclerView -import kotlinx.android.synthetic.main.delete_exceptions_button.view.* +import com.google.android.material.button.MaterialButton import org.mozilla.fenix.R -import org.mozilla.fenix.trackingprotectionexceptions.ExceptionsInteractor +import org.mozilla.fenix.exceptions.ExceptionsInteractor class ExceptionsDeleteButtonViewHolder( view: View, - private val interactor: ExceptionsInteractor + private val interactor: ExceptionsInteractor<*> ) : RecyclerView.ViewHolder(view) { - private val deleteButton = view.removeAllExceptions init { + val deleteButton: MaterialButton = view.findViewById(R.id.removeAllExceptions) deleteButton.setOnClickListener { interactor.onDeleteAll() } } - - companion object { - const val LAYOUT_ID = R.layout.delete_exceptions_button - } } diff --git a/app/src/main/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsHeaderViewHolder.kt b/app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsHeaderViewHolder.kt similarity index 68% rename from app/src/main/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsHeaderViewHolder.kt rename to app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsHeaderViewHolder.kt index 7733563f7..97374247c 100644 --- a/app/src/main/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsHeaderViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsHeaderViewHolder.kt @@ -2,20 +2,21 @@ * 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.loginexceptions.viewholders +package org.mozilla.fenix.exceptions.viewholders import android.view.View +import androidx.annotation.StringRes import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.exceptions_description.view.* import org.mozilla.fenix.R -class LoginExceptionsHeaderViewHolder( - view: View +class ExceptionsHeaderViewHolder( + view: View, + @StringRes description: Int ) : RecyclerView.ViewHolder(view) { init { - view.exceptions_description.text = - view.context.getString(R.string.preferences_passwords_exceptions_description) + view.exceptions_description.text = view.context.getString(description) } companion object { diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsListItemViewHolder.kt b/app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsListItemViewHolder.kt new file mode 100644 index 000000000..d3225d34f --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsListItemViewHolder.kt @@ -0,0 +1,42 @@ +/* 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.exceptions.viewholders + +import android.view.View +import kotlinx.android.synthetic.main.exception_item.* +import mozilla.components.browser.icons.BrowserIcons +import org.mozilla.fenix.R +import org.mozilla.fenix.exceptions.ExceptionsInteractor +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.loadIntoView +import org.mozilla.fenix.utils.view.ViewHolder + +/** + * View holder for a single website that is exempted from Tracking Protection or Logins. + */ +class ExceptionsListItemViewHolder( + view: View, + private val interactor: ExceptionsInteractor, + private val icons: BrowserIcons = view.context.components.core.icons +) : ViewHolder(view) { + + private lateinit var item: T + + init { + delete_exception.setOnClickListener { + interactor.onDeleteOne(item) + } + } + + fun bind(item: T, url: String) { + this.item = item + webAddressView.text = url + icons.loadIntoView(favicon_image, url) + } + + companion object { + const val LAYOUT_ID = R.layout.exception_item + } +} diff --git a/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsAdapter.kt b/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsAdapter.kt deleted file mode 100644 index a04a705fe..000000000 --- a/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsAdapter.kt +++ /dev/null @@ -1,83 +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.loginexceptions - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import mozilla.components.feature.logins.exceptions.LoginException -import org.mozilla.fenix.loginexceptions.viewholders.LoginExceptionsDeleteButtonViewHolder -import org.mozilla.fenix.loginexceptions.viewholders.LoginExceptionsHeaderViewHolder -import org.mozilla.fenix.loginexceptions.viewholders.LoginExceptionsListItemViewHolder - -/** - * Adapter for a list of sites that are exempted from saving logins, - * along with controls to remove the exception. - */ -class LoginExceptionsAdapter( - private val interactor: LoginExceptionsInteractor -) : ListAdapter(DiffCallback) { - - /** - * Change the list of items that are displayed. - * Header and footer items are added to the list as well. - */ - fun updateData(exceptions: List) { - val adapterItems: List = listOf(AdapterItem.Header) + - exceptions.map { AdapterItem.Item(it) } + - listOf(AdapterItem.DeleteButton) - submitList(adapterItems) - } - - override fun getItemViewType(position: Int) = when (getItem(position)) { - AdapterItem.DeleteButton -> LoginExceptionsDeleteButtonViewHolder.LAYOUT_ID - AdapterItem.Header -> LoginExceptionsHeaderViewHolder.LAYOUT_ID - is AdapterItem.Item -> LoginExceptionsListItemViewHolder.LAYOUT_ID - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false) - - return when (viewType) { - LoginExceptionsDeleteButtonViewHolder.LAYOUT_ID -> LoginExceptionsDeleteButtonViewHolder( - view, - interactor - ) - LoginExceptionsHeaderViewHolder.LAYOUT_ID -> LoginExceptionsHeaderViewHolder(view) - LoginExceptionsListItemViewHolder.LAYOUT_ID -> LoginExceptionsListItemViewHolder( - view, - interactor - ) - else -> throw IllegalStateException() - } - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - if (holder is LoginExceptionsListItemViewHolder) { - val adapterItem = getItem(position) as AdapterItem.Item - holder.bind(adapterItem.item) - } - } - - sealed class AdapterItem { - object DeleteButton : AdapterItem() - object Header : AdapterItem() - data class Item(val item: LoginException) : AdapterItem() - } - - internal object DiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: AdapterItem, newItem: AdapterItem) = - when (oldItem) { - AdapterItem.DeleteButton, AdapterItem.Header -> oldItem === newItem - is AdapterItem.Item -> newItem is AdapterItem.Item && oldItem.item.id == newItem.item.id - } - - @Suppress("DiffUtilEquals") - override fun areContentsTheSame(oldItem: AdapterItem, newItem: AdapterItem) = - oldItem == newItem - } -} diff --git a/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsInteractor.kt b/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsInteractor.kt deleted file mode 100644 index 688ca4437..000000000 --- a/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsInteractor.kt +++ /dev/null @@ -1,24 +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.loginexceptions - -import mozilla.components.feature.logins.exceptions.LoginException - -/** - * Interactor for the exceptions screen - * Provides implementations for the ExceptionsViewInteractor - */ -class LoginExceptionsInteractor( - private val deleteOne: (LoginException) -> Unit, - private val deleteAll: () -> Unit -) : ExceptionsViewInteractor { - override fun onDeleteAll() { - deleteAll.invoke() - } - - override fun onDeleteOne(item: LoginException) { - deleteOne.invoke(item) - } -} diff --git a/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsView.kt b/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsView.kt deleted file mode 100644 index f6924870b..000000000 --- a/app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsView.kt +++ /dev/null @@ -1,62 +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.loginexceptions - -import android.view.LayoutInflater -import android.view.ViewGroup -import android.widget.FrameLayout -import androidx.core.view.isVisible -import androidx.recyclerview.widget.LinearLayoutManager -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.component_exceptions.* -import mozilla.components.feature.logins.exceptions.LoginException -import org.mozilla.fenix.R - -/** - * Interface for the ExceptionsViewInteractor. This interface is implemented by objects that want - * to respond to user interaction on the ExceptionsView - */ -interface ExceptionsViewInteractor { - /** - * Called whenever all exception items are deleted - */ - fun onDeleteAll() - - /** - * Called whenever one exception item is deleted - */ - fun onDeleteOne(item: LoginException) -} - -/** - * View that contains and configures the Exceptions List - */ -class LoginExceptionsView( - container: ViewGroup, - val interactor: LoginExceptionsInteractor -) : LayoutContainer { - - override val containerView: FrameLayout = LayoutInflater.from(container.context) - .inflate(R.layout.component_exceptions, container, true) - .findViewById(R.id.exceptions_wrapper) - - private val exceptionsAdapter = LoginExceptionsAdapter(interactor) - - init { - exceptions_learn_more.isVisible = false - exceptions_empty_message.text = - containerView.context.getString(R.string.preferences_passwords_exceptions_description_empty) - exceptions_list.apply { - adapter = exceptionsAdapter - layoutManager = LinearLayoutManager(containerView.context) - } - } - - fun update(state: ExceptionsFragmentState) { - exceptions_empty_view.isVisible = state.items.isEmpty() - exceptions_list.isVisible = state.items.isNotEmpty() - exceptionsAdapter.updateData(state.items) - } -} diff --git a/app/src/main/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsDeleteButtonViewHolder.kt b/app/src/main/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsDeleteButtonViewHolder.kt deleted file mode 100644 index a69ecf312..000000000 --- a/app/src/main/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsDeleteButtonViewHolder.kt +++ /dev/null @@ -1,28 +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.loginexceptions.viewholders - -import android.view.View -import androidx.recyclerview.widget.RecyclerView -import kotlinx.android.synthetic.main.delete_exceptions_button.view.* -import org.mozilla.fenix.R -import org.mozilla.fenix.loginexceptions.LoginExceptionsInteractor - -class LoginExceptionsDeleteButtonViewHolder( - view: View, - private val interactor: LoginExceptionsInteractor -) : RecyclerView.ViewHolder(view) { - private val deleteButton = view.removeAllExceptions - - init { - deleteButton.setOnClickListener { - interactor.onDeleteAll() - } - } - - companion object { - const val LAYOUT_ID = R.layout.delete_logins_exceptions_button - } -} diff --git a/app/src/main/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsListItemViewHolder.kt b/app/src/main/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsListItemViewHolder.kt deleted file mode 100644 index 688da089a..000000000 --- a/app/src/main/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsListItemViewHolder.kt +++ /dev/null @@ -1,50 +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.loginexceptions.viewholders - -import android.view.View -import androidx.recyclerview.widget.RecyclerView -import kotlinx.android.synthetic.main.exception_item.view.* -import mozilla.components.feature.logins.exceptions.LoginException -import org.mozilla.fenix.R -import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.loadIntoView -import org.mozilla.fenix.loginexceptions.LoginExceptionsInteractor - -/** - * View holder for a single website that is exempted from Tracking Protection. - */ -class LoginExceptionsListItemViewHolder( - view: View, - private val interactor: LoginExceptionsInteractor -) : RecyclerView.ViewHolder(view) { - - private val favicon = view.favicon_image - private val url = view.webAddressView - private val deleteButton = view.delete_exception - - private var item: LoginException? = null - - init { - deleteButton.setOnClickListener { - item?.let { - interactor.onDeleteOne(it) - } - } - } - - fun bind(item: LoginException) { - this.item = item - url.text = item.origin - } - - private fun updateFavIcon(url: String) { - favicon.context.components.core.icons.loadIntoView(favicon, url) - } - - companion object { - const val LAYOUT_ID = R.layout.exception_item - } -} diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsAdapter.kt b/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsAdapter.kt deleted file mode 100644 index a2adfdd5d..000000000 --- a/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsAdapter.kt +++ /dev/null @@ -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.trackingprotectionexceptions - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import mozilla.components.concept.engine.content.blocking.TrackingProtectionException -import org.mozilla.fenix.trackingprotectionexceptions.viewholders.ExceptionsDeleteButtonViewHolder -import org.mozilla.fenix.trackingprotectionexceptions.viewholders.ExceptionsHeaderViewHolder -import org.mozilla.fenix.trackingprotectionexceptions.viewholders.ExceptionsListItemViewHolder - -/** - * Adapter for a list of sites that are exempted from Tracking Protection, - * along with controls to remove the exception. - */ -class ExceptionsAdapter( - private val interactor: ExceptionsInteractor -) : ListAdapter(DiffCallback) { - - /** - * Change the list of items that are displayed. - * Header and footer items are added to the list as well. - */ - fun updateData(exceptions: List) { - val adapterItems = mutableListOf() - adapterItems.add(AdapterItem.Header) - exceptions.mapTo(adapterItems) { AdapterItem.Item(it) } - adapterItems.add(AdapterItem.DeleteButton) - submitList(adapterItems) - } - - override fun getItemViewType(position: Int) = when (getItem(position)) { - AdapterItem.DeleteButton -> ExceptionsDeleteButtonViewHolder.LAYOUT_ID - AdapterItem.Header -> ExceptionsHeaderViewHolder.LAYOUT_ID - is AdapterItem.Item -> ExceptionsListItemViewHolder.LAYOUT_ID - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false) - - return when (viewType) { - ExceptionsDeleteButtonViewHolder.LAYOUT_ID -> ExceptionsDeleteButtonViewHolder( - view, - interactor - ) - ExceptionsHeaderViewHolder.LAYOUT_ID -> ExceptionsHeaderViewHolder(view) - ExceptionsListItemViewHolder.LAYOUT_ID -> ExceptionsListItemViewHolder(view, interactor) - else -> throw IllegalStateException() - } - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - if (holder is ExceptionsListItemViewHolder) { - val adapterItem = getItem(position) as AdapterItem.Item - holder.bind(adapterItem.item) - } - } - - sealed class AdapterItem { - object DeleteButton : AdapterItem() - object Header : AdapterItem() - data class Item(val item: TrackingProtectionException) : AdapterItem() - } - - private object DiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: AdapterItem, newItem: AdapterItem) = - areContentsTheSame(oldItem, newItem) - - @Suppress("DiffUtilEquals") - override fun areContentsTheSame(oldItem: AdapterItem, newItem: AdapterItem) = - oldItem == newItem - } -} diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsInteractor.kt b/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsInteractor.kt deleted file mode 100644 index 79694ac1c..000000000 --- a/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsInteractor.kt +++ /dev/null @@ -1,29 +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.trackingprotectionexceptions - -import mozilla.components.concept.engine.content.blocking.TrackingProtectionException - -/** - * Interactor for the exceptions screen - * Provides implementations for the ExceptionsViewInteractor - */ -class ExceptionsInteractor( - private val learnMore: () -> Unit, - private val deleteOne: (TrackingProtectionException) -> Unit, - private val deleteAll: () -> Unit -) : ExceptionsViewInteractor { - override fun onLearnMore() { - learnMore.invoke() - } - - override fun onDeleteAll() { - deleteAll.invoke() - } - - override fun onDeleteOne(item: TrackingProtectionException) { - deleteOne.invoke(item) - } -} diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsView.kt b/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsView.kt deleted file mode 100644 index fcc58f7ce..000000000 --- a/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsView.kt +++ /dev/null @@ -1,76 +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.trackingprotectionexceptions - -import android.text.method.LinkMovementMethod -import android.view.LayoutInflater -import android.view.ViewGroup -import android.widget.FrameLayout -import androidx.core.view.isVisible -import androidx.recyclerview.widget.LinearLayoutManager -import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.component_exceptions.* -import mozilla.components.concept.engine.content.blocking.TrackingProtectionException -import org.mozilla.fenix.R -import org.mozilla.fenix.ext.addUnderline - -/** - * Interface for the ExceptionsViewInteractor. This interface is implemented by objects that want - * to respond to user interaction on the ExceptionsView - */ -interface ExceptionsViewInteractor { - /** - * Called whenever learn more about tracking protection is tapped - */ - fun onLearnMore() - - /** - * Called whenever all exception items are deleted - */ - fun onDeleteAll() - - /** - * Called whenever one exception item is deleted - */ - fun onDeleteOne(item: TrackingProtectionException) -} - -/** - * View that contains and configures the Exceptions List - */ -class ExceptionsView( - container: ViewGroup, - interactor: ExceptionsInteractor -) : LayoutContainer { - - override val containerView: FrameLayout = LayoutInflater.from(container.context) - .inflate(R.layout.component_exceptions, container, true) - .findViewById(R.id.exceptions_wrapper) - - private val exceptionsAdapter = - ExceptionsAdapter( - interactor - ) - - init { - exceptions_list.apply { - adapter = exceptionsAdapter - layoutManager = LinearLayoutManager(container.context) - } - - with(exceptions_learn_more) { - addUnderline() - - movementMethod = LinkMovementMethod.getInstance() - setOnClickListener { interactor.onLearnMore() } - } - } - - fun update(state: ExceptionsFragmentState) { - exceptions_empty_view.isVisible = state.items.isEmpty() - exceptions_list.isVisible = state.items.isNotEmpty() - exceptionsAdapter.updateData(state.items) - } -} diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/TrackingProtectionExceptionsFragment.kt b/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/TrackingProtectionExceptionsFragment.kt deleted file mode 100644 index 79b37e13c..000000000 --- a/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/TrackingProtectionExceptionsFragment.kt +++ /dev/null @@ -1,107 +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.trackingprotectionexceptions - -import android.os.Bundle -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import kotlinx.android.synthetic.main.fragment_exceptions.view.* -import kotlinx.coroutines.ExperimentalCoroutinesApi -import mozilla.components.concept.engine.content.blocking.TrackingProtectionException -import mozilla.components.feature.session.TrackingProtectionUseCases -import mozilla.components.lib.state.ext.consumeFrom -import org.mozilla.fenix.BrowserDirection -import org.mozilla.fenix.HomeActivity -import org.mozilla.fenix.R -import org.mozilla.fenix.components.StoreProvider -import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.showToolbar -import org.mozilla.fenix.settings.SupportUtils - -/** - * Displays a list of sites that are exempted from Tracking Protection, - * along with controls to remove the exception. - */ -class TrackingProtectionExceptionsFragment : Fragment() { - - private lateinit var exceptionsStore: ExceptionsFragmentStore - private lateinit var exceptionsView: ExceptionsView - private lateinit var exceptionsInteractor: ExceptionsInteractor - private lateinit var trackingProtectionUseCases: TrackingProtectionUseCases - - override fun onResume() { - super.onResume() - showToolbar(getString(R.string.preference_exceptions)) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - val view = inflater.inflate(R.layout.fragment_exceptions, container, false) - trackingProtectionUseCases = view.context.components.useCases.trackingProtectionUseCases - exceptionsStore = StoreProvider.get(this) { - ExceptionsFragmentStore( - ExceptionsFragmentState( - items = emptyList() - ) - ) - } - exceptionsInteractor = - ExceptionsInteractor( - ::openLearnMore, - ::deleteOneItem, - ::deleteAllItems - ) - exceptionsView = - ExceptionsView( - view.exceptionsLayout, - exceptionsInteractor - ) - reloadExceptions() - return view - } - - @ExperimentalCoroutinesApi - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - consumeFrom(exceptionsStore) { - exceptionsView.update(it) - } - } - - private fun deleteAllItems() { - trackingProtectionUseCases.removeAllExceptions() - reloadExceptions() - } - - private fun deleteOneItem(item: TrackingProtectionException) { - trackingProtectionUseCases.removeException(item) - Log.e("Remove one exception", "$item") - reloadExceptions() - } - - private fun openLearnMore() { - (activity as HomeActivity).openToBrowserAndLoad( - searchTermOrURL = SupportUtils.getGenericSumoURLForTopic - (SupportUtils.SumoTopic.TRACKING_PROTECTION), - newTab = true, - from = BrowserDirection.FromTrackingProtectionExceptions - ) - } - - private fun reloadExceptions() { - trackingProtectionUseCases.fetchExceptions { resultList -> - exceptionsStore.dispatch( - ExceptionsFragmentAction.Change( - resultList - ) - ) - } - } -} diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsHeaderViewHolder.kt b/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsHeaderViewHolder.kt deleted file mode 100644 index 2a5c7b54d..000000000 --- a/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsHeaderViewHolder.kt +++ /dev/null @@ -1,17 +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.trackingprotectionexceptions.viewholders - -import android.view.View -import androidx.recyclerview.widget.RecyclerView -import org.mozilla.fenix.R - -class ExceptionsHeaderViewHolder( - view: View -) : RecyclerView.ViewHolder(view) { - companion object { - const val LAYOUT_ID = R.layout.exceptions_description - } -} diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsListItemViewHolder.kt b/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsListItemViewHolder.kt deleted file mode 100644 index ec43919b9..000000000 --- a/app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsListItemViewHolder.kt +++ /dev/null @@ -1,51 +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.trackingprotectionexceptions.viewholders - -import android.view.View -import androidx.recyclerview.widget.RecyclerView -import kotlinx.android.synthetic.main.exception_item.view.* -import mozilla.components.concept.engine.content.blocking.TrackingProtectionException -import org.mozilla.fenix.R -import org.mozilla.fenix.trackingprotectionexceptions.ExceptionsInteractor -import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.loadIntoView - -/** - * View holder for a single website that is exempted from Tracking Protection. - */ -class ExceptionsListItemViewHolder( - view: View, - private val interactor: ExceptionsInteractor -) : RecyclerView.ViewHolder(view) { - - private val favicon = view.favicon_image - private val url = view.webAddressView - private val deleteButton = view.delete_exception - - private var item: TrackingProtectionException? = null - - init { - deleteButton.setOnClickListener { - item?.let { - interactor.onDeleteOne(it) - } - } - } - - fun bind(item: TrackingProtectionException) { - this.item = item - url.text = item.url - updateFavIcon(item.url) - } - - private fun updateFavIcon(url: String) { - favicon.context.components.core.icons.loadIntoView(favicon, url) - } - - companion object { - const val LAYOUT_ID = R.layout.exception_item - } -} diff --git a/app/src/main/res/layout/component_exceptions.xml b/app/src/main/res/layout/component_exceptions.xml index 00d64c282..8dd612334 100644 --- a/app/src/main/res/layout/component_exceptions.xml +++ b/app/src/main/res/layout/component_exceptions.xml @@ -45,5 +45,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone" - tools:listitem="@layout/exception_item" /> + tools:listheader="@layout/exceptions_description" + tools:listitem="@layout/exception_item" + tools:listfooter="@layout/delete_exceptions_button" /> diff --git a/app/src/main/res/layout/delete_logins_exceptions_button.xml b/app/src/main/res/layout/delete_logins_exceptions_button.xml index 291e5e775..6b172eae9 100644 --- a/app/src/main/res/layout/delete_logins_exceptions_button.xml +++ b/app/src/main/res/layout/delete_logins_exceptions_button.xml @@ -2,7 +2,8 @@ - diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 3bd6c282d..55249ba73 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -347,7 +347,7 @@ @@ -609,7 +609,7 @@ android:label="@string/preferences_delete_browsing_data" /> adapter.getItemViewType(i) } + .map { viewType -> adapter.onCreateViewHolder(parent, viewType) } + .toList() + assertEquals(4, holders.size) + + assertTrue(holders[0] is ExceptionsHeaderViewHolder) + assertTrue(holders[1] is ExceptionsListItemViewHolder<*>) + assertTrue(holders[2] is ExceptionsListItemViewHolder<*>) + assertTrue(holders[3] is ExceptionsDeleteButtonViewHolder) + } + + @Test + fun `headers and delete should check if the other object is the same`() { + assertTrue( + LoginExceptionsAdapter.DiffCallback.areItemsTheSame( + ExceptionsAdapter.AdapterItem.Header, + ExceptionsAdapter.AdapterItem.Header + ) + ) + assertTrue( + LoginExceptionsAdapter.DiffCallback.areItemsTheSame( + ExceptionsAdapter.AdapterItem.DeleteButton, + ExceptionsAdapter.AdapterItem.DeleteButton + ) + ) + assertFalse( + LoginExceptionsAdapter.DiffCallback.areItemsTheSame( + ExceptionsAdapter.AdapterItem.Header, + ExceptionsAdapter.AdapterItem.DeleteButton + ) + ) + assertTrue( + LoginExceptionsAdapter.DiffCallback.areContentsTheSame( + ExceptionsAdapter.AdapterItem.Header, + ExceptionsAdapter.AdapterItem.Header + ) + ) + assertTrue( + LoginExceptionsAdapter.DiffCallback.areContentsTheSame( + ExceptionsAdapter.AdapterItem.DeleteButton, + ExceptionsAdapter.AdapterItem.DeleteButton + ) + ) + assertFalse( + LoginExceptionsAdapter.DiffCallback.areContentsTheSame( + ExceptionsAdapter.AdapterItem.DeleteButton, + ExceptionsAdapter.AdapterItem.Header + ) + ) + } + + @Test + fun `items with the same id should be marked as same`() { + assertTrue( + LoginExceptionsAdapter.DiffCallback.areItemsTheSame( + LoginExceptionsAdapter.LoginAdapterItem( + mockk { + every { id } returns 12L + } + ), + LoginExceptionsAdapter.LoginAdapterItem( + mockk { + every { id } returns 12L + } + ) + ) + ) + assertFalse( + LoginExceptionsAdapter.DiffCallback.areItemsTheSame( + LoginExceptionsAdapter.LoginAdapterItem( + mockk { + every { id } returns 14L + } + ), + LoginExceptionsAdapter.LoginAdapterItem( + mockk { + every { id } returns 12L + } + ) + ) + ) + assertFalse( + LoginExceptionsAdapter.DiffCallback.areItemsTheSame( + LoginExceptionsAdapter.LoginAdapterItem( + mockk { + every { id } returns 14L + } + ), + ExceptionsAdapter.AdapterItem.Header + ) + ) + assertFalse( + LoginExceptionsAdapter.DiffCallback.areItemsTheSame( + ExceptionsAdapter.AdapterItem.DeleteButton, + LoginExceptionsAdapter.LoginAdapterItem( + mockk { + every { id } returns 14L + } + ) + ) + ) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/exceptions/login/LoginExceptionsInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/exceptions/login/LoginExceptionsInteractorTest.kt new file mode 100644 index 000000000..b36f147e9 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/exceptions/login/LoginExceptionsInteractorTest.kt @@ -0,0 +1,42 @@ +/* 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.exceptions.login + +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.test.runBlockingTest +import mozilla.components.feature.logins.exceptions.LoginException +import mozilla.components.feature.logins.exceptions.LoginExceptionStorage +import org.junit.Before +import org.junit.Test + +@ExperimentalCoroutinesApi +class LoginExceptionsInteractorTest { + + private lateinit var loginExceptionStorage: LoginExceptionStorage + private lateinit var interactor: LoginExceptionsInteractor + private val scope = TestCoroutineScope() + + @Before + fun setup() { + loginExceptionStorage = mockk(relaxed = true) + interactor = DefaultLoginExceptionsInteractor(scope, loginExceptionStorage) + } + + @Test + fun onDeleteAll() = scope.runBlockingTest { + interactor.onDeleteAll() + verify { loginExceptionStorage.deleteAllLoginExceptions() } + } + + @Test + fun onDeleteOne() = scope.runBlockingTest { + val exceptionsItem: LoginException = mockk() + interactor.onDeleteOne(exceptionsItem) + verify { loginExceptionStorage.removeLoginException(exceptionsItem) } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/loginexceptions/LoginExceptionsViewTest.kt b/app/src/test/java/org/mozilla/fenix/exceptions/login/LoginExceptionsViewTest.kt similarity index 86% rename from app/src/test/java/org/mozilla/fenix/loginexceptions/LoginExceptionsViewTest.kt rename to app/src/test/java/org/mozilla/fenix/exceptions/login/LoginExceptionsViewTest.kt index e716bffba..b3faeb0c5 100644 --- a/app/src/test/java/org/mozilla/fenix/loginexceptions/LoginExceptionsViewTest.kt +++ b/app/src/test/java/org/mozilla/fenix/exceptions/login/LoginExceptionsViewTest.kt @@ -2,7 +2,7 @@ * 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.loginexceptions +package org.mozilla.fenix.exceptions.login import android.view.ViewGroup import android.widget.FrameLayout @@ -30,7 +30,10 @@ class LoginExceptionsViewTest { fun setup() { parent = FrameLayout(testContext) interactor = mockk() - view = LoginExceptionsView(parent, interactor) + view = LoginExceptionsView( + parent, + interactor + ) } @Test @@ -45,9 +48,7 @@ class LoginExceptionsViewTest { @Test fun `hide list when there are no items`() { - view.update(ExceptionsFragmentState( - items = emptyList() - )) + view.update(emptyList()) assertTrue(view.exceptions_empty_view.isVisible) assertFalse(view.exceptions_list.isVisible) @@ -55,9 +56,7 @@ class LoginExceptionsViewTest { @Test fun `shows list when there are items`() { - view.update(ExceptionsFragmentState( - items = listOf(mockk()) - )) + view.update(listOf(mockk())) assertFalse(view.exceptions_empty_view.isVisible) assertTrue(view.exceptions_list.isVisible) diff --git a/app/src/test/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsAdapterTest.kt b/app/src/test/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsAdapterTest.kt new file mode 100644 index 000000000..c4cd97501 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsAdapterTest.kt @@ -0,0 +1,151 @@ +/* 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.exceptions.trackingprotection + +import android.content.Context +import android.widget.FrameLayout +import androidx.appcompat.view.ContextThemeWrapper +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.ExperimentalCoroutinesApi +import mozilla.components.support.test.robolectric.testContext +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.R +import org.mozilla.fenix.exceptions.ExceptionsAdapter +import org.mozilla.fenix.exceptions.viewholders.ExceptionsDeleteButtonViewHolder +import org.mozilla.fenix.exceptions.viewholders.ExceptionsHeaderViewHolder +import org.mozilla.fenix.exceptions.viewholders.ExceptionsListItemViewHolder +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner + +@ExperimentalCoroutinesApi +@RunWith(FenixRobolectricTestRunner::class) +class TrackingProtectionExceptionsAdapterTest { + + private lateinit var interactor: TrackingProtectionExceptionsInteractor + private lateinit var adapter: TrackingProtectionExceptionsAdapter + private lateinit var context: Context + + @Before + fun setup() { + interactor = mockk() + adapter = TrackingProtectionExceptionsAdapter(interactor) + context = ContextThemeWrapper(testContext, R.style.NormalTheme) + } + + @Test + fun `creates correct view holder type`() { + val parent = FrameLayout(context) + adapter.updateData(listOf(mockk(), mockk())) + assertEquals(4, adapter.itemCount) + + val holders = (0 until adapter.itemCount).asSequence() + .map { i -> adapter.getItemViewType(i) } + .map { viewType -> adapter.onCreateViewHolder(parent, viewType) } + .toList() + assertEquals(4, holders.size) + + assertTrue(holders[0] is ExceptionsHeaderViewHolder) + assertTrue(holders[1] is ExceptionsListItemViewHolder<*>) + assertTrue(holders[2] is ExceptionsListItemViewHolder<*>) + assertTrue(holders[3] is ExceptionsDeleteButtonViewHolder) + } + + @Test + fun `headers and delete should check if the other object is the same`() { + assertTrue( + TrackingProtectionExceptionsAdapter.DiffCallback.areItemsTheSame( + ExceptionsAdapter.AdapterItem.Header, + ExceptionsAdapter.AdapterItem.Header + ) + ) + assertTrue( + TrackingProtectionExceptionsAdapter.DiffCallback.areItemsTheSame( + ExceptionsAdapter.AdapterItem.DeleteButton, + ExceptionsAdapter.AdapterItem.DeleteButton + ) + ) + assertFalse( + TrackingProtectionExceptionsAdapter.DiffCallback.areItemsTheSame( + ExceptionsAdapter.AdapterItem.Header, + ExceptionsAdapter.AdapterItem.DeleteButton + ) + ) + assertTrue( + TrackingProtectionExceptionsAdapter.DiffCallback.areContentsTheSame( + ExceptionsAdapter.AdapterItem.Header, + ExceptionsAdapter.AdapterItem.Header + ) + ) + assertTrue( + TrackingProtectionExceptionsAdapter.DiffCallback.areContentsTheSame( + ExceptionsAdapter.AdapterItem.DeleteButton, + ExceptionsAdapter.AdapterItem.DeleteButton + ) + ) + assertFalse( + TrackingProtectionExceptionsAdapter.DiffCallback.areContentsTheSame( + ExceptionsAdapter.AdapterItem.DeleteButton, + ExceptionsAdapter.AdapterItem.Header + ) + ) + } + + @Test + fun `items with the same url should be marked as same`() { + assertTrue( + TrackingProtectionExceptionsAdapter.DiffCallback.areItemsTheSame( + TrackingProtectionExceptionsAdapter.TrackingProtectionAdapterItem( + mockk { + every { url } returns "https://mozilla.org" + } + ), + TrackingProtectionExceptionsAdapter.TrackingProtectionAdapterItem( + mockk { + every { url } returns "https://mozilla.org" + } + ) + ) + ) + assertFalse( + TrackingProtectionExceptionsAdapter.DiffCallback.areItemsTheSame( + TrackingProtectionExceptionsAdapter.TrackingProtectionAdapterItem( + mockk { + every { url } returns "https://mozilla.org" + } + ), + TrackingProtectionExceptionsAdapter.TrackingProtectionAdapterItem( + mockk { + every { url } returns "https://firefox.com" + } + ) + ) + ) + assertFalse( + TrackingProtectionExceptionsAdapter.DiffCallback.areItemsTheSame( + TrackingProtectionExceptionsAdapter.TrackingProtectionAdapterItem( + mockk { + every { url } returns "https://mozilla.org" + } + ), + ExceptionsAdapter.AdapterItem.Header + ) + ) + assertFalse( + TrackingProtectionExceptionsAdapter.DiffCallback.areItemsTheSame( + ExceptionsAdapter.AdapterItem.DeleteButton, + TrackingProtectionExceptionsAdapter.TrackingProtectionAdapterItem( + mockk { + every { url } returns "https://mozilla.org" + } + ) + ) + ) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/loginexceptions/LoginExceptionFragmentStoreTest.kt b/app/src/test/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsFragmentStoreTest.kt similarity index 63% rename from app/src/test/java/org/mozilla/fenix/loginexceptions/LoginExceptionFragmentStoreTest.kt rename to app/src/test/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsFragmentStoreTest.kt index ee0bd3171..06e6748df 100644 --- a/app/src/test/java/org/mozilla/fenix/loginexceptions/LoginExceptionFragmentStoreTest.kt +++ b/app/src/test/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsFragmentStoreTest.kt @@ -2,21 +2,20 @@ 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.loginexceptions +package org.mozilla.fenix.exceptions.trackingprotection -import io.mockk.mockk import kotlinx.coroutines.runBlocking -import mozilla.components.feature.logins.exceptions.LoginException +import mozilla.components.concept.engine.content.blocking.TrackingProtectionException import org.junit.Assert.assertEquals import org.junit.Assert.assertNotSame import org.junit.Test -class LoginExceptionFragmentStoreTest { +class TrackingProtectionExceptionsFragmentStoreTest { @Test fun onChange() = runBlocking { - val initialState = emptyDefaultState() + val initialState = ExceptionsFragmentState() val store = ExceptionsFragmentStore(initialState) - val newExceptionsItem: LoginException = mockk() + val newExceptionsItem = ExceptionItem("URL") store.dispatch(ExceptionsFragmentAction.Change(listOf(newExceptionsItem))).join() assertNotSame(initialState, store.state) @@ -26,7 +25,5 @@ class LoginExceptionFragmentStoreTest { ) } - private fun emptyDefaultState(): ExceptionsFragmentState = ExceptionsFragmentState( - items = listOf() - ) + private data class ExceptionItem(override val url: String) : TrackingProtectionException } diff --git a/app/src/test/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsInteractorTest.kt new file mode 100644 index 000000000..c841b384b --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsInteractorTest.kt @@ -0,0 +1,88 @@ +/* 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.exceptions.trackingprotection + +import io.mockk.CapturingSlot +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.mockk +import io.mockk.slot +import io.mockk.verify +import io.mockk.verifySequence +import mozilla.components.concept.engine.content.blocking.TrackingProtectionException +import mozilla.components.feature.session.TrackingProtectionUseCases +import org.junit.Before +import org.junit.Test +import org.mozilla.fenix.BrowserDirection +import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.settings.SupportUtils + +class TrackingProtectionExceptionsInteractorTest { + + @MockK(relaxed = true) private lateinit var activity: HomeActivity + @MockK(relaxed = true) private lateinit var exceptionsStore: ExceptionsFragmentStore + @MockK(relaxed = true) private lateinit var trackingProtectionUseCases: TrackingProtectionUseCases + private lateinit var interactor: TrackingProtectionExceptionsInteractor + private lateinit var onResult: CapturingSlot<(List) -> Unit> + + @Before + fun setup() { + MockKAnnotations.init(this) + interactor = DefaultTrackingProtectionExceptionsInteractor( + activity = activity, + exceptionsStore = exceptionsStore, + trackingProtectionUseCases = trackingProtectionUseCases + ) + + onResult = slot() + every { trackingProtectionUseCases.fetchExceptions(capture(onResult)) } just Runs + } + + @Test + fun onLearnMore() { + interactor.onLearnMore() + + val supportUrl = SupportUtils.getGenericSumoURLForTopic( + SupportUtils.SumoTopic.TRACKING_PROTECTION + ) + verify { + activity.openToBrowserAndLoad( + searchTermOrURL = supportUrl, + newTab = true, + from = BrowserDirection.FromTrackingProtectionExceptions + ) + } + } + + @Test + fun onDeleteAll() { + interactor.onDeleteAll() + verifySequence { + trackingProtectionUseCases.removeAllExceptions() + trackingProtectionUseCases.fetchExceptions(any()) + } + + val results = mockk>() + onResult.captured(results) + verify { exceptionsStore.dispatch(ExceptionsFragmentAction.Change(results)) } + } + + @Test + fun onDeleteOne() { + val exceptionsItem = mockk() + interactor.onDeleteOne(exceptionsItem) + verifySequence { + trackingProtectionUseCases.removeException(exceptionsItem) + trackingProtectionUseCases.fetchExceptions(any()) + } + + val results = mockk>() + onResult.captured(results) + verify { exceptionsStore.dispatch(ExceptionsFragmentAction.Change(results)) } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsViewTest.kt b/app/src/test/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsViewTest.kt similarity index 71% rename from app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsViewTest.kt rename to app/src/test/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsViewTest.kt index a69c067b9..fbb41eca8 100644 --- a/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsViewTest.kt +++ b/app/src/test/java/org/mozilla/fenix/exceptions/trackingprotection/TrackingProtectionExceptionsViewTest.kt @@ -2,7 +2,7 @@ * 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.trackingprotectionexceptions +package org.mozilla.fenix.exceptions.trackingprotection import android.text.Spannable import android.text.method.LinkMovementMethod @@ -29,25 +29,28 @@ import org.junit.runner.RunWith import org.mozilla.fenix.helpers.FenixRobolectricTestRunner @RunWith(FenixRobolectricTestRunner::class) -class ExceptionsViewTest { +class TrackingProtectionExceptionsViewTest { private lateinit var container: ViewGroup - private lateinit var interactor: ExceptionsInteractor - private lateinit var exceptionsView: ExceptionsView + private lateinit var interactor: TrackingProtectionExceptionsInteractor + private lateinit var exceptionsView: TrackingProtectionExceptionsView @Before fun setup() { - mockkConstructor(ExceptionsAdapter::class) + mockkConstructor(TrackingProtectionExceptionsAdapter::class) container = FrameLayout(testContext) interactor = mockk() - exceptionsView = ExceptionsView(container, interactor) - every { anyConstructed().updateData(any()) } just Runs + exceptionsView = TrackingProtectionExceptionsView( + container, + interactor + ) + every { anyConstructed().updateData(any()) } just Runs } @After fun teardown() { - unmockkConstructor(ExceptionsAdapter::class) + unmockkConstructor(TrackingProtectionExceptionsAdapter::class) } @Test @@ -63,20 +66,20 @@ class ExceptionsViewTest { @Test fun `binds empty list to adapter`() { - exceptionsView.update(ExceptionsFragmentState(emptyList())) + exceptionsView.update(emptyList()) assertTrue(exceptionsView.exceptions_empty_view.isVisible) assertFalse(exceptionsView.exceptions_list.isVisible) - verify { anyConstructed().updateData(emptyList()) } + verify { anyConstructed().updateData(emptyList()) } } @Test fun `binds list with items to adapter`() { val items = listOf(mockk(), mockk()) - exceptionsView.update(ExceptionsFragmentState(items)) + exceptionsView.update(items) assertFalse(exceptionsView.exceptions_empty_view.isVisible) assertTrue(exceptionsView.exceptions_list.isVisible) - verify { anyConstructed().updateData(items) } + verify { anyConstructed().updateData(items) } } } diff --git a/app/src/test/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsDeleteButtonViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsDeleteButtonViewHolderTest.kt similarity index 59% rename from app/src/test/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsDeleteButtonViewHolderTest.kt rename to app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsDeleteButtonViewHolderTest.kt index 35721e5b0..7996b95ba 100644 --- a/app/src/test/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsDeleteButtonViewHolderTest.kt +++ b/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsDeleteButtonViewHolderTest.kt @@ -2,12 +2,14 @@ * 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.loginexceptions.viewholders +package org.mozilla.fenix.exceptions.viewholders import android.view.View import com.google.android.material.button.MaterialButton +import io.mockk.MockKAnnotations import io.mockk.Runs import io.mockk.every +import io.mockk.impl.annotations.MockK import io.mockk.just import io.mockk.mockk import io.mockk.slot @@ -15,28 +17,25 @@ import io.mockk.verify import org.junit.Before import org.junit.Test import org.mozilla.fenix.R -import org.mozilla.fenix.loginexceptions.LoginExceptionsInteractor +import org.mozilla.fenix.exceptions.ExceptionsInteractor -class LoginExceptionsDeleteButtonViewHolderTest { +class ExceptionsDeleteButtonViewHolderTest { - private lateinit var view: View - private lateinit var deleteButton: MaterialButton - private lateinit var interactor: LoginExceptionsInteractor + @MockK private lateinit var view: View + @MockK private lateinit var deleteButton: MaterialButton + @MockK private lateinit var interactor: ExceptionsInteractor @Before fun setup() { - deleteButton = mockk() - view = mockk { - every { findViewById(R.id.removeAllExceptions) } returns deleteButton - } - interactor = mockk() + MockKAnnotations.init(this) + every { view.findViewById(R.id.removeAllExceptions) } returns deleteButton } @Test fun `delete button calls interactor`() { val slot = slot() every { deleteButton.setOnClickListener(capture(slot)) } just Runs - LoginExceptionsDeleteButtonViewHolder(view, interactor) + ExceptionsDeleteButtonViewHolder(view, interactor) every { interactor.onDeleteAll() } just Runs slot.captured.onClick(mockk()) diff --git a/app/src/test/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsHeaderViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsHeaderViewHolderTest.kt similarity index 84% rename from app/src/test/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsHeaderViewHolderTest.kt rename to app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsHeaderViewHolderTest.kt index 08121d22e..85e4d8415 100644 --- a/app/src/test/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsHeaderViewHolderTest.kt +++ b/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsHeaderViewHolderTest.kt @@ -2,7 +2,7 @@ * 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.loginexceptions.viewholders +package org.mozilla.fenix.exceptions.viewholders import android.view.View import android.widget.TextView @@ -13,7 +13,7 @@ import org.junit.Before import org.junit.Test import org.mozilla.fenix.R -class LoginExceptionsHeaderViewHolderTest { +class ExceptionsHeaderViewHolderTest { private lateinit var view: View private lateinit var description: TextView @@ -31,7 +31,7 @@ class LoginExceptionsHeaderViewHolderTest { @Test fun `sets description text`() { - LoginExceptionsHeaderViewHolder(view) + ExceptionsHeaderViewHolder(view, R.string.preferences_passwords_exceptions_description) verify { description.text = "Logins and passwords will not be saved for these sites." } } } diff --git a/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsListItemViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsListItemViewHolderTest.kt new file mode 100644 index 000000000..e0cc1ed94 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsListItemViewHolderTest.kt @@ -0,0 +1,74 @@ +/* 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.exceptions.viewholders + +import android.view.View +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.TextView +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.mockk +import io.mockk.slot +import io.mockk.verify +import mozilla.components.browser.icons.BrowserIcons +import mozilla.components.browser.icons.IconRequest +import org.junit.Before +import org.junit.Test +import org.mozilla.fenix.R +import org.mozilla.fenix.exceptions.ExceptionsInteractor + +class ExceptionsListItemViewHolderTest { + + @MockK private lateinit var view: View + @MockK(relaxUnitFun = true) private lateinit var url: TextView + @MockK(relaxUnitFun = true) private lateinit var deleteButton: ImageButton + @MockK private lateinit var favicon: ImageView + @MockK private lateinit var icons: BrowserIcons + @MockK private lateinit var interactor: ExceptionsInteractor + + @Before + fun setup() { + MockKAnnotations.init(this) + + every { view.findViewById(R.id.webAddressView) } returns url + every { view.findViewById(R.id.delete_exception) } returns deleteButton + every { view.findViewById(R.id.favicon_image) } returns favicon + every { icons.loadIntoView(favicon, any()) } returns mockk() + } + + @Test + fun `sets url text and loads favicon - mozilla`() { + ExceptionsListItemViewHolder(view, interactor, icons) + .bind(Exception(), url = "mozilla.org") + verify { url.text = "mozilla.org" } + verify { icons.loadIntoView(favicon, IconRequest("mozilla.org")) } + } + + @Test + fun `sets url text and loads favicon - example`() { + ExceptionsListItemViewHolder(view, interactor, icons) + .bind(Exception(), url = "https://example.com/icon.svg") + verify { url.text = "https://example.com/icon.svg" } + verify { icons.loadIntoView(favicon, IconRequest("https://example.com/icon.svg")) } + } + + @Test + fun `delete button calls interactor`() { + val slot = slot() + val exception = Exception() + every { deleteButton.setOnClickListener(capture(slot)) } just Runs + ExceptionsListItemViewHolder(view, interactor, icons).bind(exception, url = "mozilla.org") + + every { interactor.onDeleteOne(exception) } just Runs + slot.captured.onClick(mockk()) + verify { interactor.onDeleteOne(exception) } + } + + class Exception +} diff --git a/app/src/test/java/org/mozilla/fenix/loginexceptions/LoginExceptionsAdapterTest.kt b/app/src/test/java/org/mozilla/fenix/loginexceptions/LoginExceptionsAdapterTest.kt deleted file mode 100644 index cada0d946..000000000 --- a/app/src/test/java/org/mozilla/fenix/loginexceptions/LoginExceptionsAdapterTest.kt +++ /dev/null @@ -1,113 +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.loginexceptions - -import android.widget.LinearLayout -import androidx.appcompat.view.ContextThemeWrapper -import io.mockk.every -import io.mockk.mockk -import mozilla.components.support.test.robolectric.testContext -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.R -import org.mozilla.fenix.helpers.FenixRobolectricTestRunner -import org.mozilla.fenix.loginexceptions.viewholders.LoginExceptionsDeleteButtonViewHolder -import org.mozilla.fenix.loginexceptions.viewholders.LoginExceptionsHeaderViewHolder -import org.mozilla.fenix.loginexceptions.viewholders.LoginExceptionsListItemViewHolder - -@RunWith(FenixRobolectricTestRunner::class) -class LoginExceptionsAdapterTest { - - private lateinit var interactor: LoginExceptionsInteractor - private lateinit var adapter: LoginExceptionsAdapter - - @Before - fun setup() { - interactor = mockk() - adapter = LoginExceptionsAdapter(interactor) - } - - @Test - fun `creates correct view holder type`() { - val parent = LinearLayout(ContextThemeWrapper(testContext, R.style.NormalTheme)) - adapter.updateData(listOf(mockk(), mockk())) - assertEquals(4, adapter.itemCount) - - val holders = (0 until adapter.itemCount).asSequence() - .map { i -> adapter.getItemViewType(i) } - .map { viewType -> adapter.onCreateViewHolder(parent, viewType) } - .toList() - assertEquals(4, holders.size) - - assertTrue(holders[0] is LoginExceptionsHeaderViewHolder) - assertTrue(holders[1] is LoginExceptionsListItemViewHolder) - assertTrue(holders[2] is LoginExceptionsListItemViewHolder) - assertTrue(holders[3] is LoginExceptionsDeleteButtonViewHolder) - } - - @Test - fun `headers and delete should check if the other object is the same`() { - assertTrue(LoginExceptionsAdapter.DiffCallback.areItemsTheSame( - LoginExceptionsAdapter.AdapterItem.Header, - LoginExceptionsAdapter.AdapterItem.Header - )) - assertTrue(LoginExceptionsAdapter.DiffCallback.areItemsTheSame( - LoginExceptionsAdapter.AdapterItem.DeleteButton, - LoginExceptionsAdapter.AdapterItem.DeleteButton - )) - assertFalse(LoginExceptionsAdapter.DiffCallback.areItemsTheSame( - LoginExceptionsAdapter.AdapterItem.Header, - LoginExceptionsAdapter.AdapterItem.DeleteButton - )) - assertTrue(LoginExceptionsAdapter.DiffCallback.areContentsTheSame( - LoginExceptionsAdapter.AdapterItem.Header, - LoginExceptionsAdapter.AdapterItem.Header - )) - assertTrue(LoginExceptionsAdapter.DiffCallback.areContentsTheSame( - LoginExceptionsAdapter.AdapterItem.DeleteButton, - LoginExceptionsAdapter.AdapterItem.DeleteButton - )) - assertFalse(LoginExceptionsAdapter.DiffCallback.areContentsTheSame( - LoginExceptionsAdapter.AdapterItem.DeleteButton, - LoginExceptionsAdapter.AdapterItem.Header - )) - } - - @Test - fun `items with the same id should be marked as same`() { - assertTrue(LoginExceptionsAdapter.DiffCallback.areItemsTheSame( - LoginExceptionsAdapter.AdapterItem.Item(mockk { - every { id } returns 12L - }), - LoginExceptionsAdapter.AdapterItem.Item(mockk { - every { id } returns 12L - }) - )) - assertFalse(LoginExceptionsAdapter.DiffCallback.areItemsTheSame( - LoginExceptionsAdapter.AdapterItem.Item(mockk { - every { id } returns 14L - }), - LoginExceptionsAdapter.AdapterItem.Item(mockk { - every { id } returns 12L - }) - )) - assertFalse(LoginExceptionsAdapter.DiffCallback.areItemsTheSame( - LoginExceptionsAdapter.AdapterItem.Item(mockk { - every { id } returns 14L - }), - LoginExceptionsAdapter.AdapterItem.Header - )) - assertFalse(LoginExceptionsAdapter.DiffCallback.areItemsTheSame( - LoginExceptionsAdapter.AdapterItem.DeleteButton, - LoginExceptionsAdapter.AdapterItem.Item(mockk { - every { id } returns 14L - }) - )) - } -} diff --git a/app/src/test/java/org/mozilla/fenix/loginexceptions/LoginExceptionsInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/loginexceptions/LoginExceptionsInteractorTest.kt deleted file mode 100644 index 309f3e681..000000000 --- a/app/src/test/java/org/mozilla/fenix/loginexceptions/LoginExceptionsInteractorTest.kt +++ /dev/null @@ -1,36 +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.loginexceptions - -import io.mockk.mockk -import mozilla.components.feature.logins.exceptions.LoginException -import org.junit.Assert.assertEquals -import org.junit.Test - -class LoginExceptionsInteractorTest { - - @Test - fun onDeleteAll() { - var onDeleteAll = false - val interactor = LoginExceptionsInteractor( - mockk(), - { onDeleteAll = true } - ) - interactor.onDeleteAll() - assertEquals(true, onDeleteAll) - } - - @Test - fun onDeleteOne() { - var exceptionsItemReceived: LoginException? = null - val exceptionsItem: LoginException = mockk() - val interactor = LoginExceptionsInteractor( - { exceptionsItemReceived = exceptionsItem }, - mockk() - ) - interactor.onDeleteOne(exceptionsItem) - assertEquals(exceptionsItemReceived, exceptionsItem) - } -} diff --git a/app/src/test/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsListItemViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsListItemViewHolderTest.kt deleted file mode 100644 index 1243f665b..000000000 --- a/app/src/test/java/org/mozilla/fenix/loginexceptions/viewholders/LoginExceptionsListItemViewHolderTest.kt +++ /dev/null @@ -1,63 +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.loginexceptions.viewholders - -import android.view.View -import android.widget.ImageButton -import android.widget.ImageView -import android.widget.TextView -import io.mockk.Runs -import io.mockk.every -import io.mockk.just -import io.mockk.mockk -import io.mockk.slot -import io.mockk.verify -import mozilla.components.feature.logins.exceptions.LoginException -import org.junit.Before -import org.junit.Test -import org.mozilla.fenix.R -import org.mozilla.fenix.loginexceptions.LoginExceptionsInteractor - -class LoginExceptionsListItemViewHolderTest { - - private lateinit var view: View - private lateinit var url: TextView - private lateinit var deleteButton: ImageButton - private lateinit var interactor: LoginExceptionsInteractor - - @Before - fun setup() { - url = mockk(relaxUnitFun = true) - deleteButton = mockk(relaxUnitFun = true) - view = mockk { - every { findViewById(R.id.webAddressView) } returns url - every { findViewById(R.id.delete_exception) } returns deleteButton - every { findViewById(R.id.favicon_image) } returns mockk() - } - interactor = mockk() - } - - @Test - fun `sets url text`() { - LoginExceptionsListItemViewHolder(view, interactor).bind(mockk { - every { origin } returns "mozilla.org" - }) - verify { url.text = "mozilla.org" } - } - - @Test - fun `delete button calls interactor`() { - val slot = slot() - val loginException = mockk { - every { origin } returns "mozilla.org" - } - every { deleteButton.setOnClickListener(capture(slot)) } just Runs - LoginExceptionsListItemViewHolder(view, interactor).bind(loginException) - - every { interactor.onDeleteOne(loginException) } just Runs - slot.captured.onClick(mockk()) - verify { interactor.onDeleteOne(loginException) } - } -} diff --git a/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsAdapterTest.kt b/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsAdapterTest.kt deleted file mode 100644 index 10fff5ab3..000000000 --- a/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsAdapterTest.kt +++ /dev/null @@ -1,42 +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.trackingprotectionexceptions - -import io.mockk.mockk -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runBlockingTest -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.trackingprotectionexceptions.viewholders.ExceptionsDeleteButtonViewHolder -import org.mozilla.fenix.trackingprotectionexceptions.viewholders.ExceptionsHeaderViewHolder -import org.mozilla.fenix.trackingprotectionexceptions.viewholders.ExceptionsListItemViewHolder - -@ExperimentalCoroutinesApi -@RunWith(FenixRobolectricTestRunner::class) -class ExceptionsAdapterTest { - - private lateinit var interactor: ExceptionsInteractor - private lateinit var adapter: ExceptionsAdapter - - @Before - fun setup() { - interactor = mockk() - adapter = ExceptionsAdapter(interactor) - } - - @Test - fun `binds header and delete button with other adapter items`() = runBlockingTest { - adapter.updateData(listOf(mockk(), mockk())) - - assertEquals(4, adapter.itemCount) - assertEquals(ExceptionsHeaderViewHolder.LAYOUT_ID, adapter.getItemViewType(0)) - assertEquals(ExceptionsListItemViewHolder.LAYOUT_ID, adapter.getItemViewType(1)) - assertEquals(ExceptionsListItemViewHolder.LAYOUT_ID, adapter.getItemViewType(2)) - assertEquals(ExceptionsDeleteButtonViewHolder.LAYOUT_ID, adapter.getItemViewType(3)) - } -} diff --git a/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsInteractorTest.kt deleted file mode 100644 index 84cfa928d..000000000 --- a/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/ExceptionsInteractorTest.kt +++ /dev/null @@ -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.trackingprotectionexceptions - -import io.mockk.mockk -import mozilla.components.concept.engine.content.blocking.TrackingProtectionException -import org.junit.Assert.assertEquals -import org.junit.Test - -class ExceptionsInteractorTest { - - @Test - fun onLearnMore() { - var learnMoreClicked = false - val interactor = - ExceptionsInteractor( - { learnMoreClicked = true }, - mockk(), - mockk() - ) - interactor.onLearnMore() - assertEquals(true, learnMoreClicked) - } - - @Test - fun onDeleteAll() { - var onDeleteAll = false - val interactor = - ExceptionsInteractor( - mockk(), - mockk(), - { onDeleteAll = true } - ) - interactor.onDeleteAll() - assertEquals(true, onDeleteAll) - } - - @Test - fun onDeleteOne() { - var exceptionsItemReceived: TrackingProtectionException? = null - val exceptionsItem = - ExceptionItem("url") - val interactor = - ExceptionsInteractor( - mockk(), - { exceptionsItemReceived = exceptionsItem }, - mockk() - ) - interactor.onDeleteOne(exceptionsItem) - assertEquals(exceptionsItemReceived, exceptionsItem) - } -} diff --git a/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/TrackingProtectionExceptionsFragmentStoreTest.kt b/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/TrackingProtectionExceptionsFragmentStoreTest.kt deleted file mode 100644 index 027f37058..000000000 --- a/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/TrackingProtectionExceptionsFragmentStoreTest.kt +++ /dev/null @@ -1,35 +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.trackingprotectionexceptions - -import kotlinx.coroutines.runBlocking -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotSame -import org.junit.Test - -class TrackingProtectionExceptionsFragmentStoreTest { - @Test - fun onChange() = runBlocking { - val initialState = emptyDefaultState() - val store = - ExceptionsFragmentStore( - initialState - ) - val newExceptionsItem = - ExceptionItem("URL") - - store.dispatch(ExceptionsFragmentAction.Change(listOf(newExceptionsItem))).join() - assertNotSame(initialState, store.state) - assertEquals( - store.state.items, - listOf(newExceptionsItem) - ) - } - - private fun emptyDefaultState(): ExceptionsFragmentState = - ExceptionsFragmentState( - items = listOf() - ) -} diff --git a/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsDeleteButtonViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsDeleteButtonViewHolderTest.kt deleted file mode 100644 index 8c53e28ff..000000000 --- a/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsDeleteButtonViewHolderTest.kt +++ /dev/null @@ -1,43 +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.trackingprotectionexceptions.viewholders - -import android.view.LayoutInflater -import android.view.View -import androidx.appcompat.view.ContextThemeWrapper -import io.mockk.mockk -import io.mockk.verify -import kotlinx.android.synthetic.main.delete_exceptions_button.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.R -import org.mozilla.fenix.trackingprotectionexceptions.ExceptionsInteractor -import org.mozilla.fenix.helpers.FenixRobolectricTestRunner - -@RunWith(FenixRobolectricTestRunner::class) -class ExceptionsDeleteButtonViewHolderTest { - - private lateinit var view: View - private lateinit var interactor: ExceptionsInteractor - private lateinit var viewHolder: ExceptionsDeleteButtonViewHolder - - @Before - fun setup() { - val appCompatContext = ContextThemeWrapper(testContext, R.style.NormalTheme) - view = LayoutInflater.from(appCompatContext) - .inflate(ExceptionsDeleteButtonViewHolder.LAYOUT_ID, null) - interactor = mockk(relaxed = true) - viewHolder = ExceptionsDeleteButtonViewHolder(view, interactor) - } - - @Test - fun `calls onDeleteAll on click`() { - view.removeAllExceptions.performClick() - - verify { interactor.onDeleteAll() } - } -} diff --git a/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsListItemViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsListItemViewHolderTest.kt deleted file mode 100644 index 3efb3d907..000000000 --- a/app/src/test/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsListItemViewHolderTest.kt +++ /dev/null @@ -1,56 +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.trackingprotectionexceptions.viewholders - -import android.view.LayoutInflater -import android.view.View -import io.mockk.mockk -import io.mockk.verify -import kotlinx.android.synthetic.main.exception_item.view.* -import mozilla.components.concept.engine.content.blocking.TrackingProtectionException -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.trackingprotectionexceptions.ExceptionsInteractor -import org.mozilla.fenix.helpers.FenixRobolectricTestRunner - -@RunWith(FenixRobolectricTestRunner::class) -class ExceptionsListItemViewHolderTest { - - private lateinit var view: View - private lateinit var interactor: ExceptionsInteractor - private lateinit var viewHolder: ExceptionsListItemViewHolder - - @Before - fun setup() { - view = LayoutInflater.from(testContext) - .inflate(ExceptionsListItemViewHolder.LAYOUT_ID, null) - interactor = mockk(relaxed = true) - viewHolder = ExceptionsListItemViewHolder(view, interactor) - } - - @Test - fun `bind url and icon`() { - val exception = object : TrackingProtectionException { - override val url = "https://example.com/icon.svg" - } - viewHolder.bind(exception) - - assertEquals(exception.url, view.webAddressView.text) - } - - @Test - fun `calls onDeleteOne on click`() { - val exception = object : TrackingProtectionException { - override val url = "https://example.com/icon.svg" - } - viewHolder.bind(exception) - view.delete_exception.performClick() - - verify { interactor.onDeleteOne(exception) } - } -}