From 61b83580132917e59d02f9d57cb9c40279f7e340 Mon Sep 17 00:00:00 2001 From: NIkita Fedrunov Date: Mon, 10 Oct 2022 10:19:01 +0200 Subject: [PATCH 1/3] new app layout home screen tests --- .../app/features/home/InvitesViewModelTest.kt | 109 +++++++++++ .../features/home/RoomsListViewModelTest.kt | 184 ++++++++++++++++++ .../app/test/fakes/FakeDrawableProvider.kt | 30 +++ .../fakes/FakeHomeLayoutPreferencesStore.kt | 43 ++++ .../vector/app/test/fakes/FakeRoomService.kt | 4 + .../im/vector/app/test/fakes/FakeSession.kt | 5 + .../app/test/fakes/FakeStringProvider.kt | 4 + .../vector/app/test/fakes/FakeUserService.kt | 32 +++ 8 files changed, 411 insertions(+) create mode 100644 vector/src/test/java/im/vector/app/features/home/InvitesViewModelTest.kt create mode 100644 vector/src/test/java/im/vector/app/features/home/RoomsListViewModelTest.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeDrawableProvider.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeHomeLayoutPreferencesStore.kt create mode 100644 vector/src/test/java/im/vector/app/test/fakes/FakeUserService.kt 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..48bf232327 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/home/InvitesViewModelTest.kt @@ -0,0 +1,109 @@ +/* + * 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..e2fef25aba --- /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 arrow.core.Option +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.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(Option.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 4171307922..0360f45d85 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 @@ -32,6 +32,7 @@ import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.user.UserService import org.matrix.android.sdk.flow.FlowSession import org.matrix.android.sdk.flow.flow @@ -41,6 +42,7 @@ class FakeSession( val fakeHomeServerCapabilitiesService: FakeHomeServerCapabilitiesService = FakeHomeServerCapabilitiesService(), val fakeSharedSecretStorageService: FakeSharedSecretStorageService = FakeSharedSecretStorageService(), val fakeRoomService: FakeRoomService = FakeRoomService(), + val fakeUserService: FakeUserService = FakeUserService(), private val fakeEventService: FakeEventService = FakeEventService(), ) : Session by mockk(relaxed = true) { @@ -58,6 +60,7 @@ class FakeSession( override fun sharedSecretStorageService() = fakeSharedSecretStorageService override fun roomService() = fakeRoomService override fun eventService() = fakeEventService + override fun userService() = fakeUserService fun givenVectorStore(vectorSessionStore: VectorSessionStore) { coEvery { @@ -88,8 +91,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..e9a29fcdf5 --- /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) } + } +} From 9f8d37718182f803cc24dfffd94681e8c0df9055 Mon Sep 17 00:00:00 2001 From: NIkita Fedrunov Date: Tue, 18 Oct 2022 10:47:44 +0200 Subject: [PATCH 2/3] lint --- .../java/im/vector/app/features/home/InvitesViewModelTest.kt | 1 - .../src/test/java/im/vector/app/test/fakes/FakeUserService.kt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) 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 index 48bf232327..99f19bd99c 100644 --- a/vector/src/test/java/im/vector/app/features/home/InvitesViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/InvitesViewModelTest.kt @@ -100,7 +100,6 @@ class InvitesViewModelTest { session = fakeSession, stringProvider = fakeStringProvider.instance, drawableProvider = fakeDrawableProvider.instance, - ).also { viewModel = it initialState = state 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 index e9a29fcdf5..065796934c 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeUserService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeUserService.kt @@ -22,7 +22,7 @@ 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() { +class FakeUserService : UserService by mockk() { private val userIdSlot = slot() From 32fcdad91696884f566ed7838415bba2d391fa4a Mon Sep 17 00:00:00 2001 From: NIkita Fedrunov Date: Fri, 23 Dec 2022 13:19:35 +0100 Subject: [PATCH 3/3] fixed compilation error --- .../im/vector/app/features/home/RoomsListViewModelTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index e2fef25aba..a601505d6c 100644 --- a/vector/src/test/java/im/vector/app/features/home/RoomsListViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/RoomsListViewModelTest.kt @@ -18,7 +18,6 @@ package im.vector.app.features.home import android.widget.ImageView import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import arrow.core.Option import com.airbnb.mvrx.test.MavericksTestRule import im.vector.app.R import im.vector.app.core.platform.StateView @@ -45,6 +44,7 @@ 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 @@ -72,7 +72,7 @@ class RoomsListViewModelTest { mockkStatic("org.matrix.android.sdk.flow.FlowSessionKt") fakeFLowSession = fakeSession.givenFlowSession() - every { fakeSpaceStateHandler.getSelectedSpaceFlow() } returns flowOf(Option.empty()) + every { fakeSpaceStateHandler.getSelectedSpaceFlow() } returns flowOf(Optional.empty()) every { fakeSpaceStateHandler.getCurrentSpace() } returns null every { fakeFLowSession.liveRoomSummaries(any(), any()) } returns flowOf(emptyList())