@ -20,7 +20,9 @@ import app.cash.molecule.RecompositionClock
@@ -20,7 +20,9 @@ import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomMembershipState
import io.element.android.libraries.matrix.api.room.RoomSummary
@ -32,6 +34,7 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID
@@ -32,6 +34,7 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.A_USER_NAME
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
@ -80,32 +83,7 @@ class InviteListPresenterTests {
@@ -80,32 +83,7 @@ class InviteListPresenterTests {
@Test
fun `present - uses user ID and avatar for direct invites` ( ) = runTest {
val invitesDataSource = FakeRoomSummaryDataSource ( )
invitesDataSource . postRoomSummary (
listOf (
RoomSummary . Filled (
RoomSummaryDetails (
roomId = A _ROOM _ID ,
name = A _USER _NAME ,
avatarURLString = null ,
isDirect = true ,
lastMessage = null ,
lastMessageTimestamp = null ,
unreadNotificationCount = 0 ,
inviter = RoomMember (
userId = A _USER _ID ,
displayName = A _USER _NAME ,
avatarUrl = AN _AVATAR _URL ,
membership = RoomMembershipState . JOIN ,
isNameAmbiguous = false ,
powerLevel = 0 ,
normalizedPowerLevel = 0 ,
isIgnored = false ,
)
)
)
)
)
val invitesDataSource = FakeRoomSummaryDataSource ( ) . withDirectChatInvitation ( )
val presenter = InviteListPresenter (
FakeMatrixClient (
sessionId = A _SESSION _ID ,
@ -119,7 +97,7 @@ class InviteListPresenterTests {
@@ -119,7 +97,7 @@ class InviteListPresenterTests {
Truth . assertThat ( withInviteState . inviteList . size ) . isEqualTo ( 1 )
Truth . assertThat ( withInviteState . inviteList [ 0 ] . roomId ) . isEqualTo ( A _ROOM _ID )
Truth . assertThat ( withInviteState . inviteList [ 0 ] . roomAlias ) . isEqualTo ( A _USER _ID . value )
Truth . assertThat ( withInviteState . inviteList [ 0 ] . roomName ) . isEqualTo ( A _USE R _NAME )
Truth . assertThat ( withInviteState . inviteList [ 0 ] . roomName ) . isEqualTo ( A _ROOM _NAME )
Truth . assertThat ( withInviteState . inviteList [ 0 ] . roomAvatarData ) . isEqualTo (
AvatarData (
id = A _USER _ID . value ,
@ -133,13 +111,283 @@ class InviteListPresenterTests {
@@ -133,13 +111,283 @@ class InviteListPresenterTests {
@Test
fun `present - includes sender details for room invites` ( ) = runTest {
val invitesDataSource = FakeRoomSummaryDataSource ( )
invitesDataSource . postRoomSummary (
val invitesDataSource = FakeRoomSummaryDataSource ( ) . withRoomInvitation ( )
val presenter = InviteListPresenter (
FakeMatrixClient (
sessionId = A _SESSION _ID ,
invitesDataSource = invitesDataSource ,
)
)
moleculeFlow ( RecompositionClock . Immediate ) {
presenter . present ( )
} . test {
val withInviteState = awaitItem ( )
Truth . assertThat ( withInviteState . inviteList . size ) . isEqualTo ( 1 )
Truth . assertThat ( withInviteState . inviteList [ 0 ] . sender ?. displayName ) . isEqualTo ( A _USER _NAME )
Truth . assertThat ( withInviteState . inviteList [ 0 ] . sender ?. userId ) . isEqualTo ( A _USER _ID )
Truth . assertThat ( withInviteState . inviteList [ 0 ] . sender ?. avatarData ) . isEqualTo (
AvatarData (
id = A _USER _ID . value ,
name = A _USER _NAME ,
url = AN _AVATAR _URL ,
)
)
}
}
@Test
fun `present - shows confirm dialog for declining direct chat invites` ( ) = runTest {
val invitesDataSource = FakeRoomSummaryDataSource ( ) . withDirectChatInvitation ( )
val presenter = InviteListPresenter (
FakeMatrixClient (
sessionId = A _SESSION _ID ,
invitesDataSource = invitesDataSource ,
)
)
moleculeFlow ( RecompositionClock . Immediate ) {
presenter . present ( )
} . test {
val originalState = awaitItem ( )
originalState . eventSink ( InviteListEvents . DeclineInvite ( originalState . inviteList [ 0 ] ) )
val newState = awaitItem ( )
Truth . assertThat ( newState . declineConfirmationDialog ) . isInstanceOf ( InviteDeclineConfirmationDialog . Visible :: class . java )
val confirmDialog = newState . declineConfirmationDialog as InviteDeclineConfirmationDialog . Visible
Truth . assertThat ( confirmDialog . isDirect ) . isTrue ( )
Truth . assertThat ( confirmDialog . name ) . isEqualTo ( A _ROOM _NAME )
}
}
@Test
fun `present - shows confirm dialog for declining room invites` ( ) = runTest {
val invitesDataSource = FakeRoomSummaryDataSource ( ) . withRoomInvitation ( )
val presenter = InviteListPresenter (
FakeMatrixClient (
sessionId = A _SESSION _ID ,
invitesDataSource = invitesDataSource ,
)
)
moleculeFlow ( RecompositionClock . Immediate ) {
presenter . present ( )
} . test {
val originalState = awaitItem ( )
originalState . eventSink ( InviteListEvents . DeclineInvite ( originalState . inviteList [ 0 ] ) )
val newState = awaitItem ( )
Truth . assertThat ( newState . declineConfirmationDialog ) . isInstanceOf ( InviteDeclineConfirmationDialog . Visible :: class . java )
val confirmDialog = newState . declineConfirmationDialog as InviteDeclineConfirmationDialog . Visible
Truth . assertThat ( confirmDialog . isDirect ) . isFalse ( )
Truth . assertThat ( confirmDialog . name ) . isEqualTo ( A _ROOM _NAME )
}
}
@Test
fun `present - hides confirm dialog when cancelling` ( ) = runTest {
val invitesDataSource = FakeRoomSummaryDataSource ( ) . withRoomInvitation ( )
val presenter = InviteListPresenter (
FakeMatrixClient (
sessionId = A _SESSION _ID ,
invitesDataSource = invitesDataSource ,
)
)
moleculeFlow ( RecompositionClock . Immediate ) {
presenter . present ( )
} . test {
val originalState = awaitItem ( )
originalState . eventSink ( InviteListEvents . DeclineInvite ( originalState . inviteList [ 0 ] ) )
skipItems ( 1 )
originalState . eventSink ( InviteListEvents . CancelDeclineInvite )
val newState = awaitItem ( )
Truth . assertThat ( newState . declineConfirmationDialog ) . isInstanceOf ( InviteDeclineConfirmationDialog . Hidden :: class . java )
}
}
@Test
fun `present - declines invite after confirming` ( ) = runTest {
val invitesDataSource = FakeRoomSummaryDataSource ( ) . withRoomInvitation ( )
val client = FakeMatrixClient (
sessionId = A _SESSION _ID ,
invitesDataSource = invitesDataSource ,
)
val room = FakeMatrixRoom ( )
val presenter = InviteListPresenter ( client )
client . givenGetRoomResult ( A _ROOM _ID , room )
moleculeFlow ( RecompositionClock . Immediate ) {
presenter . present ( )
} . test {
val originalState = awaitItem ( )
originalState . eventSink ( InviteListEvents . DeclineInvite ( originalState . inviteList [ 0 ] ) )
skipItems ( 1 )
originalState . eventSink ( InviteListEvents . ConfirmDeclineInvite )
skipItems ( 2 )
Truth . assertThat ( room . isInviteRejected ) . isTrue ( )
}
}
@Test
fun `present - declines invite after confirming and sets state on error` ( ) = runTest {
val invitesDataSource = FakeRoomSummaryDataSource ( ) . withRoomInvitation ( )
val client = FakeMatrixClient (
sessionId = A _SESSION _ID ,
invitesDataSource = invitesDataSource ,
)
val room = FakeMatrixRoom ( )
val presenter = InviteListPresenter ( client )
val ex = Throwable ( " Ruh roh! " )
room . givenRejectInviteResult ( Result . failure ( ex ) )
client . givenGetRoomResult ( A _ROOM _ID , room )
moleculeFlow ( RecompositionClock . Immediate ) {
presenter . present ( )
} . test {
val originalState = awaitItem ( )
originalState . eventSink ( InviteListEvents . DeclineInvite ( originalState . inviteList [ 0 ] ) )
skipItems ( 1 )
originalState . eventSink ( InviteListEvents . ConfirmDeclineInvite )
skipItems ( 1 )
val newState = awaitItem ( )
Truth . assertThat ( room . isInviteRejected ) . isTrue ( )
Truth . assertThat ( newState . declinedAction ) . isEqualTo ( Async . Failure < Unit > ( ex ) )
}
}
@Test
fun `present - dismisses declining error state` ( ) = runTest {
val invitesDataSource = FakeRoomSummaryDataSource ( ) . withRoomInvitation ( )
val client = FakeMatrixClient (
sessionId = A _SESSION _ID ,
invitesDataSource = invitesDataSource ,
)
val room = FakeMatrixRoom ( )
val presenter = InviteListPresenter ( client )
val ex = Throwable ( " Ruh roh! " )
room . givenRejectInviteResult ( Result . failure ( ex ) )
client . givenGetRoomResult ( A _ROOM _ID , room )
moleculeFlow ( RecompositionClock . Immediate ) {
presenter . present ( )
} . test {
val originalState = awaitItem ( )
originalState . eventSink ( InviteListEvents . DeclineInvite ( originalState . inviteList [ 0 ] ) )
skipItems ( 1 )
originalState . eventSink ( InviteListEvents . ConfirmDeclineInvite )
skipItems ( 2 )
originalState . eventSink ( InviteListEvents . DismissDeclineError )
val newState = awaitItem ( )
Truth . assertThat ( newState . declinedAction ) . isEqualTo ( Async . Uninitialized )
}
}
@Test
fun `present - accepts invites and sets state on success` ( ) = runTest {
val invitesDataSource = FakeRoomSummaryDataSource ( ) . withRoomInvitation ( )
val client = FakeMatrixClient (
sessionId = A _SESSION _ID ,
invitesDataSource = invitesDataSource ,
)
val room = FakeMatrixRoom ( )
val presenter = InviteListPresenter ( client )
client . givenGetRoomResult ( A _ROOM _ID , room )
moleculeFlow ( RecompositionClock . Immediate ) {
presenter . present ( )
} . test {
val originalState = awaitItem ( )
originalState . eventSink ( InviteListEvents . AcceptInvite ( originalState . inviteList [ 0 ] ) )
val newState = awaitItem ( )
Truth . assertThat ( room . isInviteAccepted ) . isTrue ( )
Truth . assertThat ( newState . acceptedAction ) . isEqualTo ( Async . Success ( A _ROOM _ID ) )
}
}
@Test
fun `present - accepts invites and sets state on error` ( ) = runTest {
val invitesDataSource = FakeRoomSummaryDataSource ( ) . withRoomInvitation ( )
val client = FakeMatrixClient (
sessionId = A _SESSION _ID ,
invitesDataSource = invitesDataSource ,
)
val room = FakeMatrixRoom ( )
val presenter = InviteListPresenter ( client )
val ex = Throwable ( " Ruh roh! " )
room . givenAcceptInviteResult ( Result . failure ( ex ) )
client . givenGetRoomResult ( A _ROOM _ID , room )
moleculeFlow ( RecompositionClock . Immediate ) {
presenter . present ( )
} . test {
val originalState = awaitItem ( )
originalState . eventSink ( InviteListEvents . AcceptInvite ( originalState . inviteList [ 0 ] ) )
val newState = awaitItem ( )
Truth . assertThat ( room . isInviteAccepted ) . isTrue ( )
Truth . assertThat ( newState . acceptedAction ) . isEqualTo ( Async . Failure < RoomId > ( ex ) )
}
}
@Test
fun `present - dismisses accepting error state` ( ) = runTest {
val invitesDataSource = FakeRoomSummaryDataSource ( ) . withRoomInvitation ( )
val client = FakeMatrixClient (
sessionId = A _SESSION _ID ,
invitesDataSource = invitesDataSource ,
)
val room = FakeMatrixRoom ( )
val presenter = InviteListPresenter ( client )
val ex = Throwable ( " Ruh roh! " )
room . givenAcceptInviteResult ( Result . failure ( ex ) )
client . givenGetRoomResult ( A _ROOM _ID , room )
moleculeFlow ( RecompositionClock . Immediate ) {
presenter . present ( )
} . test {
val originalState = awaitItem ( )
originalState . eventSink ( InviteListEvents . AcceptInvite ( originalState . inviteList [ 0 ] ) )
skipItems ( 1 )
originalState . eventSink ( InviteListEvents . DismissAcceptError )
val newState = awaitItem ( )
Truth . assertThat ( newState . acceptedAction ) . isEqualTo ( Async . Uninitialized )
}
}
private suspend fun FakeRoomSummaryDataSource . withRoomInvitation ( ) : FakeRoomSummaryDataSource {
postRoomSummary (
listOf (
RoomSummary . Filled (
RoomSummaryDetails (
roomId = A _ROOM _ID ,
name = A _USER _NAME ,
name = A _ROOM _NAME ,
avatarURLString = null ,
isDirect = false ,
lastMessage = null ,
@ -159,26 +407,35 @@ class InviteListPresenterTests {
@@ -159,26 +407,35 @@ class InviteListPresenterTests {
)
)
)
val presenter = InviteListPresenter (
FakeMatrixClient (
sessionId = A _SESSION _ID ,
invitesDataSource = invitesDataSource ,
)
)
moleculeFlow ( RecompositionClock . Immediate ) {
presenter . present ( )
} . test {
val withInviteState = awaitItem ( )
Truth . assertThat ( withInviteState . inviteList . size ) . isEqualTo ( 1 )
Truth . assertThat ( withInviteState . inviteList [ 0 ] . sender ?. displayName ) . isEqualTo ( A _USER _NAME )
Truth . assertThat ( withInviteState . inviteList [ 0 ] . sender ?. userId ) . isEqualTo ( A _USER _ID )
Truth . assertThat ( withInviteState . inviteList [ 0 ] . sender ?. avatarData ) . isEqualTo (
AvatarData (
id = A _USER _ID . value ,
name = A _USER _NAME ,
url = AN _AVATAR _URL ,
return this
}
private suspend fun FakeRoomSummaryDataSource . withDirectChatInvitation ( ) : FakeRoomSummaryDataSource {
postRoomSummary (
listOf (
RoomSummary . Filled (
RoomSummaryDetails (
roomId = A _ROOM _ID ,
name = A _ROOM _NAME ,
avatarURLString = null ,
isDirect = true ,
lastMessage = null ,
lastMessageTimestamp = null ,
unreadNotificationCount = 0 ,
inviter = RoomMember (
userId = A _USER _ID ,
displayName = A _USER _NAME ,
avatarUrl = AN _AVATAR _URL ,
membership = RoomMembershipState . JOIN ,
isNameAmbiguous = false ,
powerLevel = 0 ,
normalizedPowerLevel = 0 ,
isIgnored = false ,
)
)
)
)
}
)
return this
}
}