Implement Android Home activity and HTTPS synchronization (#97)
This commit is contained in:
1 parent
8c9341f214
commit
5c933a99a3
29 files changed
+2008
-110
No files matched your search
@@ -0,0 +1,188 @@
|
||||
package de.rfc1437.ironstorage
|
||||
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.assertIsNotEnabled
|
||||
import androidx.compose.ui.test.hasScrollAction
|
||||
import androidx.compose.ui.test.hasText
|
||||
import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.onRoot
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performScrollTo
|
||||
import androidx.compose.ui.test.performScrollToNode
|
||||
import androidx.compose.ui.test.performTextInput
|
||||
import androidx.compose.ui.test.performTouchInput
|
||||
import androidx.compose.ui.test.swipeDown
|
||||
import de.rfc1437.ironstorage.core.HomeAction
|
||||
import de.rfc1437.ironstorage.core.HomeActionKind
|
||||
import de.rfc1437.ironstorage.core.HomeChange
|
||||
import de.rfc1437.ironstorage.core.HomeCommit
|
||||
import de.rfc1437.ironstorage.core.HomeFailure
|
||||
import de.rfc1437.ironstorage.core.HomeFailureKind
|
||||
import de.rfc1437.ironstorage.core.HomeFreshness
|
||||
import de.rfc1437.ironstorage.core.HomeNotice
|
||||
import de.rfc1437.ironstorage.core.HomePage
|
||||
import de.rfc1437.ironstorage.core.HomeRepository
|
||||
import de.rfc1437.ironstorage.core.HomeRow
|
||||
import de.rfc1437.ironstorage.core.HomeState
|
||||
import de.rfc1437.ironstorage.core.ShellIcon
|
||||
import de.rfc1437.ironstorage.features.HomeScreen
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class HomeScreenTest {
|
||||
@get:Rule val compose = createAndroidComposeRule<MainActivity>()
|
||||
private fun show(repository: Fixture) {
|
||||
compose.activity.runOnUiThread {
|
||||
compose.activity.setContent { MaterialTheme { HomeScreen(repository) } }
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun typedActionsUseTheConfiguredIdentityAndNeverReplayAfterReload() {
|
||||
val repository = Fixture()
|
||||
show(repository)
|
||||
compose.onNodeWithText("Cached activity").assertIsDisplayed()
|
||||
compose.onAllNodesWithText("Commit")[0].performScrollTo().performClick()
|
||||
compose.onNodeWithText("Android fixture").assertIsDisplayed()
|
||||
compose.onNodeWithText("fixture@example.test").assertIsDisplayed()
|
||||
compose.onNodeWithText("Commit message").performTextInput("Commit encrypted objects")
|
||||
compose.onNodeWithText("Create commit").performClick()
|
||||
assertEquals(
|
||||
HomeActionKind.Commit to "Commit encrypted objects",
|
||||
repository.requests.single()
|
||||
)
|
||||
compose.onNodeWithText("Cancel").performClick()
|
||||
for (action in listOf(HomeActionKind.Fetch, HomeActionKind.Pull, HomeActionKind.Push)) {
|
||||
compose.onNodeWithText(action.name).performScrollTo().performClick()
|
||||
assertEquals(action, repository.requests.last().first)
|
||||
compose.onNodeWithText("Cancel").performClick()
|
||||
compose.onNodeWithText("Reload status").performClick()
|
||||
}
|
||||
val before = repository.requests.size
|
||||
show(repository)
|
||||
compose.waitForIdle()
|
||||
assertEquals(before, repository.requests.size)
|
||||
assertTrue(repository.entries > 0)
|
||||
}
|
||||
|
||||
@Test fun slowOperationsLeaveScrollingAndCancellationResponsiveWithLargeHistory() {
|
||||
val repository = Fixture(1000)
|
||||
show(repository)
|
||||
compose.onNodeWithText("Fetch").performScrollTo().performClick()
|
||||
compose.onNodeWithText("Receiving remote objects").assertIsDisplayed()
|
||||
compose.onNodeWithText("Fetch").performScrollTo().assertIsNotEnabled()
|
||||
compose.onNode(hasScrollAction()).performScrollToNode(hasText("object-999"))
|
||||
compose.onNodeWithText("object-999").assertIsDisplayed()
|
||||
compose.onNodeWithText("Cancel").performClick()
|
||||
compose.onNode(hasScrollAction()).performScrollToNode(hasText("Refresh Interrupted"))
|
||||
compose.onNodeWithText("Refresh Interrupted").assertIsDisplayed()
|
||||
assertEquals(1, repository.cancellations)
|
||||
}
|
||||
|
||||
@Test fun swipeRefreshRequestsTheRustRefreshWorkflow() {
|
||||
val repository = Fixture()
|
||||
show(repository)
|
||||
compose.onRoot().performTouchInput { swipeDown() }
|
||||
compose.waitUntil(5000) { repository.refreshes == 1 }
|
||||
assertTrue(repository.requests.isEmpty())
|
||||
compose.onNodeWithText("Cancel").performClick()
|
||||
}
|
||||
private class Fixture(changes: Int = 1) : HomeRepository {
|
||||
private val actions = HomeActionKind.entries.map {
|
||||
HomeAction(
|
||||
it,
|
||||
it.name,
|
||||
when (it) {
|
||||
HomeActionKind.Commit -> ShellIcon.Success
|
||||
HomeActionKind.Fetch -> ShellIcon.Refresh
|
||||
HomeActionKind.Pull -> ShellIcon.Incoming
|
||||
HomeActionKind.Push -> ShellIcon.Outgoing
|
||||
}
|
||||
)
|
||||
}
|
||||
override val state = MutableStateFlow(
|
||||
HomeState(
|
||||
page = HomePage(
|
||||
HomeFreshness.Cached,
|
||||
1234567890,
|
||||
listOf(
|
||||
HomeRow(
|
||||
"tracked",
|
||||
"origin/main",
|
||||
"Tracked HTTPS branch",
|
||||
ShellIcon.Branch,
|
||||
actions
|
||||
)
|
||||
),
|
||||
listOf(
|
||||
HomeCommit(
|
||||
"incoming-id",
|
||||
"Incoming encrypted objects",
|
||||
"Fixture author",
|
||||
1234567890,
|
||||
(0 until changes).map {
|
||||
HomeChange(
|
||||
"change-$it",
|
||||
"object-$it",
|
||||
"Added password entry",
|
||||
ShellIcon.Key
|
||||
)
|
||||
},
|
||||
emptyList()
|
||||
)
|
||||
),
|
||||
emptyList(),
|
||||
1u,
|
||||
0u,
|
||||
null
|
||||
),
|
||||
name = "Android fixture",
|
||||
email = "fixture@example.test"
|
||||
)
|
||||
)
|
||||
val requests = mutableListOf<Pair<HomeActionKind, String>>()
|
||||
var cancellations = 0
|
||||
var entries = 0
|
||||
var refreshes = 0
|
||||
override fun enter() {
|
||||
entries++
|
||||
}
|
||||
override fun refresh() {
|
||||
refreshes++
|
||||
busy()
|
||||
}
|
||||
override fun perform(action: HomeActionKind, message: String) {
|
||||
requests += action to message
|
||||
busy()
|
||||
}
|
||||
private fun busy() {
|
||||
state.value = state.value.copy(
|
||||
busy = true,
|
||||
failure = null,
|
||||
progress = HomeNotice("Receiving remote objects", "Slow fixture network")
|
||||
)
|
||||
}
|
||||
override fun reload() {
|
||||
state.value = state.value.copy(failure = null)
|
||||
}
|
||||
override fun cancel() {
|
||||
cancellations++
|
||||
state.value = state.value.copy(
|
||||
busy = false,
|
||||
progress = null,
|
||||
failure = HomeFailure(
|
||||
HomeFailureKind.Interrupted,
|
||||
"Refresh Interrupted",
|
||||
"Reload cached activity before retrying."
|
||||
)
|
||||
)
|
||||
}
|
||||
override fun leave() = Unit
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,9 @@ import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import de.rfc1437.ironstorage.core.HomeRepository
|
||||
import de.rfc1437.ironstorage.core.NativeFixtureShellRepository
|
||||
import de.rfc1437.ironstorage.core.NativeHomeRepository
|
||||
import de.rfc1437.ironstorage.core.NativeOnboardingRepository
|
||||
import de.rfc1437.ironstorage.core.NativePasswordRepository
|
||||
import de.rfc1437.ironstorage.core.NativePrivateSession
|
||||
@@ -23,6 +25,18 @@ import kotlinx.coroutines.SupervisorJob
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object ShellModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun homeRepository(
|
||||
session: NativePrivateSession,
|
||||
passwords: PasswordRepository
|
||||
): HomeRepository = NativeHomeRepository(
|
||||
session,
|
||||
passwords,
|
||||
CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate),
|
||||
Dispatchers.IO.limitedParallelism(2)
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun passwordRepository(session: NativePrivateSession): PasswordRepository =
|
||||
|
||||
Reference in new issue
Block a user