From 5ba6e6c0200e6c7edb5199ffd452761a653291b3 Mon Sep 17 00:00:00 2001 From: Trey t Date: Thu, 20 Nov 2025 23:07:29 -0600 Subject: [PATCH] Remove obsolete test files and unused HomeScreenView MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cleaned up old MyCribTests directory containing outdated unit tests that have been replaced by the comprehensive XCUITest suite in MyCribUITests. Also removed unused HomeScreenView that was replaced by RootView. Removed files: - iosApp/MyCribTests/*.swift: Old unit tests (11 files) - iosApp/iosApp/HomeScreenView.swift: Replaced by RootView The new XCUITest suite provides better coverage with end-to-end UI tests that validate actual user interactions rather than isolated unit tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../MyCribTests/AuthenticationUITests.swift | 269 ---------- .../ContractorViewModelTests.swift | 91 ---- iosApp/MyCribTests/DesignSystemTests.swift | 172 ------- .../MyCribTests/DocumentViewModelTests.swift | 134 ----- iosApp/MyCribTests/LoginViewModelTests.swift | 130 ----- iosApp/MyCribTests/MultiUserUITests.swift | 470 ------------------ iosApp/MyCribTests/ResidenceUITests.swift | 346 ------------- .../MyCribTests/ResidenceViewModelTests.swift | 60 --- iosApp/MyCribTests/TaskUITests.swift | 431 ---------------- iosApp/MyCribTests/TaskViewModelTests.swift | 118 ----- iosApp/MyCribTests/TestHelpers.swift | 180 ------- iosApp/iosApp/HomeScreenView.swift | 96 ---- 12 files changed, 2497 deletions(-) delete mode 100644 iosApp/MyCribTests/AuthenticationUITests.swift delete mode 100644 iosApp/MyCribTests/ContractorViewModelTests.swift delete mode 100644 iosApp/MyCribTests/DesignSystemTests.swift delete mode 100644 iosApp/MyCribTests/DocumentViewModelTests.swift delete mode 100644 iosApp/MyCribTests/LoginViewModelTests.swift delete mode 100644 iosApp/MyCribTests/MultiUserUITests.swift delete mode 100644 iosApp/MyCribTests/ResidenceUITests.swift delete mode 100644 iosApp/MyCribTests/ResidenceViewModelTests.swift delete mode 100644 iosApp/MyCribTests/TaskUITests.swift delete mode 100644 iosApp/MyCribTests/TaskViewModelTests.swift delete mode 100644 iosApp/MyCribTests/TestHelpers.swift delete mode 100644 iosApp/iosApp/HomeScreenView.swift diff --git a/iosApp/MyCribTests/AuthenticationUITests.swift b/iosApp/MyCribTests/AuthenticationUITests.swift deleted file mode 100644 index 001a225..0000000 --- a/iosApp/MyCribTests/AuthenticationUITests.swift +++ /dev/null @@ -1,269 +0,0 @@ -import XCTest - -/// Comprehensive tests for authentication flows -final class AuthenticationUITests: BaseUITest { - - // MARK: - Login Tests - - func testLoginWithValidCredentials() { - // Given: User is on login screen - XCTAssertTrue(app.staticTexts["MyCrib"].exists) - - // When: User enters valid credentials and taps login - login(username: "testuser", password: "TestPass123!") - - // Then: User should be navigated to main screen - let residencesTab = app.tabBars.buttons["Residences"] - XCTAssertTrue(residencesTab.waitForExistence(timeout: 10), "Should navigate to main tab view") - } - - func testLoginWithInvalidCredentials() { - // Given: User is on login screen - XCTAssertTrue(app.staticTexts["MyCrib"].exists) - - // When: User enters invalid credentials - login(username: "invaliduser", password: "WrongPassword!") - - // Then: Error message should be displayed - let errorMessage = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'Invalid username or password'")) - XCTAssertTrue(errorMessage.firstMatch.waitForExistence(timeout: 5), "Should show error message") - - // And: User should remain on login screen - XCTAssertTrue(app.staticTexts["MyCrib"].exists) - } - - func testLoginWithEmptyFields() { - // Given: User is on login screen - let loginButton = app.buttons["Login"] - - // When: User taps login without entering credentials - loginButton.tap() - - // Then: Validation error should be shown - let usernameError = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'Username is required'")) - XCTAssertTrue(usernameError.firstMatch.waitForExistence(timeout: 3), "Should show username required error") - } - - func testPasswordVisibilityToggle() { - // Given: User has typed password - let passwordField = app.secureTextFields["Password"] - let textField = app.textFields["Password"] - let toggleButton = app.buttons.matching(identifier: "eye").firstMatch - - passwordField.tap() - passwordField.typeText("TestPassword") - - // When: User taps the visibility toggle - toggleButton.tap() - - // Then: Password should be visible as text - XCTAssertTrue(textField.exists, "Password should be visible") - - // When: User taps toggle again - toggleButton.tap() - - // Then: Password should be hidden again - XCTAssertTrue(passwordField.exists, "Password should be secure") - } - - // MARK: - Registration Tests - - func testRegistrationWithValidData() { - // Given: User is on login screen - let signUpButton = app.buttons["Sign Up"] - - // When: User taps Sign Up - signUpButton.tap() - - // Then: Registration screen should be displayed - assertNavigatedTo(title: "Create Account", timeout: 3) - - // When: User fills in valid registration data - let timestamp = Int(Date().timeIntervalSince1970) - register( - username: "newuser\(timestamp)", - email: "newuser\(timestamp)@test.com", - password: "TestPass123!", - firstName: "Test", - lastName: "User" - ) - - // Then: User should be registered and shown verification screen - let verificationTitle = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'Verify'")) - XCTAssertTrue(verificationTitle.firstMatch.waitForExistence(timeout: 10), "Should show verification screen") - } - - func testRegistrationWithExistingUsername() { - // Given: User is on registration screen - let signUpButton = app.buttons["Sign Up"] - signUpButton.tap() - - // When: User registers with existing username - register( - username: "existinguser", - email: "newemail@test.com", - password: "TestPass123!" - ) - - // Then: Error message should be displayed - let errorMessage = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'username' OR label CONTAINS[c] 'already exists'")) - XCTAssertTrue(errorMessage.firstMatch.waitForExistence(timeout: 5), "Should show username exists error") - } - - func testRegistrationWithInvalidEmail() { - // Given: User is on registration screen - let signUpButton = app.buttons["Sign Up"] - signUpButton.tap() - - // When: User enters invalid email - let emailField = app.textFields["Email"] - let registerButton = app.buttons["Register"] - - emailField.tap() - emailField.typeText("invalidemail") - - registerButton.tap() - - // Then: Email validation error should be shown - let errorMessage = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'valid email'")) - XCTAssertTrue(errorMessage.firstMatch.waitForExistence(timeout: 3), "Should show email validation error") - } - - func testRegistrationWithWeakPassword() { - // Given: User is on registration screen - let signUpButton = app.buttons["Sign Up"] - signUpButton.tap() - - // When: User enters weak password - let timestamp = Int(Date().timeIntervalSince1970) - let usernameField = app.textFields["Username"] - let emailField = app.textFields["Email"] - let passwordField = app.secureTextFields["Password"] - let registerButton = app.buttons["Register"] - - usernameField.tap() - usernameField.typeText("testuser\(timestamp)") - - emailField.tap() - emailField.typeText("test\(timestamp)@test.com") - - passwordField.tap() - passwordField.typeText("weak") - - registerButton.tap() - - // Then: Password validation error should be shown - let errorMessage = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'password' AND (label CONTAINS[c] 'strong' OR label CONTAINS[c] 'at least')")) - XCTAssertTrue(errorMessage.firstMatch.waitForExistence(timeout: 3), "Should show password strength error") - } - - // MARK: - Logout Tests - - func testLogoutFlow() { - // Given: User is logged in - login(username: "testuser", password: "TestPass123!") - - let residencesTab = app.tabBars.buttons["Residences"] - XCTAssertTrue(residencesTab.waitForExistence(timeout: 10)) - - // When: User logs out - logout() - - // Then: User should be returned to login screen - XCTAssertTrue(app.staticTexts["MyCrib"].waitForExistence(timeout: 5), "Should return to login screen") - XCTAssertTrue(app.buttons["Login"].exists, "Login button should be visible") - } - - func testLogoutClearsUserData() { - // Given: User is logged in and has viewed some data - login(username: "testuser", password: "TestPass123!") - - let residencesTab = app.tabBars.buttons["Residences"] - XCTAssertTrue(residencesTab.waitForExistence(timeout: 10)) - - navigateToTab("Residences") - wait(seconds: 2) // Wait for data to load - - // When: User logs out - logout() - - // And: User logs back in - login(username: "testuser", password: "TestPass123!") - - // Then: Fresh data should be loaded (not cached) - let loadingIndicator = app.activityIndicators.firstMatch - XCTAssertTrue(loadingIndicator.exists || app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'Loading'")).firstMatch.exists, - "Should show loading state for fresh data") - } - - // MARK: - Session Management Tests - - func testSessionPersistence() { - // Given: User logs in - login(username: "testuser", password: "TestPass123!") - - let residencesTab = app.tabBars.buttons["Residences"] - XCTAssertTrue(residencesTab.waitForExistence(timeout: 10)) - - // When: App is terminated and relaunched - app.terminate() - app.launch() - - // Then: User should still be logged in - XCTAssertTrue(residencesTab.waitForExistence(timeout: 5), "User session should persist") - } - - func testLoginRedirectsVerifiedUser() { - // Given: Verified user logs in - login(username: "verifieduser", password: "TestPass123!") - - // Then: User should go directly to main screen (not verification) - let residencesTab = app.tabBars.buttons["Residences"] - XCTAssertTrue(residencesTab.waitForExistence(timeout: 10), "Verified user should skip verification") - } - - func testLoginRedirectsUnverifiedUser() { - // Given: Unverified user logs in - login(username: "unverifieduser", password: "TestPass123!") - - // Then: User should be shown verification screen - let verificationTitle = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'Verify'")) - XCTAssertTrue(verificationTitle.firstMatch.waitForExistence(timeout: 10), "Unverified user should see verification screen") - } - - // MARK: - Error Handling Tests - - func testLoginWithNetworkError() { - // Note: This test requires network simulation or mocking - // For now, it's a placeholder for future implementation - - // Given: Network is unavailable - // When: User attempts to login - // Then: Network error should be displayed - } - - func testLoginRetryAfterError() { - // Given: User encountered a login error - login(username: "invaliduser", password: "WrongPassword!") - - let errorMessage = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'Invalid'")) - XCTAssertTrue(errorMessage.firstMatch.waitForExistence(timeout: 5)) - - // When: User enters correct credentials - let usernameField = app.textFields["Username"] - let passwordField = app.secureTextFields["Password"] - let loginButton = app.buttons["Login"] - - app.clearText(in: usernameField) - usernameField.typeText("testuser") - - app.clearText(in: passwordField) - passwordField.typeText("TestPass123!") - - loginButton.tap() - - // Then: Login should succeed - let residencesTab = app.tabBars.buttons["Residences"] - XCTAssertTrue(residencesTab.waitForExistence(timeout: 10), "Should login successfully after retry") - } -} diff --git a/iosApp/MyCribTests/ContractorViewModelTests.swift b/iosApp/MyCribTests/ContractorViewModelTests.swift deleted file mode 100644 index 481e24e..0000000 --- a/iosApp/MyCribTests/ContractorViewModelTests.swift +++ /dev/null @@ -1,91 +0,0 @@ -import XCTest -@testable import iosApp -import ComposeApp - -@MainActor -final class ContractorViewModelTests: XCTestCase { - var sut: ContractorViewModel! - - override func setUp() { - super.setUp() - sut = ContractorViewModel() - } - - override func tearDown() { - sut = nil - super.tearDown() - } - - // MARK: - Initialization Tests - - func testInitialState() { - // Then - XCTAssertFalse(sut.isLoading) - XCTAssertNil(sut.errorMessage) - XCTAssertTrue(sut.contractors.isEmpty) - } - - // MARK: - Contractor Loading Tests - - func testLoadContractorsWithoutFilters() { - // When - sut.loadContractors() - - // Then - Should start loading or complete - XCTAssertTrue(sut.isLoading || sut.errorMessage != nil || !sut.contractors.isEmpty || (!sut.isLoading && sut.contractors.isEmpty)) - } - - func testLoadContractorsWithSpecialtyFilter() { - // When - sut.loadContractors(specialty: "Plumbing") - - // Then - Should not crash - XCTAssertNotNil(sut) - } - - func testLoadContractorsWithFavoriteFilter() { - // When - sut.loadContractors(isFavorite: true) - - // Then - Should not crash - XCTAssertNotNil(sut) - } - - func testLoadContractorsWithSearchQuery() { - // When - sut.loadContractors(search: "John") - - // Then - Should not crash - XCTAssertNotNil(sut) - } - - // MARK: - Toggle Favorite Tests - - func testToggleFavoriteWithValidId() { - // Given - let expectation = XCTestExpectation(description: "Toggle favorite callback") - var resultSuccess: Bool? - - // When - sut.toggleFavorite(id: 1) { success in - resultSuccess = success - expectation.fulfill() - } - - // Then - wait(for: [expectation], timeout: 5.0) - XCTAssertNotNil(resultSuccess) - } - - // MARK: - State Management Tests - - func testMultipleLoadCallsDontCrash() { - // When - sut.loadContractors() - sut.loadContractors(specialty: "Electrical") - sut.loadContractors(isFavorite: true) - - // Then - Should not crash - XCTAssertNotNil(sut) - } -} diff --git a/iosApp/MyCribTests/DesignSystemTests.swift b/iosApp/MyCribTests/DesignSystemTests.swift deleted file mode 100644 index 5102f97..0000000 --- a/iosApp/MyCribTests/DesignSystemTests.swift +++ /dev/null @@ -1,172 +0,0 @@ -import XCTest -@testable import iosApp -import SwiftUI - -final class DesignSystemTests: XCTestCase { - - // MARK: - Color Tests - - func testPrimaryColorsExist() { - // Then - XCTAssertNotNil(AppColors.primary) - XCTAssertNotNil(AppColors.primaryLight) - XCTAssertNotNil(AppColors.primaryDark) - } - - func testAccentColorsExist() { - // Then - XCTAssertNotNil(AppColors.accent) - XCTAssertNotNil(AppColors.accentLight) - } - - func testSemanticColorsExist() { - // Then - XCTAssertNotNil(AppColors.success) - XCTAssertNotNil(AppColors.warning) - XCTAssertNotNil(AppColors.error) - XCTAssertNotNil(AppColors.info) - } - - func testNeutralColorsExist() { - // Then - XCTAssertNotNil(AppColors.background) - XCTAssertNotNil(AppColors.surface) - XCTAssertNotNil(AppColors.surfaceSecondary) - XCTAssertNotNil(AppColors.textPrimary) - XCTAssertNotNil(AppColors.textSecondary) - XCTAssertNotNil(AppColors.textTertiary) - XCTAssertNotNil(AppColors.border) - XCTAssertNotNil(AppColors.borderLight) - } - - func testTaskStatusColorsExist() { - // Then - XCTAssertNotNil(AppColors.taskUpcoming) - XCTAssertNotNil(AppColors.taskInProgress) - XCTAssertNotNil(AppColors.taskCompleted) - XCTAssertNotNil(AppColors.taskCanceled) - XCTAssertNotNil(AppColors.taskArchived) - } - - func testGradientsExist() { - // Then - XCTAssertNotNil(AppColors.primaryGradient) - XCTAssertNotNil(AppColors.accentGradient) - } - - // MARK: - Typography Tests - - func testDisplayTypographyExists() { - // Then - XCTAssertNotNil(AppTypography.displayLarge) - XCTAssertNotNil(AppTypography.displayMedium) - XCTAssertNotNil(AppTypography.displaySmall) - } - - func testHeadlineTypographyExists() { - // Then - XCTAssertNotNil(AppTypography.headlineLarge) - XCTAssertNotNil(AppTypography.headlineMedium) - XCTAssertNotNil(AppTypography.headlineSmall) - } - - func testTitleTypographyExists() { - // Then - XCTAssertNotNil(AppTypography.titleLarge) - XCTAssertNotNil(AppTypography.titleMedium) - XCTAssertNotNil(AppTypography.titleSmall) - } - - func testBodyTypographyExists() { - // Then - XCTAssertNotNil(AppTypography.bodyLarge) - XCTAssertNotNil(AppTypography.bodyMedium) - XCTAssertNotNil(AppTypography.bodySmall) - } - - func testLabelTypographyExists() { - // Then - XCTAssertNotNil(AppTypography.labelLarge) - XCTAssertNotNil(AppTypography.labelMedium) - XCTAssertNotNil(AppTypography.labelSmall) - } - - // MARK: - Spacing Tests - - func testSpacingValuesAreCorrect() { - // Then - XCTAssertEqual(AppSpacing.xxs, 4) - XCTAssertEqual(AppSpacing.xs, 8) - XCTAssertEqual(AppSpacing.sm, 12) - XCTAssertEqual(AppSpacing.md, 16) - XCTAssertEqual(AppSpacing.lg, 24) - XCTAssertEqual(AppSpacing.xl, 32) - XCTAssertEqual(AppSpacing.xxl, 48) - XCTAssertEqual(AppSpacing.xxxl, 64) - } - - // MARK: - Radius Tests - - func testRadiusValuesAreCorrect() { - // Then - XCTAssertEqual(AppRadius.xs, 4) - XCTAssertEqual(AppRadius.sm, 8) - XCTAssertEqual(AppRadius.md, 12) - XCTAssertEqual(AppRadius.lg, 16) - XCTAssertEqual(AppRadius.xl, 20) - XCTAssertEqual(AppRadius.xxl, 24) - XCTAssertEqual(AppRadius.full, 9999) - } - - // MARK: - Shadow Tests - - func testShadowsExist() { - // Then - XCTAssertNotNil(AppShadow.sm) - XCTAssertNotNil(AppShadow.md) - XCTAssertNotNil(AppShadow.lg) - XCTAssertNotNil(AppShadow.xl) - } - - // MARK: - Color Extension Tests - - func testColorFromValidHexString() { - // When - let color = Color(hex: "FF0000") - - // Then - XCTAssertNotNil(color) - } - - func testColorFromInvalidHexString() { - // When - let color = Color(hex: "INVALID") - - // Then - XCTAssertNil(color) - } - - func testColorFrom3DigitHex() { - // When - let color = Color(hex: "F00") - - // Then - XCTAssertNotNil(color) - } - - func testColorFrom6DigitHex() { - // When - let color = Color(hex: "FF0000") - - // Then - XCTAssertNotNil(color) - } - - func testColorFrom8DigitHex() { - // When - let color = Color(hex: "FF0000FF") - - // Then - XCTAssertNotNil(color) - } -} diff --git a/iosApp/MyCribTests/DocumentViewModelTests.swift b/iosApp/MyCribTests/DocumentViewModelTests.swift deleted file mode 100644 index 842bfdb..0000000 --- a/iosApp/MyCribTests/DocumentViewModelTests.swift +++ /dev/null @@ -1,134 +0,0 @@ -import XCTest -@testable import iosApp -import ComposeApp - -@MainActor -final class DocumentViewModelTests: XCTestCase { - var sut: DocumentViewModel! - - override func setUp() { - super.setUp() - sut = DocumentViewModel() - } - - override func tearDown() { - sut = nil - super.tearDown() - } - - // MARK: - Initialization Tests - - func testInitialState() { - // Then - XCTAssertFalse(sut.isLoading) - XCTAssertNil(sut.errorMessage) - XCTAssertTrue(sut.documents.isEmpty) - } - - // MARK: - Document Loading Tests - - func testLoadDocumentsWithFilters() { - // When - sut.loadDocuments( - residenceId: 1, - documentType: "warranty", - isActive: true - ) - - // Then - Should start loading or complete - XCTAssertTrue(sut.isLoading || sut.errorMessage != nil || !sut.documents.isEmpty || (!sut.isLoading && sut.documents.isEmpty)) - } - - func testLoadDocumentsWithoutFilters() { - // When - sut.loadDocuments() - - // Then - Should not crash - XCTAssertNotNil(sut) - } - - // MARK: - Document Creation Tests - - func testCreateDocumentWithRequiredFields() { - // Given - let expectation = XCTestExpectation(description: "Create document callback") - var resultSuccess: Bool? - var resultError: String? - - // When - sut.createDocument( - title: "Test Document", - documentType: "warranty", - residenceId: 1 - ) { success, error in - resultSuccess = success - resultError = error - expectation.fulfill() - } - - // Then - wait(for: [expectation], timeout: 5.0) - XCTAssertNotNil(resultSuccess) - } - - func testCreateDocumentWithWarrantyFields() { - // Given - let expectation = XCTestExpectation(description: "Create warranty callback") - - // When - sut.createDocument( - title: "Test Warranty", - documentType: "warranty", - residenceId: 1, - itemName: "HVAC System", - provider: "ACME Corp" - ) { success, error in - expectation.fulfill() - } - - // Then - wait(for: [expectation], timeout: 5.0) - XCTAssertNotNil(sut) - } - - // MARK: - Document Update Tests - - func testUpdateDocumentWithValidId() { - // Given - let expectation = XCTestExpectation(description: "Update document callback") - - // When - sut.updateDocument( - id: 1, - title: "Updated Title" - ) { success, error in - expectation.fulfill() - } - - // Then - wait(for: [expectation], timeout: 5.0) - XCTAssertNotNil(sut) - } - - // MARK: - Document Deletion Tests - - func testDeleteDocumentWithValidId() { - // When - sut.deleteDocument(id: 1) - - // Then - Should not crash - XCTAssertNotNil(sut) - } - - // MARK: - State Management Tests - - func testMultipleOperationsDontCrash() { - // When - sut.loadDocuments() - sut.loadDocuments(documentType: "warranty") - sut.deleteDocument(id: 1) - - // Then - Should not crash - XCTAssertNotNil(sut) - } -} diff --git a/iosApp/MyCribTests/LoginViewModelTests.swift b/iosApp/MyCribTests/LoginViewModelTests.swift deleted file mode 100644 index d404b2d..0000000 --- a/iosApp/MyCribTests/LoginViewModelTests.swift +++ /dev/null @@ -1,130 +0,0 @@ -import XCTest -@testable import iosApp -import ComposeApp - -@MainActor -final class LoginViewModelTests: XCTestCase { - var sut: LoginViewModel! - - override func setUp() { - super.setUp() - sut = LoginViewModel() - } - - override func tearDown() { - sut = nil - super.tearDown() - } - - // MARK: - Initialization Tests - - func testInitialState() { - // Then - XCTAssertEqual(sut.username, "") - XCTAssertEqual(sut.password, "") - XCTAssertFalse(sut.isLoading) - XCTAssertNil(sut.errorMessage) - XCTAssertFalse(sut.isAuthenticated) - XCTAssertFalse(sut.isVerified) - XCTAssertNil(sut.currentUser) - } - - // MARK: - Validation Tests - - func testLoginWithEmptyUsername() { - // Given - sut.username = "" - sut.password = "password123" - - // When - sut.login() - - // Then - XCTAssertEqual(sut.errorMessage, "Username is required") - XCTAssertFalse(sut.isLoading) - } - - func testLoginWithEmptyPassword() { - // Given - sut.username = "testuser" - sut.password = "" - - // When - sut.login() - - // Then - XCTAssertEqual(sut.errorMessage, "Password is required") - XCTAssertFalse(sut.isLoading) - } - - func testLoginWithValidCredentials() { - // Given - sut.username = "testuser" - sut.password = "password123" - - // When - sut.login() - - // Then - XCTAssertTrue(sut.isLoading || sut.errorMessage != nil || sut.isAuthenticated) - } - - // MARK: - Error Handling Tests - - func testCleanErrorMessageRemovesJSONStructures() { - // Given - let dirtyMessage = "Error: {\"detail\": \"Invalid credentials\"}" - - // When - We can't directly test private method, but we can test the behavior - sut.errorMessage = dirtyMessage - - // Then - Error message should be set (even if not cleaned in this test) - XCTAssertNotNil(sut.errorMessage) - } - - func testClearError() { - // Given - sut.errorMessage = "Test error" - - // When - sut.clearError() - - // Then - XCTAssertNil(sut.errorMessage) - } - - // MARK: - Logout Tests - - func testLogout() { - // Given - sut.isAuthenticated = true - sut.isVerified = true - sut.username = "testuser" - sut.password = "password" - sut.errorMessage = "Test error" - - // When - sut.logout() - - // Then - XCTAssertFalse(sut.isAuthenticated) - XCTAssertFalse(sut.isVerified) - XCTAssertNil(sut.currentUser) - XCTAssertEqual(sut.username, "") - XCTAssertEqual(sut.password, "") - XCTAssertNil(sut.errorMessage) - } - - // MARK: - State Management Tests - - func testLoadingStateChanges() { - // Given - let initialLoadingState = sut.isLoading - - // When - sut.login() - - // Then - Loading state should change (either true during loading or false after quick failure) - XCTAssertTrue(sut.isLoading != initialLoadingState || sut.errorMessage != nil) - } -} diff --git a/iosApp/MyCribTests/MultiUserUITests.swift b/iosApp/MyCribTests/MultiUserUITests.swift deleted file mode 100644 index 12dc3ef..0000000 --- a/iosApp/MyCribTests/MultiUserUITests.swift +++ /dev/null @@ -1,470 +0,0 @@ -import XCTest - -/// Comprehensive tests for multi-user residence features -final class MultiUserUITests: BaseUITest { - - override func setUp() { - super.setUp() - // Login as primary owner - login(username: "testowner", password: "TestPass123!") - let residencesTab = app.tabBars.buttons["Residences"] - XCTAssertTrue(residencesTab.waitForExistence(timeout: 10)) - } - - // MARK: - Manage Users Tests - - func testManageUsersButtonVisibleForOwner() { - // Given: User owns a residence - navigateToTab("Residences") - wait(seconds: 2) - - let firstResidence = app.cells.firstMatch - if firstResidence.exists { - firstResidence.tap() - wait(seconds: 2) - - // Then: Manage users button should be visible - let manageUsersButton = app.navigationBars.buttons.matching(identifier: "person.2").firstMatch - XCTAssertTrue(manageUsersButton.exists, "Owner should see manage users button") - } - } - - func testManageUsersButtonHiddenForSharedUser() { - // Given: User is a shared user (not owner) - logout() - login(username: "shareduser", password: "TestPass123!") - - navigateToTab("Residences") - wait(seconds: 2) - - // Find a shared residence - let sharedResidence = app.cells.firstMatch - if sharedResidence.exists { - sharedResidence.tap() - wait(seconds: 2) - - // Then: Manage users button should NOT be visible - let manageUsersButton = app.navigationBars.buttons.matching(identifier: "person.2").firstMatch - XCTAssertFalse(manageUsersButton.exists, "Shared user should not see manage users button") - } - } - - func testOpenManageUsersScreen() { - // Given: Owner is viewing a residence - navigateToTab("Residences") - wait(seconds: 2) - - let firstResidence = app.cells.firstMatch - if firstResidence.exists { - firstResidence.tap() - wait(seconds: 2) - - // When: Owner taps manage users button - let manageUsersButton = app.navigationBars.buttons.matching(identifier: "person.2").firstMatch - if manageUsersButton.exists { - manageUsersButton.tap() - wait(seconds: 1) - - // Then: Manage users screen should be displayed - let manageUsersTitle = app.navigationBars["Manage Users"] - XCTAssertTrue(manageUsersTitle.waitForExistence(timeout: 3), "Should show manage users screen") - - // And: User list should be visible - let usersList = app.scrollViews.firstMatch - XCTAssertTrue(usersList.exists, "Should show users list") - } - } - } - - // MARK: - Share Code Tests - - func testShareCodeInitiallyBlank() { - // Given: Owner opens manage users screen - navigateToTab("Residences") - wait(seconds: 2) - - let firstResidence = app.cells.firstMatch - if firstResidence.exists { - firstResidence.tap() - wait(seconds: 2) - - let manageUsersButton = app.navigationBars.buttons.matching(identifier: "person.2").firstMatch - if manageUsersButton.exists { - manageUsersButton.tap() - wait(seconds: 1) - - // Then: Share code should be blank initially - let noActiveCode = app.staticTexts["No active code"] - XCTAssertTrue(noActiveCode.exists, "Share code should start blank") - } - } - } - - func testGenerateShareCode() { - // Given: Owner is on manage users screen - navigateToTab("Residences") - wait(seconds: 2) - - let firstResidence = app.cells.firstMatch - if firstResidence.exists { - firstResidence.tap() - wait(seconds: 2) - - let manageUsersButton = app.navigationBars.buttons.matching(identifier: "person.2").firstMatch - if manageUsersButton.exists { - manageUsersButton.tap() - wait(seconds: 1) - - // When: Owner taps generate code button - let generateButton = app.buttons["Generate"] - if !generateButton.exists { - // Button might say "New Code" if there's an existing code - let newCodeButton = app.buttons["New Code"] - if newCodeButton.exists { - newCodeButton.tap() - } - } else { - generateButton.tap() - } - - // Then: Share code should be generated and displayed - wait(seconds: 2) - let shareCodeTexts = app.staticTexts.matching(NSPredicate(format: "label.length == 6 AND label MATCHES %@", "[A-Z0-9]{6}")) - XCTAssertTrue(shareCodeTexts.count > 0, "Should display 6-character share code") - } - } - } - - func testRegenerateShareCode() { - // Given: Owner has generated a share code - navigateToTab("Residences") - wait(seconds: 2) - - let firstResidence = app.cells.firstMatch - if firstResidence.exists { - firstResidence.tap() - wait(seconds: 2) - - let manageUsersButton = app.navigationBars.buttons.matching(identifier: "person.2").firstMatch - if manageUsersButton.exists { - manageUsersButton.tap() - wait(seconds: 1) - - // Generate first code - let generateButton = app.buttons["Generate"] - if generateButton.exists { - generateButton.tap() - wait(seconds: 2) - - let firstCode = app.staticTexts.matching(NSPredicate(format: "label.length == 6 AND label MATCHES %@", "[A-Z0-9]{6}")).firstMatch.label - - // When: Owner generates new code - let newCodeButton = app.buttons["New Code"] - if newCodeButton.exists { - newCodeButton.tap() - wait(seconds: 2) - - // Then: A different code should be generated - let secondCode = app.staticTexts.matching(NSPredicate(format: "label.length == 6 AND label MATCHES %@", "[A-Z0-9]{6}")).firstMatch.label - XCTAssertNotEqual(firstCode, secondCode, "New code should be different") - } - } - } - } - } - - // MARK: - Join Residence Tests - - func testJoinResidenceWithValidCode() { - // This test requires coordination between two accounts - // Given: Owner generates a share code - var shareCode: String = "" - - navigateToTab("Residences") - wait(seconds: 2) - - let firstResidence = app.cells.firstMatch - if firstResidence.exists { - firstResidence.tap() - wait(seconds: 2) - - let manageUsersButton = app.navigationBars.buttons.matching(identifier: "person.2").firstMatch - if manageUsersButton.exists { - manageUsersButton.tap() - wait(seconds: 1) - - let generateButton = app.buttons["Generate"] - if generateButton.exists { - generateButton.tap() - wait(seconds: 2) - - shareCode = app.staticTexts.matching(NSPredicate(format: "label.length == 6 AND label MATCHES %@", "[A-Z0-9]{6}")).firstMatch.label - } - - // Close manage users screen - let closeButton = app.buttons["Close"] - if closeButton.exists { - closeButton.tap() - } - } - } - - // When: Different user joins with code - if !shareCode.isEmpty { - logout() - login(username: "newuser", password: "TestPass123!") - - navigateToTab("Residences") - wait(seconds: 1) - - // Find join residence button - let joinButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Join'")).firstMatch - if joinButton.exists { - joinButton.tap() - wait(seconds: 1) - - // Enter share code - let codeField = app.textFields.firstMatch - if codeField.exists { - codeField.tap() - codeField.typeText(shareCode) - - // Submit - let submitButton = app.buttons["Join"] - if submitButton.exists { - submitButton.tap() - wait(seconds: 2) - - // Then: User should be added to residence - let successMessage = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'Successfully joined'")) - XCTAssertTrue(successMessage.firstMatch.exists || app.cells.count > 0, - "Should join residence successfully") - } - } - } - } - } - - func testJoinResidenceWithInvalidCode() { - // Given: User is on join residence screen - navigateToTab("Residences") - - let joinButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Join'")).firstMatch - if joinButton.exists { - joinButton.tap() - wait(seconds: 1) - - // When: User enters invalid code - let codeField = app.textFields.firstMatch - if codeField.exists { - codeField.tap() - codeField.typeText("INVALID") - - let submitButton = app.buttons["Join"] - if submitButton.exists { - submitButton.tap() - wait(seconds: 2) - - // Then: Error message should be shown - let errorMessage = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'Invalid' OR label CONTAINS[c] 'not found'")) - XCTAssertTrue(errorMessage.firstMatch.exists, "Should show invalid code error") - } - } - } - } - - // MARK: - User List Tests - - func testUserListShowsAllUsers() { - // Given: Residence has multiple users - navigateToTab("Residences") - wait(seconds: 2) - - let firstResidence = app.cells.firstMatch - if firstResidence.exists { - firstResidence.tap() - wait(seconds: 2) - - let manageUsersButton = app.navigationBars.buttons.matching(identifier: "person.2").firstMatch - if manageUsersButton.exists { - manageUsersButton.tap() - wait(seconds: 1) - - // Then: User count should be displayed - let userCountLabel = app.staticTexts.matching(NSPredicate(format: "label CONTAINS[c] 'Users'")) - XCTAssertTrue(userCountLabel.count > 0, "Should show user count") - - // And: Individual users should be listed - let usersList = app.scrollViews.firstMatch - XCTAssertTrue(usersList.exists, "Should show users list") - } - } - } - - func testOwnerLabelDisplayed() { - // Given: Owner is viewing manage users screen - navigateToTab("Residences") - wait(seconds: 2) - - let firstResidence = app.cells.firstMatch - if firstResidence.exists { - firstResidence.tap() - wait(seconds: 2) - - let manageUsersButton = app.navigationBars.buttons.matching(identifier: "person.2").firstMatch - if manageUsersButton.exists { - manageUsersButton.tap() - wait(seconds: 1) - - // Then: Owner badge should be visible next to owner's name - let ownerBadge = app.staticTexts["Owner"] - XCTAssertTrue(ownerBadge.exists, "Should show Owner badge") - } - } - } - - // MARK: - Remove User Tests - - func testRemoveUserAsOwner() { - // Given: Owner has residence with multiple users - navigateToTab("Residences") - wait(seconds: 2) - - let firstResidence = app.cells.firstMatch - if firstResidence.exists { - firstResidence.tap() - wait(seconds: 2) - - let manageUsersButton = app.navigationBars.buttons.matching(identifier: "person.2").firstMatch - if manageUsersButton.exists { - manageUsersButton.tap() - wait(seconds: 1) - - // When: Owner taps remove button on a user - let removeButtons = app.buttons.matching(identifier: "trash") - if removeButtons.count > 0 { - let initialUserCount = removeButtons.count - removeButtons.firstMatch.tap() - - // Confirm removal if prompted - let confirmButton = app.alerts.buttons["Remove"] - if confirmButton.exists { - confirmButton.tap() - } - - // Then: User should be removed - wait(seconds: 2) - let newUserCount = app.buttons.matching(identifier: "trash").count - XCTAssertTrue(newUserCount < initialUserCount, "Should remove user") - } - } - } - } - - func testCannotRemoveOwner() { - // Given: Owner is viewing manage users - navigateToTab("Residences") - wait(seconds: 2) - - let firstResidence = app.cells.firstMatch - if firstResidence.exists { - firstResidence.tap() - wait(seconds: 2) - - let manageUsersButton = app.navigationBars.buttons.matching(identifier: "person.2").firstMatch - if manageUsersButton.exists { - manageUsersButton.tap() - wait(seconds: 1) - - // Then: Owner row should NOT have remove button - let ownerLabel = app.staticTexts["Owner"] - if ownerLabel.exists { - // Check if there's a remove button in the same container - // Owner should not have a remove button next to their name - } - } - } - } - - // MARK: - Shared User Access Tests - - func testSharedUserCanViewResidence() { - // Given: User is a shared user - logout() - login(username: "shareduser", password: "TestPass123!") - - // Then: Shared residences should appear in list - navigateToTab("Residences") - wait(seconds: 2) - - let residencesList = app.cells.count - XCTAssertTrue(residencesList > 0, "Shared user should see shared residences") - } - - func testSharedUserCanCreateTasks() { - // Given: Shared user is viewing a shared residence - logout() - login(username: "shareduser", password: "TestPass123!") - - navigateToTab("Residences") - wait(seconds: 2) - - let firstResidence = app.cells.firstMatch - if firstResidence.exists { - firstResidence.tap() - wait(seconds: 2) - - // When: Shared user tries to create a task - let addButton = app.navigationBars.buttons.matching(identifier: "plus").firstMatch - XCTAssertTrue(addButton.exists, "Shared user should be able to add tasks") - - if addButton.exists { - addButton.tap() - wait(seconds: 1) - - // Then: Add task form should be displayed - let titleField = app.textFields.firstMatch - XCTAssertTrue(titleField.exists, "Shared user should be able to create tasks") - } - } - } - - func testSharedUserCanEditTasks() { - // Given: Shared user is viewing tasks - logout() - login(username: "shareduser", password: "TestPass123!") - - navigateToTab("Tasks") - wait(seconds: 2) - - // When: Shared user tries to edit a task - let editButtons = app.buttons.matching(identifier: "pencil") - if editButtons.count > 0 { - // Then: Edit buttons should be available - XCTAssertTrue(editButtons.firstMatch.exists, "Shared user should be able to edit tasks") - } - } - - func testUserCountDisplayed() { - // Given: Owner is viewing a residence - navigateToTab("Residences") - wait(seconds: 2) - - let firstResidence = app.cells.firstMatch - if firstResidence.exists { - firstResidence.tap() - wait(seconds: 2) - - // Then: User count should be visible somewhere on the screen - // This depends on your UI design - adjust as needed - let manageUsersButton = app.navigationBars.buttons.matching(identifier: "person.2").firstMatch - if manageUsersButton.exists { - manageUsersButton.tap() - wait(seconds: 1) - - let userCountText = app.staticTexts.matching(NSPredicate(format: "label CONTAINS[c] 'Users'")) - XCTAssertTrue(userCountText.count > 0, "Should display user count") - } - } - } -} diff --git a/iosApp/MyCribTests/ResidenceUITests.swift b/iosApp/MyCribTests/ResidenceUITests.swift deleted file mode 100644 index 7c74c50..0000000 --- a/iosApp/MyCribTests/ResidenceUITests.swift +++ /dev/null @@ -1,346 +0,0 @@ -import XCTest - -/// Comprehensive tests for residence management -final class ResidenceUITests: BaseUITest { - - override func setUp() { - super.setUp() - // Login before each test - login(username: "testuser", password: "TestPass123!") - let residencesTab = app.tabBars.buttons["Residences"] - XCTAssertTrue(residencesTab.waitForExistence(timeout: 10)) - } - - // MARK: - List View Tests - - func testResidenceListDisplays() { - // Given: User is on residences tab - navigateToTab("Residences") - - // Then: Residences list should be displayed - let navigationBar = app.navigationBars["Residences"] - XCTAssertTrue(navigationBar.exists, "Should show residences navigation bar") - } - - func testResidenceListShowsProperties() { - // Given: User has residences - navigateToTab("Residences") - - // Then: Residence cards should be visible - let residenceCards = app.scrollViews.descendants(matching: .other).matching(NSPredicate(format: "identifier CONTAINS 'ResidenceCard'")) - XCTAssertTrue(residenceCards.count > 0 || app.staticTexts["No residences yet"].exists, - "Should show either residence cards or empty state") - } - - func testEmptyStateDisplays() { - // Given: User has no residences (requires test account with no data) - navigateToTab("Residences") - - // Then: Empty state should be shown - // Note: This test assumes test user has no residences - let emptyStateText = app.staticTexts["No residences yet"] - let addButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Add'")).firstMatch - - // Should show either residences or empty state with add button - XCTAssertTrue(emptyStateText.exists || app.cells.count > 0, "Should show content or empty state") - } - - // MARK: - Create Residence Tests - - func testCreateResidenceFlow() { - // Given: User is on residences screen - navigateToTab("Residences") - - // When: User taps add residence button - let addButton = app.navigationBars.buttons.matching(identifier: "plus").firstMatch - if !addButton.exists { - // Try finding add button in other locations - let fabButton = app.buttons["Add Residence"] - if fabButton.exists { - fabButton.tap() - } else { - XCTFail("Could not find add residence button") - } - } else { - addButton.tap() - } - - // Then: Add residence form should be displayed - wait(seconds: 1) - let nameField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'name' OR label CONTAINS[c] 'Name'")).firstMatch - XCTAssertTrue(nameField.exists, "Should show residence name field") - - // When: User fills in residence details - let timestamp = Int(Date().timeIntervalSince1970) - nameField.tap() - nameField.typeText("Test House \(timestamp)") - - let addressField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'address' OR label CONTAINS[c] 'Address'")).firstMatch - if addressField.exists { - addressField.tap() - addressField.typeText("123 Test Street") - } - - let cityField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'city' OR label CONTAINS[c] 'City'")).firstMatch - if cityField.exists { - cityField.tap() - cityField.typeText("Test City") - } - - // When: User saves the residence - let saveButton = app.buttons["Save"] - if saveButton.exists { - saveButton.tap() - } - - // Then: User should be returned to list view - // And: New residence should appear in the list - wait(seconds: 2) - let residenceTitle = app.staticTexts["Test House \(timestamp)"] - XCTAssertTrue(residenceTitle.waitForExistence(timeout: 5) || app.navigationBars["Residences"].exists, - "Should show new residence or navigate back to list") - } - - func testCreateResidenceValidation() { - // Given: User is on add residence screen - navigateToTab("Residences") - let addButton = app.navigationBars.buttons.matching(identifier: "plus").firstMatch - addButton.tap() - - wait(seconds: 1) - - // When: User attempts to save without required fields - let saveButton = app.buttons["Save"] - if saveButton.exists { - saveButton.tap() - } - - // Then: Validation errors should be shown - let errorMessages = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'required'")) - XCTAssertTrue(errorMessages.count > 0 || !app.navigationBars["Residences"].exists, - "Should show validation errors or prevent saving") - } - - // MARK: - View Residence Details Tests - - func testViewResidenceDetails() { - // Given: User has residences - navigateToTab("Residences") - wait(seconds: 2) - - // When: User taps on a residence - let firstResidence = app.cells.firstMatch - if firstResidence.exists { - firstResidence.tap() - - // Then: Residence details should be displayed - wait(seconds: 2) - let detailView = app.scrollViews.firstMatch - XCTAssertTrue(detailView.exists || app.navigationBars.element(boundBy: 1).exists, - "Should show residence details") - } - } - - func testResidenceDetailsShowsAllSections() { - // Given: User is viewing residence details - navigateToTab("Residences") - wait(seconds: 2) - - let firstResidence = app.cells.firstMatch - if firstResidence.exists { - firstResidence.tap() - wait(seconds: 2) - - // Then: All sections should be visible (after scrolling) - let scrollView = app.scrollViews.firstMatch - if scrollView.exists { - // Check for address section - let addressSection = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'Address'")).firstMatch - - // Check for tasks section - scrollView.swipeUp() - let tasksSection = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'Tasks'")).firstMatch - - XCTAssertTrue(addressSection.exists || tasksSection.exists, "Should show residence sections") - } - } - } - - // MARK: - Edit Residence Tests - - func testEditResidenceFlow() { - // Given: User is viewing residence details - navigateToTab("Residences") - wait(seconds: 2) - - let firstResidence = app.cells.firstMatch - if firstResidence.exists { - firstResidence.tap() - wait(seconds: 2) - - // When: User taps edit button - let editButton = app.navigationBars.buttons["Edit"] - if editButton.exists { - editButton.tap() - wait(seconds: 1) - - // Then: Edit form should be displayed - let nameField = app.textFields.containing(NSPredicate(format: "value != nil AND value != ''")).firstMatch - XCTAssertTrue(nameField.exists, "Should show edit form with current values") - - // When: User updates the name - if nameField.exists { - app.clearText(in: nameField) - nameField.typeText("Updated House Name") - - // When: User saves changes - let saveButton = app.buttons["Save"] - if saveButton.exists { - saveButton.tap() - } - - // Then: Changes should be saved and details view updated - wait(seconds: 2) - let updatedName = app.staticTexts["Updated House Name"] - XCTAssertTrue(updatedName.waitForExistence(timeout: 5) || app.navigationBars.element(boundBy: 1).exists, - "Should show updated residence name or details view") - } - } - } - } - - func testCancelEditingResidence() { - // Given: User is editing a residence - navigateToTab("Residences") - wait(seconds: 2) - - let firstResidence = app.cells.firstMatch - if firstResidence.exists { - firstResidence.tap() - wait(seconds: 2) - - let editButton = app.navigationBars.buttons["Edit"] - if editButton.exists { - editButton.tap() - wait(seconds: 1) - - // When: User makes changes - let nameField = app.textFields.firstMatch - let originalValue = nameField.value as? String - - if nameField.exists { - app.clearText(in: nameField) - nameField.typeText("Temporary Change") - - // When: User cancels - let cancelButton = app.buttons["Cancel"] - if cancelButton.exists { - cancelButton.tap() - } - - // Then: Changes should be discarded - wait(seconds: 1) - if let original = originalValue { - let originalText = app.staticTexts[original] - XCTAssertTrue(originalText.exists || app.navigationBars.element(boundBy: 1).exists, - "Should discard changes") - } - } - } - } - } - - // MARK: - Delete Residence Tests - - func testDeleteResidence() { - // Given: User has a residence to delete - navigateToTab("Residences") - wait(seconds: 2) - - let initialResidenceCount = app.cells.count - - // When: User swipes to delete (if supported) - let firstResidence = app.cells.firstMatch - if firstResidence.exists { - firstResidence.swipeLeft() - - let deleteButton = app.buttons["Delete"] - if deleteButton.exists { - deleteButton.tap() - - // Confirm deletion if alert appears - let confirmButton = app.alerts.buttons["Delete"] - if confirmButton.exists { - confirmButton.tap() - } - - // Then: Residence should be removed - wait(seconds: 2) - let newCount = app.cells.count - XCTAssertTrue(newCount < initialResidenceCount || app.staticTexts["No residences yet"].exists, - "Should remove residence from list") - } - } - } - - // MARK: - Navigation Tests - - func testNavigateBackFromDetails() { - // Given: User is viewing residence details - navigateToTab("Residences") - wait(seconds: 2) - - let firstResidence = app.cells.firstMatch - if firstResidence.exists { - firstResidence.tap() - wait(seconds: 2) - - // When: User taps back button - navigateBack() - - // Then: User should return to residences list - let navigationBar = app.navigationBars["Residences"] - XCTAssertTrue(navigationBar.waitForExistence(timeout: 3), "Should navigate back to residences list") - } - } - - // MARK: - Search and Filter Tests (if implemented) - - func testSearchResidences() { - // Given: User has multiple residences - navigateToTab("Residences") - wait(seconds: 2) - - // When: User searches for a residence - let searchField = app.searchFields.firstMatch - if searchField.exists { - searchField.tap() - searchField.typeText("Test") - - // Then: Results should be filtered - wait(seconds: 1) - let visibleResidences = app.cells.count - XCTAssertTrue(visibleResidences >= 0, "Should show filtered results") - } - } - - // MARK: - Pull to Refresh Tests - - func testPullToRefreshResidences() { - // Given: User is on residences list - navigateToTab("Residences") - wait(seconds: 2) - - // When: User pulls down to refresh - let scrollView = app.scrollViews.firstMatch - if scrollView.exists { - let startPoint = scrollView.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.2)) - let endPoint = scrollView.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.8)) - startPoint.press(forDuration: 0.1, thenDragTo: endPoint) - - // Then: Loading indicator should appear - let loadingIndicator = app.activityIndicators.firstMatch - XCTAssertTrue(loadingIndicator.exists || scrollView.exists, "Should trigger refresh") - } - } -} diff --git a/iosApp/MyCribTests/ResidenceViewModelTests.swift b/iosApp/MyCribTests/ResidenceViewModelTests.swift deleted file mode 100644 index 0f53d8a..0000000 --- a/iosApp/MyCribTests/ResidenceViewModelTests.swift +++ /dev/null @@ -1,60 +0,0 @@ -import XCTest -@testable import iosApp -import ComposeApp - -@MainActor -final class ResidenceViewModelTests: XCTestCase { - var sut: ResidenceViewModel! - - override func setUp() { - super.setUp() - sut = ResidenceViewModel() - } - - override func tearDown() { - sut = nil - super.tearDown() - } - - // MARK: - Initialization Tests - - func testInitialState() { - // Then - XCTAssertFalse(sut.isLoading) - XCTAssertNil(sut.errorMessage) - XCTAssertNil(sut.myResidences) - } - - // MARK: - Loading Tests - - func testLoadMyResidencesStartsLoading() { - // Given - let initialLoadingState = sut.isLoading - - // When - sut.loadMyResidences() - - // Then - Loading should start or complete quickly - XCTAssertTrue(sut.isLoading || sut.errorMessage != nil || sut.myResidences != nil) - } - - // MARK: - Error Handling Tests - - func testErrorMessageIsSetOnFailure() { - // This test would require mocking the API - // For now, we test that error handling mechanism exists - XCTAssertNil(sut.errorMessage) - } - - // MARK: - State Management Tests - - func testMultipleLoadCallsDontCrash() { - // When - sut.loadMyResidences() - sut.loadMyResidences() - sut.loadMyResidences() - - // Then - Should not crash - XCTAssertNotNil(sut) - } -} diff --git a/iosApp/MyCribTests/TaskUITests.swift b/iosApp/MyCribTests/TaskUITests.swift deleted file mode 100644 index 1603f33..0000000 --- a/iosApp/MyCribTests/TaskUITests.swift +++ /dev/null @@ -1,431 +0,0 @@ -import XCTest - -/// Comprehensive tests for task management -final class TaskUITests: BaseUITest { - - override func setUp() { - super.setUp() - // Login before each test - login(username: "testuser", password: "TestPass123!") - let residencesTab = app.tabBars.buttons["Residences"] - XCTAssertTrue(residencesTab.waitForExistence(timeout: 10)) - } - - // MARK: - Task List Tests - - func testTasksTabDisplays() { - // When: User navigates to tasks tab - navigateToTab("Tasks") - - // Then: Tasks screen should be displayed - let navigationBar = app.navigationBars["All Tasks"] - XCTAssertTrue(navigationBar.waitForExistence(timeout: 5), "Should show tasks navigation bar") - } - - func testTaskColumnsDisplay() { - // Given: User is on tasks tab - navigateToTab("Tasks") - wait(seconds: 2) - - // Then: Task columns should be visible - let upcomingLabel = app.staticTexts["Upcoming"] - let inProgressLabel = app.staticTexts["In Progress"] - let doneLabel = app.staticTexts["Done"] - - XCTAssertTrue(upcomingLabel.exists || inProgressLabel.exists || doneLabel.exists || - app.staticTexts["No tasks yet"].exists, - "Should show task columns or empty state") - } - - func testEmptyTasksState() { - // Given: User has no tasks (requires account with no tasks) - navigateToTab("Tasks") - wait(seconds: 2) - - // Then: Empty state should be shown - let emptyState = app.staticTexts["No tasks yet"] - let addButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Add Task'")).firstMatch - - // Should show either tasks or empty state - XCTAssertTrue(emptyState.exists || app.scrollViews.firstMatch.exists, - "Should show content or empty state") - } - - // MARK: - Create Task Tests - - func testCreateTaskFromTasksTab() { - // Given: User is on tasks tab - navigateToTab("Tasks") - - // When: User taps add task button - let addButton = app.navigationBars.buttons.matching(identifier: "plus").firstMatch - if addButton.exists { - addButton.tap() - wait(seconds: 1) - - // Then: Add task form should be displayed - let titleField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'title' OR label CONTAINS[c] 'Title'")).firstMatch - XCTAssertTrue(titleField.exists, "Should show add task form") - - // When: User fills in task details - let timestamp = Int(Date().timeIntervalSince1970) - titleField.tap() - titleField.typeText("Test Task \(timestamp)") - - let descriptionField = app.textViews.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'description' OR label CONTAINS[c] 'Description'")).firstMatch - if descriptionField.exists { - descriptionField.tap() - descriptionField.typeText("Test task description") - } - - // When: User saves the task - let saveButton = app.buttons["Save"] - if saveButton.exists { - saveButton.tap() - } - - // Then: Task should be created and user returned to tasks list - wait(seconds: 2) - XCTAssertTrue(app.navigationBars["All Tasks"].exists || app.staticTexts["Test Task \(timestamp)"].exists, - "Should create task and return to list") - } - } - - func testCreateTaskFromResidenceDetails() { - // Given: User is viewing a residence - navigateToTab("Residences") - wait(seconds: 2) - - let firstResidence = app.cells.firstMatch - if firstResidence.exists { - firstResidence.tap() - wait(seconds: 2) - - // When: User taps add task button - let addButton = app.navigationBars.buttons.matching(identifier: "plus").firstMatch - if addButton.exists { - addButton.tap() - wait(seconds: 1) - - // Then: Add task form should be displayed - let titleField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'title' OR label CONTAINS[c] 'Title'")).firstMatch - XCTAssertTrue(titleField.exists, "Should show add task form from residence") - - // When: User creates the task - let timestamp = Int(Date().timeIntervalSince1970) - titleField.tap() - titleField.typeText("Residence Task \(timestamp)") - - let saveButton = app.buttons["Save"] - if saveButton.exists { - saveButton.tap() - } - - // Then: Task should be created - wait(seconds: 2) - XCTAssertTrue(app.staticTexts["Residence Task \(timestamp)"].exists || app.navigationBars.element(boundBy: 1).exists, - "Should create task for residence") - } - } - } - - func testCreateTaskValidation() { - // Given: User is on add task form - navigateToTab("Tasks") - let addButton = app.navigationBars.buttons.matching(identifier: "plus").firstMatch - if addButton.exists { - addButton.tap() - wait(seconds: 1) - - // When: User tries to save without required fields - let saveButton = app.buttons["Save"] - if saveButton.exists { - saveButton.tap() - } - - // Then: Validation errors should be shown - let errorMessages = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'required'")) - XCTAssertTrue(errorMessages.count > 0 || !app.navigationBars["All Tasks"].exists, - "Should show validation errors") - } - } - - // MARK: - View Task Details Tests - - func testViewTaskDetails() { - // Given: User has tasks - navigateToTab("Tasks") - wait(seconds: 2) - - // When: User taps on a task card - let taskCard = app.otherElements.containing(NSPredicate(format: "identifier CONTAINS 'TaskCard'")).firstMatch - if !taskCard.exists { - // Try finding by task title - let taskTitle = app.staticTexts.matching(NSPredicate(format: "label CONTAINS[c] 'task'")).firstMatch - if taskTitle.exists { - taskTitle.tap() - } - } else { - taskCard.tap() - } - - // Note: Depending on implementation, tapping a task might show details or edit form - wait(seconds: 1) - // Verify some form of task interaction occurred - } - - // MARK: - Edit Task Tests - - func testEditTaskFlow() { - // Given: User is viewing/editing a task - navigateToTab("Tasks") - wait(seconds: 2) - - // Find and tap edit button on a task - let editButtons = app.buttons.matching(identifier: "pencil") - if editButtons.count > 0 { - editButtons.firstMatch.tap() - wait(seconds: 1) - - // When: User modifies task details - let titleField = app.textFields.containing(NSPredicate(format: "value != nil AND value != ''")).firstMatch - if titleField.exists { - app.clearText(in: titleField) - titleField.typeText("Updated Task Title") - - // When: User saves changes - let saveButton = app.buttons["Save"] - if saveButton.exists { - saveButton.tap() - } - - // Then: Changes should be saved - wait(seconds: 2) - let updatedTitle = app.staticTexts["Updated Task Title"] - XCTAssertTrue(updatedTitle.waitForExistence(timeout: 5) || app.navigationBars["All Tasks"].exists, - "Should save task changes") - } - } - } - - // MARK: - Complete Task Tests - - func testCompleteTaskFlow() { - // Given: User has an incomplete task - navigateToTab("Tasks") - wait(seconds: 2) - - // When: User taps complete button on a task - let completeButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Complete'")).firstMatch - if completeButton.exists { - completeButton.tap() - wait(seconds: 1) - - // Then: Complete task dialog should be shown - let completionDialog = app.sheets.firstMatch - XCTAssertTrue(completionDialog.exists || app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'Complete'")).firstMatch.exists, - "Should show completion dialog") - - // When: User confirms completion - let confirmButton = app.buttons["Complete"] - if confirmButton.exists { - confirmButton.tap() - } - - // Then: Task should be marked as complete - wait(seconds: 2) - // Task should move to completed column or show completion status - } - } - - func testCompleteTaskWithDetails() { - // Given: User is completing a task - navigateToTab("Tasks") - wait(seconds: 2) - - let completeButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Complete'")).firstMatch - if completeButton.exists { - completeButton.tap() - wait(seconds: 1) - - // When: User adds completion details - let notesField = app.textViews.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'notes' OR label CONTAINS[c] 'Notes'")).firstMatch - if notesField.exists { - notesField.tap() - notesField.typeText("Task completed successfully") - } - - let costField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'cost' OR label CONTAINS[c] 'Cost'")).firstMatch - if costField.exists { - costField.tap() - costField.typeText("100") - } - - // When: User saves completion - let saveButton = app.buttons["Save"] - if saveButton.exists { - saveButton.tap() - } - - // Then: Task should be completed with details - wait(seconds: 2) - } - } - - // MARK: - Task Status Changes Tests - - func testMarkTaskInProgress() { - // Given: User has a pending task - navigateToTab("Tasks") - wait(seconds: 2) - - // When: User marks task as in progress - let inProgressButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'In Progress'")).firstMatch - if inProgressButton.exists { - inProgressButton.tap() - wait(seconds: 2) - - // Then: Task should move to In Progress column - let inProgressColumn = app.staticTexts["In Progress"] - XCTAssertTrue(inProgressColumn.exists, "Should have In Progress column") - } - } - - func testCancelTask() { - // Given: User has an active task - navigateToTab("Tasks") - wait(seconds: 2) - - // When: User cancels a task - let cancelButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Cancel'")).firstMatch - if cancelButton.exists { - cancelButton.tap() - - // Confirm cancellation if prompted - let confirmButton = app.alerts.buttons["Cancel Task"] - if confirmButton.exists { - confirmButton.tap() - } - - // Then: Task should be cancelled - wait(seconds: 2) - } - } - - func testUncancelTask() { - // Given: User has a cancelled task - navigateToTab("Tasks") - wait(seconds: 2) - - // When: User uncancels a task - let uncancelButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Uncancel' OR label CONTAINS[c] 'Restore'")).firstMatch - if uncancelButton.exists { - uncancelButton.tap() - wait(seconds: 2) - - // Then: Task should be restored - } - } - - // MARK: - Archive Task Tests - - func testArchiveTask() { - // Given: User has a completed task - navigateToTab("Tasks") - wait(seconds: 2) - - // When: User archives a task - let archiveButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Archive'")).firstMatch - if archiveButton.exists { - archiveButton.tap() - wait(seconds: 2) - - // Then: Task should be archived (moved to archived column or hidden) - let archivedColumn = app.staticTexts["Archived"] - // Task may be in archived column or removed from view - } - } - - func testUnarchiveTask() { - // Given: User has archived tasks - navigateToTab("Tasks") - wait(seconds: 2) - - // Scroll to archived column if it exists - let scrollView = app.scrollViews.firstMatch - if scrollView.exists { - scrollView.swipeLeft() - scrollView.swipeLeft() - } - - // When: User unarchives a task - let unarchiveButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Unarchive'")).firstMatch - if unarchiveButton.exists { - unarchiveButton.tap() - wait(seconds: 2) - - // Then: Task should be restored from archive - } - } - - // MARK: - Task Filtering and Viewing Tests - - func testSwipeBetweenTaskColumns() { - // Given: User is viewing tasks - navigateToTab("Tasks") - wait(seconds: 2) - - let scrollView = app.scrollViews.firstMatch - if scrollView.exists { - // When: User swipes to view different columns - scrollView.swipeLeft() - wait(seconds: 0.5) - - // Then: Next column should be visible - scrollView.swipeLeft() - wait(seconds: 0.5) - - // User can navigate between columns - scrollView.swipeRight() - wait(seconds: 0.5) - } - } - - func testTasksByResidence() { - // Given: User is viewing a residence - navigateToTab("Residences") - wait(seconds: 2) - - let firstResidence = app.cells.firstMatch - if firstResidence.exists { - firstResidence.tap() - wait(seconds: 2) - - // Then: Tasks for that residence should be shown - let tasksSection = app.staticTexts["Tasks"] - XCTAssertTrue(tasksSection.exists || app.scrollViews.firstMatch.exists, - "Should show tasks section in residence details") - } - } - - // MARK: - Task Recurrence Tests - - func testCreateRecurringTask() { - // Given: User is creating a new task - navigateToTab("Tasks") - let addButton = app.navigationBars.buttons.matching(identifier: "plus").firstMatch - - if addButton.exists { - addButton.tap() - wait(seconds: 1) - - // When: User selects recurring frequency - let frequencyPicker = app.pickers.firstMatch - if frequencyPicker.exists { - // Select a frequency (e.g., Monthly) - let monthlyOption = app.pickerWheels.element.adjust(toPickerWheelValue: "Monthly") - // Task creation with recurrence - } - } - } -} diff --git a/iosApp/MyCribTests/TaskViewModelTests.swift b/iosApp/MyCribTests/TaskViewModelTests.swift deleted file mode 100644 index 48431d2..0000000 --- a/iosApp/MyCribTests/TaskViewModelTests.swift +++ /dev/null @@ -1,118 +0,0 @@ -import XCTest -@testable import iosApp -import ComposeApp - -@MainActor -final class TaskViewModelTests: XCTestCase { - var sut: TaskViewModel! - - override func setUp() { - super.setUp() - sut = TaskViewModel() - } - - override func tearDown() { - sut = nil - super.tearDown() - } - - // MARK: - Initialization Tests - - func testInitialState() { - // Then - XCTAssertFalse(sut.isLoading) - XCTAssertNil(sut.errorMessage) - XCTAssertTrue(sut.tasks.isEmpty) - } - - // MARK: - Task Operations Tests - - func testCancelTaskWithValidId() { - // Given - let taskId: Int32 = 1 - var callbackExecuted = false - - // When - sut.cancelTask(id: taskId) { success in - callbackExecuted = true - } - - // Then - Callback should eventually be called - let expectation = XCTestExpectation(description: "Callback executed") - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { - if callbackExecuted || self.sut.errorMessage != nil { - expectation.fulfill() - } - } - - wait(for: [expectation], timeout: 2.0) - } - - func testUncancelTaskWithValidId() { - // Given - let taskId: Int32 = 1 - var callbackExecuted = false - - // When - sut.uncancelTask(id: taskId) { success in - callbackExecuted = true - } - - // Then - Should not crash - XCTAssertNotNil(sut) - } - - func testArchiveTaskWithValidId() { - // Given - let taskId: Int32 = 1 - var callbackExecuted = false - - // When - sut.archiveTask(id: taskId) { success in - callbackExecuted = true - } - - // Then - Should not crash - XCTAssertNotNil(sut) - } - - func testUnarchiveTaskWithValidId() { - // Given - let taskId: Int32 = 1 - var callbackExecuted = false - - // When - sut.unarchiveTask(id: taskId) { success in - callbackExecuted = true - } - - // Then - Should not crash - XCTAssertNotNil(sut) - } - - func testMarkInProgressWithValidId() { - // Given - let taskId: Int32 = 1 - var callbackExecuted = false - - // When - sut.markInProgress(id: taskId) { success in - callbackExecuted = true - } - - // Then - Should not crash - XCTAssertNotNil(sut) - } - - // MARK: - State Management Tests - - func testMultipleOperationsDontCrash() { - // When - sut.cancelTask(id: 1) { _ in } - sut.uncancelTask(id: 2) { _ in } - sut.archiveTask(id: 3) { _ in } - - // Then - Should not crash - XCTAssertNotNil(sut) - } -} diff --git a/iosApp/MyCribTests/TestHelpers.swift b/iosApp/MyCribTests/TestHelpers.swift deleted file mode 100644 index 9580561..0000000 --- a/iosApp/MyCribTests/TestHelpers.swift +++ /dev/null @@ -1,180 +0,0 @@ -import XCTest - -/// Helper extensions and utilities for UI tests -extension XCUIApplication { - /// Launch the app and reset to initial state - func launchAndReset() { - launchArguments = ["--uitesting"] - launch() - } - - /// Clear all text from a text field - func clearText(in textField: XCUIElement) { - textField.tap() - textField.press(forDuration: 1.2) - menuItems["Select All"].tap() - textField.typeText(XCUIKeyboardKey.delete.rawValue) - } -} - -/// Base test class with common setup and teardown -class BaseUITest: XCTestCase { - var app: XCUIApplication! - - override func setUp() { - super.setUp() - continueAfterFailure = false - app = XCUIApplication() - app.launchAndReset() - } - - override func tearDown() { - app = nil - super.tearDown() - } - - // MARK: - Authentication Helpers - - func login(username: String, password: String) { - let usernameField = app.textFields["Username"] - let passwordField = app.secureTextFields["Password"] - let loginButton = app.buttons["Login"] - - if usernameField.exists { - usernameField.tap() - usernameField.typeText(username) - } - - if passwordField.exists { - passwordField.tap() - passwordField.typeText(password) - } - - if loginButton.exists { - loginButton.tap() - } - } - - func logout() { - let profileTab = app.tabBars.buttons["Profile"] - profileTab.tap() - - let logoutButton = app.buttons["Log Out"] - XCTAssertTrue(logoutButton.waitForExistence(timeout: 5)) - logoutButton.tap() - } - - func register(username: String, email: String, password: String, firstName: String = "", lastName: String = "") { - let signUpButton = app.buttons["Sign Up"] - XCTAssertTrue(signUpButton.waitForExistence(timeout: 3)) - signUpButton.tap() - - let usernameField = app.textFields["Username"] - let emailField = app.textFields["Email"] - let passwordField = app.secureTextFields["Password"] - let registerButton = app.buttons["Register"] - - usernameField.tap() - usernameField.typeText(username) - - emailField.tap() - emailField.typeText(email) - - passwordField.tap() - passwordField.typeText(password) - - if !firstName.isEmpty { - let firstNameField = app.textFields["First Name"] - firstNameField.tap() - firstNameField.typeText(firstName) - } - - if !lastName.isEmpty { - let lastNameField = app.textFields["Last Name"] - lastNameField.tap() - lastNameField.typeText(lastName) - } - - registerButton.tap() - } - - // MARK: - Navigation Helpers - - func navigateToTab(_ tabName: String) { - let tab = app.tabBars.buttons[tabName] - XCTAssertTrue(tab.waitForExistence(timeout: 3)) - tab.tap() - } - - func navigateBack() { - app.navigationBars.buttons.element(boundBy: 0).tap() - } - - // MARK: - Assertion Helpers - - func assertElementExists(_ identifier: String, timeout: TimeInterval = 5) { - let element = app.descendants(matching: .any).matching(identifier: identifier).firstMatch - XCTAssertTrue(element.waitForExistence(timeout: timeout), "Element '\(identifier)' does not exist") - } - - func assertElementDoesNotExist(_ identifier: String, timeout: TimeInterval = 2) { - let element = app.descendants(matching: .any).matching(identifier: identifier).firstMatch - XCTAssertFalse(element.waitForExistence(timeout: timeout), "Element '\(identifier)' should not exist") - } - - func assertNavigatedTo(title: String, timeout: TimeInterval = 5) { - let navigationBar = app.navigationBars[title] - XCTAssertTrue(navigationBar.waitForExistence(timeout: timeout), "Did not navigate to '\(title)'") - } - - // MARK: - Wait Helpers - - func wait(seconds: TimeInterval) { - Thread.sleep(forTimeInterval: seconds) - } - - func waitForElementToAppear(_ element: XCUIElement, timeout: TimeInterval = 5) -> Bool { - return element.waitForExistence(timeout: timeout) - } - - func waitForElementToDisappear(_ element: XCUIElement, timeout: TimeInterval = 5) -> Bool { - let predicate = NSPredicate(format: "exists == false") - let expectation = XCTNSPredicateExpectation(predicate: predicate, object: element) - let result = XCTWaiter.wait(for: [expectation], timeout: timeout) - return result == .completed - } -} - -/// Identifiers for UI elements (for better testability) -struct Identifiers { - struct Authentication { - static let usernameField = "UsernameField" - static let passwordField = "PasswordField" - static let loginButton = "LoginButton" - static let registerButton = "RegisterButton" - static let logoutButton = "LogoutButton" - } - - struct Residence { - static let addButton = "AddResidenceButton" - static let editButton = "EditResidenceButton" - static let deleteButton = "DeleteResidenceButton" - static let nameField = "ResidenceNameField" - static let addressField = "ResidenceAddressField" - } - - struct Task { - static let addButton = "AddTaskButton" - static let completeButton = "CompleteTaskButton" - static let editButton = "EditTaskButton" - static let titleField = "TaskTitleField" - static let descriptionField = "TaskDescriptionField" - } - - struct MultiUser { - static let manageUsersButton = "ManageUsersButton" - static let generateCodeButton = "GenerateCodeButton" - static let joinButton = "JoinResidenceButton" - static let shareCodeField = "ShareCodeField" - } -} diff --git a/iosApp/iosApp/HomeScreenView.swift b/iosApp/iosApp/HomeScreenView.swift deleted file mode 100644 index 23338b6..0000000 --- a/iosApp/iosApp/HomeScreenView.swift +++ /dev/null @@ -1,96 +0,0 @@ -import SwiftUI -import ComposeApp - -struct HomeScreenView: View { - @StateObject private var viewModel = ResidenceViewModel() - @StateObject private var loginViewModel = LoginViewModel() - - var body: some View { - NavigationView { - ZStack { - Color(.systemGroupedBackground) - .ignoresSafeArea() - - if viewModel.isLoading { - VStack(spacing: AppSpacing.lg) { - ProgressView() - .scaleEffect(1.2) - Text("Loading...") - .font(.body) - .foregroundColor(Color(.secondaryLabel)) - } - } else { - ScrollView(showsIndicators: false) { - VStack(spacing: AppSpacing.xl) { - // Greeting Header - VStack(alignment: .leading, spacing: AppSpacing.xs) { - Text("Hello!") - .font(.title.weight(.bold)) - .fontWeight(.bold) - .foregroundColor(Color(.label)) - - Text("Welcome to MyCrib") - .font(.body) - .foregroundColor(Color(.secondaryLabel)) - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal, AppSpacing.md) - .padding(.top, AppSpacing.md) - - // Overview Card - if let summary = viewModel.residenceSummary { - OverviewCard(summary: summary.summary) - .transition(.scale.combined(with: .opacity)) - } - - // Navigation Cards - VStack(spacing: AppSpacing.md) { - NavigationLink(destination: ResidencesListView()) { - HomeNavigationCard( - icon: "house.fill", - title: "Residences", - subtitle: "Manage your properties" - ) - } - .buttonStyle(PlainButtonStyle()) - - NavigationLink(destination: AllTasksView()) { - HomeNavigationCard( - icon: "checkmark.circle.fill", - title: "Tasks", - subtitle: "View and manage all tasks" - ) - } - .buttonStyle(PlainButtonStyle()) - } - .padding(.horizontal, AppSpacing.md) - } - .padding(.vertical, AppSpacing.md) - } - } - } - .navigationTitle("MyCrib") - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button(action: { - loginViewModel.logout() - }) { - HStack(spacing: AppSpacing.xs) { - Image(systemName: "rectangle.portrait.and.arrow.right") - .font(.system(size: 18, weight: .semibold)) - } - .foregroundColor(.red) - } - } - } - .onAppear { - viewModel.loadResidenceSummary() - } - } - } -} - -#Preview { - HomeScreenView() -}