diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d757805..92b653f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -225,6 +225,9 @@ dependencies { androidTestImplementation("androidx.test.ext:junit:1.2.1") androidTestImplementation("androidx.test:runner:1.6.2") + // GrantPermissionRule: permissions have to be granted before the + // activity launches, or its permission dialog steals focus from Compose. + androidTestImplementation("androidx.test:rules:1.6.1") androidTestImplementation(platform("androidx.compose:compose-bom:2024.12.01")) androidTestImplementation("androidx.compose.ui:ui-test-junit4") debugImplementation("androidx.compose.ui:ui-test-manifest") diff --git a/app/src/androidTest/kotlin/io/flamenet/messenger/app/ChatFlowTest.kt b/app/src/androidTest/kotlin/io/flamenet/messenger/app/ChatFlowTest.kt index e49c040..64fad42 100644 --- a/app/src/androidTest/kotlin/io/flamenet/messenger/app/ChatFlowTest.kt +++ b/app/src/androidTest/kotlin/io/flamenet/messenger/app/ChatFlowTest.kt @@ -10,6 +10,7 @@ import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTextClearance import androidx.compose.ui.test.performTextInput import androidx.compose.ui.test.printToString +import androidx.test.rule.GrantPermissionRule import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Assume.assumeTrue @@ -36,7 +37,24 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class ChatFlowTest { - @get:Rule val compose = createAndroidComposeRule() + /** + * 🔴 Granted BEFORE the activity launches, and that ordering is the point. + * + * `MainActivity.onCreate` asks for POST_NOTIFICATIONS before it calls + * `setContent`. The permission dialog is a separate activity and takes + * focus, so the Compose rule attaches to a window that has no hierarchy + * yet and every test dies with "No compose hierarchies found in the app". + * + * `order = 0` puts this outside the Compose rule, so the grant happens + * first and no dialog ever appears. + */ + @get:Rule(order = 0) + val permissions: GrantPermissionRule = GrantPermissionRule.grant( + android.Manifest.permission.POST_NOTIFICATIONS, + ) + + @get:Rule(order = 1) + val compose = createAndroidComposeRule() /** * Which relay to drive. diff --git a/app/src/androidTest/kotlin/io/flamenet/messenger/app/CrossPlatformTest.kt b/app/src/androidTest/kotlin/io/flamenet/messenger/app/CrossPlatformTest.kt index ff7d3e0..6e37e55 100644 --- a/app/src/androidTest/kotlin/io/flamenet/messenger/app/CrossPlatformTest.kt +++ b/app/src/androidTest/kotlin/io/flamenet/messenger/app/CrossPlatformTest.kt @@ -10,6 +10,7 @@ import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTextClearance import androidx.compose.ui.test.performTextInput import androidx.compose.ui.test.printToString +import androidx.test.rule.GrantPermissionRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import org.junit.Rule @@ -33,7 +34,24 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class CrossPlatformTest { - @get:Rule val compose = createAndroidComposeRule() + /** + * 🔴 Granted BEFORE the activity launches, and that ordering is the point. + * + * `MainActivity.onCreate` asks for POST_NOTIFICATIONS before it calls + * `setContent`. The permission dialog is a separate activity and takes + * focus, so the Compose rule attaches to a window that has no hierarchy + * yet and every test dies with "No compose hierarchies found in the app". + * + * `order = 0` puts this outside the Compose rule, so the grant happens + * first and no dialog ever appears. + */ + @get:Rule(order = 0) + val permissions: GrantPermissionRule = GrantPermissionRule.grant( + android.Manifest.permission.POST_NOTIFICATIONS, + ) + + @get:Rule(order = 1) + val compose = createAndroidComposeRule() private val args get() = InstrumentationRegistry.getArguments() private val relay: String get() = args.getString("relay") ?: "relay1.flamenet.io" diff --git a/app/src/androidTest/kotlin/io/flamenet/messenger/app/CrudSurfaceTest.kt b/app/src/androidTest/kotlin/io/flamenet/messenger/app/CrudSurfaceTest.kt index 9034d0a..5cbc15b 100644 --- a/app/src/androidTest/kotlin/io/flamenet/messenger/app/CrudSurfaceTest.kt +++ b/app/src/androidTest/kotlin/io/flamenet/messenger/app/CrudSurfaceTest.kt @@ -10,6 +10,7 @@ import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTextClearance import androidx.compose.ui.test.performTextInput import androidx.compose.ui.test.printToString +import androidx.test.rule.GrantPermissionRule import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Assume.assumeTrue @@ -35,7 +36,24 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class CrudSurfaceTest { - @get:Rule val compose = createAndroidComposeRule() + /** + * 🔴 Granted BEFORE the activity launches, and that ordering is the point. + * + * `MainActivity.onCreate` asks for POST_NOTIFICATIONS before it calls + * `setContent`. The permission dialog is a separate activity and takes + * focus, so the Compose rule attaches to a window that has no hierarchy + * yet and every test dies with "No compose hierarchies found in the app". + * + * `order = 0` puts this outside the Compose rule, so the grant happens + * first and no dialog ever appears. + */ + @get:Rule(order = 0) + val permissions: GrantPermissionRule = GrantPermissionRule.grant( + android.Manifest.permission.POST_NOTIFICATIONS, + ) + + @get:Rule(order = 1) + val compose = createAndroidComposeRule() /** * Which relay to drive. diff --git a/app/src/androidTest/kotlin/io/flamenet/messenger/app/FeatureUseTest.kt b/app/src/androidTest/kotlin/io/flamenet/messenger/app/FeatureUseTest.kt index db74cb2..ec8e0a3 100644 --- a/app/src/androidTest/kotlin/io/flamenet/messenger/app/FeatureUseTest.kt +++ b/app/src/androidTest/kotlin/io/flamenet/messenger/app/FeatureUseTest.kt @@ -8,6 +8,7 @@ import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTextClearance import androidx.compose.ui.test.performTextInput import androidx.compose.ui.test.printToString +import androidx.test.rule.GrantPermissionRule import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Assume.assumeTrue @@ -40,7 +41,24 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class FeatureUseTest { - @get:Rule val compose = createAndroidComposeRule() + /** + * 🔴 Granted BEFORE the activity launches, and that ordering is the point. + * + * `MainActivity.onCreate` asks for POST_NOTIFICATIONS before it calls + * `setContent`. The permission dialog is a separate activity and takes + * focus, so the Compose rule attaches to a window that has no hierarchy + * yet and every test dies with "No compose hierarchies found in the app". + * + * `order = 0` puts this outside the Compose rule, so the grant happens + * first and no dialog ever appears. + */ + @get:Rule(order = 0) + val permissions: GrantPermissionRule = GrantPermissionRule.grant( + android.Manifest.permission.POST_NOTIFICATIONS, + ) + + @get:Rule(order = 1) + val compose = createAndroidComposeRule() /** * Which relay to drive. diff --git a/app/src/androidTest/kotlin/io/flamenet/messenger/app/LiveSignUpFleetTest.kt b/app/src/androidTest/kotlin/io/flamenet/messenger/app/LiveSignUpFleetTest.kt index 816dc30..14b951a 100644 --- a/app/src/androidTest/kotlin/io/flamenet/messenger/app/LiveSignUpFleetTest.kt +++ b/app/src/androidTest/kotlin/io/flamenet/messenger/app/LiveSignUpFleetTest.kt @@ -9,6 +9,7 @@ import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTextClearance import androidx.compose.ui.test.performTextInput import androidx.compose.ui.test.printToString +import androidx.test.rule.GrantPermissionRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import org.junit.Assume.assumeTrue @@ -50,7 +51,24 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class LiveSignUpFleetTest { - @get:Rule val compose = createAndroidComposeRule() + /** + * 🔴 Granted BEFORE the activity launches, and that ordering is the point. + * + * `MainActivity.onCreate` asks for POST_NOTIFICATIONS before it calls + * `setContent`. The permission dialog is a separate activity and takes + * focus, so the Compose rule attaches to a window that has no hierarchy + * yet and every test dies with "No compose hierarchies found in the app". + * + * `order = 0` puts this outside the Compose rule, so the grant happens + * first and no dialog ever appears. + */ + @get:Rule(order = 0) + val permissions: GrantPermissionRule = GrantPermissionRule.grant( + android.Manifest.permission.POST_NOTIFICATIONS, + ) + + @get:Rule(order = 1) + val compose = createAndroidComposeRule() private val args get() = InstrumentationRegistry.getArguments() diff --git a/app/src/androidTest/kotlin/io/flamenet/messenger/app/SignUpFlowTest.kt b/app/src/androidTest/kotlin/io/flamenet/messenger/app/SignUpFlowTest.kt index 68e3b0c..c6d1379 100644 --- a/app/src/androidTest/kotlin/io/flamenet/messenger/app/SignUpFlowTest.kt +++ b/app/src/androidTest/kotlin/io/flamenet/messenger/app/SignUpFlowTest.kt @@ -9,6 +9,7 @@ import androidx.compose.ui.test.printToString import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTextClearance import androidx.compose.ui.test.performTextInput +import androidx.test.rule.GrantPermissionRule import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Assume.assumeTrue @@ -36,7 +37,24 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SignUpFlowTest { - @get:Rule val compose = createAndroidComposeRule() + /** + * 🔴 Granted BEFORE the activity launches, and that ordering is the point. + * + * `MainActivity.onCreate` asks for POST_NOTIFICATIONS before it calls + * `setContent`. The permission dialog is a separate activity and takes + * focus, so the Compose rule attaches to a window that has no hierarchy + * yet and every test dies with "No compose hierarchies found in the app". + * + * `order = 0` puts this outside the Compose rule, so the grant happens + * first and no dialog ever appears. + */ + @get:Rule(order = 0) + val permissions: GrantPermissionRule = GrantPermissionRule.grant( + android.Manifest.permission.POST_NOTIFICATIONS, + ) + + @get:Rule(order = 1) + val compose = createAndroidComposeRule() /** * Which relay to drive.