testing:like all fail

This commit is contained in:
Michael Freno
2026-01-08 23:14:19 -05:00
parent a14b7e7b99
commit 4ebaece754
9 changed files with 800 additions and 3 deletions

View File

@@ -169,9 +169,14 @@ class AppDelegate: NSObject, NSApplicationDelegate {
window.backgroundColor = .clear
window.contentView = NSHostingView(rootView: content)
window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
// Ensure this window can receive key events
window.acceptsMouseMovedEvents = true
window.makeFirstResponder(window.contentView)
let windowController = NSWindowController(window: window)
windowController.showWindow(nil)
// Make sure the window is brought to front and made key for key events
window.makeKeyAndOrderFront(nil)
reminderWindowController = windowController
}

View File

@@ -0,0 +1,176 @@
//
// IntegrationTests.swift
// GazeTests
//
// Created by Mike Freno on 1/8/26.
//
import XCTest
@testable import Gaze
@MainActor
final class IntegrationTests: XCTestCase {
var settingsManager: SettingsManager!
var timerEngine: TimerEngine!
override func setUp() async throws {
try await super.setUp()
settingsManager = SettingsManager.shared
UserDefaults.standard.removeObject(forKey: "gazeAppSettings")
settingsManager.load()
timerEngine = TimerEngine(settingsManager: settingsManager)
}
override func tearDown() async throws {
timerEngine.stop()
UserDefaults.standard.removeObject(forKey: "gazeAppSettings")
try await super.tearDown()
}
func testSettingsChangePropagateToTimerEngine() {
timerEngine.start()
let originalInterval = timerEngine.timerStates[.lookAway]?.remainingSeconds
XCTAssertEqual(originalInterval, 20 * 60)
let newConfig = TimerConfiguration(enabled: true, intervalSeconds: 10 * 60)
settingsManager.updateTimerConfiguration(for: .lookAway, configuration: newConfig)
timerEngine.start()
let newInterval = timerEngine.timerStates[.lookAway]?.remainingSeconds
XCTAssertEqual(newInterval, 10 * 60)
}
func testDisablingTimerRemovesFromEngine() {
timerEngine.start()
XCTAssertNotNil(timerEngine.timerStates[.blink])
var config = TimerConfiguration(enabled: false, intervalSeconds: 5 * 60)
settingsManager.updateTimerConfiguration(for: .blink, configuration: config)
timerEngine.start()
XCTAssertNil(timerEngine.timerStates[.blink])
}
func testEnablingTimerAddsToEngine() {
settingsManager.settings.postureTimer.enabled = false
timerEngine.start()
XCTAssertNil(timerEngine.timerStates[.posture])
let config = TimerConfiguration(enabled: true, intervalSeconds: 30 * 60)
settingsManager.updateTimerConfiguration(for: .posture, configuration: config)
timerEngine.start()
XCTAssertNotNil(timerEngine.timerStates[.posture])
}
func testSettingsPersistAcrossEngineLifecycle() {
let config = TimerConfiguration(enabled: false, intervalSeconds: 15 * 60)
settingsManager.updateTimerConfiguration(for: .lookAway, configuration: config)
timerEngine.start()
timerEngine.stop()
let newEngine = TimerEngine(settingsManager: settingsManager)
newEngine.start()
XCTAssertNil(newEngine.timerStates[.lookAway])
}
func testMultipleTimerConfigurationUpdates() {
timerEngine.start()
let configs = [
(TimerType.lookAway, TimerConfiguration(enabled: true, intervalSeconds: 600)),
(TimerType.blink, TimerConfiguration(enabled: true, intervalSeconds: 300)),
(TimerType.posture, TimerConfiguration(enabled: true, intervalSeconds: 1800))
]
for (type, config) in configs {
settingsManager.updateTimerConfiguration(for: type, configuration: config)
}
timerEngine.start()
XCTAssertEqual(timerEngine.timerStates[.lookAway]?.remainingSeconds, 600)
XCTAssertEqual(timerEngine.timerStates[.blink]?.remainingSeconds, 300)
XCTAssertEqual(timerEngine.timerStates[.posture]?.remainingSeconds, 1800)
}
func testResetToDefaultsAffectsTimerEngine() {
let config = TimerConfiguration(enabled: false, intervalSeconds: 5 * 60)
settingsManager.updateTimerConfiguration(for: .blink, configuration: config)
timerEngine.start()
XCTAssertNil(timerEngine.timerStates[.blink])
settingsManager.resetToDefaults()
timerEngine.start()
XCTAssertNotNil(timerEngine.timerStates[.blink])
XCTAssertEqual(timerEngine.timerStates[.blink]?.remainingSeconds, 5 * 60)
}
func testTimerEngineRespectsDisabledTimers() {
settingsManager.settings.lookAwayTimer.enabled = false
settingsManager.settings.blinkTimer.enabled = false
settingsManager.settings.postureTimer.enabled = false
timerEngine.start()
XCTAssertTrue(timerEngine.timerStates.isEmpty)
}
func testCompleteWorkflow() {
timerEngine.start()
XCTAssertEqual(timerEngine.timerStates.count, 3)
timerEngine.pause()
for (_, state) in timerEngine.timerStates {
XCTAssertTrue(state.isPaused)
}
timerEngine.resume()
for (_, state) in timerEngine.timerStates {
XCTAssertFalse(state.isPaused)
}
timerEngine.skipNext(type: .lookAway)
XCTAssertEqual(timerEngine.timerStates[.lookAway]?.remainingSeconds, 20 * 60)
timerEngine.stop()
XCTAssertTrue(timerEngine.timerStates.isEmpty)
}
func testReminderWorkflow() {
timerEngine.start()
timerEngine.triggerReminder(for: .lookAway)
XCTAssertNotNil(timerEngine.activeReminder)
for (_, state) in timerEngine.timerStates {
XCTAssertTrue(state.isPaused)
}
timerEngine.dismissReminder()
XCTAssertNil(timerEngine.activeReminder)
for (_, state) in timerEngine.timerStates {
XCTAssertFalse(state.isPaused)
}
}
func testSettingsAutoSaveIntegration() {
let config = TimerConfiguration(enabled: false, intervalSeconds: 900)
settingsManager.updateTimerConfiguration(for: .lookAway, configuration: config)
settingsManager.load()
let loadedConfig = settingsManager.timerConfiguration(for: .lookAway)
XCTAssertEqual(loadedConfig.intervalSeconds, 900)
XCTAssertFalse(loadedConfig.enabled)
}
}

