UI Test Suite7: Contractor tests (iOS parity)

Ports Suite7_ContractorTests.swift. testTags on contractor screens via
AccessibilityIds.Contractor.*. CRUD + sharing + link-to-task.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-04-18 14:40:21 -05:00
parent 95dabf741f
commit c772215c04
4 changed files with 509 additions and 23 deletions

View File

@@ -10,7 +10,9 @@ import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.font.FontWeight
import com.tt.honeyDue.testing.AccessibilityIds
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import honeydue.composeapp.generated.resources.*
@@ -147,7 +149,9 @@ fun AddContractorDialog(
value = name,
onValueChange = { name = it },
label = { Text(stringResource(Res.string.contractors_form_name_required)) },
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.testTag(AccessibilityIds.Contractor.nameField),
singleLine = true,
shape = RoundedCornerShape(12.dp),
leadingIcon = { Icon(Icons.Default.Person, null) },
@@ -161,7 +165,9 @@ fun AddContractorDialog(
value = company,
onValueChange = { company = it },
label = { Text(stringResource(Res.string.contractors_form_company)) },
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.testTag(AccessibilityIds.Contractor.companyField),
singleLine = true,
shape = RoundedCornerShape(12.dp),
leadingIcon = { Icon(Icons.Default.Business, null) },
@@ -243,7 +249,9 @@ fun AddContractorDialog(
value = phone,
onValueChange = { phone = it },
label = { Text(stringResource(Res.string.contractors_form_phone)) },
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.testTag(AccessibilityIds.Contractor.phoneField),
singleLine = true,
shape = RoundedCornerShape(12.dp),
leadingIcon = { Icon(Icons.Default.Phone, null) },
@@ -257,7 +265,9 @@ fun AddContractorDialog(
value = email,
onValueChange = { email = it },
label = { Text(stringResource(Res.string.contractors_form_email)) },
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.testTag(AccessibilityIds.Contractor.emailField),
singleLine = true,
shape = RoundedCornerShape(12.dp),
leadingIcon = { Icon(Icons.Default.Email, null) },
@@ -293,6 +303,7 @@ fun AddContractorDialog(
// Multi-select specialties using chips
FlowRow(
modifier = Modifier.testTag(AccessibilityIds.Contractor.specialtyPicker),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
@@ -396,7 +407,8 @@ fun AddContractorDialog(
label = { Text(stringResource(Res.string.contractors_form_private_notes)) },
modifier = Modifier
.fillMaxWidth()
.height(100.dp),
.height(100.dp)
.testTag(AccessibilityIds.Contractor.notesField),
maxLines = 4,
shape = RoundedCornerShape(12.dp),
leadingIcon = { Icon(Icons.Default.Notes, null) },
@@ -491,7 +503,8 @@ fun AddContractorDialog(
createState !is ApiResult.Loading && updateState !is ApiResult.Loading,
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xFF2563EB)
)
),
modifier = Modifier.testTag(AccessibilityIds.Contractor.saveButton)
) {
if (createState is ApiResult.Loading || updateState is ApiResult.Loading) {
CircularProgressIndicator(
@@ -505,7 +518,10 @@ fun AddContractorDialog(
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
TextButton(
onClick = onDismiss,
modifier = Modifier.testTag(AccessibilityIds.Contractor.formCancelButton)
) {
Text(cancelText, color = Color(0xFF6B7280))
}
},

View File

@@ -14,7 +14,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.font.FontWeight
import com.tt.honeyDue.testing.AccessibilityIds
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
@@ -93,15 +95,18 @@ fun ContractorDetailScreen(
actions = {
when (val state = contractorState) {
is ApiResult.Success -> {
IconButton(onClick = {
val shareCheck = SubscriptionHelper.canShareContractor()
if (shareCheck.allowed) {
shareContractor(state.data)
} else {
upgradeTriggerKey = shareCheck.triggerKey
showUpgradePrompt = true
}
}) {
IconButton(
onClick = {
val shareCheck = SubscriptionHelper.canShareContractor()
if (shareCheck.allowed) {
shareContractor(state.data)
} else {
upgradeTriggerKey = shareCheck.triggerKey
showUpgradePrompt = true
}
},
modifier = Modifier.testTag(AccessibilityIds.Contractor.shareButton)
) {
Icon(Icons.Default.Share, stringResource(Res.string.common_share))
}
IconButton(onClick = { viewModel.toggleFavorite(contractorId) }) {
@@ -111,10 +116,16 @@ fun ContractorDetailScreen(
tint = if (state.data.isFavorite) Color(0xFFF59E0B) else LocalContentColor.current
)
}
IconButton(onClick = { showEditDialog = true }) {
IconButton(
onClick = { showEditDialog = true },
modifier = Modifier.testTag(AccessibilityIds.Contractor.editButton)
) {
Icon(Icons.Default.Edit, stringResource(Res.string.common_edit))
}
IconButton(onClick = { showDeleteConfirmation = true }) {
IconButton(
onClick = { showDeleteConfirmation = true },
modifier = Modifier.testTag(AccessibilityIds.Contractor.deleteButton)
) {
Icon(Icons.Default.Delete, stringResource(Res.string.common_delete), tint = Color(0xFFEF4444))
}
}
@@ -132,6 +143,7 @@ fun ContractorDetailScreen(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.testTag(AccessibilityIds.Contractor.detailView)
) {
val uriHandler = LocalUriHandler.current
val residences = DataManager.residences.value
@@ -263,7 +275,9 @@ fun ContractorDetailScreen(
icon = Icons.Default.Phone,
label = stringResource(Res.string.contractors_call),
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.weight(1f),
modifier = Modifier
.weight(1f)
.testTag(AccessibilityIds.Contractor.callButton),
onClick = {
try {
uriHandler.openUri("tel:${phone.replace(" ", "")}")
@@ -277,7 +291,9 @@ fun ContractorDetailScreen(
icon = Icons.Default.Email,
label = stringResource(Res.string.contractors_send_email),
color = MaterialTheme.colorScheme.secondary,
modifier = Modifier.weight(1f),
modifier = Modifier
.weight(1f)
.testTag(AccessibilityIds.Contractor.emailButton),
onClick = {
try {
uriHandler.openUri("mailto:$email")

View File

@@ -11,7 +11,9 @@ import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.font.FontWeight
import com.tt.honeyDue.testing.AccessibilityIds
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
@@ -190,7 +192,8 @@ fun ContractorsScreen(
}
},
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary
contentColor = MaterialTheme.colorScheme.onPrimary,
modifier = Modifier.testTag(AccessibilityIds.Contractor.addButton)
) {
Icon(Icons.Default.Add, stringResource(Res.string.contractors_add_button))
}
@@ -288,7 +291,9 @@ fun ContractorsScreen(
if (filteredContractors.isEmpty()) {
// Empty state with organic styling
Box(
modifier = Modifier.fillMaxSize(),
modifier = Modifier
.fillMaxSize()
.testTag(AccessibilityIds.Contractor.emptyStateView),
contentAlignment = Alignment.Center
) {
Column(
@@ -331,7 +336,9 @@ fun ContractorsScreen(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
modifier = Modifier
.fillMaxSize()
.testTag(AccessibilityIds.Contractor.contractorsList),
contentPadding = PaddingValues(OrganicSpacing.medium),
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.medium)
) {
@@ -387,6 +394,7 @@ fun ContractorCard(
OrganicCard(
modifier = Modifier
.fillMaxWidth()
.testTag(AccessibilityIds.withId(AccessibilityIds.Contractor.contractorCard, contractor.id))
.clickable { onClick(contractor.id) }
) {
Row(