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() -}