Skip to content

Commit

Permalink
Add new locatable utilities (#49)
Browse files Browse the repository at this point in the history
Adds a couple more locatable utilities and makes mainView public so its possible to create more
  • Loading branch information
gabriellanata authored Aug 23, 2022
1 parent 1b8e6e8 commit d560b57
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 2 deletions.
2 changes: 1 addition & 1 deletion HammerTests.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "HammerTests"
spec.version = "0.14.2"
spec.version = "0.14.3"
spec.summary = "iOS touch and keyboard syntheis library for unit tests."
spec.description = "Hammer is a touch and keyboard synthesis library for emulating user interaction events. It enables new ways of triggering UI actions in unit tests, replicating a real world environment as much as possible."
spec.homepage = "https://github.com/lyft/Hammer"
Expand Down
2 changes: 1 addition & 1 deletion Sources/Hammer/EventGenerator/EventGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public final class EventGenerator {
public let window: UIWindow

/// The view that was used to create the event generator
private(set) var mainView: UIView
public private(set) var mainView: UIView

var activeTouches = TouchStorage()
var debugWindow = DebugVisualizerWindow()
Expand Down
54 changes: 54 additions & 0 deletions Sources/Hammer/EventGenerator/HammerLocatable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,57 @@ extension String: HammerLocatable {
return try eventGenerator.viewWithIdentifier(self).windowHitPoint(for: eventGenerator)
}
}

/// Creates an absolute offset for a location in screen points.
public struct OffsetLocation: HammerLocatable {
public let location: HammerLocatable?
public let x: CGFloat
public let y: CGFloat

/// Creates an offset for a location.
///
/// - parameter location: The location to offset. Passing nil will use the default location.
/// - parameter x: The x offset.
/// - parameter y: The y offset.
public init(location: HammerLocatable? = nil, x: CGFloat, y: CGFloat) {
self.location = location
self.x = x
self.y = y
}

public func windowHitPoint(for eventGenerator: EventGenerator) throws -> CGPoint {
let location = self.location ?? eventGenerator.mainView
let hitPoint = try location.windowHitPoint(for: eventGenerator)
return CGPoint(x: hitPoint.x + self.x,
y: hitPoint.y + self.y)
}
}

/// Creates a relative location for a view.
public struct RelativeLocation: HammerLocatable {
public let view: UIView?
public let x: CGFloat
public let y: CGFloat

/// Creates a relative location for a view
///
/// Values for x and y are relative to the dimensions of the view. From 0 to 1, 0 being the top/left of
/// the view and 1 being the bottom/right of the view. Passing a value outside those bounds will result
/// in the touch occurring outside the view.
///
/// - parameter view: The view to get a relative location for. Passing nil will use the default view.
/// - parameter x: The relative x value.
/// - parameter y: The relative y value.
public init(location view: UIView? = nil, x: CGFloat, y: CGFloat) {
self.view = view
self.x = x
self.y = y
}

public func windowHitPoint(for eventGenerator: EventGenerator) throws -> CGPoint {
let view = self.view ?? eventGenerator.mainView
let hitPoint = try eventGenerator.windowHitPoint(forView: view)
return CGPoint(x: hitPoint.x - view.bounds.center.x + view.bounds.width * self.x,
y: hitPoint.y - view.bounds.center.y + view.bounds.height * self.y)
}
}
52 changes: 52 additions & 0 deletions Tests/HammerTests/HandTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -358,4 +358,56 @@ final class HandTests: XCTestCase {
try eventGenerator.wait(0.3)
XCTAssertEqual(view.zoomScale, 1, accuracy: 0.1)
}

func testOffsetLocation() throws {
let view = UIStackView()
view.axis = .horizontal

var expectations = [XCTestExpectation]()

for i in 1...3 {
let button = UIButton()
button.setTitle("\(i)", for: .normal)
button.setSize(width: 100, height: 100)
let expectation = XCTestExpectation(description: "Button Tapped")
expectation.assertForOverFulfill = true
button.addHandler(forEvent: .primaryActionTriggered, action: expectation.fulfill)
expectations.append(expectation)
view.addArrangedSubview(button)
}

let eventGenerator = try EventGenerator(view: view)
try eventGenerator.waitUntilHittable(view.subviews[0], timeout: 1)
try eventGenerator.fingerTap(at: OffsetLocation(x: -100, y: 0))
try eventGenerator.fingerTap(at: OffsetLocation(x: 0, y: 0))
try eventGenerator.fingerTap(at: OffsetLocation(x: 100, y: 0))

XCTAssertEqual(XCTWaiter.wait(for: expectations, timeout: 1, enforceOrder: true), .completed)
}

func testRelativeLocation() throws {
let view = UIStackView()
view.axis = .horizontal

var expectations = [XCTestExpectation]()

for i in 1...3 {
let button = UIButton()
button.setTitle("\(i)", for: .normal)
button.setSize(width: 100, height: 100)
let expectation = XCTestExpectation(description: "Button Tapped")
expectation.assertForOverFulfill = true
button.addHandler(forEvent: .primaryActionTriggered, action: expectation.fulfill)
expectations.append(expectation)
view.addArrangedSubview(button)
}

let eventGenerator = try EventGenerator(view: view)
try eventGenerator.waitUntilHittable(view.subviews[0], timeout: 1)
try eventGenerator.fingerTap(at: RelativeLocation(x: 0.2, y: 0.5))
try eventGenerator.fingerTap(at: RelativeLocation(x: 0.5, y: 0.5))
try eventGenerator.fingerTap(at: RelativeLocation(x: 0.8, y: 0.5))

XCTAssertEqual(XCTWaiter.wait(for: expectations, timeout: 1, enforceOrder: true), .completed)
}
}

0 comments on commit d560b57

Please sign in to comment.