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