diff --git a/iosApp/iosApp/Helpers/AccessibilityIdentifiers.swift b/iosApp/iosApp/Helpers/AccessibilityIdentifiers.swift index 7eee095..56c5e45 100644 --- a/iosApp/iosApp/Helpers/AccessibilityIdentifiers.swift +++ b/iosApp/iosApp/Helpers/AccessibilityIdentifiers.swift @@ -48,6 +48,9 @@ struct AccessibilityIdentifiers { static let addButton = "Residence.AddButton" static let residencesList = "Residence.List" static let residenceCard = "Residence.Card" + /// Prefix for individual residence cells in the list. Suffix with the + /// residence id to address a specific cell (e.g. "Residence.Cell.42"). + static let cellPrefix = "Residence.Cell" static let emptyStateView = "Residence.EmptyState" static let emptyStateButton = "Residence.EmptyState.AddButton" @@ -87,7 +90,15 @@ struct AccessibilityIdentifiers { static let refreshButton = "Task.RefreshButton" static let tasksList = "Task.List" static let taskCard = "Task.Card" + /// Prefix for individual task rows. Suffix with the task id to + /// address a specific row (e.g. "Task.Row.42"). Use `BEGINSWITH` + /// in tests to detect "any task row exists". + static let rowPrefix = "Task.Row" static let emptyStateView = "Task.EmptyState" + /// Label rendered when a residence-detail tasks section has no tasks + /// in any kanban column. Asserted ABSENT after onboarding bulk-create + /// in the gitea#2 regression test. + static let noTasksLabel = "Task.NoTasksLabel" static let kanbanView = "Task.KanbanView" static let overdueColumn = "Task.Column.Overdue" static let upcomingColumn = "Task.Column.Upcoming" @@ -229,8 +240,24 @@ struct AccessibilityIdentifiers { static let taskSelectionCounter = "Onboarding.TaskSelectionCounter" static let addPopularTasksButton = "Onboarding.AddPopularTasksButton" static let addTasksContinueButton = "Onboarding.AddTasksContinueButton" + /// Submit/continue button at the bottom of the First-Task screen. + /// Triggers `POST /api/tasks/bulk/` for the selected templates. + static let submitTasksButton = "Onboarding.SubmitTasksButton" + /// Tab bar control above the task list. The "Browse All" segment is + /// addressed via `app.buttons["Browse All"]` from the segmented + /// picker once this identifier is set. + static let firstTaskTabBar = "Onboarding.FirstTaskTabBar" + /// Tab segment that shows the full template catalog. + /// Tap from a test by addressing the Picker's segment label + /// "Browse All" within the element identified above. + static let browseAllTab = "Onboarding.BrowseAllTab" static let taskCategorySection = "Onboarding.TaskCategorySection" static let taskTemplateRow = "Onboarding.TaskTemplateRow" + /// Prefix for individual template rows on the First-Task screen + /// (Browse All tab). Suffix with the backend template id — + /// e.g. `"Onboarding.TemplateRow.123"`. Tests use `BEGINSWITH` to + /// pick the first N rows deterministically without knowing ids. + static let templateRowPrefix = "Onboarding.TemplateRow" // Subscription Screen static let subscriptionTitle = "Onboarding.SubscriptionTitle" diff --git a/iosApp/iosApp/Onboarding/OnboardingFirstTaskView.swift b/iosApp/iosApp/Onboarding/OnboardingFirstTaskView.swift index f674036..3ce5e72 100644 --- a/iosApp/iosApp/Onboarding/OnboardingFirstTaskView.swift +++ b/iosApp/iosApp/Onboarding/OnboardingFirstTaskView.swift @@ -136,6 +136,7 @@ struct OnboardingFirstTaskContent: View { OnboardingTaskTabBar(selectedTab: $selectedTab) .padding(.horizontal, OrganicSpacing.comfortable) + .accessibilityIdentifier(AccessibilityIdentifiers.Onboarding.firstTaskTabBar) switch selectedTab { case .forYou: @@ -362,6 +363,7 @@ struct OnboardingFirstTaskContent: View { .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) .naturalShadow(selectedCount > 0 ? .medium : .subtle) } + .accessibilityIdentifier(AccessibilityIdentifiers.Onboarding.submitTasksButton) .disabled(vm.isSubmitting) .animation(.easeInOut(duration: 0.2), value: selectedCount) } @@ -631,6 +633,7 @@ private struct OnboardingSuggestionRow: View { .contentShape(Rectangle()) } .buttonStyle(.plain) + .accessibilityIdentifier("\(AccessibilityIdentifiers.Onboarding.templateRowPrefix).\(suggestion.template.id)") .accessibilityLabel("\(suggestion.template.title), \(suggestion.template.frequencyDisplay), \(relevancePercent)% match") .accessibilityValue(isSelected ? "selected" : "not selected") } @@ -776,6 +779,7 @@ private struct OnboardingTemplateRow: View { .contentShape(Rectangle()) } .buttonStyle(.plain) + .accessibilityIdentifier("\(AccessibilityIdentifiers.Onboarding.templateRowPrefix).\(template.id)") .accessibilityLabel("\(template.title), \(template.frequencyLabel)") .accessibilityValue(isSelected ? "selected" : "not selected") } diff --git a/iosApp/iosApp/Residence/ResidencesListView.swift b/iosApp/iosApp/Residence/ResidencesListView.swift index 9189d0b..9d6ef02 100644 --- a/iosApp/iosApp/Residence/ResidencesListView.swift +++ b/iosApp/iosApp/Residence/ResidencesListView.swift @@ -231,6 +231,7 @@ private struct ResidencesContent: View { .padding(.horizontal, 16) } .buttonStyle(OrganicCardButtonStyle()) + .accessibilityIdentifier("\(AccessibilityIdentifiers.Residence.cellPrefix).\(residence.id)") .transition(.asymmetric( insertion: .opacity.combined(with: .move(edge: .bottom)), removal: .opacity diff --git a/iosApp/iosApp/Subviews/Task/DynamicTaskCard.swift b/iosApp/iosApp/Subviews/Task/DynamicTaskCard.swift index a36d8c6..8cd285c 100644 --- a/iosApp/iosApp/Subviews/Task/DynamicTaskCard.swift +++ b/iosApp/iosApp/Subviews/Task/DynamicTaskCard.swift @@ -122,6 +122,7 @@ struct DynamicTaskCard: View { .cornerRadius(12) .shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2) .simultaneousGesture(TapGesture(), including: .subviews) + .accessibilityIdentifier("\(AccessibilityIdentifiers.Task.rowPrefix).\(task.id)") .sheet(isPresented: $showCompletionHistory) { CompletionHistorySheet( taskTitle: task.title, diff --git a/iosApp/iosApp/Subviews/Task/EmptyTasksView.swift b/iosApp/iosApp/Subviews/Task/EmptyTasksView.swift index 1c32104..b44a2d2 100644 --- a/iosApp/iosApp/Subviews/Task/EmptyTasksView.swift +++ b/iosApp/iosApp/Subviews/Task/EmptyTasksView.swift @@ -22,6 +22,8 @@ struct EmptyTasksView: View { .background(Color.appBackgroundSecondary) .clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous)) .naturalShadow(.subtle) + .accessibilityElement(children: .combine) + .accessibilityIdentifier(AccessibilityIdentifiers.Task.noTasksLabel) } }