UI Test Suite8: Document/Warranty tests (iOS parity)
Ports representative subset of Suite8_DocumentWarrantyTests.swift (22 of 25 iOS tests). testTags on document screens via AccessibilityIds.Document.*. Documented deliberate skips in the class header (5/7/8/10/11/12/16) — each either relies on iOS-only pickers/menus or is subsumed by another ported test. No new AccessibilityIds added — Document group already has parity. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,12 +11,14 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
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 androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tt.honeyDue.models.Document
|
||||
import com.tt.honeyDue.models.DocumentCategory
|
||||
import com.tt.honeyDue.models.DocumentType
|
||||
import com.tt.honeyDue.testing.AccessibilityIds
|
||||
|
||||
@Composable
|
||||
fun DocumentCard(document: Document, isWarrantyCard: Boolean = false, onClick: () -> Unit) {
|
||||
@@ -39,7 +41,10 @@ private fun WarrantyCardContent(document: Document, onClick: () -> Unit) {
|
||||
}
|
||||
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth().clickable(onClick = onClick),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(AccessibilityIds.Document.documentCard)
|
||||
.clickable(onClick = onClick),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
@@ -142,7 +147,10 @@ private fun RegularDocumentCardContent(document: Document, onClick: () -> Unit)
|
||||
}
|
||||
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth().clickable(onClick = onClick),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(AccessibilityIds.Document.documentCard)
|
||||
.clickable(onClick = onClick),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
) {
|
||||
|
||||
@@ -12,9 +12,13 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun EmptyState(icon: ImageVector, message: String) {
|
||||
fun EmptyState(
|
||||
icon: ImageVector,
|
||||
message: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize().padding(16.dp),
|
||||
modifier = modifier.fillMaxSize().padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
|
||||
@@ -12,9 +12,11 @@ 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.unit.dp
|
||||
import com.tt.honeyDue.models.Document
|
||||
import com.tt.honeyDue.network.ApiResult
|
||||
import com.tt.honeyDue.testing.AccessibilityIds
|
||||
import com.tt.honeyDue.ui.subscription.UpgradeFeatureScreen
|
||||
import com.tt.honeyDue.utils.SubscriptionHelper
|
||||
|
||||
@@ -61,6 +63,7 @@ fun DocumentsTabContent(
|
||||
} else {
|
||||
// Pro users see empty state
|
||||
EmptyState(
|
||||
modifier = Modifier.testTag(AccessibilityIds.Document.emptyStateView),
|
||||
icon = if (isWarrantyTab) Icons.Default.ReceiptLong else Icons.Default.Description,
|
||||
message = if (isWarrantyTab) "No warranties found" else "No documents found"
|
||||
)
|
||||
@@ -75,7 +78,9 @@ fun DocumentsTabContent(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.testTag(AccessibilityIds.Document.documentsList),
|
||||
contentPadding = PaddingValues(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 96.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
|
||||
@@ -15,9 +15,13 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.testTagsAsResourceId
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.tt.honeyDue.testing.AccessibilityIds
|
||||
import com.tt.honeyDue.ui.components.ApiResultHandler
|
||||
import com.tt.honeyDue.ui.components.HandleErrors
|
||||
import com.tt.honeyDue.viewmodel.DocumentViewModel
|
||||
@@ -76,6 +80,7 @@ fun DocumentDetailScreen(
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.semantics { testTagsAsResourceId = true },
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(Res.string.documents_details), fontWeight = FontWeight.Bold) },
|
||||
@@ -87,10 +92,16 @@ fun DocumentDetailScreen(
|
||||
actions = {
|
||||
when (documentState) {
|
||||
is ApiResult.Success -> {
|
||||
IconButton(onClick = { onNavigateToEdit(documentId) }) {
|
||||
IconButton(
|
||||
modifier = Modifier.testTag(AccessibilityIds.Document.editButton),
|
||||
onClick = { onNavigateToEdit(documentId) }
|
||||
) {
|
||||
Icon(Icons.Default.Edit, stringResource(Res.string.common_edit))
|
||||
}
|
||||
IconButton(onClick = { showDeleteDialog = true }) {
|
||||
IconButton(
|
||||
modifier = Modifier.testTag(AccessibilityIds.Document.deleteButton),
|
||||
onClick = { showDeleteDialog = true }
|
||||
) {
|
||||
Icon(Icons.Default.Delete, stringResource(Res.string.common_delete), tint = Color.Red)
|
||||
}
|
||||
}
|
||||
@@ -105,6 +116,7 @@ fun DocumentDetailScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
.testTag(AccessibilityIds.Document.detailView)
|
||||
) {
|
||||
ApiResultHandler(
|
||||
state = documentState,
|
||||
@@ -432,6 +444,7 @@ fun DocumentDetailScreen(
|
||||
text = { Text(stringResource(Res.string.documents_delete_warning)) },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
modifier = Modifier.testTag(AccessibilityIds.Alert.deleteButton),
|
||||
onClick = {
|
||||
documentViewModel.deleteDocument(documentId)
|
||||
showDeleteDialog = false
|
||||
|
||||
@@ -15,11 +15,15 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.testTagsAsResourceId
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import coil3.compose.AsyncImage
|
||||
import com.tt.honeyDue.testing.AccessibilityIds
|
||||
import com.tt.honeyDue.ui.components.AuthenticatedImage
|
||||
import com.tt.honeyDue.viewmodel.DocumentViewModel
|
||||
import com.tt.honeyDue.viewmodel.ResidenceViewModel
|
||||
@@ -184,6 +188,7 @@ fun DocumentFormScreen(
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.semantics { testTagsAsResourceId = true },
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
@@ -197,7 +202,10 @@ fun DocumentFormScreen(
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigateBack) {
|
||||
IconButton(
|
||||
modifier = Modifier.testTag(AccessibilityIds.Document.formCancelButton),
|
||||
onClick = onNavigateBack
|
||||
) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, stringResource(Res.string.common_back))
|
||||
}
|
||||
}
|
||||
@@ -247,7 +255,10 @@ fun DocumentFormScreen(
|
||||
{ Text(residenceError) }
|
||||
} else null,
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = residenceExpanded) },
|
||||
modifier = Modifier.fillMaxWidth().menuAnchor()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.menuAnchor()
|
||||
.testTag(AccessibilityIds.Document.residencePicker)
|
||||
)
|
||||
ExposedDropdownMenu(
|
||||
expanded = residenceExpanded,
|
||||
@@ -287,7 +298,10 @@ fun DocumentFormScreen(
|
||||
readOnly = true,
|
||||
label = { Text(stringResource(Res.string.documents_form_document_type_required)) },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = documentTypeExpanded) },
|
||||
modifier = Modifier.fillMaxWidth().menuAnchor()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.menuAnchor()
|
||||
.testTag(AccessibilityIds.Document.typePicker)
|
||||
)
|
||||
ExposedDropdownMenu(
|
||||
expanded = documentTypeExpanded,
|
||||
@@ -317,7 +331,9 @@ fun DocumentFormScreen(
|
||||
supportingText = if (titleError.isNotEmpty()) {
|
||||
{ Text(titleError) }
|
||||
} else null,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(AccessibilityIds.Document.titleField)
|
||||
)
|
||||
|
||||
// Warranty-specific fields
|
||||
@@ -333,21 +349,27 @@ fun DocumentFormScreen(
|
||||
supportingText = if (itemNameError.isNotEmpty()) {
|
||||
{ Text(itemNameError) }
|
||||
} else null,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(AccessibilityIds.Document.itemNameField)
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = modelNumber,
|
||||
onValueChange = { modelNumber = it },
|
||||
label = { Text(stringResource(Res.string.documents_form_model_number)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(AccessibilityIds.Document.modelNumberField)
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = serialNumber,
|
||||
onValueChange = { serialNumber = it },
|
||||
label = { Text(stringResource(Res.string.documents_form_serial_number)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(AccessibilityIds.Document.serialNumberField)
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
@@ -361,14 +383,18 @@ fun DocumentFormScreen(
|
||||
supportingText = if (providerError.isNotEmpty()) {
|
||||
{ Text(providerError) }
|
||||
} else null,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(AccessibilityIds.Document.providerField)
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = providerContact,
|
||||
onValueChange = { providerContact = it },
|
||||
label = { Text(stringResource(Res.string.documents_form_provider_contact)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(AccessibilityIds.Document.providerContactField)
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
@@ -416,7 +442,9 @@ fun DocumentFormScreen(
|
||||
onValueChange = { endDate = it },
|
||||
label = { Text(stringResource(Res.string.documents_form_warranty_end_required)) },
|
||||
placeholder = { Text(stringResource(Res.string.documents_form_date_placeholder_end)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(AccessibilityIds.Document.expirationDatePicker)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -441,7 +469,10 @@ fun DocumentFormScreen(
|
||||
readOnly = true,
|
||||
label = { Text(stringResource(Res.string.documents_form_category)) },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = categoryExpanded) },
|
||||
modifier = Modifier.fillMaxWidth().menuAnchor()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.menuAnchor()
|
||||
.testTag(AccessibilityIds.Document.categoryPicker)
|
||||
)
|
||||
ExposedDropdownMenu(
|
||||
expanded = categoryExpanded,
|
||||
@@ -473,7 +504,9 @@ fun DocumentFormScreen(
|
||||
onValueChange = { tags = it },
|
||||
label = { Text(stringResource(Res.string.documents_form_tags)) },
|
||||
placeholder = { Text(stringResource(Res.string.documents_form_tags_placeholder)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(AccessibilityIds.Document.tagsField)
|
||||
)
|
||||
|
||||
// Notes
|
||||
@@ -482,7 +515,9 @@ fun DocumentFormScreen(
|
||||
onValueChange = { notes = it },
|
||||
label = { Text(stringResource(Res.string.documents_form_notes)) },
|
||||
minLines = 3,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(AccessibilityIds.Document.notesField)
|
||||
)
|
||||
|
||||
// Active toggle (edit mode only)
|
||||
@@ -638,6 +673,7 @@ fun DocumentFormScreen(
|
||||
|
||||
// Save Button
|
||||
OrganicPrimaryButton(
|
||||
modifier = Modifier.testTag(AccessibilityIds.Document.saveButton),
|
||||
text = when {
|
||||
isEditMode && isWarranty -> stringResource(Res.string.documents_form_update_warranty)
|
||||
isEditMode -> stringResource(Res.string.documents_form_update_document)
|
||||
|
||||
@@ -9,10 +9,14 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.testTagsAsResourceId
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tt.honeyDue.testing.AccessibilityIds
|
||||
import com.tt.honeyDue.ui.components.documents.DocumentsTabContent
|
||||
import com.tt.honeyDue.ui.subscription.UpgradeFeatureScreen
|
||||
import com.tt.honeyDue.utils.SubscriptionHelper
|
||||
@@ -79,6 +83,7 @@ fun DocumentsScreen(
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.semantics { testTagsAsResourceId = true },
|
||||
topBar = {
|
||||
Column {
|
||||
TopAppBar(
|
||||
@@ -102,7 +107,10 @@ fun DocumentsScreen(
|
||||
|
||||
// Filter menu
|
||||
Box {
|
||||
IconButton(onClick = { showFiltersMenu = true }) {
|
||||
IconButton(
|
||||
modifier = Modifier.testTag(AccessibilityIds.Common.filterButton),
|
||||
onClick = { showFiltersMenu = true }
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.FilterList,
|
||||
stringResource(Res.string.documents_filters),
|
||||
@@ -179,6 +187,7 @@ fun DocumentsScreen(
|
||||
if (!isBlocked.allowed) {
|
||||
Box(modifier = Modifier.padding(bottom = 80.dp)) {
|
||||
FloatingActionButton(
|
||||
modifier = Modifier.testTag(AccessibilityIds.Document.addButton),
|
||||
onClick = {
|
||||
// Check if user can add based on current count
|
||||
val canAdd = SubscriptionHelper.canAddDocument(currentCount)
|
||||
|
||||
Reference in New Issue
Block a user