fix: finalize board detail action icons and startup guards
This commit is contained in:
@@ -5,6 +5,7 @@ import android.graphics.Color
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
@@ -17,7 +18,6 @@ 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.withContentDescription
|
||||
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isEnabled
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
@@ -38,6 +38,7 @@ import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -48,6 +49,8 @@ import space.hackenslacker.kanbn4droid.app.boarddetail.BoardDetailDataSource
|
||||
import space.hackenslacker.kanbn4droid.app.boarddetail.BoardListDetail
|
||||
import space.hackenslacker.kanbn4droid.app.boarddetail.BoardTagSummary
|
||||
import space.hackenslacker.kanbn4droid.app.boarddetail.CardBatchMutationResult
|
||||
import space.hackenslacker.kanbn4droid.app.auth.ApiKeyStore
|
||||
import space.hackenslacker.kanbn4droid.app.auth.SessionStore
|
||||
import space.hackenslacker.kanbn4droid.app.boards.BoardsApiResult
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@@ -59,6 +62,9 @@ class BoardDetailFlowTest {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MainActivity.dependencies.clear()
|
||||
MainActivity.dependencies.sessionStoreFactory = { InMemorySessionStore("https://kan.bn/") }
|
||||
MainActivity.dependencies.apiKeyStoreFactory = { InMemoryApiKeyStore("api") }
|
||||
originalLocale = Locale.getDefault()
|
||||
Intents.init()
|
||||
defaultDataSource = FakeBoardDetailDataSource(initialDetail = detailOneList())
|
||||
@@ -71,6 +77,7 @@ class BoardDetailFlowTest {
|
||||
Intents.release()
|
||||
BoardDetailActivity.testDataSourceFactory = null
|
||||
BoardDetailActivity.testUiStateObserver = null
|
||||
MainActivity.dependencies.clear()
|
||||
originalLocale?.let { Locale.setDefault(it) }
|
||||
}
|
||||
|
||||
@@ -256,6 +263,59 @@ class BoardDetailFlowTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun selectionActionIconsMatchExpectedResources() {
|
||||
val scenario = launchBoardDetail()
|
||||
|
||||
onView(withText("Card 1")).perform(longClick())
|
||||
|
||||
scenario.onActivity { activity ->
|
||||
val menu = activity.findViewById<com.google.android.material.appbar.MaterialToolbar>(R.id.boardDetailToolbar).menu
|
||||
assertNotNull(menu.findItem(R.id.actionSelectAll)?.icon)
|
||||
assertNotNull(menu.findItem(R.id.actionMoveCards)?.icon)
|
||||
assertNotNull(menu.findItem(R.id.actionDeleteCards)?.icon)
|
||||
|
||||
assertEquals(
|
||||
AppCompatResources.getDrawable(activity, R.drawable.ic_select_all_grid_24)?.constantState,
|
||||
menu.findItem(R.id.actionSelectAll)?.icon?.constantState,
|
||||
)
|
||||
assertEquals(
|
||||
AppCompatResources.getDrawable(activity, R.drawable.ic_move_cards_horizontal_24)?.constantState,
|
||||
menu.findItem(R.id.actionMoveCards)?.icon?.constantState,
|
||||
)
|
||||
assertEquals(
|
||||
AppCompatResources.getDrawable(activity, R.drawable.ic_delete_24)?.constantState,
|
||||
menu.findItem(R.id.actionDeleteCards)?.icon?.constantState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun missingBoardIdShowsBlockingDialogAndFinishes() {
|
||||
val scenario = launchBoardDetail(boardId = null)
|
||||
|
||||
onView(withText(R.string.board_detail_unable_to_open_board)).inRoot(isDialog()).check(matches(isDisplayed()))
|
||||
onView(withText(R.string.ok)).inRoot(isDialog()).perform(click())
|
||||
scenario.onActivity { activity ->
|
||||
assertTrue(activity.isFinishing)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun missingSessionShowsBlockingDialogAndFinishes() {
|
||||
BoardDetailActivity.testDataSourceFactory = null
|
||||
MainActivity.dependencies.sessionStoreFactory = { InMemorySessionStore(null) }
|
||||
MainActivity.dependencies.apiKeyStoreFactory = { InMemoryApiKeyStore(null) }
|
||||
|
||||
val scenario = launchBoardDetail()
|
||||
|
||||
onView(withText(R.string.board_detail_session_expired)).inRoot(isDialog()).check(matches(isDisplayed()))
|
||||
onView(withText(R.string.ok)).inRoot(isDialog()).perform(click())
|
||||
scenario.onActivity { activity ->
|
||||
assertTrue(activity.isFinishing)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moveDialogShowsListSelector() {
|
||||
defaultDataSource.currentDetail = detailTwoLists()
|
||||
@@ -341,6 +401,7 @@ class BoardDetailFlowTest {
|
||||
defaultDataSource.moveCalls == 1 && observedStates.lastOrNull()?.isMutating == false
|
||||
}
|
||||
|
||||
onView(withText("Some cards could not be moved. Please try again.")).check(matches(isDisplayed()))
|
||||
val last = observedStates.last()
|
||||
assertEquals(setOf("card-2"), last.selectedCardIds)
|
||||
}
|
||||
@@ -360,6 +421,7 @@ class BoardDetailFlowTest {
|
||||
defaultDataSource.moveCalls == 1 && observedStates.lastOrNull()?.isMutating == false
|
||||
}
|
||||
|
||||
onView(withText("Move failed")).check(matches(isDisplayed()))
|
||||
val last = observedStates.last()
|
||||
assertEquals(setOf("card-1"), last.selectedCardIds)
|
||||
}
|
||||
@@ -412,6 +474,7 @@ class BoardDetailFlowTest {
|
||||
defaultDataSource.deleteCalls == 1 && observedStates.lastOrNull()?.isMutating == false
|
||||
}
|
||||
|
||||
onView(withText("Some cards could not be deleted. Please try again.")).check(matches(isDisplayed()))
|
||||
val last = observedStates.last()
|
||||
assertEquals(setOf("card-2"), last.selectedCardIds)
|
||||
}
|
||||
@@ -430,6 +493,7 @@ class BoardDetailFlowTest {
|
||||
defaultDataSource.deleteCalls == 1 && observedStates.lastOrNull()?.isMutating == false
|
||||
}
|
||||
|
||||
onView(withText("Delete failed")).check(matches(isDisplayed()))
|
||||
val last = observedStates.last()
|
||||
assertEquals(setOf("card-1"), last.selectedCardIds)
|
||||
}
|
||||
@@ -480,12 +544,15 @@ class BoardDetailFlowTest {
|
||||
Intents.intended(hasExtra(CardDetailPlaceholderActivity.EXTRA_CARD_TITLE, expectedFallback))
|
||||
}
|
||||
|
||||
private fun launchBoardDetail(): ActivityScenario<BoardDetailActivity> {
|
||||
private fun launchBoardDetail(boardId: String? = "board-1"): ActivityScenario<BoardDetailActivity> {
|
||||
val intent = Intent(
|
||||
androidx.test.core.app.ApplicationProvider.getApplicationContext(),
|
||||
BoardDetailActivity::class.java,
|
||||
).putExtra(BoardDetailActivity.EXTRA_BOARD_ID, "board-1")
|
||||
.putExtra(BoardDetailActivity.EXTRA_BOARD_TITLE, "Board")
|
||||
)
|
||||
if (boardId != null) {
|
||||
intent.putExtra(BoardDetailActivity.EXTRA_BOARD_ID, boardId)
|
||||
}
|
||||
intent.putExtra(BoardDetailActivity.EXTRA_BOARD_TITLE, "Board")
|
||||
return ActivityScenario.launch(intent)
|
||||
}
|
||||
|
||||
@@ -611,6 +678,44 @@ class BoardDetailFlowTest {
|
||||
}
|
||||
}
|
||||
|
||||
private class InMemorySessionStore(
|
||||
private var baseUrl: String?,
|
||||
) : SessionStore {
|
||||
override fun getBaseUrl(): String? = baseUrl
|
||||
|
||||
override fun saveBaseUrl(url: String) {
|
||||
baseUrl = url
|
||||
}
|
||||
|
||||
override fun clearBaseUrl() {
|
||||
baseUrl = null
|
||||
}
|
||||
|
||||
override fun getWorkspaceId(): String? = "ws-1"
|
||||
|
||||
override fun saveWorkspaceId(workspaceId: String) {
|
||||
}
|
||||
|
||||
override fun clearWorkspaceId() {
|
||||
}
|
||||
}
|
||||
|
||||
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 companion object {
|
||||
fun detailOneList(): BoardDetail {
|
||||
return BoardDetail(
|
||||
|
||||
Reference in New Issue
Block a user