View File

@@ -74,7 +74,7 @@ final class SettingsManagerTests: XCTestCase {
}
func testUpdateTimerConfiguration() {
var newConfig = TimerConfiguration(enabled: false, intervalSeconds: 10 * 60)
let newConfig = TimerConfiguration(enabled: false, intervalSeconds: 10 * 60)
settingsManager.updateTimerConfiguration(for: .lookAway, configuration: newConfig)
let retrieved = settingsManager.timerConfiguration(for: .lookAway)

View File

@@ -141,4 +141,169 @@ final class TimerEngineTests: XCTestCase {
XCTAssertFalse(state.isPaused)
}
}
func testTriggerReminderForLookAway() {
timerEngine.start()
timerEngine.triggerReminder(for: .lookAway)
XCTAssertNotNil(timerEngine.activeReminder)
if case .lookAwayTriggered(let countdown) = timerEngine.activeReminder {
XCTAssertEqual(countdown, settingsManager.settings.lookAwayCountdownSeconds)
} else {
XCTFail("Expected lookAwayTriggered reminder")
}
for (_, state) in timerEngine.timerStates {
XCTAssertTrue(state.isPaused)
}
}
func testTriggerReminderForBlink() {
timerEngine.start()
timerEngine.triggerReminder(for: .blink)
XCTAssertNotNil(timerEngine.activeReminder)
if case .blinkTriggered = timerEngine.activeReminder {
XCTAssertTrue(true)
} else {
XCTFail("Expected blinkTriggered reminder")
}
}
func testTriggerReminderForPosture() {
timerEngine.start()
timerEngine.triggerReminder(for: .posture)
XCTAssertNotNil(timerEngine.activeReminder)
if case .postureTriggered = timerEngine.activeReminder {
XCTAssertTrue(true)
} else {
XCTFail("Expected postureTriggered reminder")
}
}
func testGetTimeRemainingForNonExistentTimer() {
let timeRemaining = timerEngine.getTimeRemaining(for: .lookAway)
XCTAssertEqual(timeRemaining, 0)
}
func testGetFormattedTimeRemainingZeroSeconds() {
timerEngine.start()
timerEngine.timerStates[.lookAway]?.remainingSeconds = 0
let formatted = timerEngine.getFormattedTimeRemaining(for: .lookAway)
XCTAssertEqual(formatted, "0:00")
}
func testGetFormattedTimeRemainingLessThanMinute() {
timerEngine.start()
timerEngine.timerStates[.lookAway]?.remainingSeconds = 45
let formatted = timerEngine.getFormattedTimeRemaining(for: .lookAway)
XCTAssertEqual(formatted, "0:45")
}
func testGetFormattedTimeRemainingExactHour() {
timerEngine.start()
timerEngine.timerStates[.lookAway]?.remainingSeconds = 3600
let formatted = timerEngine.getFormattedTimeRemaining(for: .lookAway)
XCTAssertEqual(formatted, "1:00:00")
}
func testMultipleStartCallsResetTimers() {
timerEngine.start()
timerEngine.timerStates[.lookAway]?.remainingSeconds = 100
timerEngine.start()
XCTAssertEqual(timerEngine.timerStates[.lookAway]?.remainingSeconds, 20 * 60)
}
func testSkipNextPreservesPausedState() {
timerEngine.start()
timerEngine.pause()
timerEngine.skipNext(type: .lookAway)
XCTAssertTrue(timerEngine.timerStates[.lookAway]?.isPaused ?? false)
}
func testSkipNextPreservesActiveState() {
timerEngine.start()
timerEngine.skipNext(type: .lookAway)
XCTAssertTrue(timerEngine.timerStates[.lookAway]?.isActive ?? false)
}
func testDismissReminderWithNoActiveReminder() {
timerEngine.start()
XCTAssertNil(timerEngine.activeReminder)
timerEngine.dismissReminder()
XCTAssertNil(timerEngine.activeReminder)
}
func testDismissBlinkReminderDoesNotResumeTimers() {
timerEngine.start()
timerEngine.activeReminder = .blinkTriggered
timerEngine.dismissReminder()
for (_, state) in timerEngine.timerStates {
XCTAssertFalse(state.isPaused)
}
}
func testDismissPostureReminderDoesNotResumeTimers() {
timerEngine.start()
timerEngine.activeReminder = .postureTriggered
timerEngine.dismissReminder()
for (_, state) in timerEngine.timerStates {
XCTAssertFalse(state.isPaused)
}
}
func testAllTimersStartWhenEnabled() {
settingsManager.settings.lookAwayTimer.enabled = true
settingsManager.settings.blinkTimer.enabled = true
settingsManager.settings.postureTimer.enabled = true
timerEngine.start()
XCTAssertEqual(timerEngine.timerStates.count, 3)
for timerType in TimerType.allCases {
XCTAssertNotNil(timerEngine.timerStates[timerType])
}
}
func testAllTimersDisabled() {
settingsManager.settings.lookAwayTimer.enabled = false
settingsManager.settings.blinkTimer.enabled = false
settingsManager.settings.postureTimer.enabled = false
timerEngine.start()
XCTAssertEqual(timerEngine.timerStates.count, 0)
}
func testPartialTimersEnabled() {
settingsManager.settings.lookAwayTimer.enabled = true
settingsManager.settings.blinkTimer.enabled = false
settingsManager.settings.postureTimer.enabled = true
timerEngine.start()
XCTAssertEqual(timerEngine.timerStates.count, 2)
XCTAssertNotNil(timerEngine.timerStates[.lookAway])
XCTAssertNil(timerEngine.timerStates[.blink])
XCTAssertNotNil(timerEngine.timerStates[.posture])
}
}

