ganfra
9 months ago
17 changed files with 831 additions and 529 deletions
@ -1,328 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright (c) 2023 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.messages.impl.timeline.factories.event |
|
||||||
|
|
||||||
import com.google.common.truth.Truth |
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent |
|
||||||
import io.element.android.features.poll.api.pollcontent.PollAnswerItem |
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags |
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService |
|
||||||
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.poll.PollAnswer |
|
||||||
import io.element.android.libraries.matrix.api.poll.PollKind |
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent |
|
||||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID |
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID |
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID_10 |
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID_2 |
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID_3 |
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID_4 |
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID_5 |
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID_6 |
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID_7 |
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID_8 |
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID_9 |
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient |
|
||||||
import kotlinx.collections.immutable.ImmutableList |
|
||||||
import kotlinx.collections.immutable.ImmutableMap |
|
||||||
import kotlinx.collections.immutable.persistentListOf |
|
||||||
import kotlinx.collections.immutable.persistentMapOf |
|
||||||
import kotlinx.collections.immutable.toImmutableMap |
|
||||||
import kotlinx.coroutines.test.runTest |
|
||||||
import org.junit.Test |
|
||||||
|
|
||||||
internal class TimelineItemContentPollFactoryTest { |
|
||||||
|
|
||||||
private val factory = TimelineItemContentPollFactory( |
|
||||||
matrixClient = FakeMatrixClient(), |
|
||||||
featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.Polls.key to true)), |
|
||||||
) |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `Disclosed poll - not ended, no votes`() = runTest { |
|
||||||
Truth.assertThat(factory.create(aPollContent(), eventId = null, eventTimelineItem.isOwn)).isEqualTo(aTimelineItemPollContent()) |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `Disclosed poll - not ended, some votes, including one from current user`() = runTest { |
|
||||||
val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() |
|
||||||
Truth.assertThat( |
|
||||||
factory.create(aPollContent(votes = votes), eventId = null, eventTimelineItem.isOwn) |
|
||||||
) |
|
||||||
.isEqualTo( |
|
||||||
aTimelineItemPollContent( |
|
||||||
answerItems = listOf( |
|
||||||
aPollAnswerItem(answer = A_POLL_ANSWER_1, votesCount = 3, percentage = 0.3f), |
|
||||||
aPollAnswerItem(answer = A_POLL_ANSWER_2, isSelected = true, votesCount = 6, percentage = 0.6f), |
|
||||||
aPollAnswerItem(answer = A_POLL_ANSWER_3), |
|
||||||
aPollAnswerItem(answer = A_POLL_ANSWER_4, votesCount = 1, percentage = 0.1f), |
|
||||||
), |
|
||||||
) |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `Disclosed poll - ended, no votes, no winner`() = runTest { |
|
||||||
Truth.assertThat( |
|
||||||
factory.create(aPollContent(endTime = 1UL), eventId = null, eventTimelineItem.isOwn) |
|
||||||
).isEqualTo( |
|
||||||
aTimelineItemPollContent().let { |
|
||||||
it.copy( |
|
||||||
answerItems = it.answerItems.map { answerItem -> answerItem.copy(isEnabled = false) }, |
|
||||||
isEnded = true, |
|
||||||
) |
|
||||||
} |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `Disclosed poll - ended, some votes, including one from current user (winner)`() = runTest { |
|
||||||
val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() |
|
||||||
Truth.assertThat( |
|
||||||
factory.create( |
|
||||||
aPollContent(votes = votes, endTime = 1UL), |
|
||||||
eventId = null, |
|
||||||
eventTimelineItem.isOwn |
|
||||||
) |
|
||||||
) |
|
||||||
.isEqualTo( |
|
||||||
aTimelineItemPollContent( |
|
||||||
answerItems = listOf( |
|
||||||
aPollAnswerItem(answer = A_POLL_ANSWER_1, isEnabled = false, votesCount = 3, percentage = 0.3f), |
|
||||||
aPollAnswerItem(answer = A_POLL_ANSWER_2, isSelected = true, isEnabled = false, isWinner = true, votesCount = 6, percentage = 0.6f), |
|
||||||
aPollAnswerItem(answer = A_POLL_ANSWER_3, isEnabled = false), |
|
||||||
aPollAnswerItem(answer = A_POLL_ANSWER_4, isEnabled = false, votesCount = 1, percentage = 0.1f), |
|
||||||
), |
|
||||||
isEnded = true, |
|
||||||
) |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `Disclosed poll - ended, some votes, including one from current user (not winner) and two winning votes`() = runTest { |
|
||||||
val votes = OTHER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() |
|
||||||
Truth.assertThat( |
|
||||||
factory.create( |
|
||||||
aPollContent(votes = votes, endTime = 1UL), |
|
||||||
eventId = null, |
|
||||||
eventTimelineItem.isOwn |
|
||||||
) |
|
||||||
) |
|
||||||
.isEqualTo( |
|
||||||
aTimelineItemPollContent( |
|
||||||
answerItems = listOf( |
|
||||||
aPollAnswerItem(answer = A_POLL_ANSWER_1, isEnabled = false, isWinner = true, votesCount = 4, percentage = 0.4f), |
|
||||||
aPollAnswerItem(answer = A_POLL_ANSWER_2, isSelected = true, isEnabled = false, votesCount = 2, percentage = 0.2f), |
|
||||||
aPollAnswerItem(answer = A_POLL_ANSWER_3, isEnabled = false), |
|
||||||
aPollAnswerItem(answer = A_POLL_ANSWER_4, isEnabled = false, isWinner = true, votesCount = 4, percentage = 0.4f), |
|
||||||
), |
|
||||||
isEnded = true, |
|
||||||
) |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `Undisclosed poll - not ended, no votes`() = runTest { |
|
||||||
Truth.assertThat( |
|
||||||
factory.create( |
|
||||||
aPollContent(PollKind.Undisclosed).copy(), |
|
||||||
eventId = null, |
|
||||||
eventTimelineItem.isOwn |
|
||||||
) |
|
||||||
).isEqualTo( |
|
||||||
aTimelineItemPollContent(pollKind = PollKind.Undisclosed).let { |
|
||||||
it.copy(answerItems = it.answerItems.map { answerItem -> answerItem.copy(isDisclosed = false) }) |
|
||||||
} |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `Undisclosed poll - not ended, some votes, including one from current user`() = runTest { |
|
||||||
val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() |
|
||||||
Truth.assertThat( |
|
||||||
factory.create( |
|
||||||
aPollContent(pollKind = PollKind.Undisclosed, votes = votes), |
|
||||||
eventId = null, |
|
||||||
eventTimelineItem.isOwn |
|
||||||
) |
|
||||||
) |
|
||||||
.isEqualTo( |
|
||||||
aTimelineItemPollContent( |
|
||||||
pollKind = PollKind.Undisclosed, |
|
||||||
answerItems = listOf( |
|
||||||
aPollAnswerItem(answer = A_POLL_ANSWER_1, isDisclosed = false, votesCount = 3, percentage = 0.3f), |
|
||||||
aPollAnswerItem(answer = A_POLL_ANSWER_2, isDisclosed = false, isSelected = true, votesCount = 6, percentage = 0.6f), |
|
||||||
aPollAnswerItem(answer = A_POLL_ANSWER_3, isDisclosed = false), |
|
||||||
aPollAnswerItem(answer = A_POLL_ANSWER_4, isDisclosed = false, votesCount = 1, percentage = 0.1f), |
|
||||||
), |
|
||||||
) |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `Undisclosed poll - ended, no votes, no winner`() = runTest { |
|
||||||
Truth.assertThat( |
|
||||||
factory.create( |
|
||||||
aPollContent(pollKind = PollKind.Undisclosed, endTime = 1UL), |
|
||||||
eventId = null, |
|
||||||
eventTimelineItem.isOwn |
|
||||||
) |
|
||||||
).isEqualTo( |
|
||||||
aTimelineItemPollContent().let { |
|
||||||
it.copy( |
|
||||||
pollKind = PollKind.Undisclosed, |
|
||||||
answerItems = it.answerItems.map { answerItem -> |
|
||||||
answerItem.copy(isDisclosed = true, isEnabled = false, isWinner = false) |
|
||||||
}, |
|
||||||
isEnded = true, |
|
||||||
) |
|
||||||
} |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `Undisclosed poll - ended, some votes, including one from current user (winner)`() = runTest { |
|
||||||
val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() |
|
||||||
Truth.assertThat( |
|
||||||
factory.create( |
|
||||||
aPollContent(pollKind = PollKind.Undisclosed, votes = votes, endTime = 1UL), |
|
||||||
eventId = null, |
|
||||||
eventTimelineItem.isOwn |
|
||||||
) |
|
||||||
) |
|
||||||
.isEqualTo( |
|
||||||
aTimelineItemPollContent( |
|
||||||
pollKind = PollKind.Undisclosed, |
|
||||||
answerItems = listOf( |
|
||||||
aPollAnswerItem(answer = A_POLL_ANSWER_1, isEnabled = false, votesCount = 3, percentage = 0.3f), |
|
||||||
aPollAnswerItem(answer = A_POLL_ANSWER_2, isSelected = true, isEnabled = false, isWinner = true, votesCount = 6, percentage = 0.6f), |
|
||||||
aPollAnswerItem(answer = A_POLL_ANSWER_3, isEnabled = false), |
|
||||||
aPollAnswerItem(answer = A_POLL_ANSWER_4, isEnabled = false, votesCount = 1, percentage = 0.1f), |
|
||||||
), |
|
||||||
isEnded = true, |
|
||||||
) |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `Undisclosed poll - ended, some votes, including one from current user (not winner) and two winning votes`() = runTest { |
|
||||||
val votes = OTHER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() |
|
||||||
Truth.assertThat( |
|
||||||
factory.create( |
|
||||||
aPollContent(PollKind.Undisclosed).copy(votes = votes, endTime = 1UL), |
|
||||||
eventId = null, |
|
||||||
eventTimelineItem.isOwn |
|
||||||
) |
|
||||||
) |
|
||||||
.isEqualTo( |
|
||||||
aTimelineItemPollContent( |
|
||||||
pollKind = PollKind.Undisclosed, |
|
||||||
answerItems = listOf( |
|
||||||
aPollAnswerItem(A_POLL_ANSWER_1, isEnabled = false, isWinner = true, votesCount = 4, percentage = 0.4f), |
|
||||||
aPollAnswerItem(A_POLL_ANSWER_2, isSelected = true, isEnabled = false, votesCount = 2, percentage = 0.2f), |
|
||||||
aPollAnswerItem(A_POLL_ANSWER_3, isEnabled = false), |
|
||||||
aPollAnswerItem(A_POLL_ANSWER_4, isEnabled = false, isWinner = true, votesCount = 4, percentage = 0.4f), |
|
||||||
), |
|
||||||
isEnded = true, |
|
||||||
) |
|
||||||
) |
|
||||||
} |
|
||||||
|
|
||||||
@Test |
|
||||||
fun `eventId is populated`() = runTest { |
|
||||||
Truth.assertThat(factory.create(aPollContent(), eventId = null, eventTimelineItem.isOwn)) |
|
||||||
.isEqualTo(aTimelineItemPollContent(eventId = null)) |
|
||||||
|
|
||||||
Truth.assertThat(factory.create( |
|
||||||
aPollContent(), |
|
||||||
eventId = AN_EVENT_ID, |
|
||||||
eventTimelineItem.isOwn |
|
||||||
)) |
|
||||||
.isEqualTo(aTimelineItemPollContent(eventId = AN_EVENT_ID)) |
|
||||||
} |
|
||||||
|
|
||||||
private fun aPollContent( |
|
||||||
pollKind: PollKind = PollKind.Disclosed, |
|
||||||
votes: ImmutableMap<String, ImmutableList<UserId>> = persistentMapOf(), |
|
||||||
endTime: ULong? = null, |
|
||||||
): PollContent = PollContent( |
|
||||||
question = A_POLL_QUESTION, |
|
||||||
kind = pollKind, |
|
||||||
maxSelections = 1UL, |
|
||||||
answers = persistentListOf(A_POLL_ANSWER_1, A_POLL_ANSWER_2, A_POLL_ANSWER_3, A_POLL_ANSWER_4), |
|
||||||
votes = votes, |
|
||||||
endTime = endTime, |
|
||||||
) |
|
||||||
|
|
||||||
private fun aTimelineItemPollContent( |
|
||||||
eventId: EventId? = null, |
|
||||||
pollKind: PollKind = PollKind.Disclosed, |
|
||||||
answerItems: List<PollAnswerItem> = listOf( |
|
||||||
aPollAnswerItem(A_POLL_ANSWER_1), |
|
||||||
aPollAnswerItem(A_POLL_ANSWER_2), |
|
||||||
aPollAnswerItem(A_POLL_ANSWER_3), |
|
||||||
aPollAnswerItem(A_POLL_ANSWER_4), |
|
||||||
), |
|
||||||
isEnded: Boolean = false, |
|
||||||
) = TimelineItemPollContent( |
|
||||||
eventId = eventId, |
|
||||||
question = A_POLL_QUESTION, |
|
||||||
answerItems = answerItems, |
|
||||||
pollKind = pollKind, |
|
||||||
isEnded = isEnded, |
|
||||||
) |
|
||||||
|
|
||||||
private fun aPollAnswerItem( |
|
||||||
answer: PollAnswer, |
|
||||||
isSelected: Boolean = false, |
|
||||||
isEnabled: Boolean = true, |
|
||||||
isWinner: Boolean = false, |
|
||||||
isDisclosed: Boolean = true, |
|
||||||
votesCount: Int = 0, |
|
||||||
percentage: Float = 0f, |
|
||||||
) = PollAnswerItem( |
|
||||||
answer = answer, |
|
||||||
isSelected = isSelected, |
|
||||||
isEnabled = isEnabled, |
|
||||||
isWinner = isWinner, |
|
||||||
isDisclosed = isDisclosed, |
|
||||||
votesCount = votesCount, |
|
||||||
percentage = percentage, |
|
||||||
) |
|
||||||
|
|
||||||
private companion object TestData { |
|
||||||
private const val A_POLL_QUESTION = "What is your favorite food?" |
|
||||||
private val A_POLL_ANSWER_1 = PollAnswer("id_1", "Pizza") |
|
||||||
private val A_POLL_ANSWER_2 = PollAnswer("id_2", "Pasta") |
|
||||||
private val A_POLL_ANSWER_3 = PollAnswer("id_3", "French Fries") |
|
||||||
private val A_POLL_ANSWER_4 = PollAnswer("id_4", "Hamburger") |
|
||||||
|
|
||||||
private val MY_USER_WINNING_VOTES = persistentMapOf( |
|
||||||
A_POLL_ANSWER_1 to persistentListOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4), |
|
||||||
A_POLL_ANSWER_2 to persistentListOf(A_USER_ID /* my vote */, A_USER_ID_5, A_USER_ID_6, A_USER_ID_7, A_USER_ID_8, A_USER_ID_9), // winner |
|
||||||
A_POLL_ANSWER_3 to persistentListOf(), |
|
||||||
A_POLL_ANSWER_4 to persistentListOf(A_USER_ID_10), |
|
||||||
) |
|
||||||
private val OTHER_WINNING_VOTES = persistentMapOf( |
|
||||||
A_POLL_ANSWER_1 to persistentListOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4, A_USER_ID_5), // winner |
|
||||||
A_POLL_ANSWER_2 to persistentListOf(A_USER_ID /* my vote */, A_USER_ID_6), |
|
||||||
A_POLL_ANSWER_3 to persistentListOf(), |
|
||||||
A_POLL_ANSWER_4 to persistentListOf(A_USER_ID_7, A_USER_ID_8, A_USER_ID_9, A_USER_ID_10), // winner |
|
||||||
) |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,55 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2023 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.poll.impl |
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.core.EventId |
||||||
|
import io.element.android.libraries.matrix.api.poll.PollAnswer |
||||||
|
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem |
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent |
||||||
|
import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline |
||||||
|
import io.element.android.libraries.matrix.test.timeline.aPollContent |
||||||
|
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem |
||||||
|
import kotlinx.collections.immutable.persistentListOf |
||||||
|
|
||||||
|
fun aPollTimeline( |
||||||
|
polls: Map<EventId, PollContent> = emptyMap(), |
||||||
|
): FakeMatrixTimeline { |
||||||
|
return FakeMatrixTimeline( |
||||||
|
initialTimelineItems = polls.map { entry -> |
||||||
|
MatrixTimelineItem.Event( |
||||||
|
entry.key.hashCode().toLong(), |
||||||
|
anEventTimelineItem( |
||||||
|
eventId = entry.key, |
||||||
|
content = entry.value, |
||||||
|
) |
||||||
|
) |
||||||
|
} |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
fun anOngoingPollContent() = aPollContent( |
||||||
|
question = "Do you like polls?", |
||||||
|
answers = persistentListOf( |
||||||
|
PollAnswer("1", "Yes"), |
||||||
|
PollAnswer("2", "No"), |
||||||
|
PollAnswer("2", "Maybe"), |
||||||
|
), |
||||||
|
) |
||||||
|
|
||||||
|
fun anEndedPollContent() = anOngoingPollContent().copy( |
||||||
|
endTime = 1702400215U |
||||||
|
) |
@ -0,0 +1,169 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2023 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.poll.impl.history |
||||||
|
|
||||||
|
import app.cash.molecule.RecompositionMode |
||||||
|
import app.cash.molecule.moleculeFlow |
||||||
|
import app.cash.turbine.test |
||||||
|
import com.google.common.truth.Truth.assertThat |
||||||
|
import io.element.android.features.poll.api.actions.EndPollAction |
||||||
|
import io.element.android.features.poll.api.actions.SendPollResponseAction |
||||||
|
import io.element.android.features.poll.impl.aPollTimeline |
||||||
|
import io.element.android.features.poll.impl.anEndedPollContent |
||||||
|
import io.element.android.features.poll.impl.anOngoingPollContent |
||||||
|
import io.element.android.features.poll.impl.history.model.PollHistoryFilter |
||||||
|
import io.element.android.features.poll.impl.history.model.PollHistoryItemsFactory |
||||||
|
import io.element.android.features.poll.impl.model.DefaultPollContentStateFactory |
||||||
|
import io.element.android.features.poll.test.actions.FakeEndPollAction |
||||||
|
import io.element.android.features.poll.test.actions.FakeSendPollResponseAction |
||||||
|
import io.element.android.libraries.dateformatter.test.FakeDaySeparatorFormatter |
||||||
|
import io.element.android.libraries.matrix.api.room.MatrixRoom |
||||||
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID |
||||||
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID_2 |
||||||
|
import io.element.android.libraries.matrix.test.FakeMatrixClient |
||||||
|
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom |
||||||
|
import io.element.android.tests.testutils.WarmUpRule |
||||||
|
import io.element.android.tests.testutils.consumeItemsUntilPredicate |
||||||
|
import io.element.android.tests.testutils.testCoroutineDispatchers |
||||||
|
import kotlinx.coroutines.CoroutineScope |
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi |
||||||
|
import kotlinx.coroutines.test.TestScope |
||||||
|
import kotlinx.coroutines.test.runCurrent |
||||||
|
import kotlinx.coroutines.test.runTest |
||||||
|
import org.junit.Rule |
||||||
|
import org.junit.Test |
||||||
|
|
||||||
|
class PollHistoryPresenterTest { |
||||||
|
|
||||||
|
@get:Rule |
||||||
|
val warmUpRule = WarmUpRule() |
||||||
|
|
||||||
|
private val room = FakeMatrixRoom( |
||||||
|
matrixTimeline = aPollTimeline( |
||||||
|
polls = mapOf( |
||||||
|
AN_EVENT_ID to anOngoingPollContent(), |
||||||
|
AN_EVENT_ID_2 to anEndedPollContent() |
||||||
|
) |
||||||
|
) |
||||||
|
) |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `present - initial states`() = runTest { |
||||||
|
val presenter = createPollHistoryPresenter(room = room) |
||||||
|
moleculeFlow(RecompositionMode.Immediate) { |
||||||
|
presenter.present() |
||||||
|
}.test { |
||||||
|
awaitItem().also { state -> |
||||||
|
assertThat(state.activeFilter).isEqualTo(PollHistoryFilter.ONGOING) |
||||||
|
assertThat(state.pollHistoryItems.size).isEqualTo(0) |
||||||
|
assertThat(state.isLoading).isTrue() |
||||||
|
assertThat(state.hasMoreToLoad).isTrue() |
||||||
|
} |
||||||
|
consumeItemsUntilPredicate { |
||||||
|
it.pollHistoryItems.size == 2 |
||||||
|
}.last().also { state -> |
||||||
|
assertThat(state.pollHistoryItems.size).isEqualTo(2) |
||||||
|
assertThat(state.pollHistoryItems.ongoing).hasSize(1) |
||||||
|
assertThat(state.pollHistoryItems.past).hasSize(1) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `present - change filter scenario`() = runTest { |
||||||
|
val presenter = createPollHistoryPresenter(room = room) |
||||||
|
moleculeFlow(RecompositionMode.Immediate) { |
||||||
|
presenter.present() |
||||||
|
}.test { |
||||||
|
awaitItem().also { state -> |
||||||
|
assertThat(state.activeFilter).isEqualTo(PollHistoryFilter.ONGOING) |
||||||
|
state.eventSink(PollHistoryEvents.OnFilterSelected(PollHistoryFilter.PAST)) |
||||||
|
} |
||||||
|
consumeItemsUntilPredicate { |
||||||
|
it.activeFilter == PollHistoryFilter.PAST |
||||||
|
}.last().also { state -> |
||||||
|
state.eventSink(PollHistoryEvents.OnFilterSelected(PollHistoryFilter.ONGOING)) |
||||||
|
} |
||||||
|
consumeItemsUntilPredicate { |
||||||
|
it.activeFilter == PollHistoryFilter.ONGOING |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class) |
||||||
|
@Test |
||||||
|
fun `present - poll actions scenario`() = runTest { |
||||||
|
val sendPollResponseAction = FakeSendPollResponseAction() |
||||||
|
val endPollAction = FakeEndPollAction() |
||||||
|
val presenter = createPollHistoryPresenter( |
||||||
|
sendPollResponseAction = sendPollResponseAction, |
||||||
|
endPollAction = endPollAction |
||||||
|
) |
||||||
|
moleculeFlow(RecompositionMode.Immediate) { |
||||||
|
presenter.present() |
||||||
|
}.test { |
||||||
|
val state = awaitItem() |
||||||
|
state.eventSink(PollHistoryEvents.PollEndClicked(AN_EVENT_ID)) |
||||||
|
runCurrent() |
||||||
|
endPollAction.verifyExecutionCount(1) |
||||||
|
state.eventSink(PollHistoryEvents.PollAnswerSelected(AN_EVENT_ID, "answer")) |
||||||
|
runCurrent() |
||||||
|
sendPollResponseAction.verifyExecutionCount(1) |
||||||
|
cancelAndConsumeRemainingEvents() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `present - load more scenario`() = runTest { |
||||||
|
val presenter = createPollHistoryPresenter(room = room) |
||||||
|
moleculeFlow(RecompositionMode.Immediate) { |
||||||
|
presenter.present() |
||||||
|
}.test { |
||||||
|
consumeItemsUntilPredicate { |
||||||
|
it.pollHistoryItems.size == 2 && !it.isLoading |
||||||
|
}.last().also { state -> |
||||||
|
state.eventSink(PollHistoryEvents.LoadMore) |
||||||
|
} |
||||||
|
consumeItemsUntilPredicate { |
||||||
|
it.isLoading |
||||||
|
} |
||||||
|
consumeItemsUntilPredicate { |
||||||
|
!it.isLoading |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private fun TestScope.createPollHistoryPresenter( |
||||||
|
room: MatrixRoom = FakeMatrixRoom(), |
||||||
|
appCoroutineScope: CoroutineScope = this, |
||||||
|
endPollAction: EndPollAction = FakeEndPollAction(), |
||||||
|
sendPollResponseAction: SendPollResponseAction = FakeSendPollResponseAction(), |
||||||
|
pollHistoryItemFactory: PollHistoryItemsFactory = PollHistoryItemsFactory( |
||||||
|
pollContentStateFactory = DefaultPollContentStateFactory(FakeMatrixClient()), |
||||||
|
daySeparatorFormatter = FakeDaySeparatorFormatter(), |
||||||
|
dispatchers = testCoroutineDispatchers(), |
||||||
|
), |
||||||
|
): PollHistoryPresenter { |
||||||
|
return PollHistoryPresenter( |
||||||
|
room = room, |
||||||
|
appCoroutineScope = appCoroutineScope, |
||||||
|
sendPollResponseAction = sendPollResponseAction, |
||||||
|
endPollAction = endPollAction, |
||||||
|
pollHistoryItemFactory = pollHistoryItemFactory, |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,288 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2023 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.poll.impl.pollcontent |
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat |
||||||
|
import io.element.android.features.poll.api.pollcontent.PollAnswerItem |
||||||
|
import io.element.android.features.poll.api.pollcontent.PollContentState |
||||||
|
import io.element.android.features.poll.impl.model.DefaultPollContentStateFactory |
||||||
|
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.poll.PollAnswer |
||||||
|
import io.element.android.libraries.matrix.api.poll.PollKind |
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent |
||||||
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID |
||||||
|
import io.element.android.libraries.matrix.test.A_USER_ID |
||||||
|
import io.element.android.libraries.matrix.test.A_USER_ID_10 |
||||||
|
import io.element.android.libraries.matrix.test.A_USER_ID_2 |
||||||
|
import io.element.android.libraries.matrix.test.A_USER_ID_3 |
||||||
|
import io.element.android.libraries.matrix.test.A_USER_ID_4 |
||||||
|
import io.element.android.libraries.matrix.test.A_USER_ID_5 |
||||||
|
import io.element.android.libraries.matrix.test.A_USER_ID_6 |
||||||
|
import io.element.android.libraries.matrix.test.A_USER_ID_7 |
||||||
|
import io.element.android.libraries.matrix.test.A_USER_ID_8 |
||||||
|
import io.element.android.libraries.matrix.test.A_USER_ID_9 |
||||||
|
import io.element.android.libraries.matrix.test.FakeMatrixClient |
||||||
|
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem |
||||||
|
import kotlinx.collections.immutable.ImmutableList |
||||||
|
import kotlinx.collections.immutable.ImmutableMap |
||||||
|
import kotlinx.collections.immutable.persistentListOf |
||||||
|
import kotlinx.collections.immutable.persistentMapOf |
||||||
|
import kotlinx.collections.immutable.toImmutableList |
||||||
|
import kotlinx.collections.immutable.toImmutableMap |
||||||
|
import kotlinx.coroutines.test.runTest |
||||||
|
import org.junit.Test |
||||||
|
|
||||||
|
class PollContentStateFactoryTest { |
||||||
|
|
||||||
|
private val factory = DefaultPollContentStateFactory(FakeMatrixClient()) |
||||||
|
private val eventTimelineItem = anEventTimelineItem() |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Disclosed poll - not ended, no votes`() = runTest { |
||||||
|
val state = factory.create(eventTimelineItem, aPollContent()) |
||||||
|
val expectedState = aPollContentState() |
||||||
|
assertThat(state).isEqualTo(expectedState) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Disclosed poll - not ended, some votes, including one from current user`() = runTest { |
||||||
|
val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() |
||||||
|
val state = factory.create( |
||||||
|
eventTimelineItem, aPollContent(votes = votes) |
||||||
|
) |
||||||
|
val expectedState = aPollContentState( |
||||||
|
answerItems = listOf( |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_1, votesCount = 3, percentage = 0.3f), |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_2, isSelected = true, votesCount = 6, percentage = 0.6f), |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_3), |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_4, votesCount = 1, percentage = 0.1f), |
||||||
|
) |
||||||
|
) |
||||||
|
assertThat(state).isEqualTo(expectedState) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Disclosed poll - ended, no votes, no winner`() = runTest { |
||||||
|
val state = factory.create(eventTimelineItem, aPollContent(endTime = 1UL)) |
||||||
|
val expectedState = aPollContentState().let { |
||||||
|
it.copy( |
||||||
|
answerItems = it.answerItems.map { answerItem -> answerItem.copy(isEnabled = false) }.toImmutableList(), |
||||||
|
isPollEnded = true, |
||||||
|
) |
||||||
|
} |
||||||
|
assertThat(state).isEqualTo(expectedState) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Disclosed poll - ended, some votes, including one from current user (winner)`() = runTest { |
||||||
|
val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() |
||||||
|
val state = factory.create( |
||||||
|
eventTimelineItem, aPollContent(votes = votes, endTime = 1UL) |
||||||
|
) |
||||||
|
val expectedState = aPollContentState( |
||||||
|
answerItems = listOf( |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_1, isEnabled = false, votesCount = 3, percentage = 0.3f), |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_2, isSelected = true, isEnabled = false, isWinner = true, votesCount = 6, percentage = 0.6f), |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_3, isEnabled = false), |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_4, isEnabled = false, votesCount = 1, percentage = 0.1f), |
||||||
|
), |
||||||
|
isEnded = true, |
||||||
|
) |
||||||
|
assertThat(state).isEqualTo(expectedState) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Disclosed poll - ended, some votes, including one from current user (not winner) and two winning votes`() = runTest { |
||||||
|
val votes = OTHER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() |
||||||
|
val state = factory.create( |
||||||
|
eventTimelineItem, aPollContent(votes = votes, endTime = 1UL) |
||||||
|
) |
||||||
|
val expectedState = aPollContentState( |
||||||
|
answerItems = listOf( |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_1, isEnabled = false, isWinner = true, votesCount = 4, percentage = 0.4f), |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_2, isSelected = true, isEnabled = false, votesCount = 2, percentage = 0.2f), |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_3, isEnabled = false), |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_4, isEnabled = false, isWinner = true, votesCount = 4, percentage = 0.4f), |
||||||
|
), |
||||||
|
isEnded = true, |
||||||
|
) |
||||||
|
assertThat(state).isEqualTo(expectedState) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Undisclosed poll - not ended, no votes`() = runTest { |
||||||
|
val state = factory.create(eventTimelineItem, aPollContent(PollKind.Undisclosed)) |
||||||
|
val expectedState = aPollContentState(pollKind = PollKind.Undisclosed).let { |
||||||
|
it.copy( |
||||||
|
answerItems = it.answerItems.map { answerItem -> answerItem.copy(isDisclosed = false) }.toImmutableList() |
||||||
|
) |
||||||
|
} |
||||||
|
assertThat(state).isEqualTo(expectedState) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Undisclosed poll - not ended, some votes, including one from current user`() = runTest { |
||||||
|
val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() |
||||||
|
val state = factory.create( |
||||||
|
eventTimelineItem, aPollContent(PollKind.Undisclosed, votes = votes) |
||||||
|
) |
||||||
|
val expectedState = aPollContentState( |
||||||
|
pollKind = PollKind.Undisclosed, |
||||||
|
answerItems = listOf( |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_1, isDisclosed = false, votesCount = 3, percentage = 0.3f), |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_2, isDisclosed = false, isSelected = true, votesCount = 6, percentage = 0.6f), |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_3, isDisclosed = false), |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_4, isDisclosed = false, votesCount = 1, percentage = 0.1f), |
||||||
|
), |
||||||
|
) |
||||||
|
assertThat(state).isEqualTo(expectedState) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Undisclosed poll - ended, no votes, no winner`() = runTest { |
||||||
|
val state = factory.create(eventTimelineItem, aPollContent(PollKind.Undisclosed, endTime = 1UL)) |
||||||
|
val expectedState = aPollContentState( |
||||||
|
isEnded = true, |
||||||
|
pollKind = PollKind.Undisclosed |
||||||
|
).let { |
||||||
|
it.copy( |
||||||
|
answerItems = it.answerItems.map { answerItem -> answerItem.copy(isDisclosed = true, isEnabled = false) }.toImmutableList(), |
||||||
|
) |
||||||
|
} |
||||||
|
assertThat(state).isEqualTo(expectedState) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Undisclosed poll - ended, some votes, including one from current user (winner)`() = runTest { |
||||||
|
val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() |
||||||
|
val state = factory.create( |
||||||
|
eventTimelineItem, aPollContent(PollKind.Undisclosed, votes = votes, endTime = 1UL) |
||||||
|
) |
||||||
|
val expectedState = aPollContentState( |
||||||
|
pollKind = PollKind.Undisclosed, |
||||||
|
answerItems = listOf( |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_1, isEnabled = false, votesCount = 3, percentage = 0.3f), |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_2, isSelected = true, isEnabled = false, isWinner = true, votesCount = 6, percentage = 0.6f), |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_3, isEnabled = false), |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_4, isEnabled = false, votesCount = 1, percentage = 0.1f), |
||||||
|
), |
||||||
|
isEnded = true, |
||||||
|
) |
||||||
|
assertThat(state).isEqualTo(expectedState) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `Undisclosed poll - ended, some votes, including one from current user (not winner) and two winning votes`() = runTest { |
||||||
|
val votes = OTHER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() |
||||||
|
val state = factory.create( |
||||||
|
eventTimelineItem, aPollContent(PollKind.Undisclosed, votes = votes, endTime = 1UL) |
||||||
|
) |
||||||
|
val expectedState = aPollContentState( |
||||||
|
pollKind = PollKind.Undisclosed, |
||||||
|
answerItems = listOf( |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_1, isEnabled = false, isWinner = true, votesCount = 4, percentage = 0.4f), |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_2, isSelected = true, isEnabled = false, votesCount = 2, percentage = 0.2f), |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_3, isEnabled = false), |
||||||
|
aPollAnswerItem(answer = A_POLL_ANSWER_4, isEnabled = false, isWinner = true, votesCount = 4, percentage = 0.4f), |
||||||
|
), |
||||||
|
isEnded = true, |
||||||
|
) |
||||||
|
assertThat(state).isEqualTo(expectedState) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun `eventId is populated`() = runTest { |
||||||
|
val state = factory.create(eventTimelineItem, aPollContent()) |
||||||
|
assertThat(state.eventId).isEqualTo(eventTimelineItem.eventId) |
||||||
|
} |
||||||
|
|
||||||
|
private fun aPollContent( |
||||||
|
pollKind: PollKind = PollKind.Disclosed, |
||||||
|
votes: ImmutableMap<String, ImmutableList<UserId>> = persistentMapOf(), |
||||||
|
endTime: ULong? = null, |
||||||
|
): PollContent = PollContent( |
||||||
|
question = A_POLL_QUESTION, |
||||||
|
kind = pollKind, |
||||||
|
maxSelections = 1UL, |
||||||
|
answers = persistentListOf(A_POLL_ANSWER_1, A_POLL_ANSWER_2, A_POLL_ANSWER_3, A_POLL_ANSWER_4), |
||||||
|
votes = votes, |
||||||
|
endTime = endTime, |
||||||
|
) |
||||||
|
|
||||||
|
private fun aPollContentState( |
||||||
|
eventId: EventId? = AN_EVENT_ID, |
||||||
|
pollKind: PollKind = PollKind.Disclosed, |
||||||
|
answerItems: List<PollAnswerItem> = listOf( |
||||||
|
aPollAnswerItem(A_POLL_ANSWER_1), |
||||||
|
aPollAnswerItem(A_POLL_ANSWER_2), |
||||||
|
aPollAnswerItem(A_POLL_ANSWER_3), |
||||||
|
aPollAnswerItem(A_POLL_ANSWER_4), |
||||||
|
), |
||||||
|
isEnded: Boolean = false, |
||||||
|
isMine: Boolean = false, |
||||||
|
isEditable: Boolean = false, |
||||||
|
question: String = A_POLL_QUESTION, |
||||||
|
) = PollContentState( |
||||||
|
eventId = eventId, |
||||||
|
question = question, |
||||||
|
answerItems = answerItems.toImmutableList(), |
||||||
|
pollKind = pollKind, |
||||||
|
isMine = isMine, |
||||||
|
isPollEnded = isEnded, |
||||||
|
isPollEditable = isEditable, |
||||||
|
) |
||||||
|
|
||||||
|
private fun aPollAnswerItem( |
||||||
|
answer: PollAnswer, |
||||||
|
isSelected: Boolean = false, |
||||||
|
isEnabled: Boolean = true, |
||||||
|
isWinner: Boolean = false, |
||||||
|
isDisclosed: Boolean = true, |
||||||
|
votesCount: Int = 0, |
||||||
|
percentage: Float = 0f, |
||||||
|
) = PollAnswerItem( |
||||||
|
answer = answer, |
||||||
|
isSelected = isSelected, |
||||||
|
isEnabled = isEnabled, |
||||||
|
isWinner = isWinner, |
||||||
|
isDisclosed = isDisclosed, |
||||||
|
votesCount = votesCount, |
||||||
|
percentage = percentage, |
||||||
|
) |
||||||
|
|
||||||
|
private companion object TestData { |
||||||
|
private const val A_POLL_QUESTION = "What is your favorite food?" |
||||||
|
private val A_POLL_ANSWER_1 = PollAnswer("id_1", "Pizza") |
||||||
|
private val A_POLL_ANSWER_2 = PollAnswer("id_2", "Pasta") |
||||||
|
private val A_POLL_ANSWER_3 = PollAnswer("id_3", "French Fries") |
||||||
|
private val A_POLL_ANSWER_4 = PollAnswer("id_4", "Hamburger") |
||||||
|
|
||||||
|
private val MY_USER_WINNING_VOTES = persistentMapOf( |
||||||
|
A_POLL_ANSWER_1 to persistentListOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4), |
||||||
|
A_POLL_ANSWER_2 to persistentListOf(A_USER_ID /* my vote */, A_USER_ID_5, A_USER_ID_6, A_USER_ID_7, A_USER_ID_8, A_USER_ID_9), // winner |
||||||
|
A_POLL_ANSWER_3 to persistentListOf(), |
||||||
|
A_POLL_ANSWER_4 to persistentListOf(A_USER_ID_10), |
||||||
|
) |
||||||
|
private val OTHER_WINNING_VOTES = persistentMapOf( |
||||||
|
A_POLL_ANSWER_1 to persistentListOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4, A_USER_ID_5), // winner |
||||||
|
A_POLL_ANSWER_2 to persistentListOf(A_USER_ID /* my vote */, A_USER_ID_6), |
||||||
|
A_POLL_ANSWER_3 to persistentListOf(), |
||||||
|
A_POLL_ANSWER_4 to persistentListOf(A_USER_ID_7, A_USER_ID_8, A_USER_ID_9, A_USER_ID_10), // winner |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2022 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. |
||||||
|
*/ |
||||||
|
|
||||||
|
plugins { |
||||||
|
id("io.element.android-library") |
||||||
|
} |
||||||
|
|
||||||
|
android { |
||||||
|
namespace = "io.element.android.features.poll.test" |
||||||
|
} |
||||||
|
|
||||||
|
dependencies { |
||||||
|
implementation(projects.libraries.matrix.api) |
||||||
|
api(projects.features.poll.api) |
||||||
|
implementation(libs.kotlinx.collections.immutable) |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2023 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.poll.test.actions |
||||||
|
|
||||||
|
import io.element.android.features.poll.api.actions.EndPollAction |
||||||
|
import io.element.android.libraries.matrix.api.core.EventId |
||||||
|
|
||||||
|
class FakeEndPollAction : EndPollAction { |
||||||
|
|
||||||
|
private var executionCount = 0 |
||||||
|
|
||||||
|
fun verifyExecutionCount(count: Int) { |
||||||
|
assert(executionCount == count) |
||||||
|
} |
||||||
|
|
||||||
|
override suspend fun execute(pollStartId: EventId): Result<Unit> { |
||||||
|
executionCount++ |
||||||
|
return Result.success(Unit) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2023 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.poll.test.actions |
||||||
|
|
||||||
|
import io.element.android.features.poll.api.actions.SendPollResponseAction |
||||||
|
import io.element.android.libraries.matrix.api.core.EventId |
||||||
|
|
||||||
|
class FakeSendPollResponseAction : SendPollResponseAction { |
||||||
|
|
||||||
|
private var executionCount = 0 |
||||||
|
|
||||||
|
fun verifyExecutionCount(count: Int) { |
||||||
|
assert(executionCount == count) |
||||||
|
} |
||||||
|
|
||||||
|
override suspend fun execute(pollStartId: EventId, answerId: String): Result<Unit> { |
||||||
|
executionCount++ |
||||||
|
return Result.success(Unit) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2023 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.poll.test.pollcontent |
||||||
|
|
||||||
|
import io.element.android.features.poll.api.pollcontent.PollAnswerItem |
||||||
|
import io.element.android.features.poll.api.pollcontent.PollContentState |
||||||
|
import io.element.android.features.poll.api.pollcontent.PollContentStateFactory |
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem |
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent |
||||||
|
import kotlinx.collections.immutable.toImmutableList |
||||||
|
|
||||||
|
class FakePollContentStateFactory : PollContentStateFactory { |
||||||
|
|
||||||
|
override suspend fun create(event: EventTimelineItem, content: PollContent): PollContentState { |
||||||
|
return PollContentState( |
||||||
|
eventId = event.eventId, |
||||||
|
isMine = event.isOwn, |
||||||
|
question = content.question, |
||||||
|
answerItems = emptyList<PollAnswerItem>().toImmutableList(), |
||||||
|
pollKind = content.kind, |
||||||
|
isPollEditable = event.isEditable, |
||||||
|
isPollEnded = content.endTime != null, |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,139 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2023 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.libraries.matrix.test.timeline |
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.core.EventId |
||||||
|
import io.element.android.libraries.matrix.api.core.TransactionId |
||||||
|
import io.element.android.libraries.matrix.api.core.UserId |
||||||
|
import io.element.android.libraries.matrix.api.poll.PollAnswer |
||||||
|
import io.element.android.libraries.matrix.api.poll.PollKind |
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo |
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent |
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction |
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem |
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo |
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState |
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent |
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.MessageType |
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent |
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent |
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimelineDetails |
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.Receipt |
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType |
||||||
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID |
||||||
|
import io.element.android.libraries.matrix.test.A_USER_ID |
||||||
|
import io.element.android.libraries.matrix.test.A_USER_NAME |
||||||
|
import kotlinx.collections.immutable.ImmutableList |
||||||
|
import kotlinx.collections.immutable.ImmutableMap |
||||||
|
import kotlinx.collections.immutable.persistentListOf |
||||||
|
import kotlinx.collections.immutable.persistentMapOf |
||||||
|
|
||||||
|
fun anEventTimelineItem( |
||||||
|
eventId: EventId = AN_EVENT_ID, |
||||||
|
transactionId: TransactionId? = null, |
||||||
|
isEditable: Boolean = false, |
||||||
|
isLocal: Boolean = false, |
||||||
|
isOwn: Boolean = false, |
||||||
|
isRemote: Boolean = false, |
||||||
|
localSendState: LocalEventSendState? = null, |
||||||
|
reactions: ImmutableList<EventReaction> = persistentListOf(), |
||||||
|
receipts: ImmutableList<Receipt> = persistentListOf(), |
||||||
|
sender: UserId = A_USER_ID, |
||||||
|
senderProfile: ProfileTimelineDetails = aProfileTimelineDetails(), |
||||||
|
timestamp: Long = 0L, |
||||||
|
content: EventContent = aProfileChangeMessageContent(), |
||||||
|
debugInfo: TimelineItemDebugInfo = aTimelineItemDebugInfo(), |
||||||
|
) = EventTimelineItem( |
||||||
|
eventId = eventId, |
||||||
|
transactionId = transactionId, |
||||||
|
isEditable = isEditable, |
||||||
|
isLocal = isLocal, |
||||||
|
isOwn = isOwn, |
||||||
|
isRemote = isRemote, |
||||||
|
localSendState = localSendState, |
||||||
|
reactions = reactions, |
||||||
|
receipts = receipts, |
||||||
|
sender = sender, |
||||||
|
senderProfile = senderProfile, |
||||||
|
timestamp = timestamp, |
||||||
|
content = content, |
||||||
|
debugInfo = debugInfo, |
||||||
|
origin = null, |
||||||
|
) |
||||||
|
|
||||||
|
fun aProfileTimelineDetails( |
||||||
|
displayName: String? = A_USER_NAME, |
||||||
|
displayNameAmbiguous: Boolean = false, |
||||||
|
avatarUrl: String? = null |
||||||
|
): ProfileTimelineDetails = ProfileTimelineDetails.Ready( |
||||||
|
displayName = displayName, |
||||||
|
displayNameAmbiguous = displayNameAmbiguous, |
||||||
|
avatarUrl = avatarUrl, |
||||||
|
) |
||||||
|
|
||||||
|
fun aProfileChangeMessageContent( |
||||||
|
displayName: String? = null, |
||||||
|
prevDisplayName: String? = null, |
||||||
|
avatarUrl: String? = null, |
||||||
|
prevAvatarUrl: String? = null, |
||||||
|
) = ProfileChangeContent( |
||||||
|
displayName = displayName, |
||||||
|
prevDisplayName = prevDisplayName, |
||||||
|
avatarUrl = avatarUrl, |
||||||
|
prevAvatarUrl = prevAvatarUrl, |
||||||
|
) |
||||||
|
|
||||||
|
fun aMessageContent( |
||||||
|
body: String = "body", |
||||||
|
inReplyTo: InReplyTo? = null, |
||||||
|
isEdited: Boolean = false, |
||||||
|
isThreaded: Boolean = false, |
||||||
|
messageType: MessageType = TextMessageType( |
||||||
|
body = body, |
||||||
|
formatted = null |
||||||
|
) |
||||||
|
) = MessageContent( |
||||||
|
body = body, |
||||||
|
inReplyTo = inReplyTo, |
||||||
|
isEdited = isEdited, |
||||||
|
isThreaded = isThreaded, |
||||||
|
type = messageType |
||||||
|
) |
||||||
|
|
||||||
|
fun aTimelineItemDebugInfo( |
||||||
|
model: String = "Rust(Model())", |
||||||
|
originalJson: String? = null, |
||||||
|
latestEditedJson: String? = null, |
||||||
|
) = TimelineItemDebugInfo( |
||||||
|
model, originalJson, latestEditedJson |
||||||
|
) |
||||||
|
|
||||||
|
fun aPollContent( |
||||||
|
question: String = "Do you like polls?", |
||||||
|
answers: ImmutableList<PollAnswer> = persistentListOf(PollAnswer("1", "Yes"), PollAnswer("2", "No")), |
||||||
|
kind: PollKind = PollKind.Disclosed, |
||||||
|
maxSelections: ULong = 1u, |
||||||
|
votes: ImmutableMap<String, ImmutableList<UserId>> = persistentMapOf(), |
||||||
|
endTime: ULong? = null, |
||||||
|
) = PollContent( |
||||||
|
question = question, |
||||||
|
kind = kind, |
||||||
|
maxSelections = maxSelections, |
||||||
|
answers = answers, |
||||||
|
votes = votes, |
||||||
|
endTime = endTime, |
||||||
|
) |
Loading…
Reference in new issue