commit 13e9a31019a8504b5fa96a7d4ee271351f987e86
parent 85ee2a59c012383d3a86f21df033ba12400d5427
Author: triesap <tyson@radroots.org>
Date: Sat, 1 Aug 2026 18:39:03 +0000
test: prove the complete account UI journey
- label account inputs for stable accessible interaction
- add two accounts through the integrated Compose surface
- exercise selection, status, cancellation, and confirmation
- return deterministically to the visible empty state
Diffstat:
2 files changed, 48 insertions(+), 0 deletions(-)
diff --git a/app/desktop/src/main/kotlin/org/radroots/studio/accounts/ui/AccountsScreen.kt b/app/desktop/src/main/kotlin/org/radroots/studio/accounts/ui/AccountsScreen.kt
@@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.selected
import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
@@ -121,6 +122,7 @@ private fun AddAccountForm(
},
modifier = Modifier
.fillMaxWidth()
+ .semantics { contentDescription = "Account display name" }
.testTag("add-display-name")
.padding(8.dp),
decorationBox = { innerTextField ->
@@ -137,6 +139,7 @@ private fun AddAccountForm(
},
modifier = Modifier
.fillMaxWidth()
+ .semantics { contentDescription = "Account server URL" }
.testTag("add-server-url")
.padding(8.dp),
decorationBox = { innerTextField ->
diff --git a/app/desktop/src/test/kotlin/org/radroots/studio/accounts/ui/AccountsScreenTest.kt b/app/desktop/src/test/kotlin/org/radroots/studio/accounts/ui/AccountsScreenTest.kt
@@ -1,5 +1,6 @@
package org.radroots.studio.accounts.ui
+import java.util.ArrayDeque
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsSelected
@@ -257,4 +258,48 @@ class AccountsScreenTest {
onAllNodesWithTag("account-row:${second.id.value}").assertCountEquals(0)
onNodeWithTag("account-row:${first.id.value}").assertIsSelected()
}
+
+ @Test
+ fun integratedAccountJourneyReturnsToTheEmptyState() = runComposeUiTest {
+ val generatedIds = ArrayDeque(listOf(AccountId("account-1"), AccountId("account-2")))
+ val store = AccountsStore(
+ initialState = AccountsState(),
+ reducer = AccountsReducer { generatedIds.removeFirst() },
+ )
+ setContent {
+ AccountsScreen(
+ state = store.state.value,
+ onAction = store::dispatch,
+ )
+ }
+
+ onNodeWithTag("accounts-empty").assertIsDisplayed()
+ addAccount("First Account", "https://first.example.test")
+ addAccount("Second Account", "https://second.example.test")
+
+ onNodeWithTag("account-row:account-1").performClick()
+ onNodeWithTag("account-row:account-1").assertIsSelected()
+ onNodeWithTag("account-login:account-1", useUnmergedTree = true).performClick()
+ onNodeWithTag("account-logout:account-1", useUnmergedTree = true).performClick()
+
+ onNodeWithTag("account-remove:account-2", useUnmergedTree = true).performClick()
+ onNodeWithTag("remove-cancel", useUnmergedTree = true).performClick()
+ onNodeWithTag("account-row:account-2").assertIsDisplayed()
+ onNodeWithTag("account-remove:account-2", useUnmergedTree = true).performClick()
+ onNodeWithTag("remove-confirm", useUnmergedTree = true).performClick()
+
+ onNodeWithTag("account-remove:account-1", useUnmergedTree = true).performClick()
+ onNodeWithTag("remove-confirm", useUnmergedTree = true).performClick()
+ onNodeWithTag("accounts-empty").assertIsDisplayed()
+ onAllNodesWithTag("accounts-list").assertCountEquals(0)
+ }
+
+ private fun androidx.compose.ui.test.ComposeUiTest.addAccount(
+ displayName: String,
+ serverUrl: String,
+ ) {
+ onNodeWithTag("add-display-name").performTextInput(displayName)
+ onNodeWithTag("add-server-url").performTextInput(serverUrl)
+ onNodeWithTag("add-submit").performClick()
+ }
}