Benoit Marty
7 months ago
committed by
GitHub
66 changed files with 840 additions and 267 deletions
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
Add ability to enter a recovery key to verify the session. Also fixes some refresh issues with the verification session state. |
@ -0,0 +1,210 @@
@@ -0,0 +1,210 @@
|
||||
/* |
||||
* Copyright (c) 2024 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 io.element.android.features.roomlist.impl |
||||
|
||||
import androidx.activity.ComponentActivity |
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule |
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule |
||||
import androidx.compose.ui.test.longClick |
||||
import androidx.compose.ui.test.onNodeWithContentDescription |
||||
import androidx.compose.ui.test.onNodeWithText |
||||
import androidx.compose.ui.test.performClick |
||||
import androidx.compose.ui.test.performTouchInput |
||||
import androidx.test.ext.junit.runners.AndroidJUnit4 |
||||
import io.element.android.features.roomlist.impl.components.RoomListMenuAction |
||||
import io.element.android.libraries.architecture.AsyncData |
||||
import io.element.android.libraries.matrix.api.core.RoomId |
||||
import io.element.android.libraries.ui.strings.CommonStrings |
||||
import io.element.android.tests.testutils.EnsureNeverCalled |
||||
import io.element.android.tests.testutils.EnsureNeverCalledWithParam |
||||
import io.element.android.tests.testutils.EventsRecorder |
||||
import io.element.android.tests.testutils.clickOn |
||||
import io.element.android.tests.testutils.ensureCalledOnce |
||||
import io.element.android.tests.testutils.ensureCalledOnceWithParam |
||||
import kotlinx.collections.immutable.persistentListOf |
||||
import org.junit.Rule |
||||
import org.junit.Test |
||||
import org.junit.rules.TestRule |
||||
import org.junit.runner.RunWith |
||||
|
||||
@RunWith(AndroidJUnit4::class) |
||||
class RoomListViewTest { |
||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>() |
||||
|
||||
@Test |
||||
fun `clicking on close verification banner emits the expected Event`() { |
||||
val eventsRecorder = EventsRecorder<RoomListEvents>() |
||||
rule.setRoomListView( |
||||
state = aRoomListState( |
||||
securityBannerState = SecurityBannerState.SessionVerification, |
||||
eventSink = eventsRecorder, |
||||
) |
||||
) |
||||
val close = rule.activity.getString(CommonStrings.action_close) |
||||
rule.onNodeWithContentDescription(close).performClick() |
||||
eventsRecorder.assertSingle(RoomListEvents.DismissRequestVerificationPrompt) |
||||
} |
||||
|
||||
@Test |
||||
fun `clicking on continue verification banner invokes the expected callback`() { |
||||
val eventsRecorder = EventsRecorder<RoomListEvents>(expectEvents = false) |
||||
ensureCalledOnce { callback -> |
||||
rule.setRoomListView( |
||||
state = aRoomListState( |
||||
securityBannerState = SecurityBannerState.SessionVerification, |
||||
eventSink = eventsRecorder, |
||||
), |
||||
onVerifyClicked = callback, |
||||
) |
||||
rule.clickOn(CommonStrings.action_continue) |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `clicking on close recovery key banner emits the expected Event`() { |
||||
val eventsRecorder = EventsRecorder<RoomListEvents>() |
||||
rule.setRoomListView( |
||||
state = aRoomListState( |
||||
securityBannerState = SecurityBannerState.RecoveryKeyConfirmation, |
||||
eventSink = eventsRecorder, |
||||
) |
||||
) |
||||
val close = rule.activity.getString(CommonStrings.action_close) |
||||
rule.onNodeWithContentDescription(close).performClick() |
||||
eventsRecorder.assertSingle(RoomListEvents.DismissRecoveryKeyPrompt) |
||||
} |
||||
|
||||
@Test |
||||
fun `clicking on continue recovery key banner invokes the expected callback`() { |
||||
val eventsRecorder = EventsRecorder<RoomListEvents>(expectEvents = false) |
||||
ensureCalledOnce { callback -> |
||||
rule.setRoomListView( |
||||
state = aRoomListState( |
||||
securityBannerState = SecurityBannerState.RecoveryKeyConfirmation, |
||||
eventSink = eventsRecorder, |
||||
), |
||||
onConfirmRecoveryKeyClicked = callback, |
||||
) |
||||
rule.clickOn(CommonStrings.action_continue) |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `clicking on start chat when the session has no room invokes the expected callback`() { |
||||
val eventsRecorder = EventsRecorder<RoomListEvents>(expectEvents = false) |
||||
ensureCalledOnce { callback -> |
||||
rule.setRoomListView( |
||||
state = aRoomListState( |
||||
eventSink = eventsRecorder, |
||||
roomList = AsyncData.Success(persistentListOf()), |
||||
), |
||||
onCreateRoomClicked = callback, |
||||
) |
||||
rule.clickOn(CommonStrings.action_start_chat) |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `clicking on a room invokes the expected callback`() { |
||||
val eventsRecorder = EventsRecorder<RoomListEvents>(expectEvents = false) |
||||
val state = aRoomListState( |
||||
eventSink = eventsRecorder, |
||||
) |
||||
val room0 = state.roomList.dataOrNull()!!.first() |
||||
ensureCalledOnceWithParam(room0.roomId) { callback -> |
||||
rule.setRoomListView( |
||||
state = state, |
||||
onRoomClicked = callback, |
||||
) |
||||
rule.onNodeWithText(room0.lastMessage!!.toString()).performClick() |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `long clicking on a room emits the expected Event`() { |
||||
val eventsRecorder = EventsRecorder<RoomListEvents>() |
||||
val state = aRoomListState( |
||||
eventSink = eventsRecorder, |
||||
) |
||||
val room0 = state.roomList.dataOrNull()!!.first() |
||||
rule.setRoomListView( |
||||
state = state, |
||||
) |
||||
rule.onNodeWithText(room0.lastMessage!!.toString()).performTouchInput { longClick() } |
||||
eventsRecorder.assertSingle(RoomListEvents.ShowContextMenu(room0)) |
||||
} |
||||
|
||||
@Test |
||||
fun `clicking on a room setting invokes the expected callback and emits expected Event`() { |
||||
val eventsRecorder = EventsRecorder<RoomListEvents>() |
||||
val state = aRoomListState( |
||||
contextMenu = aContextMenuShown(), |
||||
eventSink = eventsRecorder, |
||||
) |
||||
val room0 = (state.contextMenu as RoomListState.ContextMenu.Shown).roomId |
||||
ensureCalledOnceWithParam(room0) { callback -> |
||||
rule.setRoomListView( |
||||
state = state, |
||||
onRoomSettingsClicked = callback, |
||||
) |
||||
rule.clickOn(CommonStrings.common_settings) |
||||
} |
||||
eventsRecorder.assertSingle(RoomListEvents.HideContextMenu) |
||||
} |
||||
|
||||
@Test |
||||
fun `clicking on invites invokes the expected callback`() { |
||||
val eventsRecorder = EventsRecorder<RoomListEvents>() |
||||
val state = aRoomListState( |
||||
invitesState = InvitesState.NewInvites, |
||||
eventSink = eventsRecorder, |
||||
) |
||||
ensureCalledOnce { callback -> |
||||
rule.setRoomListView( |
||||
state = state, |
||||
onInvitesClicked = callback, |
||||
) |
||||
rule.clickOn(CommonStrings.action_invites_list) |
||||
} |
||||
} |
||||
} |
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoomListView( |
||||
state: RoomListState, |
||||
onRoomClicked: (RoomId) -> Unit = EnsureNeverCalledWithParam(), |
||||
onSettingsClicked: () -> Unit = EnsureNeverCalled(), |
||||
onVerifyClicked: () -> Unit = EnsureNeverCalled(), |
||||
onConfirmRecoveryKeyClicked: () -> Unit = EnsureNeverCalled(), |
||||
onCreateRoomClicked: () -> Unit = EnsureNeverCalled(), |
||||
onInvitesClicked: () -> Unit = EnsureNeverCalled(), |
||||
onRoomSettingsClicked: (RoomId) -> Unit = EnsureNeverCalledWithParam(), |
||||
onMenuActionClicked: (RoomListMenuAction) -> Unit = EnsureNeverCalledWithParam(), |
||||
) { |
||||
setContent { |
||||
RoomListView( |
||||
state = state, |
||||
onRoomClicked = onRoomClicked, |
||||
onSettingsClicked = onSettingsClicked, |
||||
onVerifyClicked = onVerifyClicked, |
||||
onConfirmRecoveryKeyClicked = onConfirmRecoveryKeyClicked, |
||||
onCreateRoomClicked = onCreateRoomClicked, |
||||
onInvitesClicked = onInvitesClicked, |
||||
onRoomSettingsClicked = onRoomSettingsClicked, |
||||
onMenuActionClicked = onMenuActionClicked, |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,151 @@
@@ -0,0 +1,151 @@
|
||||
/* |
||||
* Copyright (c) 2024 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 io.element.android.features.verifysession.impl |
||||
|
||||
import androidx.activity.ComponentActivity |
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule |
||||
import androidx.test.ext.junit.runners.AndroidJUnit4 |
||||
import io.element.android.libraries.architecture.AsyncData |
||||
import io.element.android.libraries.ui.strings.CommonStrings |
||||
import io.element.android.tests.testutils.EnsureNeverCalled |
||||
import io.element.android.tests.testutils.EventsRecorder |
||||
import io.element.android.tests.testutils.clickOn |
||||
import io.element.android.tests.testutils.ensureCalledOnce |
||||
import io.element.android.tests.testutils.pressBackKey |
||||
import org.junit.Rule |
||||
import org.junit.Test |
||||
import org.junit.runner.RunWith |
||||
import org.robolectric.annotation.Config |
||||
|
||||
@RunWith(AndroidJUnit4::class) |
||||
class VerifySelfSessionViewTest { |
||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>() |
||||
|
||||
@Test |
||||
fun `clicking on cancel calls the expected callback and emits the expected Event`() { |
||||
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>() |
||||
ensureCalledOnce { callback -> |
||||
rule.setContent { |
||||
VerifySelfSessionView( |
||||
aVerifySelfSessionState( |
||||
verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(true), |
||||
eventSink = eventsRecorder |
||||
), |
||||
onEnterRecoveryKey = EnsureNeverCalled(), |
||||
goBack = callback, |
||||
) |
||||
} |
||||
rule.clickOn(CommonStrings.action_cancel) |
||||
} |
||||
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.CancelAndClose) |
||||
} |
||||
|
||||
@Test |
||||
fun `clicking on back key calls the expected callback and emits the expected Event`() { |
||||
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>() |
||||
ensureCalledOnce { callback -> |
||||
rule.setContent { |
||||
VerifySelfSessionView( |
||||
aVerifySelfSessionState( |
||||
verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(true), |
||||
eventSink = eventsRecorder |
||||
), |
||||
onEnterRecoveryKey = EnsureNeverCalled(), |
||||
goBack = callback, |
||||
) |
||||
} |
||||
rule.pressBackKey() |
||||
} |
||||
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.CancelAndClose) |
||||
} |
||||
|
||||
@Test |
||||
fun `when flow is completed, the expected callback is invoked`() { |
||||
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>(expectEvents = false) |
||||
ensureCalledOnce { callback -> |
||||
rule.setContent { |
||||
VerifySelfSessionView( |
||||
aVerifySelfSessionState( |
||||
verificationFlowStep = VerifySelfSessionState.VerificationStep.Completed, |
||||
eventSink = eventsRecorder |
||||
), |
||||
onEnterRecoveryKey = EnsureNeverCalled(), |
||||
goBack = callback, |
||||
) |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Config(qualifiers = "h1024dp") |
||||
@Test |
||||
fun `clicking on enter recovery key calls the expected callback`() { |
||||
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>(expectEvents = false) |
||||
ensureCalledOnce { callback -> |
||||
rule.setContent { |
||||
VerifySelfSessionView( |
||||
aVerifySelfSessionState( |
||||
verificationFlowStep = VerifySelfSessionState.VerificationStep.Initial(true), |
||||
eventSink = eventsRecorder |
||||
), |
||||
onEnterRecoveryKey = callback, |
||||
goBack = EnsureNeverCalled(), |
||||
) |
||||
} |
||||
rule.clickOn(R.string.screen_session_verification_enter_recovery_key) |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
fun `clicking on they match emits the expected event`() { |
||||
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>() |
||||
rule.setContent { |
||||
VerifySelfSessionView( |
||||
aVerifySelfSessionState( |
||||
verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying( |
||||
data = aEmojisSessionVerificationData(), |
||||
state = AsyncData.Uninitialized, |
||||
), |
||||
eventSink = eventsRecorder |
||||
), |
||||
onEnterRecoveryKey = EnsureNeverCalled(), |
||||
goBack = EnsureNeverCalled(), |
||||
) |
||||
} |
||||
rule.clickOn(R.string.screen_session_verification_they_match) |
||||
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.ConfirmVerification) |
||||
} |
||||
|
||||
@Test |
||||
fun `clicking on they do not match emits the expected event`() { |
||||
val eventsRecorder = EventsRecorder<VerifySelfSessionViewEvents>() |
||||
rule.setContent { |
||||
VerifySelfSessionView( |
||||
aVerifySelfSessionState( |
||||
verificationFlowStep = VerifySelfSessionState.VerificationStep.Verifying( |
||||
data = aEmojisSessionVerificationData(), |
||||
state = AsyncData.Uninitialized, |
||||
), |
||||
eventSink = eventsRecorder |
||||
), |
||||
onEnterRecoveryKey = EnsureNeverCalled(), |
||||
goBack = EnsureNeverCalled(), |
||||
) |
||||
} |
||||
rule.clickOn(R.string.screen_session_verification_they_dont_match) |
||||
eventsRecorder.assertSingle(VerifySelfSessionViewEvents.DeclineVerification) |
||||
} |
||||
} |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue