Fix tests and lint issues
This commit is contained in:
parent
8f129eb285
commit
e1506d9e97
4 changed files with 75 additions and 10 deletions
|
|
@ -72,6 +72,8 @@ import io.element.android.libraries.designsystem.utils.animateScrollToItemCenter
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||||
|
import io.element.android.libraries.testtags.TestTags
|
||||||
|
import io.element.android.libraries.testtags.testTag
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
|
@ -143,7 +145,8 @@ fun TimelineView(
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.nestedScroll(nestedScrollConnection),
|
.nestedScroll(nestedScrollConnection)
|
||||||
|
.testTag(TestTags.timeline),
|
||||||
state = lazyListState,
|
state = lazyListState,
|
||||||
reverseLayout = useReverseLayout,
|
reverseLayout = useReverseLayout,
|
||||||
contentPadding = PaddingValues(vertical = 8.dp),
|
contentPadding = PaddingValues(vertical = 8.dp),
|
||||||
|
|
@ -220,6 +223,8 @@ private fun TimelinePrefetchingHelper(
|
||||||
lazyListState: LazyListState,
|
lazyListState: LazyListState,
|
||||||
prefetch: () -> Unit,
|
prefetch: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
val latestPrefetch by rememberUpdatedState(prefetch)
|
||||||
|
|
||||||
// We're using snapshot flows for these because using `LaunchedEffect` with `derivedState` doesn't seem to be responsive enough
|
// We're using snapshot flows for these because using `LaunchedEffect` with `derivedState` doesn't seem to be responsive enough
|
||||||
val firstVisibleItemIndexFlow = snapshotFlow {
|
val firstVisibleItemIndexFlow = snapshotFlow {
|
||||||
lazyListState.firstVisibleItemIndex
|
lazyListState.firstVisibleItemIndex
|
||||||
|
|
@ -231,19 +236,22 @@ private fun TimelinePrefetchingHelper(
|
||||||
lazyListState.isScrollInProgress
|
lazyListState.isScrollInProgress
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(latestPrefetch) {
|
||||||
val isCloseToStartOfLoadedTimelineFlow = combine(layoutInfoFlow, firstVisibleItemIndexFlow) { layoutInfo, firstVisibleItemIndex ->
|
val isCloseToStartOfLoadedTimelineFlow = combine(layoutInfoFlow, firstVisibleItemIndexFlow) { layoutInfo, firstVisibleItemIndex ->
|
||||||
firstVisibleItemIndex + layoutInfo.visibleItemsInfo.size >= layoutInfo.totalItemsCount - 40
|
firstVisibleItemIndex + layoutInfo.visibleItemsInfo.size >= layoutInfo.totalItemsCount - 40
|
||||||
}
|
}
|
||||||
|
|
||||||
combine(isCloseToStartOfLoadedTimelineFlow, isScrollingFlow) { needsPrefetch, isScrolling ->
|
combine(
|
||||||
|
isCloseToStartOfLoadedTimelineFlow,
|
||||||
|
isScrollingFlow,
|
||||||
|
) { needsPrefetch, isScrolling ->
|
||||||
needsPrefetch && isScrolling
|
needsPrefetch && isScrolling
|
||||||
}
|
}
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.collectLatest { needsPrefetch ->
|
.collectLatest { needsPrefetch ->
|
||||||
if (needsPrefetch) {
|
if (needsPrefetch) {
|
||||||
Timber.d("Prefetching pagination with ${lazyListState.layoutInfo.totalItemsCount} items")
|
Timber.d("Prefetching pagination with ${lazyListState.layoutInfo.totalItemsCount} items")
|
||||||
prefetch()
|
latestPrefetch()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -274,7 +282,7 @@ private fun BoxScope.TimelineScrollHelper(
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
if (lazyListState.firstVisibleItemIndex > 10) {
|
if (lazyListState.firstVisibleItemIndex > 10) {
|
||||||
lazyListState.scrollToItem(0)
|
lazyListState.scrollToItem(0)
|
||||||
} else {
|
} else if (lazyListState.firstVisibleItemIndex != 0) {
|
||||||
lazyListState.animateScrollToItem(0)
|
lazyListState.animateScrollToItem(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ import io.element.android.features.messages.impl.pinned.banner.aLoadedPinnedMess
|
||||||
import io.element.android.features.messages.impl.timeline.FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS
|
import io.element.android.features.messages.impl.timeline.FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||||
|
import io.element.android.features.messages.impl.timeline.aTimelineItemList
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineItemReadReceipts
|
import io.element.android.features.messages.impl.timeline.aTimelineItemReadReceipts
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
|
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
|
||||||
import io.element.android.features.messages.impl.timeline.aTimelineState
|
import io.element.android.features.messages.impl.timeline.aTimelineState
|
||||||
|
|
@ -50,6 +51,7 @@ import io.element.android.features.messages.impl.timeline.components.reactionsum
|
||||||
import io.element.android.features.messages.impl.timeline.components.receipt.aReadReceiptData
|
import io.element.android.features.messages.impl.timeline.components.receipt.aReadReceiptData
|
||||||
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetEvents
|
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetEvents
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||||
import io.element.android.libraries.testtags.TestTags
|
import io.element.android.libraries.testtags.TestTags
|
||||||
|
|
@ -126,6 +128,9 @@ class MessagesViewTest {
|
||||||
fun `clicking on an Event invoke expected callback`() {
|
fun `clicking on an Event invoke expected callback`() {
|
||||||
val eventsRecorder = EventsRecorder<MessagesEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<MessagesEvents>(expectEvents = false)
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
|
timelineState = aTimelineState(
|
||||||
|
timelineItems = aTimelineItemList(aTimelineItemTextContent()),
|
||||||
|
),
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
)
|
)
|
||||||
val timelineItem = state.timelineState.timelineItems.first()
|
val timelineItem = state.timelineState.timelineItems.first()
|
||||||
|
|
@ -182,6 +187,9 @@ class MessagesViewTest {
|
||||||
canSendReaction = userHasPermissionToSendReaction,
|
canSendReaction = userHasPermissionToSendReaction,
|
||||||
canPinUnpin = userCanPinEvent,
|
canPinUnpin = userCanPinEvent,
|
||||||
),
|
),
|
||||||
|
timelineState = aTimelineState(
|
||||||
|
timelineItems = aTimelineItemList(aTimelineItemTextContent()),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
||||||
rule.setMessagesView(
|
rule.setMessagesView(
|
||||||
|
|
@ -349,7 +357,10 @@ class MessagesViewTest {
|
||||||
fun `clicking on a reaction emits the expected Event`() {
|
fun `clicking on a reaction emits the expected Event`() {
|
||||||
val eventsRecorder = EventsRecorder<MessagesEvents>()
|
val eventsRecorder = EventsRecorder<MessagesEvents>()
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
eventSink = eventsRecorder
|
timelineState = aTimelineState(
|
||||||
|
timelineItems = aTimelineItemList(aTimelineItemTextContent()),
|
||||||
|
),
|
||||||
|
eventSink = eventsRecorder,
|
||||||
)
|
)
|
||||||
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
||||||
rule.setMessagesView(
|
rule.setMessagesView(
|
||||||
|
|
@ -363,6 +374,9 @@ class MessagesViewTest {
|
||||||
fun `long clicking on a reaction emits the expected Event`() {
|
fun `long clicking on a reaction emits the expected Event`() {
|
||||||
val eventsRecorder = EventsRecorder<ReactionSummaryEvents>()
|
val eventsRecorder = EventsRecorder<ReactionSummaryEvents>()
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
|
timelineState = aTimelineState(
|
||||||
|
timelineItems = aTimelineItemList(aTimelineItemTextContent()),
|
||||||
|
),
|
||||||
reactionSummaryState = aReactionSummaryState(
|
reactionSummaryState = aReactionSummaryState(
|
||||||
target = null,
|
target = null,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
|
|
@ -380,6 +394,9 @@ class MessagesViewTest {
|
||||||
fun `clicking on more reaction emits the expected Event`() {
|
fun `clicking on more reaction emits the expected Event`() {
|
||||||
val eventsRecorder = EventsRecorder<CustomReactionEvents>()
|
val eventsRecorder = EventsRecorder<CustomReactionEvents>()
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
|
timelineState = aTimelineState(
|
||||||
|
timelineItems = aTimelineItemList(aTimelineItemTextContent()),
|
||||||
|
),
|
||||||
customReactionState = aCustomReactionState(
|
customReactionState = aCustomReactionState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
|
|
@ -396,7 +413,11 @@ class MessagesViewTest {
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on more reaction from action list emits the expected Event`() {
|
fun `clicking on more reaction from action list emits the expected Event`() {
|
||||||
val eventsRecorder = EventsRecorder<CustomReactionEvents>()
|
val eventsRecorder = EventsRecorder<CustomReactionEvents>()
|
||||||
val state = aMessagesState()
|
val state = aMessagesState(
|
||||||
|
timelineState = aTimelineState(
|
||||||
|
timelineItems = aTimelineItemList(aTimelineItemTextContent()),
|
||||||
|
),
|
||||||
|
)
|
||||||
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
||||||
val stateWithActionListState = state.copy(
|
val stateWithActionListState = state.copy(
|
||||||
actionListState = anActionListState(
|
actionListState = anActionListState(
|
||||||
|
|
@ -538,7 +559,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setMessa
|
||||||
onCreatePollClick = onCreatePollClick,
|
onCreatePollClick = onCreatePollClick,
|
||||||
onJoinCallClick = onJoinCallClick,
|
onJoinCallClick = onJoinCallClick,
|
||||||
onViewAllPinnedMessagesClick = onViewAllPinnedMessagesClick,
|
onViewAllPinnedMessagesClick = onViewAllPinnedMessagesClick,
|
||||||
knockRequestsBannerView = {}
|
knockRequestsBannerView = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,18 @@ import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.performScrollToIndex
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.messages.impl.timeline.components.aCriticalShield
|
import io.element.android.features.messages.impl.timeline.components.aCriticalShield
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemUnknownContent
|
||||||
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemLoadingIndicatorModel
|
import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemLoadingIndicatorModel
|
||||||
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
|
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
|
||||||
import io.element.android.features.messages.impl.timeline.protection.aTimelineProtectionState
|
import io.element.android.features.messages.impl.timeline.protection.aTimelineProtectionState
|
||||||
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.UniqueId
|
import io.element.android.libraries.matrix.api.core.UniqueId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||||
|
|
@ -31,6 +35,7 @@ import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.setSafeContent
|
import io.element.android.tests.testutils.setSafeContent
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
import org.junit.rules.TestRule
|
||||||
|
|
@ -114,8 +119,6 @@ class TimelineViewTest {
|
||||||
rule.onNodeWithContentDescription(contentDescription).performClick()
|
rule.onNodeWithContentDescription(contentDescription).performClick()
|
||||||
eventsRecorder.assertList(
|
eventsRecorder.assertList(
|
||||||
listOf(
|
listOf(
|
||||||
TimelineEvents.OnScrollFinished(0),
|
|
||||||
TimelineEvents.OnScrollFinished(0),
|
|
||||||
TimelineEvents.OnScrollFinished(0),
|
TimelineEvents.OnScrollFinished(0),
|
||||||
TimelineEvents.ShowShieldDialog(MessageShield.UnverifiedIdentity(true)),
|
TimelineEvents.ShowShieldDialog(MessageShield.UnverifiedIdentity(true)),
|
||||||
)
|
)
|
||||||
|
|
@ -135,6 +138,34 @@ class TimelineViewTest {
|
||||||
rule.clickOn(CommonStrings.action_ok)
|
rule.clickOn(CommonStrings.action_ok)
|
||||||
eventsRecorder.assertSingle(TimelineEvents.HideShieldDialog)
|
eventsRecorder.assertSingle(TimelineEvents.HideShieldDialog)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `scrolling near to the start of the loaded items triggers a pre-fetch`() {
|
||||||
|
val eventsRecorder = EventsRecorder<TimelineEvents>()
|
||||||
|
val items = List<TimelineItem>(20) {
|
||||||
|
aTimelineItemEvent(
|
||||||
|
eventId = EventId("\$event_$it"),
|
||||||
|
content = aTimelineItemUnknownContent(),
|
||||||
|
)
|
||||||
|
}.toPersistentList()
|
||||||
|
|
||||||
|
rule.setTimelineView(
|
||||||
|
state = aTimelineState(
|
||||||
|
timelineItems = items,
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
focusedEventIndex = -1,
|
||||||
|
isLive = false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
rule.onNodeWithTag("timeline").performScrollToIndex(10)
|
||||||
|
eventsRecorder.assertList(
|
||||||
|
listOf(
|
||||||
|
TimelineEvents.OnScrollFinished(firstIndex = 0),
|
||||||
|
TimelineEvents.LoadMore(Timeline.PaginationDirection.BACKWARDS),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setTimelineView(
|
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setTimelineView(
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,11 @@ object TestTags {
|
||||||
*/
|
*/
|
||||||
val floatingActionButton = TestTag("floating-action-button")
|
val floatingActionButton = TestTag("floating-action-button")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timeline.
|
||||||
|
*/
|
||||||
|
val timeline = TestTag("timeline")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timeline item.
|
* Timeline item.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue