UI Test Suite5: Task tests (iOS parity)
Ports Suite5_TaskTests.swift. testTags on task screens via AccessibilityIds.Task.*. CRUD + kanban + filter/sort + templates + suggestions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,9 +8,11 @@ import androidx.compose.material3.*
|
||||
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.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tt.honeyDue.data.DataManager
|
||||
import com.tt.honeyDue.testing.AccessibilityIds
|
||||
import com.tt.honeyDue.models.TaskTemplate
|
||||
import com.tt.honeyDue.repository.LookupsRepository
|
||||
import com.tt.honeyDue.models.MyResidencesResponse
|
||||
@@ -150,7 +152,8 @@ fun AddTaskDialog(
|
||||
label = { Text(stringResource(Res.string.tasks_property_required)) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.menuAnchor(),
|
||||
.menuAnchor()
|
||||
.testTag(AccessibilityIds.Task.residencePicker),
|
||||
isError = residenceError,
|
||||
supportingText = if (residenceError) {
|
||||
{ Text(stringResource(Res.string.tasks_property_error)) }
|
||||
@@ -191,7 +194,9 @@ fun AddTaskDialog(
|
||||
showSuggestions = it.length >= 2 && filteredSuggestions.isNotEmpty()
|
||||
},
|
||||
label = { Text(stringResource(Res.string.tasks_title_required)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(AccessibilityIds.Task.titleField),
|
||||
isError = titleError,
|
||||
supportingText = if (titleError) {
|
||||
{ Text(stringResource(Res.string.tasks_title_error)) }
|
||||
@@ -216,7 +221,9 @@ fun AddTaskDialog(
|
||||
value = description,
|
||||
onValueChange = { description = it },
|
||||
label = { Text(stringResource(Res.string.tasks_description_label)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(AccessibilityIds.Task.descriptionField),
|
||||
minLines = 2,
|
||||
maxLines = 4
|
||||
)
|
||||
@@ -232,7 +239,8 @@ fun AddTaskDialog(
|
||||
label = { Text(stringResource(Res.string.tasks_category_required)) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.menuAnchor(),
|
||||
.menuAnchor()
|
||||
.testTag(AccessibilityIds.Task.categoryPicker),
|
||||
isError = categoryError,
|
||||
supportingText = if (categoryError) {
|
||||
{ Text(stringResource(Res.string.tasks_category_error)) }
|
||||
@@ -269,7 +277,8 @@ fun AddTaskDialog(
|
||||
label = { Text(stringResource(Res.string.tasks_frequency_label)) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.menuAnchor(),
|
||||
.menuAnchor()
|
||||
.testTag(AccessibilityIds.Task.frequencyPicker),
|
||||
readOnly = true,
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = showFrequencyDropdown) },
|
||||
enabled = frequencies.isNotEmpty()
|
||||
@@ -300,7 +309,9 @@ fun AddTaskDialog(
|
||||
value = intervalDays,
|
||||
onValueChange = { intervalDays = it.filter { char -> char.isDigit() } },
|
||||
label = { Text(stringResource(Res.string.tasks_interval_days)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(AccessibilityIds.Task.intervalDaysField),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
supportingText = { Text(stringResource(Res.string.tasks_custom_interval_help)) },
|
||||
singleLine = true
|
||||
@@ -315,7 +326,9 @@ fun AddTaskDialog(
|
||||
dueDateError = false
|
||||
},
|
||||
label = { Text(stringResource(Res.string.tasks_due_date_required)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(AccessibilityIds.Task.dueDatePicker),
|
||||
isError = dueDateError,
|
||||
supportingText = if (dueDateError) {
|
||||
{ Text(stringResource(Res.string.tasks_due_date_format_error)) }
|
||||
@@ -336,7 +349,8 @@ fun AddTaskDialog(
|
||||
label = { Text(stringResource(Res.string.tasks_priority_label)) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.menuAnchor(),
|
||||
.menuAnchor()
|
||||
.testTag(AccessibilityIds.Task.priorityPicker),
|
||||
readOnly = true,
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = showPriorityDropdown) },
|
||||
enabled = priorities.isNotEmpty()
|
||||
@@ -362,7 +376,9 @@ fun AddTaskDialog(
|
||||
value = estimatedCost,
|
||||
onValueChange = { estimatedCost = it },
|
||||
label = { Text(stringResource(Res.string.tasks_estimated_cost_label)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(AccessibilityIds.Task.estimatedCostField),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
|
||||
prefix = { Text("$") },
|
||||
singleLine = true
|
||||
@@ -381,6 +397,7 @@ fun AddTaskDialog(
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
modifier = Modifier.testTag(AccessibilityIds.Task.saveButton),
|
||||
onClick = {
|
||||
// Validation
|
||||
var hasError = false
|
||||
@@ -433,7 +450,10 @@ fun AddTaskDialog(
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
TextButton(
|
||||
modifier = Modifier.testTag(AccessibilityIds.Task.formCancelButton),
|
||||
onClick = onDismiss
|
||||
) {
|
||||
Text(stringResource(Res.string.common_cancel))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,11 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import honeydue.composeapp.generated.resources.*
|
||||
import com.tt.honeyDue.testing.AccessibilityIds
|
||||
import com.tt.honeyDue.models.TaskDetail
|
||||
import com.tt.honeyDue.models.TaskCategory
|
||||
import com.tt.honeyDue.models.TaskPriority
|
||||
@@ -38,7 +40,9 @@ fun TaskCard(
|
||||
onCompletionHistoryClick: (() -> Unit)? = null
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(AccessibilityIds.Task.taskCard),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
|
||||
@@ -18,10 +18,12 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tt.honeyDue.models.TaskColumn
|
||||
import com.tt.honeyDue.models.TaskDetail
|
||||
import com.tt.honeyDue.testing.AccessibilityIds
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
@@ -275,7 +277,7 @@ fun DynamicTaskKanbanView(
|
||||
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
modifier = modifier.fillMaxSize(),
|
||||
modifier = modifier.fillMaxSize().testTag(AccessibilityIds.Task.kanbanView),
|
||||
pageSpacing = 16.dp,
|
||||
contentPadding = PaddingValues(start = 16.dp, end = 48.dp)
|
||||
) { page ->
|
||||
|
||||
@@ -13,6 +13,8 @@ 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 androidx.compose.ui.platform.testTag
|
||||
import com.tt.honeyDue.testing.AccessibilityIds
|
||||
import com.tt.honeyDue.ui.components.AddNewTaskWithResidenceDialog
|
||||
import com.tt.honeyDue.ui.components.ApiResultHandler
|
||||
import com.tt.honeyDue.ui.components.CompleteTaskDialog
|
||||
@@ -120,7 +122,8 @@ fun AllTasksScreen(
|
||||
},
|
||||
actions = {
|
||||
IconButton(
|
||||
onClick = { viewModel.loadTasks(forceRefresh = true) }
|
||||
onClick = { viewModel.loadTasks(forceRefresh = true) },
|
||||
modifier = Modifier.testTag(AccessibilityIds.Task.refreshButton)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Refresh,
|
||||
@@ -130,7 +133,8 @@ fun AllTasksScreen(
|
||||
IconButton(
|
||||
onClick = { showNewTaskDialog = true },
|
||||
enabled = myResidencesState is ApiResult.Success &&
|
||||
(myResidencesState as ApiResult.Success).data.residences.isNotEmpty()
|
||||
(myResidencesState as ApiResult.Success).data.residences.isNotEmpty(),
|
||||
modifier = Modifier.testTag(AccessibilityIds.Task.addButton)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Add,
|
||||
@@ -185,7 +189,9 @@ fun AllTasksScreen(
|
||||
OrganicPrimaryButton(
|
||||
text = "Add Task",
|
||||
onClick = { showNewTaskDialog = true },
|
||||
modifier = Modifier.fillMaxWidth(0.7f),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.7f)
|
||||
.testTag(AccessibilityIds.Task.emptyStateView),
|
||||
enabled = myResidencesState is ApiResult.Success &&
|
||||
(myResidencesState as ApiResult.Success).data.residences.isNotEmpty()
|
||||
)
|
||||
|
||||
@@ -20,11 +20,13 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
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 com.tt.honeyDue.testing.AccessibilityIds
|
||||
import honeydue.composeapp.generated.resources.*
|
||||
import com.tt.honeyDue.models.TaskCompletionCreateRequest
|
||||
import com.tt.honeyDue.models.ContractorSummary
|
||||
@@ -87,7 +89,10 @@ fun CompleteTaskScreen(
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigateBack) {
|
||||
IconButton(
|
||||
onClick = onNavigateBack,
|
||||
modifier = Modifier.testTag(AccessibilityIds.Task.detailCancelButton)
|
||||
) {
|
||||
Icon(Icons.Default.Close, contentDescription = stringResource(Res.string.common_cancel))
|
||||
}
|
||||
},
|
||||
@@ -230,7 +235,9 @@ fun CompleteTaskScreen(
|
||||
label = { Text(stringResource(Res.string.completions_actual_cost_optional)) },
|
||||
leadingIcon = { Icon(Icons.Default.AttachMoney, null) },
|
||||
prefix = { Text("$") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(AccessibilityIds.Task.actualCostField),
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
|
||||
shape = OrganicShapes.medium
|
||||
@@ -252,7 +259,8 @@ fun CompleteTaskScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = OrganicSpacing.lg)
|
||||
.height(120.dp),
|
||||
.height(120.dp)
|
||||
.testTag(AccessibilityIds.Task.notesField),
|
||||
shape = OrganicShapes.medium
|
||||
)
|
||||
|
||||
@@ -416,7 +424,8 @@ fun CompleteTaskScreen(
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = OrganicSpacing.lg),
|
||||
.padding(horizontal = OrganicSpacing.lg)
|
||||
.testTag(AccessibilityIds.Task.submitButton),
|
||||
enabled = !isSubmitting,
|
||||
isLoading = isSubmitting,
|
||||
icon = Icons.Default.CheckCircle
|
||||
|
||||
@@ -9,10 +9,12 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
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 com.tt.honeyDue.testing.AccessibilityIds
|
||||
import com.tt.honeyDue.ui.components.HandleErrors
|
||||
import com.tt.honeyDue.viewmodel.ResidenceViewModel
|
||||
import com.tt.honeyDue.repository.LookupsRepository
|
||||
@@ -98,7 +100,10 @@ fun EditTaskScreen(
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(Res.string.tasks_edit_title)) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigateBack) {
|
||||
IconButton(
|
||||
onClick = onNavigateBack,
|
||||
modifier = Modifier.testTag(AccessibilityIds.Task.formCancelButton)
|
||||
) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(Res.string.common_back))
|
||||
}
|
||||
}
|
||||
@@ -126,7 +131,9 @@ fun EditTaskScreen(
|
||||
value = title,
|
||||
onValueChange = { title = it },
|
||||
label = { Text(stringResource(Res.string.tasks_title_required)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(AccessibilityIds.Task.titleField),
|
||||
isError = titleError.isNotEmpty(),
|
||||
supportingText = if (titleError.isNotEmpty()) {
|
||||
{ Text(titleError) }
|
||||
@@ -137,7 +144,9 @@ fun EditTaskScreen(
|
||||
value = description,
|
||||
onValueChange = { description = it },
|
||||
label = { Text(stringResource(Res.string.tasks_description_label)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(AccessibilityIds.Task.descriptionField),
|
||||
minLines = 3,
|
||||
maxLines = 5
|
||||
)
|
||||
@@ -304,6 +313,7 @@ fun EditTaskScreen(
|
||||
// Submit button
|
||||
OrganicPrimaryButton(
|
||||
text = stringResource(Res.string.tasks_update),
|
||||
modifier = Modifier.testTag(AccessibilityIds.Task.saveButton),
|
||||
onClick = {
|
||||
if (validateForm() && selectedCategory != null &&
|
||||
selectedFrequency != null && selectedPriority != null) {
|
||||
|
||||
@@ -31,6 +31,7 @@ import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -38,6 +39,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tt.honeyDue.network.ApiResult
|
||||
import com.tt.honeyDue.repository.LookupsRepository
|
||||
import com.tt.honeyDue.testing.AccessibilityIds
|
||||
import com.tt.honeyDue.ui.components.common.StandardCard
|
||||
import com.tt.honeyDue.ui.components.forms.FormTextField
|
||||
import com.tt.honeyDue.ui.theme.AppRadius
|
||||
@@ -88,7 +90,10 @@ fun AddTaskWithResidenceScreen(
|
||||
TopAppBar(
|
||||
title = { Text("New Task", fontWeight = FontWeight.SemiBold) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigateBack) {
|
||||
IconButton(
|
||||
onClick = onNavigateBack,
|
||||
modifier = Modifier.testTag(AccessibilityIds.Task.formCancelButton)
|
||||
) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||
}
|
||||
},
|
||||
@@ -112,6 +117,7 @@ fun AddTaskWithResidenceScreen(
|
||||
value = title,
|
||||
onValueChange = viewModel::onTitleChange,
|
||||
label = "Title",
|
||||
modifier = Modifier.testTag(AccessibilityIds.Task.titleField),
|
||||
placeholder = "e.g. Flush water heater",
|
||||
error = titleError,
|
||||
enabled = !isSubmitting
|
||||
@@ -123,6 +129,7 @@ fun AddTaskWithResidenceScreen(
|
||||
value = description,
|
||||
onValueChange = viewModel::onDescriptionChange,
|
||||
label = "Description",
|
||||
modifier = Modifier.testTag(AccessibilityIds.Task.descriptionField),
|
||||
placeholder = "Optional details",
|
||||
singleLine = false,
|
||||
maxLines = 4,
|
||||
@@ -242,7 +249,8 @@ fun AddTaskWithResidenceScreen(
|
||||
enabled = canSubmit && !isSubmitting,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(52.dp),
|
||||
.height(52.dp)
|
||||
.testTag(AccessibilityIds.Task.saveButton),
|
||||
shape = androidx.compose.foundation.shape.RoundedCornerShape(AppRadius.md),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.primary
|
||||
|
||||
Reference in New Issue
Block a user