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:
Trey T
2026-04-18 14:54:24 -05:00
parent 840c35a7af
commit 227c0a9240
7 changed files with 762 additions and 21 deletions

View File

@@ -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)
) {

View File

@@ -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
) {

View File

@@ -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)
) {

View File

@@ -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

View File

@@ -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)

View File

@@ -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)