Make contractor phone optional and add UI test accessibility identifiers
Updated contractor models and forms to make phone field optional. Added accessibility identifiers for add buttons to enable UI testing. Contractor changes: - Kotlin: Made phone nullable in Contractor, ContractorCreateRequest, ContractorSummary models - Android: Updated AddContractorDialog validation to only require name - Android: Removed asterisk from phone field label - Android: Updated ContractorDetailScreen to handle nullable phone - iOS: Updated ContractorFormSheet validation to only check name field - iOS: Updated form footer text to show only name as required - iOS: Updated ContractorDetailView to use optional binding for phone display Accessibility improvements: - iOS: Added accessibility identifier to contractor add button in ContractorsListView - iOS: Added accessibility identifier to task add button in ResidenceDetailView These identifiers enable reliable UI testing by allowing tests to access buttons by their accessibility identifiers instead of searching by label. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ data class Contractor(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val company: String? = null,
|
||||
val phone: String,
|
||||
val phone: String? = null,
|
||||
val email: String? = null,
|
||||
@SerialName("secondary_phone") val secondaryPhone: String? = null,
|
||||
val specialty: String? = null,
|
||||
@@ -33,7 +33,7 @@ data class Contractor(
|
||||
data class ContractorCreateRequest(
|
||||
val name: String,
|
||||
val company: String? = null,
|
||||
val phone: String,
|
||||
val phone: String? = null,
|
||||
val email: String? = null,
|
||||
@SerialName("secondary_phone") val secondaryPhone: String? = null,
|
||||
val specialty: String? = null,
|
||||
@@ -72,7 +72,7 @@ data class ContractorSummary(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val company: String? = null,
|
||||
val phone: String,
|
||||
val phone: String? = null,
|
||||
val specialty: String? = null,
|
||||
@SerialName("average_rating") val averageRating: Double? = null,
|
||||
@SerialName("is_favorite") val isFavorite: Boolean = false,
|
||||
|
||||
@@ -62,7 +62,7 @@ fun AddContractorDialog(
|
||||
val contractor = (contractorDetailState as ApiResult.Success).data
|
||||
name = contractor.name
|
||||
company = contractor.company ?: ""
|
||||
phone = contractor.phone
|
||||
phone = contractor.phone ?: ""
|
||||
email = contractor.email ?: ""
|
||||
secondaryPhone = contractor.secondaryPhone ?: ""
|
||||
specialty = contractor.specialty ?: ""
|
||||
@@ -157,7 +157,7 @@ fun AddContractorDialog(
|
||||
OutlinedTextField(
|
||||
value = phone,
|
||||
onValueChange = { phone = it },
|
||||
label = { Text("Phone *") },
|
||||
label = { Text("Phone") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
@@ -402,13 +402,13 @@ fun AddContractorDialog(
|
||||
confirmButton = {
|
||||
Button(
|
||||
onClick = {
|
||||
if (name.isNotBlank() && phone.isNotBlank()) {
|
||||
if (name.isNotBlank()) {
|
||||
if (contractorId == null) {
|
||||
viewModel.createContractor(
|
||||
ContractorCreateRequest(
|
||||
name = name,
|
||||
company = company.takeIf { it.isNotBlank() },
|
||||
phone = phone,
|
||||
phone = phone.takeIf { it.isNotBlank() },
|
||||
email = email.takeIf { it.isNotBlank() },
|
||||
secondaryPhone = secondaryPhone.takeIf { it.isNotBlank() },
|
||||
specialty = specialty.takeIf { it.isNotBlank() },
|
||||
@@ -445,7 +445,7 @@ fun AddContractorDialog(
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = name.isNotBlank() && phone.isNotBlank() &&
|
||||
enabled = name.isNotBlank() &&
|
||||
createState !is ApiResult.Loading && updateState !is ApiResult.Loading,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Color(0xFF2563EB)
|
||||
|
||||
@@ -231,12 +231,14 @@ fun ContractorDetailScreen(
|
||||
// Contact Information
|
||||
item {
|
||||
DetailSection(title = "Contact Information") {
|
||||
DetailRow(
|
||||
icon = Icons.Default.Phone,
|
||||
label = "Phone",
|
||||
value = contractor.phone,
|
||||
iconTint = Color(0xFF3B82F6)
|
||||
)
|
||||
if (contractor.phone != null) {
|
||||
DetailRow(
|
||||
icon = Icons.Default.Phone,
|
||||
label = "Phone",
|
||||
value = contractor.phone,
|
||||
iconTint = Color(0xFF3B82F6)
|
||||
)
|
||||
}
|
||||
|
||||
if (contractor.email != null) {
|
||||
DetailRow(
|
||||
|
||||
@@ -92,7 +92,9 @@ struct ContractorDetailView: View {
|
||||
|
||||
// Contact Information
|
||||
DetailSection(title: "Contact Information") {
|
||||
DetailRow(icon: "phone", label: "Phone", value: contractor.phone, iconColor: .blue)
|
||||
if let phone = contractor.phone {
|
||||
DetailRow(icon: "phone", label: "Phone", value: phone, iconColor: .blue)
|
||||
}
|
||||
|
||||
if let email = contractor.email {
|
||||
DetailRow(icon: "envelope", label: "Email", value: email, iconColor: .purple)
|
||||
|
||||
@@ -42,7 +42,7 @@ struct ContractorFormSheet: View {
|
||||
}
|
||||
|
||||
private var canSave: Bool {
|
||||
!name.isEmpty && !phone.isEmpty
|
||||
!name.isEmpty
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -67,6 +67,10 @@ struct ContractorFormSheet: View {
|
||||
}
|
||||
} header: {
|
||||
Text("Basic Information")
|
||||
} footer: {
|
||||
Text("Required: Name")
|
||||
.font(.caption)
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
|
||||
// Contact Information
|
||||
@@ -102,8 +106,7 @@ struct ContractorFormSheet: View {
|
||||
} header: {
|
||||
Text("Contact Information")
|
||||
} footer: {
|
||||
Text("Required: Name and Phone")
|
||||
.font(.caption)
|
||||
|
||||
}
|
||||
|
||||
// Business Details
|
||||
@@ -295,7 +298,7 @@ struct ContractorFormSheet: View {
|
||||
|
||||
name = contractor.name
|
||||
company = contractor.company ?? ""
|
||||
phone = contractor.phone
|
||||
phone = contractor.phone ?? ""
|
||||
email = contractor.email ?? ""
|
||||
secondaryPhone = contractor.secondaryPhone ?? ""
|
||||
specialty = contractor.specialty ?? ""
|
||||
@@ -353,7 +356,7 @@ struct ContractorFormSheet: View {
|
||||
let request = ContractorCreateRequest(
|
||||
name: name,
|
||||
company: company.isEmpty ? nil : company,
|
||||
phone: phone,
|
||||
phone: phone.isEmpty ? nil : phone,
|
||||
email: email.isEmpty ? nil : email,
|
||||
secondaryPhone: secondaryPhone.isEmpty ? nil : secondaryPhone,
|
||||
specialty: specialty.isEmpty ? nil : specialty,
|
||||
|
||||
@@ -148,6 +148,7 @@ struct ContractorsListView: View {
|
||||
.font(.title2)
|
||||
.foregroundColor(.blue)
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Contractor.addButton)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,9 +56,11 @@ struct ResidenceDetailView: View {
|
||||
|
||||
.alert("Delete Residence", isPresented: $showDeleteConfirmation) {
|
||||
Button("Cancel", role: .cancel) { }
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Alert.cancelButton)
|
||||
Button("Delete", role: .destructive) {
|
||||
deleteResidence()
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Alert.deleteButton)
|
||||
} message: {
|
||||
if let residence = viewModel.selectedResidence {
|
||||
Text("Are you sure you want to delete \(residence.name)? This action cannot be undone and will delete all associated tasks, documents, and data.")
|
||||
@@ -223,6 +225,7 @@ private extension ResidenceDetailView {
|
||||
Button("Edit") {
|
||||
showEditResidence = true
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.editButton)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,6 +259,7 @@ private extension ResidenceDetailView {
|
||||
} label: {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Task.addButton)
|
||||
|
||||
if let residence = viewModel.selectedResidence, residence.isPrimaryOwner {
|
||||
Button {
|
||||
@@ -264,6 +268,7 @@ private extension ResidenceDetailView {
|
||||
Image(systemName: "trash")
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Residence.deleteButton)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user