mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-18 00:23:25 -06:00
c8fe8f102f
* feat(macos): add Quick Chat capture context * docs(macos): describe Quick Chat capture context * fix(macos): bound the AX text walk with deadline, cancellation, and per-message timeout; keep distinct repeated text * fix(macos): ancestry-scoped echo suppression and adapter-wide AX timeouts * fix(macos): hard outer timeout abandons a hung AX walk * fix(macos): truly abandon hung AX walks; hide the bar synchronously before area selection * chore(i18n): refresh native locale artifacts for Quick Chat capture strings
106 lines
4.2 KiB
Swift
106 lines
4.2 KiB
Swift
import CoreGraphics
|
|
import Testing
|
|
@testable import OpenClaw
|
|
|
|
@Suite(.serialized)
|
|
@MainActor
|
|
struct QuickChatWindowPickerTests {
|
|
@Test func `candidate filtering excludes own process and nonregular applications`() {
|
|
let inputs = [
|
|
self.input(id: 1, processID: 100, policy: .regular),
|
|
self.input(id: 2, processID: 200, policy: .accessory),
|
|
self.input(id: 3, processID: 300, policy: .regular),
|
|
self.input(id: 4, processID: 400, policy: .regular, isRenderable: false),
|
|
]
|
|
|
|
let candidates = QuickChatWindowPickerLogic.filterCandidates(
|
|
inputs,
|
|
ownProcessID: 100,
|
|
ownBundleIdentifier: "ai.openclaw",
|
|
excludedWindowIDs: [99])
|
|
|
|
#expect(candidates.map(\.windowID) == [3])
|
|
}
|
|
|
|
@Test func `hit test selects first topmost candidate`() {
|
|
let bottom = QuickChatWindowCandidate(
|
|
windowID: 2,
|
|
processID: 2,
|
|
bundleIdentifier: "bottom",
|
|
appName: "Bottom",
|
|
title: "Window",
|
|
bounds: CGRect(x: 0, y: 0, width: 200, height: 200))
|
|
let top = QuickChatWindowCandidate(
|
|
windowID: 1,
|
|
processID: 1,
|
|
bundleIdentifier: "top",
|
|
appName: "Top",
|
|
title: "Window",
|
|
bounds: CGRect(x: 50, y: 50, width: 100, height: 100))
|
|
|
|
#expect(QuickChatWindowPickerLogic.hitTest([top, bottom], at: CGPoint(x: 75, y: 75))?.windowID == 1)
|
|
#expect(QuickChatWindowPickerLogic.hitTest([top, bottom], at: CGPoint(x: 175, y: 175))?.windowID == 2)
|
|
#expect(QuickChatWindowPickerLogic.hitTest([top, bottom], at: CGPoint(x: 250, y: 250)) == nil)
|
|
}
|
|
|
|
@Test func `label combines app and title without empty suffix`() {
|
|
#expect(QuickChatWindowPickerLogic.labelText(appName: "Safari", title: "Docs") == "Safari — Docs")
|
|
#expect(QuickChatWindowPickerLogic.labelText(appName: "Safari", title: " ") == "Safari")
|
|
#expect(QuickChatWindowPickerLogic.labelText(appName: "Safari", title: "Safari") == "Safari")
|
|
}
|
|
|
|
@Test func `area selection normalizes drags from every corner`() throws {
|
|
let expected = CGRect(x: 20, y: 30, width: 80, height: 60)
|
|
let corners = [
|
|
(CGPoint(x: 20, y: 30), CGPoint(x: 100, y: 90)),
|
|
(CGPoint(x: 100, y: 30), CGPoint(x: 20, y: 90)),
|
|
(CGPoint(x: 20, y: 90), CGPoint(x: 100, y: 30)),
|
|
(CGPoint(x: 100, y: 90), CGPoint(x: 20, y: 30)),
|
|
]
|
|
|
|
for (start, end) in corners {
|
|
#expect(try #require(QuickChatAreaPickerLogic.normalizedSelection(from: start, to: end)) == expected)
|
|
}
|
|
}
|
|
|
|
@Test func `area selection rejects either tiny dimension`() {
|
|
#expect(QuickChatAreaPickerLogic.normalizedSelection(
|
|
from: CGPoint(x: 0, y: 0),
|
|
to: CGPoint(x: 7.9, y: 40)) == nil)
|
|
#expect(QuickChatAreaPickerLogic.normalizedSelection(
|
|
from: CGPoint(x: 0, y: 0),
|
|
to: CGPoint(x: 40, y: 7.9)) == nil)
|
|
#expect(QuickChatAreaPickerLogic.normalizedSelection(
|
|
from: CGPoint(x: 0, y: 0),
|
|
to: CGPoint(x: 8, y: 8)) != nil)
|
|
}
|
|
|
|
@Test func `area coordinates convert between appkit and global display spaces`() {
|
|
let screenFrame = CGRect(x: -1440, y: 200, width: 1440, height: 900)
|
|
let displayBounds = CGRect(x: -1440, y: -200, width: 1440, height: 900)
|
|
let appKitSelection = CGRect(x: -1400, y: 900, width: 400, height: 100)
|
|
|
|
#expect(QuickChatAreaPickerLogic.globalDisplayRect(
|
|
appKitRect: appKitSelection,
|
|
screenFrame: screenFrame,
|
|
displayBounds: displayBounds) == CGRect(x: -1400, y: -100, width: 400, height: 100))
|
|
}
|
|
|
|
private func input(
|
|
id: Int,
|
|
processID: Int32,
|
|
policy: QuickChatWindowActivationPolicy,
|
|
isRenderable: Bool = true) -> QuickChatWindowCandidateInput
|
|
{
|
|
QuickChatWindowCandidateInput(
|
|
windowID: id,
|
|
processID: processID,
|
|
bundleIdentifier: "app.\(id)",
|
|
appName: "App \(id)",
|
|
title: "Window \(id)",
|
|
bounds: CGRect(x: 0, y: 0, width: 200, height: 100),
|
|
activationPolicy: policy,
|
|
isRenderable: isRenderable)
|
|
}
|
|
}
|