Poll history : add tests and fix others

This commit is contained in:
ganfra 2023-12-13 16:20:01 +01:00
parent 8f1e1398f2
commit 6378f6cffe
17 changed files with 831 additions and 529 deletions

View file

@ -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
)

View file

@ -25,21 +25,17 @@ import im.vector.app.features.analytics.plan.Composer
import im.vector.app.features.analytics.plan.PollCreation
import io.element.android.features.messages.test.FakeMessageComposerContext
import io.element.android.features.poll.api.create.CreatePollMode
import io.element.android.features.poll.impl.aPollTimeline
import io.element.android.features.poll.impl.anOngoingPollContent
import io.element.android.features.poll.impl.data.PollRepository
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.room.MatrixRoom
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.AN_EVENT_ID
import io.element.android.libraries.matrix.test.room.SavePollInvocation
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.aPollContent
import io.element.android.libraries.matrix.test.room.anEventTimelineItem
import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline
import io.element.android.libraries.matrix.test.room.SavePollInvocation
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.runTest
import org.junit.Rule
@ -52,8 +48,12 @@ class CreatePollPresenterTest {
private val pollEventId = AN_EVENT_ID
private var navUpInvocationsCount = 0
private val existingPoll = anExistingPoll()
private val fakeMatrixRoom = createFakeMatrixRoom(existingPoll)
private val existingPoll = anOngoingPollContent()
private val fakeMatrixRoom = FakeMatrixRoom(
matrixTimeline = aPollTimeline(
mapOf(pollEventId to existingPoll)
)
)
private val fakeAnalyticsService = FakeAnalyticsService()
private val fakeMessageComposerContext = FakeMessageComposerContext()
@ -80,7 +80,9 @@ class CreatePollPresenterTest {
@Test
fun `in edit mode, if poll doesn't exist, error is tracked and screen is closed`() = runTest {
val room = createFakeMatrixRoom(existingPoll = null)
val room = FakeMatrixRoom(
matrixTimeline = aPollTimeline()
)
val presenter = createCreatePollPresenter(mode = CreatePollMode.EditPoll(AN_EVENT_ID), room = room)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -475,7 +477,6 @@ class CreatePollPresenterTest {
}
}
private suspend fun TurbineTestContext<CreatePollState>.awaitDefaultItem() =
awaitItem().apply {
Truth.assertThat(canSave).isFalse()
@ -518,35 +519,8 @@ class CreatePollPresenterTest {
navigateUp = { navUpInvocationsCount++ },
mode = mode,
)
private fun createFakeMatrixRoom(
existingPoll: PollContent? = anExistingPoll(),
) = FakeMatrixRoom(
matrixTimeline = FakeMatrixTimeline(
initialTimelineItems = existingPoll?.let {
listOf(
MatrixTimelineItem.Event(
0,
anEventTimelineItem(
eventId = pollEventId,
content = it,
)
)
)
}.orEmpty()
)
)
}
private fun anExistingPoll() = aPollContent(
question = "Do you like polls?",
answers = persistentListOf(
PollAnswer("1", "Yes"),
PollAnswer("2", "No"),
PollAnswer("2", "Maybe"),
),
)
private fun PollContent.expectedAnswersState() = answers.map { answer ->
Answer(
text = answer.text,

View file

@ -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,
)
}
}

View file

@ -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
)
}
}