kopia lustrzana https://github.com/ryukoposting/Signal-Android
Implement specification testing for StoryViewerViewModel.
rodzic
469879c211
commit
19861ef0d1
|
@ -4,6 +4,7 @@ import android.os.Bundle
|
|||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.LiveDataReactiveStreams
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
@ -40,7 +41,7 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie
|
|||
storyPager.isUserInputEnabled = !it
|
||||
}
|
||||
|
||||
viewModel.state.observe(viewLifecycleOwner) { state ->
|
||||
LiveDataReactiveStreams.fromPublisher(viewModel.state).observe(viewLifecycleOwner) { state ->
|
||||
adapter.setPages(state.pages)
|
||||
if (state.pages.isNotEmpty() && storyPager.currentItem != state.page) {
|
||||
storyPager.setCurrentItem(state.page, state.previousPage > -1)
|
||||
|
@ -65,11 +66,11 @@ class StoryViewerFragment : Fragment(R.layout.stories_viewer_fragment), StoryVie
|
|||
}
|
||||
|
||||
override fun onGoToPreviousStory(recipientId: RecipientId) {
|
||||
viewModel.onGoToPreviousStory(recipientId)
|
||||
viewModel.onGoToPrevious(recipientId)
|
||||
}
|
||||
|
||||
override fun onFinishedPosts(recipientId: RecipientId) {
|
||||
viewModel.onFinishedPosts(recipientId)
|
||||
viewModel.onGoToNext(recipientId)
|
||||
}
|
||||
|
||||
override fun onStoryHidden(recipientId: RecipientId) {
|
||||
|
|
|
@ -8,7 +8,10 @@ import org.thoughtcrime.securesms.database.model.StoryResult
|
|||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
||||
class StoryViewerRepository {
|
||||
/**
|
||||
* Open for testing
|
||||
*/
|
||||
open class StoryViewerRepository {
|
||||
fun getStories(): Single<List<RecipientId>> {
|
||||
return Single.fromCallable {
|
||||
val storyResults: List<StoryResult> = SignalDatabase.mms.orderedStoryRecipientsAndIds.distinctBy { it.recipientId }
|
||||
|
|
|
@ -4,21 +4,23 @@ import androidx.lifecycle.LiveData
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable
|
||||
import io.reactivex.rxjava3.kotlin.plusAssign
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.livedata.Store
|
||||
import kotlin.math.min
|
||||
import org.thoughtcrime.securesms.util.rx.RxStore
|
||||
import kotlin.math.max
|
||||
|
||||
class StoryViewerViewModel(
|
||||
private val startRecipientId: RecipientId,
|
||||
private val repository: StoryViewerRepository
|
||||
) : ViewModel() {
|
||||
|
||||
private val store = Store(StoryViewerState())
|
||||
private val store = RxStore(StoryViewerState())
|
||||
private val disposables = CompositeDisposable()
|
||||
|
||||
val state: LiveData<StoryViewerState> = store.stateLiveData
|
||||
val stateSnapshot: StoryViewerState get() = store.state
|
||||
val state: Flowable<StoryViewerState> = store.stateFlowable
|
||||
|
||||
private val scrollStatePublisher: MutableLiveData<Boolean> = MutableLiveData(false)
|
||||
val isScrolling: LiveData<Boolean> = scrollStatePublisher
|
||||
|
@ -66,7 +68,7 @@ class StoryViewerViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun onFinishedPosts(recipientId: RecipientId) {
|
||||
fun onGoToNext(recipientId: RecipientId) {
|
||||
store.update {
|
||||
if (it.pages[it.page] == recipientId) {
|
||||
updatePages(it, it.page + 1)
|
||||
|
@ -76,10 +78,10 @@ class StoryViewerViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun onGoToPreviousStory(recipientId: RecipientId) {
|
||||
fun onGoToPrevious(recipientId: RecipientId) {
|
||||
store.update {
|
||||
if (it.pages[it.page] == recipientId) {
|
||||
updatePages(it, min(0, it.page - 1))
|
||||
updatePages(it, max(0, it.page - 1))
|
||||
} else {
|
||||
it
|
||||
}
|
||||
|
|
|
@ -238,7 +238,7 @@ class StoryViewerPageFragment :
|
|||
viewModel.setIsUserScrollingParent(isScrolling)
|
||||
}
|
||||
|
||||
sharedViewModel.state.observe(viewLifecycleOwner) { parentState ->
|
||||
LiveDataReactiveStreams.fromPublisher(sharedViewModel.state).observe(viewLifecycleOwner) { parentState ->
|
||||
if (parentState.pages.size <= parentState.page) {
|
||||
viewModel.setIsSelectedPage(false)
|
||||
} else if (storyRecipientId == parentState.pages[parentState.page]) {
|
||||
|
@ -753,7 +753,7 @@ class StoryViewerPageFragment :
|
|||
}
|
||||
|
||||
override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
|
||||
val isFirstStory = sharedViewModel.state.value?.page == 0
|
||||
val isFirstStory = sharedViewModel.stateSnapshot.page == 0
|
||||
val isXMagnitudeGreaterThanYMagnitude = abs(distanceX) > abs(distanceY) || viewToTranslate.translationX > 0f
|
||||
val isFirstAndHasYTranslationOrNegativeY = isFirstStory && (viewToTranslate.translationY > 0f || distanceY < 0f)
|
||||
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
package org.thoughtcrime.securesms.stories.viewer
|
||||
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.plugins.RxJavaPlugins
|
||||
import io.reactivex.rxjava3.schedulers.TestScheduler
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.whenever
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
|
||||
class StoryViewerViewModelTest {
|
||||
private val testScheduler = TestScheduler()
|
||||
private val repository: StoryViewerRepository = mock()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
RxJavaPlugins.setInitComputationSchedulerHandler { testScheduler }
|
||||
RxJavaPlugins.setComputationSchedulerHandler { testScheduler }
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
RxJavaPlugins.reset()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given five stories, when I initialize with story 2, then I expect to be on the right page`() {
|
||||
// GIVEN
|
||||
val stories: List<RecipientId> = (1L..5L).map(RecipientId::from)
|
||||
val startStory = RecipientId.from(2L)
|
||||
whenever(repository.getStories()).doReturn(Single.just(stories))
|
||||
|
||||
// WHEN
|
||||
val testSubject = StoryViewerViewModel(startStory, repository)
|
||||
testScheduler.triggerActions()
|
||||
|
||||
// THEN
|
||||
val expectedStartIndex = testSubject.stateSnapshot.pages.indexOf(startStory)
|
||||
val actualStartIndex = testSubject.stateSnapshot.page
|
||||
|
||||
assertEquals(expectedStartIndex, actualStartIndex)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given five stories and am on 1, when I onGoToNext, then I expect to go to 2`() {
|
||||
// GIVEN
|
||||
val stories: List<RecipientId> = (1L..5L).map(RecipientId::from)
|
||||
val startStory = RecipientId.from(1L)
|
||||
whenever(repository.getStories()).doReturn(Single.just(stories))
|
||||
val testSubject = StoryViewerViewModel(startStory, repository)
|
||||
testScheduler.triggerActions()
|
||||
|
||||
// WHEN
|
||||
testSubject.onGoToNext(RecipientId.from(1L))
|
||||
testScheduler.triggerActions()
|
||||
|
||||
// THEN
|
||||
val expectedIndex = 1
|
||||
val actualIndex = testSubject.stateSnapshot.page
|
||||
|
||||
assertEquals(expectedIndex, actualIndex)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given five stories and am on last, when I onGoToNext, then I expect to go to size`() {
|
||||
// GIVEN
|
||||
val stories: List<RecipientId> = (1L..5L).map(RecipientId::from)
|
||||
val startStory = stories.last()
|
||||
whenever(repository.getStories()).doReturn(Single.just(stories))
|
||||
val testSubject = StoryViewerViewModel(startStory, repository)
|
||||
testScheduler.triggerActions()
|
||||
|
||||
// WHEN
|
||||
testSubject.onGoToNext(startStory)
|
||||
testScheduler.triggerActions()
|
||||
|
||||
// THEN
|
||||
val expectedIndex = stories.size
|
||||
val actualIndex = testSubject.stateSnapshot.page
|
||||
|
||||
assertEquals(expectedIndex, actualIndex)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given five stories and am on last, when I onGoToPrevious, then I expect to go to last - 1`() {
|
||||
// GIVEN
|
||||
val stories: List<RecipientId> = (1L..5L).map(RecipientId::from)
|
||||
val startStory = stories.last()
|
||||
whenever(repository.getStories()).doReturn(Single.just(stories))
|
||||
val testSubject = StoryViewerViewModel(startStory, repository)
|
||||
testScheduler.triggerActions()
|
||||
|
||||
// WHEN
|
||||
testSubject.onGoToPrevious(startStory)
|
||||
testScheduler.triggerActions()
|
||||
|
||||
// THEN
|
||||
val expectedIndex = stories.lastIndex - 1
|
||||
val actualIndex = testSubject.stateSnapshot.page
|
||||
|
||||
assertEquals(expectedIndex, actualIndex)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given five stories and am on first, when I onGoToPrevious, then I expect stay at 0`() {
|
||||
// GIVEN
|
||||
val stories: List<RecipientId> = (1L..5L).map(RecipientId::from)
|
||||
val startStory = stories.first()
|
||||
whenever(repository.getStories()).doReturn(Single.just(stories))
|
||||
val testSubject = StoryViewerViewModel(startStory, repository)
|
||||
testScheduler.triggerActions()
|
||||
|
||||
// WHEN
|
||||
testSubject.onGoToPrevious(startStory)
|
||||
testScheduler.triggerActions()
|
||||
|
||||
// THEN
|
||||
val expectedIndex = 0
|
||||
val actualIndex = testSubject.stateSnapshot.page
|
||||
|
||||
assertEquals(expectedIndex, actualIndex)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given five stories and am on first, when I setSelectedPage, then I expect to go to the page I selected`() {
|
||||
// GIVEN
|
||||
val stories: List<RecipientId> = (1L..5L).map(RecipientId::from)
|
||||
val startStory = stories.first()
|
||||
whenever(repository.getStories()).doReturn(Single.just(stories))
|
||||
val testSubject = StoryViewerViewModel(startStory, repository)
|
||||
testScheduler.triggerActions()
|
||||
|
||||
// WHEN
|
||||
testSubject.setSelectedPage(2)
|
||||
testScheduler.triggerActions()
|
||||
|
||||
// THEN
|
||||
val expectedIndex = 2
|
||||
val actualIndex = testSubject.stateSnapshot.page
|
||||
|
||||
assertEquals(expectedIndex, actualIndex)
|
||||
}
|
||||
}
|
Ładowanie…
Reference in New Issue