mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
feat(companion): synthesize timestamped touch gestures
This commit is contained in:
1 parent
cd2865bbd5
commit
f45d25d1bb
1 file changed
+89
@@ -0,0 +1,89 @@
|
||||
import XCTest
|
||||
import CoreGraphics
|
||||
|
||||
// Synthesizes a single timestamped touch event from a list of primitive events.
|
||||
// A running offset (seconds) is advanced as events are consumed so sequential
|
||||
// taps collapse into one pointer path with press/lift/press/lift at exact
|
||||
// offsets, honoring the inter-tap gap to the millisecond.
|
||||
enum Gesture {
|
||||
enum GestureError: Error {
|
||||
case missingField(String)
|
||||
case unknownKind(String)
|
||||
case synthesisTimeout
|
||||
case synthesisFailed(String)
|
||||
}
|
||||
|
||||
static func perform(events: [[String: Any]]) throws {
|
||||
var offset = 0.0
|
||||
var path: XCPointerEventPath?
|
||||
|
||||
func point(_ event: [String: Any], _ xKey: String, _ yKey: String) throws -> CGPoint {
|
||||
guard let x = event[xKey] as? Double, let y = event[yKey] as? Double else {
|
||||
throw GestureError.missingField("\(xKey)/\(yKey)")
|
||||
}
|
||||
return CGPoint(x: x, y: y)
|
||||
}
|
||||
|
||||
func ensurePath(at location: CGPoint) -> XCPointerEventPath {
|
||||
if let existing = path {
|
||||
existing.move(to: location, atOffset: offset)
|
||||
return existing
|
||||
}
|
||||
let created = XCPointerEventPath(forTouchAt: location, offset: offset)
|
||||
path = created
|
||||
return created
|
||||
}
|
||||
|
||||
for event in events {
|
||||
guard let kind = event["kind"] as? String else {
|
||||
throw GestureError.missingField("kind")
|
||||
}
|
||||
switch kind {
|
||||
case "touchDown":
|
||||
let location = try point(event, "x", "y")
|
||||
ensurePath(at: location).pressDown(atOffset: offset)
|
||||
case "touchUp":
|
||||
let location = try point(event, "x", "y")
|
||||
ensurePath(at: location).liftUp(atOffset: offset)
|
||||
case "delay":
|
||||
guard let milliseconds = event["milliseconds"] as? Double else {
|
||||
throw GestureError.missingField("milliseconds")
|
||||
}
|
||||
offset += milliseconds / 1000.0
|
||||
case "swipe":
|
||||
let from = try point(event, "fromX", "fromY")
|
||||
let to = try point(event, "toX", "toY")
|
||||
guard let seconds = event["seconds"] as? Double else {
|
||||
throw GestureError.missingField("seconds")
|
||||
}
|
||||
let activePath = ensurePath(at: from)
|
||||
activePath.pressDown(atOffset: offset)
|
||||
activePath.move(to: to, atOffset: offset + seconds)
|
||||
activePath.liftUp(atOffset: offset + seconds)
|
||||
offset += seconds
|
||||
default:
|
||||
throw GestureError.unknownKind(kind)
|
||||
}
|
||||
}
|
||||
|
||||
guard let path = path else {
|
||||
throw GestureError.missingField("events")
|
||||
}
|
||||
|
||||
let record = XCSynthesizedEventRecord(name: "companion", interfaceOrientation: 0)
|
||||
record.add(path)
|
||||
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
var synthesisError: Error?
|
||||
XCTRunnerDaemonSession.shared().synthesize(event: record) { error in
|
||||
synthesisError = error
|
||||
semaphore.signal()
|
||||
}
|
||||
if semaphore.wait(timeout: .now() + 30) == .timedOut {
|
||||
throw GestureError.synthesisTimeout
|
||||
}
|
||||
if let synthesisError = synthesisError {
|
||||
throw GestureError.synthesisFailed("\(synthesisError)")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user