Merge pull request #6320 from vector-im/feature/ons/poll_unit_tests

CreatePollViewModel unit tests [PSF-1122]
This commit is contained in:
Onuray Sahin 2022-06-17 12:14:01 +03:00 committed by GitHub
commit 242cc28daa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 490 additions and 0 deletions

1
changelog.d/6320.misc Normal file
View file

@ -0,0 +1 @@
CreatePollViewModel unit tests

View file

@ -0,0 +1,252 @@
/*
* 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.poll.create
import com.airbnb.mvrx.test.MvRxTestRule
import im.vector.app.features.poll.PollMode
import im.vector.app.test.fakes.FakeCreatePollViewStates.A_FAKE_OPTIONS
import im.vector.app.test.fakes.FakeCreatePollViewStates.A_FAKE_QUESTION
import im.vector.app.test.fakes.FakeCreatePollViewStates.A_FAKE_ROOM_ID
import im.vector.app.test.fakes.FakeCreatePollViewStates.A_POLL_START_TIMELINE_EVENT
import im.vector.app.test.fakes.FakeCreatePollViewStates.createPollArgs
import im.vector.app.test.fakes.FakeCreatePollViewStates.editPollArgs
import im.vector.app.test.fakes.FakeCreatePollViewStates.editedPollViewState
import im.vector.app.test.fakes.FakeCreatePollViewStates.initialCreatePollViewState
import im.vector.app.test.fakes.FakeCreatePollViewStates.pollViewStateWithOnlyQuestion
import im.vector.app.test.fakes.FakeCreatePollViewStates.pollViewStateWithQuestionAndEnoughOptions
import im.vector.app.test.fakes.FakeCreatePollViewStates.pollViewStateWithQuestionAndEnoughOptionsButDeletedLastOption
import im.vector.app.test.fakes.FakeCreatePollViewStates.pollViewStateWithQuestionAndMaxOptions
import im.vector.app.test.fakes.FakeCreatePollViewStates.pollViewStateWithQuestionAndNotEnoughOptions
import im.vector.app.test.fakes.FakeCreatePollViewStates.pollViewStateWithoutQuestionAndEnoughOptions
import im.vector.app.test.fakes.FakeSession
import im.vector.app.test.test
import io.mockk.unmockkAll
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
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.PollType
class CreatePollViewModelTest {
private val testDispatcher = UnconfinedTestDispatcher()
@get:Rule
val mvRxTestRule = MvRxTestRule(
testDispatcher = testDispatcher // See https://github.com/airbnb/mavericks/issues/599
)
private val fakeSession = FakeSession()
private fun createPollViewModel(pollMode: PollMode): CreatePollViewModel {
return if (pollMode == PollMode.EDIT) {
CreatePollViewModel(CreatePollViewState(editPollArgs), fakeSession)
} else {
CreatePollViewModel(CreatePollViewState(createPollArgs), fakeSession)
}
}
@Before
fun setup() {
fakeSession
.roomService()
.getRoom(A_FAKE_ROOM_ID)
.timelineService()
.givenTimelineEvent(A_POLL_START_TIMELINE_EVENT)
}
@After
fun tearDown() {
unmockkAll()
}
@Test
fun `given the view model is initialized then poll cannot be created and more options can be added`() = runTest {
val createPollViewModel = createPollViewModel(PollMode.CREATE)
val test = createPollViewModel.test()
test
.assertLatestState(initialCreatePollViewState)
.finish()
}
@Test
fun `given there is not any options when the question is added then poll cannot be created and more options can be added`() = runTest {
val createPollViewModel = createPollViewModel(PollMode.CREATE)
val test = createPollViewModel.test()
createPollViewModel.handle(CreatePollAction.OnQuestionChanged(A_FAKE_QUESTION))
test
.assertLatestState(pollViewStateWithOnlyQuestion)
.finish()
}
@Test
fun `given there is not enough options when the question is added then poll cannot be created and more options can be added`() = runTest {
val createPollViewModel = createPollViewModel(PollMode.CREATE)
val test = createPollViewModel.test()
createPollViewModel.handle(CreatePollAction.OnQuestionChanged(A_FAKE_QUESTION))
repeat(CreatePollViewModel.MIN_OPTIONS_COUNT - 1) {
createPollViewModel.handle(CreatePollAction.OnOptionChanged(it, A_FAKE_OPTIONS[it]))
}
test
.assertLatestState(pollViewStateWithQuestionAndNotEnoughOptions)
.finish()
}
@Test
fun `given there is not a question when enough options are added then poll cannot be created and more options can be added`() = runTest {
val createPollViewModel = createPollViewModel(PollMode.CREATE)
val test = createPollViewModel.test()
repeat(CreatePollViewModel.MIN_OPTIONS_COUNT) {
createPollViewModel.handle(CreatePollAction.OnOptionChanged(it, A_FAKE_OPTIONS[it]))
}
test
.assertLatestState(pollViewStateWithoutQuestionAndEnoughOptions)
.finish()
}
@Test
fun `given there is a question when enough options are added then poll can be created and more options can be added`() = runTest {
val createPollViewModel = createPollViewModel(PollMode.CREATE)
val test = createPollViewModel.test()
createPollViewModel.handle(CreatePollAction.OnQuestionChanged(A_FAKE_QUESTION))
repeat(CreatePollViewModel.MIN_OPTIONS_COUNT) {
createPollViewModel.handle(CreatePollAction.OnOptionChanged(it, A_FAKE_OPTIONS[it]))
}
test
.assertLatestState(pollViewStateWithQuestionAndEnoughOptions)
.finish()
}
@Test
fun `given there is a question when max number of options are added then poll can be created and more options cannot be added`() = runTest {
val createPollViewModel = createPollViewModel(PollMode.CREATE)
val test = createPollViewModel.test()
createPollViewModel.handle(CreatePollAction.OnQuestionChanged(A_FAKE_QUESTION))
repeat(CreatePollViewModel.MAX_OPTIONS_COUNT) {
if (it >= CreatePollViewModel.MIN_OPTIONS_COUNT) {
createPollViewModel.handle(CreatePollAction.OnAddOption)
}
createPollViewModel.handle(CreatePollAction.OnOptionChanged(it, A_FAKE_OPTIONS[it]))
}
test
.assertLatestState(pollViewStateWithQuestionAndMaxOptions)
.finish()
}
@Test
fun `given an initial poll state when poll type is changed then view state is updated accordingly`() = runTest {
val createPollViewModel = createPollViewModel(PollMode.CREATE)
val test = createPollViewModel.test()
createPollViewModel.handle(CreatePollAction.OnPollTypeChanged(PollType.UNDISCLOSED))
createPollViewModel.handle(CreatePollAction.OnPollTypeChanged(PollType.DISCLOSED))
test
.assertStatesChanges(
initialCreatePollViewState,
{ copy(pollType = PollType.UNDISCLOSED) },
{ copy(pollType = PollType.DISCLOSED) },
)
.finish()
}
@Test
fun `given there is not a question and enough options when create poll is requested then error view events are post`() = runTest {
val createPollViewModel = createPollViewModel(PollMode.CREATE)
val test = createPollViewModel.test()
createPollViewModel.handle(CreatePollAction.OnCreatePoll)
createPollViewModel.handle(CreatePollAction.OnQuestionChanged(A_FAKE_QUESTION))
createPollViewModel.handle(CreatePollAction.OnCreatePoll)
createPollViewModel.handle(CreatePollAction.OnOptionChanged(0, A_FAKE_OPTIONS[0]))
createPollViewModel.handle(CreatePollAction.OnCreatePoll)
test
.assertEvents(
CreatePollViewEvents.EmptyQuestionError,
CreatePollViewEvents.NotEnoughOptionsError(requiredOptionsCount = CreatePollViewModel.MIN_OPTIONS_COUNT),
CreatePollViewEvents.NotEnoughOptionsError(requiredOptionsCount = CreatePollViewModel.MIN_OPTIONS_COUNT),
)
}
@Test
fun `given there is a question and enough options when create poll is requested then success view event is post`() = runTest {
val createPollViewModel = createPollViewModel(PollMode.CREATE)
val test = createPollViewModel.test()
createPollViewModel.handle(CreatePollAction.OnQuestionChanged(A_FAKE_QUESTION))
createPollViewModel.handle(CreatePollAction.OnOptionChanged(0, A_FAKE_OPTIONS[0]))
createPollViewModel.handle(CreatePollAction.OnOptionChanged(1, A_FAKE_OPTIONS[1]))
createPollViewModel.handle(CreatePollAction.OnCreatePoll)
test
.assertEvents(
CreatePollViewEvents.Success,
)
}
@Test
fun `given there is a question and enough options when the last option is deleted then view state should be updated accordingly`() = runTest {
val createPollViewModel = createPollViewModel(PollMode.CREATE)
val test = createPollViewModel.test()
createPollViewModel.handle(CreatePollAction.OnQuestionChanged(A_FAKE_QUESTION))
createPollViewModel.handle(CreatePollAction.OnOptionChanged(0, A_FAKE_OPTIONS[0]))
createPollViewModel.handle(CreatePollAction.OnOptionChanged(1, A_FAKE_OPTIONS[1]))
createPollViewModel.handle(CreatePollAction.OnDeleteOption(1))
test.assertLatestState(pollViewStateWithQuestionAndEnoughOptionsButDeletedLastOption)
}
@Test
fun `given an edited poll event when question and options are changed then view state is updated accordingly`() = runTest {
val createPollViewModel = createPollViewModel(PollMode.EDIT)
val test = createPollViewModel.test()
test
.assertState(editedPollViewState)
.finish()
}
@Test
fun `given an edited poll event then able to be edited`() = runTest {
val createPollViewModel = createPollViewModel(PollMode.EDIT)
val test = createPollViewModel.test()
createPollViewModel.handle(CreatePollAction.OnCreatePoll)
test
.assertEvents(
CreatePollViewEvents.Success,
)
}
}

View file

@ -86,6 +86,11 @@ class ViewModelTest<S, VE>(
return this
}
fun assertLatestState(expected: S): ViewModelTest<S, VE> {
states.assertLatestValue(expected)
return this
}
fun finish() {
states.finish()
viewEvents.finish()

View file

@ -47,6 +47,10 @@ class FlowTestObserver<T>(
return this
}
fun assertLatestValue(value: T) {
assertTrue(values.last() == value)
}
fun assertValues(values: List<T>): FlowTestObserver<T> {
assertEquals(values, this.values)
return this

View file

@ -0,0 +1,131 @@
/*
* 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.test.fakes
import im.vector.app.features.poll.PollMode
import im.vector.app.features.poll.create.CreatePollArgs
import im.vector.app.features.poll.create.CreatePollViewModel
import im.vector.app.features.poll.create.CreatePollViewState
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
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.sender.SenderInfo
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import kotlin.random.Random
object FakeCreatePollViewStates {
const val A_FAKE_ROOM_ID = "fakeRoomId"
private const val A_FAKE_EVENT_ID = "fakeEventId"
private const val A_FAKE_USER_ID = "fakeUserId"
val createPollArgs = CreatePollArgs(A_FAKE_ROOM_ID, null, PollMode.CREATE)
val editPollArgs = CreatePollArgs(A_FAKE_ROOM_ID, A_FAKE_EVENT_ID, PollMode.EDIT)
const val A_FAKE_QUESTION = "What is your favourite coffee?"
val A_FAKE_OPTIONS = List(CreatePollViewModel.MAX_OPTIONS_COUNT + 1) { "Coffee No${Random.nextInt()}" }
private val A_POLL_CONTENT = MessagePollContent(
unstablePollCreationInfo = PollCreationInfo(
question = PollQuestion(
unstableQuestion = A_FAKE_QUESTION
),
maxSelections = 1,
answers = listOf(
PollAnswer(
id = "5ef5f7b0-c9a1-49cf-a0b3-374729a43e76",
unstableAnswer = A_FAKE_OPTIONS[0]
),
PollAnswer(
id = "ec1a4db0-46d8-4d7a-9bb6-d80724715938",
unstableAnswer = A_FAKE_OPTIONS[1]
)
)
)
)
private val A_POLL_START_EVENT = Event(
type = EventType.POLL_START.first(),
eventId = A_FAKE_EVENT_ID,
originServerTs = 1652435922563,
senderId = A_FAKE_USER_ID,
roomId = A_FAKE_ROOM_ID,
content = A_POLL_CONTENT.toContent()
)
val A_POLL_START_TIMELINE_EVENT = TimelineEvent(
root = A_POLL_START_EVENT,
localId = 12345,
eventId = A_FAKE_EVENT_ID,
displayIndex = 1,
senderInfo = SenderInfo(A_FAKE_USER_ID, isUniqueDisplayName = true, avatarUrl = "", displayName = "")
)
val initialCreatePollViewState = CreatePollViewState(createPollArgs).copy(
canCreatePoll = false,
canAddMoreOptions = true
)
val pollViewStateWithOnlyQuestion = initialCreatePollViewState.copy(
question = A_FAKE_QUESTION,
canCreatePoll = false,
canAddMoreOptions = true
)
val pollViewStateWithQuestionAndNotEnoughOptions = initialCreatePollViewState.copy(
question = A_FAKE_QUESTION,
options = A_FAKE_OPTIONS.take(CreatePollViewModel.MIN_OPTIONS_COUNT - 1).toMutableList().apply { add("") },
canCreatePoll = false,
canAddMoreOptions = true
)
val pollViewStateWithoutQuestionAndEnoughOptions = initialCreatePollViewState.copy(
question = "",
options = A_FAKE_OPTIONS.take(CreatePollViewModel.MIN_OPTIONS_COUNT),
canCreatePoll = false,
canAddMoreOptions = true
)
val pollViewStateWithQuestionAndEnoughOptions = initialCreatePollViewState.copy(
question = A_FAKE_QUESTION,
options = A_FAKE_OPTIONS.take(CreatePollViewModel.MIN_OPTIONS_COUNT),
canCreatePoll = true,
canAddMoreOptions = true
)
val pollViewStateWithQuestionAndEnoughOptionsButDeletedLastOption = pollViewStateWithQuestionAndEnoughOptions.copy(
options = A_FAKE_OPTIONS.take(CreatePollViewModel.MIN_OPTIONS_COUNT).toMutableList().apply { removeLast() },
canCreatePoll = false,
canAddMoreOptions = true
)
val pollViewStateWithQuestionAndMaxOptions = initialCreatePollViewState.copy(
question = A_FAKE_QUESTION,
options = A_FAKE_OPTIONS.take(CreatePollViewModel.MAX_OPTIONS_COUNT),
canCreatePoll = true,
canAddMoreOptions = false
)
val editedPollViewState = pollViewStateWithQuestionAndEnoughOptions.copy(
editedEventId = A_FAKE_EVENT_ID,
mode = PollMode.EDIT
)
}

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2021 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.test.fakes
import io.mockk.mockk
import org.matrix.android.sdk.api.session.room.model.message.PollType
import org.matrix.android.sdk.api.session.room.model.relation.RelationService
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.util.Cancelable
class FakeRelationService : RelationService by mockk() {
private val cancelable = mockk<Cancelable>()
override fun editPoll(targetEvent: TimelineEvent, pollType: PollType, question: String, options: List<String>): Cancelable = cancelable
}

View file

@ -21,7 +21,16 @@ import org.matrix.android.sdk.api.session.room.Room
class FakeRoom(
private val fakeLocationSharingService: FakeLocationSharingService = FakeLocationSharingService(),
private val fakeSendService: FakeSendService = FakeSendService(),
private val fakeTimelineService: FakeTimelineService = FakeTimelineService(),
private val fakeRelationService: FakeRelationService = FakeRelationService(),
) : Room by mockk() {
override fun locationSharingService() = fakeLocationSharingService
override fun sendService() = fakeSendService
override fun timelineService() = fakeTimelineService
override fun relationService() = fakeRelationService
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2021 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.test.fakes
import io.mockk.mockk
import org.matrix.android.sdk.api.session.room.model.message.PollType
import org.matrix.android.sdk.api.session.room.send.SendService
import org.matrix.android.sdk.api.util.Cancelable
class FakeSendService : SendService by mockk() {
private val cancelable = mockk<Cancelable>()
override fun sendPoll(pollType: PollType, question: String, options: List<String>): Cancelable = cancelable
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2021 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.test.fakes
import io.mockk.every
import io.mockk.mockk
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.TimelineService
class FakeTimelineService : TimelineService by mockk() {
fun givenTimelineEvent(event: TimelineEvent) {
every { getTimelineEvent(any()) } returns event
}
}