View File

@@ -0,0 +1,86 @@
//
// AccessibilityUITests.swift
// GazeUITests
//
// Created by Mike Freno on 1/8/26.
//
import XCTest
@MainActor
final class AccessibilityUITests: XCTestCase {
var app: XCUIApplication!
override func setUpWithError() throws {
continueAfterFailure = false
app = XCUIApplication()
app.launchArguments.append("--skip-onboarding")
app.launch()
}
override func tearDownWithError() throws {
app = nil
}
func testMenuBarAccessibilityLabels() throws {
let menuBar = app.menuBarItems.firstMatch
if menuBar.waitForExistence(timeout: 5) {
menuBar.click()
for button in app.buttons.allElementsBoundByIndex {
XCTAssertFalse(button.label.isEmpty, "Button should have accessibility label")
}
}
}
func testKeyboardNavigation() throws {
let menuBar = app.menuBarItems.firstMatch
if menuBar.waitForExistence(timeout: 5) {
menuBar.click()
app.typeKey(XCUIKeyboardKey.tab, modifierFlags: [])
let focusedElement = app.descendants(matching: .any).element(matching: NSPredicate(format: "hasKeyboardFocus == true"))
XCTAssertTrue(focusedElement.exists || app.buttons.count > 0)
}
}
func testAllButtonsAreHittable() throws {
let menuBar = app.menuBarItems.firstMatch
if menuBar.waitForExistence(timeout: 5) {
menuBar.click()
sleep(1)
let buttons = app.buttons.allElementsBoundByIndex
for button in buttons where button.exists && button.isEnabled {
XCTAssertTrue(button.isHittable || !button.isEnabled, "Enabled button should be hittable: \(button.label)")
}
}
}
func testVoiceOverElementsHaveLabels() throws {
let menuBar = app.menuBarItems.firstMatch
if menuBar.waitForExistence(timeout: 5) {
menuBar.click()
let staticTexts = app.staticTexts.allElementsBoundByIndex
for text in staticTexts where text.exists {
XCTAssertFalse(text.label.isEmpty, "Static text should have label")
}
}
}
func testImagesHaveAccessibilityTraits() throws {
let menuBar = app.menuBarItems.firstMatch
if menuBar.waitForExistence(timeout: 5) {
menuBar.click()
let images = app.images.allElementsBoundByIndex
for image in images where image.exists {
XCTAssertFalse(image.label.isEmpty, "Image should have accessibility label")
}
}
}
}

