Files
simplex-chat/plans/ui-tests/drafts/back/BackKeyTest.kt
T
Narasimha-sc b953be020a ui tests: portable Waydroid tool, test drafts and review reports
- apps/multiplatform/scripts/waydroid: wd.sh (headless Waydroid for the Android UI tests and manual GUI testing), provision2.sh and watchdog2.sh for a second instance, weston.ini, fetch-images.sh, README; works on Qubes AppVMs and other Linux hosts (apt/dnf install, firewalld, own nft table, weston 10-14 flags);
- uitest-android.sh uses it by default (WD_SH overrides) and takes adb from it, so both use one adb;
- uitest.sh usage header;
- plans/ui-tests: test drafts (including unmerged batch 3), the 2026-10-14 review reports, results after the rebase.
2026-10-15 07:45:16 +00:00

199 lines
8.6 KiB
Kotlin

package chat.simplex.app.ui.back
import chat.simplex.app.ui.harness.ChatScreen
import chat.simplex.app.ui.harness.UiTest
import chat.simplex.app.ui.harness.connect
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.platform.chatController
import chat.simplex.common.platform.chatModel
import chat.simplex.common.ui.Ui
import chat.simplex.common.views.helpers.ModalManager
import chat.simplex.common.views.helpers.SharedContent
import chat.simplex.common.views.onboarding.OnboardingStage
import org.junit.Test
class BackKeyTest : UiTest() {
@Test fun back_chatToChatList() = uiTest {
val alice = app("alice"); val bob = cli("bob")
connect(bob)
alice.chatList.open("bob")
waitFor(Ui.Chat.Bar.part("title"))
escape()
poll(what = "the chat list") { chatModel.chatId.value == null && !exists(Ui.Chat.Bar.part("title")) }
check(!mainActivityFinished()) { "Back from a chat finished MainActivity" }
}
@Test fun back_closesChatSearchBeforeChat() = uiTest {
val alice = app("alice"); val bob = cli("bob")
connect(bob)
bob sends "@alice apple"
bob sends "@alice banana"
val bobId = alice.chatList.chatId("bob")
val chat = alice.chatList.open("bob")
chat.itemId("banana")
chat.openSearch()
typeInto(Ui.Chat.Bar.part("search"), "apple")
poll(what = "search results without `banana`") { chat.itemTexts().let { "apple" in it && "banana" !in it } }
hideKeyboard()
escape()
poll(what = "the search to close and the full chat to return") {
!exists(Ui.Chat.Bar.part("search")) && exists(Ui.Chat.Bar.part("title")) && "banana" in chat.itemTexts()
}
check(chatModel.chatId.value == bobId) { "the first Back left the chat" }
escape()
poll(what = "the chat list") { chatModel.chatId.value == null && !exists(Ui.Chat.Bar.part("title")) }
}
@Test fun back_exitsSelectionMode() = uiTest {
val alice = app("alice"); val bob = cli("bob")
connect(bob)
bob sends "@alice one"
bob sends "@alice two"
val bobId = alice.chatList.chatId("bob")
val chat = alice.chatList.open("bob")
val two = chat.itemId("two")
chat.menu("one", "select")
click(Ui.Chat.Item(two))
poll(what = "two messages selected") { exists(Ui.Chat.Selection.part("title")) && "Selected 2" in textsOf(Ui.Chat.Selection.part("title")) }
escape()
poll(what = "selection mode to end") { !exists(Ui.Chat.Selection.part("title")) && exists(Ui.Chat.Bar.part("title")) }
check(chatModel.chatId.value == bobId) { "Back in selection mode left the chat" }
}
@Test fun back_closesContactAndGroupInfo() = uiTest {
val alice = app("alice"); val bob = cli("bob")
connect(bob)
val bobId = alice.chatList.chatId("bob")
alice.chatList.open("bob").info()
waitFor(Ui.Info.Alias)
escape()
poll(what = "contact info to close to bob's chat") { !ModalManager.end.hasModalsOpen() && !exists(Ui.Info.Alias) && chatModel.chatId.value == bobId }
escape()
poll(what = "the chat list") { chatModel.chatId.value == null && !exists(Ui.Chat.Bar.part("title")) }
alice.newChat().createGroup("team")
val teamId = alice.chatList.chatId("team")
poll(what = "the invite members sheet over team") { chatModel.chatId.value == teamId && ModalManager.end.openModalCount() == 1 }
hideKeyboard()
escape()
poll(what = "the invite members sheet to close") { !ModalManager.end.hasModalsOpen() && exists(Ui.Chat.Bar.part("title")) }
ChatScreen(this).info()
waitFor(Ui.Info.Alias)
escape()
poll(what = "group info to close to team's chat") { !ModalManager.end.hasModalsOpen() && !exists(Ui.Info.Alias) && chatModel.chatId.value == teamId }
}
@Test fun back_hidesKeyboardFirst() = uiTest {
val alice = app("alice"); val bob = cli("bob")
connect(bob)
val bobId = alice.chatList.chatId("bob")
alice.chatList.open("bob")
click(Ui.Chat.Message)
poll(what = "the soft keyboard to show") { keyboardShown() }
escape()
poll(what = "the soft keyboard to hide") { !keyboardShown() }
check(chatModel.chatId.value == bobId && exists(Ui.Chat.Bar.part("title"))) { "the first Back closed the chat" }
}
@Test fun back_clearsChatListSearch() = uiTest {
app("alice")
typeInto(Ui.ChatList.Search, "zz")
hideKeyboard()
escape()
poll(what = "the search text to clear") { editableText(Ui.ChatList.Search) == "" }
check(!mainActivityFinished()) { "Back with search text finished MainActivity" }
backFromRoot()
}
@Test fun back_closesUserPicker() = uiTest {
val alice = app("alice")
alice.userPicker()
waitFor(Ui.UserPicker.Sheet)
escape()
poll(what = "the user picker to hide") { !exists(Ui.UserPicker.Sheet) }
check(chatModel.chatId.value == null && !ModalManager.start.hasModalsOpen() && !mainActivityFinished()) { "Back did not return to the chat list" }
}
@Test fun back_closesNewChatSheet() = uiTest {
val alice = app("alice")
alice.newChat()
waitFor(Ui.NewChat.CreateLink)
escape()
poll(what = "the new chat sheet to close") { !chatModel.newChatSheetVisible.value && !ModalManager.start.hasModalsOpen() && !exists(Ui.NewChat.CreateLink) }
check(!mainActivityFinished()) { "Back on the new chat sheet finished MainActivity" }
}
@Test fun back_closesModalsOneByOne() = uiTest {
val alice = app("alice")
alice.appearance()
poll(what = "Appearance over Your settings") { ModalManager.start.openModalCount() == 2 && exists(Ui.Appearance.ColorMode) }
escape()
poll(what = "Your settings again") { ModalManager.start.openModalCount() == 1 && exists(Ui.Settings.Appearance) && !exists(Ui.Appearance.ColorMode) }
escape()
poll(what = "the chat list with nothing open") { !ModalManager.start.hasModalsOpen() && !exists(Ui.Settings.Appearance) && !exists(Ui.UserPicker.Sheet) }
check(!mainActivityFinished()) { "Back on Your settings finished MainActivity" }
backFromRoot()
}
@Test fun back_onChatListRootFinishesActivity() = uiTest {
val alice = app("alice"); val bob = cli("bob")
connect(bob)
alice.chatList.row("bob")
val ctrl = chatController.getChatCtrl()
backFromRoot()
relaunch {
alice.chatList.row("bob")
check(chatModel.chatRunning.value == true && chatController.getChatCtrl() == ctrl) { "the core was restarted" }
}
}
@Test fun back_duringForward_returnsToSourceChat() = uiTest {
val alice = app("alice"); val bob = cli("bob")
connect(bob)
bob sends "@alice hello"
val bobId = alice.chatList.chatId("bob")
alice.chatList.open("bob").menu("hello", "forward")
poll(what = "the forward list") { chatModel.sharedContent.value is SharedContent.Forward && !exists(Ui.Chat.Bar.part("title")) }
escape()
poll(what = "bob's chat again") { chatModel.sharedContent.value == null && chatModel.chatId.value == bobId && exists(Ui.Chat.Bar.part("title")) }
check(!mainActivityFinished()) { "Back during a forward finished MainActivity" }
}
@Test fun back_onboardingProfile_returnsToWelcome() = uiTest {
app(null)
click(Ui.Onboarding.GetStarted)
waitFor(Ui.Onboarding.ProfileName)
hideKeyboard()
escape()
poll(what = "the welcome screen") {
appPrefs.onboardingStage.get() == OnboardingStage.Step1_SimpleXInfo && exists(Ui.Onboarding.GetStarted) && !exists(Ui.Onboarding.ProfileName)
}
check(chatModel.currentUser.value == null && chatModel.users.isEmpty()) { "a profile was created" }
}
@Test fun back_onboardingYourNetwork_finishesActivity() = uiTest {
app(null)
click(Ui.Onboarding.GetStarted)
typeInto(Ui.Onboarding.ProfileName, "alice")
hideKeyboard()
poll(what = "Create profile to be enabled") { isEnabled(Ui.Onboarding.CreateProfile) }
click(Ui.Onboarding.CreateProfile)
poll(timeoutMs = 30_000, what = "the Your network step") { appPrefs.onboardingStage.get() == OnboardingStage.Step3_ChooseServerOperators && seesText("Your network") }
backFromRoot()
check(appPrefs.onboardingStage.get() == OnboardingStage.Step3_ChooseServerOperators) { "Back changed the onboarding stage" }
relaunch {
poll(what = "Your network again") { seesText("Your network") }
check(appPrefs.onboardingStage.get() == OnboardingStage.Step3_ChooseServerOperators) { "relaunch changed the onboarding stage" }
}
}
@Test fun back_closesWhatsNew() = uiTest {
val alice = app("alice")
alice.settings()
click(Ui.Settings.Help)
click(Ui.Help.WhatsNew)
poll(what = "What's new over Help & support") { ModalManager.start.openModalCount() == 3 && !exists(Ui.Help.WhatsNew) }
escape()
poll(what = "Help & support again") { ModalManager.start.openModalCount() == 2 && exists(Ui.Help.WhatsNew) }
}
}