From a9aa1255f455954957198e3d0148fdd398078246 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=BC=ED=98=81?= Date: Sun, 13 Sep 2026 14:22:25 +0900 Subject: [PATCH] test(setting): verify last greeting touch target (#2035) --- feature/setting/presentation/build.gradle.kts | 1 + .../DeliveryConditionTouchTargetTest.kt | 102 ++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 feature/setting/presentation/src/test/kotlin/com/afternote/feature/setting/presentation/screen/DeliveryConditionTouchTargetTest.kt diff --git a/feature/setting/presentation/build.gradle.kts b/feature/setting/presentation/build.gradle.kts index c067b5e1c2..8a454bd2bc 100644 --- a/feature/setting/presentation/build.gradle.kts +++ b/feature/setting/presentation/build.gradle.kts @@ -43,6 +43,7 @@ dependencies { testImplementation(libs.robolectric) testImplementation(libs.androidx.test.core.ktx) testImplementation(testFixtures(projects.core.domain)) + testImplementation(testFixtures(projects.core.ui)) testImplementation(testFixtures(projects.feature.setting.domain)) testImplementation(projects.core.network) testImplementation(platform(libs.androidx.compose.bom)) diff --git a/feature/setting/presentation/src/test/kotlin/com/afternote/feature/setting/presentation/screen/DeliveryConditionTouchTargetTest.kt b/feature/setting/presentation/src/test/kotlin/com/afternote/feature/setting/presentation/screen/DeliveryConditionTouchTargetTest.kt new file mode 100644 index 0000000000..61c385627f --- /dev/null +++ b/feature/setting/presentation/src/test/kotlin/com/afternote/feature/setting/presentation/screen/DeliveryConditionTouchTargetTest.kt @@ -0,0 +1,102 @@ +package com.afternote.feature.setting.presentation.screen + +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.click +import androidx.compose.ui.test.junit4.v2.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.performScrollTo +import androidx.compose.ui.test.performTouchInput +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.dp +import androidx.test.core.app.ApplicationProvider +import com.afternote.core.domain.testing.FakeUserReceiverRepository +import com.afternote.core.ui.testing.MinimumTouchTargetSize +import com.afternote.core.ui.testing.scanEnabledClickTargets +import com.afternote.core.ui.theme.AfternoteTheme +import com.afternote.feature.setting.domain.UpdateTimeLetterDeliveryConditionUseCase +import com.afternote.feature.setting.presentation.R +import com.afternote.feature.setting.presentation.navigation.SettingRoute +import com.afternote.feature.setting.presentation.viewmodel.DeliveryConditionViewModel +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import org.robolectric.annotation.GraphicsMode + +@RunWith(RobolectricTestRunner::class) +@GraphicsMode(GraphicsMode.Mode.NATIVE) +@Config(sdk = [35], qualifiers = "w360dp-h800dp-xhdpi") +class DeliveryConditionTouchTargetTest { + @get:Rule + val composeRule = createComposeRule() + + @Test + fun lastGreetingAction_acceptsTouchOutsideIts24dpVisualRow() { + verifyLastGreetingTarget(fontScale = 1f, tapExpandedArea = true) + } + + @Test + @Config(qualifiers = "w320dp-h568dp-xhdpi") + fun lastGreetingAction_keeps48dpTargetAfterScrollingOnCompactScreenWithLargeText() { + verifyLastGreetingTarget(fontScale = 2f, tapExpandedArea = false) + } + + private fun verifyLastGreetingTarget( + fontScale: Float, + tapExpandedArea: Boolean, + ) { + val context = ApplicationProvider.getApplicationContext() + val actionName = context.getString(R.string.last_greeting_edit_section_title) + val repository = FakeUserReceiverRepository() + val viewModel = + DeliveryConditionViewModel( + route = SettingRoute.AfterDeliveryRoute(receiverId = 1L), + userRepository = repository, + updateTimeLetterDeliveryCondition = UpdateTimeLetterDeliveryConditionUseCase(repository), + ) + var actionCalls = 0 + var density = 0f + composeRule.setContent { + density = LocalDensity.current.density + CompositionLocalProvider(LocalDensity provides Density(density, fontScale)) { + AfternoteTheme { + DeliveryConditionScreen( + onBack = {}, + onSaveSuccess = {}, + onLastGreetingEditClick = { actionCalls++ }, + viewModel = viewModel, + ) + } + } + } + + composeRule.onNodeWithText(actionName).performScrollTo().assertIsDisplayed() + val target = composeRule.scanEnabledClickTargets().single { it.name == actionName } + val diagnostic = + "fontScale=$fontScale touch=${target.width}x${target.height} " + + "layout=${target.layoutWidth}x${target.layoutHeight}" + println(diagnostic) + assertFalse(diagnostic, target.isSmallerThan(MinimumTouchTargetSize)) + + val tapY = + if (tapExpandedArea) { + assertEquals(24.dp.value, target.layoutHeight.value, 0.1f) + assertTrue(diagnostic, target.touchBounds.top < target.layoutBounds.top) + (target.touchBounds.top.value + target.layoutBounds.top.value) / 2f + } else { + assertTrue("Large text must actually enlarge the rendered row", target.layoutHeight > 24.dp) + (target.touchBounds.top.value + target.touchBounds.bottom.value) / 2f + } + val tapX = (target.touchBounds.left.value + target.touchBounds.right.value) / 2f + composeRule.onRoot().performTouchInput { click(Offset(tapX * density, tapY * density)) } + composeRule.runOnIdle { assertEquals("The measured hit target must invoke the real screen action", 1, actionCalls) } + } +}