AddSiteViewModelTest

This commit is contained in:
Aidan Follestad 2018-12-06 19:41:12 -08:00
parent 98327c8c5b
commit b57f645c98
4 changed files with 423 additions and 295 deletions

View file

@ -16,6 +16,8 @@
package com.afollestad.nocknock.ui.addsite
import androidx.annotation.CheckResult
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.PRIVATE
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
@ -74,10 +76,7 @@ class AddSiteViewModel(
@CheckResult fun onUrlWarningVisibility(): LiveData<Boolean> {
return url.map {
val parsed = HttpUrl.parse(it)
return@map it.isNotEmpty() &&
parsed != null &&
parsed.scheme() != "http" &&
parsed.scheme() != "https"
return@map it.isNotEmpty() && parsed == null
}
}
@ -85,11 +84,10 @@ class AddSiteViewModel(
@CheckResult fun onValidationModeDescription(): LiveData<Int> {
return validationMode.map {
when (it) {
when (it!!) {
STATUS_CODE -> R.string.validation_mode_status_desc
TERM_SEARCH -> R.string.validation_mode_term_desc
JAVASCRIPT -> R.string.validation_mode_javascript_desc
else -> throw IllegalStateException("Unknown validation mode: $it")
}
}
}
@ -127,13 +125,15 @@ class AddSiteViewModel(
}
// Utilities
private fun getCheckIntervalMs(): Long {
@VisibleForTesting(otherwise = PRIVATE)
fun getCheckIntervalMs(): Long {
val value = checkIntervalValue.value ?: return 0
val unit = checkIntervalUnit.value ?: return 0
return value * unit
}
private fun getValidationArgs(): String? {
@VisibleForTesting(otherwise = PRIVATE)
fun getValidationArgs(): String? {
return when (validationMode.value) {
TERM_SEARCH -> validationSearchTerm.value
JAVASCRIPT -> validationScript.value
@ -184,13 +184,13 @@ class AddSiteViewModel(
}
// Validate arguments
if (validationMode == TERM_SEARCH &&
if (validationMode.value == TERM_SEARCH &&
validationSearchTerm.value.isNullOrEmpty()
) {
errorCount++
validationSearchTermError.value = R.string.please_enter_search_term
validationScriptError.value = null
} else if (validationMode == JAVASCRIPT &&
} else if (validationMode.value == JAVASCRIPT &&
validationScript.value.isNullOrEmpty()
) {
errorCount++

View file

@ -25,7 +25,9 @@ import com.google.common.truth.Truth.assertWithMessage
class TestLiveData<T>(data: LiveData<T>) {
private val receivedValues = mutableListOf<T>()
private val observer = Observer<T> { receivedValues.add(it) }
private val observer = Observer<T> { emission ->
emission?.let { receivedValues.add(it) }
}
init {
data.observeForever(observer)

View file

@ -15,287 +15,411 @@
*/
package com.afollestad.nocknock.ui.addsite
//import com.afollestad.nocknock.R.string
//import com.afollestad.nocknock.data.model.Site
//import com.afollestad.nocknock.data.model.SiteSettings
//import com.afollestad.nocknock.data.model.ValidationMode.JAVASCRIPT
//import com.afollestad.nocknock.data.model.ValidationMode.STATUS_CODE
//import com.afollestad.nocknock.data.model.ValidationMode.TERM_SEARCH
//import com.afollestad.nocknock.engine.validation.ValidationManager
//import com.afollestad.nocknock.mockDatabase
//import com.afollestad.nocknock.ui.addsite.AddSiteView
//import com.afollestad.nocknock.ui.addsite.InputErrors
//import com.afollestad.nocknock.ui.addsite.RealAddSitePresenter
//import com.afollestad.nocknock.utilities.ext.ScopeReceiver
//import com.google.common.truth.Truth.assertThat
//import com.nhaarman.mockitokotlin2.any
//import com.nhaarman.mockitokotlin2.argumentCaptor
//import com.nhaarman.mockitokotlin2.doAnswer
//import com.nhaarman.mockitokotlin2.mock
//import com.nhaarman.mockitokotlin2.never
//import com.nhaarman.mockitokotlin2.times
//import com.nhaarman.mockitokotlin2.verify
//import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
//import com.nhaarman.mockitokotlin2.whenever
//import kotlinx.coroutines.runBlocking
//import org.junit.After
//import org.junit.Before
//import org.junit.Test
//
//class AddSiteViewModelTest {
//
// private val database = mockDatabase()
// private val checkStatusManager = mock<ValidationManager>()
// private val view = mock<AddSiteView>()
//
// private val presenter = RealAddSitePresenter(
// database,
// checkStatusManager
// )
//
// @Before fun setup() {
// doAnswer {
// val exec = it.getArgument<ScopeReceiver>(1)
// runBlocking { exec() }
// Unit
// }.whenever(view)
// .scopeWhileAttached(any(), any())
//
// presenter.takeView(view)
// }
//
// @After fun destroy() {
// presenter.dropView()
// }
//
// @Test fun onUrlInputFocusChange_focused() {
// presenter.onUrlInputFocusChange(true, "hello")
// verifyNoMoreInteractions(view)
// }
//
// @Test fun onUrlInputFocusChange_empty() {
// presenter.onUrlInputFocusChange(false, "")
// verifyNoMoreInteractions(view)
// }
//
// @Test fun onUrlInputFocusChange_notHttpHttps() {
// presenter.onUrlInputFocusChange(false, "ftp://hello.com")
// verify(view).showOrHideUrlSchemeWarning(true)
// }
//
// @Test fun onUrlInputFocusChange_isHttpOrHttps() {
// presenter.onUrlInputFocusChange(false, "http://hello.com")
// presenter.onUrlInputFocusChange(false, "https://hello.com")
// verify(view, times(2)).showOrHideUrlSchemeWarning(false)
// }
//
// @Test fun onValidationModeSelected_statusCode() {
// presenter.onValidationModeSelected(0)
// verify(view).showOrHideValidationSearchTerm(false)
// verify(view).showOrHideScriptInput(false)
// verify(view).setValidationModeDescription(
// string.validation_mode_status_desc
// )
// }
//
// @Test fun onValidationModeSelected_termSearch() {
// presenter.onValidationModeSelected(1)
// verify(view).showOrHideValidationSearchTerm(true)
// verify(view).showOrHideScriptInput(false)
// verify(view).setValidationModeDescription(
// string.validation_mode_term_desc
// )
// }
//
// @Test fun onValidationModeSelected_javaScript() {
// presenter.onValidationModeSelected(2)
// verify(view).showOrHideValidationSearchTerm(false)
// verify(view).showOrHideScriptInput(true)
// verify(view).setValidationModeDescription(
// string.validation_mode_javascript_desc
// )
// }
//
// @Test(expected = IllegalStateException::class)
// fun onValidationModeSelected_other() {
// presenter.onValidationModeSelected(3)
// }
//
// @Test fun commit_nameError() {
// presenter.commit(
// "",
// "https://test.com",
// 1,
// STATUS_CODE,
// null,
// 60000
// )
//
// val inputErrorsCaptor = argumentCaptor<InputErrors>()
// verify(view).setInputErrors(inputErrorsCaptor.capture())
// verify(checkStatusManager, never())
// .scheduleCheck(any(), any(), any(), any())
//
// val errors = inputErrorsCaptor.firstValue
// assertThat(errors.name).isEqualTo(string.please_enter_name)
// }
//
// @Test fun commit_urlEmptyError() {
// presenter.commit(
// "Testing",
// "",
// 1,
// STATUS_CODE,
// null,
// 60000
// )
//
// val inputErrorsCaptor = argumentCaptor<InputErrors>()
// verify(view).setInputErrors(inputErrorsCaptor.capture())
// verify(checkStatusManager, never())
// .scheduleCheck(any(), any(), any(), any())
//
// val errors = inputErrorsCaptor.firstValue
// assertThat(errors.url).isEqualTo(string.please_enter_url)
// }
//
// @Test fun commit_urlFormatError() {
// presenter.commit(
// "Testing",
// "ftp://hello.com",
// 1,
// STATUS_CODE,
// null,
// 60000
// )
//
// val inputErrorsCaptor = argumentCaptor<InputErrors>()
// verify(view).setInputErrors(inputErrorsCaptor.capture())
// verify(checkStatusManager, never())
// .scheduleCheck(any(), any(), any(), any())
//
// val errors = inputErrorsCaptor.firstValue
// assertThat(errors.url).isEqualTo(string.please_enter_valid_url)
// }
//
// @Test fun commit_checkIntervalError() {
// presenter.commit(
// "Testing",
// "https://hello.com",
// -1,
// STATUS_CODE,
// null,
// 60000
// )
//
// val inputErrorsCaptor = argumentCaptor<InputErrors>()
// verify(view).setInputErrors(inputErrorsCaptor.capture())
// verify(checkStatusManager, never())
// .scheduleCheck(any(), any(), any(), any())
//
// val errors = inputErrorsCaptor.firstValue
// assertThat(errors.checkInterval).isEqualTo(
// string.please_enter_check_interval
// )
// }
//
// @Test fun commit_termSearchError() {
// presenter.commit(
// "Testing",
// "https://hello.com",
// 1,
// TERM_SEARCH,
// null,
// 60000
// )
//
// val inputErrorsCaptor = argumentCaptor<InputErrors>()
// verify(view).setInputErrors(inputErrorsCaptor.capture())
// verify(checkStatusManager, never())
// .scheduleCheck(any(), any(), any(), any())
//
// val errors = inputErrorsCaptor.firstValue
// assertThat(errors.termSearch).isEqualTo(
// string.please_enter_search_term
// )
// }
//
// @Test fun commit_networkTimeout_error() {
// presenter.commit(
// "Testing",
// "https://hello.com",
// 1,
// STATUS_CODE,
// null,
// 0
// )
//
// val inputErrorsCaptor = argumentCaptor<InputErrors>()
// verify(view).setInputErrors(inputErrorsCaptor.capture())
// verify(checkStatusManager, never())
// .scheduleCheck(any(), any(), any(), any())
//
// val errors = inputErrorsCaptor.firstValue
// assertThat(errors.networkTimeout).isEqualTo(
// string.please_enter_networkTimeout
// )
// }
//
// @Test fun commit_javaScript_error() {
// presenter.commit(
// "Testing",
// "https://hello.com",
// 1,
// JAVASCRIPT,
// null,
// 60000
// )
//
// val inputErrorsCaptor = argumentCaptor<InputErrors>()
// verify(view).setInputErrors(inputErrorsCaptor.capture())
// verify(checkStatusManager, never())
// .scheduleCheck(any(), any(), any(), any())
//
// val errors = inputErrorsCaptor.firstValue
// assertThat(errors.javaScript).isEqualTo(
// string.please_enter_javaScript
// )
// }
//
// @Test fun commit_success() = runBlocking {
// presenter.commit(
// "Testing",
// "https://hello.com",
// 1,
// STATUS_CODE,
// null,
// 60000
// )
//
// val siteCaptor = argumentCaptor<Site>()
// val settingsCaptor = argumentCaptor<SiteSettings>()
//
// verify(view).setLoading()
// verify(database.siteDao()).insert(siteCaptor.capture())
// verify(database.siteSettingsDao()).insert(settingsCaptor.capture())
// verify(database.validationResultsDao(), never()).insert(any())
//
// val settings = settingsCaptor.firstValue
// val model = siteCaptor.firstValue.copy(
// id = 1, // fill it in because our insert captor doesn't catch this
// settings = settings,
// lastResult = null
// )
//
// verify(view, never()).setInputErrors(any())
// verify(checkStatusManager).scheduleCheck(
// site = model,
// rightNow = true,
// cancelPrevious = true,
// fromFinishingJob = false
// )
//
// verify(view).setDoneLoading()
// verify(view).onSiteAdded()
// }
//}
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.afollestad.nocknock.R
import com.afollestad.nocknock.data.model.Site
import com.afollestad.nocknock.data.model.SiteSettings
import com.afollestad.nocknock.data.model.ValidationMode.JAVASCRIPT
import com.afollestad.nocknock.data.model.ValidationMode.STATUS_CODE
import com.afollestad.nocknock.data.model.ValidationMode.TERM_SEARCH
import com.afollestad.nocknock.engine.validation.ValidationManager
import com.afollestad.nocknock.mockDatabase
import com.afollestad.nocknock.test
import com.google.common.truth.Truth.assertThat
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.argumentCaptor
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.never
import com.nhaarman.mockitokotlin2.verify
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Rule
import org.junit.Test
/** @author Aidan Follestad (@afollestad) */
@ExperimentalCoroutinesApi
class AddSiteViewModelTest {
private val database = mockDatabase()
private val validationManager = mock<ValidationManager>()
@Rule @JvmField val rule = InstantTaskExecutorRule()
private val viewModel = AddSiteViewModel(
database,
validationManager,
Dispatchers.Unconfined,
Dispatchers.Unconfined
)
@After fun tearDown() = viewModel.destroy()
@Test fun onUrlWarningVisibility() {
val urlWarningVisibility = viewModel.onUrlWarningVisibility()
.test()
viewModel.url.value = ""
urlWarningVisibility.assertValues(false)
viewModel.url.value = "helloworld"
urlWarningVisibility.assertValues(true)
viewModel.url.value = "http://helloworld.com"
urlWarningVisibility.assertValues(false)
viewModel.url.value = "ftp://helloworld.com"
urlWarningVisibility.assertValues(true)
}
@Test fun onValidationModeDescription() {
val description = viewModel.onValidationModeDescription()
.test()
viewModel.validationMode.value = STATUS_CODE
description.assertValues(R.string.validation_mode_status_desc)
viewModel.validationMode.value = TERM_SEARCH
description.assertValues(R.string.validation_mode_term_desc)
viewModel.validationMode.value = JAVASCRIPT
description.assertValues(R.string.validation_mode_javascript_desc)
}
@Test fun onValidationSearchTermVisibility() {
val visibility = viewModel.onValidationSearchTermVisibility()
.test()
viewModel.validationMode.value = STATUS_CODE
visibility.assertValues(false)
viewModel.validationMode.value = TERM_SEARCH
visibility.assertValues(true)
viewModel.validationMode.value = JAVASCRIPT
visibility.assertValues(false)
}
@Test fun onValidationScriptVisibility() {
val visibility = viewModel.onValidationScriptVisibility()
.test()
viewModel.validationMode.value = STATUS_CODE
visibility.assertValues(false)
viewModel.validationMode.value = TERM_SEARCH
visibility.assertValues(false)
viewModel.validationMode.value = JAVASCRIPT
visibility.assertValues(true)
}
@Test fun getCheckIntervalMs() {
viewModel.checkIntervalValue.value = 3
viewModel.checkIntervalUnit.value = 200
assertThat(viewModel.getCheckIntervalMs()).isEqualTo(600L)
}
@Test fun getValidationArgs() {
viewModel.validationSearchTerm.value = "One"
viewModel.validationScript.value = "Two"
viewModel.validationMode.value = STATUS_CODE
assertThat(viewModel.getValidationArgs()).isNull()
viewModel.validationMode.value = TERM_SEARCH
assertThat(viewModel.getValidationArgs()).isEqualTo("One")
viewModel.validationMode.value = JAVASCRIPT
assertThat(viewModel.getValidationArgs()).isEqualTo("Two")
}
@Test fun commit_nameError() {
val onNameError = viewModel.onNameError()
.test()
val onUrlError = viewModel.onUrlError()
.test()
val onTimeoutError = viewModel.onTimeoutError()
.test()
val onCheckIntervalError = viewModel.onCheckIntervalError()
.test()
val onSearchTermError = viewModel.onValidationSearchTermError()
.test()
val onScriptError = viewModel.onValidationScriptError()
.test()
fillInModel().apply {
name.value = ""
}
val onDone = mock<() -> Unit>()
viewModel.commit(onDone)
verify(validationManager, never())
.scheduleCheck(any(), any(), any(), any())
onNameError.assertValues(R.string.please_enter_name)
onUrlError.assertNoValues()
onTimeoutError.assertNoValues()
onCheckIntervalError.assertNoValues()
onSearchTermError.assertNoValues()
onScriptError.assertNoValues()
verify(onDone, never()).invoke()
}
@Test fun commit_urlEmptyError() {
val onNameError = viewModel.onNameError()
.test()
val onUrlError = viewModel.onUrlError()
.test()
val onTimeoutError = viewModel.onTimeoutError()
.test()
val onCheckIntervalError = viewModel.onCheckIntervalError()
.test()
val onSearchTermError = viewModel.onValidationSearchTermError()
.test()
val onScriptError = viewModel.onValidationScriptError()
.test()
fillInModel().apply {
url.value = ""
}
val onDone = mock<() -> Unit>()
viewModel.commit(onDone)
verify(validationManager, never())
.scheduleCheck(any(), any(), any(), any())
onNameError.assertNoValues()
onUrlError.assertValues(R.string.please_enter_url)
onTimeoutError.assertNoValues()
onCheckIntervalError.assertNoValues()
onSearchTermError.assertNoValues()
onScriptError.assertNoValues()
verify(onDone, never()).invoke()
}
@Test fun commit_urlFormatError() {
val onNameError = viewModel.onNameError()
.test()
val onUrlError = viewModel.onUrlError()
.test()
val onTimeoutError = viewModel.onTimeoutError()
.test()
val onCheckIntervalError = viewModel.onCheckIntervalError()
.test()
val onSearchTermError = viewModel.onValidationSearchTermError()
.test()
val onScriptError = viewModel.onValidationScriptError()
.test()
fillInModel().apply {
url.value = "ftp://www.idk.com"
}
val onDone = mock<() -> Unit>()
viewModel.commit(onDone)
verify(validationManager, never())
.scheduleCheck(any(), any(), any(), any())
onNameError.assertNoValues()
onUrlError.assertValues(R.string.please_enter_valid_url)
onTimeoutError.assertNoValues()
onCheckIntervalError.assertNoValues()
onSearchTermError.assertNoValues()
onScriptError.assertNoValues()
verify(onDone, never()).invoke()
}
@Test fun commit_networkTimeout_error() {
val onNameError = viewModel.onNameError()
.test()
val onUrlError = viewModel.onUrlError()
.test()
val onTimeoutError = viewModel.onTimeoutError()
.test()
val onCheckIntervalError = viewModel.onCheckIntervalError()
.test()
val onSearchTermError = viewModel.onValidationSearchTermError()
.test()
val onScriptError = viewModel.onValidationScriptError()
.test()
fillInModel().apply {
timeout.value = 0
}
val onDone = mock<() -> Unit>()
viewModel.commit(onDone)
verify(validationManager, never())
.scheduleCheck(any(), any(), any(), any())
onNameError.assertNoValues()
onUrlError.assertNoValues()
onTimeoutError.assertValues(R.string.please_enter_networkTimeout)
onCheckIntervalError.assertNoValues()
onSearchTermError.assertNoValues()
onScriptError.assertNoValues()
verify(onDone, never()).invoke()
}
@Test fun commit_checkIntervalError() {
val onNameError = viewModel.onNameError()
.test()
val onUrlError = viewModel.onUrlError()
.test()
val onTimeoutError = viewModel.onTimeoutError()
.test()
val onCheckIntervalError = viewModel.onCheckIntervalError()
.test()
val onSearchTermError = viewModel.onValidationSearchTermError()
.test()
val onScriptError = viewModel.onValidationScriptError()
.test()
fillInModel().apply {
checkIntervalValue.value = 0
}
val onDone = mock<() -> Unit>()
viewModel.commit(onDone)
verify(validationManager, never())
.scheduleCheck(any(), any(), any(), any())
onNameError.assertNoValues()
onUrlError.assertNoValues()
onTimeoutError.assertNoValues()
onCheckIntervalError.assertValues(R.string.please_enter_check_interval)
onSearchTermError.assertNoValues()
onScriptError.assertNoValues()
verify(onDone, never()).invoke()
}
@Test fun commit_termSearchError() {
val onNameError = viewModel.onNameError()
.test()
val onUrlError = viewModel.onUrlError()
.test()
val onTimeoutError = viewModel.onTimeoutError()
.test()
val onCheckIntervalError = viewModel.onCheckIntervalError()
.test()
val onSearchTermError = viewModel.onValidationSearchTermError()
.test()
val onScriptError = viewModel.onValidationScriptError()
.test()
fillInModel().apply {
validationMode.value = TERM_SEARCH
validationSearchTerm.value = ""
}
val onDone = mock<() -> Unit>()
viewModel.commit(onDone)
verify(validationManager, never())
.scheduleCheck(any(), any(), any(), any())
onNameError.assertNoValues()
onUrlError.assertNoValues()
onTimeoutError.assertNoValues()
onCheckIntervalError.assertNoValues()
onSearchTermError.assertValues(R.string.please_enter_search_term)
onScriptError.assertNoValues()
verify(onDone, never()).invoke()
}
@Test fun commit_javaScript_error() {
val onNameError = viewModel.onNameError()
.test()
val onUrlError = viewModel.onUrlError()
.test()
val onTimeoutError = viewModel.onTimeoutError()
.test()
val onCheckIntervalError = viewModel.onCheckIntervalError()
.test()
val onSearchTermError = viewModel.onValidationSearchTermError()
.test()
val onScriptError = viewModel.onValidationScriptError()
.test()
fillInModel().apply {
validationMode.value = JAVASCRIPT
validationScript.value = ""
}
val onDone = mock<() -> Unit>()
viewModel.commit(onDone)
verify(validationManager, never())
.scheduleCheck(any(), any(), any(), any())
onNameError.assertNoValues()
onUrlError.assertNoValues()
onTimeoutError.assertNoValues()
onCheckIntervalError.assertNoValues()
onSearchTermError.assertNoValues()
onScriptError.assertValues(R.string.please_enter_javaScript)
verify(onDone, never()).invoke()
}
@Test fun commit_success() = runBlocking {
val isLoading = viewModel.onIsLoading()
.test()
val onNameError = viewModel.onNameError()
.test()
val onUrlError = viewModel.onUrlError()
.test()
val onTimeoutError = viewModel.onTimeoutError()
.test()
val onSearchTermError = viewModel.onValidationSearchTermError()
.test()
val onScriptError = viewModel.onValidationScriptError()
.test()
val onCheckIntervalError = viewModel.onCheckIntervalError()
.test()
fillInModel()
val onDone = mock<() -> Unit>()
viewModel.commit(onDone)
val siteCaptor = argumentCaptor<Site>()
val settingsCaptor = argumentCaptor<SiteSettings>()
isLoading.assertValues(true, false)
verify(database.siteDao()).insert(siteCaptor.capture())
verify(database.siteSettingsDao()).insert(settingsCaptor.capture())
verify(database.validationResultsDao(), never()).insert(any())
val settings = settingsCaptor.firstValue
val model = siteCaptor.firstValue.copy(
id = 1, // fill it in because our insert captor doesn't catch this
settings = settings,
lastResult = null
)
verify(validationManager).scheduleCheck(
site = model,
rightNow = true,
cancelPrevious = true,
fromFinishingJob = false
)
onNameError.assertNoValues()
onUrlError.assertNoValues()
onTimeoutError.assertNoValues()
onCheckIntervalError.assertNoValues()
onSearchTermError.assertNoValues()
onScriptError.assertNoValues()
verify(onDone).invoke()
}
private fun fillInModel() = viewModel.apply {
name.value = "Welcome to Wakanda"
url.value = "https://www.wakanda.gov"
timeout.value = 10000
validationMode.value = TERM_SEARCH
validationSearchTerm.value = "T'Challa"
validationScript.value = null
checkIntervalValue.value = 60
checkIntervalUnit.value = 1000
}
}

View file

@ -27,12 +27,14 @@ import com.afollestad.nocknock.test
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Rule
import org.junit.Test
/** @author Aidan Follestad (@afollestad) */
@ExperimentalCoroutinesApi
class MainViewModelTest {
private val database = mockDatabase()