diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 88d1189..79361b4 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -223,11 +223,21 @@ compose.desktop { } } -// Roborazzi screenshot-regression plugin (P8). Pin the golden-image -// output directory inside the test source set so goldens live in git -// alongside the tests themselves. Anything under build/ is gitignored -// and gets blown away by `gradle clean` — not where committed goldens -// belong. +// Roborazzi screenshot-regression plugin (parity gallery, P2). Pin the +// golden-image output directory inside the test source set so goldens live +// in git alongside the tests themselves. Anything under build/ is +// gitignored and gets blown away by `gradle clean` — not where committed +// goldens belong. +// +// NOTE on path mismatch: `captureRoboImage(filePath = ...)` in +// ScreenshotTests.kt takes a *relative path* resolved against the Gradle +// test task's working directory (`composeApp/`). We intentionally point +// that same path at `src/androidUnitTest/roborazzi/...` — and configure +// the plugin extension below to match — so record and verify read from +// and write to the exact same committed-golden location. Any other +// arrangement results in the "original file was not found" error because +// the plugin doesn't currently auto-copy between `build/outputs/roborazzi` +// and the extension outputDir for the KMM Android target. roborazzi { outputDir.set(layout.projectDirectory.dir("src/androidUnitTest/roborazzi")) } diff --git a/composeApp/src/androidUnitTest/kotlin/com/tt/honeyDue/screenshot/GallerySurfaces.kt b/composeApp/src/androidUnitTest/kotlin/com/tt/honeyDue/screenshot/GallerySurfaces.kt new file mode 100644 index 0000000..6bfdaff --- /dev/null +++ b/composeApp/src/androidUnitTest/kotlin/com/tt/honeyDue/screenshot/GallerySurfaces.kt @@ -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 = 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 = {}, + ) + }, +) diff --git a/composeApp/src/androidUnitTest/kotlin/com/tt/honeyDue/screenshot/ScreenshotTests.kt b/composeApp/src/androidUnitTest/kotlin/com/tt/honeyDue/screenshot/ScreenshotTests.kt index 6095241..9b8966b 100644 --- a/composeApp/src/androidUnitTest/kotlin/com/tt/honeyDue/screenshot/ScreenshotTests.kt +++ b/composeApp/src/androidUnitTest/kotlin/com/tt/honeyDue/screenshot/ScreenshotTests.kt @@ -1,485 +1,143 @@ @file:OptIn(androidx.compose.material3.ExperimentalMaterial3Api::class) package com.tt.honeyDue.screenshot -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.Home -import androidx.compose.material.icons.filled.Task import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp -import androidx.compose.material3.ExperimentalMaterial3Api -import com.github.takahirom.roborazzi.RoborazziOptions +import androidx.test.core.app.ApplicationProvider import com.github.takahirom.roborazzi.captureRoboImage +import com.tt.honeyDue.data.IDataManager +import com.tt.honeyDue.data.LocalDataManager +import com.tt.honeyDue.testing.FixtureDataManager import com.tt.honeyDue.ui.theme.AppThemes import com.tt.honeyDue.ui.theme.HoneyDueTheme -import com.tt.honeyDue.ui.theme.ThemeColors +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner +import org.robolectric.ParameterizedRobolectricTestRunner import org.robolectric.annotation.Config import org.robolectric.annotation.GraphicsMode /** - * Roborazzi-driven screenshot regression tests (P8). + * Parity-gallery Roborazzi snapshot tests (P2). * - * Runs entirely on the Robolectric unit-test classpath — no emulator - * required. The goal is to catch accidental UI drift (colour, spacing, - * typography) on PRs by diffing generated PNGs against a committed - * golden set. + * For every entry in [gallerySurfaces] we capture four variants: + * empty × light, empty × dark, populated × light, populated × dark * - * Matrix: 6 surfaces × 3 themes (Default / Ocean / Midnight) × 2 modes - * (light / dark) = 36 images. This is a conservative baseline; the full - * 11-theme matrix would produce 132+ images and is deferred. + * Per surface that's 4 PNGs × ~40 surfaces ≈ 160 goldens. Paired with the + * iOS swift-snapshot-testing gallery (P3) that captures the same set of + * (screen, data, theme) tuples, any visual divergence between the two + * platforms surfaces here as a golden diff rather than silently shipping. * - * Implementation notes: - * - We use the top-level `captureRoboImage(path) { composable }` form - * from roborazzi-compose. That helper registers - * `RoborazziTransparentActivity` at runtime via Robolectric's shadow - * PackageManager, so we don't need `createComposeRule()` / - * `ActivityScenarioRule` and therefore avoid the - * "Unable to resolve activity for Intent MAIN/LAUNCHER cmp=.../ComponentActivity" - * failure that bit the initial scaffolding (RoboMonitoringInstrumentation:102). - * - Goldens land under `composeApp/build/outputs/roborazzi/`, which the - * Roborazzi Gradle plugin picks up for record / verify / compare. + * How this differs from the showcase tests that lived here before: + * - Showcases rendered hand-crafted theme-agnostic surfaces; now we + * render the actual production composables (`LoginScreen(…)`, etc.) + * through the fixture-backed [LocalDataManager]. + * - Surfaces are declared in [GallerySurfaces.kt] instead of being + * inlined, so adding a new screen is a one-line change. + * - Previously 6 surfaces × 3 themes × 2 modes; now the matrix is + * N surfaces × {empty, populated} × {light, dark} — themes beyond + * the default are intentionally out of scope (theme variation is + * covered by the dedicated theme_selection surface). * - * Workflow: - * - Initial record: `./gradlew :composeApp:recordRoborazziDebug` - * - Verify in CI: `./gradlew :composeApp:verifyRoborazziDebug` - * - View diffs: `./gradlew :composeApp:compareRoborazziDebug` + * One parameterized test per surface gives granular CI failures — the + * report shows `ScreenshotTests[login]`, `ScreenshotTests[tasks]`, etc. + * rather than one monolithic failure when any surface drifts. + * + * Why the goldens land directly under `src/androidUnitTest/roborazzi/`: + * Roborazzi resolves `captureRoboImage(filePath = …)` relative to the + * Gradle test task's working directory (the module root). Writing to + * the same directory where goldens are committed means record and verify + * round-trip through one canonical location; we never have to copy + * between a transient `build/outputs/roborazzi/` and the committed + * fixture directory (which was the source of the pre-existing + * "original file was not found" failure). */ -@RunWith(RobolectricTestRunner::class) +@RunWith(ParameterizedRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(qualifiers = "w360dp-h800dp-mdpi") -class ScreenshotTests { +class ScreenshotTests( + private val surface: GallerySurface, +) { - // ---------- Login screen showcase ---------- - - @Test - fun loginScreen_default_light() = runScreen("login_default_light", AppThemes.Default, darkTheme = false) { - LoginShowcase() + /** + * Compose Multiplatform's `stringResource()` loads text via a + * JVM-static context held by `AndroidContextProvider`. In a real APK + * that ContentProvider is registered in the manifest and populated at + * app start; under Robolectric unit tests it never runs, so every + * `stringResource(...)` call throws "Android context is not + * initialized." + * + * `PreviewContextConfigurationEffect()` is the documented fix — but + * it only fires inside `LocalInspectionMode = true`, and even then + * the first composition frame renders before the effect lands, so + * `stringResource()` calls race the context set. + * + * Install the context eagerly via reflection before each test. + * `AndroidContextProvider` is `internal` in Kotlin, so we can't + * touch its class directly — but its static slot is writable + * through the generated `Companion.setANDROID_CONTEXT` accessor. + * `@Before` runs inside the Robolectric sandbox (where + * `ApplicationProvider` is valid); `@BeforeClass` would run outside + * it and fail with "No instrumentation registered!". + */ + @Before + fun bootstrapComposeResources() { + val appContext = ApplicationProvider.getApplicationContext() + val providerClass = Class.forName("org.jetbrains.compose.resources.AndroidContextProvider") + val companionField = providerClass.getDeclaredField("Companion").apply { isAccessible = true } + val companion = companionField.get(null) + val setter = companion.javaClass.getMethod("setANDROID_CONTEXT", android.content.Context::class.java) + setter.invoke(companion, appContext) } @Test - fun loginScreen_default_dark() = runScreen("login_default_dark", AppThemes.Default, darkTheme = true) { - LoginShowcase() - } - - @Test - fun loginScreen_ocean_light() = runScreen("login_ocean_light", AppThemes.Ocean, darkTheme = false) { - LoginShowcase() - } - - @Test - fun loginScreen_ocean_dark() = runScreen("login_ocean_dark", AppThemes.Ocean, darkTheme = true) { - LoginShowcase() - } - - @Test - fun loginScreen_midnight_light() = runScreen("login_midnight_light", AppThemes.Midnight, darkTheme = false) { - LoginShowcase() - } - - @Test - fun loginScreen_midnight_dark() = runScreen("login_midnight_dark", AppThemes.Midnight, darkTheme = true) { - LoginShowcase() - } - - // ---------- Tasks list showcase ---------- - - @Test - fun tasksScreen_default_light() = runScreen("tasks_default_light", AppThemes.Default, darkTheme = false) { - TasksShowcase() - } - - @Test - fun tasksScreen_default_dark() = runScreen("tasks_default_dark", AppThemes.Default, darkTheme = true) { - TasksShowcase() - } - - @Test - fun tasksScreen_ocean_light() = runScreen("tasks_ocean_light", AppThemes.Ocean, darkTheme = false) { - TasksShowcase() - } - - @Test - fun tasksScreen_ocean_dark() = runScreen("tasks_ocean_dark", AppThemes.Ocean, darkTheme = true) { - TasksShowcase() - } - - @Test - fun tasksScreen_midnight_light() = runScreen("tasks_midnight_light", AppThemes.Midnight, darkTheme = false) { - TasksShowcase() - } - - @Test - fun tasksScreen_midnight_dark() = runScreen("tasks_midnight_dark", AppThemes.Midnight, darkTheme = true) { - TasksShowcase() - } - - // ---------- Residences list showcase ---------- - - @Test - fun residencesScreen_default_light() = runScreen("residences_default_light", AppThemes.Default, darkTheme = false) { - ResidencesShowcase() - } - - @Test - fun residencesScreen_default_dark() = runScreen("residences_default_dark", AppThemes.Default, darkTheme = true) { - ResidencesShowcase() - } - - @Test - fun residencesScreen_ocean_light() = runScreen("residences_ocean_light", AppThemes.Ocean, darkTheme = false) { - ResidencesShowcase() - } - - @Test - fun residencesScreen_ocean_dark() = runScreen("residences_ocean_dark", AppThemes.Ocean, darkTheme = true) { - ResidencesShowcase() - } - - @Test - fun residencesScreen_midnight_light() = runScreen("residences_midnight_light", AppThemes.Midnight, darkTheme = false) { - ResidencesShowcase() - } - - @Test - fun residencesScreen_midnight_dark() = runScreen("residences_midnight_dark", AppThemes.Midnight, darkTheme = true) { - ResidencesShowcase() - } - - // ---------- Profile/theme-selection / complete-task showcases ---------- - - @Test - fun profileScreen_default_light() = runScreen("profile_default_light", AppThemes.Default, darkTheme = false) { - ProfileShowcase() - } - - @Test - fun profileScreen_default_dark() = runScreen("profile_default_dark", AppThemes.Default, darkTheme = true) { - ProfileShowcase() - } - - @Test - fun profileScreen_ocean_light() = runScreen("profile_ocean_light", AppThemes.Ocean, darkTheme = false) { - ProfileShowcase() - } - - @Test - fun profileScreen_ocean_dark() = runScreen("profile_ocean_dark", AppThemes.Ocean, darkTheme = true) { - ProfileShowcase() - } - - @Test - fun profileScreen_midnight_light() = runScreen("profile_midnight_light", AppThemes.Midnight, darkTheme = false) { - ProfileShowcase() - } - - @Test - fun profileScreen_midnight_dark() = runScreen("profile_midnight_dark", AppThemes.Midnight, darkTheme = true) { - ProfileShowcase() - } - - @Test - fun themeSelection_default_light() = runScreen("themes_default_light", AppThemes.Default, darkTheme = false) { - ThemePaletteShowcase() - } - - @Test - fun themeSelection_default_dark() = runScreen("themes_default_dark", AppThemes.Default, darkTheme = true) { - ThemePaletteShowcase() - } - - @Test - fun themeSelection_ocean_light() = runScreen("themes_ocean_light", AppThemes.Ocean, darkTheme = false) { - ThemePaletteShowcase() - } - - @Test - fun themeSelection_ocean_dark() = runScreen("themes_ocean_dark", AppThemes.Ocean, darkTheme = true) { - ThemePaletteShowcase() - } - - @Test - fun themeSelection_midnight_light() = runScreen("themes_midnight_light", AppThemes.Midnight, darkTheme = false) { - ThemePaletteShowcase() - } - - @Test - fun themeSelection_midnight_dark() = runScreen("themes_midnight_dark", AppThemes.Midnight, darkTheme = true) { - ThemePaletteShowcase() - } - - @Test - fun completeTask_default_light() = runScreen("complete_task_default_light", AppThemes.Default, darkTheme = false) { - CompleteTaskShowcase() - } - - @Test - fun completeTask_default_dark() = runScreen("complete_task_default_dark", AppThemes.Default, darkTheme = true) { - CompleteTaskShowcase() - } - - @Test - fun completeTask_ocean_light() = runScreen("complete_task_ocean_light", AppThemes.Ocean, darkTheme = false) { - CompleteTaskShowcase() - } - - @Test - fun completeTask_ocean_dark() = runScreen("complete_task_ocean_dark", AppThemes.Ocean, darkTheme = true) { - CompleteTaskShowcase() - } - - @Test - fun completeTask_midnight_light() = runScreen("complete_task_midnight_light", AppThemes.Midnight, darkTheme = false) { - CompleteTaskShowcase() - } - - @Test - fun completeTask_midnight_dark() = runScreen("complete_task_midnight_dark", AppThemes.Midnight, darkTheme = true) { - CompleteTaskShowcase() - } - - // ---------- Shared runner ---------- - - private fun runScreen( - name: String, - theme: ThemeColors, - darkTheme: Boolean, - content: @Composable () -> Unit, - ) { - captureRoboImage( - filePath = "build/outputs/roborazzi/$name.png", - roborazziOptions = RoborazziOptions(), - ) { - HoneyDueTheme(darkTheme = darkTheme, themeColors = theme) { - content() + fun captureAllVariants() { + Variant.all().forEach { variant -> + val fileName = "${surface.name}_${variant.state}_${variant.mode}.png" + captureRoboImage(filePath = "src/androidUnitTest/roborazzi/$fileName") { + HoneyDueTheme( + themeColors = AppThemes.Default, + darkTheme = variant.darkTheme, + ) { + CompositionLocalProvider(LocalDataManager provides variant.dataManager()) { + Box(Modifier.fillMaxSize()) { + surface.content() + } + } + } } } } -} -// ============ Theme-agnostic showcase composables ============ -// -// Each mirrors the *surface* (not the full data pipeline) of its named -// production screen. This keeps Roborazzi tests hermetic — no Ktor -// client, no DataManager, no ViewModel — while still exercising every -// colour slot in the MaterialTheme that ships with the app. - -@Composable -private fun LoginShowcase() { - Scaffold { padding -> - Column( - modifier = Modifier - .fillMaxSize() - .padding(padding) - .padding(24.dp), - verticalArrangement = Arrangement.spacedBy(16.dp), - ) { - Text( - "honeyDue", - style = MaterialTheme.typography.headlineLarge, - color = MaterialTheme.colorScheme.primary, - ) - Text( - "Keep your home running", - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - OutlinedTextField(value = "testuser", onValueChange = {}, label = { Text("Username") }) - OutlinedTextField(value = "•••••••••", onValueChange = {}, label = { Text("Password") }) - Button(onClick = {}, modifier = Modifier.fillMaxSize(1f)) { - Text("Sign In") - } - TextButton(onClick = {}) { Text("Forgot password?") } - } + companion object { + @JvmStatic + @ParameterizedRobolectricTestRunner.Parameters(name = "{0}") + fun surfaces(): List> = + gallerySurfaces.map { arrayOf(it) } } } -@Composable -private fun TasksShowcase() { - Scaffold(topBar = { - TopAppBar( - title = { Text("Tasks") }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = MaterialTheme.colorScheme.surface, - ), +/** + * One of the four render-variants captured per surface. The + * `dataManager` factory is invoked lazily so each capture gets its own + * pristine fixture (avoiding cross-test StateFlow mutation). + */ +private data class Variant( + val state: String, + val mode: String, + val darkTheme: Boolean, + val dataManager: () -> IDataManager, +) { + companion object { + fun all(): List = listOf( + Variant("empty", "light", darkTheme = false) { FixtureDataManager.empty() }, + Variant("empty", "dark", darkTheme = true) { FixtureDataManager.empty() }, + Variant("populated", "light", darkTheme = false) { FixtureDataManager.populated() }, + Variant("populated", "dark", darkTheme = true) { FixtureDataManager.populated() }, ) - }) { padding -> - Column( - modifier = Modifier - .fillMaxSize() - .padding(padding) - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(12.dp), - ) { - listOf("Replace HVAC filter", "Test smoke alarms", "Clean gutters").forEach { title -> - Card( - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceVariant, - ), - shape = RoundedCornerShape(12.dp), - ) { - Row( - modifier = Modifier.padding(16.dp), - horizontalArrangement = Arrangement.spacedBy(12.dp), - ) { - Icon(Icons.Filled.Task, null, tint = MaterialTheme.colorScheme.primary) - Text(title, style = MaterialTheme.typography.bodyLarge) - } - } - } - Button(onClick = {}, colors = ButtonDefaults.buttonColors()) { - Icon(Icons.Filled.Add, null) - Text("New task", modifier = Modifier.padding(start = 8.dp)) - } - } } } -@Composable -private fun ResidencesShowcase() { - Scaffold(topBar = { - TopAppBar(title = { Text("Residences") }) - }) { padding -> - Column( - modifier = Modifier - .fillMaxSize() - .padding(padding) - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(12.dp), - ) { - Card( - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceVariant, - ), - shape = RoundedCornerShape(12.dp), - ) { - Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { - Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { - Icon(Icons.Filled.Home, null, tint = MaterialTheme.colorScheme.primary) - Text("Primary Home", style = MaterialTheme.typography.titleMedium) - } - Text( - "1234 Sunflower Lane", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } - } - OutlinedButton(onClick = {}) { Text("Add residence") } - } - } -} - -@Composable -private fun ProfileShowcase() { - Scaffold(topBar = { TopAppBar(title = { Text("Profile") }) }) { padding -> - Column( - modifier = Modifier - .fillMaxSize() - .padding(padding) - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(12.dp), - ) { - Text( - "testuser", - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.onBackground, - ) - Text( - "claude@treymail.com", - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - listOf("Notifications", "Theme", "Help").forEach { label -> - Card( - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surface, - ), - ) { - Text(label, modifier = Modifier.padding(16.dp)) - } - } - Button( - onClick = {}, - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.error, - ), - ) { Text("Log out") } - } - } -} - -@Composable -private fun ThemePaletteShowcase() { - Scaffold(topBar = { TopAppBar(title = { Text("Theme") }) }) { padding -> - Column( - modifier = Modifier - .fillMaxSize() - .padding(padding) - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - listOf( - "Primary" to MaterialTheme.colorScheme.primary, - "Secondary" to MaterialTheme.colorScheme.secondary, - "Tertiary" to MaterialTheme.colorScheme.tertiary, - "Surface" to MaterialTheme.colorScheme.surface, - "Error" to MaterialTheme.colorScheme.error, - ).forEach { (label, color) -> - Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { - Card( - colors = CardDefaults.cardColors(containerColor = color), - shape = RoundedCornerShape(8.dp), - ) { - Column(Modifier.padding(24.dp)) { Text(" ", color = Color.Transparent) } - } - Text(label, color = MaterialTheme.colorScheme.onBackground) - } - } - } - } -} - -@Composable -private fun CompleteTaskShowcase() { - Scaffold(topBar = { TopAppBar(title = { Text("Complete Task") }) }) { padding -> - Column( - modifier = Modifier - .fillMaxSize() - .padding(padding) - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(12.dp), - ) { - Text("Test smoke alarms", style = MaterialTheme.typography.titleMedium) - OutlinedTextField(value = "42.50", onValueChange = {}, label = { Text("Actual cost") }) - OutlinedTextField(value = "All alarms passed.", onValueChange = {}, label = { Text("Notes") }) - Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - OutlinedButton(onClick = {}) { Text("Cancel") } - Button(onClick = {}) { Text("Mark complete") } - } - } - } -} diff --git a/composeApp/src/androidUnitTest/roborazzi/complete_task_default_dark.png b/composeApp/src/androidUnitTest/roborazzi/complete_task_default_dark.png deleted file mode 100644 index 7d9e776..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/complete_task_default_dark.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/complete_task_default_light.png b/composeApp/src/androidUnitTest/roborazzi/complete_task_default_light.png deleted file mode 100644 index 8b96843..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/complete_task_default_light.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/complete_task_midnight_dark.png b/composeApp/src/androidUnitTest/roborazzi/complete_task_midnight_dark.png deleted file mode 100644 index a34afee..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/complete_task_midnight_dark.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/complete_task_midnight_light.png b/composeApp/src/androidUnitTest/roborazzi/complete_task_midnight_light.png deleted file mode 100644 index aac6e30..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/complete_task_midnight_light.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/complete_task_ocean_dark.png b/composeApp/src/androidUnitTest/roborazzi/complete_task_ocean_dark.png deleted file mode 100644 index 7b9cf1e..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/complete_task_ocean_dark.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/complete_task_ocean_light.png b/composeApp/src/androidUnitTest/roborazzi/complete_task_ocean_light.png deleted file mode 100644 index 7436805..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/complete_task_ocean_light.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/forgot_password_empty_dark.png b/composeApp/src/androidUnitTest/roborazzi/forgot_password_empty_dark.png new file mode 100644 index 0000000..3b479c9 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/forgot_password_empty_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/forgot_password_empty_light.png b/composeApp/src/androidUnitTest/roborazzi/forgot_password_empty_light.png new file mode 100644 index 0000000..a612bc4 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/forgot_password_empty_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/forgot_password_populated_dark.png b/composeApp/src/androidUnitTest/roborazzi/forgot_password_populated_dark.png new file mode 100644 index 0000000..3b479c9 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/forgot_password_populated_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/forgot_password_populated_light.png b/composeApp/src/androidUnitTest/roborazzi/forgot_password_populated_light.png new file mode 100644 index 0000000..a612bc4 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/forgot_password_populated_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/home_empty_dark.png b/composeApp/src/androidUnitTest/roborazzi/home_empty_dark.png new file mode 100644 index 0000000..d13d43c Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/home_empty_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/home_empty_light.png b/composeApp/src/androidUnitTest/roborazzi/home_empty_light.png new file mode 100644 index 0000000..6ab5226 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/home_empty_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/home_populated_dark.png b/composeApp/src/androidUnitTest/roborazzi/home_populated_dark.png new file mode 100644 index 0000000..d13d43c Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/home_populated_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/home_populated_light.png b/composeApp/src/androidUnitTest/roborazzi/home_populated_light.png new file mode 100644 index 0000000..6ab5226 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/home_populated_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/login_default_dark.png b/composeApp/src/androidUnitTest/roborazzi/login_default_dark.png deleted file mode 100644 index b8209fc..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/login_default_dark.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/login_default_light.png b/composeApp/src/androidUnitTest/roborazzi/login_default_light.png deleted file mode 100644 index 319d635..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/login_default_light.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/login_empty_dark.png b/composeApp/src/androidUnitTest/roborazzi/login_empty_dark.png new file mode 100644 index 0000000..447bfb0 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/login_empty_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/login_empty_light.png b/composeApp/src/androidUnitTest/roborazzi/login_empty_light.png new file mode 100644 index 0000000..34ebf2f Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/login_empty_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/login_midnight_dark.png b/composeApp/src/androidUnitTest/roborazzi/login_midnight_dark.png deleted file mode 100644 index c37e633..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/login_midnight_dark.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/login_midnight_light.png b/composeApp/src/androidUnitTest/roborazzi/login_midnight_light.png deleted file mode 100644 index 221bb92..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/login_midnight_light.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/login_ocean_dark.png b/composeApp/src/androidUnitTest/roborazzi/login_ocean_dark.png deleted file mode 100644 index 63fd0f8..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/login_ocean_dark.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/login_ocean_light.png b/composeApp/src/androidUnitTest/roborazzi/login_ocean_light.png deleted file mode 100644 index 4b0e0a4..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/login_ocean_light.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/login_populated_dark.png b/composeApp/src/androidUnitTest/roborazzi/login_populated_dark.png new file mode 100644 index 0000000..447bfb0 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/login_populated_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/login_populated_light.png b/composeApp/src/androidUnitTest/roborazzi/login_populated_light.png new file mode 100644 index 0000000..34ebf2f Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/login_populated_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_create_account_empty_dark.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_create_account_empty_dark.png new file mode 100644 index 0000000..f541d6d Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_create_account_empty_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_create_account_empty_light.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_create_account_empty_light.png new file mode 100644 index 0000000..7dd7048 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_create_account_empty_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_create_account_populated_dark.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_create_account_populated_dark.png new file mode 100644 index 0000000..f541d6d Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_create_account_populated_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_create_account_populated_light.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_create_account_populated_light.png new file mode 100644 index 0000000..7dd7048 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_create_account_populated_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_home_profile_empty_dark.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_home_profile_empty_dark.png new file mode 100644 index 0000000..130387f Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_home_profile_empty_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_home_profile_empty_light.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_home_profile_empty_light.png new file mode 100644 index 0000000..4f12281 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_home_profile_empty_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_home_profile_populated_dark.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_home_profile_populated_dark.png new file mode 100644 index 0000000..130387f Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_home_profile_populated_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_home_profile_populated_light.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_home_profile_populated_light.png new file mode 100644 index 0000000..4f12281 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_home_profile_populated_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_join_residence_empty_dark.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_join_residence_empty_dark.png new file mode 100644 index 0000000..3ac7a14 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_join_residence_empty_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_join_residence_empty_light.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_join_residence_empty_light.png new file mode 100644 index 0000000..73c63ff Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_join_residence_empty_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_join_residence_populated_dark.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_join_residence_populated_dark.png new file mode 100644 index 0000000..3ac7a14 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_join_residence_populated_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_join_residence_populated_light.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_join_residence_populated_light.png new file mode 100644 index 0000000..73c63ff Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_join_residence_populated_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_location_empty_dark.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_location_empty_dark.png new file mode 100644 index 0000000..d31fbe0 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_location_empty_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_location_empty_light.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_location_empty_light.png new file mode 100644 index 0000000..89b4218 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_location_empty_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_location_populated_dark.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_location_populated_dark.png new file mode 100644 index 0000000..d31fbe0 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_location_populated_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_location_populated_light.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_location_populated_light.png new file mode 100644 index 0000000..89b4218 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_location_populated_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_name_residence_empty_dark.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_name_residence_empty_dark.png new file mode 100644 index 0000000..0ba56c8 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_name_residence_empty_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_name_residence_empty_light.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_name_residence_empty_light.png new file mode 100644 index 0000000..b2427a6 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_name_residence_empty_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_name_residence_populated_dark.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_name_residence_populated_dark.png new file mode 100644 index 0000000..0ba56c8 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_name_residence_populated_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_name_residence_populated_light.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_name_residence_populated_light.png new file mode 100644 index 0000000..b2427a6 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_name_residence_populated_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_subscription_empty_dark.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_subscription_empty_dark.png new file mode 100644 index 0000000..b8202e4 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_subscription_empty_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_subscription_empty_light.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_subscription_empty_light.png new file mode 100644 index 0000000..0193a6d Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_subscription_empty_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_subscription_populated_dark.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_subscription_populated_dark.png new file mode 100644 index 0000000..b8202e4 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_subscription_populated_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_subscription_populated_light.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_subscription_populated_light.png new file mode 100644 index 0000000..4c22132 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_subscription_populated_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_value_props_empty_dark.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_value_props_empty_dark.png new file mode 100644 index 0000000..dcd1d63 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_value_props_empty_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_value_props_empty_light.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_value_props_empty_light.png new file mode 100644 index 0000000..37cc3d9 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_value_props_empty_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_value_props_populated_dark.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_value_props_populated_dark.png new file mode 100644 index 0000000..dcd1d63 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_value_props_populated_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_value_props_populated_light.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_value_props_populated_light.png new file mode 100644 index 0000000..37cc3d9 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_value_props_populated_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_verify_email_empty_dark.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_verify_email_empty_dark.png new file mode 100644 index 0000000..9be6f9a Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_verify_email_empty_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_verify_email_empty_light.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_verify_email_empty_light.png new file mode 100644 index 0000000..3493d66 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_verify_email_empty_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_verify_email_populated_dark.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_verify_email_populated_dark.png new file mode 100644 index 0000000..9be6f9a Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_verify_email_populated_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_verify_email_populated_light.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_verify_email_populated_light.png new file mode 100644 index 0000000..3493d66 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_verify_email_populated_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_welcome_empty_dark.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_welcome_empty_dark.png new file mode 100644 index 0000000..b7f5efd Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_welcome_empty_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_welcome_empty_light.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_welcome_empty_light.png new file mode 100644 index 0000000..349cd49 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_welcome_empty_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_welcome_populated_dark.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_welcome_populated_dark.png new file mode 100644 index 0000000..b7f5efd Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_welcome_populated_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/onboarding_welcome_populated_light.png b/composeApp/src/androidUnitTest/roborazzi/onboarding_welcome_populated_light.png new file mode 100644 index 0000000..349cd49 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/onboarding_welcome_populated_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/profile_default_dark.png b/composeApp/src/androidUnitTest/roborazzi/profile_default_dark.png deleted file mode 100644 index f8c8465..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/profile_default_dark.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/profile_default_light.png b/composeApp/src/androidUnitTest/roborazzi/profile_default_light.png deleted file mode 100644 index ab303e8..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/profile_default_light.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/profile_midnight_dark.png b/composeApp/src/androidUnitTest/roborazzi/profile_midnight_dark.png deleted file mode 100644 index b8b11ed..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/profile_midnight_dark.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/profile_midnight_light.png b/composeApp/src/androidUnitTest/roborazzi/profile_midnight_light.png deleted file mode 100644 index a82229e..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/profile_midnight_light.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/profile_ocean_dark.png b/composeApp/src/androidUnitTest/roborazzi/profile_ocean_dark.png deleted file mode 100644 index 14651e5..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/profile_ocean_dark.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/profile_ocean_light.png b/composeApp/src/androidUnitTest/roborazzi/profile_ocean_light.png deleted file mode 100644 index dbad688..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/profile_ocean_light.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/register_empty_dark.png b/composeApp/src/androidUnitTest/roborazzi/register_empty_dark.png new file mode 100644 index 0000000..3cab07b Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/register_empty_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/register_empty_light.png b/composeApp/src/androidUnitTest/roborazzi/register_empty_light.png new file mode 100644 index 0000000..7dcca1b Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/register_empty_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/register_populated_dark.png b/composeApp/src/androidUnitTest/roborazzi/register_populated_dark.png new file mode 100644 index 0000000..3cab07b Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/register_populated_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/register_populated_light.png b/composeApp/src/androidUnitTest/roborazzi/register_populated_light.png new file mode 100644 index 0000000..7dcca1b Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/register_populated_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/reset_password_empty_dark.png b/composeApp/src/androidUnitTest/roborazzi/reset_password_empty_dark.png new file mode 100644 index 0000000..5d3da8c Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/reset_password_empty_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/reset_password_empty_light.png b/composeApp/src/androidUnitTest/roborazzi/reset_password_empty_light.png new file mode 100644 index 0000000..e6dec37 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/reset_password_empty_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/reset_password_populated_dark.png b/composeApp/src/androidUnitTest/roborazzi/reset_password_populated_dark.png new file mode 100644 index 0000000..5d3da8c Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/reset_password_populated_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/reset_password_populated_light.png b/composeApp/src/androidUnitTest/roborazzi/reset_password_populated_light.png new file mode 100644 index 0000000..e6dec37 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/reset_password_populated_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/residence_detail_empty_dark.png b/composeApp/src/androidUnitTest/roborazzi/residence_detail_empty_dark.png new file mode 100644 index 0000000..2bfef48 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/residence_detail_empty_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/residence_detail_empty_light.png b/composeApp/src/androidUnitTest/roborazzi/residence_detail_empty_light.png new file mode 100644 index 0000000..e3ab55f Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/residence_detail_empty_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/residence_detail_populated_light.png b/composeApp/src/androidUnitTest/roborazzi/residence_detail_populated_light.png new file mode 100644 index 0000000..2d3b69d Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/residence_detail_populated_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/residences_default_dark.png b/composeApp/src/androidUnitTest/roborazzi/residences_default_dark.png deleted file mode 100644 index 6eee2d2..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/residences_default_dark.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/residences_default_light.png b/composeApp/src/androidUnitTest/roborazzi/residences_default_light.png deleted file mode 100644 index e6bf1f0..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/residences_default_light.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/residences_empty_dark.png b/composeApp/src/androidUnitTest/roborazzi/residences_empty_dark.png new file mode 100644 index 0000000..f117778 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/residences_empty_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/residences_empty_light.png b/composeApp/src/androidUnitTest/roborazzi/residences_empty_light.png new file mode 100644 index 0000000..6bee9e5 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/residences_empty_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/residences_midnight_dark.png b/composeApp/src/androidUnitTest/roborazzi/residences_midnight_dark.png deleted file mode 100644 index 2c5fafc..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/residences_midnight_dark.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/residences_midnight_light.png b/composeApp/src/androidUnitTest/roborazzi/residences_midnight_light.png deleted file mode 100644 index 3f6ab52..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/residences_midnight_light.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/residences_ocean_dark.png b/composeApp/src/androidUnitTest/roborazzi/residences_ocean_dark.png deleted file mode 100644 index 89376cf..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/residences_ocean_dark.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/residences_ocean_light.png b/composeApp/src/androidUnitTest/roborazzi/residences_ocean_light.png deleted file mode 100644 index 0d8c463..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/residences_ocean_light.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/residences_populated_dark.png b/composeApp/src/androidUnitTest/roborazzi/residences_populated_dark.png new file mode 100644 index 0000000..f117778 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/residences_populated_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/residences_populated_light.png b/composeApp/src/androidUnitTest/roborazzi/residences_populated_light.png new file mode 100644 index 0000000..e874cca Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/residences_populated_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/tasks_default_dark.png b/composeApp/src/androidUnitTest/roborazzi/tasks_default_dark.png deleted file mode 100644 index 116394b..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/tasks_default_dark.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/tasks_default_light.png b/composeApp/src/androidUnitTest/roborazzi/tasks_default_light.png deleted file mode 100644 index 3d8eed2..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/tasks_default_light.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/tasks_midnight_dark.png b/composeApp/src/androidUnitTest/roborazzi/tasks_midnight_dark.png deleted file mode 100644 index 505597e..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/tasks_midnight_dark.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/tasks_midnight_light.png b/composeApp/src/androidUnitTest/roborazzi/tasks_midnight_light.png deleted file mode 100644 index dc16bc3..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/tasks_midnight_light.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/tasks_ocean_dark.png b/composeApp/src/androidUnitTest/roborazzi/tasks_ocean_dark.png deleted file mode 100644 index eed1a0b..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/tasks_ocean_dark.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/tasks_ocean_light.png b/composeApp/src/androidUnitTest/roborazzi/tasks_ocean_light.png deleted file mode 100644 index 9c5c580..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/tasks_ocean_light.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/themes_default_dark.png b/composeApp/src/androidUnitTest/roborazzi/themes_default_dark.png deleted file mode 100644 index eb8d942..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/themes_default_dark.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/themes_default_light.png b/composeApp/src/androidUnitTest/roborazzi/themes_default_light.png deleted file mode 100644 index 78aa25c..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/themes_default_light.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/themes_midnight_dark.png b/composeApp/src/androidUnitTest/roborazzi/themes_midnight_dark.png deleted file mode 100644 index a696934..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/themes_midnight_dark.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/themes_midnight_light.png b/composeApp/src/androidUnitTest/roborazzi/themes_midnight_light.png deleted file mode 100644 index 2342db0..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/themes_midnight_light.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/themes_ocean_dark.png b/composeApp/src/androidUnitTest/roborazzi/themes_ocean_dark.png deleted file mode 100644 index 4d17d08..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/themes_ocean_dark.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/themes_ocean_light.png b/composeApp/src/androidUnitTest/roborazzi/themes_ocean_light.png deleted file mode 100644 index 016ae40..0000000 Binary files a/composeApp/src/androidUnitTest/roborazzi/themes_ocean_light.png and /dev/null differ diff --git a/composeApp/src/androidUnitTest/roborazzi/verify_email_empty_dark.png b/composeApp/src/androidUnitTest/roborazzi/verify_email_empty_dark.png new file mode 100644 index 0000000..fcc7c7f Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/verify_email_empty_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/verify_email_empty_light.png b/composeApp/src/androidUnitTest/roborazzi/verify_email_empty_light.png new file mode 100644 index 0000000..cb41204 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/verify_email_empty_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/verify_email_populated_dark.png b/composeApp/src/androidUnitTest/roborazzi/verify_email_populated_dark.png new file mode 100644 index 0000000..fcc7c7f Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/verify_email_populated_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/verify_email_populated_light.png b/composeApp/src/androidUnitTest/roborazzi/verify_email_populated_light.png new file mode 100644 index 0000000..cb41204 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/verify_email_populated_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/verify_reset_code_empty_dark.png b/composeApp/src/androidUnitTest/roborazzi/verify_reset_code_empty_dark.png new file mode 100644 index 0000000..b87ca3a Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/verify_reset_code_empty_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/verify_reset_code_empty_light.png b/composeApp/src/androidUnitTest/roborazzi/verify_reset_code_empty_light.png new file mode 100644 index 0000000..62b5be6 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/verify_reset_code_empty_light.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/verify_reset_code_populated_dark.png b/composeApp/src/androidUnitTest/roborazzi/verify_reset_code_populated_dark.png new file mode 100644 index 0000000..b87ca3a Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/verify_reset_code_populated_dark.png differ diff --git a/composeApp/src/androidUnitTest/roborazzi/verify_reset_code_populated_light.png b/composeApp/src/androidUnitTest/roborazzi/verify_reset_code_populated_light.png new file mode 100644 index 0000000..62b5be6 Binary files /dev/null and b/composeApp/src/androidUnitTest/roborazzi/verify_reset_code_populated_light.png differ