diff --git a/.agents/skills/android-maps-testing/SKILL.md b/.agents/skills/android-maps-testing/SKILL.md index 93e7dea..8e49315 100644 --- a/.agents/skills/android-maps-testing/SKILL.md +++ b/.agents/skills/android-maps-testing/SKILL.md @@ -181,14 +181,13 @@ dependencies { Use `withTestMap { ... }` to spin up a measured, laid-out, initialized `GoogleMap` in one line and assert state with `com.google.maps.android.robolectric.truth.assertThat`: -Reference: `// [START maps_robolectric_demo_test]` in [`MapDemoTest.kt`](../../../app/src/test/java/com/example/androidmapsrobolectric/MapDemoTest.kt#L36-L83) +Reference: `// [START maps_robolectric_demo_test]` in [`MapDemoTest.kt`](../../../app/src/test/java/com/example/androidmapsrobolectric/MapDemoTest.kt#L35-L81) ```kotlin // [START maps_robolectric_demo_test] -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows class MapDemoTest { @Test @@ -240,7 +239,7 @@ class MapDemoTest { If you prefer plain boolean expressions with zero Google Truth dependency, import `assertMap`, `assertMarker`, `distanceTo`, and `meters` directly from `com.google.maps.android.robolectric.powerassert`: -Reference: `// [START maps_robolectric_power_assert]` in [`LazyAssertionsComparisonTest.kt`](../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/LazyAssertionsComparisonTest.kt#L151-L187) +Reference: `// [START maps_robolectric_power_assert]` in [`LazyAssertionsComparisonTest.kt`](../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/LazyAssertionsComparisonTest.kt#L149-L185) ```kotlin // [START maps_robolectric_power_assert] @@ -288,14 +287,13 @@ Reference: `// [START maps_robolectric_power_assert]` in [`LazyAssertionsCompari Pass the test `GoogleMap` from `createTestMap()` directly into your production controller (`StoreLocatorMapManager`), or locate an embedded `MapView` inside an `Activity` or Jetpack Compose screen via `requireMapView()`: -Reference: `// [START maps_robolectric_manager_test]` in [`StoreLocatorMapManagerTest.kt`](../../../app/src/test/java/com/example/androidmapsrobolectric/manager/StoreLocatorMapManagerTest.kt#L37-L129) +Reference: `// [START maps_robolectric_manager_test]` in [`StoreLocatorMapManagerTest.kt`](../../../app/src/test/java/com/example/androidmapsrobolectric/manager/StoreLocatorMapManagerTest.kt#L36-L127) ```kotlin // [START maps_robolectric_manager_test] -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows class StoreLocatorMapManagerTest { private lateinit var googleMap: GoogleMap @@ -386,7 +384,7 @@ class StoreLocatorMapManagerTest { // [END maps_robolectric_manager_test] ``` -Reference: `// [START robolectric_view_hierarchy_lookup]` in [`ShadowMapViewKotlinTest.kt`](../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt#L511-L521) +Reference: `// [START robolectric_view_hierarchy_lookup]` in [`ShadowMapViewKotlinTest.kt`](../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt#L508-L518) ```kotlin // [START robolectric_view_hierarchy_lookup] @@ -483,7 +481,7 @@ Reference: `// [START maps_robolectric_java_demo_test]` in [`MapViewDemoTest.jav Use `enableGoldenTesting(...)` to replace live Google tiles with deterministic synthetic tiles (`CoordinateGrid`, `SolidColorHash`, `Checkerboard`, or `Custom`) and verify renders with `captureSnapshot()`, `assertMatchesGolden(goldenFile)`, or `GoldenImageDiff.assertMatches(...)`: -Reference: `// [START maps_sample_golden_map_test]` in [`SampleGoldenMapTest.kt`](../../../golden-testing/src/test/java/com/google/maps/android/testing/golden/sample/SampleGoldenMapTest.kt#L46-L128) +Reference: `// [START maps_sample_golden_map_test]` in [`SampleGoldenMapTest.kt`](../../../golden-testing/src/test/java/com/google/maps/android/testing/golden/sample/SampleGoldenMapTest.kt#L45-L126) ```kotlin // [START maps_sample_golden_map_test] @@ -496,10 +494,9 @@ Reference: `// [START maps_sample_golden_map_test]` in [`SampleGoldenMapTest.kt` * 3. The map snapshot is captured via [GoogleMap.captureSnapshot]. * 4. The rendered snapshot is verified against a baseline using [GoldenImageDiff.assertMatches]. */ -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows public class SampleGoldenMapTest { private lateinit var googleMap: GoogleMap diff --git a/.agents/skills/android-maps-testing/assets/templates/GoldenMapTestTemplate.kt b/.agents/skills/android-maps-testing/assets/templates/GoldenMapTestTemplate.kt index 8da79a0..7c9d956 100644 --- a/.agents/skills/android-maps-testing/assets/templates/GoldenMapTestTemplate.kt +++ b/.agents/skills/android-maps-testing/assets/templates/GoldenMapTestTemplate.kt @@ -20,7 +20,7 @@ import android.graphics.Color import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.gms.maps.model.CircleOptions import com.google.android.gms.maps.model.LatLng -import com.google.maps.android.robolectric.annotation.EnableMapsShadows +import com.google.maps.android.robolectric.MapsRobolectricTestRunner import com.google.maps.android.robolectric.ktx.withTestMap import com.google.maps.android.testing.golden.GoldenImageDiff import com.google.maps.android.testing.golden.GoldenTileStrategy @@ -30,7 +30,6 @@ import com.google.maps.android.testing.golden.enableGoldenTesting import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config import org.robolectric.annotation.GraphicsMode import java.io.File @@ -38,10 +37,9 @@ import java.io.File /** * Standard template for deterministic, offline golden image screenshot testing. */ -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows class GoldenMapTestTemplate { @Test diff --git a/.agents/skills/android-maps-testing/assets/templates/RobolectricMapTestTemplate.kt b/.agents/skills/android-maps-testing/assets/templates/RobolectricMapTestTemplate.kt index 46aafb7..48e99e5 100644 --- a/.agents/skills/android-maps-testing/assets/templates/RobolectricMapTestTemplate.kt +++ b/.agents/skills/android-maps-testing/assets/templates/RobolectricMapTestTemplate.kt @@ -22,7 +22,7 @@ import com.google.android.gms.maps.model.BitmapDescriptorFactory import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.MarkerOptions import com.google.android.gms.maps.model.PolygonOptions -import com.google.maps.android.robolectric.annotation.EnableMapsShadows +import com.google.maps.android.robolectric.MapsRobolectricTestRunner import com.google.maps.android.robolectric.ktx.clickMarker import com.google.maps.android.robolectric.ktx.simulateUserCameraMove import com.google.maps.android.robolectric.ktx.withTestMap @@ -30,17 +30,15 @@ import com.google.maps.android.robolectric.truth.assertThat import com.google.maps.android.robolectric.truth.meters import org.junit.Test import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config import org.robolectric.annotation.GraphicsMode /** * Standard template for headless JVM Google Maps unit testing with Robolectric shadows. */ -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows class RobolectricMapTestTemplate { @Test diff --git a/.agents/skills/android-maps-testing/references/golden_image_testing.md b/.agents/skills/android-maps-testing/references/golden_image_testing.md index 0f9c8c4..6792f86 100644 --- a/.agents/skills/android-maps-testing/references/golden_image_testing.md +++ b/.agents/skills/android-maps-testing/references/golden_image_testing.md @@ -273,7 +273,7 @@ Reference: `// [START golden_diff_artifacts_and_record]` in [`GoldenImageDiffTes ## 6. End-to-End Sample Test -Reference: `// [START maps_sample_golden_map_test]` in [`SampleGoldenMapTest.kt`](../../../../golden-testing/src/test/java/com/google/maps/android/testing/golden/sample/SampleGoldenMapTest.kt#L46-L128) +Reference: `// [START maps_sample_golden_map_test]` in [`SampleGoldenMapTest.kt`](../../../../golden-testing/src/test/java/com/google/maps/android/testing/golden/sample/SampleGoldenMapTest.kt#L45-L126) ```kotlin // [START maps_sample_golden_map_test] @@ -286,10 +286,9 @@ Reference: `// [START maps_sample_golden_map_test]` in [`SampleGoldenMapTest.kt` * 3. The map snapshot is captured via [GoogleMap.captureSnapshot]. * 4. The rendered snapshot is verified against a baseline using [GoldenImageDiff.assertMatches]. */ -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows public class SampleGoldenMapTest { private lateinit var googleMap: GoogleMap diff --git a/.agents/skills/android-maps-testing/references/jetpack_compose_and_ecosystem.md b/.agents/skills/android-maps-testing/references/jetpack_compose_and_ecosystem.md index 6e8894c..6068d99 100644 --- a/.agents/skills/android-maps-testing/references/jetpack_compose_and_ecosystem.md +++ b/.agents/skills/android-maps-testing/references/jetpack_compose_and_ecosystem.md @@ -8,7 +8,7 @@ This guide covers testing Google Maps within **Jetpack Compose** applications an Both `:robolectric-testing` (`com.google.maps.android.robolectric.ktx`) and `:golden-testing` (`com.google.maps.android.testing.golden`) provide view hierarchy traversal helpers to extract the hosted `MapView` from an `Activity` or Jetpack Compose `AndroidView` container: -Reference: `// [START robolectric_view_hierarchy_lookup]` in [`ShadowMapViewKotlinTest.kt`](../../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt#L511-L521) +Reference: `// [START robolectric_view_hierarchy_lookup]` in [`ShadowMapViewKotlinTest.kt`](../../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt#L508-L518) ```kotlin // [START robolectric_view_hierarchy_lookup] @@ -151,14 +151,13 @@ public fun rememberGoldenTileProvider( `android-maps-testing` faithfully models `ShadowProjection` and `ShadowGoogleMap` so that distance-based marker clustering algorithms (like `DistanceBasedAlgorithm` and `NonHierarchicalDistanceBasedAlgorithm`) operate identically on the local JVM: -Reference: `// [START maps_robolectric_clustering_test]` in [`MapsUtilsClusteringIntegrationTest.kt`](../../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/MapsUtilsClusteringIntegrationTest.kt#L45-L167) +Reference: `// [START maps_robolectric_clustering_test]` in [`MapsUtilsClusteringIntegrationTest.kt`](../../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/MapsUtilsClusteringIntegrationTest.kt#L44-L165) ```kotlin // [START maps_robolectric_clustering_test] -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows class MapsUtilsClusteringIntegrationTest { private lateinit var mapView: MapView diff --git a/.agents/skills/android-maps-testing/references/modular_architecture_and_anti_bloat.md b/.agents/skills/android-maps-testing/references/modular_architecture_and_anti_bloat.md index 2f3925a..483199f 100644 --- a/.agents/skills/android-maps-testing/references/modular_architecture_and_anti_bloat.md +++ b/.agents/skills/android-maps-testing/references/modular_architecture_and_anti_bloat.md @@ -231,14 +231,13 @@ class StoreLocatorMapManager( ## 5. Testing the Controller on JVM with `createTestMap` & Domain Assertions -Reference: `// [START maps_robolectric_manager_test]` in [`StoreLocatorMapManagerTest.kt`](../../../../app/src/test/java/com/example/androidmapsrobolectric/manager/StoreLocatorMapManagerTest.kt#L37-L129) +Reference: `// [START maps_robolectric_manager_test]` in [`StoreLocatorMapManagerTest.kt`](../../../../app/src/test/java/com/example/androidmapsrobolectric/manager/StoreLocatorMapManagerTest.kt#L36-L127) ```kotlin // [START maps_robolectric_manager_test] -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows class StoreLocatorMapManagerTest { private lateinit var googleMap: GoogleMap diff --git a/.agents/skills/android-maps-testing/references/robolectric_shadows_and_jvm_testing.md b/.agents/skills/android-maps-testing/references/robolectric_shadows_and_jvm_testing.md index 9bedd9a..cad606b 100644 --- a/.agents/skills/android-maps-testing/references/robolectric_shadows_and_jvm_testing.md +++ b/.agents/skills/android-maps-testing/references/robolectric_shadows_and_jvm_testing.md @@ -31,18 +31,17 @@ The library provides high-fidelity shadows covering the full Google Maps SDK sur ## 2. Test Configuration & 1-Liner KTX Harness -Annotate your test class with `@EnableMapsShadows`, `@RunWith(RobolectricTestRunner::class)`, and `@GraphicsMode(GraphicsMode.Mode.NATIVE)`. +Annotate your test class with `@RunWith(MapsRobolectricTestRunner::class)` and `@GraphicsMode(GraphicsMode.Mode.NATIVE)`. The runner registers every Maps shadow; `@EnableMapsShadows` is deprecated because it never registered any. Instead of manually constructing an `Activity`, measuring/laying out a `MapView`, and idling `ShadowLooper`, use `withTestMap { ... }` or `createTestMap()` from `com.google.maps.android.robolectric.ktx`: -Reference: `// [START maps_robolectric_demo_test]` in [`MapDemoTest.kt`](../../../../app/src/test/java/com/example/androidmapsrobolectric/MapDemoTest.kt#L36-L83) +Reference: `// [START maps_robolectric_demo_test]` in [`MapDemoTest.kt`](../../../../app/src/test/java/com/example/androidmapsrobolectric/MapDemoTest.kt#L35-L81) ```kotlin // [START maps_robolectric_demo_test] -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows class MapDemoTest { @Test @@ -94,7 +93,7 @@ class MapDemoTest { `:robolectric-testing` supports both **Google Truth** domain subjects (`com.google.maps.android.robolectric.truth.assertThat`) and **Kotlin Power-Assert** (`com.google.maps.android.robolectric.powerassert.assertMap`, `assertMarker`, `assertGeo`, with `distanceTo` and `meters` included directly in `.powerassert`): -Reference: `// [START maps_robolectric_truth_domain_subjects]` and `// [START maps_robolectric_power_assert]` in [`LazyAssertionsComparisonTest.kt`](../../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/LazyAssertionsComparisonTest.kt#L114-L187) +Reference: `// [START maps_robolectric_truth_domain_subjects]` and `// [START maps_robolectric_power_assert]` in [`LazyAssertionsComparisonTest.kt`](../../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/LazyAssertionsComparisonTest.kt#L112-L185) ```kotlin // [START maps_robolectric_truth_domain_subjects] @@ -179,7 +178,7 @@ Reference: `// [START maps_robolectric_truth_domain_subjects]` and `// [START ma `ShadowGoogleMap` computes accurate Web Mercator zoom levels for `CameraUpdateFactory.newLatLngBounds` (accounting for map padding and explicit width/height) and pixel-accurate shifts for `CameraUpdateFactory.scrollBy`: -Reference: `// [START robolectric_camera_bounds_and_scroll]` in [`ShadowMapViewKotlinTest.kt`](../../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt#L248-L270) +Reference: `// [START robolectric_camera_bounds_and_scroll]` in [`ShadowMapViewKotlinTest.kt`](../../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt#L246-L268) ```kotlin // [START robolectric_camera_bounds_and_scroll] @@ -216,7 +215,7 @@ When `clickMarker` is invoked, `ShadowGoogleMap` follows the exact Maps SDK cont - Opening an `InfoWindow` on one marker automatically hides any previously open `InfoWindow` on the same map. - If the listener returns `true`, the event is consumed (suppressing default InfoWindow and camera move). -Reference: `// [START robolectric_marker_click_behavior]` in [`ShadowMapViewKotlinTest.kt`](../../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt#L287-L317) +Reference: `// [START robolectric_marker_click_behavior]` in [`ShadowMapViewKotlinTest.kt`](../../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt#L285-L315) ```kotlin // [START robolectric_marker_click_behavior] @@ -258,7 +257,7 @@ Reference: `// [START robolectric_marker_click_behavior]` in [`ShadowMapViewKotl Use `map.simulateUserCameraMove(...)` to dispatch `OnCameraMoveStartedListener.REASON_GESTURE` (distinguishing user drags from programmatic `moveCamera`/`animateCamera` calls that fire `REASON_DEVELOPER_ANIMATION`), and assert `MapColorScheme`, `MapStyleOptions`, `setLatLngBoundsForCameraTarget`, and `contentDescription`: -Reference: `// [START robolectric_user_gesture_and_styling]` in [`ShadowMapViewKotlinTest.kt`](../../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt#L328-L372) +Reference: `// [START robolectric_user_gesture_and_styling]` in [`ShadowMapViewKotlinTest.kt`](../../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt#L326-L370) ```kotlin // [START robolectric_user_gesture_and_styling] @@ -312,7 +311,7 @@ Reference: `// [START robolectric_user_gesture_and_styling]` in [`ShadowMapViewK ## 7. Extracting `MapView` from Activity or Jetpack Compose View Hierarchies -Reference: `// [START robolectric_view_hierarchy_lookup]` in [`ShadowMapViewKotlinTest.kt`](../../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt#L511-L521) +Reference: `// [START robolectric_view_hierarchy_lookup]` in [`ShadowMapViewKotlinTest.kt`](../../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt#L508-L518) ```kotlin // [START robolectric_view_hierarchy_lookup] diff --git a/.agents/skills/maps-golden-testing/SKILL.md b/.agents/skills/maps-golden-testing/SKILL.md index 4bf8564..af8040b 100644 --- a/.agents/skills/maps-golden-testing/SKILL.md +++ b/.agents/skills/maps-golden-testing/SKILL.md @@ -43,7 +43,7 @@ dependencies { `googleMap.awaitMapLoaded()` and `googleMap.captureSnapshot()` automatically idle `ShadowLooper.idleMainLooper()` when running under Robolectric, so you can call them directly inside `runTest` with zero `async` or `testScheduler` boilerplate: -Reference: `// [START maps_sample_golden_map_test]` in [`SampleGoldenMapTest.kt`](../../../golden-testing/src/test/java/com/google/maps/android/testing/golden/sample/SampleGoldenMapTest.kt#L46-L128) +Reference: `// [START maps_sample_golden_map_test]` in [`SampleGoldenMapTest.kt`](../../../golden-testing/src/test/java/com/google/maps/android/testing/golden/sample/SampleGoldenMapTest.kt#L45-L126) ```kotlin // [START maps_sample_golden_map_test] @@ -56,10 +56,9 @@ Reference: `// [START maps_sample_golden_map_test]` in [`SampleGoldenMapTest.kt` * 3. The map snapshot is captured via [GoogleMap.captureSnapshot]. * 4. The rendered snapshot is verified against a baseline using [GoldenImageDiff.assertMatches]. */ -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows public class SampleGoldenMapTest { private lateinit var googleMap: GoogleMap diff --git a/.agents/skills/maps-robolectric-testing/SKILL.md b/.agents/skills/maps-robolectric-testing/SKILL.md index d26e790..b95fb76 100644 --- a/.agents/skills/maps-robolectric-testing/SKILL.md +++ b/.agents/skills/maps-robolectric-testing/SKILL.md @@ -39,10 +39,9 @@ dependencies { Annotate your test class with: ```kotlin -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows ``` --- @@ -51,14 +50,13 @@ Annotate your test class with: ### A. 1-Liner KTX Test Harness (`withTestMap`) & Unified Google Truth (`assertThat(map)`) -Reference: `// [START maps_robolectric_demo_test]` in [`MapDemoTest.kt`](../../../app/src/test/java/com/example/androidmapsrobolectric/MapDemoTest.kt#L36-L83) +Reference: `// [START maps_robolectric_demo_test]` in [`MapDemoTest.kt`](../../../app/src/test/java/com/example/androidmapsrobolectric/MapDemoTest.kt#L35-L81) ```kotlin // [START maps_robolectric_demo_test] -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows class MapDemoTest { @Test @@ -110,7 +108,7 @@ class MapDemoTest { Import `assertMap`, `assertMarker`, `assertGeo`, `distanceTo`, and `meters` directly from `com.google.maps.android.robolectric.powerassert`: -Reference: `// [START maps_robolectric_power_assert]` in [`LazyAssertionsComparisonTest.kt`](../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/LazyAssertionsComparisonTest.kt#L151-L187) +Reference: `// [START maps_robolectric_power_assert]` in [`LazyAssertionsComparisonTest.kt`](../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/LazyAssertionsComparisonTest.kt#L149-L185) ```kotlin // [START maps_robolectric_power_assert] @@ -156,14 +154,13 @@ Reference: `// [START maps_robolectric_power_assert]` in [`LazyAssertionsCompari ### C. Testing Production Map Controllers (`createTestMap`) -Reference: `// [START maps_robolectric_manager_test]` in [`StoreLocatorMapManagerTest.kt`](../../../app/src/test/java/com/example/androidmapsrobolectric/manager/StoreLocatorMapManagerTest.kt#L37-L129) +Reference: `// [START maps_robolectric_manager_test]` in [`StoreLocatorMapManagerTest.kt`](../../../app/src/test/java/com/example/androidmapsrobolectric/manager/StoreLocatorMapManagerTest.kt#L36-L127) ```kotlin // [START maps_robolectric_manager_test] -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows class StoreLocatorMapManagerTest { private lateinit var googleMap: GoogleMap @@ -258,7 +255,7 @@ class StoreLocatorMapManagerTest { ### D. Camera Bounds (`newLatLngBounds`), `scrollBy`, Marker Clicks, and User Gestures (`REASON_GESTURE`) -- **Bounds & Scroll**: See `// [START robolectric_camera_bounds_and_scroll]` in [`ShadowMapViewKotlinTest.kt`](../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt#L248-L270). -- **Marker Click & InfoWindow Semantics**: See `// [START robolectric_marker_click_behavior]` in [`ShadowMapViewKotlinTest.kt`](../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt#L287-L317). -- **User Gesture (`REASON_GESTURE`) & Map Styling/Clamping**: See `// [START robolectric_user_gesture_and_styling]` in [`ShadowMapViewKotlinTest.kt`](../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt#L328-L372). -- **Activity & Compose View Hierarchy Lookup**: See `// [START robolectric_view_hierarchy_lookup]` in [`ShadowMapViewKotlinTest.kt`](../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt#L511-L521). +- **Bounds & Scroll**: See `// [START robolectric_camera_bounds_and_scroll]` in [`ShadowMapViewKotlinTest.kt`](../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt#L246-L268). +- **Marker Click & InfoWindow Semantics**: See `// [START robolectric_marker_click_behavior]` in [`ShadowMapViewKotlinTest.kt`](../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt#L285-L315). +- **User Gesture (`REASON_GESTURE`) & Map Styling/Clamping**: See `// [START robolectric_user_gesture_and_styling]` in [`ShadowMapViewKotlinTest.kt`](../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt#L326-L370). +- **Activity & Compose View Hierarchy Lookup**: See `// [START robolectric_view_hierarchy_lookup]` in [`ShadowMapViewKotlinTest.kt`](../../../robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt#L508-L518). diff --git a/README.md b/README.md index 0dde411..08248ec 100644 --- a/README.md +++ b/README.md @@ -153,10 +153,9 @@ Execute deterministic unit tests verifying map state, marker clicks, camera coor ```kotlin // [START maps_robolectric_demo_test] -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows class MapDemoTest { @Test @@ -268,10 +267,9 @@ Render maps with synthetic tiles and assert pixel-perfect fidelity across repeat * 3. The map snapshot is captured via [GoogleMap.captureSnapshot]. * 4. The rendered snapshot is verified against a baseline using [GoldenImageDiff.assertMatches]. */ -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows public class SampleGoldenMapTest { private lateinit var googleMap: GoogleMap diff --git a/app/src/test/java/com/example/androidmapsrobolectric/MapDemoTest.kt b/app/src/test/java/com/example/androidmapsrobolectric/MapDemoTest.kt index 320ca16..07adf7b 100644 --- a/app/src/test/java/com/example/androidmapsrobolectric/MapDemoTest.kt +++ b/app/src/test/java/com/example/androidmapsrobolectric/MapDemoTest.kt @@ -22,22 +22,20 @@ import com.google.android.gms.maps.model.BitmapDescriptorFactory import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.MarkerOptions import com.google.android.gms.maps.model.PolygonOptions -import com.google.maps.android.robolectric.annotation.EnableMapsShadows +import com.google.maps.android.robolectric.MapsRobolectricTestRunner import com.google.maps.android.robolectric.ktx.clickMarker import com.google.maps.android.robolectric.ktx.withTestMap import com.google.maps.android.robolectric.truth.assertThat import com.google.maps.android.robolectric.truth.meters import org.junit.Test import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config import org.robolectric.annotation.GraphicsMode // [START maps_robolectric_demo_test] -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows class MapDemoTest { @Test diff --git a/app/src/test/java/com/example/androidmapsrobolectric/manager/StoreLocatorLazyAssertComparisonTest.kt b/app/src/test/java/com/example/androidmapsrobolectric/manager/StoreLocatorLazyAssertComparisonTest.kt index 893c551..9ea14b8 100644 --- a/app/src/test/java/com/example/androidmapsrobolectric/manager/StoreLocatorLazyAssertComparisonTest.kt +++ b/app/src/test/java/com/example/androidmapsrobolectric/manager/StoreLocatorLazyAssertComparisonTest.kt @@ -20,7 +20,7 @@ import com.example.androidmapsrobolectric.model.Store import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.model.LatLng -import com.google.maps.android.robolectric.annotation.EnableMapsShadows +import com.google.maps.android.robolectric.MapsRobolectricTestRunner import com.google.maps.android.robolectric.ktx.cameraTarget import com.google.maps.android.robolectric.ktx.cameraZoom import com.google.maps.android.robolectric.ktx.circles @@ -43,7 +43,6 @@ import com.google.maps.android.robolectric.truth.meters import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config import org.robolectric.annotation.GraphicsMode import com.google.common.truth.Truth.assertThat as truthAssertThat @@ -58,10 +57,9 @@ import com.google.common.truth.Truth.assertThat as truthAssertThat * assertions eliminate boilerplate. */ // [START maps_robolectric_lazy_store_locator_comparison] -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows class StoreLocatorLazyAssertComparisonTest { private lateinit var map: GoogleMap diff --git a/app/src/test/java/com/example/androidmapsrobolectric/manager/StoreLocatorMapManagerTest.kt b/app/src/test/java/com/example/androidmapsrobolectric/manager/StoreLocatorMapManagerTest.kt index 9931cb2..ac39566 100644 --- a/app/src/test/java/com/example/androidmapsrobolectric/manager/StoreLocatorMapManagerTest.kt +++ b/app/src/test/java/com/example/androidmapsrobolectric/manager/StoreLocatorMapManagerTest.kt @@ -20,7 +20,7 @@ import com.example.androidmapsrobolectric.model.Store import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.model.LatLng -import com.google.maps.android.robolectric.annotation.EnableMapsShadows +import com.google.maps.android.robolectric.MapsRobolectricTestRunner import com.google.maps.android.robolectric.ktx.clickMarker import com.google.maps.android.robolectric.ktx.createTestMap import com.google.maps.android.robolectric.ktx.markers @@ -29,16 +29,14 @@ import com.google.maps.android.robolectric.truth.meters import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config import org.robolectric.annotation.GraphicsMode import com.google.common.truth.Truth.assertThat as truthAssertThat // [START maps_robolectric_manager_test] -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows class StoreLocatorMapManagerTest { private lateinit var googleMap: GoogleMap diff --git a/golden-testing/src/test/java/com/google/maps/android/testing/golden/ComposeMapTestExtensionsTest.kt b/golden-testing/src/test/java/com/google/maps/android/testing/golden/ComposeMapTestExtensionsTest.kt index 9badae9..deeb09b 100644 --- a/golden-testing/src/test/java/com/google/maps/android/testing/golden/ComposeMapTestExtensionsTest.kt +++ b/golden-testing/src/test/java/com/google/maps/android/testing/golden/ComposeMapTestExtensionsTest.kt @@ -26,7 +26,7 @@ import com.google.android.gms.maps.MapView import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.MarkerOptions import com.google.common.truth.Truth.assertThat -import com.google.maps.android.robolectric.annotation.EnableMapsShadows +import com.google.maps.android.robolectric.MapsRobolectricTestRunner import com.google.maps.android.robolectric.truth.meters import kotlinx.coroutines.async import kotlinx.coroutines.test.runTest @@ -34,15 +34,13 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.Robolectric -import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config import org.robolectric.annotation.GraphicsMode import org.robolectric.shadows.ShadowLooper -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows class ComposeMapTestExtensionsTest { private lateinit var activity: Activity diff --git a/golden-testing/src/test/java/com/google/maps/android/testing/golden/GoogleMapExtensionsRobolectricTest.kt b/golden-testing/src/test/java/com/google/maps/android/testing/golden/GoogleMapExtensionsRobolectricTest.kt index 3d80eab..11b7ef8 100644 --- a/golden-testing/src/test/java/com/google/maps/android/testing/golden/GoogleMapExtensionsRobolectricTest.kt +++ b/golden-testing/src/test/java/com/google/maps/android/testing/golden/GoogleMapExtensionsRobolectricTest.kt @@ -23,7 +23,7 @@ import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.model.CircleOptions import com.google.android.gms.maps.model.LatLng import com.google.common.truth.Truth.assertThat -import com.google.maps.android.robolectric.annotation.EnableMapsShadows +import com.google.maps.android.robolectric.MapsRobolectricTestRunner import com.google.maps.android.robolectric.ktx.createTestMap import com.google.maps.android.robolectric.ktx.shadow import com.google.maps.android.robolectric.ktx.withTestMap @@ -34,16 +34,14 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config import org.robolectric.annotation.GraphicsMode import java.io.File // [START maps_golden_extensions_test] -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows public class GoogleMapExtensionsRobolectricTest { @get:Rule diff --git a/golden-testing/src/test/java/com/google/maps/android/testing/golden/sample/SampleGoldenMapTest.kt b/golden-testing/src/test/java/com/google/maps/android/testing/golden/sample/SampleGoldenMapTest.kt index 4078f57..67fd213 100644 --- a/golden-testing/src/test/java/com/google/maps/android/testing/golden/sample/SampleGoldenMapTest.kt +++ b/golden-testing/src/test/java/com/google/maps/android/testing/golden/sample/SampleGoldenMapTest.kt @@ -28,7 +28,7 @@ import com.google.android.gms.maps.model.CircleOptions import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.PolygonOptions import com.google.common.truth.Truth.assertThat -import com.google.maps.android.robolectric.annotation.EnableMapsShadows +import com.google.maps.android.robolectric.MapsRobolectricTestRunner import com.google.maps.android.robolectric.ktx.createTestMap import com.google.maps.android.testing.golden.GoldenImageDiff import com.google.maps.android.testing.golden.GoldenTileStrategy @@ -39,7 +39,6 @@ import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config import org.robolectric.annotation.GraphicsMode @@ -53,10 +52,9 @@ import org.robolectric.annotation.GraphicsMode * 3. The map snapshot is captured via [GoogleMap.captureSnapshot]. * 4. The rendered snapshot is verified against a baseline using [GoldenImageDiff.assertMatches]. */ -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows public class SampleGoldenMapTest { private lateinit var googleMap: GoogleMap diff --git a/robolectric-testing/README.md b/robolectric-testing/README.md index efdba52..f1f6a49 100644 --- a/robolectric-testing/README.md +++ b/robolectric-testing/README.md @@ -11,7 +11,7 @@ High-fidelity Robolectric shadows and testing utilities for the Google Maps SDK - **Google Truth Domain Subjects**: Fluent subjects (`GoogleMapSubject`, `MarkerSubject`, `CameraPositionSubject`, `LatLngSubject`, `LatLngBoundsSubject`, `CircleSubject`, `PolygonSubject`, `PolylineSubject`) via unified `assertThat(...)`. - **Kotlin Power-Assert Support**: First-class boolean predicates, `Comparable` geodesic `Distance` (`distanceTo`, `<= 5.meters`), `in` bounds/circle operators, and `assertMap(...)` / `assertGeo(...)` entrypoints that render rich expression diagrams on failure. - **1-Liner Lazy Test Harness & KTX**: `createTestMap()`, `withTestMap { ... }`, `map.markers`, `map.clickMarker("Title")`, and `map.pixelColorAt(latLng)` eliminate setup and shadow extraction ceremony. -- **Zero Boilerplate**: Drop-in configuration with `@EnableMapsShadows` or automatic classpath discovery via `robolectric.properties`. +- **Zero Boilerplate**: Run the test class with `@RunWith(MapsRobolectricTestRunner::class)` and every Maps shadow is registered. `@EnableMapsShadows` is deprecated: it never registered any shadows, because Robolectric does not read `@Config` through annotations. - **Full Lifecycle & Async Dispatch**: Seamless simulation of `onCreate()`, `onStart()`, `onResume()`, `onDestroy()`, and async `getMapAsync()` callbacks. --- @@ -102,6 +102,20 @@ dependencies { ## Quick Start +### Registering the Maps shadows + +Run each test class with `MapsRobolectricTestRunner`. It registers every Maps shadow for that class, and any `@Config` on the class or method (such as `sdk`) still applies: + +```kotlin +@RunWith(MapsRobolectricTestRunner::class) +@Config(sdk = [34]) +class MyMapTest +``` + +- **`@EnableMapsShadows` is deprecated.** Robolectric does not read `@Config` placed on another annotation, so it never registered any shadows. Replace it with the runner. +- **Tests that use `@RunWith(AndroidJUnit4::class)`** run on Robolectric's default runner. Switch Maps tests to `MapsRobolectricTestRunner`, or list the shadows yourself with `@Config(shadows = [...])`. +- **Until 1.0.0 the library also ships a `robolectric.properties`** that registers the shadows for every Robolectric test. Robolectric reads only one `robolectric.properties` from the root of the classpath, so if your module has its own, the Maps shadows from that file are lost. The runner does not depend on it, and the file will be removed before 1.0.0. + ### Basic Map & Marker Test with Kotlin Power-Assert ```kotlin @@ -109,7 +123,7 @@ import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.gms.maps.model.BitmapDescriptorFactory import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.MarkerOptions -import com.google.maps.android.robolectric.annotation.EnableMapsShadows +import com.google.maps.android.robolectric.MapsRobolectricTestRunner import com.google.maps.android.robolectric.ktx.cameraTarget import com.google.maps.android.robolectric.ktx.cameraZoom import com.google.maps.android.robolectric.ktx.withTestMap @@ -119,10 +133,8 @@ import com.google.maps.android.robolectric.powerassert.distanceTo import com.google.maps.android.robolectric.powerassert.meters import org.junit.Test import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -@RunWith(RobolectricTestRunner::class) -@EnableMapsShadows +@RunWith(MapsRobolectricTestRunner::class) class MapScreenPowerAssertTest { @Test @@ -161,16 +173,14 @@ import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.gms.maps.model.BitmapDescriptorFactory import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.MarkerOptions -import com.google.maps.android.robolectric.annotation.EnableMapsShadows +import com.google.maps.android.robolectric.MapsRobolectricTestRunner import com.google.maps.android.robolectric.ktx.withTestMap import com.google.maps.android.robolectric.truth.assertThat import com.google.maps.android.robolectric.truth.meters import org.junit.Test import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -@RunWith(RobolectricTestRunner::class) -@EnableMapsShadows +@RunWith(MapsRobolectricTestRunner::class) class MapScreenTest { @Test @@ -318,10 +328,9 @@ Pass the test `GoogleMap` from `createTestMap()` into your own production contro ```kotlin // [START maps_robolectric_manager_test] -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows class StoreLocatorMapManagerTest { private lateinit var googleMap: GoogleMap diff --git a/robolectric-testing/api/robolectric-testing.api b/robolectric-testing/api/robolectric-testing.api index 31449d0..cf4884d 100644 --- a/robolectric-testing/api/robolectric-testing.api +++ b/robolectric-testing/api/robolectric-testing.api @@ -1,3 +1,12 @@ +public class com/google/maps/android/robolectric/MapsRobolectricTestRunner : org/robolectric/RobolectricTestRunner { + public static final field Companion Lcom/google/maps/android/robolectric/MapsRobolectricTestRunner$Companion; + public static final field MAPS_SHADOWS Ljava/util/List; + public fun (Ljava/lang/Class;)V +} + +public final class com/google/maps/android/robolectric/MapsRobolectricTestRunner$Companion { +} + public abstract interface annotation class com/google/maps/android/robolectric/annotation/EnableMapsShadows : java/lang/annotation/Annotation { } diff --git a/robolectric-testing/src/main/java/com/google/maps/android/robolectric/MapsRobolectricTestRunner.kt b/robolectric-testing/src/main/java/com/google/maps/android/robolectric/MapsRobolectricTestRunner.kt new file mode 100644 index 0000000..4b83df4 --- /dev/null +++ b/robolectric-testing/src/main/java/com/google/maps/android/robolectric/MapsRobolectricTestRunner.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.maps.android.robolectric + +import com.google.maps.android.robolectric.shadows.ShadowBitmapDescriptor +import com.google.maps.android.robolectric.shadows.ShadowBitmapDescriptorFactory +import com.google.maps.android.robolectric.shadows.ShadowCameraUpdate +import com.google.maps.android.robolectric.shadows.ShadowCameraUpdateFactory +import com.google.maps.android.robolectric.shadows.ShadowCircle +import com.google.maps.android.robolectric.shadows.ShadowGoogleMap +import com.google.maps.android.robolectric.shadows.ShadowGroundOverlay +import com.google.maps.android.robolectric.shadows.ShadowMapView +import com.google.maps.android.robolectric.shadows.ShadowMapsInitializer +import com.google.maps.android.robolectric.shadows.ShadowMarker +import com.google.maps.android.robolectric.shadows.ShadowPolygon +import com.google.maps.android.robolectric.shadows.ShadowPolyline +import com.google.maps.android.robolectric.shadows.ShadowProjection +import com.google.maps.android.robolectric.shadows.ShadowSupportMapFragment +import com.google.maps.android.robolectric.shadows.ShadowTileOverlay +import com.google.maps.android.robolectric.shadows.ShadowUiSettings +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import org.robolectric.pluginapi.config.GlobalConfigProvider +import org.robolectric.util.inject.Injector + +/** + * Robolectric test runner that registers every Google Maps SDK shadow for the tests it runs. + * + * Other `@Config` values on the test class or method (such as `sdk`) still apply, and shadows + * listed there are added to the Maps shadows. + * + * ```kotlin + * @RunWith(MapsRobolectricTestRunner::class) + * @Config(sdk = [34]) + * class MyMapTest { + * // All Maps shadows are registered + * } + * ``` + */ +// [START maps_robolectric_test_runner] +open class MapsRobolectricTestRunner(testClass: Class<*>) : RobolectricTestRunner(testClass, INJECTOR) { + + /** Holds the Maps shadows registered by [MapsRobolectricTestRunner]. */ + companion object { + /** Every shadow in `:robolectric-testing`, in the order they are registered. */ + @JvmField + val MAPS_SHADOWS: List> = listOf( + ShadowMapView::class.java, + ShadowGoogleMap::class.java, + ShadowCameraUpdate::class.java, + ShadowCameraUpdateFactory::class.java, + ShadowMarker::class.java, + ShadowPolygon::class.java, + ShadowPolyline::class.java, + ShadowProjection::class.java, + ShadowCircle::class.java, + ShadowUiSettings::class.java, + ShadowBitmapDescriptor::class.java, + ShadowBitmapDescriptorFactory::class.java, + ShadowMapsInitializer::class.java, + ShadowSupportMapFragment::class.java, + ShadowGroundOverlay::class.java, + ShadowTileOverlay::class.java, + ) + + // One injector for every test class using this runner, so Robolectric keeps reusing its + // sandboxes. Binding the GlobalConfigProvider here, instead of overriding the deprecated + // buildGlobalConfig(), matters: buildGlobalConfig() is stored in a static field that only the + // first runner created in the JVM gets to set. + private val INJECTOR: Injector = defaultInjector() + .bind(GlobalConfigProvider::class.java, MapsGlobalConfigProvider) + .build() + } +} + +/** Supplies the default [Config] for [MapsRobolectricTestRunner]: Robolectric's defaults plus the Maps shadows. */ +internal object MapsGlobalConfigProvider : GlobalConfigProvider { + override fun get(): Config = Config.Builder().setShadows(*MapsRobolectricTestRunner.MAPS_SHADOWS.toTypedArray()).build() +} +// [END maps_robolectric_test_runner] diff --git a/robolectric-testing/src/main/java/com/google/maps/android/robolectric/annotation/EnableMapsShadows.kt b/robolectric-testing/src/main/java/com/google/maps/android/robolectric/annotation/EnableMapsShadows.kt index 3d0f4d9..ef8f4dd 100644 --- a/robolectric-testing/src/main/java/com/google/maps/android/robolectric/annotation/EnableMapsShadows.kt +++ b/robolectric-testing/src/main/java/com/google/maps/android/robolectric/annotation/EnableMapsShadows.kt @@ -16,6 +16,7 @@ package com.google.maps.android.robolectric.annotation +import com.google.maps.android.robolectric.MapsRobolectricTestRunner import com.google.maps.android.robolectric.shadows.ShadowBitmapDescriptor import com.google.maps.android.robolectric.shadows.ShadowBitmapDescriptorFactory import com.google.maps.android.robolectric.shadows.ShadowCameraUpdate @@ -35,17 +36,22 @@ import com.google.maps.android.robolectric.shadows.ShadowUiSettings import org.robolectric.annotation.Config /** - * Convenience annotation to configure Robolectric tests with all Google Maps SDK shadows. + * Intended to register all Google Maps SDK shadows, but Robolectric does not read `@Config` placed + * on another annotation, so this annotation registers no shadows. Tests that use it only got the + * shadows from the `robolectric.properties` this library ships, which will be removed before 1.0.0. * - * Example: + * Use [MapsRobolectricTestRunner] instead: * ```kotlin - * @RunWith(RobolectricTestRunner::class) - * @EnableMapsShadows + * @RunWith(MapsRobolectricTestRunner::class) * class MyMapTest { - * // All Maps shadows are automatically registered + * // All Maps shadows are registered * } * ``` */ +@Deprecated( + "Registers no shadows: Robolectric does not read @Config through annotations. " + + "Use @RunWith(MapsRobolectricTestRunner::class) instead.", +) @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) @Config( diff --git a/robolectric-testing/src/main/java/com/google/maps/android/robolectric/ktx/MapTestHarness.kt b/robolectric-testing/src/main/java/com/google/maps/android/robolectric/ktx/MapTestHarness.kt index d0d321b..6f1357e 100644 --- a/robolectric-testing/src/main/java/com/google/maps/android/robolectric/ktx/MapTestHarness.kt +++ b/robolectric-testing/src/main/java/com/google/maps/android/robolectric/ktx/MapTestHarness.kt @@ -89,7 +89,7 @@ fun MapView.getMapBlocking(): GoogleMap { getMapAsync { mapRef.set(it) } ShadowLooper.idleMainLooper() return checkNotNull(mapRef.get()) { - "Failed to obtain GoogleMap from MapView. Did you annotate the test with @EnableMapsShadows?" + "Failed to obtain GoogleMap from MapView. Does the test run with @RunWith(MapsRobolectricTestRunner::class)?" }.also { map -> if (width > 0 && height > 0) { map.shadow.setViewportSize(width, height) @@ -106,7 +106,7 @@ fun SupportMapFragment.getMapBlocking(): GoogleMap { getMapAsync { mapRef.set(it) } ShadowLooper.idleMainLooper() return checkNotNull(mapRef.get()) { - "Failed to obtain GoogleMap from SupportMapFragment. Did you annotate the test with @EnableMapsShadows?" + "Failed to obtain GoogleMap from SupportMapFragment. Does the test run with @RunWith(MapsRobolectricTestRunner::class)?" } } diff --git a/robolectric-testing/src/test/java/com/google/maps/android/robolectric/MapsRobolectricTestRunnerTest.kt b/robolectric-testing/src/test/java/com/google/maps/android/robolectric/MapsRobolectricTestRunnerTest.kt new file mode 100644 index 0000000..712045b --- /dev/null +++ b/robolectric-testing/src/test/java/com/google/maps/android/robolectric/MapsRobolectricTestRunnerTest.kt @@ -0,0 +1,104 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.maps.android.robolectric + +import android.app.Activity +import android.os.Build +import android.os.Bundle +import com.google.android.gms.maps.MapView +import com.google.common.truth.Truth.assertThat +import com.google.maps.android.robolectric.shadows.ShadowMapView +import org.junit.Test +import org.junit.runner.JUnitCore +import org.junit.runner.RunWith +import org.robolectric.Robolectric +import org.robolectric.annotation.Config +import org.robolectric.annotation.Implements +import org.robolectric.shadow.api.Shadow +import java.io.File +import java.net.JarURLConnection +import java.util.jar.JarFile + +class MapsRobolectricTestRunnerTest { + + @Test + fun globalConfigRegistersEveryMapsShadow() { + val config = MapsGlobalConfigProvider.get() + + assertThat(config.shadows.map { it.java }) + .containsExactlyElementsIn(MapsRobolectricTestRunner.MAPS_SHADOWS) + } + + @Test + fun mapsShadowsListsEveryShadowInTheLibrary() { + val shadowClasses = classesInPackage(SHADOWS_PACKAGE) + .filter { it.isAnnotationPresent(Implements::class.java) } + + assertThat(shadowClasses).isNotEmpty() + assertThat(MapsRobolectricTestRunner.MAPS_SHADOWS).containsExactlyElementsIn(shadowClasses) + } + + @Test + fun classLevelConfigStillAppliesOnTopOfMapsShadows() { + val result = JUnitCore.runClasses(RunnerFixture::class.java) + + assertThat(result.failures.map { it.trace }).isEmpty() + assertThat(result.runCount).isEqualTo(1) + } + + private fun classesInPackage(packageName: String): List> { + val path = packageName.replace('.', '/') + val loader = checkNotNull(javaClass.classLoader) + // The package is split between the library's classes and this module's tests, so scan every location. + val classFiles = loader.getResources(path).toList().flatMap { url -> + when (url.protocol) { + "jar" -> JarFile((url.openConnection() as JarURLConnection).jarFileURL.path).use { jar -> + jar.entries().toList().map { it.name } + .filter { it.startsWith("$path/") && it.endsWith(".class") && it.indexOf('/', path.length + 1) == -1 } + .map { it.substringAfterLast('/') } + } + + else -> File(url.toURI()).listFiles().orEmpty().map { it.name }.filter { it.endsWith(".class") } + } + } + return classFiles + .filterNot { it.contains('$') || it.endsWith("Test.class") } + .distinct() + .map { Class.forName("$packageName.${it.removeSuffix(".class")}", false, loader) } + } + + private companion object { + const val SHADOWS_PACKAGE = "com.google.maps.android.robolectric.shadows" + } +} + +/** Run through [JUnitCore] by [MapsRobolectricTestRunnerTest]; not meant to run on its own. */ +@RunWith(MapsRobolectricTestRunner::class) +@Config(sdk = [33]) +class RunnerFixture { + + @Test + fun mapsShadowsAndClassConfigBothApply() { + assertThat(Build.VERSION.SDK_INT).isEqualTo(33) + + val activity = Robolectric.buildActivity(Activity::class.java).create().get() + val mapView = MapView(activity) + mapView.onCreate(Bundle()) + + assertThat(Shadow.extract(mapView)).isInstanceOf(ShadowMapView::class.java) + } +} diff --git a/robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/LazyAssertionsComparisonTest.kt b/robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/LazyAssertionsComparisonTest.kt index d2019dc..e334045 100644 --- a/robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/LazyAssertionsComparisonTest.kt +++ b/robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/LazyAssertionsComparisonTest.kt @@ -31,7 +31,7 @@ import com.google.android.gms.maps.model.MarkerOptions import com.google.android.gms.maps.model.PolygonOptions import com.google.android.gms.maps.model.PolylineOptions import com.google.android.gms.maps.model.TileOverlayOptions -import com.google.maps.android.robolectric.annotation.EnableMapsShadows +import com.google.maps.android.robolectric.MapsRobolectricTestRunner import com.google.maps.android.robolectric.ktx.activeInfoWindowMarker import com.google.maps.android.robolectric.ktx.cameraBearing import com.google.maps.android.robolectric.ktx.cameraTarget @@ -91,7 +91,6 @@ import com.google.maps.android.robolectric.truth.miles import org.junit.Assert.assertThrows import org.junit.Test import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config import org.robolectric.annotation.GraphicsMode import com.google.common.truth.Truth.assertThat as truthAssertThat @@ -101,10 +100,9 @@ import com.google.common.truth.Truth.assertThat as truthAssertThat * expressions for the same Google Maps testing scenarios. */ // [START maps_robolectric_parallel_truth_vs_power_assert] -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows class LazyAssertionsComparisonTest { private val boulder = LatLng(40.0150, -105.2705) diff --git a/robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/MapsUtilsClusteringIntegrationTest.kt b/robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/MapsUtilsClusteringIntegrationTest.kt index 787ddc1..2bf97ed 100644 --- a/robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/MapsUtilsClusteringIntegrationTest.kt +++ b/robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/MapsUtilsClusteringIntegrationTest.kt @@ -25,12 +25,11 @@ import com.google.android.gms.maps.MapView import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.MarkerOptions import com.google.common.truth.Truth.assertThat -import com.google.maps.android.robolectric.annotation.EnableMapsShadows +import com.google.maps.android.robolectric.MapsRobolectricTestRunner import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.Robolectric -import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config import org.robolectric.annotation.GraphicsMode import org.robolectric.shadow.api.Shadow @@ -43,10 +42,9 @@ import kotlin.math.hypot * projection and camera lifecycle semantics required by android-maps-utils clustering algorithms. */ // [START maps_robolectric_clustering_test] -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows class MapsUtilsClusteringIntegrationTest { private lateinit var mapView: MapView diff --git a/robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt b/robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt index 739a2cc..24fe3d3 100644 --- a/robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt +++ b/robolectric-testing/src/test/java/com/google/maps/android/robolectric/shadows/ShadowMapViewKotlinTest.kt @@ -38,7 +38,7 @@ import com.google.android.gms.maps.model.Marker import com.google.android.gms.maps.model.MarkerOptions import com.google.android.gms.maps.model.PolygonOptions import com.google.common.truth.Truth.assertThat -import com.google.maps.android.robolectric.annotation.EnableMapsShadows +import com.google.maps.android.robolectric.MapsRobolectricTestRunner import com.google.maps.android.robolectric.ktx.cameraTargetBounds import com.google.maps.android.robolectric.ktx.contentDescription import com.google.maps.android.robolectric.ktx.getMapBlocking @@ -55,17 +55,15 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.Robolectric -import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config import org.robolectric.annotation.GraphicsMode import org.robolectric.shadow.api.Shadow import org.robolectric.shadows.ShadowLooper import java.util.concurrent.atomic.AtomicReference -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @GraphicsMode(GraphicsMode.Mode.NATIVE) @Config(sdk = [34]) -@EnableMapsShadows class ShadowMapViewKotlinTest { private lateinit var activity: Activity @@ -412,10 +410,9 @@ class ShadowMapViewKotlinTest { } } -@RunWith(RobolectricTestRunner::class) +@RunWith(MapsRobolectricTestRunner::class) @Config(sdk = [34]) -@EnableMapsShadows -class EnableMapsShadowsAnnotationTest { +class MapsRobolectricTestRunnerShadowsTest { @Test fun testMapsShadowsAutoConfigured() {