For #4127 - Converts Exceptions to LibState and adds tests
parent
9b5baa2358
commit
be10d427e8
|
@ -258,6 +258,12 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback
|
|||
customTabSessionId
|
||||
)
|
||||
}
|
||||
BrowserDirection.FromExceptions -> {
|
||||
fragmentId = R.id.exceptionsFragment
|
||||
ExceptionsFragmentDirections.actionExceptionsFragmentToBrowserFragment(
|
||||
customTabSessionId
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
|
|
|
@ -7,7 +7,6 @@ package org.mozilla.fenix.exceptions
|
|||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.reactivex.Observer
|
||||
import org.mozilla.fenix.exceptions.viewholders.ExceptionsDeleteButtonViewHolder
|
||||
import org.mozilla.fenix.exceptions.viewholders.ExceptionsHeaderViewHolder
|
||||
import org.mozilla.fenix.exceptions.viewholders.ExceptionsListItemViewHolder
|
||||
|
@ -21,6 +20,7 @@ private sealed class AdapterItem {
|
|||
|
||||
private class ExceptionsList(val exceptions: List<ExceptionsItem>) {
|
||||
val items: List<AdapterItem>
|
||||
|
||||
init {
|
||||
val items = mutableListOf<AdapterItem>()
|
||||
items.add(AdapterItem.Header)
|
||||
|
@ -33,7 +33,7 @@ private class ExceptionsList(val exceptions: List<ExceptionsItem>) {
|
|||
}
|
||||
|
||||
class ExceptionsAdapter(
|
||||
private val actionEmitter: Observer<ExceptionsAction>
|
||||
private val interactor: ExceptionsInteractor
|
||||
) : AdapterWithJob<RecyclerView.ViewHolder>() {
|
||||
private var exceptionsList: ExceptionsList = ExceptionsList(emptyList())
|
||||
|
||||
|
@ -56,9 +56,16 @@ class ExceptionsAdapter(
|
|||
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
||||
|
||||
return when (viewType) {
|
||||
ExceptionsDeleteButtonViewHolder.LAYOUT_ID -> ExceptionsDeleteButtonViewHolder(view, actionEmitter)
|
||||
ExceptionsDeleteButtonViewHolder.LAYOUT_ID -> ExceptionsDeleteButtonViewHolder(
|
||||
view,
|
||||
interactor
|
||||
)
|
||||
ExceptionsHeaderViewHolder.LAYOUT_ID -> ExceptionsHeaderViewHolder(view)
|
||||
ExceptionsListItemViewHolder.LAYOUT_ID -> ExceptionsListItemViewHolder(view, actionEmitter, adapterJob)
|
||||
ExceptionsListItemViewHolder.LAYOUT_ID -> ExceptionsListItemViewHolder(
|
||||
view,
|
||||
interactor,
|
||||
adapterJob
|
||||
)
|
||||
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.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.whenStarted
|
||||
import androidx.navigation.Navigation
|
||||
import kotlinx.android.synthetic.main.fragment_exceptions.view.*
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.lib.state.ext.observe
|
||||
import org.mozilla.fenix.BrowserDirection
|
||||
import org.mozilla.fenix.FenixViewModelProvider
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.mvi.ActionBusFactory
|
||||
import org.mozilla.fenix.mvi.getAutoDisposeObservable
|
||||
import org.mozilla.fenix.mvi.getManagedEmitter
|
||||
import org.mozilla.fenix.components.StoreProvider
|
||||
import org.mozilla.fenix.settings.SupportUtils
|
||||
|
||||
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() {
|
||||
super.onResume()
|
||||
|
@ -41,45 +42,54 @@ class ExceptionsFragment : Fragment() {
|
|||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(R.layout.fragment_exceptions, container, false)
|
||||
exceptionsComponent = ExceptionsComponent(
|
||||
view.exceptions_layout,
|
||||
ActionBusFactory.get(this),
|
||||
FenixViewModelProvider.create(
|
||||
this,
|
||||
ExceptionsViewModel::class.java
|
||||
) {
|
||||
ExceptionsViewModel(ExceptionsState(loadAndMapExceptions()))
|
||||
}
|
||||
exceptionsStore = StoreProvider.get(this) {
|
||||
ExceptionsStore(
|
||||
ExceptionsState(
|
||||
items = loadAndMapExceptions()
|
||||
)
|
||||
)
|
||||
}
|
||||
exceptionsInteractor =
|
||||
ExceptionsInteractor(::openLearnMore, ::deleteOneItem, ::deleteAllItems)
|
||||
exceptionsView = ExceptionsView(view.exceptions_layout, exceptionsInteractor)
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
getAutoDisposeObservable<ExceptionsAction>()
|
||||
.subscribe {
|
||||
when (it) {
|
||||
is ExceptionsAction.LearnMore -> {
|
||||
(activity as HomeActivity).openToBrowserAndLoad(
|
||||
searchTermOrURL = SupportUtils.getGenericSumoURLForTopic
|
||||
(SupportUtils.SumoTopic.TRACKING_PROTECTION),
|
||||
newTab = true,
|
||||
from = BrowserDirection.FromExceptions
|
||||
)
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
exceptionsStore.observe(view) {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
whenStarted {
|
||||
exceptionsView.update(it)
|
||||
}
|
||||
is ExceptionsAction.Delete.All -> viewLifecycleOwner.lifecycleScope.launch(IO) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteAllItems() {
|
||||
viewLifecycleOwner.lifecycleScope.launch(IO) {
|
||||
val domains = ExceptionDomains.load(context!!)
|
||||
ExceptionDomains.remove(context!!, domains)
|
||||
launch(Main) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun openLearnMore() {
|
||||
(activity as HomeActivity).openToBrowserAndLoad(
|
||||
searchTermOrURL = SupportUtils.getGenericSumoURLForTopic
|
||||
(SupportUtils.SumoTopic.TRACKING_PROTECTION),
|
||||
newTab = true,
|
||||
from = BrowserDirection.FromExceptions
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadAndMapExceptions(): List<ExceptionsItem> {
|
||||
|
@ -100,7 +110,7 @@ class ExceptionsFragment : Fragment() {
|
|||
view?.let { view: View -> Navigation.findNavController(view).navigateUp() }
|
||||
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.widget.FrameLayout
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Observer
|
||||
import io.reactivex.functions.Consumer
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
import kotlinx.android.synthetic.main.component_exceptions.view.*
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.mvi.UIView
|
||||
|
||||
class ExceptionsUIView(
|
||||
container: ViewGroup,
|
||||
actionEmitter: Observer<ExceptionsAction>,
|
||||
changesObservable: Observable<ExceptionsChange>
|
||||
) :
|
||||
UIView<ExceptionsState, ExceptionsAction, ExceptionsChange>(
|
||||
container,
|
||||
actionEmitter,
|
||||
changesObservable
|
||||
) {
|
||||
/**
|
||||
* 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()
|
||||
|
||||
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)
|
||||
.findViewById(R.id.exceptions_wrapper)
|
||||
|
||||
override val containerView: View?
|
||||
get() = container
|
||||
|
||||
init {
|
||||
view.exceptions_list.apply {
|
||||
adapter = ExceptionsAdapter(actionEmitter)
|
||||
adapter = ExceptionsAdapter(interactor)
|
||||
layoutManager = LinearLayoutManager(container.context)
|
||||
}
|
||||
val descriptionText = String
|
||||
|
@ -48,7 +66,7 @@ class ExceptionsUIView(
|
|||
val linkStartIndex = descriptionText.indexOf("\n\n") + 2
|
||||
val linkAction = object : ClickableSpan() {
|
||||
override fun onClick(widget: View?) {
|
||||
actionEmitter.onNext(ExceptionsAction.LearnMore)
|
||||
interactor.onLearnMore()
|
||||
}
|
||||
}
|
||||
val textWithLink = SpannableString(descriptionText).apply {
|
||||
|
@ -61,9 +79,10 @@ class ExceptionsUIView(
|
|||
view.exceptions_empty_view.text = textWithLink
|
||||
}
|
||||
|
||||
override fun updateView() = Consumer<ExceptionsState> {
|
||||
view.exceptions_empty_view.visibility = if (it.items.isEmpty()) View.VISIBLE else View.GONE
|
||||
view.exceptions_list.visibility = if (it.items.isEmpty()) View.GONE else View.VISIBLE
|
||||
(view.exceptions_list.adapter as ExceptionsAdapter).updateData(it.items)
|
||||
fun update(state: ExceptionsState) {
|
||||
view.exceptions_empty_view.visibility =
|
||||
if (state.items.isEmpty()) View.VISIBLE else View.GONE
|
||||
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 androidx.recyclerview.widget.RecyclerView
|
||||
import io.reactivex.Observer
|
||||
import kotlinx.android.synthetic.main.delete_exceptions_button.view.*
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.exceptions.ExceptionsAction
|
||||
import org.mozilla.fenix.exceptions.ExceptionsInteractor
|
||||
|
||||
class ExceptionsDeleteButtonViewHolder(
|
||||
view: View,
|
||||
private val actionEmitter: Observer<ExceptionsAction>
|
||||
private val interactor: ExceptionsInteractor
|
||||
) : RecyclerView.ViewHolder(view) {
|
||||
private val deleteButton = view.removeAllExceptions
|
||||
|
||||
init {
|
||||
deleteButton.setOnClickListener {
|
||||
actionEmitter.onNext(ExceptionsAction.Delete.All)
|
||||
interactor.onDeleteAll()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ package org.mozilla.fenix.exceptions.viewholders
|
|||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import io.reactivex.Observer
|
||||
import kotlinx.android.synthetic.main.exception_item.view.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -14,14 +13,14 @@ import kotlinx.coroutines.Job
|
|||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.browser.icons.IconRequest
|
||||
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.ext.components
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class ExceptionsListItemViewHolder(
|
||||
view: View,
|
||||
private val actionEmitter: Observer<ExceptionsAction>,
|
||||
private val interactor: ExceptionsInteractor,
|
||||
val job: Job
|
||||
) : RecyclerView.ViewHolder(view), CoroutineScope {
|
||||
|
||||
|
@ -37,7 +36,7 @@ class ExceptionsListItemViewHolder(
|
|||
init {
|
||||
deleteButton.setOnClickListener {
|
||||
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