mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 11:07:10 +00:00
- WebLedgerStore.accountExistsByName case-insensitive (matches SQL COLLATE NOCASE) - drop SampleApplication.maybeInjectDebugError; sample spec now passes clean - drop noLogcatErrors from sample spec (default matches system-wide E logs) - openRandomAccount picks uniformly from findAll instead of first match - clear txnError on any add-transaction interaction, not only valid amount input - escapeForAdbInputText quotes shell metacharacters (quotes, backslash, etc.) - extract buildClearKeyevents helper with empty/normal/cap tests - broaden spec_integration_test.go to cover home, add-account, ledger, add-transaction action generators - document FocusTracker single-focus invariant
This commit is contained in:
8 files changed
+284
-26
No files matched your search
@@ -93,6 +93,32 @@ class StubDriverBackend(private val platform: String) : DriverBackend {
|
||||
// hierarchy that reports an enormous text length for the focused field.
|
||||
internal const val MAX_CLEAR_DELETES: Int = 1024
|
||||
|
||||
internal fun buildClearKeyevents(textLength: Int): List<String> {
|
||||
if (textLength <= 0) return emptyList()
|
||||
val deletes = minOf(textLength, MAX_CLEAR_DELETES)
|
||||
val args = mutableListOf("shell", "input", "keyevent", "KEYCODE_MOVE_END")
|
||||
repeat(deletes) { args.add("KEYCODE_DEL") }
|
||||
return args
|
||||
}
|
||||
|
||||
// `adb shell input text` runs through a remote sh, so shell metacharacters
|
||||
// in the payload would be interpreted by the device shell. Substitute
|
||||
// spaces with %s (input's escape) and backslash-escape characters sh
|
||||
// would otherwise expand. Keep this list conservative; anything not
|
||||
// listed passes through literally.
|
||||
internal fun escapeForAdbInputText(text: String): String {
|
||||
val sb = StringBuilder(text.length)
|
||||
for (ch in text) {
|
||||
when (ch) {
|
||||
' ' -> sb.append("%s")
|
||||
'\\', '"', '\'', '&', '|', ';', '<', '>', '(', ')', '*', '?',
|
||||
'$', '`', '[', ']', '{', '}', '~', '#', -> sb.append('\\').append(ch)
|
||||
else -> sb.append(ch)
|
||||
}
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
// Matches a uiautomator-dump <node ...> tag where `focused="true"` is
|
||||
// present. Captures only the tag's attribute string so we can pull
|
||||
// `text="..."` out of it without building a full XML tree.
|
||||
@@ -188,16 +214,13 @@ class StubDriverBackend(private val platform: String) : DriverBackend {
|
||||
// pure replace: read the current value's length from the hierarchy,
|
||||
// then move-end + N backspaces before typing.
|
||||
clearFocusedField()
|
||||
runAdb(listOf("shell", "input", "text", text.replace(" ", "%s")))
|
||||
runAdb(listOf("shell", "input", "text", escapeForAdbInputText(text)))
|
||||
}
|
||||
|
||||
private fun clearFocusedField() {
|
||||
val current = focusedFieldText() ?: return
|
||||
if (current.isEmpty()) return
|
||||
val deletes = minOf(current.length, MAX_CLEAR_DELETES)
|
||||
val keyevents = mutableListOf("shell", "input", "keyevent", "KEYCODE_MOVE_END")
|
||||
repeat(deletes) { keyevents.add("KEYCODE_DEL") }
|
||||
runAdb(keyevents)
|
||||
runAdb(buildClearKeyevents(current.length))
|
||||
}
|
||||
|
||||
private fun focusedFieldText(): String? {
|
||||
|
||||
@@ -3,9 +3,51 @@ package dev.uatu.sidecar
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class InputTextClearTest {
|
||||
|
||||
@Test fun buildClearKeyeventsEmptyWhenNothingToDelete() {
|
||||
assertEquals(emptyList(), StubDriverBackend.buildClearKeyevents(0))
|
||||
assertEquals(emptyList(), StubDriverBackend.buildClearKeyevents(-3))
|
||||
}
|
||||
|
||||
@Test fun buildClearKeyeventsPrefixesMoveEndThenOneDelPerChar() {
|
||||
val args = StubDriverBackend.buildClearKeyevents(3)
|
||||
assertEquals(listOf("shell", "input", "keyevent", "KEYCODE_MOVE_END",
|
||||
"KEYCODE_DEL", "KEYCODE_DEL", "KEYCODE_DEL"), args)
|
||||
}
|
||||
|
||||
@Test fun buildClearKeyeventsCapsAtMaxClearDeletes() {
|
||||
val huge = StubDriverBackend.MAX_CLEAR_DELETES * 10
|
||||
val args = StubDriverBackend.buildClearKeyevents(huge)
|
||||
val deletes = args.count { it == "KEYCODE_DEL" }
|
||||
assertEquals(StubDriverBackend.MAX_CLEAR_DELETES, deletes)
|
||||
assertEquals("KEYCODE_MOVE_END", args[3])
|
||||
}
|
||||
|
||||
@Test fun escapeForAdbInputTextSubstitutesSpaces() {
|
||||
assertEquals("hello%sworld", StubDriverBackend.escapeForAdbInputText("hello world"))
|
||||
}
|
||||
|
||||
@Test fun escapeForAdbInputTextEscapesShellMetacharacters() {
|
||||
val escaped = StubDriverBackend.escapeForAdbInputText("a&b|c;d\$e`f")
|
||||
assertEquals("a\\&b\\|c\\;d\\\$e\\`f", escaped)
|
||||
}
|
||||
|
||||
@Test fun escapeForAdbInputTextEscapesQuotesAndBackslash() {
|
||||
assertEquals("\\'", StubDriverBackend.escapeForAdbInputText("'"))
|
||||
assertEquals("\\\"", StubDriverBackend.escapeForAdbInputText("\""))
|
||||
assertEquals("\\\\", StubDriverBackend.escapeForAdbInputText("\\"))
|
||||
}
|
||||
|
||||
@Test fun escapeForAdbInputTextLeavesSimpleTextAlone() {
|
||||
assertEquals("12.34", StubDriverBackend.escapeForAdbInputText("12.34"))
|
||||
assertEquals("Coffee", StubDriverBackend.escapeForAdbInputText("Coffee"))
|
||||
assertTrue("-5" == StubDriverBackend.escapeForAdbInputText("-5"))
|
||||
}
|
||||
|
||||
|
||||
@Test fun parsesTextFromFocusedNode() {
|
||||
val xml = """
|
||||
<hierarchy>
|
||||
|
||||
Reference in new issue
Block a user