diff --git a/composeApp/src/commonMain/kotlin/com/example/mycrib/models/Document.kt b/composeApp/src/commonMain/kotlin/com/example/mycrib/models/Document.kt index 2e7b81f..31f2ae3 100644 --- a/composeApp/src/commonMain/kotlin/com/example/mycrib/models/Document.kt +++ b/composeApp/src/commonMain/kotlin/com/example/mycrib/models/Document.kt @@ -3,6 +3,13 @@ package com.example.mycrib.models import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +@Serializable +data class WarrantyStatus( + @SerialName("status_text") val statusText: String, + @SerialName("status_color") val statusColor: String, + @SerialName("is_expiring_soon") val isExpiringSoon: Boolean +) + @Serializable data class DocumentImage( val id: Int? = null, @@ -48,6 +55,7 @@ data class Document( val notes: String? = null, @SerialName("is_active") val isActive: Boolean = true, @SerialName("days_until_expiration") val daysUntilExpiration: Int? = null, + @SerialName("warranty_status") val warrantyStatus: WarrantyStatus? = null, @SerialName("created_at") val createdAt: String? = null, @SerialName("updated_at") val updatedAt: String? = null ) diff --git a/composeApp/src/commonMain/kotlin/com/example/mycrib/network/ApiConfig.kt b/composeApp/src/commonMain/kotlin/com/example/mycrib/network/ApiConfig.kt index dc5b491..706910a 100644 --- a/composeApp/src/commonMain/kotlin/com/example/mycrib/network/ApiConfig.kt +++ b/composeApp/src/commonMain/kotlin/com/example/mycrib/network/ApiConfig.kt @@ -9,7 +9,7 @@ package com.example.mycrib.network */ object ApiConfig { // ⚠️ CHANGE THIS TO TOGGLE ENVIRONMENT ⚠️ - val CURRENT_ENV = Environment.DEV + val CURRENT_ENV = Environment.LOCAL enum class Environment { LOCAL, diff --git a/iosApp/MyCribUITests/ComprehensiveDocumentWarrantyTests.swift b/iosApp/MyCribUITests/ComprehensiveDocumentWarrantyTests.swift new file mode 100644 index 0000000..b60b307 --- /dev/null +++ b/iosApp/MyCribUITests/ComprehensiveDocumentWarrantyTests.swift @@ -0,0 +1,931 @@ +import XCTest + +/// Comprehensive documents and warranties testing suite covering all scenarios, edge cases, and variations +/// Tests both document types (permits, receipts, etc.) and warranties with filtering, searching, and CRUD operations +final class ComprehensiveDocumentWarrantyTests: XCTestCase { + var app: XCUIApplication! + + // Test data tracking + var createdDocumentTitles: [String] = [] + var currentResidenceId: Int32? + + override func setUpWithError() throws { + continueAfterFailure = false + app = XCUIApplication() + app.launch() + + // Ensure user is logged in + UITestHelpers.ensureLoggedIn(app: app) + + // Navigate to a residence first (documents are residence-specific) + navigateToFirstResidence() + } + + override func tearDownWithError() throws { + createdDocumentTitles.removeAll() + currentResidenceId = nil + app = nil + } + + // MARK: - Helper Methods + + private func navigateToFirstResidence() { + // Tap Residences tab + let residencesTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Residences'")).firstMatch + if residencesTab.waitForExistence(timeout: 5) { + residencesTab.tap() + sleep(3) + } + + // Tap first residence card + let firstResidence = app.collectionViews.cells.firstMatch + if firstResidence.waitForExistence(timeout: 5) { + firstResidence.tap() + sleep(2) + } + } + + private func navigateToDocumentsTab() { + // Look for Documents tab or navigation link + let documentsButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Documents' OR label CONTAINS[c] 'Warranties'")).firstMatch + if documentsButton.waitForExistence(timeout: 5) { + documentsButton.tap() + sleep(3) + } + } + + private func openDocumentForm() -> Bool { + let addButton = findAddButton() + guard addButton.exists && addButton.isEnabled else { return false } + addButton.tap() + sleep(3) + + // Verify form opened + let titleField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Title'")).firstMatch + return titleField.waitForExistence(timeout: 5) + } + + private func findAddButton() -> XCUIElement { + sleep(2) + + // Look for add button by various methods + let navBarButtons = app.navigationBars.buttons + for i in 0.. Bool { + let submitButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Save' OR label CONTAINS[c] 'Add' OR label CONTAINS[c] 'Create'")).firstMatch + guard submitButton.exists && submitButton.isEnabled else { return false } + submitButton.tap() + sleep(3) + return true + } + + private func cancelForm() { + let cancelButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Cancel'")).firstMatch + if cancelButton.exists { + cancelButton.tap() + sleep(2) + } + } + + private func switchToWarrantiesTab() { + app/*@START_MENU_TOKEN@*/.buttons["checkmark.shield"]/*[[".segmentedControls",".buttons[\"Warranties\"]",".buttons[\"checkmark.shield\"]"],[[[-1,2],[-1,1],[-1,0,1]],[[-1,2],[-1,1]]],[0]]@END_MENU_TOKEN@*/.firstMatch.tap() + } + + private func switchToDocumentsTab() { + app/*@START_MENU_TOKEN@*/.buttons["doc.text"]/*[[".segmentedControls",".buttons[\"Documents\"]",".buttons[\"doc.text\"]"],[[[-1,2],[-1,0,1]],[[-1,2],[-1,1]]],[0]]@END_MENU_TOKEN@*/.firstMatch.tap() + } + + private func searchFor(text: String) { + let searchField = app.searchFields.firstMatch + if searchField.exists { + searchField.tap() + searchField.typeText(text) + sleep(2) + } + } + + private func clearSearch() { + let searchField = app.searchFields.firstMatch + if searchField.exists { + let clearButton = searchField.buttons["Clear text"] + if clearButton.exists { + clearButton.tap() + sleep(1) + } + } + } + + private func applyFilter(filterName: String) { + // Open filter menu + let filterButton = app.buttons.containing(NSPredicate(format: "label CONTAINS 'line.3.horizontal.decrease'")).firstMatch + if filterButton.exists { + filterButton.tap() + sleep(1) + + // Select filter option + let filterOption = app.buttons[filterName] + if filterOption.exists { + filterOption.tap() + sleep(2) + } + } + } + + private func toggleActiveFilter() { + let activeFilterButton = app.buttons.containing(NSPredicate(format: "label CONTAINS 'checkmark.circle'")).firstMatch + if activeFilterButton.exists { + activeFilterButton.tap() + sleep(2) + } + } + + // MARK: - Test Cases + + // MARK: Navigation Tests + + func test01_NavigateToDocumentsScreen() { + navigateToDocumentsTab() + + // Verify we're on documents screen + let navigationTitle = app.navigationBars["Documents & Warranties"] + XCTAssertTrue(navigationTitle.waitForExistence(timeout: 5), "Should navigate to Documents & Warranties screen") + + // Verify tabs are visible + let warrantiesTab = app.buttons["Warranties"] + let documentsTab = app.buttons["Documents"] + XCTAssertTrue(warrantiesTab.exists || documentsTab.exists, "Should see tab switcher") + } + + func test02_SwitchBetweenWarrantiesAndDocuments() { + navigateToDocumentsTab() + + // Start on warranties tab + switchToWarrantiesTab() + sleep(1) + + // Switch to documents tab + switchToDocumentsTab() + sleep(1) + + // Switch back to warranties + switchToWarrantiesTab() + sleep(1) + + // Should not crash and tabs should still exist + let warrantiesTab = app.buttons["Warranties"] + XCTAssertTrue(warrantiesTab.exists, "Tabs should remain functional after switching") + } + + // MARK: Document Creation Tests + + func test03_CreateDocumentWithAllFields() { + navigateToDocumentsTab() + switchToDocumentsTab() + + XCTAssertTrue(openDocumentForm(), "Should open document form") + + let testTitle = "Test Permit \(UUID().uuidString.prefix(8))" + createdDocumentTitles.append(testTitle) + + // Fill all fields + selectProperty() // REQUIRED - Select property first + fillTextField(placeholder: "Title", text: testTitle) + selectDocumentType(type: "Insurance") + fillTextEditor(text: "Test permit description with detailed information") + fillTextField(placeholder: "Tags", text: "construction,permit") + fillTextField(placeholder: "Item Name", text: "Kitchen Renovation") + fillTextField(placeholder: "Location", text: "Main Kitchen") + + XCTAssertTrue(submitForm(), "Should submit form successfully") + + // Verify document appears in list + sleep(2) + let documentCard = app.staticTexts[testTitle] + XCTAssertTrue(documentCard.exists, "Created document should appear in list") + } + + func test04_CreateDocumentWithMinimalFields() { + navigateToDocumentsTab() + switchToDocumentsTab() + + XCTAssertTrue(openDocumentForm(), "Should open document form") + + let testTitle = "Min Doc \(UUID().uuidString.prefix(8))" + createdDocumentTitles.append(testTitle) + + // Fill only required fields + selectProperty() // REQUIRED - Select property first + fillTextField(placeholder: "Title", text: testTitle) + selectDocumentType(type: "Insurance") + + XCTAssertTrue(submitForm(), "Should submit form with minimal fields") + + // Verify document appears + sleep(2) + let documentCard = app.staticTexts[testTitle] + XCTAssertTrue(documentCard.exists, "Document with minimal fields should appear") + } + + func test05_CreateDocumentWithEmptyTitle_ShouldFail() { + navigateToDocumentsTab() + switchToDocumentsTab() + + XCTAssertTrue(openDocumentForm(), "Should open document form") + + // Try to submit without title + selectProperty() // REQUIRED - Select property first + selectDocumentType(type: "Insurance") + + let submitButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Save' OR label CONTAINS[c] 'Add'")).firstMatch + + // Submit button should be disabled or show error + if submitButton.exists && submitButton.isEnabled { + submitButton.tap() + sleep(2) + + // Should show error message + let errorMessage = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'required' OR label CONTAINS[c] 'title'")).firstMatch + XCTAssertTrue(errorMessage.exists, "Should show validation error for missing title") + } + + cancelForm() + } + + // MARK: Warranty Creation Tests + + func test06_CreateWarrantyWithAllFields() { + navigateToDocumentsTab() + switchToWarrantiesTab() + + XCTAssertTrue(openDocumentForm(), "Should open warranty form") + + let testTitle = "Test Warranty \(UUID().uuidString.prefix(8))" + createdDocumentTitles.append(testTitle) + + // Fill all warranty fields (including required fields) + selectProperty() // REQUIRED - Select property first + fillTextField(placeholder: "Title", text: testTitle) + selectCategory(category: "Appliances") + fillTextField(placeholder: "Item Name", text: "Dishwasher") // REQUIRED + fillTextField(placeholder: "Provider", text: "Bosch") // REQUIRED + fillTextField(placeholder: "Model", text: "SHPM65Z55N") + fillTextField(placeholder: "Serial", text: "SN123456789") + fillTextField(placeholder: "Provider Contact", text: "1-800-BOSCH-00") + fillTextEditor(text: "Full warranty coverage for 2 years") + + // Select dates + selectDate(dateType: "Start Date", daysFromNow: -30) + selectDate(dateType: "End Date", daysFromNow: 700) // ~2 years + + XCTAssertTrue(submitForm(), "Should submit warranty successfully") + + // Verify warranty appears + sleep(2) + let warrantyCard = app.staticTexts[testTitle] + XCTAssertTrue(warrantyCard.exists, "Created warranty should appear in list") + } + + func test07_CreateWarrantyWithFutureDates() { + navigateToDocumentsTab() + switchToWarrantiesTab() + + XCTAssertTrue(openDocumentForm(), "Should open warranty form") + + let testTitle = "Future Warranty \(UUID().uuidString.prefix(8))" + createdDocumentTitles.append(testTitle) + + selectProperty() // REQUIRED - Select property first + fillTextField(placeholder: "Title", text: testTitle) + selectCategory(category: "HVAC") + fillTextField(placeholder: "Item Name", text: "Air Conditioner") // REQUIRED + fillTextField(placeholder: "Provider", text: "Carrier HVAC") // REQUIRED + + // Set start date in future + selectDate(dateType: "Start Date", daysFromNow: 30) + selectDate(dateType: "End Date", daysFromNow: 400) + + XCTAssertTrue(submitForm(), "Should create warranty with future dates") + + sleep(2) + let warrantyCard = app.staticTexts[testTitle] + XCTAssertTrue(warrantyCard.exists, "Warranty with future dates should be created") + } + + func test08_CreateExpiredWarranty() { + navigateToDocumentsTab() + switchToWarrantiesTab() + + XCTAssertTrue(openDocumentForm(), "Should open warranty form") + + let testTitle = "Expired Warranty \(UUID().uuidString.prefix(8))" + createdDocumentTitles.append(testTitle) + + selectProperty() // REQUIRED - Select property first + fillTextField(placeholder: "Title", text: testTitle) + selectCategory(category: "Plumbing") + fillTextField(placeholder: "Item Name", text: "Water Heater") // REQUIRED + fillTextField(placeholder: "Provider", text: "AO Smith") // REQUIRED + + // Set dates in the past + selectDate(dateType: "Start Date", daysFromNow: -400) + selectDate(dateType: "End Date", daysFromNow: -30) + + XCTAssertTrue(submitForm(), "Should create expired warranty") + + sleep(2) + // Expired warranty might not show with active filter on + // Toggle active filter off to see it + toggleActiveFilter() + sleep(1) + + let warrantyCard = app.staticTexts[testTitle] + XCTAssertTrue(warrantyCard.exists, "Expired warranty should be created and visible when filter is off") + } + + // MARK: Search and Filter Tests + + func test09_SearchDocumentsByTitle() { + navigateToDocumentsTab() + switchToDocumentsTab() + + // Create a test document first + XCTAssertTrue(openDocumentForm(), "Should open form") + let searchableTitle = "Searchable Doc \(UUID().uuidString.prefix(8))" + createdDocumentTitles.append(searchableTitle) + selectProperty() // REQUIRED - Select property first + fillTextField(placeholder: "Title", text: searchableTitle) + selectDocumentType(type: "Insurance") + XCTAssertTrue(submitForm(), "Should create document") + sleep(2) + + // Search for it + searchFor(text: String(searchableTitle.prefix(15))) + + // Should find the document + let foundDocument = app.staticTexts[searchableTitle] + XCTAssertTrue(foundDocument.exists, "Should find document by search") + + clearSearch() + } + + func test10_FilterWarrantiesByCategory() { + navigateToDocumentsTab() + switchToWarrantiesTab() + + // Apply category filter + applyFilter(filterName: "Appliances") + + sleep(2) + + // Should show filter chip or indication + let filterChip = app.staticTexts["Appliances"] + XCTAssertTrue(filterChip.exists || app.buttons["Appliances"].exists, "Should show active category filter") + + // Clear filter + applyFilter(filterName: "All Categories") + } + + func test11_FilterDocumentsByType() { + navigateToDocumentsTab() + switchToDocumentsTab() + + // Apply type filter + applyFilter(filterName: "Permit") + + sleep(2) + + // Should show filter indication + let filterChip = app.staticTexts["Permit"] + XCTAssertTrue(filterChip.exists || app.buttons["Permit"].exists, "Should show active type filter") + + // Clear filter + applyFilter(filterName: "All Types") + } + + func test12_ToggleActiveWarrantiesFilter() { + navigateToDocumentsTab() + switchToWarrantiesTab() + + // Toggle active filter off + toggleActiveFilter() + sleep(1) + + // Toggle it back on + toggleActiveFilter() + sleep(1) + + // Should not crash + let warrantiesTab = app.buttons["Warranties"] + XCTAssertTrue(warrantiesTab.exists, "Active filter toggle should work without crashing") + } + + // MARK: Document Detail Tests + + func test13_ViewDocumentDetail() { + navigateToDocumentsTab() + switchToDocumentsTab() + + // Create a document + XCTAssertTrue(openDocumentForm(), "Should open form") + let testTitle = "Detail Test Doc \(UUID().uuidString.prefix(8))" + createdDocumentTitles.append(testTitle) + selectProperty() // REQUIRED - Select property first + fillTextField(placeholder: "Title", text: testTitle) + selectDocumentType(type: "Insurance") + fillTextEditor(text: "This is a test receipt with details") + XCTAssertTrue(submitForm(), "Should create document") + sleep(2) + + // Tap on the document card + let documentCard = app.staticTexts[testTitle] + XCTAssertTrue(documentCard.exists, "Document should exist in list") + documentCard.tap() + sleep(2) + + // Should show detail screen + let detailTitle = app.staticTexts[testTitle] + XCTAssertTrue(detailTitle.exists, "Should show document detail screen") + + // Go back + let backButton = app.navigationBars.buttons.firstMatch + backButton.tap() + sleep(1) + } + + func test14_ViewWarrantyDetailWithDates() { + navigateToDocumentsTab() + switchToWarrantiesTab() + + // Create a warranty + XCTAssertTrue(openDocumentForm(), "Should open form") + let testTitle = "Warranty Detail Test \(UUID().uuidString.prefix(8))" + createdDocumentTitles.append(testTitle) + selectProperty() // REQUIRED - Select property first + fillTextField(placeholder: "Title", text: testTitle) + selectCategory(category: "Appliances") + fillTextField(placeholder: "Item Name", text: "Test Appliance") // REQUIRED + fillTextField(placeholder: "Provider", text: "Test Company") // REQUIRED + selectDate(dateType: "Start Date", daysFromNow: -30) + selectDate(dateType: "End Date", daysFromNow: 335) + XCTAssertTrue(submitForm(), "Should create warranty") + sleep(2) + + // Tap on warranty + let warrantyCard = app.staticTexts[testTitle] + XCTAssertTrue(warrantyCard.exists, "Warranty should exist") + warrantyCard.tap() + sleep(2) + + // Should show warranty details with dates + let detailScreen = app.staticTexts[testTitle] + XCTAssertTrue(detailScreen.exists, "Should show warranty detail") + + // Look for date information + let dateLabels = app.staticTexts.matching(NSPredicate(format: "label CONTAINS[c] '20' OR label CONTAINS[c] 'Start' OR label CONTAINS[c] 'End'")) + XCTAssertTrue(dateLabels.count > 0, "Should display date information") + + // Go back + app.navigationBars.buttons.firstMatch.tap() + sleep(1) + } + + // MARK: Edit Tests + + func test15_EditDocumentTitle() { + navigateToDocumentsTab() + switchToDocumentsTab() + + // Create document + XCTAssertTrue(openDocumentForm(), "Should open form") + let originalTitle = "Edit Test \(UUID().uuidString.prefix(8))" + createdDocumentTitles.append(originalTitle) + selectProperty() // REQUIRED - Select property first + fillTextField(placeholder: "Title", text: originalTitle) + selectDocumentType(type: "Insurance") + XCTAssertTrue(submitForm(), "Should create document") + sleep(2) + + // Open detail + let documentCard = app.staticTexts[originalTitle] + XCTAssertTrue(documentCard.exists, "Document should exist") + documentCard.tap() + sleep(2) + + // Tap edit button + let editButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Edit'")).firstMatch + if editButton.exists { + editButton.tap() + sleep(2) + + // Change title + let titleField = app.textFields.containing(NSPredicate(format: "value == '\(originalTitle)'")).firstMatch + if titleField.exists { + titleField.tap() + titleField.clearText() + let newTitle = "Edited \(originalTitle)" + titleField.typeText(newTitle) + createdDocumentTitles.append(newTitle) + + XCTAssertTrue(submitForm(), "Should save edited document") + sleep(2) + + // Verify new title appears + let updatedTitle = app.staticTexts[newTitle] + XCTAssertTrue(updatedTitle.exists, "Updated title should appear") + } + } + + // Go back to list + app.navigationBars.buttons.element(boundBy: 0).tap() + sleep(1) + } + + func test16_EditWarrantyDates() { + navigateToDocumentsTab() + switchToWarrantiesTab() + + // Create warranty + XCTAssertTrue(openDocumentForm(), "Should open form") + let testTitle = "Edit Dates Warranty \(UUID().uuidString.prefix(8))" + createdDocumentTitles.append(testTitle) + selectProperty() // REQUIRED - Select property first + fillTextField(placeholder: "Title", text: testTitle) + selectCategory(category: "Electronics") + fillTextField(placeholder: "Item Name", text: "TV") // REQUIRED + fillTextField(placeholder: "Provider", text: "Samsung") // REQUIRED + selectDate(dateType: "Start Date", daysFromNow: -60) + selectDate(dateType: "End Date", daysFromNow: 305) + XCTAssertTrue(submitForm(), "Should create warranty") + sleep(2) + + // Open and edit + let warrantyCard = app.staticTexts[testTitle] + XCTAssertTrue(warrantyCard.exists, "Warranty should exist") + warrantyCard.tap() + sleep(2) + + let editButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Edit'")).firstMatch + if editButton.exists { + editButton.tap() + sleep(2) + + // Change end date to extend warranty + selectDate(dateType: "End Date", daysFromNow: 730) // 2 years + + XCTAssertTrue(submitForm(), "Should save edited warranty dates") + sleep(2) + } + + app.navigationBars.buttons.element(boundBy: 0).tap() + sleep(1) + } + + // MARK: Delete Tests + + func test17_DeleteDocument() { + navigateToDocumentsTab() + switchToDocumentsTab() + + // Create document to delete + XCTAssertTrue(openDocumentForm(), "Should open form") + let deleteTitle = "To Delete \(UUID().uuidString.prefix(8))" + selectProperty() // REQUIRED - Select property first + fillTextField(placeholder: "Title", text: deleteTitle) + selectDocumentType(type: "Insurance") + XCTAssertTrue(submitForm(), "Should create document") + sleep(2) + + // Open detail + let documentCard = app.staticTexts[deleteTitle] + XCTAssertTrue(documentCard.exists, "Document should exist") + documentCard.tap() + sleep(2) + + // Find and tap delete button + let deleteButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Delete' OR label CONTAINS[c] 'trash'")).firstMatch + if deleteButton.exists { + deleteButton.tap() + sleep(1) + + // Confirm deletion + let confirmButton = app.alerts.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Delete' OR label CONTAINS[c] 'Confirm'")).firstMatch + if confirmButton.exists { + confirmButton.tap() + sleep(2) + } + + // Should navigate back to list + sleep(2) + + // Verify document no longer exists + let deletedCard = app.staticTexts[deleteTitle] + XCTAssertFalse(deletedCard.exists, "Deleted document should not appear in list") + } + } + + func test18_DeleteWarranty() { + navigateToDocumentsTab() + switchToWarrantiesTab() + + // Create warranty to delete + XCTAssertTrue(openDocumentForm(), "Should open form") + let deleteTitle = "Warranty to Delete \(UUID().uuidString.prefix(8))" + selectProperty() // REQUIRED - Select property first + fillTextField(placeholder: "Title", text: deleteTitle) + selectCategory(category: "Other") + fillTextField(placeholder: "Item Name", text: "Test Item") // REQUIRED + fillTextField(placeholder: "Provider", text: "Test Provider") // REQUIRED + XCTAssertTrue(submitForm(), "Should create warranty") + sleep(2) + + // Open and delete + let warrantyCard = app.staticTexts[deleteTitle] + XCTAssertTrue(warrantyCard.exists, "Warranty should exist") + warrantyCard.tap() + sleep(2) + + let deleteButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Delete' OR label CONTAINS[c] 'trash'")).firstMatch + if deleteButton.exists { + deleteButton.tap() + sleep(1) + + // Confirm + let confirmButton = app.alerts.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Delete'")).firstMatch + if confirmButton.exists { + confirmButton.tap() + sleep(2) + } + + // Verify deleted + sleep(2) + let deletedCard = app.staticTexts[deleteTitle] + XCTAssertFalse(deletedCard.exists, "Deleted warranty should not appear") + } + } + + // MARK: Edge Cases and Error Handling + + func test19_CancelDocumentCreation() { + navigateToDocumentsTab() + switchToDocumentsTab() + + XCTAssertTrue(openDocumentForm(), "Should open form") + + // Fill some fields + selectProperty() // REQUIRED - Select property first + fillTextField(placeholder: "Title", text: "Cancelled Document") + selectDocumentType(type: "Insurance") + + // Cancel instead of save + cancelForm() + + // Should not appear in list + sleep(2) + let cancelledDoc = app.staticTexts["Cancelled Document"] + XCTAssertFalse(cancelledDoc.exists, "Cancelled document should not be created") + } + + func test20_HandleEmptyDocumentsList() { + navigateToDocumentsTab() + switchToDocumentsTab() + + // Apply very specific filter to get empty list + searchFor(text: "NONEXISTENT_DOCUMENT_12345") + + sleep(2) + + // Should show empty state + let emptyMessage = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'No documents' OR label CONTAINS[c] 'No results' OR label CONTAINS[c] 'empty'")).firstMatch + + // Either empty state exists or no items are shown + let hasNoItems = app.cells.count == 0 + XCTAssertTrue(emptyMessage.exists || hasNoItems, "Should handle empty documents list gracefully") + + clearSearch() + } + + func test21_HandleEmptyWarrantiesList() { + navigateToDocumentsTab() + switchToWarrantiesTab() + + // Search for non-existent warranty + searchFor(text: "NONEXISTENT_WARRANTY_99999") + + sleep(2) + + let emptyMessage = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'No warranties' OR label CONTAINS[c] 'No results' OR label CONTAINS[c] 'empty'")).firstMatch + let hasNoItems = app.cells.count == 0 + XCTAssertTrue(emptyMessage.exists || hasNoItems, "Should handle empty warranties list gracefully") + + clearSearch() + } + + func test22_CreateDocumentWithLongTitle() { + navigateToDocumentsTab() + switchToDocumentsTab() + + XCTAssertTrue(openDocumentForm(), "Should open form") + + let longTitle = "This is a very long document title that exceeds normal length expectations to test how the UI handles lengthy text input " + UUID().uuidString + createdDocumentTitles.append(longTitle) + + selectProperty() // REQUIRED - Select property first + fillTextField(placeholder: "Title", text: longTitle) + selectDocumentType(type: "Insurance") + + XCTAssertTrue(submitForm(), "Should handle long title") + + sleep(2) + // Just verify it was created (partial match) + let partialTitle = String(longTitle.prefix(30)) + let documentExists = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] '\(partialTitle)'")).firstMatch.exists + XCTAssertTrue(documentExists, "Document with long title should be created") + } + + func test23_CreateWarrantyWithSpecialCharacters() { + navigateToDocumentsTab() + switchToWarrantiesTab() + + XCTAssertTrue(openDocumentForm(), "Should open form") + + let specialTitle = "Warranty w/ Special #Chars: @ & $ % \(UUID().uuidString.prefix(8))" + createdDocumentTitles.append(specialTitle) + + selectProperty() // REQUIRED - Select property first + fillTextField(placeholder: "Title", text: specialTitle) + selectCategory(category: "Other") + fillTextField(placeholder: "Item Name", text: "Test @#$ Item") // REQUIRED + fillTextField(placeholder: "Provider", text: "Special & Co.") // REQUIRED + + XCTAssertTrue(submitForm(), "Should handle special characters") + + sleep(2) + let partialTitle = String(specialTitle.prefix(20)) + let warrantyExists = app.staticTexts.containing(NSPredicate(format: "label CONTAINS '\(partialTitle)'")).firstMatch.exists + XCTAssertTrue(warrantyExists, "Warranty with special characters should be created") + } + + func test24_RapidTabSwitching() { + navigateToDocumentsTab() + + // Rapidly switch between tabs + for _ in 0..<5 { + switchToWarrantiesTab() + usleep(500000) // 0.5 seconds + switchToDocumentsTab() + usleep(500000) // 0.5 seconds + } + + // Should remain stable + let warrantiesTab = app.buttons["Warranties"] + let documentsTab = app.buttons["Documents"] + XCTAssertTrue(warrantiesTab.exists && documentsTab.exists, "Should handle rapid tab switching without crashing") + } + + func test25_MultipleFiltersCombined() { + navigateToDocumentsTab() + switchToWarrantiesTab() + + // Apply multiple filters + toggleActiveFilter() // Turn off active filter + sleep(1) + applyFilter(filterName: "Appliances") + sleep(1) + searchFor(text: "Test") + + sleep(2) + + // Should apply all filters without crashing + let searchField = app.searchFields.firstMatch + XCTAssertTrue(searchField.exists, "Should handle multiple filters simultaneously") + + // Clean up + clearSearch() + sleep(1) + applyFilter(filterName: "All Categories") + sleep(1) + toggleActiveFilter() // Turn active filter back on + } +} + +// MARK: - XCUIElement Extension for Clearing Text + +extension XCUIElement { + func clearText() { + guard let stringValue = self.value as? String else { + return + } + + self.tap() + + let deleteString = String(repeating: XCUIKeyboardKey.delete.rawValue, count: stringValue.count) + self.typeText(deleteString) + } +} diff --git a/iosApp/iosApp/Documents/Components/WarrantyCard.swift b/iosApp/iosApp/Documents/Components/WarrantyCard.swift index 851ead7..7dbef22 100644 --- a/iosApp/iosApp/Documents/Components/WarrantyCard.swift +++ b/iosApp/iosApp/Documents/Components/WarrantyCard.swift @@ -9,6 +9,12 @@ struct WarrantyCard: View { } var statusColor: Color { + // Use backend-calculated status color if available + if let warrantyStatus = document.warrantyStatus { + return Color(hex: warrantyStatus.statusColor) ?? Color.appPrimary + } + + // Fallback to client-side calculation (shouldn't happen with updated backend) if !document.isActive { return Color.appTextSecondary } if daysUntilExpiration < 0 { return Color.appError } if daysUntilExpiration < 30 { return Color.appAccent } @@ -17,6 +23,12 @@ struct WarrantyCard: View { } var statusText: String { + // Use backend-calculated status text if available + if let warrantyStatus = document.warrantyStatus { + return warrantyStatus.statusText + } + + // Fallback to client-side calculation (shouldn't happen with updated backend) if !document.isActive { return "Inactive" } if daysUntilExpiration < 0 { return "Expired" } if daysUntilExpiration < 30 { return "Expiring soon" } diff --git a/iosApp/iosApp/Documents/DocumentFormView.swift b/iosApp/iosApp/Documents/DocumentFormView.swift index b464ce7..ba221bb 100644 --- a/iosApp/iosApp/Documents/DocumentFormView.swift +++ b/iosApp/iosApp/Documents/DocumentFormView.swift @@ -470,6 +470,8 @@ struct DocumentFormView: View { ) { success, error in isProcessing = false if success { + // Reload documents to show updated item + documentViewModel.loadDocuments(residenceId: residenceId) dismiss() } else { alertMessage = error ?? "Failed to update document" @@ -503,6 +505,8 @@ struct DocumentFormView: View { ) { success, error in isProcessing = false if success { + // Reload documents to show new item + documentViewModel.loadDocuments(residenceId: actualResidenceId) isPresented = false } else { alertMessage = error ?? "Failed to create document" diff --git a/iosApp/iosApp/PushNotifications/PushNotificationManager.swift b/iosApp/iosApp/PushNotifications/PushNotificationManager.swift index 96a2635..89facd2 100644 --- a/iosApp/iosApp/PushNotifications/PushNotificationManager.swift +++ b/iosApp/iosApp/PushNotifications/PushNotificationManager.swift @@ -199,11 +199,11 @@ class PushNotificationManager: NSObject, ObservableObject { // MARK: - Badge Management func clearBadge() { - UIApplication.shared.applicationIconBadgeNumber = 0 + UNUserNotificationCenter.current().setBadgeCount(0) } func setBadge(count: Int) { - UIApplication.shared.applicationIconBadgeNumber = count + UNUserNotificationCenter.current().setBadgeCount(count) } }