Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue/8676 scmainvm tests #9142

Merged
merged 10 commits into from
Feb 7, 2019
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,6 @@ import javax.inject.Inject

private const val KEY_CURRENT_STEP = "key_current_step"
private const val KEY_SITE_CREATION_STATE = "key_site_creation_state"
private val SITE_CREATION_STEPS =
listOf(
SiteCreationStep.fromString("site_creation_segments"),
SiteCreationStep.fromString("site_creation_verticals"),
SiteCreationStep.fromString("site_creation_site_info"),
SiteCreationStep.fromString("site_creation_domains"),
SiteCreationStep.fromString("site_creation_site_preview")
)

@Parcelize
@SuppressLint("ParcelCreator")
Expand All @@ -44,7 +36,10 @@ data class SiteCreationState(

typealias NavigationTarget = WizardNavigationTarget<SiteCreationStep, SiteCreationState>

class NewSiteCreationMainVM @Inject constructor(private val tracker: NewSiteCreationTracker) : ViewModel() {
class NewSiteCreationMainVM @Inject constructor(
private val stepsProvider: NewSiteCreationStepsProvider,
private val tracker: NewSiteCreationTracker
) : ViewModel() {
private var isStarted = false
private lateinit var wizardManager: WizardManager<SiteCreationStep>
private lateinit var siteCreationState: SiteCreationState
Expand All @@ -65,11 +60,11 @@ class NewSiteCreationMainVM @Inject constructor(private val tracker: NewSiteCrea
if (savedInstanceState == null) {
tracker.trackSiteCreationAccessed()
siteCreationState = SiteCreationState()
wizardManager = WizardManager(SITE_CREATION_STEPS)
wizardManager = WizardManager(stepsProvider.getSteps())
} else {
siteCreationState = savedInstanceState.getParcelable(KEY_SITE_CREATION_STATE)
val currentStepIndex = savedInstanceState.getInt(KEY_CURRENT_STEP)
wizardManager = WizardManager(SITE_CREATION_STEPS, currentStepIndex)
wizardManager = WizardManager(stepsProvider.getSteps(), currentStepIndex)
}
isStarted = true
if (savedInstanceState == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.wordpress.android.ui.sitecreation

import org.wordpress.android.util.wizard.WizardStep
import javax.inject.Inject
import javax.inject.Singleton

enum class SiteCreationStep : WizardStep {
SEGMENTS, VERTICALS, SITE_INFO, DOMAINS, SITE_PREVIEW;
Expand All @@ -18,3 +20,16 @@ enum class SiteCreationStep : WizardStep {
}
}
}

@Singleton
class NewSiteCreationStepsProvider @Inject constructor() {
fun getSteps(): List<SiteCreationStep> {
return listOf(
SiteCreationStep.fromString("site_creation_segments"),
SiteCreationStep.fromString("site_creation_verticals"),
SiteCreationStep.fromString("site_creation_site_info"),
SiteCreationStep.fromString("site_creation_domains"),
SiteCreationStep.fromString("site_creation_site_preview")
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import android.arch.lifecycle.Observer
* Note that only one observer can be subscribed.
*/
class SingleEventObservable<T>(private val sourceLiveData: LiveData<T>) {
private var lastEvent: T? = null
var lastEvent: T? = null
private set

fun observe(owner: LifecycleOwner, observer: Observer<T>) {
if (sourceLiveData.hasObservers()) {
Expand All @@ -29,4 +30,16 @@ class SingleEventObservable<T>(private val sourceLiveData: LiveData<T>) {
}
})
}

fun observeForever(observer: Observer<T>) {
if (sourceLiveData.hasObservers()) {
throw IllegalStateException("SingleEventObservable can be observed only by a single observer.")
}
sourceLiveData.observeForever {
if (it !== lastEvent) {
lastEvent = it
observer.onChanged(it)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
package org.wordpress.android.ui.sitecreation.MainVM
oguzkocer marked this conversation as resolved.
Show resolved Hide resolved

import android.arch.core.executor.testing.InstantTaskExecutorRule
import android.arch.lifecycle.Observer
import android.os.Bundle
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.argThat
import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.junit.MockitoJUnitRunner
import org.wordpress.android.ui.sitecreation.NavigationTarget
import org.wordpress.android.ui.sitecreation.NewSiteCreationMainVM
import org.wordpress.android.ui.sitecreation.NewSiteCreationMainVM.NewSiteCreationScreenTitle.ScreenTitleEmpty
import org.wordpress.android.ui.sitecreation.NewSiteCreationMainVM.NewSiteCreationScreenTitle.ScreenTitleGeneral
import org.wordpress.android.ui.sitecreation.NewSiteCreationMainVM.NewSiteCreationScreenTitle.ScreenTitleStepCount
import org.wordpress.android.ui.sitecreation.NewSiteCreationStepsProvider
import org.wordpress.android.ui.sitecreation.SiteCreationState
import org.wordpress.android.ui.sitecreation.SiteCreationStep.DOMAINS
import org.wordpress.android.ui.sitecreation.SiteCreationStep.SEGMENTS
import org.wordpress.android.ui.sitecreation.SiteCreationStep.SITE_INFO
import org.wordpress.android.ui.sitecreation.SiteCreationStep.SITE_PREVIEW
import org.wordpress.android.ui.sitecreation.SiteCreationStep.VERTICALS
import org.wordpress.android.ui.sitecreation.misc.NewSiteCreationTracker
import org.wordpress.android.ui.sitecreation.previews.NewSitePreviewViewModel.CreateSiteState
import org.wordpress.android.ui.sitecreation.previews.NewSitePreviewViewModel.CreateSiteState.SiteCreationCompleted

private const val LOCAL_SITE_ID = 1
private const val SEGMENT_ID = 1L
private const val VERTICAL_ID = "m1v1"
private const val SITE_TITLE = "test title"
private const val SITE_TAGLINE = "test tagline"
private const val DOMAIN = "test.domain.com"

private val SITE_CREATION_STEPS = listOf(SITE_INFO, VERTICALS, SEGMENTS, DOMAINS, SITE_PREVIEW)

@RunWith(MockitoJUnitRunner::class)
class NewSiteCreationMainVMTest {
@Rule
@JvmField val rule = InstantTaskExecutorRule()

@Mock lateinit var tracker: NewSiteCreationTracker
@Mock lateinit var stepsProvider: NewSiteCreationStepsProvider
@Mock lateinit var navigationTargetObserver: Observer<NavigationTarget>
@Mock lateinit var wizardFinishedObserver: Observer<CreateSiteState>
@Mock lateinit var savedInstanceState: Bundle

@Captor
private val captorNavigationTarget: ArgumentCaptor<NavigationTarget>? = null

private lateinit var viewModel: NewSiteCreationMainVM

@Before
fun setUp() {
whenever(stepsProvider.getSteps()).thenReturn(SITE_CREATION_STEPS)
viewModel = NewSiteCreationMainVM(stepsProvider, tracker)
viewModel.start(null)
viewModel.navigationTargetObservable.observeForever(navigationTargetObserver)
viewModel.wizardFinishedObservable.observeForever(wizardFinishedObserver)
}

@Test
fun skipClickedResultsInNextStep() {
viewModel.onSkipClicked()
verifyNavigatedToNextStep()
}

@Test
fun segmentSelectedResultsInNextStep() {
viewModel.onSegmentSelected(SEGMENT_ID)
verifyNavigatedToNextStep()
}

@Test
fun verticalSelectedResultsInNextStep() {
viewModel.onVerticalsScreenFinished(VERTICAL_ID)
verifyNavigatedToNextStep()
}

@Test
fun siteInfoFinishedResultsInNextStep() {
viewModel.onInfoScreenFinished(SITE_TITLE, null)
verifyNavigatedToNextStep()
}

@Test
fun domainSelectedResultsInNextStep() {
viewModel.onDomainsScreenFinished(DOMAIN)
verifyNavigatedToNextStep()
}

@Test
fun siteCreationStateUpdatedWithSelectedSegment() {
viewModel.onSegmentSelected(SEGMENT_ID)
assertThat(viewModel.navigationTargetObservable.lastEvent!!.wizardState.segmentId).isEqualTo(SEGMENT_ID)
oguzkocer marked this conversation as resolved.
Show resolved Hide resolved
}

@Test
fun siteCreationStateUpdatedWithSelectedVertical() {
viewModel.onVerticalsScreenFinished(VERTICAL_ID)
assertThat(viewModel.navigationTargetObservable.lastEvent!!.wizardState.verticalId).isEqualTo(VERTICAL_ID)
}

@Test
fun siteCreationStateUpdatedWithSiteInfo() {
viewModel.onInfoScreenFinished(SITE_TITLE, SITE_TAGLINE)
assertThat(viewModel.navigationTargetObservable.lastEvent!!.wizardState.siteTitle).isEqualTo(SITE_TITLE)
assertThat(viewModel.navigationTargetObservable.lastEvent!!.wizardState.siteTagLine).isEqualTo(SITE_TAGLINE)
}

@Test
fun siteCreationStateUpdatedWithSelectedDomain() {
viewModel.onDomainsScreenFinished(DOMAIN)
assertThat(viewModel.navigationTargetObservable.lastEvent!!.wizardState.domain).isEqualTo(DOMAIN)
}

@Test
fun wizardFinishedInvokedOnSitePreviewCompleted() {
val state = SiteCreationCompleted(LOCAL_SITE_ID)
viewModel.onSitePreviewScreenFinished(state)

val captor = ArgumentCaptor.forClass(CreateSiteState::class.java)
verify(wizardFinishedObserver).onChanged(captor.capture())

assertThat(captor.value).isEqualTo(state)
}

@Test
fun nextStepAfterOnBackPressedIsCurrentStep() {
oguzkocer marked this conversation as resolved.
Show resolved Hide resolved
val startingStep = currentStep()
viewModel.onBackPressed() // navigate to previous step
viewModel.onSkipClicked() // navigate to next step
assertThat(currentStep()).isEqualTo(startingStep)
}

@Test
fun backSupressedOnlyForLastStep() {
oguzkocer marked this conversation as resolved.
Show resolved Hide resolved
while (!isAtLastStep()) {
assertThat(viewModel.shouldSuppressBackPress()).isFalse()
viewModel.onSkipClicked() // navigate to a next step
}
assertThat(viewModel.shouldSuppressBackPress()).isTrue()
}

@Test
fun titleForFirstStepIsGeneralSiteCreation() {
assertThat(viewModel.screenTitleForWizardStep(SITE_CREATION_STEPS.first()))
.isInstanceOf(ScreenTitleGeneral::class.java)
}

@Test
fun titleForLastStepIsEmptyTitle() {
assertThat(viewModel.screenTitleForWizardStep(SITE_CREATION_STEPS.last()))
.isInstanceOf(ScreenTitleEmpty::class.java)
}

@Test
fun titlesForOtherThanFirstAndLastStepIsStepCount() {
val isFirstStep = { index: Int -> index == 0 }
val isLastStep = { index: Int -> index == SITE_CREATION_STEPS.size - 1 }

SITE_CREATION_STEPS.filterIndexed({ index, step ->
oguzkocer marked this conversation as resolved.
Show resolved Hide resolved
!isFirstStep(index) && !isLastStep(index)
}).forEach() { step ->
oguzkocer marked this conversation as resolved.
Show resolved Hide resolved
assertThat(viewModel.screenTitleForWizardStep(step)).isInstanceOf(ScreenTitleStepCount::class.java)
}
}

@Test
fun siteCreationStateWrittenToBundle() {
viewModel.writeToBundle(savedInstanceState)
verify(savedInstanceState).putParcelable(any(), argThat() { this is SiteCreationState })
oguzkocer marked this conversation as resolved.
Show resolved Hide resolved
}

@Test
fun siteCreationStateRestored() {
val expectedState = SiteCreationState()
whenever(savedInstanceState.getParcelable<SiteCreationState>(any()))
.thenReturn(expectedState)

// we need to create a new instance of the VM as the `viewModel` has already been started in setUp()
val newViewModel = NewSiteCreationMainVM(stepsProvider, tracker)
newViewModel.start(savedInstanceState)

/* we need to navigate to the next step as the value of navigationTargetObservable isn't changed when the VM
is restored from a savedInstanceState. */
newViewModel.onSkipClicked()

newViewModel.navigationTargetObservable.observeForever(navigationTargetObserver)
assertThat(newViewModel.navigationTargetObservable.lastEvent!!.wizardState).isSameAs(expectedState)
}

private fun verifyNavigatedToNextStep() {
oguzkocer marked this conversation as resolved.
Show resolved Hide resolved
// onChange invoked after viewModel.start() and after viewModel.onSkipClicked()
oguzkocer marked this conversation as resolved.
Show resolved Hide resolved
verify(navigationTargetObserver, times(2)).onChanged(captorNavigationTarget!!.capture())
}

private fun currentStep() = viewModel.navigationTargetObservable.lastEvent!!.wizardStep

private fun isAtLastStep() = SITE_CREATION_STEPS.last().equals(currentStep())
oguzkocer marked this conversation as resolved.
Show resolved Hide resolved
}