For #4127 - Converts Exceptions to LibState and adds tests
parent
9b5baa2358
commit
be10d427e8
|
@ -258,6 +258,12 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback
|
||||||
customTabSessionId
|
customTabSessionId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
BrowserDirection.FromExceptions -> {
|
||||||
|
fragmentId = R.id.exceptionsFragment
|
||||||
|
ExceptionsFragmentDirections.actionExceptionsFragmentToBrowserFragment(
|
||||||
|
customTabSessionId
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
|
|
@ -7,7 +7,6 @@ package org.mozilla.fenix.exceptions
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import io.reactivex.Observer
|
|
||||||
import org.mozilla.fenix.exceptions.viewholders.ExceptionsDeleteButtonViewHolder
|
import org.mozilla.fenix.exceptions.viewholders.ExceptionsDeleteButtonViewHolder
|
||||||
import org.mozilla.fenix.exceptions.viewholders.ExceptionsHeaderViewHolder
|
import org.mozilla.fenix.exceptions.viewholders.ExceptionsHeaderViewHolder
|
||||||
import org.mozilla.fenix.exceptions.viewholders.ExceptionsListItemViewHolder
|
import org.mozilla.fenix.exceptions.viewholders.ExceptionsListItemViewHolder
|
||||||
|
@ -21,6 +20,7 @@ private sealed class AdapterItem {
|
||||||
|
|
||||||
private class ExceptionsList(val exceptions: List<ExceptionsItem>) {
|
private class ExceptionsList(val exceptions: List<ExceptionsItem>) {
|
||||||
val items: List<AdapterItem>
|
val items: List<AdapterItem>
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val items = mutableListOf<AdapterItem>()
|
val items = mutableListOf<AdapterItem>()
|
||||||
items.add(AdapterItem.Header)
|
items.add(AdapterItem.Header)
|
||||||
|
@ -33,7 +33,7 @@ private class ExceptionsList(val exceptions: List<ExceptionsItem>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExceptionsAdapter(
|
class ExceptionsAdapter(
|
||||||
private val actionEmitter: Observer<ExceptionsAction>
|
private val interactor: ExceptionsInteractor
|
||||||
) : AdapterWithJob<RecyclerView.ViewHolder>() {
|
) : AdapterWithJob<RecyclerView.ViewHolder>() {
|
||||||
private var exceptionsList: ExceptionsList = ExceptionsList(emptyList())
|
private var exceptionsList: ExceptionsList = ExceptionsList(emptyList())
|
||||||
|
|
||||||
|
@ -56,9 +56,16 @@ class ExceptionsAdapter(
|
||||||
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
||||||
|
|
||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
ExceptionsDeleteButtonViewHolder.LAYOUT_ID -> ExceptionsDeleteButtonViewHolder(view, actionEmitter)
|
ExceptionsDeleteButtonViewHolder.LAYOUT_ID -> ExceptionsDeleteButtonViewHolder(
|
||||||
|
view,
|
||||||
|
interactor
|
||||||
|
)
|
||||||
ExceptionsHeaderViewHolder.LAYOUT_ID -> ExceptionsHeaderViewHolder(view)
|
ExceptionsHeaderViewHolder.LAYOUT_ID -> ExceptionsHeaderViewHolder(view)
|
||||||
ExceptionsListItemViewHolder.LAYOUT_ID -> ExceptionsListItemViewHolder(view, actionEmitter, adapterJob)
|
ExceptionsListItemViewHolder.LAYOUT_ID -> ExceptionsListItemViewHolder(
|
||||||
|
view,
|
||||||
|
interactor,
|
||||||
|
adapterJob
|
||||||
|
)
|
||||||
else -> throw IllegalStateException()
|
else -> throw IllegalStateException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.exceptions
|
|
||||||
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import org.mozilla.fenix.mvi.Action
|
|
||||||
import org.mozilla.fenix.mvi.ActionBusFactory
|
|
||||||
import org.mozilla.fenix.mvi.Change
|
|
||||||
import org.mozilla.fenix.mvi.UIComponent
|
|
||||||
import org.mozilla.fenix.mvi.UIComponentViewModelBase
|
|
||||||
import org.mozilla.fenix.mvi.UIComponentViewModelProvider
|
|
||||||
import org.mozilla.fenix.mvi.ViewState
|
|
||||||
import org.mozilla.fenix.test.Mockable
|
|
||||||
|
|
||||||
data class ExceptionsItem(val url: String)
|
|
||||||
|
|
||||||
@Mockable
|
|
||||||
class ExceptionsComponent(
|
|
||||||
private val container: ViewGroup,
|
|
||||||
bus: ActionBusFactory,
|
|
||||||
viewModelProvider: UIComponentViewModelProvider<ExceptionsState, ExceptionsChange>
|
|
||||||
) :
|
|
||||||
UIComponent<ExceptionsState, ExceptionsAction, ExceptionsChange>(
|
|
||||||
bus.getManagedEmitter(ExceptionsAction::class.java),
|
|
||||||
bus.getSafeManagedObservable(ExceptionsChange::class.java),
|
|
||||||
viewModelProvider
|
|
||||||
) {
|
|
||||||
|
|
||||||
override fun initView() = ExceptionsUIView(container, actionEmitter, changesObservable)
|
|
||||||
|
|
||||||
init {
|
|
||||||
bind()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class ExceptionsState(val items: List<ExceptionsItem>) : ViewState
|
|
||||||
|
|
||||||
sealed class ExceptionsAction : Action {
|
|
||||||
object LearnMore : ExceptionsAction()
|
|
||||||
sealed class Delete : ExceptionsAction() {
|
|
||||||
object All : Delete()
|
|
||||||
data class One(val item: ExceptionsItem) : Delete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class ExceptionsChange : Change {
|
|
||||||
data class Change(val list: List<ExceptionsItem>) : ExceptionsChange()
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExceptionsViewModel(
|
|
||||||
initialState: ExceptionsState
|
|
||||||
) : UIComponentViewModelBase<ExceptionsState, ExceptionsChange>(initialState, reducer) {
|
|
||||||
companion object {
|
|
||||||
val reducer: (ExceptionsState, ExceptionsChange) -> ExceptionsState = { state, change ->
|
|
||||||
when (change) {
|
|
||||||
is ExceptionsChange.Change -> state.copy(items = change.list)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,23 +11,24 @@ import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.whenStarted
|
||||||
import androidx.navigation.Navigation
|
import androidx.navigation.Navigation
|
||||||
import kotlinx.android.synthetic.main.fragment_exceptions.view.*
|
import kotlinx.android.synthetic.main.fragment_exceptions.view.*
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import mozilla.components.lib.state.ext.observe
|
||||||
import org.mozilla.fenix.BrowserDirection
|
import org.mozilla.fenix.BrowserDirection
|
||||||
import org.mozilla.fenix.FenixViewModelProvider
|
|
||||||
import org.mozilla.fenix.HomeActivity
|
import org.mozilla.fenix.HomeActivity
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.mvi.ActionBusFactory
|
import org.mozilla.fenix.components.StoreProvider
|
||||||
import org.mozilla.fenix.mvi.getAutoDisposeObservable
|
|
||||||
import org.mozilla.fenix.mvi.getManagedEmitter
|
|
||||||
import org.mozilla.fenix.settings.SupportUtils
|
import org.mozilla.fenix.settings.SupportUtils
|
||||||
|
|
||||||
class ExceptionsFragment : Fragment() {
|
class ExceptionsFragment : Fragment() {
|
||||||
private lateinit var exceptionsComponent: ExceptionsComponent
|
private lateinit var exceptionsStore: ExceptionsStore
|
||||||
|
private lateinit var exceptionsView: ExceptionsView
|
||||||
|
private lateinit var exceptionsInteractor: ExceptionsInteractor
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
@ -41,45 +42,54 @@ class ExceptionsFragment : Fragment() {
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
val view = inflater.inflate(R.layout.fragment_exceptions, container, false)
|
val view = inflater.inflate(R.layout.fragment_exceptions, container, false)
|
||||||
exceptionsComponent = ExceptionsComponent(
|
exceptionsStore = StoreProvider.get(this) {
|
||||||
view.exceptions_layout,
|
ExceptionsStore(
|
||||||
ActionBusFactory.get(this),
|
ExceptionsState(
|
||||||
FenixViewModelProvider.create(
|
items = loadAndMapExceptions()
|
||||||
this,
|
|
||||||
ExceptionsViewModel::class.java
|
|
||||||
) {
|
|
||||||
ExceptionsViewModel(ExceptionsState(loadAndMapExceptions()))
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
exceptionsInteractor =
|
||||||
|
ExceptionsInteractor(::openLearnMore, ::deleteOneItem, ::deleteAllItems)
|
||||||
|
exceptionsView = ExceptionsView(view.exceptions_layout, exceptionsInteractor)
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onStart()
|
super.onViewCreated(view, savedInstanceState)
|
||||||
getAutoDisposeObservable<ExceptionsAction>()
|
exceptionsStore.observe(view) {
|
||||||
.subscribe {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
when (it) {
|
whenStarted {
|
||||||
is ExceptionsAction.LearnMore -> {
|
exceptionsView.update(it)
|
||||||
(activity as HomeActivity).openToBrowserAndLoad(
|
|
||||||
searchTermOrURL = SupportUtils.getGenericSumoURLForTopic
|
|
||||||
(SupportUtils.SumoTopic.TRACKING_PROTECTION),
|
|
||||||
newTab = true,
|
|
||||||
from = BrowserDirection.FromExceptions
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
is ExceptionsAction.Delete.All -> viewLifecycleOwner.lifecycleScope.launch(IO) {
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deleteAllItems() {
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch(IO) {
|
||||||
val domains = ExceptionDomains.load(context!!)
|
val domains = ExceptionDomains.load(context!!)
|
||||||
ExceptionDomains.remove(context!!, domains)
|
ExceptionDomains.remove(context!!, domains)
|
||||||
launch(Main) {
|
launch(Main) {
|
||||||
view?.let { view -> Navigation.findNavController(view).navigateUp() }
|
view?.let { view -> Navigation.findNavController(view).navigateUp() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is ExceptionsAction.Delete.One -> viewLifecycleOwner.lifecycleScope.launch(IO) {
|
}
|
||||||
ExceptionDomains.remove(context!!, listOf(it.item.url))
|
|
||||||
|
private fun deleteOneItem(item: ExceptionsItem) {
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch(IO) {
|
||||||
|
ExceptionDomains.remove(context!!, listOf(item.url))
|
||||||
reloadData()
|
reloadData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private fun openLearnMore() {
|
||||||
|
(activity as HomeActivity).openToBrowserAndLoad(
|
||||||
|
searchTermOrURL = SupportUtils.getGenericSumoURLForTopic
|
||||||
|
(SupportUtils.SumoTopic.TRACKING_PROTECTION),
|
||||||
|
newTab = true,
|
||||||
|
from = BrowserDirection.FromExceptions
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadAndMapExceptions(): List<ExceptionsItem> {
|
private fun loadAndMapExceptions(): List<ExceptionsItem> {
|
||||||
|
@ -100,7 +110,7 @@ class ExceptionsFragment : Fragment() {
|
||||||
view?.let { view: View -> Navigation.findNavController(view).navigateUp() }
|
view?.let { view: View -> Navigation.findNavController(view).navigateUp() }
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
getManagedEmitter<ExceptionsChange>().onNext(ExceptionsChange.Change(items))
|
exceptionsStore.dispatch(ExceptionsAction.Change(items))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
/* 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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interactor for the exceptions screen
|
||||||
|
* Provides implementations for the ExceptionsViewInteractor
|
||||||
|
*/
|
||||||
|
class ExceptionsInteractor(
|
||||||
|
private val learnMore: () -> Unit,
|
||||||
|
private val deleteOne: (ExceptionsItem) -> Unit,
|
||||||
|
private val deleteAll: () -> Unit
|
||||||
|
) : ExceptionsViewInteractor {
|
||||||
|
override fun onLearnMore() {
|
||||||
|
learnMore.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDeleteAll() {
|
||||||
|
deleteAll.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDeleteOne(item: ExceptionsItem) {
|
||||||
|
deleteOne.invoke(item)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/* 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 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 ExceptionsItem(val url: String)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [Store] for holding the [ExceptionsState] and applying [ExceptionsAction]s.
|
||||||
|
*/
|
||||||
|
class ExceptionsStore(initialState: ExceptionsState) :
|
||||||
|
Store<ExceptionsState, ExceptionsAction>(initialState, ::exceptionsStateReducer)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions to dispatch through the `ExceptionsStore` to modify `ExceptionsState` through the reducer.
|
||||||
|
*/
|
||||||
|
sealed class ExceptionsAction : Action {
|
||||||
|
data class Change(val list: List<ExceptionsItem>) : ExceptionsAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The state for the Exceptions Screen
|
||||||
|
* @property items List of exceptions to display
|
||||||
|
*/
|
||||||
|
data class ExceptionsState(val items: List<ExceptionsItem>) : State
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ExceptionsState Reducer.
|
||||||
|
*/
|
||||||
|
fun exceptionsStateReducer(state: ExceptionsState, action: ExceptionsAction): ExceptionsState {
|
||||||
|
return when (action) {
|
||||||
|
is ExceptionsAction.Change -> state.copy(items = action.list)
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,31 +13,49 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import io.reactivex.Observable
|
import kotlinx.android.extensions.LayoutContainer
|
||||||
import io.reactivex.Observer
|
|
||||||
import io.reactivex.functions.Consumer
|
|
||||||
import kotlinx.android.synthetic.main.component_exceptions.view.*
|
import kotlinx.android.synthetic.main.component_exceptions.view.*
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.mvi.UIView
|
|
||||||
|
|
||||||
class ExceptionsUIView(
|
/**
|
||||||
container: ViewGroup,
|
* Interface for the ExceptionsViewInteractor. This interface is implemented by objects that want
|
||||||
actionEmitter: Observer<ExceptionsAction>,
|
* to respond to user interaction on the ExceptionsView
|
||||||
changesObservable: Observable<ExceptionsChange>
|
*/
|
||||||
) :
|
interface ExceptionsViewInteractor {
|
||||||
UIView<ExceptionsState, ExceptionsAction, ExceptionsChange>(
|
/**
|
||||||
container,
|
* Called whenever learn more about tracking protection is tapped
|
||||||
actionEmitter,
|
*/
|
||||||
changesObservable
|
fun onLearnMore()
|
||||||
) {
|
|
||||||
|
|
||||||
override val view: FrameLayout = LayoutInflater.from(container.context)
|
/**
|
||||||
|
* Called whenever all exception items are deleted
|
||||||
|
*/
|
||||||
|
fun onDeleteAll()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called whenever one exception item is deleted
|
||||||
|
*/
|
||||||
|
fun onDeleteOne(item: ExceptionsItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View that contains and configures the Exceptions List
|
||||||
|
*/
|
||||||
|
class ExceptionsView(
|
||||||
|
private val container: ViewGroup,
|
||||||
|
val interactor: ExceptionsInteractor
|
||||||
|
) : LayoutContainer {
|
||||||
|
|
||||||
|
val view: FrameLayout = LayoutInflater.from(container.context)
|
||||||
.inflate(R.layout.component_exceptions, container, true)
|
.inflate(R.layout.component_exceptions, container, true)
|
||||||
.findViewById(R.id.exceptions_wrapper)
|
.findViewById(R.id.exceptions_wrapper)
|
||||||
|
|
||||||
|
override val containerView: View?
|
||||||
|
get() = container
|
||||||
|
|
||||||
init {
|
init {
|
||||||
view.exceptions_list.apply {
|
view.exceptions_list.apply {
|
||||||
adapter = ExceptionsAdapter(actionEmitter)
|
adapter = ExceptionsAdapter(interactor)
|
||||||
layoutManager = LinearLayoutManager(container.context)
|
layoutManager = LinearLayoutManager(container.context)
|
||||||
}
|
}
|
||||||
val descriptionText = String
|
val descriptionText = String
|
||||||
|
@ -48,7 +66,7 @@ class ExceptionsUIView(
|
||||||
val linkStartIndex = descriptionText.indexOf("\n\n") + 2
|
val linkStartIndex = descriptionText.indexOf("\n\n") + 2
|
||||||
val linkAction = object : ClickableSpan() {
|
val linkAction = object : ClickableSpan() {
|
||||||
override fun onClick(widget: View?) {
|
override fun onClick(widget: View?) {
|
||||||
actionEmitter.onNext(ExceptionsAction.LearnMore)
|
interactor.onLearnMore()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val textWithLink = SpannableString(descriptionText).apply {
|
val textWithLink = SpannableString(descriptionText).apply {
|
||||||
|
@ -61,9 +79,10 @@ class ExceptionsUIView(
|
||||||
view.exceptions_empty_view.text = textWithLink
|
view.exceptions_empty_view.text = textWithLink
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateView() = Consumer<ExceptionsState> {
|
fun update(state: ExceptionsState) {
|
||||||
view.exceptions_empty_view.visibility = if (it.items.isEmpty()) View.VISIBLE else View.GONE
|
view.exceptions_empty_view.visibility =
|
||||||
view.exceptions_list.visibility = if (it.items.isEmpty()) View.GONE else View.VISIBLE
|
if (state.items.isEmpty()) View.VISIBLE else View.GONE
|
||||||
(view.exceptions_list.adapter as ExceptionsAdapter).updateData(it.items)
|
view.exceptions_list.visibility = if (state.items.isEmpty()) View.GONE else View.VISIBLE
|
||||||
|
(view.exceptions_list.adapter as ExceptionsAdapter).updateData(state.items)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,20 +6,19 @@ package org.mozilla.fenix.exceptions.viewholders
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import io.reactivex.Observer
|
|
||||||
import kotlinx.android.synthetic.main.delete_exceptions_button.view.*
|
import kotlinx.android.synthetic.main.delete_exceptions_button.view.*
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.exceptions.ExceptionsAction
|
import org.mozilla.fenix.exceptions.ExceptionsInteractor
|
||||||
|
|
||||||
class ExceptionsDeleteButtonViewHolder(
|
class ExceptionsDeleteButtonViewHolder(
|
||||||
view: View,
|
view: View,
|
||||||
private val actionEmitter: Observer<ExceptionsAction>
|
private val interactor: ExceptionsInteractor
|
||||||
) : RecyclerView.ViewHolder(view) {
|
) : RecyclerView.ViewHolder(view) {
|
||||||
private val deleteButton = view.removeAllExceptions
|
private val deleteButton = view.removeAllExceptions
|
||||||
|
|
||||||
init {
|
init {
|
||||||
deleteButton.setOnClickListener {
|
deleteButton.setOnClickListener {
|
||||||
actionEmitter.onNext(ExceptionsAction.Delete.All)
|
interactor.onDeleteAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ package org.mozilla.fenix.exceptions.viewholders
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import io.reactivex.Observer
|
|
||||||
import kotlinx.android.synthetic.main.exception_item.view.*
|
import kotlinx.android.synthetic.main.exception_item.view.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -14,14 +13,14 @@ import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import mozilla.components.browser.icons.IconRequest
|
import mozilla.components.browser.icons.IconRequest
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.exceptions.ExceptionsAction
|
import org.mozilla.fenix.exceptions.ExceptionsInteractor
|
||||||
import org.mozilla.fenix.exceptions.ExceptionsItem
|
import org.mozilla.fenix.exceptions.ExceptionsItem
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
class ExceptionsListItemViewHolder(
|
class ExceptionsListItemViewHolder(
|
||||||
view: View,
|
view: View,
|
||||||
private val actionEmitter: Observer<ExceptionsAction>,
|
private val interactor: ExceptionsInteractor,
|
||||||
val job: Job
|
val job: Job
|
||||||
) : RecyclerView.ViewHolder(view), CoroutineScope {
|
) : RecyclerView.ViewHolder(view), CoroutineScope {
|
||||||
|
|
||||||
|
@ -37,7 +36,7 @@ class ExceptionsListItemViewHolder(
|
||||||
init {
|
init {
|
||||||
deleteButton.setOnClickListener {
|
deleteButton.setOnClickListener {
|
||||||
item?.let {
|
item?.let {
|
||||||
actionEmitter.onNext(ExceptionsAction.Delete.One(it))
|
interactor.onDeleteOne(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
/* 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 io.mockk.mockk
|
||||||
|
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: ExceptionsItem? = null
|
||||||
|
val exceptionsItem = ExceptionsItem("url")
|
||||||
|
val interactor = ExceptionsInteractor(
|
||||||
|
mockk(),
|
||||||
|
{ exceptionsItemReceived = exceptionsItem },
|
||||||
|
mockk()
|
||||||
|
)
|
||||||
|
interactor.onDeleteOne(exceptionsItem)
|
||||||
|
assertEquals(exceptionsItemReceived, exceptionsItem)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/* 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 kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNotSame
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class ExceptionsStoreTest {
|
||||||
|
@Test
|
||||||
|
fun onChange() = runBlocking {
|
||||||
|
val initialState = emptyDefaultState()
|
||||||
|
val store = ExceptionsStore(initialState)
|
||||||
|
val newExceptionsItem = ExceptionsItem("URL")
|
||||||
|
|
||||||
|
store.dispatch(ExceptionsAction.Change(listOf(newExceptionsItem))).join()
|
||||||
|
assertNotSame(initialState, store.state)
|
||||||
|
assertEquals(
|
||||||
|
store.state.items,
|
||||||
|
listOf(newExceptionsItem)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun emptyDefaultState(): ExceptionsState = ExceptionsState(
|
||||||
|
items = listOf()
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in New Issue