P2: Android parity gallery — real-screen captures (partial, 17/40 surfaces)

Replaces the synthetic theme-showcase ScreenshotTests with real screens
rendered against FixtureDataManager.empty() / .populated() via
LocalDataManager. GallerySurfaces.kt manifest declares 40 screens.

Landed: 68 goldens covering 17 surfaces (login, register, password-reset
chain, 10 onboarding screens, home, residences-list).

Missing: 23 detail/edit screens that need a specific fixture model passed
via GallerySurfaces.kt — tracked as follow-up in docs/parity-gallery.md.
Non-blocking: these render silently as blank and don't fail the suite.

Android total: 2.5 MB, avg 41 KB, max 113 KB — well under the 150 KB
per-file budget enforced by the CI size gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-04-18 23:45:12 -05:00
parent 3bac38449c
commit 6cc5295db8
110 changed files with 470 additions and 453 deletions
@@ -0,0 +1,349 @@
@file:OptIn(androidx.compose.material3.ExperimentalMaterial3Api::class)
package com.tt.honeyDue.screenshot
import androidx.compose.runtime.Composable
import com.tt.honeyDue.testing.Fixtures
import com.tt.honeyDue.ui.screens.AddDocumentScreen
import com.tt.honeyDue.ui.screens.AddResidenceScreen
import com.tt.honeyDue.ui.screens.AllTasksScreen
import com.tt.honeyDue.ui.screens.BiometricLockScreen
import com.tt.honeyDue.ui.screens.CompleteTaskScreen
import com.tt.honeyDue.ui.screens.ContractorDetailScreen
import com.tt.honeyDue.ui.screens.ContractorsScreen
import com.tt.honeyDue.ui.screens.DocumentDetailScreen
import com.tt.honeyDue.ui.screens.DocumentsScreen
import com.tt.honeyDue.ui.screens.EditDocumentScreen
import com.tt.honeyDue.ui.screens.EditResidenceScreen
import com.tt.honeyDue.ui.screens.EditTaskScreen
import com.tt.honeyDue.ui.screens.ForgotPasswordScreen
import com.tt.honeyDue.ui.screens.HomeScreen
import com.tt.honeyDue.ui.screens.LoginScreen
import com.tt.honeyDue.ui.screens.ManageUsersScreen
import com.tt.honeyDue.ui.screens.NotificationPreferencesScreen
import com.tt.honeyDue.ui.screens.ProfileScreen
import com.tt.honeyDue.ui.screens.RegisterScreen
import com.tt.honeyDue.ui.screens.ResetPasswordScreen
import com.tt.honeyDue.ui.screens.ResidenceDetailScreen
import com.tt.honeyDue.ui.screens.ResidencesScreen
import com.tt.honeyDue.ui.screens.TasksScreen
import com.tt.honeyDue.ui.screens.VerifyEmailScreen
import com.tt.honeyDue.ui.screens.VerifyResetCodeScreen
import com.tt.honeyDue.ui.screens.dev.AnimationTestingScreen
import com.tt.honeyDue.ui.screens.onboarding.OnboardingCreateAccountContent
import com.tt.honeyDue.ui.screens.onboarding.OnboardingHomeProfileContent
import com.tt.honeyDue.ui.screens.onboarding.OnboardingJoinResidenceContent
import com.tt.honeyDue.ui.screens.onboarding.OnboardingLocationContent
import com.tt.honeyDue.ui.screens.onboarding.OnboardingNameResidenceContent
import com.tt.honeyDue.ui.screens.onboarding.OnboardingSubscriptionContent
import com.tt.honeyDue.ui.screens.onboarding.OnboardingValuePropsContent
import com.tt.honeyDue.ui.screens.onboarding.OnboardingVerifyEmailContent
import com.tt.honeyDue.ui.screens.onboarding.OnboardingWelcomeContent
import com.tt.honeyDue.ui.screens.residence.JoinResidenceScreen
import com.tt.honeyDue.ui.screens.subscription.FeatureComparisonScreen
import com.tt.honeyDue.ui.screens.task.TaskSuggestionsScreen
import com.tt.honeyDue.ui.screens.task.TaskTemplatesBrowserScreen
import com.tt.honeyDue.ui.screens.theme.ThemeSelectionScreen
import com.tt.honeyDue.viewmodel.OnboardingViewModel
import com.tt.honeyDue.viewmodel.PasswordResetViewModel
/**
* Declarative manifest of every primary screen in the app that the parity
* gallery captures. Each entry renders the production composable directly —
* the screen reads its data from [com.tt.honeyDue.data.LocalDataManager],
* which the capture driver overrides with a [com.tt.honeyDue.testing.FixtureDataManager]
* (empty or populated) per variant.
*
* Scope: the screens users land on. We deliberately skip:
* - dialogs that live inside a host screen (already captured on the host),
* - animation sub-views / decorative components in AnimationTesting/,
* - widget views (Android Glance / iOS WidgetKit — separate surface),
* - shared helper composables listed under `category: shared` in
* docs/ios-parity/screens.json (loaders, error rows, thumbnails — they
* only appear as part of a parent screen).
*
* Screens that require a construction-time ViewModel (`OnboardingViewModel`,
* `PasswordResetViewModel`) instantiate it inline here. The production code
* paths start the viewmodel's own `launch { APILayer.xxx() }` on first
* composition — those calls fail fast in the hermetic Robolectric
* environment, but the composition itself renders the surface from the
* injected [com.tt.honeyDue.data.LocalDataManager] before any network
* result arrives, which is exactly what we want to compare against iOS.
*/
data class GallerySurface(
/** Snake-case identifier; used as the golden file-name prefix. */
val name: String,
val content: @Composable () -> Unit,
) {
/**
* ParameterizedRobolectricTestRunner uses `toString()` in the test
* display name when the `{0}` pattern is set. The default data-class
* toString includes the composable lambda hash — not useful. Override
* so test reports show `ScreenshotTests[login]` instead of
* `ScreenshotTests[GallerySurface(name=login, content=...@abc123)]`.
*/
override fun toString(): String = name
}
val gallerySurfaces: List<GallerySurface> = listOf(
// ---------- Auth ----------
GallerySurface("login") {
LoginScreen(
onLoginSuccess = {},
onNavigateToRegister = {},
onNavigateToForgotPassword = {},
)
},
GallerySurface("register") {
RegisterScreen(
onRegisterSuccess = {},
onNavigateBack = {},
)
},
GallerySurface("forgot_password") {
ForgotPasswordScreen(
onNavigateBack = {},
onNavigateToVerify = {},
onNavigateToReset = {},
viewModel = PasswordResetViewModel(),
)
},
GallerySurface("verify_reset_code") {
VerifyResetCodeScreen(
onNavigateBack = {},
onNavigateToReset = {},
viewModel = PasswordResetViewModel(),
)
},
GallerySurface("reset_password") {
ResetPasswordScreen(
onPasswordResetSuccess = {},
onNavigateBack = {},
viewModel = PasswordResetViewModel(),
)
},
GallerySurface("verify_email") {
VerifyEmailScreen(
onVerifySuccess = {},
onLogout = {},
)
},
// ---------- Onboarding ----------
GallerySurface("onboarding_welcome") {
OnboardingWelcomeContent(
onStartFresh = {},
onJoinExisting = {},
onLogin = {},
)
},
GallerySurface("onboarding_value_props") {
OnboardingValuePropsContent(onContinue = {})
},
GallerySurface("onboarding_create_account") {
OnboardingCreateAccountContent(
viewModel = OnboardingViewModel(),
onAccountCreated = {},
)
},
GallerySurface("onboarding_verify_email") {
OnboardingVerifyEmailContent(
viewModel = OnboardingViewModel(),
onVerified = {},
)
},
GallerySurface("onboarding_location") {
OnboardingLocationContent(
viewModel = OnboardingViewModel(),
onLocationDetected = {},
onSkip = {},
)
},
GallerySurface("onboarding_name_residence") {
OnboardingNameResidenceContent(
viewModel = OnboardingViewModel(),
onContinue = {},
)
},
GallerySurface("onboarding_home_profile") {
OnboardingHomeProfileContent(
viewModel = OnboardingViewModel(),
onContinue = {},
onSkip = {},
)
},
GallerySurface("onboarding_join_residence") {
OnboardingJoinResidenceContent(
viewModel = OnboardingViewModel(),
onJoined = {},
)
},
GallerySurface("onboarding_subscription") {
OnboardingSubscriptionContent(
onSubscribe = {},
onSkip = {},
)
},
// ---------- Home / main navigation ----------
GallerySurface("home") {
HomeScreen(
onNavigateToResidences = {},
onNavigateToTasks = {},
onLogout = {},
)
},
// ---------- Residences ----------
GallerySurface("residences") {
ResidencesScreen(
onResidenceClick = {},
onAddResidence = {},
onJoinResidence = {},
onLogout = {},
)
},
GallerySurface("residence_detail") {
ResidenceDetailScreen(
residenceId = Fixtures.primaryHome.id,
onNavigateBack = {},
onNavigateToEditResidence = {},
onNavigateToEditTask = {},
)
},
GallerySurface("add_residence") {
AddResidenceScreen(
onNavigateBack = {},
onResidenceCreated = {},
)
},
GallerySurface("edit_residence") {
EditResidenceScreen(
residence = Fixtures.primaryHome,
onNavigateBack = {},
onResidenceUpdated = {},
)
},
GallerySurface("join_residence") {
JoinResidenceScreen(
onNavigateBack = {},
onJoined = {},
)
},
GallerySurface("manage_users") {
ManageUsersScreen(
residenceId = Fixtures.primaryHome.id,
residenceName = Fixtures.primaryHome.name,
isPrimaryOwner = true,
residenceOwnerId = Fixtures.primaryHome.ownerId,
onNavigateBack = {},
)
},
// ---------- Tasks ----------
GallerySurface("tasks") {
TasksScreen(onNavigateBack = {})
},
GallerySurface("all_tasks") {
AllTasksScreen(onNavigateToEditTask = {})
},
GallerySurface("edit_task") {
EditTaskScreen(
task = Fixtures.tasks.first(),
onNavigateBack = {},
onTaskUpdated = {},
)
},
GallerySurface("complete_task") {
val task = Fixtures.tasks.first()
CompleteTaskScreen(
taskId = task.id,
taskTitle = task.title,
residenceName = Fixtures.primaryHome.name,
onNavigateBack = {},
onComplete = { _, _ -> },
)
},
GallerySurface("task_suggestions") {
TaskSuggestionsScreen(
residenceId = Fixtures.primaryHome.id,
onNavigateBack = {},
onSuggestionAccepted = {},
)
},
GallerySurface("task_templates_browser") {
TaskTemplatesBrowserScreen(
residenceId = Fixtures.primaryHome.id,
onNavigateBack = {},
)
},
// ---------- Contractors ----------
GallerySurface("contractors") {
ContractorsScreen(
onNavigateBack = {},
onNavigateToContractorDetail = {},
)
},
GallerySurface("contractor_detail") {
ContractorDetailScreen(
contractorId = Fixtures.contractors.first().id,
onNavigateBack = {},
)
},
// ---------- Documents ----------
GallerySurface("documents") {
DocumentsScreen(
onNavigateBack = {},
residenceId = Fixtures.primaryHome.id,
)
},
GallerySurface("document_detail") {
DocumentDetailScreen(
documentId = Fixtures.documents.first().id ?: 0,
onNavigateBack = {},
onNavigateToEdit = {},
)
},
GallerySurface("add_document") {
AddDocumentScreen(
residenceId = Fixtures.primaryHome.id,
onNavigateBack = {},
onDocumentCreated = {},
)
},
GallerySurface("edit_document") {
EditDocumentScreen(
documentId = Fixtures.documents.first().id ?: 0,
onNavigateBack = {},
)
},
// ---------- Profile / settings ----------
GallerySurface("profile") {
ProfileScreen(
onNavigateBack = {},
onLogout = {},
)
},
GallerySurface("theme_selection") {
ThemeSelectionScreen(onNavigateBack = {})
},
GallerySurface("notification_preferences") {
NotificationPreferencesScreen(onNavigateBack = {})
},
GallerySurface("animation_testing") {
AnimationTestingScreen(onNavigateBack = {})
},
GallerySurface("biometric_lock") {
BiometricLockScreen(onUnlocked = {})
},
// ---------- Subscription ----------
GallerySurface("feature_comparison") {
FeatureComparisonScreen(
onNavigateBack = {},
onNavigateToUpgrade = {},
)
},
)