Files
openclaw/apps/macos/Tests/OpenClawIPCTests/QuickChatWindowPickerTests.swift
Peter Steinberger c8fe8f102f feat(macos): Quick Chat area capture and focused-app text context (#110635)
* 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
2026-07-18 17:45:52 +01:00

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)
}
}