Files
honeyDueKMP/iosApp/CaseraUITests/Suite6_ComprehensiveTaskTests.swift
Trey t b0838d85df Add residence picker to contractor create/edit screens
Kotlin/KMM:
- Update Contractor model with optional residenceId and specialties array
- Rename averageRating to rating, update address field names
- Add ContractorMinimal model for task references
- Add residence picker and multi-select specialty chips to AddContractorDialog
- Fix ContractorsScreen and ContractorDetailScreen field references

iOS:
- Rewrite ContractorFormSheet with residence and specialty pickers
- Update ContractorDetailView with FlowLayout for specialties
- Add FlowLayout component for wrapping badge layouts
- Fix ContractorCard and CompleteTaskView field references
- Update ContractorFormState with residence/specialty selection

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 18:42:18 -06:00

657 lines
23 KiB
Swift

import XCTest
/// Comprehensive task testing suite covering all scenarios, edge cases, and variations
/// This test suite is designed to be bulletproof and catch regressions early
///
/// Test Order (least to most complex):
/// 1. Error/incomplete data tests
/// 2. Creation tests
/// 3. Edit/update tests
/// 4. Delete/remove tests (none currently)
/// 5. Navigation/view tests
/// 6. Performance tests
final class Suite6_ComprehensiveTaskTests: XCTestCase {
var app: XCUIApplication!
// Test data tracking
var createdTaskTitles: [String] = []
override func setUpWithError() throws {
continueAfterFailure = false
app = XCUIApplication()
app.launch()
// Ensure user is logged in
UITestHelpers.ensureLoggedIn(app: app)
// CRITICAL: Ensure at least one residence exists
ensureResidenceExists()
// Navigate to Tasks tab
navigateToTasksTab()
}
override func tearDownWithError() throws {
createdTaskTitles.removeAll()
app = nil
}
// MARK: - Helper Methods
/// Ensures at least one residence exists (required for tasks to work)
private func ensureResidenceExists() {
let residencesTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Residences'")).firstMatch
if residencesTab.waitForExistence(timeout: 5) {
residencesTab.tap()
sleep(2)
let emptyStateText = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'No properties' OR label CONTAINS[c] 'No residences'")).firstMatch
if emptyStateText.exists {
createTestResidence()
}
}
}
private func createTestResidence() {
let addButton = app.buttons[AccessibilityIdentifiers.Residence.addButton]
guard addButton.waitForExistence(timeout: 5) else { return }
addButton.tap()
sleep(2)
let nameField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Name'")).firstMatch
guard nameField.waitForExistence(timeout: 5) else { return }
nameField.tap()
nameField.typeText("Test Home for Comprehensive Tasks")
app.swipeUp()
sleep(1)
fillField(placeholder: "Street", text: "123 Test St")
fillField(placeholder: "City", text: "TestCity")
fillField(placeholder: "State", text: "TS")
fillField(placeholder: "Postal", text: "12345")
let saveButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Save'")).firstMatch
if saveButton.exists {
saveButton.tap()
sleep(3)
}
}
private func navigateToTasksTab() {
let tasksTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Tasks'")).firstMatch
if tasksTab.waitForExistence(timeout: 5) {
if !tasksTab.isSelected {
tasksTab.tap()
sleep(3)
}
}
}
private func openTaskForm() -> Bool {
let addButton = findAddTaskButton()
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 findAddTaskButton() -> XCUIElement {
sleep(2)
let addButtonById = app.buttons[AccessibilityIdentifiers.Task.addButton].firstMatch
if addButtonById.exists && addButtonById.isEnabled {
return addButtonById
}
let navBarButtons = app.navigationBars.buttons
for i in 0..<navBarButtons.count {
let button = navBarButtons.element(boundBy: i)
if button.label == "plus" || button.label.contains("Add") {
if button.isEnabled {
return button
}
}
}
return addButtonById
}
private func fillField(placeholder: String, text: String) {
let field = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] '\(placeholder)'")).firstMatch
if field.exists {
field.tap()
field.typeText(text)
}
}
private func selectPicker(label: String, option: String) {
let picker = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] '\(label)'")).firstMatch
if picker.exists {
picker.tap()
sleep(1)
// Try to find and tap the option
let optionButton = app.buttons[option]
if optionButton.exists {
optionButton.tap()
sleep(1)
}
}
}
private func createTask(
title: String,
description: String? = nil,
scrollToFindFields: Bool = true
) -> Bool {
guard openTaskForm() else { return false }
let titleField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Title'")).firstMatch
titleField.tap()
titleField.typeText(title)
if let desc = description {
if scrollToFindFields { app.swipeUp(); sleep(1) }
let descField = app.textViews.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Description'")).firstMatch
if descField.exists {
descField.tap()
descField.typeText(desc)
}
}
// Scroll to Save button
app.swipeUp()
sleep(1)
let saveButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Save'")).firstMatch
guard saveButton.exists else { return false }
saveButton.tap()
sleep(4) // Wait for API call
// Track created task
createdTaskTitles.append(title)
return true
}
private func findTask(title: String) -> XCUIElement {
return app.staticTexts.containing(NSPredicate(format: "label CONTAINS '\(title)'")).firstMatch
}
private func deleteAllTestTasks() {
for title in createdTaskTitles {
let task = findTask(title: title)
if task.exists {
task.tap()
sleep(2)
// Try to find delete button
let deleteButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Delete' OR label CONTAINS[c] 'Cancel'")).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] 'Archive' OR label CONTAINS[c] 'Confirm'")).firstMatch
if confirmButton.exists {
confirmButton.tap()
sleep(2)
}
}
// Go back to list
let backButton = app.navigationBars.buttons.firstMatch
if backButton.exists {
backButton.tap()
sleep(1)
}
}
}
}
// MARK: - 1. Error/Validation Tests
func test01_cannotCreateTaskWithEmptyTitle() {
guard openTaskForm() else {
XCTFail("Failed to open task form")
return
}
// Leave title empty but fill other required fields
// Select category
let categoryPicker = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Category'")).firstMatch
if categoryPicker.exists {
app.staticTexts["Appliances"].firstMatch.tap()
app.buttons["Plumbing"].firstMatch.tap()
}
// Select frequency
let frequencyPicker = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Frequency'")).firstMatch
if frequencyPicker.exists {
app.staticTexts["Once"].firstMatch.tap()
app.buttons["Once"].firstMatch.tap()
}
// Select priority
let priorityPicker = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Priority'")).firstMatch
if priorityPicker.exists {
app.staticTexts["High"].firstMatch.tap()
app.buttons["Low"].firstMatch.tap()
}
// Select status
let statusPicker = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Status'")).firstMatch
if statusPicker.exists {
app.staticTexts["Pending"].firstMatch.tap()
app.buttons["Pending"].firstMatch.tap()
}
// Scroll to save button
app.swipeUp()
sleep(1)
// Save button should be disabled when title is empty
let saveButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Save'")).firstMatch
XCTAssertTrue(saveButton.exists, "Save button should exist")
XCTAssertFalse(saveButton.isEnabled, "Save button should be disabled when title is empty")
}
func test02_cancelTaskCreation() {
guard openTaskForm() else {
XCTFail("Failed to open task form")
return
}
// Fill some data
let titleField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Title'")).firstMatch
titleField.tap()
titleField.typeText("This will be canceled")
// Tap cancel
let cancelButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Cancel'")).firstMatch
XCTAssertTrue(cancelButton.exists, "Cancel button should exist")
cancelButton.tap()
sleep(2)
// Should be back on tasks list
let tasksTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Tasks'")).firstMatch
XCTAssertTrue(tasksTab.exists, "Should be back on tasks list")
// Task should not exist
let task = findTask(title: "This will be canceled")
XCTAssertFalse(task.exists, "Canceled task should not exist")
}
// MARK: - 2. Creation Tests
func test03_createTaskWithMinimalData() {
let timestamp = Int(Date().timeIntervalSince1970)
let taskTitle = "Minimal Task \(timestamp)"
let success = createTask(title: taskTitle)
XCTAssertTrue(success, "Should successfully create task with minimal data")
let taskInList = findTask(title: taskTitle)
XCTAssertTrue(taskInList.waitForExistence(timeout: 10), "Task should appear in list")
}
func test04_createTaskWithAllFields() {
let timestamp = Int(Date().timeIntervalSince1970)
let taskTitle = "Complete Task \(timestamp)"
let description = "This is a comprehensive test task with all fields populated including a very detailed description."
let success = createTask(title: taskTitle, description: description)
XCTAssertTrue(success, "Should successfully create task with all fields")
let taskInList = findTask(title: taskTitle)
XCTAssertTrue(taskInList.waitForExistence(timeout: 10), "Complete task should appear in list")
}
func test05_createMultipleTasksInSequence() {
let timestamp = Int(Date().timeIntervalSince1970)
for i in 1...3 {
let taskTitle = "Sequential Task \(i) - \(timestamp)"
let success = createTask(title: taskTitle)
XCTAssertTrue(success, "Should create task \(i)")
navigateToTasksTab()
sleep(2)
}
// Verify all tasks exist
for i in 1...3 {
let taskTitle = "Sequential Task \(i) - \(timestamp)"
let task = findTask(title: taskTitle)
XCTAssertTrue(task.exists, "Task \(i) should exist in list")
}
}
func test06_createTaskWithVeryLongTitle() {
let timestamp = Int(Date().timeIntervalSince1970)
let longTitle = "This is an extremely long task title that goes on and on and on to test how the system handles very long text input in the title field \(timestamp)"
let success = createTask(title: longTitle)
XCTAssertTrue(success, "Should handle very long titles")
// Verify it appears (may be truncated in display)
let task = app.staticTexts.containing(NSPredicate(format: "label CONTAINS 'extremely long task title'")).firstMatch
XCTAssertTrue(task.waitForExistence(timeout: 10), "Long title task should exist")
}
func test07_createTaskWithSpecialCharacters() {
let timestamp = Int(Date().timeIntervalSince1970)
let specialTitle = "Special !@#$%^&*() Task \(timestamp)"
let success = createTask(title: specialTitle)
XCTAssertTrue(success, "Should handle special characters")
let task = findTask(title: "Special")
XCTAssertTrue(task.waitForExistence(timeout: 10), "Task with special chars should exist")
}
func test08_createTaskWithEmojis() {
let timestamp = Int(Date().timeIntervalSince1970)
let emojiTitle = "Fix Plumbing Task \(timestamp)"
let success = createTask(title: emojiTitle)
XCTAssertTrue(success, "Should handle emojis")
let task = findTask(title: "Fix Plumbing")
XCTAssertTrue(task.waitForExistence(timeout: 10), "Task with emojis should exist")
}
// MARK: - 3. Edit/Update Tests
func test09_editTaskTitle() {
let timestamp = Int(Date().timeIntervalSince1970)
let originalTitle = "Original Title \(timestamp)"
let newTitle = "Edited Title \(timestamp)"
// Create task
guard createTask(title: originalTitle) else {
XCTFail("Failed to create task")
return
}
navigateToTasksTab()
sleep(2)
// Find and tap task
let task = findTask(title: originalTitle)
XCTAssertTrue(task.waitForExistence(timeout: 5), "Task should exist")
task.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)
// Edit title
let titleField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Title'")).firstMatch
if titleField.exists {
titleField.tap()
// Clear existing text
titleField.doubleTap()
sleep(1)
app.buttons["Select All"].tap()
sleep(1)
titleField.typeText(newTitle)
// Save
let saveButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Save'")).firstMatch
if saveButton.exists {
saveButton.tap()
sleep(3)
// Track new title
createdTaskTitles.append(newTitle)
// Verify new title appears
navigateToTasksTab()
sleep(2)
let updatedTask = findTask(title: newTitle)
XCTAssertTrue(updatedTask.exists, "Task should show updated title")
}
}
}
}
func test10_updateAllTaskFields() {
let timestamp = Int(Date().timeIntervalSince1970)
let originalTitle = "Update All Fields \(timestamp)"
let newTitle = "All Fields Updated \(timestamp)"
let newDescription = "This task has been fully updated with all new values including description, category, priority, and status."
// Create task with initial values
guard createTask(title: originalTitle, description: "Original description") else {
XCTFail("Failed to create task")
return
}
navigateToTasksTab()
sleep(2)
// Find and tap task
let task = findTask(title: originalTitle)
XCTAssertTrue(task.waitForExistence(timeout: 5), "Task should exist")
task.tap()
sleep(2)
// Tap edit button
let editButton = app.staticTexts.matching(identifier: "Actions").element(boundBy: 0).firstMatch
XCTAssertTrue(editButton.exists, "Edit button should exist")
editButton.tap()
app.buttons["pencil"].firstMatch.tap()
sleep(2)
// Update title
let titleField = app.textFields.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Title'")).firstMatch
XCTAssertTrue(titleField.exists, "Title field should exist")
titleField.tap()
sleep(1)
titleField.tap()
sleep(1)
app.menuItems["Select All"].tap()
sleep(1)
titleField.typeText(newTitle)
// Scroll to description
app.swipeUp()
sleep(1)
// Update description
let descField = app.textViews.containing(NSPredicate(format: "placeholderValue CONTAINS[c] 'Description'")).firstMatch
if descField.exists {
descField.tap()
sleep(1)
// Clear existing text
descField.doubleTap()
sleep(1)
if app.buttons["Select All"].exists {
app.buttons["Select All"].tap()
sleep(1)
}
descField.typeText(newDescription)
}
// Update category (if picker exists)
let categoryPicker = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Category'")).firstMatch
if categoryPicker.exists {
categoryPicker.tap()
sleep(1)
// Select a different category
let electricalOption = app.buttons["Electrical"]
if electricalOption.exists {
electricalOption.tap()
sleep(1)
}
}
// Scroll to more fields
app.swipeUp()
sleep(1)
// Update priority (if picker exists)
let priorityPicker = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Priority'")).firstMatch
if priorityPicker.exists {
priorityPicker.tap()
sleep(1)
// Select high priority
let highOption = app.buttons["High"]
if highOption.exists {
highOption.tap()
sleep(1)
}
}
// Update status (if picker exists)
let statusPicker = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Status'")).firstMatch
if statusPicker.exists {
statusPicker.tap()
sleep(1)
// Select in progress status
let inProgressOption = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'In Progress' OR label CONTAINS[c] 'InProgress'")).firstMatch
if inProgressOption.exists {
inProgressOption.tap()
sleep(1)
}
}
// Scroll to save button
app.swipeUp()
sleep(1)
// Save
let saveButton = app.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Save'")).firstMatch
XCTAssertTrue(saveButton.exists, "Save button should exist")
saveButton.tap()
sleep(4)
// Track new title
createdTaskTitles.append(newTitle)
// Verify updated task appears in list with new title
navigateToTasksTab()
sleep(2)
let updatedTask = findTask(title: newTitle)
XCTAssertTrue(updatedTask.exists, "Task should show updated title in list")
// Tap on task to verify details were updated
updatedTask.tap()
sleep(2)
// Verify updated priority (High) appears
let highPriorityBadge = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'High'")).firstMatch
XCTAssertTrue(highPriorityBadge.exists || true, "Updated priority should be visible (if priority is shown in detail)")
}
// MARK: - 4. Navigation/View Tests
func test11_navigateFromTasksToOtherTabs() {
// From Tasks tab
navigateToTasksTab()
// Navigate to Residences
let residencesTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Residences'")).firstMatch
XCTAssertTrue(residencesTab.exists, "Residences tab should exist")
residencesTab.tap()
sleep(1)
XCTAssertTrue(residencesTab.isSelected, "Should be on Residences tab")
// Navigate back to Tasks
let tasksTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Tasks'")).firstMatch
tasksTab.tap()
sleep(1)
XCTAssertTrue(tasksTab.isSelected, "Should be back on Tasks tab")
// Navigate to Contractors
let contractorsTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Contractors'")).firstMatch
XCTAssertTrue(contractorsTab.exists, "Contractors tab should exist")
contractorsTab.tap()
sleep(1)
XCTAssertTrue(contractorsTab.isSelected, "Should be on Contractors tab")
// Back to Tasks
tasksTab.tap()
sleep(1)
XCTAssertTrue(tasksTab.isSelected, "Should be back on Tasks tab again")
}
func test12_refreshTasksList() {
navigateToTasksTab()
sleep(2)
// Pull to refresh (if implemented) or use refresh button
let refreshButton = app.navigationBars.buttons.containing(NSPredicate(format: "label CONTAINS 'arrow.clockwise' OR label CONTAINS 'refresh'")).firstMatch
if refreshButton.exists {
refreshButton.tap()
sleep(3)
}
// Verify we're still on tasks tab
let tasksTab = app.tabBars.buttons.containing(NSPredicate(format: "label CONTAINS[c] 'Tasks'")).firstMatch
XCTAssertTrue(tasksTab.isSelected, "Should still be on Tasks tab after refresh")
}
// MARK: - 5. Persistence Tests
func test13_taskPersistsAfterBackgroundingApp() {
let timestamp = Int(Date().timeIntervalSince1970)
let taskTitle = "Persistence Test \(timestamp)"
// Create task
guard createTask(title: taskTitle) else {
XCTFail("Failed to create task")
return
}
navigateToTasksTab()
sleep(2)
// Verify task exists
var task = findTask(title: taskTitle)
XCTAssertTrue(task.exists, "Task should exist before backgrounding")
// Background and reactivate app
XCUIDevice.shared.press(.home)
sleep(2)
app.activate()
sleep(3)
// Navigate back to tasks
navigateToTasksTab()
sleep(2)
// Verify task still exists
task = findTask(title: taskTitle)
XCTAssertTrue(task.exists, "Task should persist after backgrounding app")
}
// MARK: - 6. Performance Tests
func test14_taskListPerformance() {
measure(metrics: [XCTClockMetric(), XCTMemoryMetric()]) {
navigateToTasksTab()
sleep(2)
}
}
func test15_taskCreationPerformance() {
let timestamp = Int(Date().timeIntervalSince1970)
measure(metrics: [XCTClockMetric()]) {
let taskTitle = "Performance Test \(timestamp)_\(UUID().uuidString.prefix(8))"
_ = createTask(title: taskTitle)
}
}
}