test(ioscompanion): cover replace-on-input and TextReplacer capability

This commit is contained in:
pj committed 2026-06-07 12:19:56 +05:30
1 parent 6cc84fcafe
commit 4792626515
2 files changed
+36 -25

No files matched your search

+9 -5
View File
@@ -409,9 +409,9 @@ func TestHealthReportsIOS(t *testing.T) {
} }
} }
// The driver must satisfy DeviceDriver and ForegroundChecker, but must NOT // The driver must satisfy DeviceDriver, ForegroundChecker, and TextReplacer
// satisfy TextReplacer: its InputText appends, so the runner's pre-erase must // (its InputText clears the field then types, so the runner skips its
// stay in place. // blackout-fragile pre-erase), but must NOT satisfy FocusedWindowChecker.
func TestCapabilityAssertions(t *testing.T) { func TestCapabilityAssertions(t *testing.T) {
var instance any = newTestDriver(&fakeCompanion{}) var instance any = newTestDriver(&fakeCompanion{})
if _, ok := instance.(driver.DeviceDriver); !ok { if _, ok := instance.(driver.DeviceDriver); !ok {
@@ -420,8 +420,12 @@ func TestCapabilityAssertions(t *testing.T) {
if _, ok := instance.(driver.ForegroundChecker); !ok { if _, ok := instance.(driver.ForegroundChecker); !ok {
t.Fatal("Driver must implement ForegroundChecker") t.Fatal("Driver must implement ForegroundChecker")
} }
if _, ok := instance.(driver.TextReplacer); ok { replacer, ok := instance.(driver.TextReplacer)
t.Fatal("Driver must NOT implement TextReplacer") if !ok {
t.Fatal("Driver must implement TextReplacer")
}
if !replacer.ReplacesTextOnInput() {
t.Fatal("ReplacesTextOnInput must report true")
} }
if _, ok := instance.(driver.FocusedWindowChecker); ok { if _, ok := instance.(driver.FocusedWindowChecker); ok {
t.Fatal("Driver must NOT implement FocusedWindowChecker") t.Fatal("Driver must NOT implement FocusedWindowChecker")
+27 -20
View File
@@ -262,7 +262,9 @@ func TestInputTextFastPathUsesHardwareKeyboard(t *testing.T) {
if len(fake.hidStreams) != 1 { if len(fake.hidStreams) != 1 {
t.Fatalf("fast path must send one HID stream, got %d", len(fake.hidStreams)) t.Fatalf("fast path must send one HID stream, got %d", len(fake.hidStreams))
} }
want := keyPressEvents([]KeyPress{{Usage: usageA + ('h' - 'a')}, {Usage: usageA + ('i' - 'a')}}) // One stream: clear the field, then type the runes.
want := append(clearFieldEvents(),
keyPressEvents([]KeyPress{{Usage: usageA + ('h' - 'a')}, {Usage: usageA + ('i' - 'a')}})...)
eventsEqual(t, fake.hidStreams[0], want) eventsEqual(t, fake.hidStreams[0], want)
} }
@@ -276,10 +278,12 @@ func TestInputTextPasteLandsImmediately(t *testing.T) {
if fake.pasteboard != "Café" { if fake.pasteboard != "Café" {
t.Fatalf("pasteboard: got %q", fake.pasteboard) t.Fatalf("pasteboard: got %q", fake.pasteboard)
} }
if len(fake.hidStreams) != 1 { // Streams: clear field, then paste chord.
t.Fatalf("expected one paste chord, got %d streams", len(fake.hidStreams)) if len(fake.hidStreams) != 2 {
t.Fatalf("expected clear then paste chord, got %d streams", len(fake.hidStreams))
} }
eventsEqual(t, fake.hidStreams[0], pasteChordEvents()) eventsEqual(t, fake.hidStreams[0], clearFieldEvents())
eventsEqual(t, fake.hidStreams[1], pasteChordEvents())
} }
func TestInputTextPasteDismissesDialogThenLands(t *testing.T) { func TestInputTextPasteDismissesDialogThenLands(t *testing.T) {
@@ -292,14 +296,15 @@ func TestInputTextPasteDismissesDialogThenLands(t *testing.T) {
if err := inputText(context.Background(), fake, "Café ☕ 😀", field); err != nil { if err := inputText(context.Background(), fake, "Café ☕ 😀", field); err != nil {
t.Fatalf("inputText: %v", err) t.Fatalf("inputText: %v", err)
} }
// Streams: paste chord, tap allow, refocus field, paste chord again. // Streams: clear field, paste chord, tap allow, refocus field, paste chord again.
if len(fake.hidStreams) != 4 { if len(fake.hidStreams) != 5 {
t.Fatalf("expected 4 HID streams, got %d", len(fake.hidStreams)) t.Fatalf("expected 5 HID streams, got %d", len(fake.hidStreams))
} }
eventsEqual(t, fake.hidStreams[0], pasteChordEvents()) eventsEqual(t, fake.hidStreams[0], clearFieldEvents())
eventsEqual(t, fake.hidStreams[1], tapEvents(280, 465)) eventsEqual(t, fake.hidStreams[1], pasteChordEvents())
eventsEqual(t, fake.hidStreams[2], tapEvents(195, 222)) eventsEqual(t, fake.hidStreams[2], tapEvents(280, 465))
eventsEqual(t, fake.hidStreams[3], pasteChordEvents()) eventsEqual(t, fake.hidStreams[3], tapEvents(195, 222))
eventsEqual(t, fake.hidStreams[4], pasteChordEvents())
} }
func TestInputTextPasteFailsAfterAllAttempts(t *testing.T) { func TestInputTextPasteFailsAfterAllAttempts(t *testing.T) {
@@ -310,11 +315,12 @@ func TestInputTextPasteFailsAfterAllAttempts(t *testing.T) {
if err == nil { if err == nil {
t.Fatal("expected error after exhausting the verify budget") t.Fatal("expected error after exhausting the verify budget")
} }
// The chord is sent exactly once: with no dialog on screen, re-sending it // Streams: clear field, then exactly one paste chord (no dialog, so it is
// on a slow render would paste the text twice. // not re-sent: re-sending on a slow render would paste twice).
if len(fake.hidStreams) != 1 { if len(fake.hidStreams) != 2 {
t.Fatalf("expected exactly one paste chord, got %d", len(fake.hidStreams)) t.Fatalf("expected clear then one paste chord, got %d streams", len(fake.hidStreams))
} }
eventsEqual(t, fake.hidStreams[0], clearFieldEvents())
wantPolls := int(pasteVerifyTimeout / pastePoll) wantPolls := int(pasteVerifyTimeout / pastePoll)
if fake.sleepCount != wantPolls { if fake.sleepCount != wantPolls {
t.Fatalf("expected %d verify polls, got %d", wantPolls, fake.sleepCount) t.Fatalf("expected %d verify polls, got %d", wantPolls, fake.sleepCount)
@@ -333,9 +339,9 @@ func TestInputTextPasteLandsAfterBridgeBlackout(t *testing.T) {
if err := inputText(context.Background(), fake, "😀", field); err != nil { if err := inputText(context.Background(), fake, "😀", field); err != nil {
t.Fatalf("inputText: %v", err) t.Fatalf("inputText: %v", err)
} }
// One chord, no dialog seen, success once the value appears. // Clear field, one chord, no dialog seen, success once the value appears.
if len(fake.hidStreams) != 1 { if len(fake.hidStreams) != 2 {
t.Fatalf("expected exactly one paste chord, got %d", len(fake.hidStreams)) t.Fatalf("expected clear then one paste chord, got %d streams", len(fake.hidStreams))
} }
} }
@@ -347,8 +353,9 @@ func TestInputTextPasteUnverifiableFieldSingleChord(t *testing.T) {
if err := inputText(context.Background(), fake, "😀", fieldTarget{}); err != nil { if err := inputText(context.Background(), fake, "😀", fieldTarget{}); err != nil {
t.Fatalf("inputText: %v", err) t.Fatalf("inputText: %v", err)
} }
if len(fake.hidStreams) != 1 { // Clear field, then one paste chord.
t.Fatalf("expected exactly one paste chord, got %d", len(fake.hidStreams)) if len(fake.hidStreams) != 2 {
t.Fatalf("expected clear then one paste chord, got %d streams", len(fake.hidStreams))
} }
} }