From 8b202d41a63b6dea88edc0841caddc7bd6a460fa Mon Sep 17 00:00:00 2001 From: Pascal Date: Wed, 10 Jun 2026 20:49:07 -0400 Subject: [PATCH 1/4] Add ruler cursor and mouse tick behavior tests --- Free Ruler/MouseTickTimerPolicy.swift | 6 ++++++ Free Ruler/RulerCursorController.swift | 17 ++++++++++------ FreeRulerTests/RulerCoreTests.swift | 28 ++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/Free Ruler/MouseTickTimerPolicy.swift b/Free Ruler/MouseTickTimerPolicy.swift index 8faa6fb..d213705 100644 --- a/Free Ruler/MouseTickTimerPolicy.swift +++ b/Free Ruler/MouseTickTimerPolicy.swift @@ -45,4 +45,10 @@ final class MouseTickTimerPolicy { func resume(owner: AnyObject) { suspendedOwners.remove(owner) } + + func resume(owners: [AnyObject]) { + for owner in owners { + resume(owner: owner) + } + } } diff --git a/Free Ruler/RulerCursorController.swift b/Free Ruler/RulerCursorController.swift index 9d621f3..e4ef7f9 100644 --- a/Free Ruler/RulerCursorController.swift +++ b/Free Ruler/RulerCursorController.swift @@ -49,6 +49,11 @@ final class RulerCursorController { updateCursor() } + func mouseMovedInRuler() { + mouseIsOverRuler = true + updateCursor(force: true) + } + func mouseExitedRuler() { mouseIsOverRuler = false updateCursor() @@ -66,20 +71,20 @@ final class RulerCursorController { updateCursor() } - private func updateCursor() { + private func updateCursor(force: Bool = false) { guard appIsActive else { return } if mouseIsDraggingRuler { - setCursor(.closedHand) + setCursor(.closedHand, force: force) } else if mouseIsOverRuler { - setCursor(.openHand) + setCursor(.openHand, force: force) } else { - setCursor(.crosshair) + setCursor(.crosshair, force: force) } } - private func setCursor(_ cursor: CursorStyle) { - guard cursor != currentCursor else { return } + private func setCursor(_ cursor: CursorStyle, force: Bool = false) { + guard force || cursor != currentCursor else { return } currentCursor = cursor applyCursor(cursor) diff --git a/FreeRulerTests/RulerCoreTests.swift b/FreeRulerTests/RulerCoreTests.swift index cb1c206..48b3080 100644 --- a/FreeRulerTests/RulerCoreTests.swift +++ b/FreeRulerTests/RulerCoreTests.swift @@ -131,6 +131,19 @@ final class RulerCoreTests: XCTestCase { ]) } + func testRulerCursorControllerReappliesHandOnMouseMovement() { + var appliedCursors: [RulerCursorController.CursorStyle] = [] + let controller = RulerCursorController { appliedCursors.append($0) } + + controller.applicationDidBecomeActive() + controller.mouseEnteredRuler() + appliedCursors.removeAll() + + controller.mouseMovedInRuler() + + XCTAssertEqual(appliedCursors, [.openHand]) + } + func testMouseTickTimerPolicyRunsOnlyWhenRulersAreVisible() { let policy = MouseTickTimerPolicy(foregroundInterval: 1 / 60, backgroundInterval: 1 / 30) @@ -198,4 +211,19 @@ final class RulerCoreTests: XCTestCase { XCTAssertEqual(policy.desiredInterval, 1 / 60) } + + func testMouseTickTimerPolicyResumesMultipleOwnersTogether() { + let policy = MouseTickTimerPolicy(foregroundInterval: 1 / 60, backgroundInterval: 1 / 30) + let firstOwner = NSObject() + let secondOwner = NSObject() + + policy.applicationDidBecomeActive() + policy.updateVisibleRulers(true) + policy.suspend(owner: firstOwner) + policy.suspend(owner: secondOwner) + + policy.resume(owners: [firstOwner, secondOwner]) + + XCTAssertEqual(policy.desiredInterval, 1 / 60) + } } From eb331b20e2c9f86e25275ec5186e6f07dde20775 Mon Sep 17 00:00:00 2001 From: Pascal Date: Wed, 10 Jun 2026 21:08:41 -0400 Subject: [PATCH 2/4] Make cursor behavior tests test-only --- Free Ruler/MouseTickTimerPolicy.swift | 6 --- Free Ruler/RulerCursorController.swift | 17 +++---- FreeRulerTests/RulerCoreTests.swift | 65 ++++++++++++++++++-------- 3 files changed, 51 insertions(+), 37 deletions(-) diff --git a/Free Ruler/MouseTickTimerPolicy.swift b/Free Ruler/MouseTickTimerPolicy.swift index d213705..8faa6fb 100644 --- a/Free Ruler/MouseTickTimerPolicy.swift +++ b/Free Ruler/MouseTickTimerPolicy.swift @@ -45,10 +45,4 @@ final class MouseTickTimerPolicy { func resume(owner: AnyObject) { suspendedOwners.remove(owner) } - - func resume(owners: [AnyObject]) { - for owner in owners { - resume(owner: owner) - } - } } diff --git a/Free Ruler/RulerCursorController.swift b/Free Ruler/RulerCursorController.swift index e4ef7f9..9d621f3 100644 --- a/Free Ruler/RulerCursorController.swift +++ b/Free Ruler/RulerCursorController.swift @@ -49,11 +49,6 @@ final class RulerCursorController { updateCursor() } - func mouseMovedInRuler() { - mouseIsOverRuler = true - updateCursor(force: true) - } - func mouseExitedRuler() { mouseIsOverRuler = false updateCursor() @@ -71,20 +66,20 @@ final class RulerCursorController { updateCursor() } - private func updateCursor(force: Bool = false) { + private func updateCursor() { guard appIsActive else { return } if mouseIsDraggingRuler { - setCursor(.closedHand, force: force) + setCursor(.closedHand) } else if mouseIsOverRuler { - setCursor(.openHand, force: force) + setCursor(.openHand) } else { - setCursor(.crosshair, force: force) + setCursor(.crosshair) } } - private func setCursor(_ cursor: CursorStyle, force: Bool = false) { - guard force || cursor != currentCursor else { return } + private func setCursor(_ cursor: CursorStyle) { + guard cursor != currentCursor else { return } currentCursor = cursor applyCursor(cursor) diff --git a/FreeRulerTests/RulerCoreTests.swift b/FreeRulerTests/RulerCoreTests.swift index 48b3080..7aaa281 100644 --- a/FreeRulerTests/RulerCoreTests.swift +++ b/FreeRulerTests/RulerCoreTests.swift @@ -131,17 +131,57 @@ final class RulerCoreTests: XCTestCase { ]) } - func testRulerCursorControllerReappliesHandOnMouseMovement() { + func testRulerCursorControllerTracksMouseOverMouseDownAndMouseOutActions() { var appliedCursors: [RulerCursorController.CursorStyle] = [] let controller = RulerCursorController { appliedCursors.append($0) } + func assertCursor( + after actionName: String, + _ action: () -> Void, + is expectedCursor: RulerCursorController.CursorStyle, + file: StaticString = #filePath, + line: UInt = #line + ) { + action() + XCTAssertEqual(controller.currentCursor, expectedCursor, actionName, file: file, line: line) + XCTAssertEqual(appliedCursors.last, expectedCursor, actionName, file: file, line: line) + } + controller.applicationDidBecomeActive() - controller.mouseEnteredRuler() - appliedCursors.removeAll() + XCTAssertEqual(controller.currentCursor, .crosshair) + + assertCursor(after: "mouse over ruler", { + controller.mouseEnteredRuler() + }, is: .openHand) + + assertCursor(after: "mouse down inside ruler", { + controller.mouseDownInRuler() + }, is: .closedHand) + + let appliedCursorCountBeforeMouseOut = appliedCursors.count + controller.mouseExitedRuler() + XCTAssertEqual(controller.currentCursor, .closedHand, "mouse out while dragging") + XCTAssertEqual(appliedCursors.count, appliedCursorCountBeforeMouseOut, "mouse out while dragging") + + assertCursor(after: "mouse up outside ruler", { + controller.mouseUpInRuler(mouseIsInsideRuler: false) + }, is: .crosshair) + + assertCursor(after: "mouse over ruler again", { + controller.mouseEnteredRuler() + }, is: .openHand) - controller.mouseMovedInRuler() + assertCursor(after: "mouse down inside ruler again", { + controller.mouseDownInRuler() + }, is: .closedHand) - XCTAssertEqual(appliedCursors, [.openHand]) + assertCursor(after: "mouse up inside ruler", { + controller.mouseUpInRuler(mouseIsInsideRuler: true) + }, is: .openHand) + + assertCursor(after: "mouse out after release", { + controller.mouseExitedRuler() + }, is: .crosshair) } func testMouseTickTimerPolicyRunsOnlyWhenRulersAreVisible() { @@ -211,19 +251,4 @@ final class RulerCoreTests: XCTestCase { XCTAssertEqual(policy.desiredInterval, 1 / 60) } - - func testMouseTickTimerPolicyResumesMultipleOwnersTogether() { - let policy = MouseTickTimerPolicy(foregroundInterval: 1 / 60, backgroundInterval: 1 / 30) - let firstOwner = NSObject() - let secondOwner = NSObject() - - policy.applicationDidBecomeActive() - policy.updateVisibleRulers(true) - policy.suspend(owner: firstOwner) - policy.suspend(owner: secondOwner) - - policy.resume(owners: [firstOwner, secondOwner]) - - XCTAssertEqual(policy.desiredInterval, 1 / 60) - } } From ce933cdafe6862faf444f4e24ab7ddaf95f55297 Mon Sep 17 00:00:00 2001 From: Pascal Date: Wed, 10 Jun 2026 21:46:00 -0400 Subject: [PATCH 3/4] Add UI test cursor state & forward mouse events Enable UI tests to observe cursor changes and ensure mouse events propagate correctly. - AppDelegate: read FREE_RULER_UI_TEST_CURSOR_STATE_NAME env var, add writeUITestCursorState(), and initialize cursor state to "none" during UI tests. - RulerCursorController: add accessibilityValue for cursor styles and write the current cursor state to the test file whenever cursor changes. - RuleView: forward mouseEntered/mouseExited/mouseDown/mouseUp/mouseMoved to nextResponder and accept first mouse to ensure expected behavior during UI interactions. - RulerWindow: forward mouseDown/mouseUp to nextResponder and call super. - FreeRulerUITests: create per-run temp cursor state file, set env var, add multiple tests and helpers to hover/press elements and assert cursor sequences by reading the state file; clean up after tests. These changes make cursor-related assertions in UI tests deterministic by communicating cursor state via a temporary file and ensure UI elements receive mouse events as expected. --- Free Ruler/AppDelegate.swift | 10 ++ Free Ruler/RuleView.swift | 24 ++++ Free Ruler/RulerCursorController.swift | 20 +++ Free Ruler/RulerWindow.swift | 10 ++ FreeRulerUITests/FreeRulerUITests.swift | 182 ++++++++++++++++++++++++ 5 files changed, 246 insertions(+) diff --git a/Free Ruler/AppDelegate.swift b/Free Ruler/AppDelegate.swift index adf23bf..d5f7dc5 100644 --- a/Free Ruler/AppDelegate.swift +++ b/Free Ruler/AppDelegate.swift @@ -9,6 +9,15 @@ import Sparkle let env = ProcessInfo.processInfo.environment let UI_TESTS = env["FREE_RULER_UI_TESTS"] != nil +let UI_TEST_CURSOR_STATE_NAME = env["FREE_RULER_UI_TEST_CURSOR_STATE_NAME"] + +func writeUITestCursorState(_ value: String) { + guard let name = UI_TEST_CURSOR_STATE_NAME else { return } + + let url = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) + .appendingPathComponent(name) + try? value.write(to: url, atomically: true, encoding: .utf8) +} private enum HotkeyBezelLocalizationKey: String { case rulersFloated = "HotkeyBezel.RulersFloated" @@ -99,6 +108,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { if UI_TESTS { resetStateForUITests() + writeUITestCursorState("none") } subscribeToPrefs() diff --git a/Free Ruler/RuleView.swift b/Free Ruler/RuleView.swift index 06b81bb..79ace76 100644 --- a/Free Ruler/RuleView.swift +++ b/Free Ruler/RuleView.swift @@ -34,6 +34,30 @@ class RuleView: NSView { addTrackingArea(trackingArea!) } + override func mouseEntered(with event: NSEvent) { + nextResponder?.mouseEntered(with: event) + } + + override func mouseExited(with event: NSEvent) { + nextResponder?.mouseExited(with: event) + } + + override func mouseDown(with event: NSEvent) { + nextResponder?.mouseDown(with: event) + } + + override func mouseUp(with event: NSEvent) { + nextResponder?.mouseUp(with: event) + } + + override func mouseMoved(with event: NSEvent) { + nextResponder?.mouseMoved(with: event) + } + + override func acceptsFirstMouse(for event: NSEvent?) -> Bool { + return true + } + func drawMouseTick(at mouseLoc: NSPoint) { // required override // TODO: is there a better way to do this, maybe via a protocol? diff --git a/Free Ruler/RulerCursorController.swift b/Free Ruler/RulerCursorController.swift index 9d621f3..7733e2d 100644 --- a/Free Ruler/RulerCursorController.swift +++ b/Free Ruler/RulerCursorController.swift @@ -7,6 +7,19 @@ final class RulerCursorController { case openHand case closedHand + var accessibilityValue: String { + switch self { + case .arrow: + return "arrow" + case .crosshair: + return "crosshair" + case .openHand: + return "open-hand" + case .closedHand: + return "closed-hand" + } + } + var nsCursor: NSCursor { switch self { case .arrow: @@ -83,5 +96,12 @@ final class RulerCursorController { currentCursor = cursor applyCursor(cursor) + cursor.writeUITestStateIfNeeded() + } +} + +private extension RulerCursorController.CursorStyle { + func writeUITestStateIfNeeded() { + writeUITestCursorState(accessibilityValue) } } diff --git a/Free Ruler/RulerWindow.swift b/Free Ruler/RulerWindow.swift index 383f85f..9a30405 100644 --- a/Free Ruler/RulerWindow.swift +++ b/Free Ruler/RulerWindow.swift @@ -57,6 +57,16 @@ class RulerWindow: NSPanel { set {} } + override func mouseDown(with event: NSEvent) { + nextResponder?.mouseDown(with: event) + super.mouseDown(with: event) + } + + override func mouseUp(with event: NSEvent) { + nextResponder?.mouseUp(with: event) + super.mouseUp(with: event) + } + } private func getTitle(for orientation: Orientation) -> String { diff --git a/FreeRulerUITests/FreeRulerUITests.swift b/FreeRulerUITests/FreeRulerUITests.swift index 88304a3..6c5c303 100644 --- a/FreeRulerUITests/FreeRulerUITests.swift +++ b/FreeRulerUITests/FreeRulerUITests.swift @@ -1,14 +1,28 @@ import XCTest +import Darwin final class FreeRulerUITests: XCTestCase { private var app: XCUIApplication! + private var cursorStateURL: URL! override func setUpWithError() throws { continueAfterFailure = false + let cursorStateName = "FreeRulerUITests-\(UUID().uuidString).cursor" + let homeDirectory = currentUserHomeDirectory() + cursorStateURL = URL(fileURLWithPath: homeDirectory) + .appendingPathComponent("Library/Containers/com.pascal.freeruler/Data/tmp", isDirectory: true) + .appendingPathComponent(cursorStateName) + try? FileManager.default.createDirectory( + at: cursorStateURL.deletingLastPathComponent(), + withIntermediateDirectories: true + ) + try? FileManager.default.removeItem(at: cursorStateURL) + app = XCUIApplication() app.launchEnvironment["FREE_RULER_UI_TESTS"] = "1" + app.launchEnvironment["FREE_RULER_UI_TEST_CURSOR_STATE_NAME"] = cursorStateName app.launch() app.activate() } @@ -16,6 +30,8 @@ final class FreeRulerUITests: XCTestCase { override func tearDownWithError() throws { app.terminate() app = nil + try? FileManager.default.removeItem(at: cursorStateURL) + cursorStateURL = nil } func testRulerVisibilityKeyboardCommands() { @@ -210,6 +226,46 @@ final class FreeRulerUITests: XCTestCase { XCTAssertTrue(verticalRuler.waitForFrameChange(from: originalVerticalFrame, timeout: 2)) } + func testHorizontalRulerCursorForMouseoverMousedownAndMouseoutActions() { + XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 3)) + XCTAssertTrue(verticalRuler.waitForExistence(timeout: 3)) + + isolateHorizontalRulerByUngroupingWithVerticalToggle() + + assertCursorSequence(on: horizontalRuler, label: "ungrouped horizontal ruler") + } + + func testVerticalRulerCursorForMouseoverMousedownAndMouseoutActions() { + XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 3)) + XCTAssertTrue(verticalRuler.waitForExistence(timeout: 3)) + + isolateHorizontalRulerByUngroupingWithVerticalToggle() + + app.typeKey("h", modifierFlags: []) + XCTAssertTrue(horizontalRuler.waitForNonExistence(timeout: 2)) + app.typeKey("v", modifierFlags: []) + XCTAssertTrue(verticalRuler.waitForExistence(timeout: 2)) + + assertCursorSequence(on: verticalRuler, label: "ungrouped vertical ruler") + } + + func testGroupedRulerCursorsForKeyAndChildWindows() { + XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 3)) + XCTAssertTrue(verticalRuler.waitForExistence(timeout: 3)) + + setGroupRulers(true) + XCTAssertTrue(groupRulersEnabledInPreferences()) + closePreferences() + + horizontalRuler.click() + assertCursorSequence(on: horizontalRulerView, label: "grouped key horizontal ruler") + assertCursorSequence(on: verticalRulerView, label: "grouped child vertical ruler") + + verticalRuler.click() + assertCursorSequence(on: verticalRulerView, label: "grouped key vertical ruler") + assertCursorSequence(on: horizontalRulerView, label: "grouped child horizontal ruler") + } + private var horizontalRuler: XCUIElement { app.dialogs["horizontal-ruler-window"] } @@ -222,6 +278,10 @@ final class FreeRulerUITests: XCTestCase { app.otherElements["horizontal-ruler-view"] } + private var verticalRulerView: XCUIElement { + app.otherElements["vertical-ruler-view"] + } + private var preferencesWindow: XCUIElement { app.windows["Free Ruler Preferences"] } @@ -284,6 +344,36 @@ final class FreeRulerUITests: XCTestCase { return rulerShadowCheckbox.isChecked } + private func isolateHorizontalRulerByUngroupingWithVerticalToggle() { + horizontalRuler.click() + app.typeKey("v", modifierFlags: []) + + XCTAssertTrue(horizontalRuler.waitForExistence(timeout: 2)) + XCTAssertTrue(verticalRuler.waitForNonExistence(timeout: 2)) + XCTAssertFalse(groupRulersEnabledInPreferences()) + closePreferences() + } + + private func assertCursorSequence(on ruler: XCUIElement, label: String) { + hover(over: ruler) + assertCursor("open-hand", after: "mouseover \(label)") + + pressAndRelease(in: ruler, assertingCursorDuringPress: "closed-hand") + assertCursor("open-hand", after: "mousedown and mouseup inside \(label)") + + hover(over: pointOutside(ruler)) + assertCursor("crosshair", after: "mouseout \(label)") + + hover(over: ruler) + assertCursor("open-hand", after: "mouseover \(label) again") + + pressAndRelease(in: ruler, assertingCursorDuringPress: "closed-hand") + assertCursor("open-hand", after: "mousedown and mouseup inside \(label) again") + + hover(over: pointOutside(ruler)) + assertCursor("crosshair", after: "mouseout \(label) again") + } + private func waitForHotkeyBezel(_ expectedLabel: String, timeout: TimeInterval = 2) -> Bool { let deadline = Date().addingTimeInterval(timeout) @@ -302,6 +392,98 @@ final class FreeRulerUITests: XCTestCase { return (hotkeyBezel.exists && hotkeyBezel.value as? String == expectedLabel) || (hotkeyBezelLabel.exists && hotkeyBezelLabel.label == expectedLabel) } + + private func hover(over element: XCUIElement) { + hover(over: interactionPoint(in: element)) + } + + private func hover(over coordinate: XCUICoordinate) { + coordinate.hover() + RunLoop.current.run(until: Date().addingTimeInterval(0.1)) + } + + private func pressAndRelease(in element: XCUIElement, assertingCursorDuringPress expectedCursor: String) { + let expectation = expectationForCursor(expectedCursor, after: "mousedown inside \(element.identifier)") + let coordinate = interactionPoint(in: element) + coordinate.press(forDuration: 0.4) + wait(for: [expectation], timeout: 1) + RunLoop.current.run(until: Date().addingTimeInterval(0.1)) + } + + private func pointOutside(_ element: XCUIElement) -> XCUICoordinate { + let yOffset = isVerticalRulerElement(element) ? 0.75 : 1.5 + return element.coordinate(withNormalizedOffset: CGVector(dx: 1.5, dy: yOffset)) + } + + private func interactionPoint(in element: XCUIElement) -> XCUICoordinate { + let yOffset = isVerticalRulerElement(element) ? 0.75 : 0.5 + return element.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: yOffset)) + } + + private func isVerticalRulerElement(_ element: XCUIElement) -> Bool { + return element.identifier.contains("vertical-ruler") + } + + private func expectationForCursor(_ expectedCursor: String, after action: String) -> XCTestExpectation { + let expectation = expectation(description: "Expected cursor \(expectedCursor) after \(action)") + + DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 0.15) { [cursorStateURL] in + let deadline = Date().addingTimeInterval(1.5) + + while Date() < deadline { + let cursor = try? String(contentsOf: cursorStateURL!, encoding: .utf8) + + if cursor == expectedCursor { + expectation.fulfill() + return + } + + Thread.sleep(forTimeInterval: 0.025) + } + } + + return expectation + } + + private func assertCursor( + _ expectedCursor: String, + after action: String, + file: StaticString = #filePath, + line: UInt = #line + ) { + XCTAssertTrue( + waitForCursor(expectedCursor, timeout: 2), + "Expected cursor \(expectedCursor) after \(action); actual cursor was \(readCursorState() ?? "nil")", + file: file, + line: line + ) + } + + private func waitForCursor(_ expectedCursor: String, timeout: TimeInterval) -> Bool { + let deadline = Date().addingTimeInterval(timeout) + + while Date() < deadline { + if readCursorState() == expectedCursor { + return true + } + + RunLoop.current.run(until: Date().addingTimeInterval(0.05)) + } + + return readCursorState() == expectedCursor + } + + private func readCursorState() -> String? { + return try? String(contentsOf: cursorStateURL, encoding: .utf8) + } + + private func currentUserHomeDirectory() -> String { + guard let passwd = getpwuid(getuid()) else { + return NSHomeDirectory() + } + + return String(cString: passwd.pointee.pw_dir) + } } private extension XCUIElement { From 511b6b146b642023d17b40b8bb9f45520e9ed3f2 Mon Sep 17 00:00:00 2001 From: Pascal Date: Wed, 10 Jun 2026 21:57:38 -0400 Subject: [PATCH 4/4] Address cursor UI test review feedback --- Free Ruler/AppDelegate.swift | 10 +++++++++- Free Ruler/RulerCursorController.swift | 4 ++-- FreeRulerUITests/FreeRulerUITests.swift | 4 +++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Free Ruler/AppDelegate.swift b/Free Ruler/AppDelegate.swift index d5f7dc5..f7b8954 100644 --- a/Free Ruler/AppDelegate.swift +++ b/Free Ruler/AppDelegate.swift @@ -9,7 +9,15 @@ import Sparkle let env = ProcessInfo.processInfo.environment let UI_TESTS = env["FREE_RULER_UI_TESTS"] != nil -let UI_TEST_CURSOR_STATE_NAME = env["FREE_RULER_UI_TEST_CURSOR_STATE_NAME"] +let UI_TEST_CURSOR_STATE_NAME: String? = { + guard UI_TESTS, + let name = env["FREE_RULER_UI_TEST_CURSOR_STATE_NAME"] else { return nil } + + let lastPathComponent = URL(fileURLWithPath: name).lastPathComponent + guard !lastPathComponent.isEmpty, lastPathComponent == name else { return nil } + + return name +}() func writeUITestCursorState(_ value: String) { guard let name = UI_TEST_CURSOR_STATE_NAME else { return } diff --git a/Free Ruler/RulerCursorController.swift b/Free Ruler/RulerCursorController.swift index 7733e2d..6ff2b2e 100644 --- a/Free Ruler/RulerCursorController.swift +++ b/Free Ruler/RulerCursorController.swift @@ -7,7 +7,7 @@ final class RulerCursorController { case openHand case closedHand - var accessibilityValue: String { + var uiTestStateValue: String { switch self { case .arrow: return "arrow" @@ -102,6 +102,6 @@ final class RulerCursorController { private extension RulerCursorController.CursorStyle { func writeUITestStateIfNeeded() { - writeUITestCursorState(accessibilityValue) + writeUITestCursorState(uiTestStateValue) } } diff --git a/FreeRulerUITests/FreeRulerUITests.swift b/FreeRulerUITests/FreeRulerUITests.swift index 6c5c303..f2d8378 100644 --- a/FreeRulerUITests/FreeRulerUITests.swift +++ b/FreeRulerUITests/FreeRulerUITests.swift @@ -428,10 +428,12 @@ final class FreeRulerUITests: XCTestCase { let expectation = expectation(description: "Expected cursor \(expectedCursor) after \(action)") DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 0.15) { [cursorStateURL] in + guard let cursorStateURL = cursorStateURL else { return } + let deadline = Date().addingTimeInterval(1.5) while Date() < deadline { - let cursor = try? String(contentsOf: cursorStateURL!, encoding: .utf8) + let cursor = try? String(contentsOf: cursorStateURL, encoding: .utf8) if cursor == expectedCursor { expectation.fulfill()