Introduce PushHistoryService to store data about the received push (#4573)
* Introduce PushHistoryService to store data about the received push Add a push database. * Update screenshots * Improve preview. * Update screenshots * Add missing test. * Add test for PushHistoryView * Fix configuration issue. Was: w: /libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/history/PushHistoryPresenterTest.kt:35:27 Cannot access class 'PushProvider' in the expression type. While it may work, this case indicates a configuration mistake and can lead to avoidable compilation errors, so it may be forbidden soon. Check your module classpath for missing or conflicting dependencies. --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
parent
a0b619007a
commit
a1d8322738
60 changed files with 1656 additions and 214 deletions
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.troubleshoot.impl.history
|
||||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.troubleshoot.api.PushHistoryEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultPushHistoryEntryPoint @Inject constructor() : PushHistoryEntryPoint {
|
||||
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): PushHistoryEntryPoint.NodeBuilder {
|
||||
val plugins = ArrayList<Plugin>()
|
||||
|
||||
return object : PushHistoryEntryPoint.NodeBuilder {
|
||||
override fun callback(callback: PushHistoryEntryPoint.Callback): PushHistoryEntryPoint.NodeBuilder {
|
||||
plugins += callback
|
||||
return this
|
||||
}
|
||||
|
||||
override fun build(): Node {
|
||||
return parentNode.createNode<PushHistoryNode>(buildContext, plugins)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.troubleshoot.impl.history
|
||||
|
||||
sealed interface PushHistoryEvents {
|
||||
data object Reset : PushHistoryEvents
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.troubleshoot.impl.history
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import com.bumble.appyx.core.plugin.plugins
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.features.analytics.plan.MobileScreen
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.troubleshoot.api.PushHistoryEntryPoint
|
||||
import io.element.android.services.analytics.api.ScreenTracker
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
class PushHistoryNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val presenter: PushHistoryPresenter,
|
||||
private val screenTracker: ScreenTracker,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
private fun onDone() {
|
||||
plugins<PushHistoryEntryPoint.Callback>().forEach {
|
||||
it.onDone()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onItemClick(sessionId: SessionId, roomId: RoomId, eventId: EventId) {
|
||||
plugins<PushHistoryEntryPoint.Callback>().forEach {
|
||||
it.onItemClick(sessionId, roomId, eventId)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
screenTracker.TrackScreen(MobileScreen.ScreenName.NotificationTroubleshoot)
|
||||
val state = presenter.present()
|
||||
PushHistoryView(
|
||||
state = state,
|
||||
onBackClick = ::onDone,
|
||||
onItemClick = ::onItemClick,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.troubleshoot.impl.history
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.push.api.PushService
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class PushHistoryPresenter @Inject constructor(
|
||||
private val pushService: PushService,
|
||||
) : Presenter<PushHistoryState> {
|
||||
@Composable
|
||||
override fun present(): PushHistoryState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val pushCounter by pushService.pushCounter.collectAsState(0)
|
||||
val pushHistory by remember {
|
||||
pushService.getPushHistoryItemsFlow()
|
||||
}.collectAsState(emptyList())
|
||||
|
||||
fun handleEvents(event: PushHistoryEvents) {
|
||||
when (event) {
|
||||
PushHistoryEvents.Reset -> coroutineScope.launch {
|
||||
pushService.resetPushHistory()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PushHistoryState(
|
||||
pushCounter = pushCounter,
|
||||
pushHistoryItems = pushHistory.toImmutableList(),
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.troubleshoot.impl.history
|
||||
|
||||
import io.element.android.libraries.push.api.history.PushHistoryItem
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
data class PushHistoryState(
|
||||
val pushCounter: Int,
|
||||
val pushHistoryItems: ImmutableList<PushHistoryItem>,
|
||||
val eventSink: (PushHistoryEvents) -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.troubleshoot.impl.history
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.push.api.history.PushHistoryItem
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
open class PushHistoryStateProvider : PreviewParameterProvider<PushHistoryState> {
|
||||
override val values: Sequence<PushHistoryState>
|
||||
get() = sequenceOf(
|
||||
aPushHistoryState(),
|
||||
aPushHistoryState(
|
||||
pushCounter = 123,
|
||||
pushHistoryItems = listOf(
|
||||
aPushHistoryItem(
|
||||
hasBeenResolved = false,
|
||||
comment = "An error description"
|
||||
),
|
||||
aPushHistoryItem(
|
||||
pushDate = 1,
|
||||
providerInfo = "providerInfo2",
|
||||
eventId = EventId("\$anEventId"),
|
||||
roomId = RoomId("!roomId:domain"),
|
||||
sessionId = SessionId("@alice:server.org"),
|
||||
hasBeenResolved = true,
|
||||
comment = "A comment"
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun aPushHistoryState(
|
||||
pushCounter: Int = 0,
|
||||
pushHistoryItems: List<PushHistoryItem> = emptyList(),
|
||||
eventSink: (PushHistoryEvents) -> Unit = {},
|
||||
) = PushHistoryState(
|
||||
pushCounter = pushCounter,
|
||||
pushHistoryItems = pushHistoryItems.toImmutableList(),
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
fun aPushHistoryItem(
|
||||
pushDate: Long = 0,
|
||||
formattedDate: String = "formattedDate",
|
||||
providerInfo: String = "providerInfo",
|
||||
eventId: EventId? = null,
|
||||
roomId: RoomId? = null,
|
||||
sessionId: SessionId? = null,
|
||||
hasBeenResolved: Boolean = false,
|
||||
comment: String? = null,
|
||||
): PushHistoryItem {
|
||||
return PushHistoryItem(
|
||||
pushDate = pushDate,
|
||||
formattedDate = formattedDate,
|
||||
providerInfo = providerInfo,
|
||||
eventId = eventId,
|
||||
roomId = roomId,
|
||||
sessionId = sessionId,
|
||||
hasBeenResolved = hasBeenResolved,
|
||||
comment = comment
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.troubleshoot.impl.history
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.statusBars
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.push.api.history.PushHistoryItem
|
||||
import io.element.android.libraries.troubleshoot.impl.R
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun PushHistoryView(
|
||||
state: PushHistoryState,
|
||||
onBackClick: () -> Unit,
|
||||
onItemClick: (SessionId, RoomId, EventId) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Scaffold(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.systemBarsPadding()
|
||||
.imePadding(),
|
||||
contentWindowInsets = WindowInsets.statusBars,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
navigationIcon = {
|
||||
BackButton(onClick = onBackClick)
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.screen_push_history_title),
|
||||
style = ElementTheme.typography.aliasScreenTitle,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
},
|
||||
actions = {
|
||||
TextButton(
|
||||
text = stringResource(CommonStrings.action_reset),
|
||||
onClick = {
|
||||
state.eventSink(PushHistoryEvents.Reset)
|
||||
},
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
) { padding ->
|
||||
PushHistoryContent(
|
||||
modifier = Modifier
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding),
|
||||
state = state,
|
||||
onItemClick = onItemClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PushHistoryContent(
|
||||
state: PushHistoryState,
|
||||
onItemClick: (SessionId, RoomId, EventId) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth()
|
||||
) {
|
||||
ListItem(
|
||||
headlineContent = { Text("Total number of received push") },
|
||||
trailingContent = ListItemContent.Text(state.pushCounter.toString()),
|
||||
)
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
items(
|
||||
items = state.pushHistoryItems,
|
||||
key = {
|
||||
it.pushDate.toString() + it.sessionId + it.roomId + it.eventId
|
||||
},
|
||||
) { pushHistory ->
|
||||
PushHistoryItem(
|
||||
pushHistory,
|
||||
onClick = {
|
||||
val sessionId = pushHistory.sessionId
|
||||
val roomId = pushHistory.roomId
|
||||
val eventId = pushHistory.eventId
|
||||
if (sessionId != null && roomId != null && eventId != null) {
|
||||
onItemClick(sessionId, roomId, eventId)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PushHistoryItem(
|
||||
pushHistoryItem: PushHistoryItem,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
onClick()
|
||||
},
|
||||
) {
|
||||
HorizontalDivider()
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(end = 8.dp),
|
||||
) {
|
||||
Text(
|
||||
text = pushHistoryItem.formattedDate,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
)
|
||||
Text(
|
||||
text = pushHistoryItem.providerInfo,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 8.dp, top = 8.dp),
|
||||
text = pushHistoryItem.sessionId?.value ?: "No sessionId",
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 8.dp),
|
||||
text = pushHistoryItem.roomId?.value ?: "No roomId",
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 8.dp),
|
||||
text = pushHistoryItem.eventId?.value ?: "No eventId",
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
)
|
||||
pushHistoryItem.comment?.let {
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
text = it,
|
||||
color = if (pushHistoryItem.hasBeenResolved) {
|
||||
ElementTheme.colors.textSecondary
|
||||
} else {
|
||||
ElementTheme.colors.textCriticalPrimary
|
||||
},
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (pushHistoryItem.hasBeenResolved) {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.CheckCircleSolid(),
|
||||
modifier = Modifier.size(24.dp),
|
||||
tint = ElementTheme.colors.iconSuccessPrimary,
|
||||
contentDescription = null,
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.Error(),
|
||||
modifier = Modifier.size(24.dp),
|
||||
tint = ElementTheme.colors.iconCriticalPrimary,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun PushHistoryViewPreview(
|
||||
@PreviewParameter(PushHistoryStateProvider::class) state: PushHistoryState,
|
||||
) = ElementPreview {
|
||||
PushHistoryView(
|
||||
state = state,
|
||||
onBackClick = {},
|
||||
onItemClick = { _, _, _ -> },
|
||||
)
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_push_history_title">"Push history"</string>
|
||||
<string name="troubleshoot_notifications_screen_action">"Run tests"</string>
|
||||
<string name="troubleshoot_notifications_screen_action_again">"Run tests again"</string>
|
||||
<string name="troubleshoot_notifications_screen_failure">"Some tests failed. Please check the details."</string>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package io.element.android.libraries.troubleshoot.impl.history
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.push.api.PushService
|
||||
import io.element.android.libraries.push.test.FakePushService
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class PushHistoryPresenterTest {
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val presenter = createPushHistoryPresenter()
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.pushCounter).isEqualTo(0)
|
||||
assertThat(initialState.pushHistoryItems).isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - updating state`() = runTest {
|
||||
val pushService = FakePushService()
|
||||
val presenter = createPushHistoryPresenter(
|
||||
pushService = pushService,
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.pushCounter).isEqualTo(0)
|
||||
assertThat(initialState.pushHistoryItems).isEmpty()
|
||||
pushService.emitPushCounter(1)
|
||||
assertThat(awaitItem().pushCounter).isEqualTo(1)
|
||||
val item = aPushHistoryItem()
|
||||
pushService.emitPushHistoryItems(listOf(item))
|
||||
assertThat(awaitItem().pushHistoryItems).containsExactly(item)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - reset`() = runTest {
|
||||
val resetPushHistoryResult = lambdaRecorder<Unit> { }
|
||||
val pushService = FakePushService(
|
||||
resetPushHistoryResult = resetPushHistoryResult,
|
||||
)
|
||||
val presenter = createPushHistoryPresenter(
|
||||
pushService = pushService,
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(PushHistoryEvents.Reset)
|
||||
runCurrent()
|
||||
resetPushHistoryResult.assertions().isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPushHistoryPresenter(
|
||||
pushService: PushService = FakePushService(),
|
||||
): PushHistoryPresenter {
|
||||
return PushHistoryPresenter(
|
||||
pushService = pushService,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.troubleshoot.impl.history
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.A_FORMATTED_DATE
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||
import io.element.android.tests.testutils.EnsureNeverCalledWithThreeParams
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.clickOn
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class PushHistoryViewTest {
|
||||
@get:Rule
|
||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test
|
||||
fun `clicking on Reset sends a PushHistoryEvents`() {
|
||||
val eventsRecorder = EventsRecorder<PushHistoryEvents>()
|
||||
rule.setPushHistoryView(
|
||||
aPushHistoryState(
|
||||
pushCounter = 123,
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_reset)
|
||||
eventsRecorder.assertSingle(PushHistoryEvents.Reset)
|
||||
// Also check that the push counter is rendered
|
||||
rule.onNodeWithText("123").assertExists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on an invalid event has no effect`() {
|
||||
val eventsRecorder = EventsRecorder<PushHistoryEvents>(expectEvents = false)
|
||||
rule.setPushHistoryView(
|
||||
aPushHistoryState(
|
||||
pushHistoryItems = listOf(
|
||||
aPushHistoryItem(
|
||||
formattedDate = A_FORMATTED_DATE,
|
||||
)
|
||||
),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.onNodeWithText(A_FORMATTED_DATE).performClick()
|
||||
// No callback invoked
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on a valid event invokes the expected callback`() {
|
||||
val eventsRecorder = EventsRecorder<PushHistoryEvents>(expectEvents = false)
|
||||
val onItemClick = lambdaRecorder<SessionId, RoomId, EventId, Unit> { _, _, _ -> }
|
||||
rule.setPushHistoryView(
|
||||
aPushHistoryState(
|
||||
pushHistoryItems = listOf(
|
||||
aPushHistoryItem(
|
||||
formattedDate = A_FORMATTED_DATE,
|
||||
eventId = AN_EVENT_ID,
|
||||
roomId = A_ROOM_ID,
|
||||
sessionId = A_SESSION_ID,
|
||||
)
|
||||
),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
onItemClick = onItemClick,
|
||||
)
|
||||
rule.onNodeWithText(A_FORMATTED_DATE).performClick()
|
||||
onItemClick.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(A_SESSION_ID), value(A_ROOM_ID), value(AN_EVENT_ID))
|
||||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setPushHistoryView(
|
||||
state: PushHistoryState,
|
||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||
onItemClick: (SessionId, RoomId, EventId) -> Unit = EnsureNeverCalledWithThreeParams(),
|
||||
) {
|
||||
setContent {
|
||||
PushHistoryView(
|
||||
state = state,
|
||||
onBackClick = onBackClick,
|
||||
onItemClick = onItemClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue