more tests around the directory reducer and helpers
This commit is contained in:
parent
6c3006142b
commit
86f640d301
|
@ -27,6 +27,7 @@ inline fun <reified S, E> ComponentActivity.state(
|
|||
noinline factory: () -> StateViewModel<S, E>
|
||||
): Lazy<State<S, E>> {
|
||||
val factoryPromise = object : Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return when(modelClass) {
|
||||
StateViewModel::class.java -> factory() as T
|
||||
|
@ -34,7 +35,7 @@ inline fun <reified S, E> ComponentActivity.state(
|
|||
}
|
||||
}
|
||||
}
|
||||
return FooViewModelLazy(
|
||||
return KeyedViewModelLazy(
|
||||
key = S::class.java.canonicalName!!,
|
||||
StateViewModel::class,
|
||||
{ viewModelStore },
|
||||
|
@ -42,12 +43,11 @@ inline fun <reified S, E> ComponentActivity.state(
|
|||
) as Lazy<State<S, E>>
|
||||
}
|
||||
|
||||
class FooViewModelLazy<VM : ViewModel> @JvmOverloads constructor(
|
||||
class KeyedViewModelLazy<VM : ViewModel> @JvmOverloads constructor(
|
||||
private val key: String,
|
||||
private val viewModelClass: KClass<VM>,
|
||||
private val storeProducer: () -> ViewModelStore,
|
||||
private val factoryProducer: () -> ViewModelProvider.Factory,
|
||||
private val extrasProducer: () -> CreationExtras = { CreationExtras.Empty }
|
||||
) : Lazy<VM> {
|
||||
private var cached: VM? = null
|
||||
|
||||
|
@ -60,7 +60,7 @@ class FooViewModelLazy<VM : ViewModel> @JvmOverloads constructor(
|
|||
ViewModelProvider(
|
||||
store,
|
||||
factory,
|
||||
extrasProducer()
|
||||
CreationExtras.Empty
|
||||
).get(key, viewModelClass.java).also {
|
||||
cached = it
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package app.dapk.st.directory
|
|||
import app.dapk.st.directory.state.*
|
||||
import app.dapk.st.engine.DirectoryItem
|
||||
import app.dapk.st.engine.UnreadCount
|
||||
import app.dapk.state.ReducerFactory
|
||||
import fake.FakeChatEngine
|
||||
import fixture.aRoomOverview
|
||||
import io.mockk.mockk
|
||||
|
@ -18,61 +19,71 @@ class DirectoryReducerTest {
|
|||
private val fakeShortcutHandler = FakeShortcutHandler()
|
||||
private val fakeChatEngine = FakeChatEngine()
|
||||
private val fakeJobBag = FakeJobBag()
|
||||
private val fakeEventSource = FakeEventSource<DirectoryEvent>()
|
||||
|
||||
private val reducer = directoryReducer(
|
||||
fakeChatEngine,
|
||||
fakeShortcutHandler.instance,
|
||||
fakeJobBag.instance,
|
||||
fakeEventSource,
|
||||
)
|
||||
private val runReducerTest = testReducer { fakeEventSource ->
|
||||
directoryReducer(
|
||||
fakeChatEngine,
|
||||
fakeShortcutHandler.instance,
|
||||
fakeJobBag.instance,
|
||||
fakeEventSource,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial state is empty loading`() = runReducerTest(reducer) {
|
||||
fun `initial state is empty loading`() = runReducerTest {
|
||||
assertInitialState(DirectoryScreenState.EmptyLoading)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given directory content, when Visible, then updates shortcuts and dispatches room state`() = runReducerTest(reducer) {
|
||||
fun `given directory content, when Visible, then updates shortcuts and dispatches room state`() = runReducerTest {
|
||||
fakeShortcutHandler.instance.expectUnit { it.onDirectoryUpdate(listOf(AN_OVERVIEW)) }
|
||||
fakeJobBag.instance.expect { it.add("sync", any()) }
|
||||
fakeChatEngine.givenDirectory().returns(flowOf(listOf(AN_OVERVIEW_STATE)))
|
||||
|
||||
reduce(ComponentLifecycle.OnVisible)
|
||||
|
||||
assertNoStateChange()
|
||||
assertDispatches(listOf(DirectoryStateChange.Content(listOf(AN_OVERVIEW_STATE))))
|
||||
assertOnlyDispatches(listOf(DirectoryStateChange.Content(listOf(AN_OVERVIEW_STATE))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given no directory content, when Visible, then updates shortcuts and dispatches empty state`() = runReducerTest(reducer) {
|
||||
fun `given no directory content, when Visible, then updates shortcuts and dispatches empty state`() = runReducerTest {
|
||||
fakeShortcutHandler.instance.expectUnit { it.onDirectoryUpdate(emptyList()) }
|
||||
fakeJobBag.instance.expect { it.add("sync", any()) }
|
||||
fakeChatEngine.givenDirectory().returns(flowOf(emptyList()))
|
||||
|
||||
reduce(ComponentLifecycle.OnVisible)
|
||||
|
||||
assertNoStateChange()
|
||||
assertDispatches(listOf(DirectoryStateChange.Empty))
|
||||
assertOnlyDispatches(listOf(DirectoryStateChange.Empty))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when Gone, then cancels sync job`() = runReducerTest(reducer) {
|
||||
fun `when Gone, then cancels sync job`() = runReducerTest {
|
||||
fakeJobBag.instance.expect { it.cancel("sync") }
|
||||
|
||||
reduce(ComponentLifecycle.OnGone)
|
||||
|
||||
assertNoStateChange()
|
||||
assertNoDispatches()
|
||||
assertNoChanges()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given ScrollToTop, then emits Scroll event`() = runReducerTest(reducer) {
|
||||
fun `when ScrollToTop, then emits Scroll event`() = runReducerTest {
|
||||
reduce(DirectorySideEffect.ScrollToTop)
|
||||
|
||||
assertNoStateChange()
|
||||
assertNoDispatches()
|
||||
fakeEventSource.assertEvents(listOf(DirectoryEvent.ScrollToTop))
|
||||
assertOnlyEvents(listOf(DirectoryEvent.ScrollToTop))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when Content StateChange, then returns Content state`() = runReducerTest {
|
||||
reduce(DirectoryStateChange.Content(listOf(AN_OVERVIEW_STATE)))
|
||||
|
||||
assertOnlyStateChange(DirectoryScreenState.Content(listOf(AN_OVERVIEW_STATE)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when Empty StateChange, then returns Empty state`() = runReducerTest {
|
||||
reduce(DirectoryStateChange.Empty)
|
||||
|
||||
assertOnlyStateChange(DirectoryScreenState.Empty)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,3 +94,4 @@ internal class FakeShortcutHandler {
|
|||
class FakeJobBag {
|
||||
val instance = mockk<JobBag>()
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import org.amshove.kluent.internal.assertEquals
|
|||
|
||||
class FakeEventSource<E> : (E) -> Unit {
|
||||
|
||||
val captures = mutableListOf<E>()
|
||||
private val captures = mutableListOf<E>()
|
||||
|
||||
override fun invoke(event: E) {
|
||||
captures.add(event)
|
||||
|
@ -13,4 +13,8 @@ class FakeEventSource<E> : (E) -> Unit {
|
|||
fun assertEvents(expected: List<E>) {
|
||||
assertEquals(expected, captures)
|
||||
}
|
||||
|
||||
fun assertNoEvents() {
|
||||
assertEquals(emptyList(), captures)
|
||||
}
|
||||
}
|
|
@ -12,16 +12,31 @@ import org.amshove.kluent.shouldBeEqualTo
|
|||
import test.ExpectTest
|
||||
import test.ExpectTestScope
|
||||
|
||||
fun <S> runReducerTest(reducerFactory: ReducerFactory<S>, block: suspend ReducerTestScope<S>.() -> Unit) {
|
||||
interface ReducerTest<S, E> {
|
||||
operator fun invoke(block: suspend ReducerTestScope<S, E>.() -> Unit)
|
||||
}
|
||||
|
||||
fun <S, E> testReducer(block: ((E) -> Unit) -> ReducerFactory<S>): ReducerTest<S, E> {
|
||||
val fakeEventSource = FakeEventSource<E>()
|
||||
val reducerFactory = block(fakeEventSource)
|
||||
return object : ReducerTest<S, E> {
|
||||
override fun invoke(block: suspend ReducerTestScope<S, E>.() -> Unit) {
|
||||
runReducerTest(reducerFactory, fakeEventSource, block)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <S, E> runReducerTest(reducerFactory: ReducerFactory<S>, fakeEventSource: FakeEventSource<E>, block: suspend ReducerTestScope<S, E>.() -> Unit) {
|
||||
runTest {
|
||||
val expectTestScope = ExpectTest(coroutineContext)
|
||||
block(ReducerTestScope(reducerFactory, expectTestScope))
|
||||
block(ReducerTestScope(reducerFactory, fakeEventSource, expectTestScope))
|
||||
expectTestScope.verifyExpects()
|
||||
}
|
||||
}
|
||||
|
||||
class ReducerTestScope<S>(
|
||||
class ReducerTestScope<S, E>(
|
||||
private val reducerFactory: ReducerFactory<S>,
|
||||
private val fakeEventSource: FakeEventSource<E>,
|
||||
private val expectTestScope: ExpectTestScope
|
||||
) : ExpectTestScope by expectTestScope, Reducer<S> {
|
||||
|
||||
|
@ -51,7 +66,17 @@ class ReducerTestScope<S>(
|
|||
reducerFactory.initialState() shouldBeEqualTo expected
|
||||
}
|
||||
|
||||
fun <S> assertDispatches(expected: List<S>) {
|
||||
fun assertOnlyStateChange(expected: S) {
|
||||
assertStateChange(expected)
|
||||
assertNoDispatches()
|
||||
fakeEventSource.assertNoEvents()
|
||||
}
|
||||
|
||||
fun assertStateChange(expected: S) {
|
||||
capturedResult shouldBeEqualTo expected
|
||||
}
|
||||
|
||||
fun assertDispatches(expected: List<Action>) {
|
||||
assertEquals(expected, actionCaptures)
|
||||
}
|
||||
|
||||
|
@ -62,4 +87,22 @@ class ReducerTestScope<S>(
|
|||
fun assertNoStateChange() {
|
||||
assertEquals(reducerFactory.initialState(), capturedResult)
|
||||
}
|
||||
|
||||
fun assertOnlyDispatches(expected: List<Action>) {
|
||||
assertDispatches(expected)
|
||||
fakeEventSource.assertNoEvents()
|
||||
assertNoStateChange()
|
||||
}
|
||||
|
||||
fun assertOnlyEvents(events: List<E>) {
|
||||
fakeEventSource.assertEvents(events)
|
||||
assertNoDispatches()
|
||||
assertNoStateChange()
|
||||
}
|
||||
|
||||
fun assertNoChanges() {
|
||||
assertNoStateChange()
|
||||
fakeEventSource.assertNoEvents()
|
||||
assertNoDispatches()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue