fix(cua): distinguish disabled paste commands and verify fixture focus
This commit is contained in:
1 parent
13a41cd8ed
commit
9a562aa6ca
3 files changed
+15
-5
No files matched your search
@@ -164,14 +164,14 @@ final class AXAccess {
|
||||
if try string(node, "AXRole") == "AXMenuItem", try string(node, "AXMenuItemCmdChar")?.lowercased() == "v" {
|
||||
let raw = try attribute(node, "AXMenuItemCmdModifiers")
|
||||
if let raw, CFGetTypeID(raw) == CFNumberGetTypeID(), let number = raw as? NSNumber, number.intValue == 0,
|
||||
try actions(node).contains("AXPress"), try boolean(node, "AXEnabled") != false {
|
||||
try actions(node).contains("AXPress") {
|
||||
guard found == nil else { throw NativeFailure(code: "UNSUPPORTED", message: "The application exposes ambiguous Paste commands") }
|
||||
found = node
|
||||
}
|
||||
}
|
||||
for child in try children(node) { pending.append((child, depth + 1)) }
|
||||
}
|
||||
guard let found else { throw NativeFailure(code: "UNSUPPORTED", message: "No enabled standard Paste command is available") }
|
||||
guard let found else { throw NativeFailure(code: "UNSUPPORTED", message: "No standard Paste command is available") }
|
||||
try beforeDispatch()
|
||||
try perform(found, "AXPress")
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ final class CapabilityPad: NSView {
|
||||
}
|
||||
}
|
||||
|
||||
final class CapabilitiesFixture: NSObject, NSApplicationDelegate, NSTextViewDelegate {
|
||||
final class CapabilitiesFixture: NSObject, NSApplicationDelegate, NSWindowDelegate, NSTextViewDelegate {
|
||||
let statePath: String
|
||||
var window: NSWindow!
|
||||
var text: NSTextView!
|
||||
@@ -44,6 +44,7 @@ final class CapabilitiesFixture: NSObject, NSApplicationDelegate, NSTextViewDele
|
||||
NSApplication.shared.mainMenu = bar
|
||||
window = NSWindow(contentRect: NSRect(x: 140, y: 160, width: 640, height: 500),
|
||||
styleMask: [.titled, .closable], backing: .buffered, defer: false)
|
||||
window.delegate = self
|
||||
window.title = "DSH CUA capability checks"
|
||||
window.isReleasedWhenClosed = false
|
||||
window.animationBehavior = .none
|
||||
@@ -74,6 +75,10 @@ final class CapabilitiesFixture: NSObject, NSApplicationDelegate, NSTextViewDele
|
||||
NSApplication.shared.activate(ignoringOtherApps: true)
|
||||
persist()
|
||||
}
|
||||
func applicationDidBecomeActive(_ notification: Notification) { persist() }
|
||||
func applicationDidResignActive(_ notification: Notification) { persist() }
|
||||
func windowDidBecomeKey(_ notification: Notification) { persist() }
|
||||
func windowDidResignKey(_ notification: Notification) { persist() }
|
||||
func textDidChange(_ notification: Notification) { persist() }
|
||||
func textViewDidChangeSelection(_ notification: Notification) { if pad != nil { persist() } }
|
||||
func persist() {
|
||||
@@ -91,6 +96,8 @@ final class CapabilitiesFixture: NSObject, NSApplicationDelegate, NSTextViewDele
|
||||
let frame = pad.convert(pad.bounds, to: nil)
|
||||
let selection = text.selectedRange()
|
||||
let result: [String: Any] = ["pid": ProcessInfo.processInfo.processIdentifier, "windowId": window.windowNumber,
|
||||
"active": NSApplication.shared.isActive, "keyWindow": window.isKeyWindow,
|
||||
"textFocused": window.firstResponder === text,
|
||||
"text": text.string, "selection": [selection.location, selection.length], "runs": runs, "events": events,
|
||||
"pad": [frame.minX, window.frame.height - frame.maxY, frame.width, frame.height]]
|
||||
try! JSONSerialization.data(withJSONObject: result).write(to: URL(fileURLWithPath: statePath), options: .atomic)
|
||||
|
||||
@@ -78,7 +78,8 @@ def main():
|
||||
# The Host consent barrier is separately exercised by provider-lifecycle.spec.ts.
|
||||
app = call("launchApp", owner)
|
||||
pid = app["pid"]
|
||||
state = wait_for_state(state_file, lambda value: value["pid"] == pid)
|
||||
call("activate", owner)
|
||||
state = wait_for_state(state_file, lambda value: value["pid"] == pid and value["active"] and value["keyWindow"])
|
||||
assert call("findApps", {"query": str(application), "maxAppMatches": 20})[0]["pid"] == pid
|
||||
observed = call("getState", {**owner, "maxElements": 2000, "maxDepth": 25})
|
||||
field = next(item for item in observed["elements"] if item["role"] == "AXTextArea")
|
||||
@@ -120,8 +121,10 @@ def main():
|
||||
pid = json.loads(state_file.read_text())["pid"]
|
||||
if args.artifacts:
|
||||
args.artifacts.mkdir(parents=True, exist_ok=True)
|
||||
if state_file.exists():
|
||||
(args.artifacts / "state-final.json").write_bytes(state_file.read_bytes())
|
||||
(args.artifacts / "transcript.json").write_text(json.dumps(transcript, ensure_ascii=False, indent=2))
|
||||
run_cleanup(*([helper.close] if helper else []), *([lambda: stop_application(pid)] if pid else []))
|
||||
run_cleanup(*([helper.stop] if helper else []), *([lambda: stop_application(pid)] if pid else []))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in new issue
Block a user