mirror of
https://github.com/afollestad/nock-nock.git
synced 2025-08-12 02:48:40 +00:00
Move majority of MainActivity business logic to MainPresenter, write unit test
This commit is contained in:
parent
b36b41ca9d
commit
f87e1438d2
8 changed files with 315 additions and 101 deletions
|
@ -34,6 +34,11 @@ dependencies {
|
||||||
kapt 'com.google.dagger:dagger-compiler:' + versions.dagger
|
kapt 'com.google.dagger:dagger-compiler:' + versions.dagger
|
||||||
|
|
||||||
implementation 'com.afollestad.material-dialogs:core:' + versions.materialDialogs
|
implementation 'com.afollestad.material-dialogs:core:' + versions.materialDialogs
|
||||||
|
|
||||||
|
testImplementation 'junit:junit:' + versions.junit
|
||||||
|
testImplementation 'org.mockito:mockito-core:' + versions.mockito
|
||||||
|
testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:' + versions.mockitoKotlin
|
||||||
|
testImplementation 'com.google.truth:truth:' + versions.truth
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: '../spotless.gradle'
|
apply from: '../spotless.gradle'
|
|
@ -27,6 +27,7 @@ import javax.inject.Singleton
|
||||||
@Component(
|
@Component(
|
||||||
modules = [
|
modules = [
|
||||||
MainModule::class,
|
MainModule::class,
|
||||||
|
MainBindModule::class,
|
||||||
EngineModule::class,
|
EngineModule::class,
|
||||||
NotificationsModule::class,
|
NotificationsModule::class,
|
||||||
UtilitiesModule::class
|
UtilitiesModule::class
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Licensed under Apache-2.0
|
||||||
|
*
|
||||||
|
* Designed and developed by Aidan Follestad (@afollestad)
|
||||||
|
*/
|
||||||
|
package com.afollestad.nocknock.di
|
||||||
|
|
||||||
|
import com.afollestad.nocknock.presenters.MainPresenter
|
||||||
|
import com.afollestad.nocknock.presenters.RealMainPresenter
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
/** @author Aidan Follestad (afollestad) */
|
||||||
|
@Module
|
||||||
|
abstract class MainBindModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
abstract fun provideMainPresenter(
|
||||||
|
presenter: RealMainPresenter
|
||||||
|
): MainPresenter
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* Licensed under Apache-2.0
|
||||||
|
*
|
||||||
|
* Designed and developed by Aidan Follestad (@afollestad)
|
||||||
|
*/
|
||||||
|
package com.afollestad.nocknock.presenters
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import com.afollestad.nocknock.data.ServerModel
|
||||||
|
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 javax.inject.Inject
|
||||||
|
|
||||||
|
/** @author Aidan Follestad (afollestad) */
|
||||||
|
interface MainPresenter {
|
||||||
|
|
||||||
|
fun takeView(view: MainView)
|
||||||
|
|
||||||
|
fun onBroadcast(intent: Intent)
|
||||||
|
|
||||||
|
fun resume()
|
||||||
|
|
||||||
|
fun refreshSite(site: ServerModel)
|
||||||
|
|
||||||
|
fun removeSite(site: ServerModel)
|
||||||
|
|
||||||
|
fun dropView()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @author Aidan Follestad (afollestad) */
|
||||||
|
class RealMainPresenter @Inject constructor(
|
||||||
|
private val serverModelStore: ServerModelStore,
|
||||||
|
private val notificationManager: NockNotificationManager,
|
||||||
|
private val checkStatusManager: CheckStatusManager
|
||||||
|
) : MainPresenter {
|
||||||
|
|
||||||
|
private var view: MainView? = null
|
||||||
|
|
||||||
|
override fun takeView(view: MainView) {
|
||||||
|
this.view = view
|
||||||
|
notificationManager.createChannels()
|
||||||
|
ensureCheckJobs()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBroadcast(intent: Intent) {
|
||||||
|
if (intent.action == ACTION_STATUS_UPDATE) {
|
||||||
|
val model = intent.getSerializableExtra(KEY_UPDATE_MODEL) as? ServerModel ?: return
|
||||||
|
view?.updateModel(model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun resume() {
|
||||||
|
notificationManager.cancelStatusNotifications()
|
||||||
|
view?.run {
|
||||||
|
setModels(listOf())
|
||||||
|
scopeWhileAttached(Main) {
|
||||||
|
launch(coroutineContext) {
|
||||||
|
val models = async(IO) {
|
||||||
|
serverModelStore.get()
|
||||||
|
}
|
||||||
|
setModels(models.await())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun refreshSite(site: ServerModel) {
|
||||||
|
checkStatusManager.scheduleCheck(
|
||||||
|
site = site,
|
||||||
|
rightNow = true,
|
||||||
|
cancelPrevious = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeSite(site: ServerModel) {
|
||||||
|
checkStatusManager.cancelCheck(site)
|
||||||
|
notificationManager.cancelStatusNotification(site)
|
||||||
|
view?.scopeWhileAttached(Main) {
|
||||||
|
launch(coroutineContext) {
|
||||||
|
async(IO) { serverModelStore.delete(site) }.await()
|
||||||
|
view?.onSiteDeleted(site)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dropView() {
|
||||||
|
view = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ensureCheckJobs() {
|
||||||
|
view?.scopeWhileAttached(IO) {
|
||||||
|
launch(coroutineContext) {
|
||||||
|
checkStatusManager.ensureScheduledChecks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Licensed under Apache-2.0
|
||||||
|
*
|
||||||
|
* Designed and developed by Aidan Follestad (@afollestad)
|
||||||
|
*/
|
||||||
|
package com.afollestad.nocknock.presenters
|
||||||
|
|
||||||
|
import com.afollestad.nocknock.data.ServerModel
|
||||||
|
import com.afollestad.nocknock.utilities.ext.ScopeReceiver
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
/** @author Aidan Follestad (afollestad) */
|
||||||
|
interface MainView {
|
||||||
|
|
||||||
|
fun setModels(models: List<ServerModel>)
|
||||||
|
|
||||||
|
fun updateModel(model: ServerModel)
|
||||||
|
|
||||||
|
fun onSiteDeleted(model: ServerModel)
|
||||||
|
|
||||||
|
fun scopeWhileAttached(
|
||||||
|
context: CoroutineContext,
|
||||||
|
exec: ScopeReceiver
|
||||||
|
)
|
||||||
|
}
|
|
@ -12,76 +12,51 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.text.HtmlCompat
|
|
||||||
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
|
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
|
||||||
|
import androidx.core.text.HtmlCompat.fromHtml
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
|
import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import com.afollestad.materialdialogs.list.listItems
|
import com.afollestad.materialdialogs.list.listItems
|
||||||
import com.afollestad.nocknock.BuildConfig
|
|
||||||
import com.afollestad.nocknock.R
|
import com.afollestad.nocknock.R
|
||||||
import com.afollestad.nocknock.adapter.ServerAdapter
|
import com.afollestad.nocknock.adapter.ServerAdapter
|
||||||
import com.afollestad.nocknock.data.ServerModel
|
import com.afollestad.nocknock.data.ServerModel
|
||||||
import com.afollestad.nocknock.dialogs.AboutDialog
|
import com.afollestad.nocknock.dialogs.AboutDialog
|
||||||
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.ACTION_STATUS_UPDATE
|
||||||
import com.afollestad.nocknock.engine.statuscheck.CheckStatusJob.Companion.KEY_UPDATE_MODEL
|
import com.afollestad.nocknock.presenters.MainPresenter
|
||||||
import com.afollestad.nocknock.engine.statuscheck.CheckStatusManager
|
import com.afollestad.nocknock.presenters.MainView
|
||||||
import com.afollestad.nocknock.notifications.NockNotificationManager
|
import com.afollestad.nocknock.utilities.ext.ScopeReceiver
|
||||||
import com.afollestad.nocknock.utilities.ext.injector
|
import com.afollestad.nocknock.utilities.ext.injector
|
||||||
import com.afollestad.nocknock.utilities.ext.safeRegisterReceiver
|
import com.afollestad.nocknock.utilities.ext.safeRegisterReceiver
|
||||||
import com.afollestad.nocknock.utilities.ext.safeUnregisterReceiver
|
import com.afollestad.nocknock.utilities.ext.safeUnregisterReceiver
|
||||||
import com.afollestad.nocknock.utilities.ext.scopeWhileAttached
|
import com.afollestad.nocknock.utilities.ext.scopeWhileAttached
|
||||||
import com.afollestad.nocknock.viewcomponents.ext.show
|
|
||||||
import com.afollestad.nocknock.viewcomponents.ext.showOrHide
|
import com.afollestad.nocknock.viewcomponents.ext.showOrHide
|
||||||
import kotlinx.android.synthetic.main.activity_main.fab
|
import kotlinx.android.synthetic.main.activity_main.fab
|
||||||
import kotlinx.android.synthetic.main.activity_main.list
|
import kotlinx.android.synthetic.main.activity_main.list
|
||||||
import kotlinx.android.synthetic.main.activity_main.rootView
|
import kotlinx.android.synthetic.main.activity_main.rootView
|
||||||
import kotlinx.android.synthetic.main.activity_main.toolbar
|
import kotlinx.android.synthetic.main.activity_main.toolbar
|
||||||
import kotlinx.android.synthetic.main.include_empty_view.emptyText
|
import kotlinx.android.synthetic.main.include_empty_view.emptyText
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
/** @author Aidan Follestad (afollestad) */
|
/** @author Aidan Follestad (afollestad) */
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity(), MainView {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val ADD_SITE_RQ = 6969
|
private const val ADD_SITE_RQ = 6969
|
||||||
private const val VIEW_SITE_RQ = 6923
|
private const val VIEW_SITE_RQ = 6923
|
||||||
|
|
||||||
private fun log(message: String) {
|
|
||||||
if (BuildConfig.DEBUG) {
|
|
||||||
Log.d("MainActivity", message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val intentReceiver = object : BroadcastReceiver() {
|
private val intentReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(
|
override fun onReceive(
|
||||||
context: Context,
|
context: Context,
|
||||||
intent: Intent
|
intent: Intent
|
||||||
) {
|
) = presenter.onBroadcast(intent)
|
||||||
log("Received broadcast ${intent.action}")
|
|
||||||
when (intent.action) {
|
|
||||||
ACTION_STATUS_UPDATE -> {
|
|
||||||
val model = intent.getSerializableExtra(KEY_UPDATE_MODEL) as? ServerModel ?: return
|
|
||||||
log("Received model update: $model")
|
|
||||||
list.post { adapter.update(model) }
|
|
||||||
}
|
|
||||||
else -> throw IllegalStateException("Unexpected intent: ${intent.action}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject lateinit var serverModelStore: ServerModelStore
|
@Inject lateinit var presenter: MainPresenter
|
||||||
@Inject lateinit var notificationManager: NockNotificationManager
|
|
||||||
@Inject lateinit var checkStatusManager: CheckStatusManager
|
|
||||||
|
|
||||||
private lateinit var adapter: ServerAdapter
|
private lateinit var adapter: ServerAdapter
|
||||||
|
|
||||||
|
@ -112,16 +87,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
notificationManager.createChannels()
|
presenter.takeView(this)
|
||||||
ensureCheckJobs()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ensureCheckJobs() {
|
|
||||||
rootView.scopeWhileAttached(IO) {
|
|
||||||
launch(coroutineContext) {
|
|
||||||
checkStatusManager.ensureScheduledChecks()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
@ -130,9 +96,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
addAction(ACTION_STATUS_UPDATE)
|
addAction(ACTION_STATUS_UPDATE)
|
||||||
}
|
}
|
||||||
safeRegisterReceiver(intentReceiver, filter)
|
safeRegisterReceiver(intentReceiver, filter)
|
||||||
|
presenter.resume()
|
||||||
notificationManager.cancelStatusNotifications()
|
|
||||||
refreshModels()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
|
@ -140,29 +104,28 @@ class MainActivity : AppCompatActivity() {
|
||||||
safeUnregisterReceiver(intentReceiver)
|
safeUnregisterReceiver(intentReceiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshModels() {
|
override fun onDestroy() {
|
||||||
adapter.clear()
|
presenter.dropView()
|
||||||
emptyText.show()
|
super.onDestroy()
|
||||||
rootView.scopeWhileAttached(Main) {
|
|
||||||
launch(coroutineContext) {
|
|
||||||
val models = async(IO) { serverModelStore.get() }.await()
|
|
||||||
adapter.set(models)
|
|
||||||
emptyText.showOrHide(adapter.itemCount == 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(
|
override fun setModels(models: List<ServerModel>) {
|
||||||
requestCode: Int,
|
adapter.set(models)
|
||||||
resultCode: Int,
|
emptyText.showOrHide(models.isEmpty())
|
||||||
data: Intent?
|
|
||||||
) {
|
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
|
||||||
if (resultCode == RESULT_OK) {
|
|
||||||
refreshModels()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun updateModel(model: ServerModel) = adapter.update(model)
|
||||||
|
|
||||||
|
override fun onSiteDeleted(model: ServerModel) {
|
||||||
|
adapter.remove(model)
|
||||||
|
emptyText.showOrHide(adapter.itemCount == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun scopeWhileAttached(
|
||||||
|
context: CoroutineContext,
|
||||||
|
exec: ScopeReceiver
|
||||||
|
) = rootView.scopeWhileAttached(context, exec)
|
||||||
|
|
||||||
private fun onSiteSelected(
|
private fun onSiteSelected(
|
||||||
model: ServerModel,
|
model: ServerModel,
|
||||||
longClick: Boolean
|
longClick: Boolean
|
||||||
|
@ -172,20 +135,8 @@ class MainActivity : AppCompatActivity() {
|
||||||
title(R.string.options)
|
title(R.string.options)
|
||||||
listItems(R.array.site_long_options) { _, i, _ ->
|
listItems(R.array.site_long_options) { _, i, _ ->
|
||||||
when (i) {
|
when (i) {
|
||||||
0 -> {
|
0 -> presenter.refreshSite(model)
|
||||||
checkStatusManager.scheduleCheck(
|
1 -> maybeRemoveSite(model)
|
||||||
site = model,
|
|
||||||
rightNow = true,
|
|
||||||
cancelPrevious = true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
1 -> {
|
|
||||||
maybeRemoveSite(model) {
|
|
||||||
adapter.remove(model)
|
|
||||||
emptyText.showOrHide(adapter.itemCount == 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw IllegalStateException("Unexpected index: $i")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -199,35 +150,19 @@ class MainActivity : AppCompatActivity() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun maybeRemoveSite(
|
private fun maybeRemoveSite(model: ServerModel) {
|
||||||
model: ServerModel,
|
|
||||||
onRemoved: (() -> Unit)?
|
|
||||||
) {
|
|
||||||
MaterialDialog(this).show {
|
MaterialDialog(this).show {
|
||||||
title(R.string.remove_site)
|
title(R.string.remove_site)
|
||||||
message(
|
message(
|
||||||
text = HtmlCompat.fromHtml(
|
text = fromHtml(
|
||||||
context.getString(R.string.remove_site_prompt, model.name), FROM_HTML_MODE_LEGACY
|
context.getString(R.string.remove_site_prompt, model.name),
|
||||||
|
FROM_HTML_MODE_LEGACY
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
positiveButton(R.string.remove) {
|
positiveButton(R.string.remove) {
|
||||||
checkStatusManager.cancelCheck(model)
|
presenter.removeSite(model)
|
||||||
notificationManager.cancelStatusNotification(model)
|
|
||||||
performRemoveSite(model, onRemoved)
|
|
||||||
}
|
}
|
||||||
negativeButton(android.R.string.cancel)
|
negativeButton(android.R.string.cancel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun performRemoveSite(
|
|
||||||
model: ServerModel,
|
|
||||||
onRemoved: (() -> Unit)?
|
|
||||||
) {
|
|
||||||
rootView.scopeWhileAttached(Main) {
|
|
||||||
launch(coroutineContext) {
|
|
||||||
async(IO) { serverModelStore.delete(model) }.await()
|
|
||||||
onRemoved?.invoke()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
119
app/src/test/java/com/afollestad/nocknock/MainPresenterTest.kt
Normal file
119
app/src/test/java/com/afollestad/nocknock/MainPresenterTest.kt
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
* 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.ServerModel
|
||||||
|
import com.afollestad.nocknock.data.ValidationMode.STATUS_CODE
|
||||||
|
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.presenters.MainView
|
||||||
|
import com.afollestad.nocknock.presenters.RealMainPresenter
|
||||||
|
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.times
|
||||||
|
import com.nhaarman.mockitokotlin2.verify
|
||||||
|
import com.nhaarman.mockitokotlin2.whenever
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class MainPresenterTest {
|
||||||
|
|
||||||
|
private val serverModelStore = mock<ServerModelStore>()
|
||||||
|
private val notificationManager = mock<NockNotificationManager>()
|
||||||
|
private val checkStatusManager = mock<CheckStatusManager>()
|
||||||
|
private val view = mock<MainView>()
|
||||||
|
|
||||||
|
private val presenter = RealMainPresenter(
|
||||||
|
serverModelStore,
|
||||||
|
notificationManager,
|
||||||
|
checkStatusManager
|
||||||
|
)
|
||||||
|
|
||||||
|
@Before fun setup() {
|
||||||
|
doAnswer {
|
||||||
|
val exec = it.getArgument<ScopeReceiver>(1)
|
||||||
|
runBlocking { exec() }
|
||||||
|
Unit
|
||||||
|
}.whenever(view)
|
||||||
|
.scopeWhileAttached(any(), any())
|
||||||
|
|
||||||
|
presenter.takeView(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
@After fun destroy() {
|
||||||
|
presenter.dropView()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun 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)
|
||||||
|
verify(view, times(1)).updateModel(model)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun resume() = runBlocking {
|
||||||
|
val model = fakeModel()
|
||||||
|
whenever(serverModelStore.get()).doReturn(listOf(model))
|
||||||
|
presenter.resume()
|
||||||
|
|
||||||
|
verify(notificationManager).cancelStatusNotifications()
|
||||||
|
|
||||||
|
val modelsCaptor = argumentCaptor<List<ServerModel>>()
|
||||||
|
verify(view, times(2)).setModels(modelsCaptor.capture())
|
||||||
|
assertThat(modelsCaptor.firstValue).isEmpty()
|
||||||
|
assertThat(modelsCaptor.lastValue.single()).isEqualTo(model)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun refreshSite() {
|
||||||
|
val model = fakeModel()
|
||||||
|
presenter.refreshSite(model)
|
||||||
|
|
||||||
|
verify(checkStatusManager).scheduleCheck(
|
||||||
|
site = model,
|
||||||
|
rightNow = true,
|
||||||
|
cancelPrevious = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun removeSite() = runBlocking {
|
||||||
|
val model = fakeModel()
|
||||||
|
presenter.removeSite(model)
|
||||||
|
|
||||||
|
verify(checkStatusManager).cancelCheck(model)
|
||||||
|
verify(notificationManager).cancelStatusNotification(model)
|
||||||
|
verify(serverModelStore).delete(model)
|
||||||
|
verify(view).onSiteDeleted(model)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fakeModel() = ServerModel(
|
||||||
|
name = "Test",
|
||||||
|
url = "https://test.com",
|
||||||
|
validationMode = STATUS_CODE
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun fakeIntent(action: String): Intent {
|
||||||
|
return mock {
|
||||||
|
on { getAction() } doReturn action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,9 +10,11 @@ import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
typealias ScopeReceiver = CoroutineScope.() -> Unit
|
||||||
|
|
||||||
fun View.scopeWhileAttached(
|
fun View.scopeWhileAttached(
|
||||||
context: CoroutineContext,
|
context: CoroutineContext,
|
||||||
exec: CoroutineScope.() -> Unit
|
exec: ScopeReceiver
|
||||||
) {
|
) {
|
||||||
val job = Job(context[Job])
|
val job = Job(context[Job])
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue