mirror of
https://github.com/afollestad/nock-nock.git
synced 2025-04-20 03:25:14 +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
|
||||
implementation 'com.afollestad.material-dialogs:core:' + versions.materialDialogs
|
||||
implementation 'com.afollestad:vvalidator:' + versions.vvalidator
|
||||
|
||||
// Debugging
|
||||
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.livedata.distinct
|
||||
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.livedata.attachLiveData
|
||||
import com.afollestad.nocknock.viewcomponents.livedata.toViewError
|
||||
import com.afollestad.nocknock.viewcomponents.livedata.toViewText
|
||||
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.headersLayout
|
||||
import kotlinx.android.synthetic.main.activity_addsite.inputName
|
||||
|
@ -64,13 +66,15 @@ class AddSiteActivity : DarkModeSwitchActivity() {
|
|||
}
|
||||
|
||||
private val viewModel by viewModel<AddSiteViewModel>()
|
||||
private lateinit var validationForm: Form
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_addsite)
|
||||
setupUi()
|
||||
|
||||
setupUi()
|
||||
setupValidation()
|
||||
lifecycle.addObserver(viewModel)
|
||||
|
||||
// Populate view model with initial data
|
||||
|
@ -82,23 +86,17 @@ class AddSiteActivity : DarkModeSwitchActivity() {
|
|||
|
||||
// Name
|
||||
inputName.attachLiveData(this, viewModel.name)
|
||||
viewModel.onNameError()
|
||||
.toViewError(this, inputName)
|
||||
|
||||
// Tags
|
||||
inputTags.attachLiveData(this, viewModel.tags)
|
||||
|
||||
// Url
|
||||
inputUrl.attachLiveData(this, viewModel.url)
|
||||
viewModel.onUrlError()
|
||||
.toViewError(this, inputUrl)
|
||||
viewModel.onUrlWarningVisibility()
|
||||
.toViewVisibility(this, textUrlWarning)
|
||||
|
||||
// Timeout
|
||||
responseTimeoutInput.attachLiveData(this, viewModel.timeout)
|
||||
viewModel.onTimeoutError()
|
||||
.toViewError(this, responseTimeoutInput)
|
||||
|
||||
// Validation mode
|
||||
responseValidationMode.attachLiveData(
|
||||
|
@ -107,8 +105,6 @@ class AddSiteActivity : DarkModeSwitchActivity() {
|
|||
outTransformer = { ValidationMode.fromIndex(it) },
|
||||
inTransformer = { it.toIndex() }
|
||||
)
|
||||
viewModel.onValidationSearchTermError()
|
||||
.toViewError(this, responseValidationSearchTerm)
|
||||
viewModel.onValidationModeDescription()
|
||||
.toViewText(this, validationModeDescription)
|
||||
|
||||
|
@ -124,15 +120,15 @@ class AddSiteActivity : DarkModeSwitchActivity() {
|
|||
// Validation script
|
||||
scriptInputLayout.attach(
|
||||
codeData = viewModel.validationScript,
|
||||
errorData = viewModel.onValidationScriptError(),
|
||||
visibility = viewModel.onValidationScriptVisibility()
|
||||
visibility = viewModel.onValidationScriptVisibility(),
|
||||
form = validationForm
|
||||
)
|
||||
|
||||
// Check interval
|
||||
checkIntervalLayout.attach(
|
||||
valueData = viewModel.checkIntervalValue,
|
||||
multiplierData = viewModel.checkIntervalUnit,
|
||||
errorData = viewModel.onCheckIntervalError()
|
||||
form = validationForm
|
||||
)
|
||||
|
||||
// Retry Policy
|
||||
|
@ -145,8 +141,6 @@ class AddSiteActivity : DarkModeSwitchActivity() {
|
|||
sslCertificateInput.onTextChanged { viewModel.certificateUri.value = it }
|
||||
viewModel.certificateUri.distinct()
|
||||
.observe(this, Observer { sslCertificateInput.setTextAndMaintainSelection(it) })
|
||||
viewModel.onCertificateError()
|
||||
.toViewError(this, sslCertificateInput)
|
||||
|
||||
// Headers
|
||||
headersLayout.attach(viewModel.headers)
|
||||
|
@ -156,15 +150,6 @@ class AddSiteActivity : DarkModeSwitchActivity() {
|
|||
toolbarTitle.setText(R.string.add_site)
|
||||
toolbar.run {
|
||||
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)
|
||||
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() {
|
||||
super.onResume()
|
||||
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.ui.ScopedViewModel
|
||||
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.viewcomponents.ext.isNullOrLessThan
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
@ -83,23 +81,10 @@ class AddSiteViewModel(
|
|||
headers.value = emptyList()
|
||||
}
|
||||
|
||||
// Private properties
|
||||
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 onNameError(): LiveData<Int?> = nameError
|
||||
|
||||
@CheckResult fun onUrlError(): LiveData<Int?> = urlError
|
||||
|
||||
@CheckResult fun onUrlWarningVisibility(): LiveData<Boolean> {
|
||||
return url.map {
|
||||
val parsed = HttpUrl.parse(it)
|
||||
|
@ -107,8 +92,6 @@ class AddSiteViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
@CheckResult fun onTimeoutError(): LiveData<Int?> = timeoutError
|
||||
|
||||
@CheckResult fun onValidationModeDescription(): LiveData<Int> {
|
||||
return validationMode.map {
|
||||
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() =
|
||||
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
|
||||
@CheckResult fun onValidationScriptVisibility() = validationMode.map { it == JAVASCRIPT }
|
||||
|
||||
// Actions
|
||||
fun commit(done: () -> Unit) {
|
||||
|
@ -171,89 +144,7 @@ class AddSiteViewModel(
|
|||
}
|
||||
|
||||
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
|
||||
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 newSettings = SiteSettings(
|
||||
|
@ -275,7 +166,8 @@ class AddSiteViewModel(
|
|||
val retryPolicyMinutes = retryPolicyMinutes.value ?: 0
|
||||
val newRetryPolicy: RetryPolicy? = if (retryPolicyTimes > 0 && retryPolicyMinutes > 0) {
|
||||
RetryPolicy(
|
||||
count = retryPolicyTimes, minutes = retryPolicyMinutes
|
||||
count = retryPolicyTimes,
|
||||
minutes = retryPolicyMinutes
|
||||
)
|
||||
} else {
|
||||
null
|
||||
|
|
|
@ -29,9 +29,7 @@
|
|||
<string name="please_enter_name">Please enter a name!</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_check_interval">Please input a validation interval.</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_validCertUri">Certificate should be a valid file or content URI.</string>
|
||||
|
||||
|
|
|
@ -149,247 +149,9 @@ class AddSiteViewModelTest {
|
|||
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 {
|
||||
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>()
|
||||
|
@ -416,12 +178,6 @@ class AddSiteViewModelTest {
|
|||
cancelPrevious = true,
|
||||
fromFinishingJob = false
|
||||
)
|
||||
onNameError.assertNoValues()
|
||||
onUrlError.assertNoValues()
|
||||
onTimeoutError.assertNoValues()
|
||||
onCheckIntervalError.assertNoValues()
|
||||
onSearchTermError.assertNoValues()
|
||||
onScriptError.assertNoValues()
|
||||
|
||||
verify(onDone).invoke()
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ dependencies {
|
|||
implementation project(':common')
|
||||
implementation project(':data')
|
||||
|
||||
api 'com.afollestad:vvalidator:' + versions.vvalidator
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:' + versions.androidxCore
|
||||
implementation 'com.google.android.material:material:' + versions.googleMaterial
|
||||
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.GONE
|
||||
import android.view.View.INVISIBLE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.ViewTreeObserver
|
||||
import androidx.annotation.DimenRes
|
||||
import com.afollestad.vvalidator.form.Condition
|
||||
|
||||
fun View.show() {
|
||||
visibility = VISIBLE
|
||||
}
|
||||
|
||||
fun View.conceal() {
|
||||
visibility = INVISIBLE
|
||||
}
|
||||
|
||||
fun View.hide() {
|
||||
visibility = GONE
|
||||
}
|
||||
|
@ -51,3 +47,7 @@ fun View.onLayout(cb: () -> Unit) {
|
|||
fun View.dimenFloat(@DimenRes res: Int) = resources.getDimension(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.widget.ArrayAdapter
|
||||
import android.widget.LinearLayout
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.afollestad.nocknock.utilities.ext.DAY
|
||||
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.livedata.attachLiveData
|
||||
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.spinner
|
||||
|
||||
|
@ -66,8 +65,14 @@ class ValidationIntervalLayout(
|
|||
fun attach(
|
||||
valueData: MutableLiveData<Int>,
|
||||
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)
|
||||
spinner.attachLiveData(
|
||||
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 androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.afollestad.nocknock.viewcomponents.R
|
||||
import com.afollestad.nocknock.viewcomponents.R.dimen
|
||||
import com.afollestad.nocknock.viewcomponents.R.layout
|
||||
import com.afollestad.nocknock.viewcomponents.ext.dimenFloat
|
||||
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.livedata.attachLiveData
|
||||
import com.afollestad.nocknock.viewcomponents.livedata.lifecycleOwner
|
||||
import com.afollestad.nocknock.viewcomponents.livedata.toViewError
|
||||
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.userInput
|
||||
|
||||
|
@ -55,11 +57,20 @@ class JavaScriptInputLayout(
|
|||
|
||||
fun attach(
|
||||
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)
|
||||
errorData.toViewError(lifecycleOwner(), this, ::setError)
|
||||
visibility.toViewVisibility(lifecycleOwner(), this)
|
||||
}
|
||||
|
||||
|
|
|
@ -23,4 +23,8 @@
|
|||
<string name="header_name">Header Name</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>
|
||||
|
|
Loading…
Add table
Reference in a new issue