diff --git a/domains/state/src/testFixtures/kotlin/test/ReducerTest.kt b/domains/state/src/testFixtures/kotlin/test/ReducerTest.kt index ebdd7b7..b50846b 100644 --- a/domains/state/src/testFixtures/kotlin/test/ReducerTest.kt +++ b/domains/state/src/testFixtures/kotlin/test/ReducerTest.kt @@ -39,6 +39,8 @@ class ReducerTestScope( private val expectTestScope: ExpectTestScope ) : ExpectTestScope by expectTestScope, Reducer { + private var invalidateCapturedState: Boolean = false + private val actionSideEffects = mutableMapOf S>() private var manualState: S? = null private var capturedResult: S? = null @@ -47,6 +49,10 @@ class ReducerTestScope( override val coroutineScope = CoroutineScope(UnconfinedTestDispatcher()) override fun dispatch(action: Action) { actionCaptures.add(action) + + if (actionSideEffects.containsKey(action)) { + setState(actionSideEffects.getValue(action).invoke(), invalidateCapturedState = true) + } } override fun getState() = manualState ?: reducerFactory.initialState() @@ -54,15 +60,20 @@ class ReducerTestScope( private val reducer: Reducer = reducerFactory.create(reducerScope) override fun reduce(action: Action) = reducer.reduce(action).also { - capturedResult = it + capturedResult = if (invalidateCapturedState) manualState else it } - fun setState(state: S) { + fun actionSideEffect(action: Action, handler: () -> S) { + actionSideEffects[action] = handler + } + + fun setState(state: S, invalidateCapturedState: Boolean = false) { manualState = state + this.invalidateCapturedState = invalidateCapturedState } fun setState(block: (S) -> S) { - manualState = block(reducerScope.getState()) + setState(block(reducerScope.getState())) } fun assertInitialState(expected: S) { diff --git a/features/messenger/src/main/kotlin/app/dapk/st/messenger/gallery/state/ImageGalleryReducer.kt b/features/messenger/src/main/kotlin/app/dapk/st/messenger/gallery/state/ImageGalleryReducer.kt index fd37f1b..138851f 100644 --- a/features/messenger/src/main/kotlin/app/dapk/st/messenger/gallery/state/ImageGalleryReducer.kt +++ b/features/messenger/src/main/kotlin/app/dapk/st/messenger/gallery/state/ImageGalleryReducer.kt @@ -46,7 +46,6 @@ fun imageGalleryReducer( parent = ImageGalleryPage.Routes.folders, state = ImageGalleryPage.Files(Lce.Loading(), action.folder) ) - dispatch(PageAction.GoTo(page)) jobBag.replace(ImageGalleryPage.Files::class, coroutineScope.launch { diff --git a/features/messenger/src/test/kotlin/app/dapk/st/messenger/gallery/state/ImageGalleryReducerTest.kt b/features/messenger/src/test/kotlin/app/dapk/st/messenger/gallery/state/ImageGalleryReducerTest.kt index f5fc127..49fbe18 100644 --- a/features/messenger/src/test/kotlin/app/dapk/st/messenger/gallery/state/ImageGalleryReducerTest.kt +++ b/features/messenger/src/test/kotlin/app/dapk/st/messenger/gallery/state/ImageGalleryReducerTest.kt @@ -1,54 +1,123 @@ package app.dapk.st.messenger.gallery.state +import android.net.Uri import app.dapk.st.core.Lce +import app.dapk.st.core.page.PageAction +import app.dapk.st.core.page.PageContainer +import app.dapk.st.core.page.PageStateChange import app.dapk.st.design.components.SpiderPage import app.dapk.st.messenger.gallery.FetchMediaFoldersUseCase import app.dapk.st.messenger.gallery.FetchMediaUseCase -import app.dapk.st.core.page.PageContainer +import app.dapk.st.messenger.gallery.Folder +import app.dapk.st.messenger.gallery.Media import app.dapk.state.Combined2 import fake.FakeJobBag +import fake.FakeUri +import io.mockk.coEvery import io.mockk.mockk import org.junit.Test +import test.assertOnlyDispatches +import test.delegateReturn +import test.expect import test.testReducer private const val A_ROOM_NAME = "a room name" +private val A_FOLDER = Folder( + bucketId = "a-bucket-id", + title = "a title", + thumbnail = FakeUri().instance, +) +private val A_MEDIA_RESULT = listOf(aMedia()) +private val A_FOLDERS_RESULT = listOf(aFolder()) +private val AN_INITIAL_FILES_PAGE = SpiderPage( + route = ImageGalleryPage.Routes.files, + label = "Send to $A_ROOM_NAME", + parent = ImageGalleryPage.Routes.folders, + state = ImageGalleryPage.Files(Lce.Loading(), A_FOLDER) +) + +private val AN_INITIAL_FOLDERS_PAGE = SpiderPage( + route = ImageGalleryPage.Routes.folders, + label = "Send to $A_ROOM_NAME", + parent = null, + state = ImageGalleryPage.Folders(Lce.Loading()) +) class ImageGalleryReducerTest { private val fakeJobBag = FakeJobBag() + private val fakeFetchMediaFoldersUseCase = FakeFetchMediaFoldersUseCase() + private val fakeFetchMediaUseCase = FakeFetchMediaUseCase() private val runReducerTest = testReducer { _: (Unit) -> Unit -> imageGalleryReducer( A_ROOM_NAME, - FakeFetchMediaFoldersUseCase().instance, - FakeFetchMediaUseCase().instance, + fakeFetchMediaFoldersUseCase.instance, + fakeFetchMediaUseCase.instance, fakeJobBag.instance, ) } @Test fun `initial state is folders page`() = runReducerTest { - assertInitialState( - Combined2( - state1 = PageContainer( - SpiderPage( - route = ImageGalleryPage.Routes.folders, - label = "Send to $A_ROOM_NAME", - parent = null, - state = ImageGalleryPage.Folders(Lce.Loading()) - ) - ), - state2 = Unit - ) + assertInitialState(pageState(AN_INITIAL_FOLDERS_PAGE)) + } + + @Test + fun `when Visible, then updates Folders content`() = runReducerTest { + fakeJobBag.instance.expect { it.replace(ImageGalleryPage.Folders::class, any()) } + fakeFetchMediaFoldersUseCase.givenFolders().returns(A_FOLDERS_RESULT) + + reduce(ImageGalleryActions.Visible) + + assertOnlyDispatches( + PageStateChange.UpdatePage(AN_INITIAL_FOLDERS_PAGE.state.copy(content = Lce.Content(A_FOLDERS_RESULT))) + ) + } + + @Test + fun `when SelectFolder, then goes to Folder page and fetches content`() = runReducerTest { + fakeJobBag.instance.expect { it.replace(ImageGalleryPage.Files::class, any()) } + fakeFetchMediaUseCase.givenMedia(A_FOLDER.bucketId).returns(A_MEDIA_RESULT) + val goToFolderPage = PageAction.GoTo(AN_INITIAL_FILES_PAGE) + actionSideEffect(goToFolderPage) { pageState(goToFolderPage.page) } + + reduce(ImageGalleryActions.SelectFolder(A_FOLDER)) + + assertOnlyDispatches( + goToFolderPage, + PageStateChange.UpdatePage(goToFolderPage.page.state.copy(content = Lce.Content(A_MEDIA_RESULT))) ) } } +private fun

pageState(page: SpiderPage) = Combined2(PageContainer(page), Unit) + class FakeFetchMediaFoldersUseCase { val instance = mockk() + + fun givenFolders() = coEvery { instance.fetchFolders() }.delegateReturn() } class FakeFetchMediaUseCase { val instance = mockk() -} \ No newline at end of file + + fun givenMedia(bucketId: String) = coEvery { instance.getMediaInBucket(bucketId) }.delegateReturn() +} + +fun aMedia( + id: Long = 1L, + uri: Uri = FakeUri().instance, + mimeType: String = "image/png", + width: Int = 100, + height: Int = 250, + size: Long = 1000L, + dateModifiedEpochMillis: Long = 5000L, +) = Media(id, uri, mimeType, width, height, size, dateModifiedEpochMillis) + +fun aFolder( + bucketId: String = "a-bucket-id", + title: String = "a title", + thumbnail: Uri = FakeUri().instance, +) = Folder(bucketId, title, thumbnail) \ No newline at end of file