change(members): update tests to match new ui and logic
This commit is contained in:
parent
8fd34051e0
commit
bf45ed3f1f
3 changed files with 198 additions and 314 deletions
|
|
@ -25,7 +25,6 @@ import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.architecture.map
|
import io.element.android.libraries.architecture.map
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||||
|
|
@ -59,9 +58,6 @@ class RoomMemberListPresenter(
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): RoomMemberListState {
|
override fun present(): RoomMemberListState {
|
||||||
var searchQuery by rememberSaveable { mutableStateOf("") }
|
var searchQuery by rememberSaveable { mutableStateOf("") }
|
||||||
var searchResults by remember {
|
|
||||||
mutableStateOf<SearchBarResultState<AsyncData<RoomMembers>>>(SearchBarResultState.Initial())
|
|
||||||
}
|
|
||||||
val membersState by room.membersStateFlow.collectAsState()
|
val membersState by room.membersStateFlow.collectAsState()
|
||||||
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
||||||
val canInvite by room.canInviteAsState(syncUpdateFlow.value)
|
val canInvite by room.canInviteAsState(syncUpdateFlow.value)
|
||||||
|
|
@ -75,8 +71,8 @@ class RoomMemberListPresenter(
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
var roomMembers: AsyncData<RoomMembers> by remember { mutableStateOf(AsyncData.Loading()) }
|
|
||||||
var selectedSection by remember { mutableStateOf(SelectedSection.MEMBERS) }
|
var selectedSection by remember { mutableStateOf(SelectedSection.MEMBERS) }
|
||||||
|
var roomMembers: AsyncData<RoomMembers> by remember { mutableStateOf(AsyncData.Loading()) }
|
||||||
var filteredRoomMembers: AsyncData<RoomMembers> by remember { mutableStateOf(AsyncData.Loading()) }
|
var filteredRoomMembers: AsyncData<RoomMembers> by remember { mutableStateOf(AsyncData.Loading()) }
|
||||||
|
|
||||||
// Update the room members when the screen is loaded
|
// Update the room members when the screen is loaded
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
|
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
|
||||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
|
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.architecture.map
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
|
|
@ -21,108 +21,59 @@ import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMemberListState> {
|
internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMemberListState> {
|
||||||
override val values: Sequence<RoomMemberListState>
|
override val values: Sequence<RoomMemberListState>
|
||||||
get() = roomMemberListStates() + bannedRoomMemberListStates()
|
get() = sequenceOf(
|
||||||
|
aRoomMemberListState(
|
||||||
|
roomMembers = AsyncData.Loading(),
|
||||||
|
selectedSection = SelectedSection.MEMBERS,
|
||||||
|
),
|
||||||
|
aRoomMemberListState(
|
||||||
|
roomMembers = AsyncData.Failure(Exception("Error details")),
|
||||||
|
selectedSection = SelectedSection.MEMBERS,
|
||||||
|
),
|
||||||
|
aRoomMemberListState(
|
||||||
|
roomMembers = aLoadedRoomMembers(),
|
||||||
|
selectedSection = SelectedSection.MEMBERS,
|
||||||
|
),
|
||||||
|
aRoomMemberListState(
|
||||||
|
roomMembers = aLoadedRoomMembers(),
|
||||||
|
selectedSection = SelectedSection.BANNED,
|
||||||
|
moderationState = aRoomMemberModerationState(canBan = true),
|
||||||
|
),
|
||||||
|
aRoomMemberListState(
|
||||||
|
roomMembers = aLoadedRoomMembers(),
|
||||||
|
canInvite = true,
|
||||||
|
selectedSection = SelectedSection.MEMBERS,
|
||||||
|
),
|
||||||
|
aRoomMemberListState(
|
||||||
|
roomMembers = aLoadedRoomMembers(),
|
||||||
|
searchQuery = "alice",
|
||||||
|
selectedSection = SelectedSection.MEMBERS,
|
||||||
|
),
|
||||||
|
aRoomMemberListState(
|
||||||
|
roomMembers = aLoadedRoomMembers(),
|
||||||
|
searchQuery = "something-with-no-results",
|
||||||
|
selectedSection = SelectedSection.MEMBERS,
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun roomMemberListStates(): Sequence<RoomMemberListState> = sequenceOf(
|
private fun aLoadedRoomMembers() = AsyncData.Success(
|
||||||
aRoomMemberListState(
|
RoomMembers(
|
||||||
roomMembers = AsyncData.Success(
|
invited = persistentListOf(
|
||||||
RoomMembers(
|
anInvitedVictor().withIdentity(),
|
||||||
invited = persistentListOf(aVictor().withIdentity(), aWalter().withIdentity()),
|
anInvitedWalter().withIdentity(),
|
||||||
joined = persistentListOf(anAlice().withIdentity(), aBob().withIdentity(), aWalter().withIdentity()),
|
|
||||||
banned = persistentListOf(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
selectedSection = SelectedSection.MEMBERS,
|
joined = persistentListOf(
|
||||||
),
|
anAlice().withIdentity(identityState = IdentityState.Verified),
|
||||||
aRoomMemberListState(
|
aBob().withIdentity(identityState = IdentityState.PinViolation),
|
||||||
roomMembers = AsyncData.Success(
|
aCarol().withIdentity(),
|
||||||
RoomMembers(
|
aDavid().withIdentity(),
|
||||||
invited = persistentListOf(aVictor().withIdentity(), aWalter().withIdentity()),
|
anEve().withIdentity(identityState = IdentityState.VerificationViolation)
|
||||||
joined = persistentListOf(
|
|
||||||
anAlice().withIdentity(identityState = IdentityState.Verified),
|
|
||||||
aBob().withIdentity(identityState = IdentityState.PinViolation),
|
|
||||||
aWalter().withIdentity(identityState = IdentityState.VerificationViolation)
|
|
||||||
),
|
|
||||||
banned = persistentListOf(),
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
selectedSection = SelectedSection.MEMBERS,
|
banned = persistentListOf(
|
||||||
moderationState = aRoomMemberModerationState(canBan = true)
|
aBannedMallory().withIdentity(),
|
||||||
),
|
aBannedSusie().withIdentity()
|
||||||
aRoomMemberListState(
|
|
||||||
roomMembers = AsyncData.Loading(),
|
|
||||||
selectedSection = SelectedSection.MEMBERS,
|
|
||||||
),
|
|
||||||
aRoomMemberListState().copy(
|
|
||||||
canInvite = true,
|
|
||||||
selectedSection = SelectedSection.MEMBERS,
|
|
||||||
),
|
|
||||||
aRoomMemberListState().copy(
|
|
||||||
selectedSection = SelectedSection.MEMBERS,
|
|
||||||
),
|
|
||||||
aRoomMemberListState().copy(
|
|
||||||
selectedSection = SelectedSection.MEMBERS,
|
|
||||||
),
|
|
||||||
aRoomMemberListState().copy(
|
|
||||||
searchQuery = "someone",
|
|
||||||
selectedSection = SelectedSection.MEMBERS,
|
|
||||||
),
|
|
||||||
aRoomMemberListState().copy(
|
|
||||||
searchQuery = "@someone:matrix.org",
|
|
||||||
selectedSection = SelectedSection.MEMBERS,
|
|
||||||
),
|
|
||||||
aRoomMemberListState().copy(
|
|
||||||
searchQuery = "something-with-no-results",
|
|
||||||
selectedSection = SelectedSection.MEMBERS,
|
|
||||||
),
|
|
||||||
aRoomMemberListState(
|
|
||||||
roomMembers = AsyncData.Failure(Exception("Error details")),
|
|
||||||
selectedSection = SelectedSection.MEMBERS,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun bannedRoomMemberListStates(): Sequence<RoomMemberListState> = sequenceOf(
|
|
||||||
aRoomMemberListState(
|
|
||||||
roomMembers = AsyncData.Success(
|
|
||||||
RoomMembers(
|
|
||||||
invited = persistentListOf(),
|
|
||||||
joined = persistentListOf(),
|
|
||||||
banned = persistentListOf(
|
|
||||||
aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice").withIdentity(),
|
|
||||||
aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob").withIdentity(),
|
|
||||||
aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie").withIdentity(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
moderationState = aRoomMemberModerationState(),
|
|
||||||
selectedSection = SelectedSection.BANNED,
|
|
||||||
),
|
|
||||||
aRoomMemberListState(
|
|
||||||
roomMembers = AsyncData.Loading(
|
|
||||||
RoomMembers(
|
|
||||||
invited = persistentListOf(),
|
|
||||||
joined = persistentListOf(),
|
|
||||||
banned = persistentListOf(
|
|
||||||
aRoomMember(userId = UserId("@alice:example.com"), displayName = "Alice").withIdentity(),
|
|
||||||
aRoomMember(userId = UserId("@bob:example.com"), displayName = "Bob").withIdentity(),
|
|
||||||
aRoomMember(userId = UserId("@charlie:example.com"), displayName = "Charlie").withIdentity(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
moderationState = aRoomMemberModerationState(),
|
|
||||||
selectedSection = SelectedSection.BANNED,
|
|
||||||
),
|
|
||||||
aRoomMemberListState(
|
|
||||||
roomMembers = AsyncData.Success(
|
|
||||||
RoomMembers(
|
|
||||||
invited = persistentListOf(),
|
|
||||||
joined = persistentListOf(),
|
|
||||||
banned = persistentListOf(),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
moderationState = aRoomMemberModerationState(),
|
|
||||||
selectedSection = SelectedSection.BANNED,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -130,14 +81,17 @@ internal fun aRoomMemberListState(
|
||||||
roomMembers: AsyncData<RoomMembers> = AsyncData.Loading(),
|
roomMembers: AsyncData<RoomMembers> = AsyncData.Loading(),
|
||||||
moderationState: RoomMemberModerationState = aRoomMemberModerationState(),
|
moderationState: RoomMemberModerationState = aRoomMemberModerationState(),
|
||||||
selectedSection: SelectedSection = SelectedSection.MEMBERS,
|
selectedSection: SelectedSection = SelectedSection.MEMBERS,
|
||||||
|
searchQuery: String = "",
|
||||||
|
canInvite: Boolean = false,
|
||||||
|
eventSink: (RoomMemberListEvents) -> Unit = {},
|
||||||
) = RoomMemberListState(
|
) = RoomMemberListState(
|
||||||
roomMembers = roomMembers,
|
roomMembers = roomMembers,
|
||||||
filteredRoomMembers = roomMembers,
|
filteredRoomMembers = roomMembers.map { it.filter(searchQuery) },
|
||||||
searchQuery = "",
|
searchQuery = searchQuery,
|
||||||
canInvite = false,
|
canInvite = canInvite,
|
||||||
moderationState = moderationState,
|
moderationState = moderationState,
|
||||||
selectedSection = selectedSection,
|
selectedSection = selectedSection,
|
||||||
eventSink = {}
|
eventSink = eventSink
|
||||||
)
|
)
|
||||||
|
|
||||||
fun aRoomMemberModerationState(
|
fun aRoomMemberModerationState(
|
||||||
|
|
@ -176,21 +130,30 @@ fun aRoomMember(
|
||||||
fun aRoomMemberList() = persistentListOf(
|
fun aRoomMemberList() = persistentListOf(
|
||||||
anAlice(),
|
anAlice(),
|
||||||
aBob(),
|
aBob(),
|
||||||
aRoomMember(UserId("@carol:server.org"), "Carol"),
|
aCarol(),
|
||||||
aRoomMember(UserId("@david:server.org"), "David"),
|
aDavid(),
|
||||||
aRoomMember(UserId("@eve:server.org"), "Eve"),
|
anEve(),
|
||||||
aRoomMember(UserId("@justin:server.org"), "Justin"),
|
anInvitedVictor(),
|
||||||
aRoomMember(UserId("@mallory:server.org"), "Mallory"),
|
anInvitedWalter(),
|
||||||
aRoomMember(UserId("@susie:server.org"), "Susie"),
|
aBannedSusie(),
|
||||||
aVictor(),
|
aBannedMallory(),
|
||||||
aWalter(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun anEve(): RoomMember = aRoomMember(UserId("@eve:server.org"), "Eve")
|
||||||
|
|
||||||
|
fun aDavid(): RoomMember = aRoomMember(UserId("@david:server.org"), "David")
|
||||||
|
|
||||||
|
fun aCarol(): RoomMember = aRoomMember(UserId("@carol:server.org"), "Carol")
|
||||||
|
|
||||||
fun anAlice() = aRoomMember(UserId("@alice:server.org"), "Alice", role = RoomMember.Role.Admin)
|
fun anAlice() = aRoomMember(UserId("@alice:server.org"), "Alice", role = RoomMember.Role.Admin)
|
||||||
fun aBob() = aRoomMember(UserId("@bob:server.org"), "Bob", role = RoomMember.Role.Moderator)
|
fun aBob() = aRoomMember(UserId("@bob:server.org"), "Bob", role = RoomMember.Role.Moderator)
|
||||||
|
|
||||||
fun aVictor() = aRoomMember(UserId("@victor:server.org"), "Victor", membership = RoomMembershipState.INVITE)
|
fun anInvitedVictor() = aRoomMember(UserId("@victor:server.org"), "Victor", membership = RoomMembershipState.INVITE)
|
||||||
|
|
||||||
fun aWalter() = aRoomMember(UserId("@walter:server.org"), "Walter", membership = RoomMembershipState.INVITE)
|
fun anInvitedWalter() = aRoomMember(UserId("@walter:server.org"), "Walter", membership = RoomMembershipState.INVITE)
|
||||||
|
|
||||||
|
fun aBannedSusie(): RoomMember = aRoomMember(UserId("@susie:server.org"), "Susie", membership = RoomMembershipState.BAN)
|
||||||
|
|
||||||
|
fun aBannedMallory(): RoomMember = aRoomMember(UserId("@mallory:server.org"), "Mallory", membership = RoomMembershipState.BAN)
|
||||||
|
|
||||||
private fun RoomMember.withIdentity(identityState: IdentityState? = null) = RoomMemberWithIdentityState(this, identityState)
|
private fun RoomMember.withIdentity(identityState: IdentityState? = null) = RoomMemberWithIdentityState(this, identityState)
|
||||||
|
|
|
||||||
|
|
@ -8,36 +8,27 @@
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.members
|
package io.element.android.features.roomdetails.impl.members
|
||||||
|
|
||||||
import app.cash.molecule.RecompositionMode
|
|
||||||
import app.cash.molecule.moleculeFlow
|
|
||||||
import app.cash.turbine.test
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
|
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembersState
|
import io.element.android.libraries.matrix.api.room.RoomMembersState
|
||||||
|
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||||
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
||||||
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
||||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.test
|
||||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import kotlinx.coroutines.time.withTimeout
|
|
||||||
import kotlinx.coroutines.withTimeout
|
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
@ExperimentalCoroutinesApi
|
||||||
class RoomMemberListPresenterTest {
|
class RoomMemberListPresenterTest {
|
||||||
|
|
@ -45,176 +36,132 @@ class RoomMemberListPresenterTest {
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `member loading is done automatically on start, but is async`() = runTest {
|
fun `initial state is loading`() = runTest {
|
||||||
val room = FakeJoinedRoom(
|
val presenter = createPresenter()
|
||||||
baseRoom = FakeBaseRoom(
|
presenter.test {
|
||||||
updateMembersResult = { Result.success(Unit) },
|
|
||||||
canInviteResult = { Result.success(true) }
|
|
||||||
).apply {
|
|
||||||
// Needed to avoid discarding the loaded members as a partial and invalid result
|
|
||||||
givenRoomInfo(aRoomInfo(joinedMembersCount = 2))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
val presenter = createPresenter(joinedRoom = room)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.roomMembers.isLoading()).isTrue()
|
assertThat(initialState.filteredRoomMembers.isLoading()).isTrue()
|
||||||
assertThat(initialState.searchQuery).isEmpty()
|
assertThat(initialState.searchQuery).isEmpty()
|
||||||
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java)
|
assertThat(initialState.selectedSection).isEqualTo(SelectedSection.MEMBERS)
|
||||||
assertThat(initialState.isSearchActive).isFalse()
|
|
||||||
room.givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
|
||||||
// Skip item while the new members state is processed
|
|
||||||
skipItems(1)
|
|
||||||
val loadedMembersState = awaitItem()
|
|
||||||
assertThat(loadedMembersState.roomMembers.isLoading()).isFalse()
|
|
||||||
assertThat(loadedMembersState.roomMembers.dataOrNull()?.invited)
|
|
||||||
.isEqualTo(listOf(RoomMemberWithIdentityState(aVictor(), null), RoomMemberWithIdentityState(aWalter(), null)))
|
|
||||||
assertThat(loadedMembersState.roomMembers.dataOrNull()?.joined).isNotEmpty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `member loading is done automatically when RoomInfo's activeMemberCount changes`() = runTest {
|
fun `hide banned section when there is no banned users`() = runTest {
|
||||||
val reloadMembersMutex = Mutex()
|
val allRoomMembers = aRoomMemberList()
|
||||||
val updateMembersLambda = lambdaRecorder<Unit> {
|
val noBannedMembers = allRoomMembers
|
||||||
if (reloadMembersMutex.isLocked) {
|
.filterNot { it.membership == RoomMembershipState.BAN }
|
||||||
reloadMembersMutex.unlock()
|
.toImmutableList()
|
||||||
|
val room = createFakeJoinedRoom()
|
||||||
|
.apply {
|
||||||
|
givenRoomMembersState(RoomMembersState.Ready(allRoomMembers))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
val room = FakeJoinedRoom(
|
|
||||||
baseRoom = FakeBaseRoom(
|
|
||||||
updateMembersResult = updateMembersLambda,
|
|
||||||
canInviteResult = { Result.success(true) }
|
|
||||||
).apply {
|
|
||||||
// Needed to avoid discarding the loaded members as a partial and invalid result
|
|
||||||
givenRoomInfo(aRoomInfo(joinedMembersCount = 2))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
val presenter = createPresenter(joinedRoom = room)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
skipItems(1)
|
|
||||||
val initialState = awaitItem()
|
|
||||||
assertThat(initialState.roomMembers.isLoading()).isTrue()
|
|
||||||
room.givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
|
||||||
// Skip item while the new members state is processed
|
|
||||||
skipItems(1)
|
|
||||||
val loadedMembersState = awaitItem()
|
|
||||||
assertThat(loadedMembersState.roomMembers.isLoading()).isFalse()
|
|
||||||
assertThat(loadedMembersState.roomMembers.dataOrNull()?.joined).isNotEmpty()
|
|
||||||
|
|
||||||
// Assert no events are emitted only with that change
|
|
||||||
expectNoEvents()
|
|
||||||
|
|
||||||
// This will only progress if the `Room.updateMembers()` function is called, triggered by the RoomInfo change
|
|
||||||
withTimeout(10.seconds) {
|
|
||||||
reloadMembersMutex.withLock {
|
|
||||||
launch { room.givenRoomInfo(aRoomInfo(activeMembersCount = 0L)) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the room members state as `Room.updateMembers()` would have done with the actual implementation
|
|
||||||
room.givenRoomMembersState(RoomMembersState.Ready(persistentListOf()))
|
|
||||||
// Wait for another update
|
|
||||||
skipItems(1)
|
|
||||||
// The members should be reloaded now
|
|
||||||
assertThat(awaitItem().roomMembers.dataOrNull()?.joined).isEmpty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `open search`() = runTest {
|
|
||||||
val presenter = createPresenter(
|
val presenter = createPresenter(
|
||||||
joinedRoom = FakeJoinedRoom(
|
joinedRoom = room,
|
||||||
baseRoom = FakeBaseRoom(
|
roomMemberModerationState = aRoomMemberModerationState(canBan = true),
|
||||||
updateMembersResult = { Result.success(Unit) },
|
|
||||||
canInviteResult = { Result.success(true) }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
presenter.test {
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val loadedState = awaitItem()
|
val loadedState = awaitItem()
|
||||||
loadedState.eventSink(RoomMemberListEvents.OnSearchActiveChanged(true))
|
assertThat(loadedState.showBannedSection).isTrue()
|
||||||
|
loadedState.eventSink(RoomMemberListEvents.ChangeSelectedSection(SelectedSection.BANNED))
|
||||||
|
val bannedSectionState = awaitItem()
|
||||||
|
assertThat(bannedSectionState.selectedSection).isEqualTo(SelectedSection.BANNED)
|
||||||
|
// Now update the room members to have no banned users
|
||||||
|
room.givenRoomMembersState(RoomMembersState.Ready(noBannedMembers))
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val searchActiveState = awaitItem()
|
val noBannedMembersState = awaitItem()
|
||||||
assertThat(searchActiveState.isSearchActive).isTrue()
|
assertThat(noBannedMembersState.showBannedSection).isFalse()
|
||||||
|
skipItems(1)
|
||||||
|
val finalState = awaitItem()
|
||||||
|
assertThat(finalState.selectedSection).isEqualTo(SelectedSection.MEMBERS)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `member loading is done automatically on start, but is async`() = runTest {
|
||||||
|
val room = createFakeJoinedRoom()
|
||||||
|
val presenter = createPresenter(joinedRoom = room)
|
||||||
|
presenter.test {
|
||||||
|
skipItems(1)
|
||||||
|
val initialState = awaitItem()
|
||||||
|
assertThat(initialState.filteredRoomMembers.isLoading()).isTrue()
|
||||||
|
assertThat(initialState.searchQuery).isEmpty()
|
||||||
|
room.givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||||
|
// Skip items while the new members state is processed
|
||||||
|
skipItems(2)
|
||||||
|
val loadedState = awaitItem()
|
||||||
|
val loadedRoomMembers = loadedState.filteredRoomMembers.dataOrNull()!!
|
||||||
|
assertThat(loadedRoomMembers.joined).isNotEmpty()
|
||||||
|
assertThat(loadedRoomMembers.banned).isNotEmpty()
|
||||||
|
assertThat(loadedRoomMembers.invited).isNotEmpty()
|
||||||
|
assertThat(loadedRoomMembers.isEmpty(SelectedSection.MEMBERS)).isFalse()
|
||||||
|
assertThat(loadedRoomMembers.isEmpty(SelectedSection.BANNED)).isFalse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `search for something which is not found`() = runTest {
|
fun `search for something which is not found`() = runTest {
|
||||||
val presenter = createPresenter(
|
val room = createFakeJoinedRoom().apply {
|
||||||
joinedRoom = FakeJoinedRoom(
|
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||||
baseRoom = FakeBaseRoom(
|
}
|
||||||
updateMembersResult = { Result.success(Unit) },
|
val presenter = createPresenter(joinedRoom = room)
|
||||||
canInviteResult = { Result.success(true) }
|
presenter.test {
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val loadedState = awaitItem()
|
val loadedState = awaitItem()
|
||||||
loadedState.eventSink(RoomMemberListEvents.OnSearchActiveChanged(true))
|
val loadedRoomMembers = loadedState.filteredRoomMembers.dataOrNull()!!
|
||||||
val searchActiveState = awaitItem()
|
assertThat(loadedRoomMembers.joined).isNotEmpty()
|
||||||
searchActiveState.eventSink(RoomMemberListEvents.UpdateSearchQuery("something"))
|
assertThat(loadedRoomMembers.banned).isNotEmpty()
|
||||||
skipItems(1)
|
assertThat(loadedRoomMembers.invited).isNotEmpty()
|
||||||
|
assertThat(loadedRoomMembers.isEmpty(SelectedSection.MEMBERS)).isFalse()
|
||||||
|
assertThat(loadedRoomMembers.isEmpty(SelectedSection.BANNED)).isFalse()
|
||||||
|
loadedState.eventSink(RoomMemberListEvents.UpdateSearchQuery("something"))
|
||||||
val searchQueryUpdatedState = awaitItem()
|
val searchQueryUpdatedState = awaitItem()
|
||||||
assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("something")
|
assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("something")
|
||||||
val searchSearchResultDelivered = awaitItem()
|
val searchSearchResultDelivered = awaitItem()
|
||||||
assertThat(searchSearchResultDelivered.searchResults).isInstanceOf(SearchBarResultState.NoResultsFound::class.java)
|
val emptyRoomMembers = searchSearchResultDelivered.filteredRoomMembers.dataOrNull()!!
|
||||||
|
assertThat(emptyRoomMembers.joined).isEmpty()
|
||||||
|
assertThat(emptyRoomMembers.banned).isEmpty()
|
||||||
|
assertThat(emptyRoomMembers.invited).isEmpty()
|
||||||
|
assertThat(emptyRoomMembers.isEmpty(SelectedSection.MEMBERS)).isTrue()
|
||||||
|
assertThat(emptyRoomMembers.isEmpty(SelectedSection.BANNED)).isTrue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `search for something which is found`() = runTest {
|
fun `search for something which is found`() = runTest {
|
||||||
val presenter = createPresenter(
|
val room = createFakeJoinedRoom().apply {
|
||||||
joinedRoom = FakeJoinedRoom(
|
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||||
baseRoom = FakeBaseRoom(
|
}
|
||||||
updateMembersResult = { Result.success(Unit) },
|
val presenter = createPresenter(joinedRoom = room)
|
||||||
canInviteResult = { Result.success(true) }
|
presenter.test {
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val loadedState = awaitItem()
|
val loadedState = awaitItem()
|
||||||
loadedState.eventSink(RoomMemberListEvents.OnSearchActiveChanged(true))
|
val loadedRoomMembers = loadedState.filteredRoomMembers.dataOrNull()!!
|
||||||
val searchActiveState = awaitItem()
|
assertThat(loadedRoomMembers.joined).isNotEmpty()
|
||||||
searchActiveState.eventSink(RoomMemberListEvents.UpdateSearchQuery("Alice"))
|
assertThat(loadedRoomMembers.banned).isNotEmpty()
|
||||||
skipItems(1)
|
assertThat(loadedRoomMembers.invited).isNotEmpty()
|
||||||
|
assertThat(loadedRoomMembers.isEmpty(SelectedSection.MEMBERS)).isFalse()
|
||||||
|
assertThat(loadedRoomMembers.isEmpty(SelectedSection.BANNED)).isFalse()
|
||||||
|
loadedState.eventSink(RoomMemberListEvents.UpdateSearchQuery("alice"))
|
||||||
val searchQueryUpdatedState = awaitItem()
|
val searchQueryUpdatedState = awaitItem()
|
||||||
assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("Alice")
|
assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("alice")
|
||||||
val searchSearchResultDelivered = awaitItem()
|
val searchSearchResultDelivered = awaitItem()
|
||||||
assertThat(searchSearchResultDelivered.searchResults).isInstanceOf(SearchBarResultState.Results::class.java)
|
val emptyRoomMembers = searchSearchResultDelivered.filteredRoomMembers.dataOrNull()!!
|
||||||
assertThat((searchSearchResultDelivered.searchResults as SearchBarResultState.Results).results.dataOrNull()!!.joined.first().roomMember.displayName)
|
assertThat(emptyRoomMembers.joined).isNotEmpty()
|
||||||
.isEqualTo("Alice")
|
assertThat(emptyRoomMembers.banned).isEmpty()
|
||||||
|
assertThat(emptyRoomMembers.invited).isEmpty()
|
||||||
|
assertThat(emptyRoomMembers.isEmpty(SelectedSection.MEMBERS)).isFalse()
|
||||||
|
assertThat(emptyRoomMembers.isEmpty(SelectedSection.BANNED)).isTrue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - asynchronously sets canInvite when user has correct power level`() = runTest {
|
fun `present - asynchronously sets canInvite when user has correct power level`() = runTest {
|
||||||
val presenter = createPresenter(
|
val presenter = createPresenter()
|
||||||
joinedRoom = FakeJoinedRoom(
|
presenter.test {
|
||||||
baseRoom = FakeBaseRoom(
|
|
||||||
canInviteResult = { Result.success(true) },
|
|
||||||
updateMembersResult = { Result.success(Unit) }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val loadedState = awaitItem()
|
val loadedState = awaitItem()
|
||||||
assertThat(loadedState.canInvite).isTrue()
|
assertThat(loadedState.canInvite).isTrue()
|
||||||
|
|
@ -224,17 +171,11 @@ class RoomMemberListPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - asynchronously sets canInvite when user does not have correct power level`() = runTest {
|
fun `present - asynchronously sets canInvite when user does not have correct power level`() = runTest {
|
||||||
val presenter = createPresenter(
|
val presenter = createPresenter(
|
||||||
joinedRoom = FakeJoinedRoom(
|
joinedRoom = createFakeJoinedRoom(
|
||||||
baseRoom = FakeBaseRoom(
|
canInviteResult = { Result.success(false) },
|
||||||
canInviteResult = { Result.success(false) },
|
|
||||||
updateMembersResult = { Result.success(Unit) }
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
presenter.test {
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
skipItems(1)
|
|
||||||
val loadedState = awaitItem()
|
val loadedState = awaitItem()
|
||||||
assertThat(loadedState.canInvite).isFalse()
|
assertThat(loadedState.canInvite).isFalse()
|
||||||
}
|
}
|
||||||
|
|
@ -243,70 +184,54 @@ class RoomMemberListPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - asynchronously sets canInvite when power level check fails`() = runTest {
|
fun `present - asynchronously sets canInvite when power level check fails`() = runTest {
|
||||||
val presenter = createPresenter(
|
val presenter = createPresenter(
|
||||||
joinedRoom = FakeJoinedRoom(
|
joinedRoom = createFakeJoinedRoom(
|
||||||
baseRoom = FakeBaseRoom(
|
canInviteResult = { Result.failure(RuntimeException("Eek")) },
|
||||||
canInviteResult = { Result.failure(RuntimeException("Eek")) },
|
|
||||||
updateMembersResult = { Result.success(Unit) }
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
presenter.test {
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
skipItems(1)
|
|
||||||
val loadedState = awaitItem()
|
val loadedState = awaitItem()
|
||||||
assertThat(loadedState.canInvite).isFalse()
|
assertThat(loadedState.canInvite).isFalse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - RoomMemberSelected will open the moderation options when target user is not banned`() = runTest {
|
fun `present - RoomMemberSelected will open the moderation options`() = runTest {
|
||||||
val roomMemberModerationPresenter = Presenter {
|
|
||||||
aRoomMemberModerationState(canBan = true, canKick = true)
|
|
||||||
}
|
|
||||||
val presenter = createPresenter(
|
val presenter = createPresenter(
|
||||||
roomMemberModerationPresenter = roomMemberModerationPresenter,
|
roomMemberModerationState = aRoomMemberModerationState(canBan = true, canKick = true)
|
||||||
joinedRoom = FakeJoinedRoom(
|
|
||||||
baseRoom = FakeBaseRoom(
|
|
||||||
updateMembersResult = { Result.success(Unit) },
|
|
||||||
canInviteResult = { Result.success(true) }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
presenter.test {
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
awaitItem().eventSink(RoomMemberListEvents.RoomMemberSelected(aVictor()))
|
awaitItem().eventSink(RoomMemberListEvents.RoomMemberSelected(anInvitedVictor()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
private fun createFakeJoinedRoom(
|
||||||
private fun TestScope.createDataSource(
|
updateMembersResult: () -> Unit = { },
|
||||||
room: BaseRoom = FakeBaseRoom().apply {
|
canInviteResult: (UserId) -> Result<Boolean> = { Result.success(true) },
|
||||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
): FakeJoinedRoom {
|
||||||
},
|
return FakeJoinedRoom(
|
||||||
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers()
|
baseRoom = FakeBaseRoom(
|
||||||
) = RoomMemberListDataSource(room, coroutineDispatchers)
|
updateMembersResult = updateMembersResult,
|
||||||
|
canInviteResult = canInviteResult,
|
||||||
|
).apply {
|
||||||
|
// Needed to avoid discarding the loaded members as a partial and invalid result
|
||||||
|
givenRoomInfo(aRoomInfo(joinedMembersCount = 2))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
@ExperimentalCoroutinesApi
|
||||||
private fun TestScope.createPresenter(
|
private fun TestScope.createPresenter(
|
||||||
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
||||||
joinedRoom: JoinedRoom = FakeJoinedRoom(
|
joinedRoom: JoinedRoom = createFakeJoinedRoom(),
|
||||||
baseRoom = FakeBaseRoom(
|
|
||||||
updateMembersResult = { Result.success(Unit) }
|
|
||||||
)
|
|
||||||
),
|
|
||||||
roomMemberListDataSource: RoomMemberListDataSource = createDataSource(coroutineDispatchers = coroutineDispatchers),
|
|
||||||
encryptedService: FakeEncryptionService = FakeEncryptionService(),
|
encryptedService: FakeEncryptionService = FakeEncryptionService(),
|
||||||
roomMemberModerationPresenter: Presenter<RoomMemberModerationState> = Presenter {
|
roomMemberModerationState: RoomMemberModerationState = aRoomMemberModerationState(),
|
||||||
aRoomMemberModerationState()
|
|
||||||
},
|
|
||||||
) = RoomMemberListPresenter(
|
) = RoomMemberListPresenter(
|
||||||
room = joinedRoom,
|
room = joinedRoom,
|
||||||
roomMemberListDataSource = roomMemberListDataSource,
|
|
||||||
coroutineDispatchers = coroutineDispatchers,
|
coroutineDispatchers = coroutineDispatchers,
|
||||||
roomMembersModerationPresenter = roomMemberModerationPresenter,
|
roomMembersModerationPresenter = Presenter {
|
||||||
|
roomMemberModerationState
|
||||||
|
},
|
||||||
encryptionService = encryptedService,
|
encryptionService = encryptedService,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue