mirror of
https://github.com/afollestad/nock-nock.git
synced 2025-04-20 19:45:17 +00:00
Move majority of view site logic to a presenter, add unit tests
This commit is contained in:
parent
cf802dfa2f
commit
2d81575e4b
7 changed files with 782 additions and 238 deletions
|
@ -9,6 +9,8 @@ import com.afollestad.nocknock.ui.addsite.AddSitePresenter
|
|||
import com.afollestad.nocknock.ui.addsite.RealAddSitePresenter
|
||||
import com.afollestad.nocknock.ui.main.MainPresenter
|
||||
import com.afollestad.nocknock.ui.main.RealMainPresenter
|
||||
import com.afollestad.nocknock.ui.viewsite.RealViewSitePresenter
|
||||
import com.afollestad.nocknock.ui.viewsite.ViewSitePresenter
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import javax.inject.Singleton
|
||||
|
@ -28,4 +30,10 @@ abstract class MainBindModule {
|
|||
abstract fun provideAddSitePresenter(
|
||||
presenter: RealAddSitePresenter
|
||||
): AddSitePresenter
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun provideViewSitePresenter(
|
||||
presenter: RealViewSitePresenter
|
||||
): ViewSitePresenter
|
||||
}
|
||||
|
|
|
@ -10,20 +10,12 @@ import android.content.BroadcastReceiver
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.util.Patterns.WEB_URL
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.ArrayAdapter
|
||||
import androidx.annotation.CheckResult
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.nocknock.BuildConfig
|
||||
import com.afollestad.nocknock.R
|
||||
import com.afollestad.nocknock.data.LAST_CHECK_NONE
|
||||
import com.afollestad.nocknock.data.ServerModel
|
||||
|
@ -35,24 +27,17 @@ import com.afollestad.nocknock.data.ValidationMode.STATUS_CODE
|
|||
import com.afollestad.nocknock.data.ValidationMode.TERM_SEARCH
|
||||
import com.afollestad.nocknock.data.indexToValidationMode
|
||||
import com.afollestad.nocknock.data.textRes
|
||||
import com.afollestad.nocknock.engine.db.ServerModelStore
|
||||
import com.afollestad.nocknock.engine.statuscheck.CheckStatusJob.Companion.ACTION_STATUS_UPDATE
|
||||
import com.afollestad.nocknock.engine.statuscheck.CheckStatusManager
|
||||
import com.afollestad.nocknock.notifications.NockNotificationManager
|
||||
import com.afollestad.nocknock.ui.main.MainActivity
|
||||
import com.afollestad.nocknock.utilities.ext.ScopeReceiver
|
||||
import com.afollestad.nocknock.utilities.ext.formatDate
|
||||
import com.afollestad.nocknock.utilities.ext.injector
|
||||
import com.afollestad.nocknock.utilities.ext.isHttpOrHttps
|
||||
import com.afollestad.nocknock.utilities.ext.safeRegisterReceiver
|
||||
import com.afollestad.nocknock.utilities.ext.safeUnregisterReceiver
|
||||
import com.afollestad.nocknock.utilities.ext.scopeWhileAttached
|
||||
import com.afollestad.nocknock.viewcomponents.ext.dimenFloat
|
||||
import com.afollestad.nocknock.viewcomponents.ext.disable
|
||||
import com.afollestad.nocknock.viewcomponents.ext.enable
|
||||
import com.afollestad.nocknock.viewcomponents.ext.hide
|
||||
import com.afollestad.nocknock.viewcomponents.ext.onItemSelected
|
||||
import com.afollestad.nocknock.viewcomponents.ext.onScroll
|
||||
import com.afollestad.nocknock.viewcomponents.ext.show
|
||||
import com.afollestad.nocknock.viewcomponents.ext.showOrHide
|
||||
import com.afollestad.nocknock.viewcomponents.ext.trimmedText
|
||||
import kotlinx.android.synthetic.main.activity_viewsite.checkIntervalLayout
|
||||
|
@ -72,13 +57,8 @@ import kotlinx.android.synthetic.main.activity_viewsite.textNextCheck
|
|||
import kotlinx.android.synthetic.main.activity_viewsite.textUrlWarning
|
||||
import kotlinx.android.synthetic.main.activity_viewsite.toolbar
|
||||
import kotlinx.android.synthetic.main.activity_viewsite.validationModeDescription
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val KEY_VIEW_MODEL = "site_model"
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
/** @author Aidan Follestad (@afollestad) */
|
||||
fun MainActivity.intentToView(model: ServerModel) =
|
||||
|
@ -87,40 +67,15 @@ fun MainActivity.intentToView(model: ServerModel) =
|
|||
}
|
||||
|
||||
/** @author Aidan Follestad (@afollestad) */
|
||||
class ViewSiteActivity : AppCompatActivity(),
|
||||
View.OnClickListener,
|
||||
Toolbar.OnMenuItemClickListener {
|
||||
companion object {
|
||||
private fun log(message: String) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d("ViewSiteActivity", message)
|
||||
}
|
||||
}
|
||||
}
|
||||
class ViewSiteActivity : AppCompatActivity(), ViewSiteView {
|
||||
|
||||
private lateinit var currentModel: ServerModel
|
||||
|
||||
@Inject lateinit var serverModelStore: ServerModelStore
|
||||
@Inject lateinit var notificationManager: NockNotificationManager
|
||||
@Inject lateinit var checkStatusManager: CheckStatusManager
|
||||
@Inject lateinit var presenter: ViewSitePresenter
|
||||
|
||||
private val intentReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(
|
||||
context: Context,
|
||||
intent: Intent
|
||||
) {
|
||||
log(
|
||||
"Received broadcast ${intent.action}"
|
||||
)
|
||||
val model = intent.getSerializableExtra(KEY_VIEW_MODEL) as? ServerModel
|
||||
if (model != null) {
|
||||
this@ViewSiteActivity.currentModel = model
|
||||
log(
|
||||
"Received model update: $currentModel"
|
||||
)
|
||||
displayCurrentModel()
|
||||
}
|
||||
}
|
||||
) = presenter.onBroadcast(intent)
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
|
@ -133,7 +88,13 @@ class ViewSiteActivity : AppCompatActivity(),
|
|||
toolbar.run {
|
||||
setNavigationOnClickListener { finish() }
|
||||
inflateMenu(R.menu.menu_viewsite)
|
||||
setOnMenuItemClickListener(this@ViewSiteActivity)
|
||||
setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.refresh -> presenter.checkNow()
|
||||
R.id.remove -> maybeRemoveSite()
|
||||
}
|
||||
return@setOnMenuItemClickListener true
|
||||
}
|
||||
}
|
||||
|
||||
scrollView.onScroll {
|
||||
|
@ -145,25 +106,7 @@ class ViewSiteActivity : AppCompatActivity(),
|
|||
}
|
||||
|
||||
inputUrl.setOnFocusChangeListener { _, hasFocus ->
|
||||
if (!hasFocus) {
|
||||
val inputStr = inputUrl.text
|
||||
.toString()
|
||||
.trim()
|
||||
if (inputStr.isEmpty()) {
|
||||
return@setOnFocusChangeListener
|
||||
}
|
||||
|
||||
val uri = Uri.parse(inputStr)
|
||||
if (uri.scheme == null) {
|
||||
inputUrl.setText("http://$inputStr")
|
||||
textUrlWarning.hide()
|
||||
} else if (!uri.isHttpOrHttps()) {
|
||||
textUrlWarning.show()
|
||||
textUrlWarning.setText(R.string.warning_http_url)
|
||||
} else {
|
||||
textUrlWarning.hide()
|
||||
}
|
||||
}
|
||||
presenter.onUrlInputFocusChange(hasFocus, inputUrl.trimmedText())
|
||||
}
|
||||
|
||||
val validationOptionsAdapter = ArrayAdapter(
|
||||
|
@ -174,34 +117,56 @@ class ViewSiteActivity : AppCompatActivity(),
|
|||
validationOptionsAdapter.setDropDownViewResource(R.layout.list_item_spinner_dropdown)
|
||||
responseValidationMode.adapter = validationOptionsAdapter
|
||||
|
||||
responseValidationMode.onItemSelected { pos ->
|
||||
responseValidationSearchTerm.showOrHide(pos == 1)
|
||||
scriptInputLayout.showOrHide(pos == 2)
|
||||
responseValidationMode.onItemSelected(presenter::onValidationModeSelected)
|
||||
|
||||
validationModeDescription.setText(
|
||||
when (pos) {
|
||||
0 -> R.string.validation_mode_status_desc
|
||||
1 -> R.string.validation_mode_term_desc
|
||||
2 -> R.string.validation_mode_javascript_desc
|
||||
else -> throw IllegalStateException("Unexpected position: $pos")
|
||||
}
|
||||
doneBtn.setOnClickListener {
|
||||
val checkInterval = checkIntervalLayout.getSelectedCheckInterval()
|
||||
val validationMode =
|
||||
responseValidationMode.selectedItemPosition.indexToValidationMode()
|
||||
|
||||
presenter.commit(
|
||||
name = inputName.trimmedText(),
|
||||
url = inputUrl.trimmedText(),
|
||||
checkInterval = checkInterval,
|
||||
validationMode = validationMode,
|
||||
validationContent = validationMode.validationContent()
|
||||
)
|
||||
}
|
||||
|
||||
currentModel = intent.getSerializableExtra(KEY_VIEW_MODEL) as ServerModel
|
||||
displayCurrentModel()
|
||||
disableChecksButton.setOnClickListener { maybeDisableChecks() }
|
||||
|
||||
presenter.takeView(this, intent)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
if (intent != null && intent.hasExtra(KEY_VIEW_MODEL)) {
|
||||
currentModel = intent.getSerializableExtra(KEY_VIEW_MODEL) as ServerModel
|
||||
displayCurrentModel()
|
||||
presenter.onNewIntent(intent)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
presenter.dropView()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun setLoading() = loadingProgress.setLoading()
|
||||
|
||||
override fun setDoneLoading() = loadingProgress.setDone()
|
||||
|
||||
override fun showOrHideUrlSchemeWarning(show: Boolean) {
|
||||
textUrlWarning.showOrHide(show)
|
||||
if (show) {
|
||||
textUrlWarning.setText(R.string.warning_http_url)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun displayCurrentModel() = with(currentModel) {
|
||||
override fun showOrHideValidationSearchTerm(show: Boolean) =
|
||||
responseValidationSearchTerm.showOrHide(show)
|
||||
|
||||
override fun showOrHideScriptInput(show: Boolean) = scriptInputLayout.showOrHide(show)
|
||||
|
||||
override fun setValidationModeDescription(res: Int) = validationModeDescription.setText(res)
|
||||
|
||||
override fun displayModel(model: ServerModel) = with(model) {
|
||||
iconStatus.setStatus(this.status)
|
||||
inputName.setText(this.name)
|
||||
inputUrl.setText(this.url)
|
||||
|
@ -217,26 +182,25 @@ class ViewSiteActivity : AppCompatActivity(),
|
|||
}
|
||||
}
|
||||
|
||||
textNextCheck.text = (this.lastCheck + this.checkInterval).formatDate()
|
||||
if (this.disabled) {
|
||||
textNextCheck.setText(R.string.auto_checks_disabled)
|
||||
} else {
|
||||
textNextCheck.text = (this.lastCheck + this.checkInterval).formatDate()
|
||||
}
|
||||
checkIntervalLayout.set(this.checkInterval)
|
||||
|
||||
responseValidationMode.setSelection(validationMode.value - 1)
|
||||
|
||||
when (this.validationMode) {
|
||||
TERM_SEARCH -> responseValidationSearchTerm.setText(this.validationContent ?: "")
|
||||
JAVASCRIPT -> {
|
||||
scriptInputLayout.setCode(this.validationContent)
|
||||
}
|
||||
JAVASCRIPT -> scriptInputLayout.setCode(this.validationContent)
|
||||
else -> {
|
||||
responseValidationSearchTerm.setText("")
|
||||
scriptInputLayout.clear()
|
||||
}
|
||||
}
|
||||
|
||||
disableChecksButton.setOnClickListener(this@ViewSiteActivity)
|
||||
disableChecksButton.showOrHide(!this.disabled)
|
||||
|
||||
doneBtn.setOnClickListener(this@ViewSiteActivity)
|
||||
doneBtn.setText(
|
||||
if (this.disabled) R.string.renable_and_save_changes
|
||||
else R.string.save_changes
|
||||
|
@ -245,6 +209,43 @@ class ViewSiteActivity : AppCompatActivity(),
|
|||
invalidateMenuForStatus()
|
||||
}
|
||||
|
||||
override fun setInputErrors(errors: InputErrors) {
|
||||
inputName.error = if (errors.name != null) {
|
||||
getString(errors.name!!)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
inputUrl.error = if (errors.url != null) {
|
||||
getString(errors.url!!)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
checkIntervalLayout.setError(
|
||||
if (errors.checkInterval != null) {
|
||||
getString(errors.checkInterval!!)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
responseValidationSearchTerm.error = if (errors.termSearch != null) {
|
||||
getString(errors.termSearch!!)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
scriptInputLayout.setError(
|
||||
if (errors.javaScript != null) {
|
||||
getString(errors.javaScript!!)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun scopeWhileAttached(
|
||||
context: CoroutineContext,
|
||||
exec: ScopeReceiver
|
||||
) = rootView.scopeWhileAttached(context, exec)
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
val filter = IntentFilter().apply {
|
||||
|
@ -258,175 +259,41 @@ class ViewSiteActivity : AppCompatActivity(),
|
|||
safeUnregisterReceiver(intentReceiver)
|
||||
}
|
||||
|
||||
override fun onClick(view: View) = when (view.id) {
|
||||
R.id.doneBtn -> performSaveChangesAndFinish()
|
||||
R.id.disableChecksButton -> maybeDisableChecks()
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.refresh -> performCheckNow()
|
||||
R.id.remove -> maybeRemoveSite()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun performCheckNow() {
|
||||
rootView.scopeWhileAttached(Main) {
|
||||
launch(coroutineContext) {
|
||||
disableChecksButton.disable()
|
||||
loadingProgress.setLoading()
|
||||
updateModelFromInput(false)
|
||||
currentModel = currentModel.copy(status = WAITING)
|
||||
displayCurrentModel()
|
||||
|
||||
async(IO) { serverModelStore.update(currentModel) }.await()
|
||||
|
||||
checkStatusManager.scheduleCheck(
|
||||
site = currentModel,
|
||||
rightNow = true,
|
||||
cancelPrevious = true
|
||||
)
|
||||
loadingProgress.setDone()
|
||||
disableChecksButton.enable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeRemoveSite() {
|
||||
val model = presenter.currentModel()
|
||||
MaterialDialog(this).show {
|
||||
title(R.string.remove_site)
|
||||
message(
|
||||
text = HtmlCompat.fromHtml(
|
||||
context.getString(R.string.remove_site_prompt, currentModel.name),
|
||||
context.getString(R.string.remove_site_prompt, model.name),
|
||||
FROM_HTML_MODE_LEGACY
|
||||
)
|
||||
)
|
||||
positiveButton(R.string.remove) {
|
||||
checkStatusManager.cancelCheck(currentModel)
|
||||
notificationManager.cancelStatusNotification(currentModel)
|
||||
performRemoveSite()
|
||||
}
|
||||
positiveButton(R.string.remove) { presenter.removeSite() }
|
||||
negativeButton(android.R.string.cancel)
|
||||
}
|
||||
}
|
||||
|
||||
private fun performRemoveSite() {
|
||||
rootView.scopeWhileAttached(Main) {
|
||||
launch(coroutineContext) {
|
||||
loadingProgress.setLoading()
|
||||
async(IO) { serverModelStore.delete(currentModel) }.await()
|
||||
loadingProgress.setDone()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeDisableChecks() {
|
||||
val model = presenter.currentModel()
|
||||
MaterialDialog(this).show {
|
||||
title(R.string.disable_automatic_checks)
|
||||
message(
|
||||
text = HtmlCompat.fromHtml(
|
||||
context.getString(R.string.disable_automatic_checks_prompt, currentModel.name),
|
||||
context.getString(R.string.disable_automatic_checks_prompt, model.name),
|
||||
FROM_HTML_MODE_LEGACY
|
||||
)
|
||||
)
|
||||
positiveButton(R.string.disable) {
|
||||
checkStatusManager.cancelCheck(currentModel)
|
||||
notificationManager.cancelStatusNotification(currentModel)
|
||||
performDisableChecks()
|
||||
}
|
||||
positiveButton(R.string.disable) { presenter.disableChecks() }
|
||||
negativeButton(android.R.string.cancel)
|
||||
}
|
||||
}
|
||||
|
||||
private fun performDisableChecks() {
|
||||
rootView.scopeWhileAttached(Main) {
|
||||
launch(coroutineContext) {
|
||||
loadingProgress.setLoading()
|
||||
currentModel = currentModel.copy(
|
||||
disabled = true,
|
||||
lastCheck = LAST_CHECK_NONE
|
||||
)
|
||||
async(IO) { serverModelStore.update(currentModel) }.await()
|
||||
loadingProgress.setDone()
|
||||
displayCurrentModel() // invalidate UI to reflect disabled state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun performSaveChangesAndFinish() {
|
||||
rootView.scopeWhileAttached(Main) {
|
||||
launch(coroutineContext) {
|
||||
loadingProgress.setLoading()
|
||||
if (!updateModelFromInput(true)) {
|
||||
// Validation didn't pass
|
||||
loadingProgress.setDone()
|
||||
return@launch
|
||||
}
|
||||
|
||||
async(IO) { serverModelStore.update(currentModel) }.await()
|
||||
checkStatusManager.scheduleCheck(
|
||||
site = currentModel,
|
||||
rightNow = true,
|
||||
cancelPrevious = true
|
||||
)
|
||||
|
||||
loadingProgress.setDone()
|
||||
setResult(RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun invalidateMenuForStatus() {
|
||||
val model = presenter.currentModel()
|
||||
val item = toolbar.menu.findItem(R.id.refresh)
|
||||
item.isEnabled = currentModel.status != CHECKING && currentModel.status != WAITING
|
||||
}
|
||||
|
||||
@CheckResult private fun updateModelFromInput(withValidation: Boolean): Boolean {
|
||||
currentModel = currentModel.copy(
|
||||
name = inputName.trimmedText(),
|
||||
url = inputUrl.trimmedText(),
|
||||
status = WAITING,
|
||||
disabled = false
|
||||
)
|
||||
|
||||
if (withValidation && currentModel.name.isEmpty()) {
|
||||
inputName.error = getString(R.string.please_enter_name)
|
||||
return false
|
||||
} else {
|
||||
inputName.error = null
|
||||
}
|
||||
|
||||
if (withValidation && currentModel.url.isEmpty()) {
|
||||
inputUrl.error = getString(R.string.please_enter_url)
|
||||
return false
|
||||
} else {
|
||||
inputUrl.error = null
|
||||
if (withValidation && !WEB_URL.matcher(currentModel.url).find()) {
|
||||
inputUrl.error = getString(R.string.please_enter_valid_url)
|
||||
return false
|
||||
} else {
|
||||
val uri = Uri.parse(currentModel.url)
|
||||
if (uri.scheme == null) {
|
||||
currentModel = currentModel.copy(url = "http://${currentModel.url}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val selectedCheckInterval = checkIntervalLayout.getSelectedCheckInterval()
|
||||
val selectedValidationMode =
|
||||
responseValidationMode.selectedItemPosition.indexToValidationMode()
|
||||
|
||||
currentModel = currentModel.copy(
|
||||
checkInterval = selectedCheckInterval,
|
||||
validationMode = selectedValidationMode,
|
||||
validationContent = selectedValidationMode.validationContent()
|
||||
)
|
||||
|
||||
return true
|
||||
item.isEnabled = model.status != CHECKING &&
|
||||
model.status != WAITING
|
||||
}
|
||||
|
||||
private fun ValidationMode.validationContent() = when (this) {
|
||||
|
|
|
@ -0,0 +1,268 @@
|
|||
/*
|
||||
* Licensed under Apache-2.0
|
||||
*
|
||||
* Designed and developed by Aidan Follestad (@afollestad)
|
||||
*/
|
||||
package com.afollestad.nocknock.ui.viewsite
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.annotation.CheckResult
|
||||
import com.afollestad.nocknock.R
|
||||
import com.afollestad.nocknock.data.ServerModel
|
||||
import com.afollestad.nocknock.data.ServerStatus.WAITING
|
||||
import com.afollestad.nocknock.data.ValidationMode
|
||||
import com.afollestad.nocknock.data.ValidationMode.JAVASCRIPT
|
||||
import com.afollestad.nocknock.data.ValidationMode.TERM_SEARCH
|
||||
import com.afollestad.nocknock.engine.db.ServerModelStore
|
||||
import com.afollestad.nocknock.engine.statuscheck.CheckStatusJob.Companion.ACTION_STATUS_UPDATE
|
||||
import com.afollestad.nocknock.engine.statuscheck.CheckStatusJob.Companion.KEY_UPDATE_MODEL
|
||||
import com.afollestad.nocknock.engine.statuscheck.CheckStatusManager
|
||||
import com.afollestad.nocknock.notifications.NockNotificationManager
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.HttpUrl
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import javax.inject.Inject
|
||||
|
||||
const val KEY_VIEW_MODEL = "site_model"
|
||||
|
||||
/** @author Aidan Follestad (@afollestad) */
|
||||
data class InputErrors(
|
||||
var name: Int? = null,
|
||||
var url: Int? = null,
|
||||
var checkInterval: Int? = null,
|
||||
var termSearch: Int? = null,
|
||||
var javaScript: Int? = null
|
||||
) {
|
||||
@CheckResult fun any(): Boolean {
|
||||
return name != null || url != null || checkInterval != null ||
|
||||
termSearch != null || javaScript != null
|
||||
}
|
||||
}
|
||||
|
||||
/** @author Aidan Follestad (@afollestad) */
|
||||
interface ViewSitePresenter {
|
||||
|
||||
fun takeView(
|
||||
view: ViewSiteView,
|
||||
intent: Intent
|
||||
)
|
||||
|
||||
fun onBroadcast(intent: Intent)
|
||||
|
||||
fun onNewIntent(intent: Intent?)
|
||||
|
||||
fun onUrlInputFocusChange(
|
||||
focused: Boolean,
|
||||
content: String
|
||||
)
|
||||
|
||||
fun onValidationModeSelected(index: Int)
|
||||
|
||||
fun commit(
|
||||
name: String,
|
||||
url: String,
|
||||
checkInterval: Long,
|
||||
validationMode: ValidationMode,
|
||||
validationContent: String?
|
||||
)
|
||||
|
||||
fun checkNow()
|
||||
|
||||
fun disableChecks()
|
||||
|
||||
fun removeSite()
|
||||
|
||||
fun currentModel(): ServerModel
|
||||
|
||||
fun dropView()
|
||||
}
|
||||
|
||||
/** @author Aidan Follestad (@afollestad) */
|
||||
class RealViewSitePresenter @Inject constructor(
|
||||
private val serverModelStore: ServerModelStore,
|
||||
private val checkStatusManager: CheckStatusManager,
|
||||
private val notificationManager: NockNotificationManager
|
||||
) : ViewSitePresenter {
|
||||
|
||||
private var view: ViewSiteView? = null
|
||||
private var currentModel: ServerModel? = null
|
||||
|
||||
override fun takeView(
|
||||
view: ViewSiteView,
|
||||
intent: Intent
|
||||
) {
|
||||
this.currentModel = intent.getSerializableExtra(KEY_VIEW_MODEL) as ServerModel
|
||||
this.view = view.apply {
|
||||
displayModel(currentModel!!)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBroadcast(intent: Intent) {
|
||||
if (intent.action == ACTION_STATUS_UPDATE) {
|
||||
val model = intent.getSerializableExtra(KEY_UPDATE_MODEL) as? ServerModel ?: return
|
||||
this.currentModel = model
|
||||
view?.run {
|
||||
displayModel(model)
|
||||
setDoneLoading() // in case this is the result of a manual refresh
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
if (intent != null && intent.hasExtra(KEY_VIEW_MODEL)) {
|
||||
currentModel = intent.getSerializableExtra(KEY_VIEW_MODEL) as ServerModel
|
||||
view?.displayModel(currentModel!!)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUrlInputFocusChange(
|
||||
focused: Boolean,
|
||||
content: String
|
||||
) {
|
||||
if (content.isEmpty() || focused) {
|
||||
return
|
||||
}
|
||||
val url = HttpUrl.parse(content)
|
||||
if (url == null ||
|
||||
(url.scheme() != "http" &&
|
||||
url.scheme() != "https")
|
||||
) {
|
||||
view?.showOrHideUrlSchemeWarning(true)
|
||||
} else {
|
||||
view?.showOrHideUrlSchemeWarning(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onValidationModeSelected(index: Int) = with(view!!) {
|
||||
showOrHideValidationSearchTerm(index == 1)
|
||||
showOrHideScriptInput(index == 2)
|
||||
setValidationModeDescription(
|
||||
when (index) {
|
||||
0 -> R.string.validation_mode_status_desc
|
||||
1 -> R.string.validation_mode_term_desc
|
||||
2 -> R.string.validation_mode_javascript_desc
|
||||
else -> throw IllegalStateException("Unknown validation mode position: $index")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun commit(
|
||||
name: String,
|
||||
url: String,
|
||||
checkInterval: Long,
|
||||
validationMode: ValidationMode,
|
||||
validationContent: String?
|
||||
) {
|
||||
val inputErrors = InputErrors()
|
||||
|
||||
if (name.isEmpty()) {
|
||||
inputErrors.name = R.string.please_enter_name
|
||||
}
|
||||
if (url.isEmpty()) {
|
||||
inputErrors.url = R.string.please_enter_url
|
||||
} else if (HttpUrl.parse(url) == null) {
|
||||
inputErrors.url = R.string.please_enter_valid_url
|
||||
}
|
||||
if (checkInterval <= 0) {
|
||||
inputErrors.checkInterval = R.string.please_enter_check_interval
|
||||
}
|
||||
if (validationMode == TERM_SEARCH && validationContent.isNullOrEmpty()) {
|
||||
inputErrors.termSearch = R.string.please_enter_search_term
|
||||
} else if (validationMode == JAVASCRIPT && validationContent.isNullOrEmpty()) {
|
||||
inputErrors.javaScript = R.string.please_enter_javaScript
|
||||
}
|
||||
|
||||
if (inputErrors.any()) {
|
||||
view?.setInputErrors(inputErrors)
|
||||
return
|
||||
}
|
||||
|
||||
val newModel = currentModel!!.copy(
|
||||
name = name,
|
||||
url = url,
|
||||
status = WAITING,
|
||||
checkInterval = checkInterval,
|
||||
validationMode = validationMode,
|
||||
validationContent = validationContent,
|
||||
disabled = false
|
||||
)
|
||||
|
||||
with(view!!) {
|
||||
scopeWhileAttached(Main) {
|
||||
launch(coroutineContext) {
|
||||
setLoading()
|
||||
async(IO) { serverModelStore.update(newModel) }.await()
|
||||
checkStatusManager.scheduleCheck(
|
||||
site = newModel,
|
||||
rightNow = true,
|
||||
cancelPrevious = true
|
||||
)
|
||||
setDoneLoading()
|
||||
view?.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun checkNow() = with(view!!) {
|
||||
setLoading()
|
||||
val checkModel = currentModel!!.copy(
|
||||
status = WAITING
|
||||
)
|
||||
view?.displayModel(checkModel)
|
||||
checkStatusManager.scheduleCheck(
|
||||
site = checkModel,
|
||||
rightNow = true,
|
||||
cancelPrevious = true
|
||||
)
|
||||
}
|
||||
|
||||
override fun disableChecks() {
|
||||
val site = currentModel!!
|
||||
checkStatusManager.cancelCheck(site)
|
||||
notificationManager.cancelStatusNotification(site)
|
||||
|
||||
with(view!!) {
|
||||
scopeWhileAttached(Main) {
|
||||
launch(coroutineContext) {
|
||||
setLoading()
|
||||
currentModel = currentModel!!.copy(disabled = true)
|
||||
async(IO) { serverModelStore.update(currentModel!!) }.await()
|
||||
setDoneLoading()
|
||||
view?.displayModel(currentModel!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeSite() {
|
||||
val site = currentModel!!
|
||||
checkStatusManager.cancelCheck(site)
|
||||
notificationManager.cancelStatusNotification(site)
|
||||
|
||||
with(view!!) {
|
||||
scopeWhileAttached(Main) {
|
||||
launch(coroutineContext) {
|
||||
setLoading()
|
||||
async(IO) { serverModelStore.delete(site) }.await()
|
||||
setDoneLoading()
|
||||
view?.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun currentModel() = this.currentModel!!
|
||||
|
||||
override fun dropView() {
|
||||
view = null
|
||||
currentModel = null
|
||||
}
|
||||
|
||||
@TestOnly fun setModel(model: ServerModel) {
|
||||
this.currentModel = model
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Licensed under Apache-2.0
|
||||
*
|
||||
* Designed and developed by Aidan Follestad (@afollestad)
|
||||
*/
|
||||
package com.afollestad.nocknock.ui.viewsite
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.afollestad.nocknock.data.ServerModel
|
||||
import com.afollestad.nocknock.utilities.ext.ScopeReceiver
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
/** @author Aidan Follestad (@afollestad) */
|
||||
interface ViewSiteView {
|
||||
|
||||
fun setLoading()
|
||||
|
||||
fun setDoneLoading()
|
||||
|
||||
fun displayModel(model: ServerModel)
|
||||
|
||||
fun showOrHideUrlSchemeWarning(show: Boolean)
|
||||
|
||||
fun showOrHideValidationSearchTerm(show: Boolean)
|
||||
|
||||
fun showOrHideScriptInput(show: Boolean)
|
||||
|
||||
fun setValidationModeDescription(@StringRes res: Int)
|
||||
|
||||
fun setInputErrors(errors: InputErrors)
|
||||
|
||||
fun scopeWhileAttached(
|
||||
context: CoroutineContext,
|
||||
exec: ScopeReceiver
|
||||
)
|
||||
|
||||
fun finish()
|
||||
}
|
|
@ -0,0 +1,362 @@
|
|||
/*
|
||||
* Licensed under Apache-2.0
|
||||
*
|
||||
* Designed and developed by Aidan Follestad (@afollestad)
|
||||
*/
|
||||
package com.afollestad.nocknock
|
||||
|
||||
import android.content.Intent
|
||||
import com.afollestad.nocknock.data.LAST_CHECK_NONE
|
||||
import com.afollestad.nocknock.data.ServerModel
|
||||
import com.afollestad.nocknock.data.ServerStatus.WAITING
|
||||
import com.afollestad.nocknock.data.ValidationMode.JAVASCRIPT
|
||||
import com.afollestad.nocknock.data.ValidationMode.STATUS_CODE
|
||||
import com.afollestad.nocknock.data.ValidationMode.TERM_SEARCH
|
||||
import com.afollestad.nocknock.engine.db.ServerModelStore
|
||||
import com.afollestad.nocknock.engine.statuscheck.CheckStatusJob.Companion.ACTION_STATUS_UPDATE
|
||||
import com.afollestad.nocknock.engine.statuscheck.CheckStatusJob.Companion.KEY_UPDATE_MODEL
|
||||
import com.afollestad.nocknock.engine.statuscheck.CheckStatusManager
|
||||
import com.afollestad.nocknock.notifications.NockNotificationManager
|
||||
import com.afollestad.nocknock.ui.viewsite.InputErrors
|
||||
import com.afollestad.nocknock.ui.viewsite.KEY_VIEW_MODEL
|
||||
import com.afollestad.nocknock.ui.viewsite.RealViewSitePresenter
|
||||
import com.afollestad.nocknock.ui.viewsite.ViewSiteView
|
||||
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.doReturn
|
||||
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 ViewSitePresenterTest {
|
||||
|
||||
private val serverModelStore = mock<ServerModelStore> {
|
||||
on { runBlocking { put(any()) } } doAnswer { inv ->
|
||||
inv.getArgument<ServerModel>(0)
|
||||
}
|
||||
}
|
||||
private val checkStatusManager = mock<CheckStatusManager>()
|
||||
private val notificationManager = mock<NockNotificationManager>()
|
||||
private val view = mock<ViewSiteView>()
|
||||
|
||||
private val presenter = RealViewSitePresenter(
|
||||
serverModelStore,
|
||||
checkStatusManager,
|
||||
notificationManager
|
||||
)
|
||||
|
||||
@Before fun setup() {
|
||||
doAnswer {
|
||||
val exec = it.getArgument<ScopeReceiver>(1)
|
||||
runBlocking { exec() }
|
||||
Unit
|
||||
}.whenever(view)
|
||||
.scopeWhileAttached(any(), any())
|
||||
|
||||
val model = fakeModel().copy(lastCheck = 0)
|
||||
val intent = fakeIntent("")
|
||||
whenever(intent.getSerializableExtra(KEY_VIEW_MODEL))
|
||||
.doReturn(model)
|
||||
presenter.takeView(view, intent)
|
||||
assertThat(presenter.currentModel()).isEqualTo(model)
|
||||
verify(view, times(1)).displayModel(model)
|
||||
}
|
||||
|
||||
@After fun destroy() {
|
||||
presenter.dropView()
|
||||
}
|
||||
|
||||
@Test fun onBroadcast() {
|
||||
val badIntent = fakeIntent("Hello World")
|
||||
presenter.onBroadcast(badIntent)
|
||||
|
||||
val model = fakeModel()
|
||||
val goodIntent = fakeIntent(ACTION_STATUS_UPDATE)
|
||||
whenever(goodIntent.getSerializableExtra(KEY_UPDATE_MODEL))
|
||||
.doReturn(model)
|
||||
|
||||
presenter.onBroadcast(goodIntent)
|
||||
assertThat(presenter.currentModel()).isEqualTo(model)
|
||||
verify(view, times(1)).displayModel(model)
|
||||
verify(view).setDoneLoading()
|
||||
}
|
||||
|
||||
@Test fun onNewIntent() {
|
||||
val badIntent = fakeIntent(ACTION_STATUS_UPDATE)
|
||||
presenter.onBroadcast(badIntent)
|
||||
|
||||
val model = fakeModel()
|
||||
val goodIntent = fakeIntent(ACTION_STATUS_UPDATE)
|
||||
whenever(goodIntent.getSerializableExtra(KEY_VIEW_MODEL))
|
||||
.doReturn(model)
|
||||
presenter.onBroadcast(goodIntent)
|
||||
|
||||
verify(view, times(1)).displayModel(model)
|
||||
}
|
||||
|
||||
@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(R.string.validation_mode_status_desc)
|
||||
}
|
||||
|
||||
@Test fun onValidationModeSelected_termSearch() {
|
||||
presenter.onValidationModeSelected(1)
|
||||
verify(view).showOrHideValidationSearchTerm(true)
|
||||
verify(view).showOrHideScriptInput(false)
|
||||
verify(view).setValidationModeDescription(R.string.validation_mode_term_desc)
|
||||
}
|
||||
|
||||
@Test fun onValidationModeSelected_javaScript() {
|
||||
presenter.onValidationModeSelected(2)
|
||||
verify(view).showOrHideValidationSearchTerm(false)
|
||||
verify(view).showOrHideScriptInput(true)
|
||||
verify(view).setValidationModeDescription(R.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
|
||||
)
|
||||
|
||||
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(R.string.please_enter_name)
|
||||
}
|
||||
|
||||
@Test fun commit_urlEmptyError() {
|
||||
presenter.commit(
|
||||
"Testing",
|
||||
"",
|
||||
1,
|
||||
STATUS_CODE,
|
||||
null
|
||||
)
|
||||
|
||||
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(R.string.please_enter_url)
|
||||
}
|
||||
|
||||
@Test fun commit_urlFormatError() {
|
||||
presenter.commit(
|
||||
"Testing",
|
||||
"ftp://hello.com",
|
||||
1,
|
||||
STATUS_CODE,
|
||||
null
|
||||
)
|
||||
|
||||
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(R.string.please_enter_valid_url)
|
||||
}
|
||||
|
||||
@Test fun commit_checkIntervalError() {
|
||||
presenter.commit(
|
||||
"Testing",
|
||||
"https://hello.com",
|
||||
-1,
|
||||
STATUS_CODE,
|
||||
null
|
||||
)
|
||||
|
||||
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(R.string.please_enter_check_interval)
|
||||
}
|
||||
|
||||
@Test fun commit_termSearchError() {
|
||||
presenter.commit(
|
||||
"Testing",
|
||||
"https://hello.com",
|
||||
1,
|
||||
TERM_SEARCH,
|
||||
null
|
||||
)
|
||||
|
||||
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(R.string.please_enter_search_term)
|
||||
}
|
||||
|
||||
@Test fun commit_javaScript_error() {
|
||||
presenter.commit(
|
||||
"Testing",
|
||||
"https://hello.com",
|
||||
1,
|
||||
JAVASCRIPT,
|
||||
null
|
||||
)
|
||||
|
||||
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(R.string.please_enter_javaScript)
|
||||
}
|
||||
|
||||
@Test fun commit_success() = runBlocking {
|
||||
val name = "Testing"
|
||||
val url = "https://hello.com"
|
||||
val checkInterval = 60000L
|
||||
val validationMode = TERM_SEARCH
|
||||
val validationContent = "Hello World"
|
||||
|
||||
val disabledModel = presenter.currentModel()
|
||||
.copy(disabled = true)
|
||||
presenter.setModel(disabledModel)
|
||||
|
||||
presenter.commit(
|
||||
name,
|
||||
url,
|
||||
checkInterval,
|
||||
validationMode,
|
||||
validationContent
|
||||
)
|
||||
|
||||
val modelCaptor = argumentCaptor<ServerModel>()
|
||||
verify(view).setLoading()
|
||||
verify(serverModelStore).update(modelCaptor.capture())
|
||||
|
||||
val model = modelCaptor.firstValue
|
||||
assertThat(model.name).isEqualTo(name)
|
||||
assertThat(model.url).isEqualTo(url)
|
||||
assertThat(model.checkInterval).isEqualTo(checkInterval)
|
||||
assertThat(model.validationMode).isEqualTo(validationMode)
|
||||
assertThat(model.validationContent).isEqualTo(validationContent)
|
||||
assertThat(model.disabled).isFalse()
|
||||
|
||||
verify(view, never()).setInputErrors(any())
|
||||
verify(checkStatusManager).scheduleCheck(
|
||||
site = model,
|
||||
rightNow = true,
|
||||
cancelPrevious = true,
|
||||
fromFinishingJob = false
|
||||
)
|
||||
verify(view).setDoneLoading()
|
||||
verify(view).finish()
|
||||
}
|
||||
|
||||
@Test fun checkNow() {
|
||||
val newModel = presenter.currentModel()
|
||||
.copy(
|
||||
status = WAITING
|
||||
)
|
||||
presenter.checkNow()
|
||||
|
||||
verify(view).setLoading()
|
||||
verify(view).displayModel(newModel)
|
||||
verify(checkStatusManager).scheduleCheck(
|
||||
site = newModel,
|
||||
rightNow = true,
|
||||
cancelPrevious = true
|
||||
)
|
||||
}
|
||||
|
||||
@Test fun disableChecks() = runBlocking {
|
||||
val model = presenter.currentModel()
|
||||
presenter.disableChecks()
|
||||
|
||||
verify(checkStatusManager).cancelCheck(model)
|
||||
verify(notificationManager).cancelStatusNotification(model)
|
||||
verify(view).setLoading()
|
||||
|
||||
val modelCaptor = argumentCaptor<ServerModel>()
|
||||
verify(serverModelStore).update(modelCaptor.capture())
|
||||
val newModel = modelCaptor.firstValue
|
||||
assertThat(newModel.disabled).isTrue()
|
||||
assertThat(newModel.lastCheck).isEqualTo(LAST_CHECK_NONE)
|
||||
|
||||
verify(view).setDoneLoading()
|
||||
verify(view, times(1)).displayModel(newModel)
|
||||
}
|
||||
|
||||
@Test fun removeSite() = runBlocking {
|
||||
val model = presenter.currentModel()
|
||||
presenter.removeSite()
|
||||
|
||||
verify(checkStatusManager).cancelCheck(model)
|
||||
verify(notificationManager).cancelStatusNotification(model)
|
||||
verify(view).setLoading()
|
||||
verify(serverModelStore).delete(model)
|
||||
verify(view).setDoneLoading()
|
||||
verify(view).finish()
|
||||
}
|
||||
|
||||
private fun fakeModel() = ServerModel(
|
||||
id = 1,
|
||||
name = "Test",
|
||||
url = "https://test.com",
|
||||
validationMode = STATUS_CODE
|
||||
)
|
||||
|
||||
private fun fakeIntent(action: String): Intent {
|
||||
return mock {
|
||||
on { getAction() } doReturn action
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
<resources>
|
||||
<string name="checks_disabled">Checks Disabled</string>
|
||||
<string name="auto_checks_disabled">Automatic Checks Disabled</string>
|
||||
</resources>
|
||||
|
|
|
@ -18,7 +18,7 @@ class LoadingIndicatorFrame(
|
|||
attrs: AttributeSet? = null
|
||||
) : FrameLayout(context, attrs) {
|
||||
companion object {
|
||||
private const val SHOW_DELAY_MS = 200L
|
||||
private const val SHOW_DELAY_MS = 100L
|
||||
}
|
||||
|
||||
private val showRunnable = Runnable { show() }
|
||||
|
|
Loading…
Add table
Reference in a new issue