feat: implement boards list view workflows
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
package space.hackenslacker.kanbn4droid.app
|
||||
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.action.ViewActions.longClick
|
||||
import androidx.test.espresso.action.ViewActions.replaceText
|
||||
import androidx.test.espresso.action.ViewActions.swipeDown
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra
|
||||
import androidx.test.espresso.matcher.RootMatchers.isDialog
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import space.hackenslacker.kanbn4droid.app.auth.ApiKeyStore
|
||||
import space.hackenslacker.kanbn4droid.app.auth.AuthResult
|
||||
import space.hackenslacker.kanbn4droid.app.auth.KanbnApiClient
|
||||
import space.hackenslacker.kanbn4droid.app.auth.SessionStore
|
||||
import space.hackenslacker.kanbn4droid.app.boards.BoardSummary
|
||||
import space.hackenslacker.kanbn4droid.app.boards.BoardTemplate
|
||||
import space.hackenslacker.kanbn4droid.app.boards.BoardsApiResult
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class BoardsFlowTest {
|
||||
@Before
|
||||
fun setUp() {
|
||||
MainActivity.dependencies.clear()
|
||||
Intents.init()
|
||||
MainActivity.dependencies.sessionStoreFactory = { InMemorySessionStore("https://kan.bn/") }
|
||||
MainActivity.dependencies.apiKeyStoreFactory = { InMemoryApiKeyStore("api") }
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
Intents.release()
|
||||
MainActivity.dependencies.clear()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun boardTapNavigatesToDetailPlaceholderWithExtras() {
|
||||
MainActivity.dependencies.apiClientFactory = {
|
||||
FakeBoardsApiClient(
|
||||
boards = mutableListOf(BoardSummary("1", "Alpha")),
|
||||
templates = listOf(BoardTemplate("tpl-1", "Starter")),
|
||||
)
|
||||
}
|
||||
|
||||
ActivityScenario.launch(BoardsActivity::class.java)
|
||||
|
||||
onView(withText("Alpha")).perform(click())
|
||||
|
||||
Intents.intended(hasComponent(BoardDetailPlaceholderActivity::class.java.name))
|
||||
Intents.intended(hasExtra(BoardDetailPlaceholderActivity.EXTRA_BOARD_ID, "1"))
|
||||
Intents.intended(hasExtra(BoardDetailPlaceholderActivity.EXTRA_BOARD_TITLE, "Alpha"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createBoardWithTemplateNavigatesToCreatedBoard() {
|
||||
MainActivity.dependencies.apiClientFactory = {
|
||||
FakeBoardsApiClient(
|
||||
boards = mutableListOf(BoardSummary("1", "Alpha")),
|
||||
templates = listOf(BoardTemplate("tpl-1", "Starter")),
|
||||
)
|
||||
}
|
||||
|
||||
ActivityScenario.launch(BoardsActivity::class.java)
|
||||
|
||||
onView(withId(R.id.createBoardFab)).perform(click())
|
||||
onView(withId(R.id.createBoardNameInput)).perform(replaceText("Roadmap"))
|
||||
onView(withId(R.id.useTemplateChip)).perform(click())
|
||||
onView(withId(android.R.id.button1)).inRoot(isDialog()).perform(click())
|
||||
|
||||
Intents.intended(hasComponent(BoardDetailPlaceholderActivity::class.java.name))
|
||||
Intents.intended(hasExtra(BoardDetailPlaceholderActivity.EXTRA_BOARD_TITLE, "Roadmap"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deleteBoardRequiresSecondConfirmation() {
|
||||
val fake = FakeBoardsApiClient(
|
||||
boards = mutableListOf(BoardSummary("1", "Alpha"), BoardSummary("2", "Beta")),
|
||||
templates = emptyList(),
|
||||
)
|
||||
MainActivity.dependencies.apiClientFactory = { fake }
|
||||
|
||||
ActivityScenario.launch(BoardsActivity::class.java)
|
||||
|
||||
onView(withText("Alpha")).perform(longClick())
|
||||
onView(withText(R.string.delete)).inRoot(isDialog()).perform(click())
|
||||
onView(withText(R.string.im_sure)).inRoot(isDialog()).perform(click())
|
||||
|
||||
onView(withText("Alpha")).check(doesNotExist())
|
||||
onView(withText("Beta")).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun pullToRefreshWorks() {
|
||||
MainActivity.dependencies.apiClientFactory = {
|
||||
FakeBoardsApiClient(
|
||||
boards = mutableListOf(BoardSummary("1", "Alpha")),
|
||||
templates = emptyList(),
|
||||
)
|
||||
}
|
||||
|
||||
ActivityScenario.launch(BoardsActivity::class.java)
|
||||
|
||||
onView(withId(R.id.boardsSwipeRefresh)).perform(swipeDown())
|
||||
onView(withText("Alpha")).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
private class InMemorySessionStore(
|
||||
private var baseUrl: String? = null,
|
||||
) : SessionStore {
|
||||
override fun getBaseUrl(): String? = baseUrl
|
||||
|
||||
override fun saveBaseUrl(url: String) {
|
||||
baseUrl = url
|
||||
}
|
||||
|
||||
override fun clearBaseUrl() {
|
||||
baseUrl = null
|
||||
}
|
||||
}
|
||||
|
||||
private class InMemoryApiKeyStore(
|
||||
private var key: String?,
|
||||
) : ApiKeyStore {
|
||||
override suspend fun saveApiKey(baseUrl: String, apiKey: String): Result<Unit> {
|
||||
key = apiKey
|
||||
return Result.success(Unit)
|
||||
}
|
||||
|
||||
override suspend fun getApiKey(baseUrl: String): Result<String?> = Result.success(key)
|
||||
|
||||
override suspend fun invalidateApiKey(baseUrl: String): Result<Unit> {
|
||||
key = null
|
||||
return Result.success(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
private class FakeBoardsApiClient(
|
||||
private val boards: MutableList<BoardSummary>,
|
||||
private val templates: List<BoardTemplate>,
|
||||
) : KanbnApiClient {
|
||||
override suspend fun healthCheck(baseUrl: String, apiKey: String): AuthResult = AuthResult.Success
|
||||
|
||||
override suspend fun listBoards(baseUrl: String, apiKey: String): BoardsApiResult<List<BoardSummary>> {
|
||||
return BoardsApiResult.Success(boards.toList())
|
||||
}
|
||||
|
||||
override suspend fun listBoardTemplates(
|
||||
baseUrl: String,
|
||||
apiKey: String,
|
||||
): BoardsApiResult<List<BoardTemplate>> {
|
||||
return BoardsApiResult.Success(templates)
|
||||
}
|
||||
|
||||
override suspend fun createBoard(
|
||||
baseUrl: String,
|
||||
apiKey: String,
|
||||
name: String,
|
||||
templateId: String?,
|
||||
): BoardsApiResult<BoardSummary> {
|
||||
val next = BoardSummary((boards.size + 1).toString(), name)
|
||||
boards.add(next)
|
||||
return BoardsApiResult.Success(next)
|
||||
}
|
||||
|
||||
override suspend fun deleteBoard(baseUrl: String, apiKey: String, boardId: String): BoardsApiResult<Unit> {
|
||||
boards.removeAll { it.id == boardId }
|
||||
return BoardsApiResult.Success(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ class LoginFlowTest {
|
||||
|
||||
ActivityScenario.launch(MainActivity::class.java)
|
||||
|
||||
Intents.intended(hasComponent(BoardsPlaceholderActivity::class.java.name))
|
||||
Intents.intended(hasComponent(BoardsActivity::class.java.name))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -91,7 +91,7 @@ class LoginFlowTest {
|
||||
onView(withId(R.id.apiKeyInput)).perform(replaceText("kan_new"), closeSoftKeyboard())
|
||||
onView(withId(R.id.signInButton)).perform(click())
|
||||
|
||||
Intents.intended(hasComponent(BoardsPlaceholderActivity::class.java.name))
|
||||
Intents.intended(hasComponent(BoardsActivity::class.java.name))
|
||||
assertEquals("https://kan.bn/", sessionStore.getBaseUrl())
|
||||
assertEquals("kan_new", keyStore.savedKey)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user