fix(cua): distinguish disabled paste commands and verify fixture focus

This commit is contained in:
creatixchu committed 2026-09-29 18:16:58 +08:00
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__":