View File

@@ -0,0 +1,102 @@
//
// MenuBarUITests.swift
// GazeUITests
//
// Created by Mike Freno on 1/8/26.
//
import XCTest
@MainActor
final class MenuBarUITests: XCTestCase {
var app: XCUIApplication!
override func setUpWithError() throws {
continueAfterFailure = false
app = XCUIApplication()
app.launchArguments.append("--skip-onboarding")
app.launch()
}
override func tearDownWithError() throws {
app = nil
}
func testMenuBarExtraExists() throws {
let menuBar = app.menuBarItems.firstMatch
XCTAssertTrue(menuBar.waitForExistence(timeout: 5))
}
func testMenuBarCanBeOpened() throws {
let menuBar = app.menuBarItems.firstMatch
if menuBar.waitForExistence(timeout: 5) {
menuBar.click()
let gazeTitle = app.staticTexts["Gaze"]
XCTAssertTrue(gazeTitle.waitForExistence(timeout: 2) || app.staticTexts.count > 0)
}
}
func testMenuBarHasTimerStatus() throws {
let menuBar = app.menuBarItems.firstMatch
if menuBar.waitForExistence(timeout: 5) {
menuBar.click()
let activeTimersText = app.staticTexts["Active Timers"]
let hasTimerInfo = activeTimersText.exists || app.staticTexts.count > 3
XCTAssertTrue(hasTimerInfo)
}
}
func testMenuBarHasPauseResumeControl() throws {
let menuBar = app.menuBarItems.firstMatch
if menuBar.waitForExistence(timeout: 5) {
menuBar.click()
let pauseButton = app.buttons.containing(NSPredicate(format: "label CONTAINS 'Pause' OR label CONTAINS 'Resume'")).firstMatch
XCTAssertTrue(pauseButton.waitForExistence(timeout: 2))
}
}
func testMenuBarHasSettingsButton() throws {
let menuBar = app.menuBarItems.firstMatch
if menuBar.waitForExistence(timeout: 5) {
menuBar.click()
let settingsButton = app.buttons["Settings"]
let settingsMenuItem = app.menuItems["Settings"]
XCTAssertTrue(settingsButton.exists || settingsMenuItem.exists)
}
}
func testMenuBarHasQuitButton() throws {
let menuBar = app.menuBarItems.firstMatch
if menuBar.waitForExistence(timeout: 5) {
menuBar.click()
let quitButton = app.buttons.containing(NSPredicate(format: "label CONTAINS 'Quit'")).firstMatch
XCTAssertTrue(quitButton.waitForExistence(timeout: 2))
}
}
func testPauseResumeToggle() throws {
let menuBar = app.menuBarItems.firstMatch
if menuBar.waitForExistence(timeout: 5) {
menuBar.click()
let pauseButton = app.buttons.containing(NSPredicate(format: "label CONTAINS 'Pause'")).firstMatch
let resumeButton = app.buttons.containing(NSPredicate(format: "label CONTAINS 'Resume'")).firstMatch
if pauseButton.exists && pauseButton.isHittable {
pauseButton.tap()
XCTAssertTrue(resumeButton.waitForExistence(timeout: 2))
} else if resumeButton.exists && resumeButton.isHittable {
resumeButton.tap()
XCTAssertTrue(pauseButton.waitForExistence(timeout: 2))
}
}
}
}

