Merge pull request #2413 from element-hq/feature/bma/unitTests
Add more unit tests
This commit is contained in:
commit
afec6370c1
18 changed files with 463 additions and 92 deletions
|
|
@ -22,6 +22,11 @@ plugins {
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "io.element.android.features.location.impl"
|
namespace = "io.element.android.features.location.impl"
|
||||||
|
testOptions {
|
||||||
|
unitTests {
|
||||||
|
isIncludeAndroidResources = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
anvil {
|
anvil {
|
||||||
|
|
@ -51,11 +56,14 @@ dependencies {
|
||||||
testImplementation(libs.test.junit)
|
testImplementation(libs.test.junit)
|
||||||
testImplementation(libs.coroutines.test)
|
testImplementation(libs.coroutines.test)
|
||||||
testImplementation(libs.molecule.runtime)
|
testImplementation(libs.molecule.runtime)
|
||||||
|
testImplementation(libs.test.robolectric)
|
||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
testImplementation(libs.test.turbine)
|
testImplementation(libs.test.turbine)
|
||||||
testImplementation(libs.test.truth)
|
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
|
testImplementation(projects.libraries.testtags)
|
||||||
testImplementation(projects.services.analytics.test)
|
testImplementation(projects.services.analytics.test)
|
||||||
testImplementation(projects.features.messages.test)
|
testImplementation(projects.features.messages.test)
|
||||||
testImplementation(projects.tests.testutils)
|
testImplementation(projects.tests.testutils)
|
||||||
|
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||||
|
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,78 +24,47 @@ private const val APP_NAME = "ApplicationName"
|
||||||
class ShowLocationStateProvider : PreviewParameterProvider<ShowLocationState> {
|
class ShowLocationStateProvider : PreviewParameterProvider<ShowLocationState> {
|
||||||
override val values: Sequence<ShowLocationState>
|
override val values: Sequence<ShowLocationState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
ShowLocationState(
|
aShowLocationState(),
|
||||||
ShowLocationState.Dialog.None,
|
aShowLocationState(
|
||||||
Location(1.23, 2.34, 4f),
|
permissionDialog = ShowLocationState.Dialog.PermissionDenied,
|
||||||
description = null,
|
|
||||||
hasLocationPermission = false,
|
|
||||||
isTrackMyLocation = false,
|
|
||||||
appName = APP_NAME,
|
|
||||||
eventSink = {},
|
|
||||||
),
|
),
|
||||||
ShowLocationState(
|
aShowLocationState(
|
||||||
ShowLocationState.Dialog.PermissionDenied,
|
permissionDialog = ShowLocationState.Dialog.PermissionRationale,
|
||||||
Location(1.23, 2.34, 4f),
|
|
||||||
description = null,
|
|
||||||
hasLocationPermission = false,
|
|
||||||
isTrackMyLocation = false,
|
|
||||||
appName = APP_NAME,
|
|
||||||
eventSink = {},
|
|
||||||
),
|
),
|
||||||
ShowLocationState(
|
aShowLocationState(
|
||||||
ShowLocationState.Dialog.PermissionRationale,
|
|
||||||
Location(1.23, 2.34, 4f),
|
|
||||||
description = null,
|
|
||||||
hasLocationPermission = false,
|
|
||||||
isTrackMyLocation = false,
|
|
||||||
appName = APP_NAME,
|
|
||||||
eventSink = {},
|
|
||||||
),
|
|
||||||
ShowLocationState(
|
|
||||||
ShowLocationState.Dialog.None,
|
|
||||||
Location(1.23, 2.34, 4f),
|
|
||||||
description = null,
|
|
||||||
hasLocationPermission = true,
|
hasLocationPermission = true,
|
||||||
isTrackMyLocation = false,
|
|
||||||
appName = APP_NAME,
|
|
||||||
eventSink = {},
|
|
||||||
),
|
),
|
||||||
ShowLocationState(
|
aShowLocationState(
|
||||||
ShowLocationState.Dialog.None,
|
|
||||||
Location(1.23, 2.34, 4f),
|
|
||||||
description = null,
|
|
||||||
hasLocationPermission = true,
|
hasLocationPermission = true,
|
||||||
isTrackMyLocation = true,
|
isTrackMyLocation = true,
|
||||||
appName = APP_NAME,
|
|
||||||
eventSink = {},
|
|
||||||
),
|
),
|
||||||
ShowLocationState(
|
aShowLocationState(
|
||||||
ShowLocationState.Dialog.None,
|
|
||||||
Location(1.23, 2.34, 4f),
|
|
||||||
description = "My favourite place!",
|
description = "My favourite place!",
|
||||||
hasLocationPermission = false,
|
|
||||||
isTrackMyLocation = false,
|
|
||||||
appName = APP_NAME,
|
|
||||||
eventSink = {},
|
|
||||||
),
|
),
|
||||||
ShowLocationState(
|
aShowLocationState(
|
||||||
ShowLocationState.Dialog.None,
|
|
||||||
Location(1.23, 2.34, 4f),
|
|
||||||
description = "For some reason I decided to to write a small essay that wraps at just two lines!",
|
description = "For some reason I decided to to write a small essay that wraps at just two lines!",
|
||||||
hasLocationPermission = false,
|
|
||||||
isTrackMyLocation = false,
|
|
||||||
appName = APP_NAME,
|
|
||||||
eventSink = {},
|
|
||||||
),
|
),
|
||||||
ShowLocationState(
|
aShowLocationState(
|
||||||
ShowLocationState.Dialog.None,
|
|
||||||
Location(1.23, 2.34, 4f),
|
|
||||||
description = "For some reason I decided to write a small essay in the location description. " +
|
description = "For some reason I decided to write a small essay in the location description. " +
|
||||||
"It is so long that it will wrap onto more than two lines!",
|
"It is so long that it will wrap onto more than two lines!",
|
||||||
hasLocationPermission = false,
|
|
||||||
isTrackMyLocation = false,
|
|
||||||
appName = APP_NAME,
|
|
||||||
eventSink = {},
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun aShowLocationState(
|
||||||
|
permissionDialog: ShowLocationState.Dialog = ShowLocationState.Dialog.None,
|
||||||
|
location: Location = Location(1.23, 2.34, 4f),
|
||||||
|
description: String? = null,
|
||||||
|
hasLocationPermission: Boolean = false,
|
||||||
|
isTrackMyLocation: Boolean = false,
|
||||||
|
appName: String = APP_NAME,
|
||||||
|
eventSink: (ShowLocationEvents) -> Unit = {},
|
||||||
|
) = ShowLocationState(
|
||||||
|
permissionDialog = permissionDialog,
|
||||||
|
location = location,
|
||||||
|
description = description,
|
||||||
|
hasLocationPermission = hasLocationPermission,
|
||||||
|
isTrackMyLocation = isTrackMyLocation,
|
||||||
|
appName = appName,
|
||||||
|
eventSink = eventSink,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -120,10 +120,14 @@ fun ShowLocationView(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
BackButton(onClick = onBackPressed)
|
BackButton(
|
||||||
|
onClick = onBackPressed,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = { state.eventSink(ShowLocationEvents.Share) }) {
|
IconButton(
|
||||||
|
onClick = { state.eventSink(ShowLocationEvents.Share) }
|
||||||
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = CompoundIcons.ShareAndroid(),
|
imageVector = CompoundIcons.ShareAndroid(),
|
||||||
contentDescription = stringResource(CommonStrings.action_share),
|
contentDescription = stringResource(CommonStrings.action_share),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
/*
|
||||||
|
* 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.location.impl.show
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.ui.platform.LocalInspectionMode
|
||||||
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import io.element.android.libraries.testtags.TestTags
|
||||||
|
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.pressBack
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.rules.TestRule
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class ShowLocationViewTest {
|
||||||
|
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test back action`() {
|
||||||
|
val eventsRecorder = EventsRecorder<ShowLocationEvents>(expectEvents = false)
|
||||||
|
ensureCalledOnce { callback ->
|
||||||
|
rule.setShowLocationView(
|
||||||
|
state = aShowLocationState(
|
||||||
|
eventSink = eventsRecorder
|
||||||
|
),
|
||||||
|
onBackPressed = callback,
|
||||||
|
)
|
||||||
|
rule.pressBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test share action`() {
|
||||||
|
val eventsRecorder = EventsRecorder<ShowLocationEvents>()
|
||||||
|
rule.setShowLocationView(
|
||||||
|
aShowLocationState(
|
||||||
|
eventSink = eventsRecorder
|
||||||
|
),
|
||||||
|
onBackPressed = EnsureNeverCalled(),
|
||||||
|
)
|
||||||
|
val shareContentDescription = rule.activity.getString(CommonStrings.action_share)
|
||||||
|
rule.onNodeWithContentDescription(shareContentDescription).performClick()
|
||||||
|
eventsRecorder.assertSingle(ShowLocationEvents.Share)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test fab click`() {
|
||||||
|
val eventsRecorder = EventsRecorder<ShowLocationEvents>()
|
||||||
|
rule.setShowLocationView(
|
||||||
|
aShowLocationState(
|
||||||
|
eventSink = eventsRecorder
|
||||||
|
),
|
||||||
|
onBackPressed = EnsureNeverCalled(),
|
||||||
|
)
|
||||||
|
val shareContentDescription = rule.activity.getString(CommonStrings.action_share)
|
||||||
|
rule.onNodeWithTag(TestTags.floatingActionButton.value).performClick()
|
||||||
|
eventsRecorder.assertSingle(ShowLocationEvents.TrackMyLocation(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when permission denied is displayed user can open the settings`() {
|
||||||
|
val eventsRecorder = EventsRecorder<ShowLocationEvents>()
|
||||||
|
rule.setShowLocationView(
|
||||||
|
aShowLocationState(
|
||||||
|
permissionDialog = ShowLocationState.Dialog.PermissionDenied,
|
||||||
|
eventSink = eventsRecorder
|
||||||
|
),
|
||||||
|
onBackPressed = EnsureNeverCalled(),
|
||||||
|
)
|
||||||
|
rule.clickOn(CommonStrings.action_continue)
|
||||||
|
eventsRecorder.assertSingle(ShowLocationEvents.OpenAppSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when permission denied is displayed user can close the dialog`() {
|
||||||
|
val eventsRecorder = EventsRecorder<ShowLocationEvents>()
|
||||||
|
rule.setShowLocationView(
|
||||||
|
aShowLocationState(
|
||||||
|
permissionDialog = ShowLocationState.Dialog.PermissionDenied,
|
||||||
|
eventSink = eventsRecorder
|
||||||
|
),
|
||||||
|
onBackPressed = EnsureNeverCalled(),
|
||||||
|
)
|
||||||
|
rule.clickOn(CommonStrings.action_cancel)
|
||||||
|
eventsRecorder.assertSingle(ShowLocationEvents.DismissDialog)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when permission rationale is displayed user can request permissions`() {
|
||||||
|
val eventsRecorder = EventsRecorder<ShowLocationEvents>()
|
||||||
|
rule.setShowLocationView(
|
||||||
|
aShowLocationState(
|
||||||
|
permissionDialog = ShowLocationState.Dialog.PermissionRationale,
|
||||||
|
eventSink = eventsRecorder
|
||||||
|
),
|
||||||
|
onBackPressed = EnsureNeverCalled(),
|
||||||
|
)
|
||||||
|
rule.clickOn(CommonStrings.action_continue)
|
||||||
|
eventsRecorder.assertSingle(ShowLocationEvents.RequestPermissions)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when permission rationale is displayed user can close the dialog`() {
|
||||||
|
val eventsRecorder = EventsRecorder<ShowLocationEvents>()
|
||||||
|
rule.setShowLocationView(
|
||||||
|
aShowLocationState(
|
||||||
|
permissionDialog = ShowLocationState.Dialog.PermissionRationale,
|
||||||
|
eventSink = eventsRecorder
|
||||||
|
),
|
||||||
|
onBackPressed = EnsureNeverCalled(),
|
||||||
|
)
|
||||||
|
rule.clickOn(CommonStrings.action_cancel)
|
||||||
|
eventsRecorder.assertSingle(ShowLocationEvents.DismissDialog)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setShowLocationView(
|
||||||
|
state: ShowLocationState,
|
||||||
|
onBackPressed: () -> Unit = EnsureNeverCalled(),
|
||||||
|
) {
|
||||||
|
setContent {
|
||||||
|
// Simulate a LocalInspectionMode for MapboxMap
|
||||||
|
CompositionLocalProvider(LocalInspectionMode provides true) {
|
||||||
|
ShowLocationView(
|
||||||
|
state = state,
|
||||||
|
onBackPressed = onBackPressed,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -49,6 +49,7 @@ import io.element.android.features.messages.impl.voicemessages.timeline.FakeReda
|
||||||
import io.element.android.features.messages.test.FakeMessageComposerContext
|
import io.element.android.features.messages.test.FakeMessageComposerContext
|
||||||
import io.element.android.features.messages.test.timeline.FakeHtmlConverterProvider
|
import io.element.android.features.messages.test.timeline.FakeHtmlConverterProvider
|
||||||
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
||||||
|
import io.element.android.features.poll.api.actions.EndPollAction
|
||||||
import io.element.android.features.poll.test.actions.FakeEndPollAction
|
import io.element.android.features.poll.test.actions.FakeEndPollAction
|
||||||
import io.element.android.features.poll.test.actions.FakeSendPollResponseAction
|
import io.element.android.features.poll.test.actions.FakeSendPollResponseAction
|
||||||
import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper
|
import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper
|
||||||
|
|
@ -98,6 +99,7 @@ import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
import kotlinx.coroutines.test.runCurrent
|
import kotlinx.coroutines.test.runCurrent
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -372,6 +374,22 @@ class MessagesPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - handle action end poll`() = runTest {
|
||||||
|
val endPollAction = FakeEndPollAction()
|
||||||
|
val presenter = createMessagesPresenter(endPollAction = endPollAction)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitFirstItem()
|
||||||
|
endPollAction.verifyExecutionCount(0)
|
||||||
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.EndPoll, aMessageEvent(content = aTimelineItemPollContent())))
|
||||||
|
delay(1)
|
||||||
|
endPollAction.verifyExecutionCount(1)
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - handle action redact`() = runTest {
|
fun `present - handle action redact`() = runTest {
|
||||||
val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true)
|
val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true)
|
||||||
|
|
@ -683,6 +701,7 @@ class MessagesPresenterTest {
|
||||||
clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(),
|
clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(),
|
||||||
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
||||||
permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(),
|
permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(),
|
||||||
|
endPollAction: EndPollAction = FakeEndPollAction(),
|
||||||
): MessagesPresenter {
|
): MessagesPresenter {
|
||||||
val mediaSender = MediaSender(FakeMediaPreProcessor(), matrixRoom)
|
val mediaSender = MediaSender(FakeMediaPreProcessor(), matrixRoom)
|
||||||
val permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter)
|
val permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter)
|
||||||
|
|
@ -721,7 +740,7 @@ class MessagesPresenterTest {
|
||||||
encryptionService = FakeEncryptionService(),
|
encryptionService = FakeEncryptionService(),
|
||||||
verificationService = FakeSessionVerificationService(),
|
verificationService = FakeSessionVerificationService(),
|
||||||
redactedVoiceMessageManager = FakeRedactedVoiceMessageManager(),
|
redactedVoiceMessageManager = FakeRedactedVoiceMessageManager(),
|
||||||
endPollAction = FakeEndPollAction(),
|
endPollAction = endPollAction,
|
||||||
sendPollResponseAction = FakeSendPollResponseAction(),
|
sendPollResponseAction = FakeSendPollResponseAction(),
|
||||||
sessionPreferencesStore = sessionPreferencesStore,
|
sessionPreferencesStore = sessionPreferencesStore,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -87,10 +87,13 @@ import kotlinx.coroutines.test.advanceUntilIdle
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
import uniffi.wysiwyg_composer.MentionsState
|
import uniffi.wysiwyg_composer.MentionsState
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@Suppress("LargeClass")
|
@Suppress("LargeClass")
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
class MessageComposerPresenterTest {
|
class MessageComposerPresenterTest {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|
@ -875,6 +878,19 @@ class MessageComposerPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - send uri`() = runTest {
|
||||||
|
val presenter = createPresenter(this)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
val state = presenter.present()
|
||||||
|
remember(state, state.richTextEditorState.messageHtml) { state }
|
||||||
|
}.test {
|
||||||
|
val initialState = awaitFirstItem()
|
||||||
|
initialState.eventSink.invoke(MessageComposerEvents.SendUri(Uri.parse("content://uri")))
|
||||||
|
waitForPredicate { mediaPreProcessor.processCallCount == 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - handle typing notice event`() = runTest {
|
fun `present - handle typing notice event`() = runTest {
|
||||||
val room = FakeMatrixRoom()
|
val room = FakeMatrixRoom()
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,11 @@ plugins {
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "io.element.android.features.roomdetails.impl"
|
namespace = "io.element.android.features.roomdetails.impl"
|
||||||
|
testOptions {
|
||||||
|
unitTests {
|
||||||
|
isIncludeAndroidResources = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
anvil {
|
anvil {
|
||||||
|
|
@ -61,6 +66,7 @@ dependencies {
|
||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
testImplementation(libs.test.turbine)
|
testImplementation(libs.test.turbine)
|
||||||
testImplementation(libs.test.mockk)
|
testImplementation(libs.test.mockk)
|
||||||
|
testImplementation(libs.test.robolectric)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
testImplementation(projects.libraries.mediaupload.test)
|
testImplementation(projects.libraries.mediaupload.test)
|
||||||
testImplementation(projects.libraries.mediapickers.test)
|
testImplementation(projects.libraries.mediapickers.test)
|
||||||
|
|
@ -70,6 +76,8 @@ dependencies {
|
||||||
testImplementation(projects.tests.testutils)
|
testImplementation(projects.tests.testutils)
|
||||||
testImplementation(projects.features.leaveroom.test)
|
testImplementation(projects.features.leaveroom.test)
|
||||||
testImplementation(projects.features.createroom.test)
|
testImplementation(projects.features.createroom.test)
|
||||||
|
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||||
|
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
||||||
|
|
||||||
ksp(libs.showkase.processor)
|
ksp(libs.showkase.processor)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,10 @@ fun BlockUserDialogs(state: RoomMemberDetailsState) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun BlockConfirmationDialog(onBlockAction: () -> Unit, onDismiss: () -> Unit) {
|
private fun BlockConfirmationDialog(
|
||||||
|
onBlockAction: () -> Unit,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
) {
|
||||||
ConfirmationDialog(
|
ConfirmationDialog(
|
||||||
title = stringResource(R.string.screen_dm_details_block_user),
|
title = stringResource(R.string.screen_dm_details_block_user),
|
||||||
content = stringResource(R.string.screen_dm_details_block_alert_description),
|
content = stringResource(R.string.screen_dm_details_block_alert_description),
|
||||||
|
|
@ -66,7 +69,10 @@ private fun BlockConfirmationDialog(onBlockAction: () -> Unit, onDismiss: () ->
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun UnblockConfirmationDialog(onUnblockAction: () -> Unit, onDismiss: () -> Unit) {
|
private fun UnblockConfirmationDialog(
|
||||||
|
onUnblockAction: () -> Unit,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
) {
|
||||||
ConfirmationDialog(
|
ConfirmationDialog(
|
||||||
title = stringResource(R.string.screen_dm_details_unblock_user),
|
title = stringResource(R.string.screen_dm_details_unblock_user),
|
||||||
content = stringResource(R.string.screen_dm_details_unblock_alert_description),
|
content = stringResource(R.string.screen_dm_details_unblock_alert_description),
|
||||||
|
|
|
||||||
|
|
@ -19,28 +19,38 @@ package io.element.android.features.roomdetails.impl.members.details
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
open class RoomMemberDetailsStateProvider : PreviewParameterProvider<RoomMemberDetailsState> {
|
open class RoomMemberDetailsStateProvider : PreviewParameterProvider<RoomMemberDetailsState> {
|
||||||
override val values: Sequence<RoomMemberDetailsState>
|
override val values: Sequence<RoomMemberDetailsState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aRoomMemberDetailsState(),
|
aRoomMemberDetailsState(),
|
||||||
aRoomMemberDetailsState().copy(userName = null),
|
aRoomMemberDetailsState(userName = null),
|
||||||
aRoomMemberDetailsState().copy(isBlocked = AsyncData.Success(true)),
|
aRoomMemberDetailsState(isBlocked = AsyncData.Success(true)),
|
||||||
aRoomMemberDetailsState().copy(displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Block),
|
aRoomMemberDetailsState(displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Block),
|
||||||
aRoomMemberDetailsState().copy(displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Unblock),
|
aRoomMemberDetailsState(displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Unblock),
|
||||||
aRoomMemberDetailsState().copy(isBlocked = AsyncData.Loading(true)),
|
aRoomMemberDetailsState(isBlocked = AsyncData.Loading(true)),
|
||||||
aRoomMemberDetailsState().copy(startDmActionState = AsyncAction.Loading),
|
aRoomMemberDetailsState(startDmActionState = AsyncAction.Loading),
|
||||||
// Add other states here
|
// Add other states here
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aRoomMemberDetailsState() = RoomMemberDetailsState(
|
fun aRoomMemberDetailsState(
|
||||||
userId = "@daniel:domain.com",
|
userId: String = "@daniel:domain.com",
|
||||||
userName = "Daniel",
|
userName: String? = "Daniel",
|
||||||
avatarUrl = null,
|
avatarUrl: String? = null,
|
||||||
isBlocked = AsyncData.Success(false),
|
isBlocked: AsyncData<Boolean> = AsyncData.Success(false),
|
||||||
startDmActionState = AsyncAction.Uninitialized,
|
startDmActionState: AsyncAction<RoomId> = AsyncAction.Uninitialized,
|
||||||
displayConfirmationDialog = null,
|
displayConfirmationDialog: RoomMemberDetailsState.ConfirmationDialog? = null,
|
||||||
isCurrentUser = false,
|
isCurrentUser: Boolean = false,
|
||||||
eventSink = {},
|
eventSink: (RoomMemberDetailsEvents) -> Unit = {},
|
||||||
|
) = RoomMemberDetailsState(
|
||||||
|
userId = userId,
|
||||||
|
userName = userName,
|
||||||
|
avatarUrl = avatarUrl,
|
||||||
|
isBlocked = isBlocked,
|
||||||
|
startDmActionState = startDmActionState,
|
||||||
|
displayConfirmationDialog = displayConfirmationDialog,
|
||||||
|
isCurrentUser = isCurrentUser,
|
||||||
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
* 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.roomdetails.impl.blockuser
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import io.element.android.features.roomdetails.impl.R
|
||||||
|
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsEvents
|
||||||
|
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState
|
||||||
|
import io.element.android.features.roomdetails.impl.members.details.aRoomMemberDetailsState
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
|
import io.element.android.tests.testutils.clickOn
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class BlockUserDialogsTest {
|
||||||
|
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `confirm block user emit expected Event`() {
|
||||||
|
val eventsRecorder = EventsRecorder<RoomMemberDetailsEvents>()
|
||||||
|
rule.setContent {
|
||||||
|
BlockUserDialogs(
|
||||||
|
state = aRoomMemberDetailsState(
|
||||||
|
displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Block,
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
rule.clickOn(R.string.screen_dm_details_block_alert_action)
|
||||||
|
eventsRecorder.assertSingle(RoomMemberDetailsEvents.BlockUser(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `cancel block user emit expected Event`() {
|
||||||
|
val eventsRecorder = EventsRecorder<RoomMemberDetailsEvents>()
|
||||||
|
rule.setContent {
|
||||||
|
BlockUserDialogs(
|
||||||
|
state = aRoomMemberDetailsState(
|
||||||
|
displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Block,
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
rule.clickOn(CommonStrings.action_cancel)
|
||||||
|
eventsRecorder.assertSingle(RoomMemberDetailsEvents.ClearConfirmationDialog)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `confirm unblock user emit expected Event`() {
|
||||||
|
val eventsRecorder = EventsRecorder<RoomMemberDetailsEvents>()
|
||||||
|
rule.setContent {
|
||||||
|
BlockUserDialogs(
|
||||||
|
state = aRoomMemberDetailsState(
|
||||||
|
displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Unblock,
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
rule.clickOn(R.string.screen_dm_details_unblock_alert_action)
|
||||||
|
eventsRecorder.assertSingle(RoomMemberDetailsEvents.UnblockUser(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `cancel unblock user emit expected Event`() {
|
||||||
|
val eventsRecorder = EventsRecorder<RoomMemberDetailsEvents>()
|
||||||
|
rule.setContent {
|
||||||
|
BlockUserDialogs(
|
||||||
|
state = aRoomMemberDetailsState(
|
||||||
|
displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Unblock,
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
rule.clickOn(CommonStrings.action_cancel)
|
||||||
|
eventsRecorder.assertSingle(RoomMemberDetailsEvents.ClearConfirmationDialog)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -75,13 +75,14 @@ class RoomListPresenter @Inject constructor(
|
||||||
private val inviteStateDataSource: InviteStateDataSource,
|
private val inviteStateDataSource: InviteStateDataSource,
|
||||||
private val leaveRoomPresenter: LeaveRoomPresenter,
|
private val leaveRoomPresenter: LeaveRoomPresenter,
|
||||||
private val roomListDataSource: RoomListDataSource,
|
private val roomListDataSource: RoomListDataSource,
|
||||||
private val encryptionService: EncryptionService,
|
|
||||||
private val featureFlagService: FeatureFlagService,
|
private val featureFlagService: FeatureFlagService,
|
||||||
private val indicatorService: IndicatorService,
|
private val indicatorService: IndicatorService,
|
||||||
private val searchPresenter: Presenter<RoomListSearchState>,
|
private val searchPresenter: Presenter<RoomListSearchState>,
|
||||||
private val migrationScreenPresenter: MigrationScreenPresenter,
|
private val migrationScreenPresenter: MigrationScreenPresenter,
|
||||||
private val sessionPreferencesStore: SessionPreferencesStore,
|
private val sessionPreferencesStore: SessionPreferencesStore,
|
||||||
) : Presenter<RoomListState> {
|
) : Presenter<RoomListState> {
|
||||||
|
private val encryptionService: EncryptionService = client.encryptionService()
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): RoomListState {
|
override fun present(): RoomListState {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
@ -139,7 +140,6 @@ class RoomListPresenter @Inject constructor(
|
||||||
contextMenu.value = RoomListState.ContextMenu.Hidden
|
contextMenu.value = RoomListState.ContextMenu.Hidden
|
||||||
}
|
}
|
||||||
is RoomListEvents.LeaveRoom -> leaveRoomState.eventSink(LeaveRoomEvent.ShowConfirmation(event.roomId))
|
is RoomListEvents.LeaveRoom -> leaveRoomState.eventSink(LeaveRoomEvent.ShowConfirmation(event.roomId))
|
||||||
|
|
||||||
is RoomListEvents.SetRoomIsFavorite -> coroutineScope.launch {
|
is RoomListEvents.SetRoomIsFavorite -> coroutineScope.launch {
|
||||||
client.getRoom(event.roomId)?.use { room ->
|
client.getRoom(event.roomId)?.use { room ->
|
||||||
room.setIsFavorite(event.isFavorite)
|
room.setIsFavorite(event.isFavorite)
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,10 @@ fun aRoomListSearchState(
|
||||||
isSearchActive: Boolean = false,
|
isSearchActive: Boolean = false,
|
||||||
query: String = "",
|
query: String = "",
|
||||||
results: ImmutableList<RoomListRoomSummary> = persistentListOf(),
|
results: ImmutableList<RoomListRoomSummary> = persistentListOf(),
|
||||||
|
eventSink: (RoomListSearchEvents) -> Unit = { },
|
||||||
) = RoomListSearchState(
|
) = RoomListSearchState(
|
||||||
isSearchActive = isSearchActive,
|
isSearchActive = isSearchActive,
|
||||||
query = query,
|
query = query,
|
||||||
results = results,
|
results = results,
|
||||||
eventSink = { },
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryF
|
||||||
import io.element.android.features.roomlist.impl.migration.InMemoryMigrationScreenStore
|
import io.element.android.features.roomlist.impl.migration.InMemoryMigrationScreenStore
|
||||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter
|
import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter
|
||||||
import io.element.android.features.roomlist.impl.model.createRoomListRoomSummary
|
import io.element.android.features.roomlist.impl.model.createRoomListRoomSummary
|
||||||
|
import io.element.android.features.roomlist.impl.search.RoomListSearchEvents
|
||||||
import io.element.android.features.roomlist.impl.search.RoomListSearchState
|
import io.element.android.features.roomlist.impl.search.RoomListSearchState
|
||||||
import io.element.android.features.roomlist.impl.search.aRoomListSearchState
|
import io.element.android.features.roomlist.impl.search.aRoomListSearchState
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
|
@ -42,13 +43,14 @@ import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampF
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||||
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
||||||
import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter
|
import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||||
import io.element.android.libraries.featureflag.test.InMemorySessionPreferencesStore
|
import io.element.android.libraries.featureflag.test.InMemorySessionPreferencesStore
|
||||||
import io.element.android.libraries.indicator.impl.DefaultIndicatorService
|
import io.element.android.libraries.indicator.impl.DefaultIndicatorService
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.encryption.BackupState
|
import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||||
|
|
@ -68,6 +70,7 @@ import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled
|
import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled
|
||||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
||||||
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
||||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
|
|
@ -108,9 +111,12 @@ class RoomListPresenterTests {
|
||||||
fun `present - show avatar indicator`() = runTest {
|
fun `present - show avatar indicator`() = runTest {
|
||||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||||
val encryptionService = FakeEncryptionService()
|
val encryptionService = FakeEncryptionService()
|
||||||
|
val matrixClient = FakeMatrixClient(
|
||||||
|
encryptionService = encryptionService,
|
||||||
|
)
|
||||||
val sessionVerificationService = FakeSessionVerificationService()
|
val sessionVerificationService = FakeSessionVerificationService()
|
||||||
val presenter = createRoomListPresenter(
|
val presenter = createRoomListPresenter(
|
||||||
encryptionService = encryptionService,
|
client = matrixClient,
|
||||||
sessionVerificationService = sessionVerificationService,
|
sessionVerificationService = sessionVerificationService,
|
||||||
coroutineScope = scope
|
coroutineScope = scope
|
||||||
)
|
)
|
||||||
|
|
@ -255,6 +261,33 @@ class RoomListPresenterTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - handle DismissRecoveryKeyPrompt`() = runTest {
|
||||||
|
val encryptionService = FakeEncryptionService()
|
||||||
|
val matrixClient = FakeMatrixClient(
|
||||||
|
encryptionService = encryptionService,
|
||||||
|
)
|
||||||
|
val scope = CoroutineScope(context = coroutineContext + SupervisorJob())
|
||||||
|
val presenter = createRoomListPresenter(
|
||||||
|
client = matrixClient,
|
||||||
|
coroutineScope = scope,
|
||||||
|
)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
skipItems(1)
|
||||||
|
val initialState = awaitItem()
|
||||||
|
assertThat(initialState.displayRecoveryKeyPrompt).isFalse()
|
||||||
|
encryptionService.emitRecoveryState(RecoveryState.INCOMPLETE)
|
||||||
|
val nextState = awaitItem()
|
||||||
|
assertThat(nextState.displayRecoveryKeyPrompt).isTrue()
|
||||||
|
nextState.eventSink(RoomListEvents.DismissRecoveryKeyPrompt)
|
||||||
|
val finalState = awaitItem()
|
||||||
|
assertThat(finalState.displayRecoveryKeyPrompt).isFalse()
|
||||||
|
scope.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - sets invite state`() = runTest {
|
fun `present - sets invite state`() = runTest {
|
||||||
val inviteStateFlow = MutableStateFlow(InvitesState.NoInvites)
|
val inviteStateFlow = MutableStateFlow(InvitesState.NoInvites)
|
||||||
|
|
@ -383,6 +416,40 @@ class RoomListPresenterTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - toggle search menu`() = runTest {
|
||||||
|
val eventRecorder = EventsRecorder<RoomListSearchEvents>()
|
||||||
|
val searchPresenter: Presenter<RoomListSearchState> = Presenter {
|
||||||
|
aRoomListSearchState(
|
||||||
|
eventSink = eventRecorder
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||||
|
val presenter = createRoomListPresenter(
|
||||||
|
coroutineScope = scope,
|
||||||
|
searchPresenter = searchPresenter,
|
||||||
|
)
|
||||||
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
presenter.present()
|
||||||
|
}.test {
|
||||||
|
skipItems(1)
|
||||||
|
val initialState = awaitItem()
|
||||||
|
eventRecorder.assertEmpty()
|
||||||
|
initialState.eventSink(RoomListEvents.ToggleSearchResults)
|
||||||
|
eventRecorder.assertSingle(
|
||||||
|
RoomListSearchEvents.ToggleSearchVisibility
|
||||||
|
)
|
||||||
|
initialState.eventSink(RoomListEvents.ToggleSearchResults)
|
||||||
|
eventRecorder.assertList(
|
||||||
|
listOf(
|
||||||
|
RoomListSearchEvents.ToggleSearchVisibility,
|
||||||
|
RoomListSearchEvents.ToggleSearchVisibility
|
||||||
|
)
|
||||||
|
)
|
||||||
|
scope.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - change in notification settings updates the summary for decorations`() = runTest {
|
fun `present - change in notification settings updates the summary for decorations`() = runTest {
|
||||||
val userDefinedMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
|
val userDefinedMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
|
||||||
|
|
@ -506,8 +573,8 @@ class RoomListPresenterTests {
|
||||||
givenFormat(A_FORMATTED_DATE)
|
givenFormat(A_FORMATTED_DATE)
|
||||||
},
|
},
|
||||||
roomLastMessageFormatter: RoomLastMessageFormatter = FakeRoomLastMessageFormatter(),
|
roomLastMessageFormatter: RoomLastMessageFormatter = FakeRoomLastMessageFormatter(),
|
||||||
encryptionService: EncryptionService = FakeEncryptionService(),
|
|
||||||
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),
|
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),
|
||||||
|
featureFlagService: FeatureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SecureStorage.key to true)),
|
||||||
coroutineScope: CoroutineScope,
|
coroutineScope: CoroutineScope,
|
||||||
migrationScreenPresenter: MigrationScreenPresenter = MigrationScreenPresenter(
|
migrationScreenPresenter: MigrationScreenPresenter = MigrationScreenPresenter(
|
||||||
matrixClient = client,
|
matrixClient = client,
|
||||||
|
|
@ -531,12 +598,11 @@ class RoomListPresenterTests {
|
||||||
notificationSettingsService = client.notificationSettingsService(),
|
notificationSettingsService = client.notificationSettingsService(),
|
||||||
appScope = coroutineScope
|
appScope = coroutineScope
|
||||||
),
|
),
|
||||||
encryptionService = encryptionService,
|
featureFlagService = featureFlagService,
|
||||||
featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SecureStorage.key to true)),
|
|
||||||
indicatorService = DefaultIndicatorService(
|
indicatorService = DefaultIndicatorService(
|
||||||
sessionVerificationService = sessionVerificationService,
|
sessionVerificationService = sessionVerificationService,
|
||||||
encryptionService = encryptionService,
|
encryptionService = client.encryptionService(),
|
||||||
featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SecureStorage.key to true)),
|
featureFlagService = featureFlagService,
|
||||||
),
|
),
|
||||||
migrationScreenPresenter = migrationScreenPresenter,
|
migrationScreenPresenter = migrationScreenPresenter,
|
||||||
searchPresenter = searchPresenter,
|
searchPresenter = searchPresenter,
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
||||||
|
import io.element.android.libraries.testtags.TestTags
|
||||||
|
import io.element.android.libraries.testtags.testTag
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FloatingActionButton(
|
fun FloatingActionButton(
|
||||||
|
|
@ -48,7 +50,7 @@ fun FloatingActionButton(
|
||||||
) {
|
) {
|
||||||
androidx.compose.material3.FloatingActionButton(
|
androidx.compose.material3.FloatingActionButton(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
modifier = modifier,
|
modifier = modifier.testTag(TestTags.floatingActionButton),
|
||||||
shape = shape,
|
shape = shape,
|
||||||
containerColor = containerColor,
|
containerColor = containerColor,
|
||||||
contentColor = contentColor,
|
contentColor = contentColor,
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,10 @@ class FakeEncryptionService : EncryptionService {
|
||||||
backupStateStateFlow.emit(state)
|
backupStateStateFlow.emit(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun emitRecoveryState(state: RecoveryState) {
|
||||||
|
recoveryStateStateFlow.emit(state)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun emitEnableRecoveryProgress(state: EnableRecoveryProgress) {
|
suspend fun emitEnableRecoveryProgress(state: EnableRecoveryProgress) {
|
||||||
enableRecoveryProgressStateFlow.emit(state)
|
enableRecoveryProgressStateFlow.emit(state)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,11 @@ object TestTags {
|
||||||
val dialogNegative = TestTag("dialog-negative")
|
val dialogNegative = TestTag("dialog-negative")
|
||||||
val dialogNeutral = TestTag("dialog-neutral")
|
val dialogNeutral = TestTag("dialog-neutral")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Floating Action Button.
|
||||||
|
*/
|
||||||
|
val floatingActionButton = TestTag("floating-action-button")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timeline item.
|
* Timeline item.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,8 @@ fun Project.setupKover() {
|
||||||
"*Presenter\$present\$*",
|
"*Presenter\$present\$*",
|
||||||
// Forked from compose
|
// Forked from compose
|
||||||
"io.element.android.libraries.designsystem.theme.components.bottomsheet.*",
|
"io.element.android.libraries.designsystem.theme.components.bottomsheet.*",
|
||||||
|
// Test presenter
|
||||||
|
"io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter",
|
||||||
)
|
)
|
||||||
annotatedBy(
|
annotatedBy(
|
||||||
"androidx.compose.ui.tooling.preview.Preview",
|
"androidx.compose.ui.tooling.preview.Preview",
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,6 @@ class RoomListScreen(
|
||||||
notificationSettingsService = matrixClient.notificationSettingsService(),
|
notificationSettingsService = matrixClient.notificationSettingsService(),
|
||||||
appScope = Singleton.appScope
|
appScope = Singleton.appScope
|
||||||
),
|
),
|
||||||
encryptionService = encryptionService,
|
|
||||||
indicatorService = DefaultIndicatorService(
|
indicatorService = DefaultIndicatorService(
|
||||||
sessionVerificationService = sessionVerificationService,
|
sessionVerificationService = sessionVerificationService,
|
||||||
encryptionService = encryptionService,
|
encryptionService = encryptionService,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue