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>
This commit is contained in:
180
iosApp/MyCribTests/TestHelpers.swift
Normal file
180
iosApp/MyCribTests/TestHelpers.swift
Normal file
@@ -0,0 +1,180 @@
|
||||
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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user