mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 11:07:10 +00:00
feat(sample-app): ledger spec parity (#18)
* feat(sample-app): add FocusTracker * feat(sample-app): support description on TextInput * feat(sample-app): support description on AppButton and Segmented * feat(sample-app): stable ids on login screen * feat(sample-app): stable ids on add-account screen * feat(sample-app): stable ids on add-transaction screen * feat(sample-app): stable ids on home and ledger screens * feat(sample-app): hoist error + form state into UiState * feat(sample-app): register auth_status + accounts snapshots * feat(sample-app): register ledger_rows + ledger_balance snapshots * feat(sample-app): register error + focused_input snapshots * refactor(sample-app): scaffold spec.ts extractors + safety * feat(sample-app): spec accounting invariants * feat(sample-app): spec state-machine monotonicity * feat(sample-app): spec liveness properties * feat(sample-app): spec auth + account action generators * feat(sample-app): spec transaction action generators * feat(sample-app): spec weighted workflow * feat(sample-app): publish login form input snapshots * feat(sample-app): publish account + txn input snapshots * feat(sample-app): register form input snapshots * fix(sample-app): sequence login + idempotent text inputs in spec * fix(sample-app): clear UiState on page dispose * fix(sample-app): tighten adversarial login, allow retype on error * test(verifier): update sample-app spec integration for new selectors * feat(sidecar): clear focused field before InputText types * refactor(sample-app): simplify loginHelper to focus-driven sequencing * refactor(sample-app): drop input-value UiState mirrors * refactor(sample-app): drop input-value snapshot registrations * test(verifier): adjust sample-app integration for replace-on-type
This commit is contained in:
13 files changed
+718
-120
No files matched your search
@@ -89,6 +89,31 @@ class StubDriverBackend(private val platform: String) : DriverBackend {
|
||||
return null
|
||||
}
|
||||
|
||||
// Hard cap on KEYCODE_DEL events per clear. Guards against a pathological
|
||||
// hierarchy that reports an enormous text length for the focused field.
|
||||
internal const val MAX_CLEAR_DELETES: Int = 1024
|
||||
|
||||
// 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.
|
||||
private val FOCUSED_NODE = Regex(
|
||||
"<node\\b([^>]*\\bfocused=\"true\"[^>]*)/?>",
|
||||
)
|
||||
private val TEXT_ATTRIBUTE = Regex("\\btext=\"([^\"]*)\"")
|
||||
|
||||
internal fun parseFocusedText(xml: String): String? {
|
||||
val node = FOCUSED_NODE.find(xml) ?: return null
|
||||
val match = TEXT_ATTRIBUTE.find(node.groupValues[1]) ?: return ""
|
||||
return decodeXmlAttribute(match.groupValues[1])
|
||||
}
|
||||
|
||||
private fun decodeXmlAttribute(value: String): String = value
|
||||
.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace(""", "\"")
|
||||
.replace("'", "'")
|
||||
|
||||
internal val KEY_MAP: Map<String, String> = mapOf(
|
||||
"back" to "KEYCODE_BACK",
|
||||
"home" to "KEYCODE_HOME",
|
||||
@@ -158,9 +183,34 @@ class StubDriverBackend(private val platform: String) : DriverBackend {
|
||||
|
||||
override fun inputText(text: String) {
|
||||
lastInputText = text
|
||||
// `adb shell input text` types keystrokes at the caret, so repeated
|
||||
// calls append. Clear the focused field first so the caller sees a
|
||||
// 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")))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
private fun focusedFieldText(): String? {
|
||||
val xml = try {
|
||||
hierarchy()
|
||||
} catch (cause: Exception) {
|
||||
println("inputText: hierarchy dump failed: $cause")
|
||||
return null
|
||||
}
|
||||
if (xml.isBlank() || xml == "<hierarchy/>") return null
|
||||
return parseFocusedText(xml)
|
||||
}
|
||||
|
||||
@Volatile var lastSwipe: SwipeRecord? = null
|
||||
private set
|
||||
@Volatile var lastKey: String? = null
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package dev.uatu.sidecar
|
||||
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNull
|
||||
|
||||
class InputTextClearTest {
|
||||
|
||||
@Test fun parsesTextFromFocusedNode() {
|
||||
val xml = """
|
||||
<hierarchy>
|
||||
<node text="ignored" focused="false"/>
|
||||
<node resource-id="app:id/email" text="old@value" focused="true"/>
|
||||
</hierarchy>
|
||||
""".trimIndent()
|
||||
|
||||
assertEquals("old@value", StubDriverBackend.parseFocusedText(xml))
|
||||
}
|
||||
|
||||
@Test fun returnsEmptyWhenFocusedNodeHasNoTextAttribute() {
|
||||
val xml = """<hierarchy><node focused="true"/></hierarchy>"""
|
||||
|
||||
assertEquals("", StubDriverBackend.parseFocusedText(xml))
|
||||
}
|
||||
|
||||
@Test fun returnsNullWhenNoFocusedNode() {
|
||||
val xml = """<hierarchy><node text="x" focused="false"/></hierarchy>"""
|
||||
|
||||
assertNull(StubDriverBackend.parseFocusedText(xml))
|
||||
}
|
||||
|
||||
@Test fun decodesXmlEntitiesInAttribute() {
|
||||
val xml = """<hierarchy><node text="a&b<c"d" focused="true"/></hierarchy>"""
|
||||
|
||||
assertEquals("a&b<c\"d", StubDriverBackend.parseFocusedText(xml))
|
||||
}
|
||||
|
||||
@Test fun picksFirstFocusedNodeWhenMultiplePresent() {
|
||||
val xml = """
|
||||
<hierarchy>
|
||||
<node text="first" focused="true"/>
|
||||
<node text="second" focused="true"/>
|
||||
</hierarchy>
|
||||
""".trimIndent()
|
||||
|
||||
assertEquals("first", StubDriverBackend.parseFocusedText(xml))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user