diff --git a/vector/src/test/java/im/vector/app/features/home/InvitesViewModelTest.kt b/vector/src/test/java/im/vector/app/features/home/InvitesViewModelTest.kt new file mode 100644 index 0000000000..99f19bd99c --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/home/InvitesViewModelTest.kt @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home + +import com.airbnb.mvrx.test.MavericksTestRule +import im.vector.app.features.home.room.list.home.invites.InvitesAction +import im.vector.app.features.home.room.list.home.invites.InvitesViewEvents +import im.vector.app.features.home.room.list.home.invites.InvitesViewModel +import im.vector.app.features.home.room.list.home.invites.InvitesViewState +import im.vector.app.test.fakes.FakeDrawableProvider +import im.vector.app.test.fakes.FakeSession +import im.vector.app.test.fakes.FakeStringProvider +import im.vector.app.test.fixtures.RoomSummaryFixture +import im.vector.app.test.test +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.matrix.android.sdk.api.session.room.model.Membership + +class InvitesViewModelTest { + + @get:Rule + val mavericksTestRule = MavericksTestRule() + + private val fakeSession = FakeSession() + private val fakeStringProvider = FakeStringProvider() + private val fakeDrawableProvider = FakeDrawableProvider() + + private var initialState = InvitesViewState() + private lateinit var viewModel: InvitesViewModel + + private val anInvite = RoomSummaryFixture.aRoomSummary("invite") + + @Before + fun setUp() { + mockkStatic("org.matrix.android.sdk.flow.FlowSessionKt") + + every { + fakeSession.fakeRoomService.getPagedRoomSummariesLive( + queryParams = match { + it.memberships == listOf(Membership.INVITE) + }, + pagedListConfig = any(), + sortOrder = any() + ) + } returns mockk() + + viewModelWith(initialState) + } + + @Test + fun `when invite accepted then membership map is updated and open event posted`() = runTest { + val test = viewModel.test() + + viewModel.handle(InvitesAction.AcceptInvitation(anInvite)) + + test.assertEvents( + InvitesViewEvents.OpenRoom( + roomSummary = anInvite, + shouldCloseInviteView = false, + isInviteAlreadySelected = true + ) + ).finish() + } + + @Test + fun `when invite rejected then membership map is updated and open event posted`() = runTest { + coEvery { fakeSession.roomService().leaveRoom(any(), any()) } returns Unit + + viewModel.handle(InvitesAction.RejectInvitation(anInvite)) + + coVerify { + fakeSession.roomService().leaveRoom(anInvite.roomId) + } + } + + private fun viewModelWith(state: InvitesViewState) { + InvitesViewModel( + state, + session = fakeSession, + stringProvider = fakeStringProvider.instance, + drawableProvider = fakeDrawableProvider.instance, + ).also { + viewModel = it + initialState = state + } + } +} diff --git a/vector/src/test/java/im/vector/app/features/home/RoomsListViewModelTest.kt b/vector/src/test/java/im/vector/app/features/home/RoomsListViewModelTest.kt new file mode 100644 index 0000000000..a601505d6c --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/home/RoomsListViewModelTest.kt @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home + +import android.widget.ImageView +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import com.airbnb.mvrx.test.MavericksTestRule +import im.vector.app.R +import im.vector.app.core.platform.StateView +import im.vector.app.features.displayname.getBestName +import im.vector.app.features.home.room.list.home.HomeRoomListAction +import im.vector.app.features.home.room.list.home.HomeRoomListViewModel +import im.vector.app.features.home.room.list.home.HomeRoomListViewState +import im.vector.app.features.home.room.list.home.header.HomeRoomFilter +import im.vector.app.test.fakes.FakeAnalyticsTracker +import im.vector.app.test.fakes.FakeDrawableProvider +import im.vector.app.test.fakes.FakeHomeLayoutPreferencesStore +import im.vector.app.test.fakes.FakeSession +import im.vector.app.test.fakes.FakeSpaceStateHandler +import im.vector.app.test.fakes.FakeStringProvider +import im.vector.app.test.fixtures.RoomSummaryFixture.aRoomSummary +import im.vector.app.test.test +import io.mockk.every +import io.mockk.mockkStatic +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.matrix.android.sdk.api.query.SpaceFilter +import org.matrix.android.sdk.api.session.getUserOrDefault +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.api.util.toMatrixItem +import org.matrix.android.sdk.flow.FlowSession + +class RoomsListViewModelTest { + + @get:Rule + val mavericksTestRule = MavericksTestRule() + + @get:Rule + var rule = InstantTaskExecutorRule() + + private val fakeSession = FakeSession() + private val fakeAnalyticsTracker = FakeAnalyticsTracker() + private val fakeStringProvider = FakeStringProvider() + private val fakeDrawableProvider = FakeDrawableProvider() + private val fakeSpaceStateHandler = FakeSpaceStateHandler() + private val fakeHomeLayoutPreferencesStore = FakeHomeLayoutPreferencesStore() + + private var initialState = HomeRoomListViewState() + private lateinit var viewModel: HomeRoomListViewModel + private lateinit var fakeFLowSession: FlowSession + + @Before + fun setUp() { + mockkStatic("org.matrix.android.sdk.flow.FlowSessionKt") + fakeFLowSession = fakeSession.givenFlowSession() + + every { fakeSpaceStateHandler.getSelectedSpaceFlow() } returns flowOf(Optional.empty()) + every { fakeSpaceStateHandler.getCurrentSpace() } returns null + every { fakeFLowSession.liveRoomSummaries(any(), any()) } returns flowOf(emptyList()) + + val roomA = aRoomSummary("room_a") + val roomB = aRoomSummary("room_b") + val roomC = aRoomSummary("room_c") + val allRooms = listOf(roomA, roomB, roomC) + + every { + fakeFLowSession.liveRoomSummaries( + match { + it.roomCategoryFilter == null && + it.roomTagQueryFilter == null && + it.memberships == listOf(Membership.JOIN) && + it.spaceFilter is SpaceFilter.NoFilter + }, any() + ) + } returns flowOf(allRooms) + + viewModelWith(initialState) + } + + @Test + fun `when recents are enabled then updates state`() = runTest { + val fakeFLowSession = fakeSession.givenFlowSession() + every { fakeFLowSession.liveRoomSummaries(any()) } returns flowOf(emptyList()) + val test = viewModel.test() + + val roomA = aRoomSummary("room_a") + val roomB = aRoomSummary("room_b") + val roomC = aRoomSummary("room_c") + val recentRooms = listOf(roomA, roomB, roomC) + + every { fakeFLowSession.liveBreadcrumbs(any()) } returns flowOf(recentRooms) + fakeHomeLayoutPreferencesStore.givenRecentsEnabled(true) + + val userName = fakeSession.getUserOrDefault(fakeSession.myUserId).toMatrixItem().getBestName() + val allEmptyState = StateView.State.Empty( + title = fakeStringProvider.instance.getString(R.string.home_empty_no_rooms_title, userName), + message = fakeStringProvider.instance.getString(R.string.home_empty_no_rooms_message), + image = fakeDrawableProvider.instance.getDrawable(R.drawable.ill_empty_all_chats), + isBigImage = true + ) + + test.assertLatestState( + initialState.copy(emptyState = allEmptyState, headersData = initialState.headersData.copy(recents = recentRooms)) + ) + } + + @Test + fun `when filter tabs are enabled then updates state`() = runTest { + val test = viewModel.test() + + fakeHomeLayoutPreferencesStore.givenFiltersEnabled(true) + + val filtersData = mutableListOf( + HomeRoomFilter.ALL, + HomeRoomFilter.UNREADS + ) + + val userName = fakeSession.getUserOrDefault(fakeSession.myUserId).toMatrixItem().getBestName() + val allEmptyState = StateView.State.Empty( + title = fakeStringProvider.instance.getString(R.string.home_empty_no_rooms_title, userName), + message = fakeStringProvider.instance.getString(R.string.home_empty_no_rooms_message), + image = fakeDrawableProvider.instance.getDrawable(R.drawable.ill_empty_all_chats), + isBigImage = true + ) + + test.assertLatestState( + initialState.copy(emptyState = allEmptyState, headersData = initialState.headersData.copy(filtersList = filtersData)) + ) + } + + @Test + fun `when filter tab is selected then updates state`() = runTest { + val test = viewModel.test() + + val aFilter = HomeRoomFilter.UNREADS + viewModel.handle(HomeRoomListAction.ChangeRoomFilter(filter = aFilter)) + + val unreadsEmptyState = StateView.State.Empty( + title = fakeStringProvider.instance.getString(R.string.home_empty_no_unreads_title), + message = fakeStringProvider.instance.getString(R.string.home_empty_no_unreads_message), + image = fakeDrawableProvider.instance.getDrawable(R.drawable.ill_empty_unreads), + isBigImage = true, + imageScaleType = ImageView.ScaleType.CENTER_INSIDE + ) + + test.assertLatestState( + initialState.copy(emptyState = unreadsEmptyState, headersData = initialState.headersData.copy(currentFilter = aFilter)) + ) + } + + private fun viewModelWith(state: HomeRoomListViewState) { + HomeRoomListViewModel( + state, + session = fakeSession, + spaceStateHandler = fakeSpaceStateHandler, + preferencesStore = fakeHomeLayoutPreferencesStore.instance, + stringProvider = fakeStringProvider.instance, + drawableProvider = fakeDrawableProvider.instance, + analyticsTracker = fakeAnalyticsTracker + + ).also { + viewModel = it + initialState = state + } + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeDrawableProvider.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeDrawableProvider.kt new file mode 100644 index 0000000000..26fa7af3f5 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeDrawableProvider.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fakes + +import im.vector.app.core.resources.DrawableProvider +import io.mockk.every +import io.mockk.mockk + +class FakeDrawableProvider { + val instance = mockk() + + init { + every { instance.getDrawable(any()) } returns mockk() + every { instance.getDrawable(any(), any()) } returns mockk() + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeHomeLayoutPreferencesStore.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeHomeLayoutPreferencesStore.kt new file mode 100644 index 0000000000..bd5dd20d37 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeHomeLayoutPreferencesStore.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fakes + +import im.vector.app.features.home.room.list.home.HomeLayoutPreferencesStore +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.flow.MutableSharedFlow + +class FakeHomeLayoutPreferencesStore { + + private val _areRecentsEnabledFlow = MutableSharedFlow() + private val _areFiltersEnabledFlow = MutableSharedFlow() + private val _isAZOrderingEnabledFlow = MutableSharedFlow() + + val instance = mockk(relaxed = true) { + every { areRecentsEnabledFlow } returns _areRecentsEnabledFlow + every { areFiltersEnabledFlow } returns _areFiltersEnabledFlow + every { isAZOrderingEnabledFlow } returns _isAZOrderingEnabledFlow + } + + suspend fun givenRecentsEnabled(enabled: Boolean) { + _areRecentsEnabledFlow.emit(enabled) + } + + suspend fun givenFiltersEnabled(enabled: Boolean) { + _areFiltersEnabledFlow.emit(enabled) + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt index 506e96ba11..e957266383 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt @@ -30,4 +30,8 @@ class FakeRoomService( fun getRoomSummaryReturns(roomSummary: RoomSummary?) { every { getRoomSummary(any()) } returns roomSummary } + + fun set(roomSummary: RoomSummary?) { + every { getRoomSummary(any()) } returns roomSummary + } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt index e368fbbcf2..a05dce9c54 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt @@ -42,6 +42,7 @@ class FakeSession( val fakeSharedSecretStorageService: FakeSharedSecretStorageService = FakeSharedSecretStorageService(), val fakeRoomService: FakeRoomService = FakeRoomService(), val fakePushersService: FakePushersService = FakePushersService(), + val fakeUserService: FakeUserService = FakeUserService(), private val fakeEventService: FakeEventService = FakeEventService(), val fakeSessionAccountDataService: FakeSessionAccountDataService = FakeSessionAccountDataService() ) : Session by mockk(relaxed = true) { @@ -62,6 +63,7 @@ class FakeSession( override fun eventService() = fakeEventService override fun pushersService() = fakePushersService override fun accountDataService() = fakeSessionAccountDataService + override fun userService() = fakeUserService fun givenVectorStore(vectorSessionStore: VectorSessionStore) { coEvery { @@ -92,8 +94,10 @@ class FakeSession( /** * Do not forget to call mockkStatic("org.matrix.android.sdk.flow.FlowSessionKt") in the setup method of the tests. */ + @SuppressWarnings("all") fun givenFlowSession(): FlowSession { val fakeFlowSession = mockk() + every { flow() } returns fakeFlowSession return fakeFlowSession } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt index 28d9f7c732..83f8607261 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt @@ -17,6 +17,7 @@ package im.vector.app.test.fakes import im.vector.app.core.resources.StringProvider +import io.mockk.InternalPlatformDsl.toStr import io.mockk.every import io.mockk.mockk @@ -27,6 +28,9 @@ class FakeStringProvider { every { instance.getString(any()) } answers { "test-${args[0]}" } + every { instance.getString(any(), any()) } answers { + "test-${args[0]}-${args[1].toStr()}" + } every { instance.getQuantityString(any(), any(), any()) } answers { "test-${args[0]}-${args[1]}" diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeUserService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeUserService.kt new file mode 100644 index 0000000000..065796934c --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeUserService.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fakes + +import io.mockk.every +import io.mockk.mockk +import io.mockk.slot +import org.matrix.android.sdk.api.session.user.UserService +import org.matrix.android.sdk.api.session.user.model.User + +class FakeUserService : UserService by mockk() { + + private val userIdSlot = slot() + + init { + every { getUser(capture(userIdSlot)) } answers { User(userId = userIdSlot.captured) } + } +}