mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
90 lines
3.4 KiB
Swift
90 lines
3.4 KiB
Swift
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)")
|
|
}
|
|
}
|
|
}
|