Skip to content

Commit cec945c

Browse files
committed
Merge branch 'release/0.6.3' into main
2 parents cfc0fc9 + 7eac45e commit cec945c

File tree

96 files changed

+2597
-369
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

96 files changed

+2597
-369
lines changed

CHANGES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
Changes in Element X v0.6.2 (2024-09-17)
2+
========================================
3+
4+
### ✨ Features
5+
* Account deactivation. by @bmarty in https://github.com/element-hq/element-x-android/pull/3479
6+
17
Changes in Element X v0.6.1 (2024-09-17)
28
========================================
39

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Element X is the new generation of Element for professional and personal use on mobile. It’s the fastest Matrix client with a seamless & intuitive user interface.
2+
Full changelog: https://github.com/element-hq/element-x-android/releases

features/deactivation/impl/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ dependencies {
3333
implementation(projects.libraries.architecture)
3434
implementation(projects.libraries.matrix.api)
3535
implementation(projects.libraries.designsystem)
36+
implementation(projects.libraries.testtags)
3637
implementation(projects.libraries.uiStrings)
3738
api(projects.features.deactivation.api)
3839

features/deactivation/impl/src/main/kotlin/io/element/android/features/logout/impl/AccountDeactivationView.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ import io.element.android.libraries.designsystem.theme.components.Text
6565
import io.element.android.libraries.designsystem.theme.components.TopAppBar
6666
import io.element.android.libraries.designsystem.theme.components.autofill
6767
import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext
68+
import io.element.android.libraries.testtags.TestTags
69+
import io.element.android.libraries.testtags.testTag
6870
import io.element.android.libraries.ui.strings.CommonStrings
6971
import kotlinx.collections.immutable.persistentListOf
7072

@@ -277,6 +279,7 @@ private fun Content(
277279
.padding(top = 8.dp)
278280
.fillMaxWidth()
279281
.onTabOrEnterKeyFocusNext(focusManager)
282+
.testTag(TestTags.loginPassword)
280283
.autofill(
281284
autofillTypes = listOf(AutofillType.Password),
282285
onFill = {

features/deactivation/impl/src/test/kotlin/io/element/android/features/logout/impl/AccountDeactivationViewTest.kt

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,26 @@ package io.element.android.features.logout.impl
1010
import androidx.activity.ComponentActivity
1111
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
1212
import androidx.compose.ui.test.junit4.createAndroidComposeRule
13+
import androidx.compose.ui.test.onNodeWithTag
14+
import androidx.compose.ui.test.performTextInput
1315
import androidx.test.ext.junit.runners.AndroidJUnit4
16+
import io.element.android.features.deactivation.impl.R
17+
import io.element.android.libraries.architecture.AsyncAction
18+
import io.element.android.libraries.matrix.test.AN_EXCEPTION
19+
import io.element.android.libraries.matrix.test.A_PASSWORD
20+
import io.element.android.libraries.testtags.TestTags
21+
import io.element.android.libraries.ui.strings.CommonStrings
1422
import io.element.android.tests.testutils.EnsureNeverCalled
1523
import io.element.android.tests.testutils.EventsRecorder
24+
import io.element.android.tests.testutils.clickOn
1625
import io.element.android.tests.testutils.ensureCalledOnce
1726
import io.element.android.tests.testutils.pressBack
27+
import io.element.android.tests.testutils.pressTag
1828
import org.junit.Rule
1929
import org.junit.Test
2030
import org.junit.rules.TestRule
2131
import org.junit.runner.RunWith
32+
import org.robolectric.annotation.Config
2233

2334
@RunWith(AndroidJUnit4::class)
2435
class AccountDeactivationViewTest {
@@ -36,7 +47,96 @@ class AccountDeactivationViewTest {
3647
}
3748
}
3849

39-
// TODO Add more tests
50+
@Config(qualifiers = "h1024dp")
51+
@Test
52+
fun `clicking on Deactivate emits the expected Event`() {
53+
val eventsRecorder = EventsRecorder<AccountDeactivationEvents>()
54+
rule.setAccountDeactivationView(
55+
state = anAccountDeactivationState(
56+
deactivateFormState = aDeactivateFormState(
57+
password = A_PASSWORD,
58+
),
59+
eventSink = eventsRecorder,
60+
),
61+
)
62+
rule.clickOn(CommonStrings.action_deactivate)
63+
eventsRecorder.assertSingle(AccountDeactivationEvents.DeactivateAccount(false))
64+
}
65+
66+
@Test
67+
fun `clicking on Deactivate on the confirmation dialog emits the expected Event`() {
68+
val eventsRecorder = EventsRecorder<AccountDeactivationEvents>()
69+
rule.setAccountDeactivationView(
70+
state = anAccountDeactivationState(
71+
deactivateFormState = aDeactivateFormState(
72+
password = A_PASSWORD,
73+
),
74+
accountDeactivationAction = AsyncAction.Confirming,
75+
eventSink = eventsRecorder,
76+
),
77+
)
78+
rule.pressTag(TestTags.dialogPositive.value)
79+
eventsRecorder.assertSingle(AccountDeactivationEvents.DeactivateAccount(false))
80+
}
81+
82+
@Test
83+
fun `clicking on retry on the confirmation dialog emits the expected Event`() {
84+
val eventsRecorder = EventsRecorder<AccountDeactivationEvents>()
85+
rule.setAccountDeactivationView(
86+
state = anAccountDeactivationState(
87+
deactivateFormState = aDeactivateFormState(
88+
password = A_PASSWORD,
89+
),
90+
accountDeactivationAction = AsyncAction.Failure(AN_EXCEPTION),
91+
eventSink = eventsRecorder,
92+
),
93+
)
94+
rule.clickOn(CommonStrings.action_retry)
95+
eventsRecorder.assertSingle(AccountDeactivationEvents.DeactivateAccount(true))
96+
}
97+
98+
@Test
99+
fun `switching on the erase all switch emits the expected Event`() {
100+
val eventsRecorder = EventsRecorder<AccountDeactivationEvents>()
101+
rule.setAccountDeactivationView(
102+
state = anAccountDeactivationState(
103+
eventSink = eventsRecorder,
104+
),
105+
)
106+
rule.clickOn(R.string.screen_deactivate_account_delete_all_messages)
107+
eventsRecorder.assertSingle(AccountDeactivationEvents.SetEraseData(true))
108+
}
109+
110+
@Test
111+
fun `switching off the erase all switch emits the expected Event`() {
112+
val eventsRecorder = EventsRecorder<AccountDeactivationEvents>()
113+
rule.setAccountDeactivationView(
114+
state = anAccountDeactivationState(
115+
deactivateFormState = aDeactivateFormState(
116+
eraseData = true,
117+
),
118+
eventSink = eventsRecorder,
119+
),
120+
)
121+
rule.clickOn(R.string.screen_deactivate_account_delete_all_messages)
122+
eventsRecorder.assertSingle(AccountDeactivationEvents.SetEraseData(false))
123+
}
124+
125+
@Config(qualifiers = "h1024dp")
126+
@Test
127+
fun `typing text in the password field emits the expected Event`() {
128+
val eventsRecorder = EventsRecorder<AccountDeactivationEvents>()
129+
rule.setAccountDeactivationView(
130+
state = anAccountDeactivationState(
131+
deactivateFormState = aDeactivateFormState(
132+
password = A_PASSWORD,
133+
),
134+
eventSink = eventsRecorder,
135+
),
136+
)
137+
rule.onNodeWithTag(TestTags.loginPassword.value).performTextInput("A")
138+
eventsRecorder.assertSingle(AccountDeactivationEvents.SetPassword("A$A_PASSWORD"))
139+
}
40140
}
41141

42142
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setAccountDeactivationView(

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,8 @@ private fun VerifiedUserSendFailureView(
374374
fun VerifiedUserSendFailure.headline(): String {
375375
return when (this) {
376376
is None -> ""
377-
is UnsignedDevice -> stringResource(CommonStrings.screen_timeline_item_menu_send_failure_unsigned_device, userDisplayName)
377+
is UnsignedDevice.FromOther -> stringResource(CommonStrings.screen_timeline_item_menu_send_failure_unsigned_device, userDisplayName)
378+
is UnsignedDevice.FromYou -> stringResource(CommonStrings.screen_timeline_item_menu_send_failure_you_unsigned_device)
378379
is ChangedIdentity -> stringResource(CommonStrings.screen_timeline_item_menu_send_failure_changed_identity, userDisplayName)
379380
}
380381
}

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/VerifiedUserSendFailure.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ import androidx.compose.runtime.Immutable
1313
sealed interface VerifiedUserSendFailure {
1414
data object None : VerifiedUserSendFailure
1515

16-
data class UnsignedDevice(
17-
val userDisplayName: String,
18-
) : VerifiedUserSendFailure
16+
sealed interface UnsignedDevice : VerifiedUserSendFailure {
17+
data object FromYou : UnsignedDevice
18+
data class FromOther(val userDisplayName: String) : UnsignedDevice
19+
}
1920

2021
data class ChangedIdentity(
2122
val userDisplayName: String,

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/VerifiedUserSendFailureFactory.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,12 @@ class VerifiedUserSendFailureFactory @Inject constructor(
2323
if (userId == null) {
2424
VerifiedUserSendFailure.None
2525
} else {
26-
val displayName = room.userDisplayName(userId).getOrNull() ?: userId.value
27-
VerifiedUserSendFailure.UnsignedDevice(displayName)
26+
if (userId == room.sessionId) {
27+
VerifiedUserSendFailure.UnsignedDevice.FromYou
28+
} else {
29+
val displayName = room.userDisplayName(userId).getOrNull() ?: userId.value
30+
VerifiedUserSendFailure.UnsignedDevice.FromOther(displayName)
31+
}
2832
}
2933
}
3034
is LocalEventSendState.Failed.VerifiedUserChangedIdentity -> {

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureStateProvider.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ fun aResolveVerifiedUserSendFailureState(
3636
eventSink = eventSink
3737
)
3838

39-
fun anUnsignedDeviceSendFailure(userDisplayName: String = "Alice") = VerifiedUserSendFailure.UnsignedDevice(
39+
fun anUnsignedDeviceSendFailure(userDisplayName: String = "Alice") = VerifiedUserSendFailure.UnsignedDevice.FromOther(
4040
userDisplayName = userDisplayName,
4141
)
4242

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/sendfailure/resolve/ResolveVerifiedUserSendFailureView.kt

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,28 +113,33 @@ fun ResolveVerifiedUserSendFailureView(
113113
@Composable
114114
private fun VerifiedUserSendFailure.title(): String {
115115
return when (this) {
116-
is VerifiedUserSendFailure.UnsignedDevice -> stringResource(id = CommonStrings.screen_resolve_send_failure_unsigned_device_title, userDisplayName)
116+
is VerifiedUserSendFailure.UnsignedDevice.FromOther -> stringResource(
117+
id = CommonStrings.screen_resolve_send_failure_unsigned_device_title,
118+
userDisplayName
119+
)
120+
VerifiedUserSendFailure.UnsignedDevice.FromYou -> stringResource(id = CommonStrings.screen_resolve_send_failure_you_unsigned_device_title)
117121
is VerifiedUserSendFailure.ChangedIdentity -> stringResource(
118122
id = CommonStrings.screen_resolve_send_failure_changed_identity_title,
119123
userDisplayName
120124
)
121-
VerifiedUserSendFailure.None -> error("This method should never be called for this state")
125+
VerifiedUserSendFailure.None -> ""
122126
}
123127
}
124128

125129
@Composable
126130
private fun VerifiedUserSendFailure.subtitle(): String {
127131
return when (this) {
128-
is VerifiedUserSendFailure.UnsignedDevice -> stringResource(
132+
is VerifiedUserSendFailure.UnsignedDevice.FromOther -> stringResource(
129133
id = CommonStrings.screen_resolve_send_failure_unsigned_device_subtitle,
130134
userDisplayName,
131135
userDisplayName,
132136
)
137+
VerifiedUserSendFailure.UnsignedDevice.FromYou -> stringResource(id = CommonStrings.screen_resolve_send_failure_you_unsigned_device_subtitle)
133138
is VerifiedUserSendFailure.ChangedIdentity -> stringResource(
134139
id = CommonStrings.screen_resolve_send_failure_changed_identity_subtitle,
135140
userDisplayName
136141
)
137-
VerifiedUserSendFailure.None -> error("This method should never be called for this state")
142+
VerifiedUserSendFailure.None -> ""
138143
}
139144
}
140145

@@ -143,7 +148,7 @@ private fun VerifiedUserSendFailure.resolveAction(): String {
143148
return when (this) {
144149
is VerifiedUserSendFailure.UnsignedDevice -> stringResource(id = CommonStrings.screen_resolve_send_failure_unsigned_device_primary_button_title)
145150
is VerifiedUserSendFailure.ChangedIdentity -> stringResource(id = CommonStrings.screen_resolve_send_failure_changed_identity_primary_button_title)
146-
VerifiedUserSendFailure.None -> error("This method should never be called for this state")
151+
VerifiedUserSendFailure.None -> ""
147152
}
148153
}
149154

0 commit comments

Comments
 (0)