mirror of
https://github.com/afollestad/nock-nock.git
synced 2025-08-08 00:48:39 +00:00
Use vvalidator in the add site page
This commit is contained in:
parent
7dc4ee7fb1
commit
b3f8a43f71
10 changed files with 79 additions and 400 deletions
|
@ -55,7 +55,6 @@ dependencies {
|
||||||
|
|
||||||
// afollestad
|
// afollestad
|
||||||
implementation 'com.afollestad.material-dialogs:core:' + versions.materialDialogs
|
implementation 'com.afollestad.material-dialogs:core:' + versions.materialDialogs
|
||||||
implementation 'com.afollestad:vvalidator:' + versions.vvalidator
|
|
||||||
|
|
||||||
// Debugging
|
// Debugging
|
||||||
implementation 'com.jakewharton.timber:timber:' + versions.timber
|
implementation 'com.jakewharton.timber:timber:' + versions.timber
|
||||||
|
|
|
@ -31,11 +31,13 @@ import com.afollestad.nocknock.utilities.ext.onTextChanged
|
||||||
import com.afollestad.nocknock.utilities.ext.setTextAndMaintainSelection
|
import com.afollestad.nocknock.utilities.ext.setTextAndMaintainSelection
|
||||||
import com.afollestad.nocknock.utilities.livedata.distinct
|
import com.afollestad.nocknock.utilities.livedata.distinct
|
||||||
import com.afollestad.nocknock.viewcomponents.ext.dimenFloat
|
import com.afollestad.nocknock.viewcomponents.ext.dimenFloat
|
||||||
|
import com.afollestad.nocknock.viewcomponents.ext.isVisibleCondition
|
||||||
import com.afollestad.nocknock.viewcomponents.ext.onScroll
|
import com.afollestad.nocknock.viewcomponents.ext.onScroll
|
||||||
import com.afollestad.nocknock.viewcomponents.livedata.attachLiveData
|
import com.afollestad.nocknock.viewcomponents.livedata.attachLiveData
|
||||||
import com.afollestad.nocknock.viewcomponents.livedata.toViewError
|
|
||||||
import com.afollestad.nocknock.viewcomponents.livedata.toViewText
|
import com.afollestad.nocknock.viewcomponents.livedata.toViewText
|
||||||
import com.afollestad.nocknock.viewcomponents.livedata.toViewVisibility
|
import com.afollestad.nocknock.viewcomponents.livedata.toViewVisibility
|
||||||
|
import com.afollestad.vvalidator.form
|
||||||
|
import com.afollestad.vvalidator.form.Form
|
||||||
import kotlinx.android.synthetic.main.activity_addsite.checkIntervalLayout
|
import kotlinx.android.synthetic.main.activity_addsite.checkIntervalLayout
|
||||||
import kotlinx.android.synthetic.main.activity_addsite.headersLayout
|
import kotlinx.android.synthetic.main.activity_addsite.headersLayout
|
||||||
import kotlinx.android.synthetic.main.activity_addsite.inputName
|
import kotlinx.android.synthetic.main.activity_addsite.inputName
|
||||||
|
@ -64,13 +66,15 @@ class AddSiteActivity : DarkModeSwitchActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private val viewModel by viewModel<AddSiteViewModel>()
|
private val viewModel by viewModel<AddSiteViewModel>()
|
||||||
|
private lateinit var validationForm: Form
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_addsite)
|
setContentView(R.layout.activity_addsite)
|
||||||
setupUi()
|
|
||||||
|
|
||||||
|
setupUi()
|
||||||
|
setupValidation()
|
||||||
lifecycle.addObserver(viewModel)
|
lifecycle.addObserver(viewModel)
|
||||||
|
|
||||||
// Populate view model with initial data
|
// Populate view model with initial data
|
||||||
|
@ -82,23 +86,17 @@ class AddSiteActivity : DarkModeSwitchActivity() {
|
||||||
|
|
||||||
// Name
|
// Name
|
||||||
inputName.attachLiveData(this, viewModel.name)
|
inputName.attachLiveData(this, viewModel.name)
|
||||||
viewModel.onNameError()
|
|
||||||
.toViewError(this, inputName)
|
|
||||||
|
|
||||||
// Tags
|
// Tags
|
||||||
inputTags.attachLiveData(this, viewModel.tags)
|
inputTags.attachLiveData(this, viewModel.tags)
|
||||||
|
|
||||||
// Url
|
// Url
|
||||||
inputUrl.attachLiveData(this, viewModel.url)
|
inputUrl.attachLiveData(this, viewModel.url)
|
||||||
viewModel.onUrlError()
|
|
||||||
.toViewError(this, inputUrl)
|
|
||||||
viewModel.onUrlWarningVisibility()
|
viewModel.onUrlWarningVisibility()
|
||||||
.toViewVisibility(this, textUrlWarning)
|
.toViewVisibility(this, textUrlWarning)
|
||||||
|
|
||||||
// Timeout
|
// Timeout
|
||||||
responseTimeoutInput.attachLiveData(this, viewModel.timeout)
|
responseTimeoutInput.attachLiveData(this, viewModel.timeout)
|
||||||
viewModel.onTimeoutError()
|
|
||||||
.toViewError(this, responseTimeoutInput)
|
|
||||||
|
|
||||||
// Validation mode
|
// Validation mode
|
||||||
responseValidationMode.attachLiveData(
|
responseValidationMode.attachLiveData(
|
||||||
|
@ -107,8 +105,6 @@ class AddSiteActivity : DarkModeSwitchActivity() {
|
||||||
outTransformer = { ValidationMode.fromIndex(it) },
|
outTransformer = { ValidationMode.fromIndex(it) },
|
||||||
inTransformer = { it.toIndex() }
|
inTransformer = { it.toIndex() }
|
||||||
)
|
)
|
||||||
viewModel.onValidationSearchTermError()
|
|
||||||
.toViewError(this, responseValidationSearchTerm)
|
|
||||||
viewModel.onValidationModeDescription()
|
viewModel.onValidationModeDescription()
|
||||||
.toViewText(this, validationModeDescription)
|
.toViewText(this, validationModeDescription)
|
||||||
|
|
||||||
|
@ -124,15 +120,15 @@ class AddSiteActivity : DarkModeSwitchActivity() {
|
||||||
// Validation script
|
// Validation script
|
||||||
scriptInputLayout.attach(
|
scriptInputLayout.attach(
|
||||||
codeData = viewModel.validationScript,
|
codeData = viewModel.validationScript,
|
||||||
errorData = viewModel.onValidationScriptError(),
|
visibility = viewModel.onValidationScriptVisibility(),
|
||||||
visibility = viewModel.onValidationScriptVisibility()
|
form = validationForm
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check interval
|
// Check interval
|
||||||
checkIntervalLayout.attach(
|
checkIntervalLayout.attach(
|
||||||
valueData = viewModel.checkIntervalValue,
|
valueData = viewModel.checkIntervalValue,
|
||||||
multiplierData = viewModel.checkIntervalUnit,
|
multiplierData = viewModel.checkIntervalUnit,
|
||||||
errorData = viewModel.onCheckIntervalError()
|
form = validationForm
|
||||||
)
|
)
|
||||||
|
|
||||||
// Retry Policy
|
// Retry Policy
|
||||||
|
@ -145,8 +141,6 @@ class AddSiteActivity : DarkModeSwitchActivity() {
|
||||||
sslCertificateInput.onTextChanged { viewModel.certificateUri.value = it }
|
sslCertificateInput.onTextChanged { viewModel.certificateUri.value = it }
|
||||||
viewModel.certificateUri.distinct()
|
viewModel.certificateUri.distinct()
|
||||||
.observe(this, Observer { sslCertificateInput.setTextAndMaintainSelection(it) })
|
.observe(this, Observer { sslCertificateInput.setTextAndMaintainSelection(it) })
|
||||||
viewModel.onCertificateError()
|
|
||||||
.toViewError(this, sslCertificateInput)
|
|
||||||
|
|
||||||
// Headers
|
// Headers
|
||||||
headersLayout.attach(viewModel.headers)
|
headersLayout.attach(viewModel.headers)
|
||||||
|
@ -156,15 +150,6 @@ class AddSiteActivity : DarkModeSwitchActivity() {
|
||||||
toolbarTitle.setText(R.string.add_site)
|
toolbarTitle.setText(R.string.add_site)
|
||||||
toolbar.run {
|
toolbar.run {
|
||||||
inflateMenu(R.menu.menu_addsite)
|
inflateMenu(R.menu.menu_addsite)
|
||||||
setOnMenuItemClickListener {
|
|
||||||
if (it.itemId == R.id.commit) {
|
|
||||||
viewModel.commit {
|
|
||||||
setResult(RESULT_OK)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
setNavigationIcon(R.drawable.ic_action_close)
|
setNavigationIcon(R.drawable.ic_action_close)
|
||||||
setNavigationOnClickListener { finish() }
|
setNavigationOnClickListener { finish() }
|
||||||
}
|
}
|
||||||
|
@ -195,6 +180,38 @@ class AddSiteActivity : DarkModeSwitchActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupValidation() {
|
||||||
|
validationForm = form {
|
||||||
|
input(inputName, name = "Name") {
|
||||||
|
isNotEmpty().description(R.string.please_enter_name)
|
||||||
|
}
|
||||||
|
input(inputUrl, name = "URL") {
|
||||||
|
isNotEmpty().description(R.string.please_enter_url)
|
||||||
|
isUrl().description(R.string.please_enter_valid_url)
|
||||||
|
}
|
||||||
|
input(responseTimeoutInput, name = "Timeout", optional = true) {
|
||||||
|
isNumber().greaterThan(0)
|
||||||
|
.description(R.string.please_enter_networkTimeout)
|
||||||
|
}
|
||||||
|
input(responseValidationSearchTerm, name = "Search term") {
|
||||||
|
conditional(responseValidationSearchTerm.isVisibleCondition()) {
|
||||||
|
isNotEmpty().description(R.string.please_enter_search_term)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input(sslCertificateInput, name = "Certificate Path") {
|
||||||
|
isUri().hasScheme("file", "content")
|
||||||
|
.that { it.host != null }
|
||||||
|
.description(R.string.please_enter_validCertUri)
|
||||||
|
}
|
||||||
|
submitWith(toolbar.menu, R.id.commit) {
|
||||||
|
viewModel.commit {
|
||||||
|
setResult(RESULT_OK)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
appToolbar.elevation = if (scrollView.scrollY > appToolbar.measuredHeight / 2) {
|
appToolbar.elevation = if (scrollView.scrollY > appToolbar.measuredHeight / 2) {
|
||||||
|
|
|
@ -39,9 +39,7 @@ import com.afollestad.nocknock.data.putSite
|
||||||
import com.afollestad.nocknock.engine.validation.ValidationExecutor
|
import com.afollestad.nocknock.engine.validation.ValidationExecutor
|
||||||
import com.afollestad.nocknock.ui.ScopedViewModel
|
import com.afollestad.nocknock.ui.ScopedViewModel
|
||||||
import com.afollestad.nocknock.utilities.ext.MINUTE
|
import com.afollestad.nocknock.utilities.ext.MINUTE
|
||||||
import com.afollestad.nocknock.utilities.ext.toUri
|
|
||||||
import com.afollestad.nocknock.utilities.livedata.map
|
import com.afollestad.nocknock.utilities.livedata.map
|
||||||
import com.afollestad.nocknock.viewcomponents.ext.isNullOrLessThan
|
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
@ -83,23 +81,10 @@ class AddSiteViewModel(
|
||||||
headers.value = emptyList()
|
headers.value = emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private properties
|
|
||||||
private val isLoading = MutableLiveData<Boolean>()
|
private val isLoading = MutableLiveData<Boolean>()
|
||||||
private val nameError = MutableLiveData<Int?>()
|
|
||||||
private val urlError = MutableLiveData<Int?>()
|
|
||||||
private val timeoutError = MutableLiveData<Int?>()
|
|
||||||
private val validationSearchTermError = MutableLiveData<Int?>()
|
|
||||||
private val validationScriptError = MutableLiveData<Int?>()
|
|
||||||
private val checkIntervalValueError = MutableLiveData<Int?>()
|
|
||||||
private val certificateError = MutableLiveData<Int?>()
|
|
||||||
|
|
||||||
// Expose private properties or calculated properties
|
|
||||||
@CheckResult fun onIsLoading(): LiveData<Boolean> = isLoading
|
@CheckResult fun onIsLoading(): LiveData<Boolean> = isLoading
|
||||||
|
|
||||||
@CheckResult fun onNameError(): LiveData<Int?> = nameError
|
|
||||||
|
|
||||||
@CheckResult fun onUrlError(): LiveData<Int?> = urlError
|
|
||||||
|
|
||||||
@CheckResult fun onUrlWarningVisibility(): LiveData<Boolean> {
|
@CheckResult fun onUrlWarningVisibility(): LiveData<Boolean> {
|
||||||
return url.map {
|
return url.map {
|
||||||
val parsed = HttpUrl.parse(it)
|
val parsed = HttpUrl.parse(it)
|
||||||
|
@ -107,8 +92,6 @@ class AddSiteViewModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@CheckResult fun onTimeoutError(): LiveData<Int?> = timeoutError
|
|
||||||
|
|
||||||
@CheckResult fun onValidationModeDescription(): LiveData<Int> {
|
@CheckResult fun onValidationModeDescription(): LiveData<Int> {
|
||||||
return validationMode.map {
|
return validationMode.map {
|
||||||
when (it!!) {
|
when (it!!) {
|
||||||
|
@ -119,19 +102,9 @@ class AddSiteViewModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@CheckResult fun onValidationSearchTermError(): LiveData<Int?> = validationSearchTermError
|
@CheckResult fun onValidationSearchTermVisibility() = validationMode.map { it == TERM_SEARCH }
|
||||||
|
|
||||||
@CheckResult fun onValidationSearchTermVisibility() =
|
@CheckResult fun onValidationScriptVisibility() = validationMode.map { it == JAVASCRIPT }
|
||||||
validationMode.map { it == TERM_SEARCH }
|
|
||||||
|
|
||||||
@CheckResult fun onValidationScriptError(): LiveData<Int?> = validationScriptError
|
|
||||||
|
|
||||||
@CheckResult fun onValidationScriptVisibility() =
|
|
||||||
validationMode.map { it == JAVASCRIPT }
|
|
||||||
|
|
||||||
@CheckResult fun onCheckIntervalError(): LiveData<Int?> = checkIntervalValueError
|
|
||||||
|
|
||||||
@CheckResult fun onCertificateError(): LiveData<Int?> = certificateError
|
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
fun commit(done: () -> Unit) {
|
fun commit(done: () -> Unit) {
|
||||||
|
@ -171,89 +144,7 @@ class AddSiteViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateDbModel(): Site? {
|
private fun generateDbModel(): Site? {
|
||||||
var errorCount = 0
|
|
||||||
|
|
||||||
// Validation name
|
|
||||||
if (name.value.isNullOrEmpty()) {
|
|
||||||
nameError.value = R.string.please_enter_name
|
|
||||||
errorCount++
|
|
||||||
} else {
|
|
||||||
nameError.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate URL
|
|
||||||
when {
|
|
||||||
url.value.isNullOrEmpty() -> {
|
|
||||||
urlError.value = R.string.please_enter_url
|
|
||||||
errorCount++
|
|
||||||
}
|
|
||||||
HttpUrl.parse(url.value!!) == null -> {
|
|
||||||
urlError.value = R.string.please_enter_valid_url
|
|
||||||
errorCount++
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
urlError.value = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate timeout
|
|
||||||
val timeout = timeout.value ?: 10_000
|
val timeout = timeout.value ?: 10_000
|
||||||
if (timeout < 0) {
|
|
||||||
timeoutError.value = R.string.please_enter_networkTimeout
|
|
||||||
errorCount++
|
|
||||||
} else {
|
|
||||||
timeoutError.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate check interval
|
|
||||||
if (checkIntervalValue.value.isNullOrLessThan(1)) {
|
|
||||||
checkIntervalValueError.value = R.string.please_enter_check_interval
|
|
||||||
errorCount++
|
|
||||||
} else {
|
|
||||||
checkIntervalValueError.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate arguments
|
|
||||||
if (validationMode.value == TERM_SEARCH &&
|
|
||||||
validationSearchTerm.value.isNullOrEmpty()
|
|
||||||
) {
|
|
||||||
errorCount++
|
|
||||||
validationSearchTermError.value = R.string.please_enter_search_term
|
|
||||||
validationScriptError.value = null
|
|
||||||
} else if (validationMode.value == JAVASCRIPT &&
|
|
||||||
validationScript.value.isNullOrEmpty()
|
|
||||||
) {
|
|
||||||
errorCount++
|
|
||||||
validationSearchTermError.value = null
|
|
||||||
validationScriptError.value = R.string.please_enter_javaScript
|
|
||||||
} else {
|
|
||||||
validationSearchTermError.value = null
|
|
||||||
validationScriptError.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate SSL certificate
|
|
||||||
val certString = certificateUri.value
|
|
||||||
if (certString != null) {
|
|
||||||
val rawCertUri = certString.toUri()
|
|
||||||
val certUri = if (rawCertUri.scheme == null) {
|
|
||||||
rawCertUri.buildUpon()
|
|
||||||
.scheme("file")
|
|
||||||
.build()
|
|
||||||
} else {
|
|
||||||
rawCertUri
|
|
||||||
}
|
|
||||||
if (certUri.scheme != "content" && certUri.scheme != "file") {
|
|
||||||
errorCount++
|
|
||||||
certificateError.value = R.string.please_enter_validCertUri
|
|
||||||
} else {
|
|
||||||
certificateError.value = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errorCount > 0) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
val cleanedTags = tags.value?.split(',')?.joinToString { it.trim() } ?: ""
|
val cleanedTags = tags.value?.split(',')?.joinToString { it.trim() } ?: ""
|
||||||
|
|
||||||
val newSettings = SiteSettings(
|
val newSettings = SiteSettings(
|
||||||
|
@ -275,7 +166,8 @@ class AddSiteViewModel(
|
||||||
val retryPolicyMinutes = retryPolicyMinutes.value ?: 0
|
val retryPolicyMinutes = retryPolicyMinutes.value ?: 0
|
||||||
val newRetryPolicy: RetryPolicy? = if (retryPolicyTimes > 0 && retryPolicyMinutes > 0) {
|
val newRetryPolicy: RetryPolicy? = if (retryPolicyTimes > 0 && retryPolicyMinutes > 0) {
|
||||||
RetryPolicy(
|
RetryPolicy(
|
||||||
count = retryPolicyTimes, minutes = retryPolicyMinutes
|
count = retryPolicyTimes,
|
||||||
|
minutes = retryPolicyMinutes
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
|
|
@ -29,9 +29,7 @@
|
||||||
<string name="please_enter_name">Please enter a name!</string>
|
<string name="please_enter_name">Please enter a name!</string>
|
||||||
<string name="please_enter_url">Please enter a URL.</string>
|
<string name="please_enter_url">Please enter a URL.</string>
|
||||||
<string name="please_enter_valid_url">Please enter a valid URL.</string>
|
<string name="please_enter_valid_url">Please enter a valid URL.</string>
|
||||||
<string name="please_enter_check_interval">Please input a validation interval.</string>
|
|
||||||
<string name="please_enter_search_term">Please input a search term.</string>
|
<string name="please_enter_search_term">Please input a search term.</string>
|
||||||
<string name="please_enter_javaScript">Please input a validation script.</string>
|
|
||||||
<string name="please_enter_networkTimeout">Please enter a network timeout greater than 0.</string>
|
<string name="please_enter_networkTimeout">Please enter a network timeout greater than 0.</string>
|
||||||
<string name="please_enter_validCertUri">Certificate should be a valid file or content URI.</string>
|
<string name="please_enter_validCertUri">Certificate should be a valid file or content URI.</string>
|
||||||
|
|
||||||
|
|
|
@ -149,247 +149,9 @@ class AddSiteViewModelTest {
|
||||||
assertThat(viewModel.getValidationArgs()).isEqualTo("Two")
|
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())
|
|
||||||
.scheduleValidation(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())
|
|
||||||
.scheduleValidation(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())
|
|
||||||
.scheduleValidation(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())
|
|
||||||
.scheduleValidation(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())
|
|
||||||
.scheduleValidation(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())
|
|
||||||
.scheduleValidation(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())
|
|
||||||
.scheduleValidation(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 {
|
@Test fun commit_success() = runBlocking {
|
||||||
val isLoading = viewModel.onIsLoading()
|
val isLoading = viewModel.onIsLoading()
|
||||||
.test()
|
.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()
|
fillInModel()
|
||||||
val onDone = mock<() -> Unit>()
|
val onDone = mock<() -> Unit>()
|
||||||
|
@ -416,12 +178,6 @@ class AddSiteViewModelTest {
|
||||||
cancelPrevious = true,
|
cancelPrevious = true,
|
||||||
fromFinishingJob = false
|
fromFinishingJob = false
|
||||||
)
|
)
|
||||||
onNameError.assertNoValues()
|
|
||||||
onUrlError.assertNoValues()
|
|
||||||
onTimeoutError.assertNoValues()
|
|
||||||
onCheckIntervalError.assertNoValues()
|
|
||||||
onSearchTermError.assertNoValues()
|
|
||||||
onScriptError.assertNoValues()
|
|
||||||
|
|
||||||
verify(onDone).invoke()
|
verify(onDone).invoke()
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ dependencies {
|
||||||
implementation project(':common')
|
implementation project(':common')
|
||||||
implementation project(':data')
|
implementation project(':data')
|
||||||
|
|
||||||
|
api 'com.afollestad:vvalidator:' + versions.vvalidator
|
||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:' + versions.androidxCore
|
implementation 'androidx.appcompat:appcompat:' + versions.androidxCore
|
||||||
implementation 'com.google.android.material:material:' + versions.googleMaterial
|
implementation 'com.google.android.material:material:' + versions.googleMaterial
|
||||||
api 'androidx.lifecycle:lifecycle-extensions:' + versions.lifecycle
|
api 'androidx.lifecycle:lifecycle-extensions:' + versions.lifecycle
|
||||||
|
|
|
@ -17,19 +17,15 @@ package com.afollestad.nocknock.viewcomponents.ext
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
import android.view.View.INVISIBLE
|
|
||||||
import android.view.View.VISIBLE
|
import android.view.View.VISIBLE
|
||||||
import android.view.ViewTreeObserver
|
import android.view.ViewTreeObserver
|
||||||
import androidx.annotation.DimenRes
|
import androidx.annotation.DimenRes
|
||||||
|
import com.afollestad.vvalidator.form.Condition
|
||||||
|
|
||||||
fun View.show() {
|
fun View.show() {
|
||||||
visibility = VISIBLE
|
visibility = VISIBLE
|
||||||
}
|
}
|
||||||
|
|
||||||
fun View.conceal() {
|
|
||||||
visibility = INVISIBLE
|
|
||||||
}
|
|
||||||
|
|
||||||
fun View.hide() {
|
fun View.hide() {
|
||||||
visibility = GONE
|
visibility = GONE
|
||||||
}
|
}
|
||||||
|
@ -51,3 +47,7 @@ fun View.onLayout(cb: () -> Unit) {
|
||||||
fun View.dimenFloat(@DimenRes res: Int) = resources.getDimension(res)
|
fun View.dimenFloat(@DimenRes res: Int) = resources.getDimension(res)
|
||||||
|
|
||||||
fun View.dimenInt(@DimenRes res: Int) = resources.getDimensionPixelSize(res)
|
fun View.dimenInt(@DimenRes res: Int) = resources.getDimensionPixelSize(res)
|
||||||
|
|
||||||
|
fun View.isVisibleCondition(): Condition = {
|
||||||
|
visibility == VISIBLE
|
||||||
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import com.afollestad.nocknock.utilities.ext.DAY
|
import com.afollestad.nocknock.utilities.ext.DAY
|
||||||
import com.afollestad.nocknock.utilities.ext.HOUR
|
import com.afollestad.nocknock.utilities.ext.HOUR
|
||||||
|
@ -28,7 +27,7 @@ import com.afollestad.nocknock.utilities.ext.WEEK
|
||||||
import com.afollestad.nocknock.viewcomponents.R
|
import com.afollestad.nocknock.viewcomponents.R
|
||||||
import com.afollestad.nocknock.viewcomponents.livedata.attachLiveData
|
import com.afollestad.nocknock.viewcomponents.livedata.attachLiveData
|
||||||
import com.afollestad.nocknock.viewcomponents.livedata.lifecycleOwner
|
import com.afollestad.nocknock.viewcomponents.livedata.lifecycleOwner
|
||||||
import com.afollestad.nocknock.viewcomponents.livedata.toViewError
|
import com.afollestad.vvalidator.form.Form
|
||||||
import kotlinx.android.synthetic.main.validation_interval_layout.view.input
|
import kotlinx.android.synthetic.main.validation_interval_layout.view.input
|
||||||
import kotlinx.android.synthetic.main.validation_interval_layout.view.spinner
|
import kotlinx.android.synthetic.main.validation_interval_layout.view.spinner
|
||||||
|
|
||||||
|
@ -66,8 +65,14 @@ class ValidationIntervalLayout(
|
||||||
fun attach(
|
fun attach(
|
||||||
valueData: MutableLiveData<Int>,
|
valueData: MutableLiveData<Int>,
|
||||||
multiplierData: MutableLiveData<Long>,
|
multiplierData: MutableLiveData<Long>,
|
||||||
errorData: LiveData<Int?>
|
form: Form
|
||||||
) {
|
) {
|
||||||
|
form.input(input, name = "Interval") {
|
||||||
|
isNotEmpty().description(R.string.please_enter_check_interval)
|
||||||
|
length().greaterThan(0)
|
||||||
|
.description(R.string.check_interval_must_be_greater_zero)
|
||||||
|
}
|
||||||
|
|
||||||
input.attachLiveData(lifecycleOwner(), valueData)
|
input.attachLiveData(lifecycleOwner(), valueData)
|
||||||
spinner.attachLiveData(
|
spinner.attachLiveData(
|
||||||
lifecycleOwner = lifecycleOwner(),
|
lifecycleOwner = lifecycleOwner(),
|
||||||
|
@ -91,10 +96,5 @@ class ValidationIntervalLayout(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
errorData.toViewError(lifecycleOwner(), this, ::setError)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setError(error: String?) {
|
|
||||||
input.error = error
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,15 +20,17 @@ import android.util.AttributeSet
|
||||||
import android.widget.HorizontalScrollView
|
import android.widget.HorizontalScrollView
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import com.afollestad.nocknock.viewcomponents.R
|
||||||
import com.afollestad.nocknock.viewcomponents.R.dimen
|
import com.afollestad.nocknock.viewcomponents.R.dimen
|
||||||
import com.afollestad.nocknock.viewcomponents.R.layout
|
import com.afollestad.nocknock.viewcomponents.R.layout
|
||||||
import com.afollestad.nocknock.viewcomponents.ext.dimenFloat
|
import com.afollestad.nocknock.viewcomponents.ext.dimenFloat
|
||||||
import com.afollestad.nocknock.viewcomponents.ext.dimenInt
|
import com.afollestad.nocknock.viewcomponents.ext.dimenInt
|
||||||
|
import com.afollestad.nocknock.viewcomponents.ext.isVisibleCondition
|
||||||
import com.afollestad.nocknock.viewcomponents.ext.showOrHide
|
import com.afollestad.nocknock.viewcomponents.ext.showOrHide
|
||||||
import com.afollestad.nocknock.viewcomponents.livedata.attachLiveData
|
import com.afollestad.nocknock.viewcomponents.livedata.attachLiveData
|
||||||
import com.afollestad.nocknock.viewcomponents.livedata.lifecycleOwner
|
import com.afollestad.nocknock.viewcomponents.livedata.lifecycleOwner
|
||||||
import com.afollestad.nocknock.viewcomponents.livedata.toViewError
|
|
||||||
import com.afollestad.nocknock.viewcomponents.livedata.toViewVisibility
|
import com.afollestad.nocknock.viewcomponents.livedata.toViewVisibility
|
||||||
|
import com.afollestad.vvalidator.form.Form
|
||||||
import kotlinx.android.synthetic.main.javascript_input_layout.view.error_text
|
import kotlinx.android.synthetic.main.javascript_input_layout.view.error_text
|
||||||
import kotlinx.android.synthetic.main.javascript_input_layout.view.userInput
|
import kotlinx.android.synthetic.main.javascript_input_layout.view.userInput
|
||||||
|
|
||||||
|
@ -55,11 +57,20 @@ class JavaScriptInputLayout(
|
||||||
|
|
||||||
fun attach(
|
fun attach(
|
||||||
codeData: MutableLiveData<String>,
|
codeData: MutableLiveData<String>,
|
||||||
errorData: LiveData<Int?>,
|
visibility: LiveData<Boolean>,
|
||||||
visibility: LiveData<Boolean>
|
form: Form
|
||||||
) {
|
) {
|
||||||
|
form.input(userInput, name = "Script") {
|
||||||
|
conditional(isVisibleCondition()) {
|
||||||
|
isNotEmpty().description(R.string.please_enter_javaScript)
|
||||||
|
}
|
||||||
|
onErrors { _, errors ->
|
||||||
|
val error = errors.firstOrNull()
|
||||||
|
setError(error.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
userInput.attachLiveData(lifecycleOwner(), codeData)
|
userInput.attachLiveData(lifecycleOwner(), codeData)
|
||||||
errorData.toViewError(lifecycleOwner(), this, ::setError)
|
|
||||||
visibility.toViewVisibility(lifecycleOwner(), this)
|
visibility.toViewVisibility(lifecycleOwner(), this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,4 +23,8 @@
|
||||||
<string name="header_name">Header Name</string>
|
<string name="header_name">Header Name</string>
|
||||||
<string name="header_value">Header Value</string>
|
<string name="header_value">Header Value</string>
|
||||||
|
|
||||||
|
<string name="please_enter_check_interval">Please input a validation interval.</string>
|
||||||
|
<string name="check_interval_must_be_greater_zero">The validation interval must be greater than 0.</string>
|
||||||
|
<string name="please_enter_javaScript">Please input a validation script.</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue