diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index fba6ffbe51..d2032bb4c4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -53,6 +53,8 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem_ +import im.vector.app.features.home.room.detail.timeline.item.PollItem +import im.vector.app.features.home.room.detail.timeline.item.PollItem_ import im.vector.app.features.home.room.detail.timeline.item.RedactedMessageItem import im.vector.app.features.home.room.detail.timeline.item.RedactedMessageItem_ import im.vector.app.features.home.room.detail.timeline.item.VerificationRequestItem @@ -128,7 +130,7 @@ class MessageItemFactory @Inject constructor( private val vectorPreferences: VectorPreferences, private val urlMapProvider: UrlMapProvider, private val liveLocationShareMessageItemFactory: LiveLocationShareMessageItemFactory, - private val pollItemFactory: PollItemFactory, + private val pollItemViewStateFactory: PollItemViewStateFactory, ) { // TODO inject this properly? @@ -188,7 +190,7 @@ class MessageItemFactory @Inject constructor( is MessageFileContent -> buildFileMessageItem(messageContent, highlight, attributes) is MessageAudioContent -> buildAudioContent(params, messageContent, informationData, highlight, attributes) is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessagePollContent -> pollItemFactory.create(messageContent, informationData, highlight, callback, attributes) + is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes) is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, attributes) is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(params.event, highlight, attributes) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) @@ -224,6 +226,28 @@ class MessageItemFactory @Inject constructor( .leftGuideline(avatarSizeProvider.leftGuideline) } + private fun buildPollItem( + pollContent: MessagePollContent, + informationData: MessageInformationData, + highlight: Boolean, + callback: TimelineEventController.Callback?, + attributes: AbsMessageItem.Attributes, + ): PollItem { + val pollViewState = pollItemViewStateFactory.create(pollContent, informationData, callback) + + return PollItem_() + .attributes(attributes) + .eventId(informationData.eventId) + .pollQuestion(pollViewState.question) + .canVote(pollViewState.canVote) + .totalVotesText(pollViewState.totalVotes) + .optionViewStates(pollViewState.optionViewStates) + .edited(informationData.hasBeenEdited) + .highlighted(highlight) + .leftGuideline(avatarSizeProvider.leftGuideline) + .callback(callback) + } + private fun buildAudioMessageItem( params: TimelineItemFactoryParams, messageContent: MessageAudioContent, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemFactory.kt deleted file mode 100644 index 05e945c193..0000000000 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemFactory.kt +++ /dev/null @@ -1,135 +0,0 @@ -/* - * 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. - */ - -package im.vector.app.features.home.room.detail.timeline.factory - -import androidx.annotation.VisibleForTesting -import im.vector.app.R -import im.vector.app.core.epoxy.VectorEpoxyModel -import im.vector.app.core.resources.ColorProvider -import im.vector.app.core.resources.StringProvider -import im.vector.app.core.utils.DimensionConverter -import im.vector.app.features.home.room.detail.timeline.TimelineEventController -import im.vector.app.features.home.room.detail.timeline.factory.MessageItemFactoryHelper.annotateWithEdited -import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider -import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem -import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData -import im.vector.app.features.home.room.detail.timeline.item.PollItem_ -import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState -import im.vector.app.features.home.room.detail.timeline.item.PollResponseData -import im.vector.app.features.poll.PollState -import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence -import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent -import org.matrix.android.sdk.api.session.room.model.message.PollAnswer -import javax.inject.Inject - -class PollItemFactory @Inject constructor( - private val stringProvider: StringProvider, - private val avatarSizeProvider: AvatarSizeProvider, - private val colorProvider: ColorProvider, - private val dimensionConverter: DimensionConverter, -) { - - fun create( - pollContent: MessagePollContent, - informationData: MessageInformationData, - highlight: Boolean, - callback: TimelineEventController.Callback?, - attributes: AbsMessageItem.Attributes, - ): VectorEpoxyModel<*>? { - val pollResponseSummary = informationData.pollResponseAggregatedSummary - val pollState = createPollState(informationData, pollResponseSummary, pollContent) - val pollCreationInfo = pollContent.getBestPollCreationInfo() - val questionText = pollCreationInfo?.question?.getBestQuestion().orEmpty() - val question = createPollQuestion(informationData, questionText, callback) - val optionViewStates = pollCreationInfo?.answers?.mapToOptions(pollState, informationData) - val totalVotesText = createTotalVotesText(pollState, pollResponseSummary) - - return PollItem_() - .attributes(attributes) - .eventId(informationData.eventId) - .pollQuestion(question) - .canVote(pollState.isVotable()) - .totalVotesText(totalVotesText) - .optionViewStates(optionViewStates) - .edited(informationData.hasBeenEdited) - .highlighted(highlight) - .leftGuideline(avatarSizeProvider.leftGuideline) - .callback(callback) - } - - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - internal fun createPollState( - informationData: MessageInformationData, - pollResponseSummary: PollResponseData?, - pollContent: MessagePollContent, - ): PollState = when { - !informationData.sendState.isSent() -> PollState.Sending - pollResponseSummary?.isClosed.orFalse() -> PollState.Ended - pollContent.getBestPollCreationInfo()?.isUndisclosed().orFalse() -> PollState.Undisclosed - pollResponseSummary?.myVote?.isNotEmpty().orFalse() -> PollState.Voted(pollResponseSummary?.totalVotes ?: 0) - else -> PollState.Ready - } - - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - internal fun List.mapToOptions( - pollState: PollState, - informationData: MessageInformationData, - ) = map { answer -> - val pollResponseSummary = informationData.pollResponseAggregatedSummary - val winnerVoteCount = pollResponseSummary?.winnerVoteCount - val optionId = answer.id ?: "" - val optionAnswer = answer.getBestAnswer() ?: "" - val voteSummary = pollResponseSummary?.votes?.get(answer.id) - val voteCount = voteSummary?.total ?: 0 - val votePercentage = voteSummary?.percentage ?: 0.0 - val isMyVote = pollResponseSummary?.myVote == answer.id - val isWinner = winnerVoteCount != 0 && voteCount == winnerVoteCount - - when (pollState) { - PollState.Sending -> PollOptionViewState.PollSending(optionId, optionAnswer) - PollState.Ready -> PollOptionViewState.PollReady(optionId, optionAnswer) - is PollState.Voted -> PollOptionViewState.PollVoted(optionId, optionAnswer, voteCount, votePercentage, isMyVote) - PollState.Undisclosed -> PollOptionViewState.PollUndisclosed(optionId, optionAnswer, isMyVote) - PollState.Ended -> PollOptionViewState.PollEnded(optionId, optionAnswer, voteCount, votePercentage, isWinner) - } - } - - private fun createPollQuestion( - informationData: MessageInformationData, - question: String, - callback: TimelineEventController.Callback?, - ) = if (informationData.hasBeenEdited) { - annotateWithEdited(stringProvider, colorProvider, dimensionConverter, question, callback, informationData) - } else { - question - }.toEpoxyCharSequence() - - private fun createTotalVotesText( - pollState: PollState, - pollResponseSummary: PollResponseData?, - ): String { - val votes = pollResponseSummary?.totalVotes ?: 0 - return when { - pollState is PollState.Ended -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, votes, votes) - pollState is PollState.Undisclosed -> "" - pollState is PollState.Voted -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, votes, votes) - votes == 0 -> stringProvider.getString(R.string.poll_no_votes_cast) - else -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, votes, votes) - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt new file mode 100644 index 0000000000..8365f0710e --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt @@ -0,0 +1,185 @@ +/* + * 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. + */ + +package im.vector.app.features.home.room.detail.timeline.factory + +import im.vector.app.R +import im.vector.app.core.resources.ColorProvider +import im.vector.app.core.resources.StringProvider +import im.vector.app.core.utils.DimensionConverter +import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.home.room.detail.timeline.factory.MessageItemFactoryHelper.annotateWithEdited +import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState +import im.vector.app.features.home.room.detail.timeline.item.PollResponseData +import im.vector.app.features.poll.PollViewState +import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence +import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent +import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo +import javax.inject.Inject + +class PollItemViewStateFactory @Inject constructor( + private val stringProvider: StringProvider, + private val colorProvider: ColorProvider, + private val dimensionConverter: DimensionConverter, +) { + + fun create( + pollContent: MessagePollContent, + informationData: MessageInformationData, + callback: TimelineEventController.Callback?, + ): PollViewState { + val pollCreationInfo = pollContent.getBestPollCreationInfo() + + val questionText = pollCreationInfo?.question?.getBestQuestion().orEmpty() + val question = createPollQuestion(informationData, questionText, callback) + + val pollResponseSummary = informationData.pollResponseAggregatedSummary + val winnerVoteCount = pollResponseSummary?.winnerVoteCount + val totalVotes = pollResponseSummary?.totalVotes ?: 0 + + return when { + !informationData.sendState.isSent() -> { + createSendingPollViewState(question, pollCreationInfo) + } + informationData.pollResponseAggregatedSummary?.isClosed.orFalse() -> { + createEndedPollViewState(question, pollCreationInfo, pollResponseSummary, totalVotes, winnerVoteCount) + } + pollContent.getBestPollCreationInfo()?.isUndisclosed().orFalse() -> { + createUndisclosedPollViewState(question, pollCreationInfo, pollResponseSummary) + } + informationData.pollResponseAggregatedSummary?.myVote?.isNotEmpty().orFalse() -> { + createVotedPollViewState(question, pollCreationInfo, pollResponseSummary, totalVotes) + } + else -> { + createReadyPollViewState(question, pollCreationInfo, totalVotes) + } + } + } + + private fun createSendingPollViewState(question: EpoxyCharSequence, pollCreationInfo: PollCreationInfo?): PollViewState { + return PollViewState( + question = question, + totalVotes = stringProvider.getString(R.string.poll_no_votes_cast), + canVote = false, + optionViewStates = pollCreationInfo?.answers?.map { answer -> + PollOptionViewState.PollSending( + optionId = answer.id ?: "", + optionAnswer = answer.getBestAnswer() ?: "" + ) + }, + ) + } + + private fun createEndedPollViewState( + question: EpoxyCharSequence, + pollCreationInfo: PollCreationInfo?, + pollResponseSummary: PollResponseData?, + totalVotes: Int, + winnerVoteCount: Int?, + ): PollViewState { + return PollViewState( + question = question, + totalVotes = stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, totalVotes, totalVotes), + canVote = false, + optionViewStates = pollCreationInfo?.answers?.map { answer -> + val voteSummary = pollResponseSummary?.getVoteSummaryOfAnOption(answer.id ?: "") + PollOptionViewState.PollEnded( + optionId = answer.id ?: "", + optionAnswer = answer.getBestAnswer() ?: "", + voteCount = voteSummary?.total ?: 0, + votePercentage = voteSummary?.percentage ?: 0.0, + isWinner = winnerVoteCount != 0 && voteSummary?.total == winnerVoteCount + ) + }, + ) + } + + private fun createUndisclosedPollViewState( + question: EpoxyCharSequence, + pollCreationInfo: PollCreationInfo?, + pollResponseSummary: PollResponseData? + ): PollViewState { + return PollViewState( + question = question, + totalVotes = "", + canVote = true, + optionViewStates = pollCreationInfo?.answers?.map { answer -> + val isMyVote = pollResponseSummary?.myVote == answer.id + PollOptionViewState.PollUndisclosed( + optionId = answer.id ?: "", + optionAnswer = answer.getBestAnswer() ?: "", + isSelected = isMyVote + ) + }, + ) + } + + private fun createVotedPollViewState( + question: EpoxyCharSequence, + pollCreationInfo: PollCreationInfo?, + pollResponseSummary: PollResponseData?, + totalVotes: Int + ): PollViewState { + return PollViewState( + question = question, + totalVotes = stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, totalVotes, totalVotes), + canVote = true, + optionViewStates = pollCreationInfo?.answers?.map { answer -> + val isMyVote = pollResponseSummary?.myVote == answer.id + val voteSummary = pollResponseSummary?.getVoteSummaryOfAnOption(answer.id ?: "") + PollOptionViewState.PollVoted( + optionId = answer.id ?: "", + optionAnswer = answer.getBestAnswer() ?: "", + voteCount = voteSummary?.total ?: 0, + votePercentage = voteSummary?.percentage ?: 0.0, + isSelected = isMyVote + ) + }, + ) + } + + private fun createReadyPollViewState(question: EpoxyCharSequence, pollCreationInfo: PollCreationInfo?, totalVotes: Int): PollViewState { + val totalVotesText = if (totalVotes == 0) { + stringProvider.getString(R.string.poll_no_votes_cast) + } else { + stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, totalVotes, totalVotes) + } + return PollViewState( + question = question, + totalVotes = totalVotesText, + canVote = true, + optionViewStates = pollCreationInfo?.answers?.map { answer -> + PollOptionViewState.PollReady( + optionId = answer.id ?: "", + optionAnswer = answer.getBestAnswer() ?: "" + ) + }, + ) + } + + private fun createPollQuestion( + informationData: MessageInformationData, + question: String, + callback: TimelineEventController.Callback?, + ) = if (informationData.hasBeenEdited) { + annotateWithEdited(stringProvider, colorProvider, dimensionConverter, question, callback, informationData) + } else { + question + }.toEpoxyCharSequence() +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt index 554dd0ada8..9b24720c88 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt @@ -91,7 +91,10 @@ data class PollResponseData( val totalVotes: Int = 0, val winnerVoteCount: Int = 0, val isClosed: Boolean = false -) : Parcelable +) : Parcelable { + + fun getVoteSummaryOfAnOption(optionId: String) = votes?.get(optionId) +} @Parcelize data class PollVoteSummaryData( diff --git a/vector/src/main/java/im/vector/app/features/poll/PollState.kt b/vector/src/main/java/im/vector/app/features/poll/PollViewState.kt similarity index 66% rename from vector/src/main/java/im/vector/app/features/poll/PollState.kt rename to vector/src/main/java/im/vector/app/features/poll/PollViewState.kt index 93cdb0ecbe..01947d8850 100644 --- a/vector/src/main/java/im/vector/app/features/poll/PollState.kt +++ b/vector/src/main/java/im/vector/app/features/poll/PollViewState.kt @@ -16,12 +16,12 @@ package im.vector.app.features.poll -sealed interface PollState { - object Sending : PollState - object Ready : PollState - data class Voted(val votes: Int) : PollState - object Undisclosed : PollState - object Ended : PollState +import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState +import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence - fun isVotable() = this !is Sending && this !is Ended -} +data class PollViewState( + val question: EpoxyCharSequence, + val totalVotes: String, + val canVote: Boolean, + val optionViewStates: List?, +) diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemFactoryTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemFactoryTest.kt deleted file mode 100644 index be397e25ea..0000000000 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemFactoryTest.kt +++ /dev/null @@ -1,282 +0,0 @@ -/* - * 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. - */ - -package im.vector.app.features.home.room.detail.timeline.factory - -import com.airbnb.mvrx.test.MvRxTestRule -import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData -import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState -import im.vector.app.features.home.room.detail.timeline.item.PollResponseData -import im.vector.app.features.home.room.detail.timeline.item.ReactionsSummaryData -import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout -import im.vector.app.features.poll.PollState -import io.mockk.mockk -import io.mockk.unmockkAll -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.runTest -import org.amshove.kluent.shouldBe -import org.amshove.kluent.shouldBeEqualTo -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent -import org.matrix.android.sdk.api.session.room.model.message.PollAnswer -import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo -import org.matrix.android.sdk.api.session.room.model.message.PollQuestion -import org.matrix.android.sdk.api.session.room.model.message.PollType -import org.matrix.android.sdk.api.session.room.send.SendState - -private val A_MESSAGE_INFORMATION_DATA = MessageInformationData( - eventId = "eventId", - senderId = "senderId", - ageLocalTS = 0, - avatarUrl = "", - sendState = SendState.SENT, - messageLayout = TimelineMessageLayout.Default(showAvatar = true, showDisplayName = true, showTimestamp = true), - reactionsSummary = ReactionsSummaryData(), - sentByMe = true, -) - -private val A_POLL_RESPONSE_DATA = PollResponseData( - myVote = null, - votes = emptyMap(), -) - -private val A_POLL_CONTENT = MessagePollContent( - unstablePollCreationInfo = PollCreationInfo( - question = PollQuestion( - unstableQuestion = "What is your favourite coffee?" - ), - kind = PollType.UNDISCLOSED_UNSTABLE, - maxSelections = 1, - answers = listOf( - PollAnswer( - id = "5ef5f7b0-c9a1-49cf-a0b3-374729a43e76", - unstableAnswer = "Double Espresso" - ), - PollAnswer( - id = "ec1a4db0-46d8-4d7a-9bb6-d80724715938", - unstableAnswer = "Macchiato" - ), - PollAnswer( - id = "3677ca8e-061b-40ab-bffe-b22e4e88fcad", - unstableAnswer = "Iced Coffee" - ), - ) - ) -) - -class PollItemFactoryTest { - - private val testDispatcher = UnconfinedTestDispatcher() - - @get:Rule - val mvRxTestRule = MvRxTestRule( - testDispatcher = testDispatcher // See https://github.com/airbnb/mavericks/issues/599 - ) - - private lateinit var pollItemFactory: PollItemFactory - - @Before - fun setup() { - // We are not going to test any UI related code - pollItemFactory = PollItemFactory( - stringProvider = mockk(), - avatarSizeProvider = mockk(), - colorProvider = mockk(), - dimensionConverter = mockk(), - ) - } - - @After - fun tearDown() { - unmockkAll() - } - - @Test - fun `given a sending poll state then PollState is Sending`() = runTest { - val sendingPollInformationData = A_MESSAGE_INFORMATION_DATA.copy(sendState = SendState.SENDING) - pollItemFactory.createPollState( - informationData = sendingPollInformationData, - pollResponseSummary = A_POLL_RESPONSE_DATA, - pollContent = A_POLL_CONTENT, - ) shouldBe PollState.Sending - } - - @Test - fun `given a sent poll state when poll is closed then PollState is Ended`() = runTest { - val closedPollSummary = A_POLL_RESPONSE_DATA.copy(isClosed = true) - - pollItemFactory.createPollState( - informationData = A_MESSAGE_INFORMATION_DATA, - pollResponseSummary = closedPollSummary, - pollContent = A_POLL_CONTENT, - ) shouldBe PollState.Ended - } - - @Test - fun `given a sent poll when undisclosed poll type is selected then PollState is Undisclosed`() = runTest { - pollItemFactory.createPollState( - informationData = A_MESSAGE_INFORMATION_DATA, - pollResponseSummary = A_POLL_RESPONSE_DATA, - pollContent = A_POLL_CONTENT, - ) shouldBe PollState.Undisclosed - } - - @Test - fun `given a sent poll when my vote exists then PollState is Voted`() = runTest { - val votedPollData = A_POLL_RESPONSE_DATA.copy( - totalVotes = 1, - myVote = "5ef5f7b0-c9a1-49cf-a0b3-374729a43e76", - ) - val disclosedPollContent = A_POLL_CONTENT.copy( - unstablePollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()?.copy( - kind = PollType.DISCLOSED_UNSTABLE - ) - ) - - pollItemFactory.createPollState( - informationData = A_MESSAGE_INFORMATION_DATA, - pollResponseSummary = votedPollData, - pollContent = disclosedPollContent, - ) shouldBeEqualTo PollState.Voted(1) - } - - @Test - fun `given a sent poll when poll type is disclosed then PollState is Ready`() = runTest { - val disclosedPollContent = A_POLL_CONTENT.copy( - unstablePollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()?.copy( - kind = PollType.DISCLOSED_UNSTABLE - ) - ) - - pollItemFactory.createPollState( - informationData = A_MESSAGE_INFORMATION_DATA, - pollResponseSummary = A_POLL_RESPONSE_DATA, - pollContent = disclosedPollContent, - ) shouldBe PollState.Ready - } - - @Test - fun `given a sending poll then all option view states is PollSending`() = runTest { - with(pollItemFactory) { - A_POLL_CONTENT - .getBestPollCreationInfo() - ?.answers - ?.mapToOptions(PollState.Sending, A_MESSAGE_INFORMATION_DATA) - ?.forEachIndexed { index, pollOptionViewState -> - A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.get(index)?.let { option -> - pollOptionViewState shouldBeEqualTo PollOptionViewState.PollSending(option.id ?: "", option.getBestAnswer() ?: "") - } - } - } - } - - @Test - fun `given a sent poll then all option view states is PollReady`() = runTest { - with(pollItemFactory) { - A_POLL_CONTENT - .getBestPollCreationInfo() - ?.answers - ?.mapToOptions(PollState.Sending, A_MESSAGE_INFORMATION_DATA) - ?.forEachIndexed { index, pollOptionViewState -> - A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.get(index)?.let { option -> - pollOptionViewState shouldBeEqualTo PollOptionViewState.PollSending(option.id ?: "", option.getBestAnswer() ?: "") - } - } - } - } - - @Test - fun `given a sent poll when a vote is cast then all option view states is PollVoted`() = runTest { - with(pollItemFactory) { - A_POLL_CONTENT - .getBestPollCreationInfo() - ?.answers - ?.mapToOptions(PollState.Voted(1), A_MESSAGE_INFORMATION_DATA) - ?.forEachIndexed { index, pollOptionViewState -> - A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.get(index)?.let { option -> - val voteSummary = A_MESSAGE_INFORMATION_DATA.pollResponseAggregatedSummary?.votes?.get(option.id) - pollOptionViewState shouldBeEqualTo PollOptionViewState.PollVoted( - optionId = option.id ?: "", - optionAnswer = option.getBestAnswer() ?: "", - voteCount = A_MESSAGE_INFORMATION_DATA.pollResponseAggregatedSummary?.totalVotes ?: 0, - votePercentage = voteSummary?.percentage ?: 0.0, - isSelected = A_MESSAGE_INFORMATION_DATA.pollResponseAggregatedSummary?.myVote == option.id, - ) - } - } - } - } - - @Test - fun `given a sent poll when the poll is undisclosed then all option view states is PollUndisclosed`() = runTest { - with(pollItemFactory) { - A_POLL_CONTENT - .getBestPollCreationInfo() - ?.answers - ?.mapToOptions(PollState.Undisclosed, A_MESSAGE_INFORMATION_DATA) - ?.forEachIndexed { index, pollOptionViewState -> - A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.get(index)?.let { option -> - pollOptionViewState shouldBeEqualTo PollOptionViewState.PollUndisclosed( - optionId = option.id ?: "", - optionAnswer = option.getBestAnswer() ?: "", - isSelected = A_MESSAGE_INFORMATION_DATA.pollResponseAggregatedSummary?.myVote == option.id, - ) - } - } - } - } - - @Test - fun `given an ended poll then all option view states is Ended`() = runTest { - with(pollItemFactory) { - A_POLL_CONTENT - .getBestPollCreationInfo() - ?.answers - ?.mapToOptions(PollState.Ended, A_MESSAGE_INFORMATION_DATA) - ?.forEachIndexed { index, pollOptionViewState -> - A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.get(index)?.let { option -> - val voteSummary = A_MESSAGE_INFORMATION_DATA.pollResponseAggregatedSummary?.votes?.get(option.id) - val voteCount = A_MESSAGE_INFORMATION_DATA.pollResponseAggregatedSummary?.totalVotes ?: 0 - val winnerVoteCount = A_MESSAGE_INFORMATION_DATA.pollResponseAggregatedSummary?.winnerVoteCount ?: 0 - pollOptionViewState shouldBeEqualTo PollOptionViewState.PollEnded( - optionId = option.id ?: "", - optionAnswer = option.getBestAnswer() ?: "", - voteCount = voteCount, - votePercentage = voteSummary?.percentage ?: 0.0, - isWinner = winnerVoteCount != 0 && voteCount == winnerVoteCount, - ) - } - } - } - } - - @Test - fun `given a poll state when it is not Sending and not Ended then the poll is votable`() = runTest { - val sendingPollState = PollState.Sending - sendingPollState.isVotable() shouldBe false - val readyPollState = PollState.Ready - readyPollState.isVotable() shouldBe true - val votedPollState = PollState.Voted(1) - votedPollState.isVotable() shouldBe true - val undisclosedPollState = PollState.Undisclosed - undisclosedPollState.isVotable() shouldBe true - var endedPollState = PollState.Ended - endedPollState.isVotable() shouldBe false - } -}