View File

@@ -0,0 +1,92 @@
//
// OnboardingUITests.swift
// GazeUITests
//
// Created by Mike Freno on 1/8/26.
//
import XCTest
@MainActor
final class OnboardingUITests: XCTestCase {
var app: XCUIApplication!
override func setUpWithError() throws {
continueAfterFailure = false
app = XCUIApplication()
app.launchArguments.append("--reset-onboarding")
app.launch()
}
override func tearDownWithError() throws {
app = nil
}
func testOnboardingWelcomeScreen() throws {
XCTAssertTrue(app.staticTexts["Welcome to Gaze"].exists || app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'Welcome'")).firstMatch.exists)
}
func testOnboardingNavigationFromWelcome() throws {
let continueButton = app.buttons["Continue"]
if continueButton.waitForExistence(timeout: 2) {
continueButton.tap()
let nextScreen = app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'Setup' OR label CONTAINS 'Configure'")).firstMatch
XCTAssertTrue(nextScreen.waitForExistence(timeout: 2))
}
}
func testOnboardingBackNavigation() throws {
let continueButton = app.buttons["Continue"]
if continueButton.waitForExistence(timeout: 2) {
continueButton.tap()
let backButton = app.buttons["Back"]
if backButton.waitForExistence(timeout: 1) {
backButton.tap()
XCTAssertTrue(app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'Welcome'")).firstMatch.waitForExistence(timeout: 1))
}
}
}
func testOnboardingCompleteFlow() throws {
let continueButtons = app.buttons.matching(identifier: "Continue")
let nextButtons = app.buttons.matching(identifier: "Next")
var currentStep = 0
let maxSteps = 10
while currentStep < maxSteps {
if continueButtons.firstMatch.exists && continueButtons.firstMatch.isHittable {
continueButtons.firstMatch.tap()
currentStep += 1
sleep(1)
} else if nextButtons.firstMatch.exists && nextButtons.firstMatch.isHittable {
nextButtons.firstMatch.tap()
currentStep += 1
sleep(1)
} else if app.buttons["Get Started"].exists {
app.buttons["Get Started"].tap()
break
} else if app.buttons["Done"].exists {
app.buttons["Done"].tap()
break
} else {
break
}
}
XCTAssertLessThan(currentStep, maxSteps, "Onboarding flow should complete")
}
func testOnboardingHasRequiredElements() throws {
let hasText = app.staticTexts.count > 0
let hasButtons = app.buttons.count > 0
XCTAssertTrue(hasText, "Onboarding should have text elements")
XCTAssertTrue(hasButtons, "Onboarding should have buttons")
}
}

View File

@@ -0,0 +1,88 @@
//
// PerformanceUITests.swift
// GazeUITests
//
// Created by Mike Freno on 1/8/26.
//
import XCTest
@MainActor
final class PerformanceUITests: XCTestCase {
var app: XCUIApplication!
override func setUpWithError() throws {
continueAfterFailure = false
app = XCUIApplication()
app.launchArguments.append("--skip-onboarding")
}
override func tearDownWithError() throws {
app = nil
}
func testAppLaunchPerformance() throws {
measure(metrics: [XCTApplicationLaunchMetric()]) {
app.launch()
app.terminate()
}
}
func testMenuBarOpenPerformance() throws {
app.launch()
measure {
let menuBar = app.menuBarItems.firstMatch
if menuBar.waitForExistence(timeout: 5) {
menuBar.click()
_ = app.staticTexts["Gaze"].waitForExistence(timeout: 2)
}
}
}
func testSettingsWindowOpenPerformance() throws {
app.launch()
let menuBar = app.menuBarItems.firstMatch
if menuBar.waitForExistence(timeout: 5) {
menuBar.click()
measure {
let settingsButton = app.menuItems["Settings"]
if settingsButton.waitForExistence(timeout: 2) {
settingsButton.click()
let settingsWindow = app.windows["Settings"]
_ = settingsWindow.waitForExistence(timeout: 3)
if settingsWindow.exists {
let closeButton = settingsWindow.buttons[XCUIIdentifierCloseWindow]
if closeButton.exists {
closeButton.click()
}
}
}
menuBar.click()
}
}
}
func testMemoryUsageDuringOperation() throws {
app.launch()
let menuBar = app.menuBarItems.firstMatch
if menuBar.waitForExistence(timeout: 5) {
measure(metrics: [XCTMemoryMetric()]) {
for _ in 0..<5 {
menuBar.click()
sleep(1)
menuBar.click()
sleep(1)
}
}
}
}
}

View File

@@ -0,0 +1,83 @@
//
// SettingsUITests.swift
// GazeUITests
//
// Created by Mike Freno on 1/8/26.
//
import XCTest
@MainActor
final class SettingsUITests: XCTestCase {
var app: XCUIApplication!
override func setUpWithError() throws {
continueAfterFailure = false
app = XCUIApplication()
app.launchArguments.append("--skip-onboarding")
app.launch()
}
override func tearDownWithError() throws {
app = nil
}
func testOpenSettingsWindow() throws {
let menuBar = app.menuBarItems.firstMatch
if menuBar.exists {
menuBar.click()
let settingsButton = app.menuItems["Settings"]
if settingsButton.waitForExistence(timeout: 2) {
settingsButton.click()
let settingsWindow = app.windows["Settings"]
XCTAssertTrue(settingsWindow.waitForExistence(timeout: 3))
}
}
}
func testSettingsWindowHasTimerControls() throws {
let menuBar = app.menuBarItems.firstMatch
if menuBar.exists {
menuBar.click()
let settingsButton = app.menuItems["Settings"]
if settingsButton.waitForExistence(timeout: 2) {
settingsButton.click()
sleep(1)
let hasSliders = app.sliders.count > 0
let hasTextFields = app.textFields.count > 0
let hasSwitches = app.switches.count > 0
let hasControls = hasSliders || hasTextFields || hasSwitches
XCTAssertTrue(hasControls, "Settings should have timer controls")
}
}
}
func testSettingsWindowCanBeClosed() throws {
let menuBar = app.menuBarItems.firstMatch
if menuBar.exists {
menuBar.click()
let settingsButton = app.menuItems["Settings"]
if settingsButton.waitForExistence(timeout: 2) {
settingsButton.click()
let settingsWindow = app.windows["Settings"]
if settingsWindow.waitForExistence(timeout: 3) {
let closeButton = settingsWindow.buttons[XCUIIdentifierCloseWindow]
if closeButton.exists {
closeButton.click()
XCTAssertFalse(settingsWindow.exists)
}
}
}
}
}
}