Files
honeyDueKMP/iosApp/MyCribTests/TestHelpers.swift
Trey t d5d16c5c48 Add comprehensive unit tests for iOS and Android/KMM
This commit adds extensive unit test coverage for the entire application,
including iOS ViewModels, design system, and shared Kotlin Multiplatform code.

iOS Unit Tests (49 tests):
- LoginViewModelTests: Authentication state and validation tests
- ResidenceViewModelTests: Residence loading and state management
- TaskViewModelTests: Task operations (cancel, archive, mark progress)
- DocumentViewModelTests: Document/warranty CRUD operations
- ContractorViewModelTests: Contractor management and favorites
- DesignSystemTests: Color system, typography, spacing, radius, shadows

Shared KMM Unit Tests (26 tests):
- AuthViewModelTest: Login, register, verify email state initialization
- TaskViewModelTest: Task state management verification
- DocumentViewModelTest: Document state initialization tests
- ResidenceViewModelTest: Residence state management tests
- ContractorViewModelTest: Contractor state initialization tests

Test Infrastructure:
- Reorganized test files from iosAppUITests to MyCribTests
- All shared KMM tests passing successfully (./gradlew test)
- Tests focus on state initialization and core functionality
- Ready for CI/CD integration

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-12 17:50:29 -06:00

181 lines
5.7 KiB
Swift

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"
}
}