diff --git a/app/src/main/java/com/nyasa/notifybridge/ui/apps/AppsScreen.kt b/app/src/main/java/com/nyasa/notifybridge/ui/apps/AppsScreen.kt index cc4aebd..18869c6 100644 --- a/app/src/main/java/com/nyasa/notifybridge/ui/apps/AppsScreen.kt +++ b/app/src/main/java/com/nyasa/notifybridge/ui/apps/AppsScreen.kt @@ -92,7 +92,7 @@ fun AppsScreen(nav: NavHostController) { } @Composable -private fun AppsContent( +internal fun AppsContent( rows: List, query: String, icons: Map, diff --git a/app/src/main/java/com/nyasa/notifybridge/ui/broker/BrokerScreen.kt b/app/src/main/java/com/nyasa/notifybridge/ui/broker/BrokerScreen.kt index ec973b4..efd3de4 100644 --- a/app/src/main/java/com/nyasa/notifybridge/ui/broker/BrokerScreen.kt +++ b/app/src/main/java/com/nyasa/notifybridge/ui/broker/BrokerScreen.kt @@ -178,7 +178,7 @@ fun BrokerScreen(nav: NavHostController) { @OptIn(ExperimentalMaterial3Api::class) @Composable -private fun BrokerContent( +internal fun BrokerContent( config: BrokerConfig, testResult: String?, saving: Boolean, diff --git a/app/src/main/java/com/nyasa/notifybridge/ui/language/LanguageSettingsScreen.kt b/app/src/main/java/com/nyasa/notifybridge/ui/language/LanguageSettingsScreen.kt index e812697..c683fa1 100644 --- a/app/src/main/java/com/nyasa/notifybridge/ui/language/LanguageSettingsScreen.kt +++ b/app/src/main/java/com/nyasa/notifybridge/ui/language/LanguageSettingsScreen.kt @@ -63,7 +63,7 @@ private data class LanguageOption( ) @Composable -private fun LanguageSettingsContent( +internal fun LanguageSettingsContent( currentTag: String?, onPick: (String?) -> Unit, onBack: () -> Unit, diff --git a/app/src/test/java/com/nyasa/notifybridge/screenshot/AppsScreenScreenshotTest.kt b/app/src/test/java/com/nyasa/notifybridge/screenshot/AppsScreenScreenshotTest.kt new file mode 100644 index 0000000..df190bb --- /dev/null +++ b/app/src/test/java/com/nyasa/notifybridge/screenshot/AppsScreenScreenshotTest.kt @@ -0,0 +1,40 @@ +package com.nyasa.notifybridge.screenshot + +import androidx.compose.ui.test.onRoot +import com.github.takahirom.roborazzi.captureRoboImage +import com.nyasa.notifybridge.ui.apps.AppRow +import com.nyasa.notifybridge.ui.apps.AppsContent +import org.junit.Test + +/** Baselines for the app allow-list — populated and empty (mirrors @Preview). */ +class AppsScreenScreenshotTest : ScreenshotTest() { + + private val rows = listOf( + AppRow("Signal", "org.thoughtcrime.securesms", true), + AppRow("Gmail", "com.google.android.gm", false), + AppRow("Slack", "com.Slack", true), + AppRow("WhatsApp", "com.whatsapp", false), + ) + + private fun shoot(name: String, rows: List) { + setScreen { + AppsContent( + rows = rows, + query = "", + icons = emptyMap(), + onQueryChange = {}, + onToggle = { _, _ -> }, + onNavStatus = {}, + onNavBroker = {}, + onNavPermissions = {}, + ) + } + compose.onRoot().captureRoboImage("src/test/screenshots/AppsScreen_$name.png") + } + + @Test + fun populated() = shoot("populated", rows) + + @Test + fun empty() = shoot("empty", emptyList()) +} diff --git a/app/src/test/java/com/nyasa/notifybridge/screenshot/BrokerScreenScreenshotTest.kt b/app/src/test/java/com/nyasa/notifybridge/screenshot/BrokerScreenScreenshotTest.kt new file mode 100644 index 0000000..702db43 --- /dev/null +++ b/app/src/test/java/com/nyasa/notifybridge/screenshot/BrokerScreenScreenshotTest.kt @@ -0,0 +1,46 @@ +package com.nyasa.notifybridge.screenshot + +import androidx.compose.ui.test.onRoot +import com.github.takahirom.roborazzi.captureRoboImage +import com.nyasa.notifybridge.domain.model.BrokerConfig +import com.nyasa.notifybridge.domain.model.TlsMode +import com.nyasa.notifybridge.ui.broker.BrokerContent +import org.junit.Test + +/** Baselines for the broker config form — empty and filled+TLS+connected. */ +class BrokerScreenScreenshotTest : ScreenshotTest() { + + private fun shoot(name: String, config: BrokerConfig, testResult: String?) { + setScreen { + BrokerContent( + config = config, + testResult = testResult, + saving = false, + onHostChange = {}, + onPortChange = {}, + onDeviceNameChange = {}, + onUsernameChange = {}, + onPasswordChange = {}, + onTlsModeChange = {}, + onPickCertFile = {}, + onTest = {}, + onSave = {}, + onBack = {}, + onNavStatus = {}, + onNavApps = {}, + onNavPermissions = {}, + ) + } + compose.onRoot().captureRoboImage("src/test/screenshots/BrokerScreen_$name.png") + } + + @Test + fun empty() = shoot("empty", BrokerConfig(), testResult = null) + + @Test + fun filledTlsConnected() = shoot( + "filled_tls_connected", + BrokerConfig(host = "192.168.1.10", port = 1883, deviceName = "phone", tlsMode = TlsMode.SYSTEM_CA), + testResult = "Connected", + ) +} diff --git a/app/src/test/java/com/nyasa/notifybridge/screenshot/LanguageSettingsScreenshotTest.kt b/app/src/test/java/com/nyasa/notifybridge/screenshot/LanguageSettingsScreenshotTest.kt new file mode 100644 index 0000000..3239a25 --- /dev/null +++ b/app/src/test/java/com/nyasa/notifybridge/screenshot/LanguageSettingsScreenshotTest.kt @@ -0,0 +1,23 @@ +package com.nyasa.notifybridge.screenshot + +import androidx.compose.ui.test.onRoot +import com.github.takahirom.roborazzi.captureRoboImage +import com.nyasa.notifybridge.ui.language.LanguageSettingsContent +import org.junit.Test + +/** Baselines for the language picker — system default and a selected locale. */ +class LanguageSettingsScreenshotTest : ScreenshotTest() { + + private fun shoot(name: String, currentTag: String?) { + setScreen { + LanguageSettingsContent(currentTag = currentTag, onPick = {}, onBack = {}) + } + compose.onRoot().captureRoboImage("src/test/screenshots/LanguageSettings_$name.png") + } + + @Test + fun systemDefault() = shoot("system_default", currentTag = null) + + @Test + fun frenchSelected() = shoot("french_selected", currentTag = "fr") +} diff --git a/app/src/test/screenshots/AppsScreen_empty.png b/app/src/test/screenshots/AppsScreen_empty.png new file mode 100644 index 0000000..95fc0a8 Binary files /dev/null and b/app/src/test/screenshots/AppsScreen_empty.png differ diff --git a/app/src/test/screenshots/AppsScreen_populated.png b/app/src/test/screenshots/AppsScreen_populated.png new file mode 100644 index 0000000..e8f5871 Binary files /dev/null and b/app/src/test/screenshots/AppsScreen_populated.png differ diff --git a/app/src/test/screenshots/BrokerScreen_empty.png b/app/src/test/screenshots/BrokerScreen_empty.png new file mode 100644 index 0000000..0f0aaf0 Binary files /dev/null and b/app/src/test/screenshots/BrokerScreen_empty.png differ diff --git a/app/src/test/screenshots/BrokerScreen_filled_tls_connected.png b/app/src/test/screenshots/BrokerScreen_filled_tls_connected.png new file mode 100644 index 0000000..c428793 Binary files /dev/null and b/app/src/test/screenshots/BrokerScreen_filled_tls_connected.png differ diff --git a/app/src/test/screenshots/LanguageSettings_french_selected.png b/app/src/test/screenshots/LanguageSettings_french_selected.png new file mode 100644 index 0000000..e5fe048 Binary files /dev/null and b/app/src/test/screenshots/LanguageSettings_french_selected.png differ diff --git a/app/src/test/screenshots/LanguageSettings_system_default.png b/app/src/test/screenshots/LanguageSettings_system_default.png new file mode 100644 index 0000000..66daee2 Binary files /dev/null and b/app/src/test/screenshots/LanguageSettings_system_default.png differ diff --git a/config/detekt/baseline-debug.xml b/config/detekt/baseline-debug.xml index 51fb2fc..46726d5 100644 --- a/config/detekt/baseline-debug.xml +++ b/config/detekt/baseline-debug.xml @@ -30,7 +30,7 @@ ArgumentListWrapping:OnboardingScreen.kt$(alpha = 0.6f) ArgumentListWrapping:OnboardingViewModel.kt$(when { !notifAccess -> OnboardingStep.GRANT_ACCESS !brokerSet -> OnboardingStep.CONNECT_BROKER !appsChosen -> OnboardingStep.CHOOSE_APPS else -> OnboardingStep.DONE }) ArgumentListWrapping:Theme.kt$( primary = Teal, background = BgBase, surface = Surface, error = ErrorRed) - CyclomaticComplexMethod:BrokerScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable private fun BrokerContent( config: BrokerConfig, testResult: String?, saving: Boolean, onHostChange: (String) -> Unit, onPortChange: (String) -> Unit, onDeviceNameChange: (String) -> Unit, onUsernameChange: (String) -> Unit, onPasswordChange: (String) -> Unit, onTlsModeChange: (TlsMode) -> Unit, onPickCertFile: () -> Unit, onTest: () -> Unit, onSave: () -> Unit, onBack: () -> Unit, onNavStatus: () -> Unit, onNavApps: () -> Unit, onNavPermissions: () -> Unit, ) + CyclomaticComplexMethod:BrokerScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable internal fun BrokerContent( config: BrokerConfig, testResult: String?, saving: Boolean, onHostChange: (String) -> Unit, onPortChange: (String) -> Unit, onDeviceNameChange: (String) -> Unit, onUsernameChange: (String) -> Unit, onPasswordChange: (String) -> Unit, onTlsModeChange: (TlsMode) -> Unit, onPickCertFile: () -> Unit, onTest: () -> Unit, onSave: () -> Unit, onBack: () -> Unit, onNavStatus: () -> Unit, onNavApps: () -> Unit, onNavPermissions: () -> Unit, ) FinalNewline:NotifyBridgeApp.kt$com.nyasa.notifybridge.NotifyBridgeApp.kt IgnoredReturnValue:HiveMqClientManager.kt$HiveMqClientManager$sslWithDefaultConfig() ImportOrdering:BrokerScreen.kt$import android.app.Activity import android.content.Intent import android.view.WindowManager import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Apps import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Dashboard import androidx.compose.material.icons.filled.Lock import androidx.compose.material.icons.filled.Router import androidx.compose.material.icons.filled.Security import androidx.compose.material.icons.filled.Shield import androidx.compose.material.icons.filled.Visibility import androidx.compose.material.icons.filled.VisibilityOff import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationBarItemDefaults import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.Switch import androidx.compose.material3.SwitchDefaults import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController import com.nyasa.notifybridge.domain.model.BrokerConfig import com.nyasa.notifybridge.domain.model.TlsMode import com.nyasa.notifybridge.localization.Dictionary import com.nyasa.notifybridge.localization.back import com.nyasa.notifybridge.localization.certificateHeading import com.nyasa.notifybridge.localization.comingSoon import com.nyasa.notifybridge.localization.fieldDevice import com.nyasa.notifybridge.localization.fieldDevicePlaceholder import com.nyasa.notifybridge.localization.fieldDeviceSupporting import com.nyasa.notifybridge.localization.fieldHost import com.nyasa.notifybridge.localization.fieldHostPlaceholder import com.nyasa.notifybridge.localization.fieldPassword import com.nyasa.notifybridge.localization.fieldPort import com.nyasa.notifybridge.localization.fieldPortPlaceholder import com.nyasa.notifybridge.localization.fieldUsername import com.nyasa.notifybridge.localization.localized import com.nyasa.notifybridge.localization.navAccess import com.nyasa.notifybridge.localization.navApps import com.nyasa.notifybridge.localization.navBroker import com.nyasa.notifybridge.localization.navStatus import com.nyasa.notifybridge.localization.passwordHide import com.nyasa.notifybridge.localization.passwordShow import com.nyasa.notifybridge.localization.pinnedCertNote import com.nyasa.notifybridge.localization.saveButton import com.nyasa.notifybridge.localization.saveButtonSaving import com.nyasa.notifybridge.localization.sectionAuthentication import com.nyasa.notifybridge.localization.sectionConnection import com.nyasa.notifybridge.localization.sectionTls import com.nyasa.notifybridge.localization.selectCertFile import com.nyasa.notifybridge.localization.testButton import com.nyasa.notifybridge.localization.testResultConnected import com.nyasa.notifybridge.localization.tlsPinnedButton import com.nyasa.notifybridge.localization.tlsSystemCaButton import com.nyasa.notifybridge.localization.title import com.nyasa.notifybridge.localization.useTls import com.nyasa.notifybridge.service.MqttForegroundService import com.nyasa.notifybridge.ui.theme.Amber import com.nyasa.notifybridge.ui.theme.NotifyBridgeTheme import com.nyasa.notifybridge.ui.theme.Teal @@ -48,8 +48,8 @@ InjectDispatcher:NotifListenerService.kt$NotifListenerService$IO InjectDispatcher:StatusScreen.kt$IO LongMethod:AppsScreen.kt$@Composable private fun AppRowItem( row: AppRow, icon: android.graphics.drawable.Drawable?, onToggle: (Boolean) -> Unit, ) - LongMethod:AppsScreen.kt$@Composable private fun AppsContent( rows: List<AppRow>, query: String, icons: Map<String, android.graphics.drawable.Drawable?>, onQueryChange: (String) -> Unit, onToggle: (String, Boolean) -> Unit, onNavStatus: () -> Unit, onNavBroker: () -> Unit, onNavPermissions: () -> Unit, ) - LongMethod:BrokerScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable private fun BrokerContent( config: BrokerConfig, testResult: String?, saving: Boolean, onHostChange: (String) -> Unit, onPortChange: (String) -> Unit, onDeviceNameChange: (String) -> Unit, onUsernameChange: (String) -> Unit, onPasswordChange: (String) -> Unit, onTlsModeChange: (TlsMode) -> Unit, onPickCertFile: () -> Unit, onTest: () -> Unit, onSave: () -> Unit, onBack: () -> Unit, onNavStatus: () -> Unit, onNavApps: () -> Unit, onNavPermissions: () -> Unit, ) + LongMethod:AppsScreen.kt$@Composable internal fun AppsContent( rows: List<AppRow>, query: String, icons: Map<String, android.graphics.drawable.Drawable?>, onQueryChange: (String) -> Unit, onToggle: (String, Boolean) -> Unit, onNavStatus: () -> Unit, onNavBroker: () -> Unit, onNavPermissions: () -> Unit, ) + LongMethod:BrokerScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable internal fun BrokerContent( config: BrokerConfig, testResult: String?, saving: Boolean, onHostChange: (String) -> Unit, onPortChange: (String) -> Unit, onDeviceNameChange: (String) -> Unit, onUsernameChange: (String) -> Unit, onPasswordChange: (String) -> Unit, onTlsModeChange: (TlsMode) -> Unit, onPickCertFile: () -> Unit, onTest: () -> Unit, onSave: () -> Unit, onBack: () -> Unit, onNavStatus: () -> Unit, onNavApps: () -> Unit, onNavPermissions: () -> Unit, ) LongMethod:HiveMqClientManager.kt$HiveMqClientManager$override suspend fun connect(config: BrokerConfig) LongMethod:OnboardingScreen.kt$@Composable internal fun OnboardingContent( state: OnboardingUiState, onGrantAccess: () -> Unit, onConfigureBroker: () -> Unit, onChooseApps: () -> Unit, ) LongMethod:OnboardingScreen.kt$@Composable private fun StepCard( number: Int, title: String, description: String, buttonLabel: String, buttonIcon: (@Composable () -> Unit)?, isActive: Boolean, isEnabled: Boolean, onClick: () -> Unit, ) diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml index 44d5f2f..30e78af 100644 --- a/config/detekt/baseline.xml +++ b/config/detekt/baseline.xml @@ -46,7 +46,7 @@ ArgumentListWrapping:SettingsRepositoryImplTest.kt$SettingsRepositoryImplTest$(ApplicationProvider.getApplicationContext<android.content.Context>() .cacheDir, "t${System.nanoTime()}.preferences_pb") ArgumentListWrapping:SettingsRepositoryImplTest.kt$SettingsRepositoryImplTest$(host = "h", port = 8883, tlsMode = TlsMode.PINNED, deviceName = "Pixel 7") ArgumentListWrapping:Theme.kt$( primary = Teal, background = BgBase, surface = Surface, error = ErrorRed) - CyclomaticComplexMethod:BrokerScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable private fun BrokerContent( config: BrokerConfig, testResult: String?, saving: Boolean, onHostChange: (String) -> Unit, onPortChange: (String) -> Unit, onDeviceNameChange: (String) -> Unit, onUsernameChange: (String) -> Unit, onPasswordChange: (String) -> Unit, onTlsModeChange: (TlsMode) -> Unit, onPickCertFile: () -> Unit, onTest: () -> Unit, onSave: () -> Unit, onBack: () -> Unit, onNavStatus: () -> Unit, onNavApps: () -> Unit, onNavPermissions: () -> Unit, ) + CyclomaticComplexMethod:BrokerScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable internal fun BrokerContent( config: BrokerConfig, testResult: String?, saving: Boolean, onHostChange: (String) -> Unit, onPortChange: (String) -> Unit, onDeviceNameChange: (String) -> Unit, onUsernameChange: (String) -> Unit, onPasswordChange: (String) -> Unit, onTlsModeChange: (TlsMode) -> Unit, onPickCertFile: () -> Unit, onTest: () -> Unit, onSave: () -> Unit, onBack: () -> Unit, onNavStatus: () -> Unit, onNavApps: () -> Unit, onNavPermissions: () -> Unit, ) EmptyFunctionBlock:BrokerSeedTest.kt$BrokerSeedTest.FakeSettings${} EmptyFunctionBlock:DrainPrunesOutboxTest.kt$RecordingOutbox${} EmptyFunctionBlock:UseCasesTest.kt$MemOutbox${} @@ -61,8 +61,8 @@ Indentation:NotificationMapperImpl.kt$NotificationMapperImpl$ Indentation:StatusScreen.kt$ LongMethod:AppsScreen.kt$@Composable private fun AppRowItem( row: AppRow, icon: android.graphics.drawable.Drawable?, onToggle: (Boolean) -> Unit, ) - LongMethod:AppsScreen.kt$@Composable private fun AppsContent( rows: List<AppRow>, query: String, icons: Map<String, android.graphics.drawable.Drawable?>, onQueryChange: (String) -> Unit, onToggle: (String, Boolean) -> Unit, onNavStatus: () -> Unit, onNavBroker: () -> Unit, onNavPermissions: () -> Unit, ) - LongMethod:BrokerScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable private fun BrokerContent( config: BrokerConfig, testResult: String?, saving: Boolean, onHostChange: (String) -> Unit, onPortChange: (String) -> Unit, onDeviceNameChange: (String) -> Unit, onUsernameChange: (String) -> Unit, onPasswordChange: (String) -> Unit, onTlsModeChange: (TlsMode) -> Unit, onPickCertFile: () -> Unit, onTest: () -> Unit, onSave: () -> Unit, onBack: () -> Unit, onNavStatus: () -> Unit, onNavApps: () -> Unit, onNavPermissions: () -> Unit, ) + LongMethod:AppsScreen.kt$@Composable internal fun AppsContent( rows: List<AppRow>, query: String, icons: Map<String, android.graphics.drawable.Drawable?>, onQueryChange: (String) -> Unit, onToggle: (String, Boolean) -> Unit, onNavStatus: () -> Unit, onNavBroker: () -> Unit, onNavPermissions: () -> Unit, ) + LongMethod:BrokerScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable internal fun BrokerContent( config: BrokerConfig, testResult: String?, saving: Boolean, onHostChange: (String) -> Unit, onPortChange: (String) -> Unit, onDeviceNameChange: (String) -> Unit, onUsernameChange: (String) -> Unit, onPasswordChange: (String) -> Unit, onTlsModeChange: (TlsMode) -> Unit, onPickCertFile: () -> Unit, onTest: () -> Unit, onSave: () -> Unit, onBack: () -> Unit, onNavStatus: () -> Unit, onNavApps: () -> Unit, onNavPermissions: () -> Unit, ) LongMethod:HiveMqClientManager.kt$HiveMqClientManager$override suspend fun connect(config: BrokerConfig) LongMethod:OnboardingScreen.kt$@Composable internal fun OnboardingContent( state: OnboardingUiState, onGrantAccess: () -> Unit, onConfigureBroker: () -> Unit, onChooseApps: () -> Unit, ) LongMethod:OnboardingScreen.kt$@Composable private fun StepCard( number: Int, title: String, description: String, buttonLabel: String, buttonIcon: (@Composable () -> Unit)?, isActive: Boolean, isEnabled: Boolean, onClick: () -> Unit, ) diff --git a/docs/screenshot-testing-strategy.md b/docs/screenshot-testing-strategy.md index 339bde9..c7b6741 100644 --- a/docs/screenshot-testing-strategy.md +++ b/docs/screenshot-testing-strategy.md @@ -261,6 +261,31 @@ reusing the Phase-1 harness with no new infrastructure. copy (not key-echo) and large-font surfaces wrapping/clipping. All 9 new Mac-recorded baselines again verified unchanged on Linux CI. +## Phase 3 — Remaining screens — + +**Goal:** complete screen-level coverage (Apps, Broker, Language) and settle the +two operational switches from the plan. + +**Steps taken:** +1. Promoted `AppsContent`, `BrokerContent`, `LanguageSettingsContent` + `private → internal`; refreshed their detekt `LongMethod` baseline IDs. +2. New tests/baselines: Apps (populated / empty), Broker (empty / + filled+TLS+connected), Language (system-default / French). Fixtures mirror the + existing `@Preview` data; `icons = emptyMap()` keeps Apps deterministic. +3. `recordRoborazziDebug` → **20 baselines total** (was 14). Full local gate green. + +**Operational-switch decisions:** +- **CI-side baseline recording — deliberately skipped.** Phases 1–2 proved Mac↔Linux + rendering is stable (baselines recorded locally verified unchanged on CI), so + record-local / verify-CI is sufficient. Building a CI-record workflow would be + infrastructure we don't need; revisit only if a future run diffs on fonts. +- **Branch protection — owner action (not code).** Making `verifyRoborazziDebug` a + *required* check is a GitHub repo setting (Settings → Branches → protect `main` → + require the "Build, unit tests, detekt, lint" status check). Left to the repo owner. + +**Outcome:** all 7 screens now have screen-level baselines (20 total), gating every PR +via the existing JVM job. + ## Recurring lessons - **detekt/lint baselines are signature/import-string keyed.** Any visibility,