Skip to content

Commit aca69b8

Browse files
committed
test(computer-use): cover the caret_unconfirmed refusal with an editor that ignores caret moves
1 parent e5d829d commit aca69b8

2 files changed

Lines changed: 18 additions & 4 deletions

File tree

‎apps/desktop/native/computer-use/tests/Fixture.swift‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@ final class MarkerView: NSView {
99
}
1010

1111
/// A field that ignores Accessibility focus requests but takes focus from a click, as SwiftUI sheets do.
12-
final class ClickFocusField: NSTextField {
12+
class ClickFocusField: NSTextField {
1313
override func setAccessibilityFocused(_ accessibilityFocused: Bool) {}
1414
}
15+
/// A click-only editor that also ignores Accessibility caret moves, so its caret cannot be confirmed.
16+
final class FixedCaretField: ClickFocusField {
17+
override func setAccessibilitySelectedTextRange(_ accessibilitySelectedTextRange: NSRange) {}
18+
}
1519
final class FixtureDelegate: NSObject, NSApplicationDelegate {
1620
var window: NSWindow!
1721
var count = 0
@@ -36,6 +40,7 @@ final class FixtureDelegate: NSObject, NSApplicationDelegate {
3640
let secure = NSSecureTextField(string: "fixture-only-secret"); secure.setAccessibilityIdentifier("fixture-password"); secure.setAccessibilityLabel("Fixture password")
3741
let button = NSButton(title: "Increment", target: self, action: #selector(increment)); button.setAccessibilityIdentifier("fixture-increment")
3842
let clickField = ClickFocusField(string: ""); clickField.setAccessibilityLabel("Fixture click-focus field")
43+
let caretField = FixedCaretField(string: String(repeating: "keep ", count: 40)); caretField.setAccessibilityLabel("Fixture fixed-caret field")
3944
let sheetButton = NSButton(title: "Show sheet", target: self, action: #selector(showSheet)); sheetButton.setAccessibilityIdentifier("fixture-show-sheet")
4045
let slider = NSSlider(value: 25, minValue: 0, maxValue: 100, target: nil, action: nil); slider.setAccessibilityLabel("Fixture slider")
4146
let time = NSDatePicker(); time.datePickerStyle = .textField; time.datePickerElements = .hourMinute; time.setAccessibilityLabel("Fixture time")
@@ -49,9 +54,9 @@ final class FixtureDelegate: NSObject, NSApplicationDelegate {
4954
scroll.documentView = document
5055
scroll.contentView.postsBoundsChangedNotifications = true
5156
NotificationCenter.default.addObserver(self, selector: #selector(scrolled(_:)), name: NSView.boundsDidChangeNotification, object: scroll.contentView)
52-
for view in [field, secure, button, counter, clickField, sheetButton, slider, time, timeLabel, marker, scrollCounter, scroll] { stack.addArrangedSubview(view) }
57+
for view in [field, secure, button, counter, clickField, caretField, sheetButton, slider, time, timeLabel, marker, scrollCounter, scroll] { stack.addArrangedSubview(view) }
5358
window.contentView!.addSubview(stack)
54-
NSLayoutConstraint.activate([stack.leadingAnchor.constraint(equalTo: window.contentView!.leadingAnchor, constant: 30), stack.trailingAnchor.constraint(equalTo: window.contentView!.trailingAnchor, constant: -30), stack.topAnchor.constraint(equalTo: window.contentView!.topAnchor, constant: 30), field.widthAnchor.constraint(equalTo: stack.widthAnchor), secure.widthAnchor.constraint(equalTo: stack.widthAnchor), clickField.widthAnchor.constraint(equalTo: stack.widthAnchor), slider.widthAnchor.constraint(equalTo: stack.widthAnchor), scroll.widthAnchor.constraint(equalTo: stack.widthAnchor), scroll.heightAnchor.constraint(equalToConstant: 230), marker.widthAnchor.constraint(equalToConstant: 100), marker.heightAnchor.constraint(equalToConstant: 70)])
59+
NSLayoutConstraint.activate([stack.leadingAnchor.constraint(equalTo: window.contentView!.leadingAnchor, constant: 30), stack.trailingAnchor.constraint(equalTo: window.contentView!.trailingAnchor, constant: -30), stack.topAnchor.constraint(equalTo: window.contentView!.topAnchor, constant: 30), field.widthAnchor.constraint(equalTo: stack.widthAnchor), secure.widthAnchor.constraint(equalTo: stack.widthAnchor), clickField.widthAnchor.constraint(equalTo: stack.widthAnchor), caretField.widthAnchor.constraint(equalTo: stack.widthAnchor), slider.widthAnchor.constraint(equalTo: stack.widthAnchor), scroll.widthAnchor.constraint(equalTo: stack.widthAnchor), scroll.heightAnchor.constraint(equalToConstant: 230), marker.widthAnchor.constraint(equalToConstant: 100), marker.heightAnchor.constraint(equalToConstant: 70)])
5560
window.makeKeyAndOrderFront(nil)
5661
window.makeFirstResponder(field)
5762
NSApplication.shared.activate()

‎apps/desktop/native/computer-use/tests/live.py‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,15 @@ def batch(snapshot, steps):
183183
snapshot = state()
184184
call('type_text', bundleId=bundle, snapshotId=snapshot['snapshotId'], elementId=find(snapshot, role='AXTextField', label='Fixture click-focus field')['elementId'], text=' twice')
185185
assert find(state(), role='AXTextField', label='Fixture click-focus field').get('value') == 'clicked twice', 'Click-to-focus typed at the click point'
186+
# An editor whose caret cannot be confirmed at the end gets no keystrokes after its focus click.
187+
snapshot = state()
188+
mutate('click', snapshot, **coordinate(snapshot, find(snapshot, role='AXTextField', label='Fixture editable text')))
189+
snapshot = state()
190+
caret_field = find(snapshot, role='AXTextField', label='Fixture fixed-caret field')
191+
before_text = caret_field.get('value')
192+
error = refused('type_text', snapshot, elementId=caret_field['elementId'], text='X')
193+
assert error['code'] == 'caret_unconfirmed', error
194+
assert find(state(), role='AXTextField', label='Fixture fixed-caret field').get('value') == before_text, 'Text reached an editor whose caret was not confirmed'
186195
# Select-all first makes the caret position moot: the click-focused editor's whole value is replaced.
187196
snapshot = state()
188197
mutate('click', snapshot, **coordinate(snapshot, find(snapshot, role='AXTextField', label='Fixture editable text')))
@@ -200,7 +209,7 @@ def batch(snapshot, steps):
200209
assert error['dispatchState'] == 'not_started', error
201210
assert find(state(), role='AXTextField', label='Fixture click-focus field').get('value') == 'replaced', 'A background app was clicked or typed into'
202211
call('activate_app', bundleId=bundle)
203-
print('PASS: real fixture state, secure suppression, AX click/set/action, Unicode typing, keyboard chord, coordinate drag and scrolling, text refused by a slider, typing into a date picker, a window target typing into its focused editor, a title-bar click, a batch that applies, stops at a failing step, refuses before dispatch and stops when a sheet opens, click-to-focus for an editor that ignores Accessibility focus (and none in the background); every mutation verified in fresh state; screenshot=' + ('verified' if permissions['screenRecording'] else 'BLOCKED'))
212+
print('PASS: real fixture state, secure suppression, AX click/set/action, Unicode typing, keyboard chord, coordinate drag and scrolling, text refused by a slider, typing into a date picker, a window target typing into its focused editor, a title-bar click, a batch that applies, stops at a failing step, refuses before dispatch and stops when a sheet opens, click-to-focus for an editor that ignores Accessibility focus (inserting at the end, refusing an unconfirmed caret, and none in the background); every mutation verified in fresh state; screenshot=' + ('verified' if permissions['screenRecording'] else 'BLOCKED'))
204213
finally:
205214
if fixture:
206215
fixture.terminate()

0 commit comments

Comments
 (0)