diff options
Diffstat (limited to 'platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java')
9 files changed, 1790 insertions, 0 deletions
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/locationlayer/LocationLayerPluginTest.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/locationlayer/LocationLayerPluginTest.kt new file mode 100644 index 0000000000..12a64850b0 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/locationlayer/LocationLayerPluginTest.kt @@ -0,0 +1,1089 @@ +package com.mapbox.mapboxsdk.plugins.locationlayer + +import android.Manifest +import android.R +import android.arch.lifecycle.Lifecycle +import android.content.Context +import android.graphics.Color +import android.graphics.RectF +import android.location.Location +import android.support.test.espresso.Espresso.onView +import android.support.test.espresso.IdlingRegistry +import android.support.test.espresso.UiController +import android.support.test.espresso.assertion.ViewAssertions.matches +import android.support.test.espresso.matcher.ViewMatchers.* +import android.support.test.filters.LargeTest +import android.support.test.rule.ActivityTestRule +import android.support.test.rule.GrantPermissionRule +import android.support.test.runner.AndroidJUnit4 +import android.support.v4.content.ContextCompat +import com.mapbox.geojson.Point +import com.mapbox.mapboxsdk.camera.CameraPosition +import com.mapbox.mapboxsdk.camera.CameraUpdateFactory +import com.mapbox.mapboxsdk.constants.Style +import com.mapbox.mapboxsdk.geometry.LatLng +import com.mapbox.mapboxsdk.maps.MapView +import com.mapbox.mapboxsdk.maps.MapboxMap +import com.mapbox.mapboxsdk.maps.MapboxMapOptions +import com.mapbox.mapboxsdk.maps.SupportMapFragment +import com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.* +import com.mapbox.mapboxsdk.plugins.locationlayer.modes.CameraMode +import com.mapbox.mapboxsdk.plugins.locationlayer.modes.RenderMode +import com.mapbox.mapboxsdk.plugins.utils.* +import com.mapbox.mapboxsdk.plugins.utils.MapboxTestingUtils.Companion.MAPBOX_HEAVY_STYLE +import com.mapbox.mapboxsdk.plugins.utils.MapboxTestingUtils.Companion.pushSourceUpdates +import com.mapbox.mapboxsdk.plugins.utils.PluginGenerationUtil.Companion.MAP_CONNECTION_DELAY +import com.mapbox.mapboxsdk.plugins.utils.PluginGenerationUtil.Companion.MAP_RENDER_DELAY +import com.mapbox.mapboxsdk.style.layers.PropertyFactory +import com.mapbox.mapboxsdk.style.sources.GeoJsonSource +import com.mapbox.mapboxsdk.testapp.activity.SingleFragmentActivity +import org.hamcrest.CoreMatchers.* +import org.junit.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.rules.TestName +import org.junit.runner.RunWith +import timber.log.Timber + +/** + * Test class that uses a map fragment to keep onMapReady actions isolated to within the test + */ +@RunWith(AndroidJUnit4::class) +@LargeTest +class LocationLayerPluginTest { + + @Rule + @JvmField + val activityRule = ActivityTestRule(SingleFragmentActivity::class.java, true, true) + + @Rule + @JvmField + val nameRule = TestName() + + @Rule + @JvmField + val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(Manifest.permission.ACCESS_FINE_LOCATION) + + private lateinit var idlingResource: OnMapFragmentReadyIdlingResource + private lateinit var styleChangeIdlingResource: StyleChangeIdlingResource + private lateinit var fragment: SupportMapFragment + private lateinit var mapboxMap: MapboxMap + private val location: Location by lazy { + val initLocation = Location("test") + initLocation.latitude = 15.0 + initLocation.longitude = 17.0 + initLocation + } + + @Before + fun beforeTest() { + + // Create a default support map fragment and pass it into the empty activity + val options = MapboxMapOptions() + .camera(CameraPosition.Builder().zoom(2.0).build()) // to match plugins min zoom + fragment = SupportMapFragment.newInstance(options) + activityRule.activity.setFragment(fragment) + + Timber.e("@Before: ${nameRule.methodName} - register idle resource") + // If idlingResource is null, throw Kotlin exception + idlingResource = OnMapFragmentReadyIdlingResource(fragment) + styleChangeIdlingResource = StyleChangeIdlingResource() + IdlingRegistry.getInstance().register(idlingResource) + IdlingRegistry.getInstance().register(styleChangeIdlingResource) + onView(withId(R.id.content)).check(matches(isDisplayed())) + mapboxMap = idlingResource.mapboxMap + } + + @Test + fun locationLayerPlugin_initializesLocationEngineCorrectlyWhenOnesNotProvided() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + + val locationEngine = plugin.locationEngine + assertThat(locationEngine, notNullValue()) + + uiController.loopMainThreadForAtLeast(MAP_CONNECTION_DELAY) + assertThat(locationEngine?.isConnected, `is`(true)) + } + } + + executePluginTest(pluginAction, PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity, true)) + } + + @Test + fun locationLayerPlugin_initializesLocationEngineCorrectlyWhenOnesNotProvidedButHasOptions() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + + val locationEngine = plugin.locationEngine + val pluginOptions = plugin.locationLayerOptions + + assertThat(locationEngine, notNullValue()) + assertThat(pluginOptions, notNullValue()) + + uiController.loopMainThreadForAtLeast(MAP_CONNECTION_DELAY) + assertThat(locationEngine?.isConnected, `is`(true)) + assertThat(pluginOptions?.accuracyAlpha(), `is`(.5f)) + assertThat(pluginOptions?.accuracyColor(), `is`(Color.BLUE)) + } + } + + val options = LocationLayerOptions.builder(fragment.activity) + .staleStateTimeout(200) + .enableStaleState(false) + .accuracyAlpha(.5f) + .accuracyColor(Color.BLUE) + .build() + + executePluginTest(pluginAction, PluginGenerationUtil.getLocationLayerPluginProvider( + activityRule.activity, true, null, options)) + } + + @Test + fun settingMapStyleImmediatelyBeforeLoadingPlugin_doesStillLoadLayersProperly() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + + uiController.loopMainThreadForAtLeast(MAP_CONNECTION_DELAY) + + assertThat(plugin.renderMode, `is`(equalTo(RenderMode.NORMAL))) + assertThat(mapboxMap.isLayerVisible(FOREGROUND_LAYER), `is`(true)) + assertThat(mapboxMap.isLayerVisible(BACKGROUND_LAYER), `is`(true)) + assertThat(mapboxMap.isLayerVisible(SHADOW_LAYER), `is`(true)) + assertThat(mapboxMap.isLayerVisible(ACCURACY_LAYER), `is`(true)) + assertThat(mapboxMap.isLayerVisible(BEARING_LAYER), `is`(false)) + } + } + + executePluginTest(pluginAction, object : GenericPluginAction.PluginProvider<LocationLayerPlugin> { + override fun providePlugin(mapView: MapView, mapboxMap: MapboxMap, context: Context): LocationLayerPlugin { + // changing the style just before instantiating the plugin + mapboxMap.setStyleUrl(Style.LIGHT) + val plugin = + PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity, false, null, null, false) + .providePlugin(mapView, mapboxMap, context) + plugin.forceLocationUpdate(location) + return plugin + } + + override fun isPluginDataReady(plugin: LocationLayerPlugin, mapboxMap: MapboxMap): Boolean { + val source = mapboxMap.getSource(LOCATION_SOURCE) + return source != null && (source as GeoJsonSource).querySourceFeatures(null).isNotEmpty() + } + }) + } + + @Test + fun locationLayer_doesntShowUntilFirstLocationFix() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + + // Source should be present but empty + val mapView = fragment.view as MapView + assertThat(mapboxMap.queryRenderedFeatures( + RectF(0f, 0f, mapView.width.toFloat(), mapView.height.toFloat()), FOREGROUND_LAYER) + .isEmpty(), `is`(true)) + + // Force the first location update + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(MAP_CONNECTION_DELAY) + + // Check if the puck is visible + assertThat(mapboxMap.queryRenderedFeatures(location, FOREGROUND_LAYER).isEmpty(), `is`(false)) + } + } + executePluginTest(pluginAction) + } + + // + // Location Layer Options + // + + @Test + fun locationLayerOptions_disablingStaleStateDoesWorkCorrectly() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(200) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + + mapboxMap.querySourceFeatures(LOCATION_SOURCE).also { + it.forEach { + assertThat(it.getBooleanProperty(PROPERTY_LOCATION_STALE), `is`(false)) + } + } + } + } + + val options = LocationLayerOptions.builder(fragment.activity) + .staleStateTimeout(200) + .enableStaleState(false) + .build() + executePluginTest(pluginAction, + PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity, false, null, options)) + } + + @Test + fun locationLayerOptions_loadsForegroundBitmapFromNameOption() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + val foregroundDrawable = ContextCompat.getDrawable(context, R.drawable.ic_media_play) + mapboxMap.addImageFromDrawable("custom-foreground-bitmap", foregroundDrawable!!) + mapboxMap.addImageFromDrawable("custom-background-bitmap", foregroundDrawable) + mapboxMap.addImageFromDrawable("custom-foreground-stale-bitmap", foregroundDrawable) + mapboxMap.addImageFromDrawable("custom-background-stale-bitmap", foregroundDrawable) + mapboxMap.addImageFromDrawable("custom-bearing-bitmap", foregroundDrawable) + + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(MAP_CONNECTION_DELAY) + assertThat(mapboxMap.queryRenderedFeatures(location, FOREGROUND_LAYER).isEmpty(), `is`(false)) + + val feature = mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0] + assertThat(feature.getStringProperty(PROPERTY_FOREGROUND_ICON), `is`(equalTo("custom-foreground-bitmap"))) + assertThat(feature.getStringProperty(PROPERTY_BACKGROUND_ICON), `is`(equalTo("custom-background-bitmap"))) + assertThat(feature.getStringProperty(PROPERTY_FOREGROUND_STALE_ICON), `is`(equalTo("custom-foreground-stale-bitmap"))) + assertThat(feature.getStringProperty(PROPERTY_BACKGROUND_STALE_ICON), `is`(equalTo("custom-background-stale-bitmap"))) + assertThat(feature.getStringProperty(PROPERTY_BEARING_ICON), `is`(equalTo("custom-bearing-bitmap"))) + } + } + + val options = LocationLayerOptions.builder(fragment.activity) + .foregroundName("custom-foreground-bitmap") + .backgroundName("custom-background-bitmap") + .foregroundStaleName("custom-foreground-stale-bitmap") + .backgroundStaleName("custom-background-stale-bitmap") + .bearingName("custom-bearing-bitmap") + .build() + executePluginTest(pluginAction, + PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity, false, null, options)) + } + + @Test + fun locationLayerOptions_loadsGpsNameWithGpsRenderMode() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.renderMode = RenderMode.GPS + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + val foregroundDrawable = ContextCompat.getDrawable(context, R.drawable.ic_media_play) + mapboxMap.addImageFromDrawable("custom-foreground-bitmap", foregroundDrawable!!) + mapboxMap.addImageFromDrawable("custom-gps-bitmap", foregroundDrawable) + + val foregroundId = mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0].getStringProperty(PROPERTY_FOREGROUND_ICON) + assertThat(foregroundId, `is`(equalTo("custom-gps-bitmap"))) + } + } + + val options = LocationLayerOptions.builder(fragment.activity) + .foregroundName("custom-foreground-bitmap") + .gpsName("custom-gps-bitmap") + .build() + executePluginTest(pluginAction, + PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity, false, null, options)) + } + + @Test + fun locationLayerOptions_customIconNameRevertsToDefault() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.renderMode = RenderMode.GPS + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + + val foregroundId = mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0].getStringProperty(PROPERTY_FOREGROUND_ICON) + assertThat(foregroundId, `is`(equalTo("custom-gps-bitmap"))) + + plugin.applyStyle(LocationLayerOptions.builder(fragment.activity).build()) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + + val revertedForegroundId = mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0].getStringProperty(PROPERTY_FOREGROUND_ICON) + assertThat(revertedForegroundId, `is`(equalTo(FOREGROUND_ICON))) + } + } + + val options = LocationLayerOptions.builder(fragment.activity) + .foregroundName("custom-foreground-bitmap") + .gpsName("custom-gps-bitmap") + .build() + executePluginTest(pluginAction, + PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity, false, null, options)) + } + + @Test + fun locationLayerOptions_customGpsIconNameChangeBackWithMode() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.renderMode = RenderMode.GPS + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + + val foregroundId = mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0].getStringProperty(PROPERTY_FOREGROUND_ICON) + assertThat(foregroundId, `is`(equalTo("custom-gps-bitmap"))) + + plugin.renderMode = RenderMode.NORMAL + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + + val revertedForegroundId = mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0].getStringProperty(PROPERTY_FOREGROUND_ICON) + assertThat(revertedForegroundId, `is`(equalTo(FOREGROUND_ICON))) + } + } + + val options = LocationLayerOptions.builder(fragment.activity) + .gpsName("custom-gps-bitmap") + .build() + executePluginTest(pluginAction, + PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity, false, null, options)) + } + + @Test + fun stillStaleAfterResuming() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + val testLifecycleOwner = TestLifecycleOwner() + testLifecycleOwner.markState(Lifecycle.State.RESUMED) + testLifecycleOwner.lifecycle.addObserver(plugin) + + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(300) // engaging stale state + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + assertThat(mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0].getBooleanProperty(PROPERTY_LOCATION_STALE), `is`(true)) + + testLifecycleOwner.markState(Lifecycle.State.CREATED) + testLifecycleOwner.markState(Lifecycle.State.RESUMED) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + + assertThat(mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0].getBooleanProperty(PROPERTY_LOCATION_STALE), `is`(true)) + assertThat(mapboxMap.isLayerVisible(ACCURACY_LAYER), `is`(false)) + } + } + val options = LocationLayerOptions.builder(fragment.activity) + .staleStateTimeout(200) + .build() + executePluginTest(pluginAction, + PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity, false, null, options, false)) + } + + @Test + fun stillNotStaleAfterResuming() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + val testLifecycleOwner = TestLifecycleOwner() + testLifecycleOwner.markState(Lifecycle.State.RESUMED) + testLifecycleOwner.lifecycle.addObserver(plugin) + + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + assertThat(mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0].getBooleanProperty(PROPERTY_LOCATION_STALE), `is`(false)) + + testLifecycleOwner.markState(Lifecycle.State.CREATED) + testLifecycleOwner.markState(Lifecycle.State.RESUMED) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + + assertThat(mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0].getBooleanProperty(PROPERTY_LOCATION_STALE), `is`(false)) + assertThat(mapboxMap.isLayerVisible(ACCURACY_LAYER), `is`(true)) + } + } + executePluginTest(pluginAction, + PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity)) + } + + @Test + fun locationLayerOptions_accuracyRingWithColor() { + val color = Color.parseColor("#4A90E2") + val rgbaColor = PropertyFactory.colorToRgbaString(color) + + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + + // Check that the source property changes correctly + mapboxMap.querySourceFeatures(LOCATION_SOURCE).also { + it.forEach { + assertThat(it.getStringProperty(PROPERTY_ACCURACY_COLOR), `is`(equalTo(rgbaColor))) + } + } + } + } + + val options = LocationLayerOptions.builder(fragment.activity) + .accuracyColor(color) + .build() + executePluginTest(pluginAction, + PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity, false, null, options)) + } + + @Test + fun forceLocationUpdate_doesMoveLocationLayerIconToCorrectPosition() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + + plugin.forceLocationUpdate(location) + + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + + val point: Point = mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0].geometry() as Point + + assertThat(plugin.locationEngine, nullValue()) + assertEquals(point.latitude(), location.latitude, 0.1) + assertEquals(point.longitude(), location.longitude, 0.1) + } + } + executePluginTest(pluginAction) + } + + @Test + fun disablingPluginHidesPuck() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + val point: Point = mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0].geometry() as Point + assertEquals(point.latitude(), location.latitude, 0.1) + assertEquals(point.longitude(), location.longitude, 0.1) + + plugin.isLocationLayerEnabled = false + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + assertThat(mapboxMap.querySourceFeatures(LOCATION_SOURCE).isEmpty(), `is`(true)) + } + } + executePluginTest(pluginAction) + } + + @Test + fun disablingPluginAndChangingStyleAllowsToEnableAgain() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + + plugin.isLocationLayerEnabled = false + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + mapboxMap.setStyle(Style.LIGHT) + uiController.loopMainThreadForAtLeast(MAP_CONNECTION_DELAY) + plugin.isLocationLayerEnabled = true + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + assertThat(mapboxMap.isLayerVisible(FOREGROUND_LAYER), `is`(true)) + } + } + executePluginTest(pluginAction) + } + + @Test + fun lifecycle_keepsEnabledWhenStoppedAndStarted() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + val testLifecycleOwner = TestLifecycleOwner() + testLifecycleOwner.markState(Lifecycle.State.RESUMED) + testLifecycleOwner.lifecycle.addObserver(plugin) + + assertThat(plugin.isLocationLayerEnabled, `is`(true)) + testLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE) + testLifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_STOP) + testLifecycleOwner.markState(Lifecycle.State.RESUMED) + assertThat(plugin.isLocationLayerEnabled, `is`(true)) + } + } + executePluginTest(pluginAction, + PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity, false, null, null, false)) + } + + @Test + fun lifecycle_keepsDisabledWhenStoppedAndStarted() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.isLocationLayerEnabled = false + + val testLifecycleOwner = TestLifecycleOwner() + testLifecycleOwner.markState(Lifecycle.State.RESUMED) + testLifecycleOwner.lifecycle.addObserver(plugin) + + assertThat(plugin.isLocationLayerEnabled, `is`(false)) + testLifecycleOwner.markState(Lifecycle.State.CREATED) + testLifecycleOwner.markState(Lifecycle.State.RESUMED) + assertThat(plugin.isLocationLayerEnabled, `is`(false)) + } + } + executePluginTest(pluginAction, + PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity, false, null, null, false)) + } + + @Test + fun lifecycle_ableToChangeStyleAfterResuming() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + + val testLifecycleOwner = TestLifecycleOwner() + testLifecycleOwner.markState(Lifecycle.State.RESUMED) + testLifecycleOwner.lifecycle.addObserver(plugin) + + testLifecycleOwner.markState(Lifecycle.State.CREATED) + testLifecycleOwner.markState(Lifecycle.State.RESUMED) + + mapboxMap.setStyle(Style.DARK) + uiController.loopMainThreadForAtLeast(MAP_CONNECTION_DELAY) + } + } + executePluginTest(pluginAction, + PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity, false, null, null, false)) + } + + @Test + fun lifecycle_interruptedDuringStyleChange() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + + val testLifecycleOwner = TestLifecycleOwner() + testLifecycleOwner.markState(Lifecycle.State.RESUMED) + testLifecycleOwner.lifecycle.addObserver(plugin) + mapboxMap.setStyle(Style.DARK) + + testLifecycleOwner.markState(Lifecycle.State.CREATED) + testLifecycleOwner.markState(Lifecycle.State.RESUMED) + uiController.loopMainThreadForAtLeast(MAP_CONNECTION_DELAY) + } + } + executePluginTest(pluginAction, + PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity, false, null, null, false)) + } + + @Test + fun lifecycle_forceLocationUpdateAfterStopped() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + + val testLifecycleOwner = TestLifecycleOwner() + testLifecycleOwner.markState(Lifecycle.State.RESUMED) + testLifecycleOwner.lifecycle.addObserver(plugin) + + testLifecycleOwner.markState(Lifecycle.State.CREATED) + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + + assertThat(mapboxMap.querySourceFeatures(LOCATION_SOURCE).isEmpty(), `is`(true)) + } + } + executePluginTest(pluginAction, + PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity, false, null, null, false)) + } + + @Test + fun lifecycle_acceptAndReuseLocationUpdatesBeforeLayerStarted() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + + val testLifecycleOwner = TestLifecycleOwner() + testLifecycleOwner.markState(Lifecycle.State.RESUMED) + testLifecycleOwner.lifecycle.addObserver(plugin) + + testLifecycleOwner.markState(Lifecycle.State.CREATED) + plugin.forceLocationUpdate(location) + testLifecycleOwner.markState(Lifecycle.State.RESUMED) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + + val point: Point = mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0].geometry() as Point + assertEquals(point.latitude(), location.latitude, 0.1) + assertEquals(point.longitude(), location.longitude, 0.1) + } + } + executePluginTest(pluginAction, + PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity, false, null, null, false)) + } + + @Test + fun lifecycle_lifecycleChangeRightAfterStyleReload() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + + val testLifecycleOwner = TestLifecycleOwner() + testLifecycleOwner.markState(Lifecycle.State.RESUMED) + testLifecycleOwner.lifecycle.addObserver(plugin) + + plugin.forceLocationUpdate(location) + mapboxMap.setStyle(Style.LIGHT) + testLifecycleOwner.markState(Lifecycle.State.CREATED) + uiController.loopMainThreadForAtLeast(MAP_CONNECTION_DELAY) + testLifecycleOwner.markState(Lifecycle.State.RESUMED) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + + val point: Point = mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0].geometry() as Point + assertEquals(point.latitude(), location.latitude, 0.1) + assertEquals(point.longitude(), location.longitude, 0.1) + + assertThat(mapboxMap.isLayerVisible(FOREGROUND_LAYER), `is`(true)) + assertThat(mapboxMap.isLayerVisible(BACKGROUND_LAYER), `is`(true)) + assertThat(mapboxMap.isLayerVisible(SHADOW_LAYER), `is`(true)) + assertThat(mapboxMap.isLayerVisible(ACCURACY_LAYER), `is`(true)) + assertThat(mapboxMap.isLayerVisible(BEARING_LAYER), `is`(false)) + } + } + executePluginTest(pluginAction, + PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity, false, null, null, false)) + } + + @Test + fun mapChange_settingPluginStyle() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + styleChangeIdlingResource.waitForStyle(fragment.view as MapView, mapboxMap, MAPBOX_HEAVY_STYLE) + val options = LocationLayerOptions.builder(fragment.activity) + .accuracyColor(Color.RED) + .build() + + pushSourceUpdates(styleChangeIdlingResource) { + plugin.applyStyle(options) + } + + uiController.loopMainThreadForAtLeast(MAP_CONNECTION_DELAY) + } + } + executePluginTest(pluginAction, + PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity)) + + // Waiting for style to finish loading while pushing updates + onView(withId(R.id.content)).check(matches(isDisplayed())) + } + + @Test + fun mapChange_forcingLocation() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + styleChangeIdlingResource.waitForStyle(fragment.view as MapView, mapboxMap, MAPBOX_HEAVY_STYLE) + + pushSourceUpdates(styleChangeIdlingResource) { + plugin.forceLocationUpdate(location) + } + + uiController.loopMainThreadForAtLeast(MAP_CONNECTION_DELAY) + } + } + executePluginTest(pluginAction, + PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity)) + + // Waiting for style to finish loading while pushing updates + onView(withId(R.id.content)).check(matches(isDisplayed())) + } + + @Test + fun mapChange_settingMapStyleBeforePluginCreation() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + val options = LocationLayerOptions.builder(fragment.activity) + .accuracyColor(Color.RED) + .build() + + pushSourceUpdates(styleChangeIdlingResource) { + plugin.forceLocationUpdate(location) + plugin.applyStyle(options) + } + } + } + + executePluginTest(pluginAction, object : GenericPluginAction.PluginProvider<LocationLayerPlugin> { + override fun providePlugin(mapView: MapView, mapboxMap: MapboxMap, context: Context): LocationLayerPlugin { + // changing the style just before instantiating the plugin + styleChangeIdlingResource.waitForStyle(mapView, mapboxMap, MAPBOX_HEAVY_STYLE) + return PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity, false, null, null, false) + .providePlugin(mapView, mapboxMap, context) + } + + override fun isPluginDataReady(plugin: LocationLayerPlugin, mapboxMap: MapboxMap): Boolean { + return true + } + }) + + // Waiting for style to finish loading while pushing updates + onView(withId(R.id.content)).check(matches(isDisplayed())) + } + + @Test + fun animators_layerBearingCorrect() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.renderMode = RenderMode.GPS + location.bearing = 77f + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(1000) + assertEquals(77.0, mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0].getNumberProperty(PROPERTY_GPS_BEARING) as Double, 0.1) + + location.bearing = 92f + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(2000) // Waiting for the animation to finish + assertEquals(92.0, mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0].getNumberProperty(PROPERTY_GPS_BEARING) as Double, 0.1) + } + } + + executePluginTest(pluginAction, PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity)) + } + + @Test + fun animators_cameraLatLngBearingCorrect() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.cameraMode = CameraMode.TRACKING_GPS + location.bearing = 77f + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(1000) + assertEquals(77.0, mapboxMap.cameraPosition.bearing, 0.1) + assertEquals(location.latitude, mapboxMap.cameraPosition.target.latitude, 0.1) + assertEquals(location.longitude, mapboxMap.cameraPosition.target.longitude, 0.1) + + location.bearing = 92f + location.latitude = 30.0 + location.longitude = 35.0 + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(2000) // Waiting for the animation to finish + assertEquals(92.0, mapboxMap.cameraPosition.bearing, 0.1) + assertEquals(location.latitude, mapboxMap.cameraPosition.target.latitude, 0.1) + assertEquals(location.longitude, mapboxMap.cameraPosition.target.longitude, 0.1) + } + } + + executePluginTest(pluginAction, PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity)) + } + + @Test + fun animators_cameraBearingCorrect() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.cameraMode = CameraMode.NONE_GPS + val latitude = mapboxMap.cameraPosition.target.latitude + val longitude = mapboxMap.cameraPosition.target.longitude + + location.bearing = 77f + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(1000) + assertEquals(77.0, mapboxMap.cameraPosition.bearing, 0.1) + assertEquals(latitude, mapboxMap.cameraPosition.target.latitude, 0.1) + assertEquals(longitude, mapboxMap.cameraPosition.target.longitude, 0.1) + + location.bearing = 92f + location.latitude = 30.0 + location.longitude = 35.0 + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(2000) // Waiting for the animation to finish + assertEquals(92.0, mapboxMap.cameraPosition.bearing, 0.1) + assertEquals(latitude, mapboxMap.cameraPosition.target.latitude, 0.1) + assertEquals(longitude, mapboxMap.cameraPosition.target.longitude, 0.1) + } + } + + executePluginTest(pluginAction, PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity)) + } + + @Test + fun animators_cameraNoneCorrect() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.cameraMode = CameraMode.NONE + val latitude = mapboxMap.cameraPosition.target.latitude + val longitude = mapboxMap.cameraPosition.target.longitude + val bearing = mapboxMap.cameraPosition.bearing + + location.bearing = 77f + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(1000) + assertEquals(bearing, mapboxMap.cameraPosition.bearing, 0.1) + assertEquals(latitude, mapboxMap.cameraPosition.target.latitude, 0.1) + assertEquals(longitude, mapboxMap.cameraPosition.target.longitude, 0.1) + + location.bearing = 92f + location.latitude = 30.0 + location.longitude = 35.0 + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(2000) // Waiting for the animation to finish + assertEquals(bearing, mapboxMap.cameraPosition.bearing, 0.1) + assertEquals(latitude, mapboxMap.cameraPosition.target.latitude, 0.1) + assertEquals(longitude, mapboxMap.cameraPosition.target.longitude, 0.1) + } + } + + executePluginTest(pluginAction, PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity)) + } + + @Test + fun animators_focalPointAdjustment() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.cameraMode = CameraMode.TRACKING + plugin.cameraMode = CameraMode.NONE + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + + assertThat(mapboxMap.uiSettings.focalPoint, nullValue()) + } + } + + executePluginTest(pluginAction, PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity)) + } + + @Test + fun animators_dontZoomWhileNotTracking() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.cameraMode = CameraMode.NONE + val zoom = mapboxMap.cameraPosition.zoom + plugin.zoomWhileTracking(10.0) + uiController.loopMainThreadForAtLeast(DEFAULT_TRACKING_ZOOM_ANIMATION_DURATION) + + assertEquals(zoom, mapboxMap.cameraPosition.zoom, 0.1) + } + } + + executePluginTest(pluginAction, PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity)) + } + + @Test + fun animators_zoomWhileTracking() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.cameraMode = CameraMode.TRACKING + plugin.zoomWhileTracking(10.0) + uiController.loopMainThreadForAtLeast(DEFAULT_TRACKING_ZOOM_ANIMATION_DURATION) + + assertEquals(10.0, mapboxMap.cameraPosition.zoom, 0.1) + } + } + + executePluginTest(pluginAction, PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity)) + } + + @Test + @Ignore + fun animators_zoomWhileTrackingCanceledOnModeChange() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.cameraMode = CameraMode.TRACKING + plugin.zoomWhileTracking(15.0) + uiController.loopMainThreadForAtLeast(DEFAULT_TRACKING_ZOOM_ANIMATION_DURATION / 2) + plugin.cameraMode = CameraMode.NONE + uiController.loopMainThreadForAtLeast(DEFAULT_TRACKING_ZOOM_ANIMATION_DURATION / 2) + + assertEquals(15.0 / 2.0, mapboxMap.cameraPosition.zoom, 3.0) + } + } + + executePluginTest(pluginAction, PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity)) + } + + @Test + fun animators_dontZoomWhileStopped() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + + val testLifecycleOwner = TestLifecycleOwner() + testLifecycleOwner.markState(Lifecycle.State.RESUMED) + testLifecycleOwner.lifecycle.addObserver(plugin) + + plugin.cameraMode = CameraMode.TRACKING + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + val zoom = mapboxMap.cameraPosition.zoom + + testLifecycleOwner.markState(Lifecycle.State.CREATED) + plugin.zoomWhileTracking(10.0) + uiController.loopMainThreadForAtLeast(DEFAULT_TRACKING_ZOOM_ANIMATION_DURATION) + + assertEquals(zoom, mapboxMap.cameraPosition.zoom, 0.1) + } + } + + executePluginTest(pluginAction, + PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity, false, null, null, false)) + } + + @Test + @Ignore + fun animators_cancelZoomWhileTracking() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.cameraMode = CameraMode.TRACKING + plugin.zoomWhileTracking(15.0) + uiController.loopMainThreadForAtLeast(DEFAULT_TRACKING_ZOOM_ANIMATION_DURATION / 2) + plugin.cancelZoomWhileTrackingAnimation() + uiController.loopMainThreadForAtLeast(DEFAULT_TRACKING_ZOOM_ANIMATION_DURATION / 2) + + assertEquals(15.0 / 2.0, mapboxMap.cameraPosition.zoom, 3.0) + } + } + + executePluginTest(pluginAction, PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity)) + } + + @Test + fun animators_dontTiltWhileNotTracking() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.cameraMode = CameraMode.NONE + val tilt = mapboxMap.cameraPosition.tilt + plugin.tiltWhileTracking(30.0) + uiController.loopMainThreadForAtLeast(DEFAULT_TRACKING_TILT_ANIMATION_DURATION) + + assertEquals(tilt, mapboxMap.cameraPosition.tilt, 0.1) + } + } + + executePluginTest(pluginAction, PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity)) + } + + @Test + fun animators_tiltWhileTracking() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.cameraMode = CameraMode.TRACKING + plugin.tiltWhileTracking(30.0) + uiController.loopMainThreadForAtLeast(DEFAULT_TRACKING_TILT_ANIMATION_DURATION) + + assertEquals(30.0, mapboxMap.cameraPosition.tilt, 0.1) + } + } + + executePluginTest(pluginAction, PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity)) + } + + @Test + @Ignore + fun animators_tiltWhileTrackingCanceledOnModeChange() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.cameraMode = CameraMode.TRACKING + plugin.tiltWhileTracking(30.0) + uiController.loopMainThreadForAtLeast(DEFAULT_TRACKING_TILT_ANIMATION_DURATION / 2) + plugin.cameraMode = CameraMode.NONE + uiController.loopMainThreadForAtLeast(DEFAULT_TRACKING_TILT_ANIMATION_DURATION / 2) + + assertEquals(30.0 / 2.0, mapboxMap.cameraPosition.tilt, 3.0) + } + } + + executePluginTest(pluginAction, PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity)) + } + + @Test + fun animators_dontTiltWhileStopped() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + + val testLifecycleOwner = TestLifecycleOwner() + testLifecycleOwner.markState(Lifecycle.State.RESUMED) + testLifecycleOwner.lifecycle.addObserver(plugin) + + plugin.cameraMode = CameraMode.TRACKING + val tilt = mapboxMap.cameraPosition.tilt + + testLifecycleOwner.markState(Lifecycle.State.CREATED) + plugin.tiltWhileTracking(30.0) + uiController.loopMainThreadForAtLeast(DEFAULT_TRACKING_TILT_ANIMATION_DURATION) + + assertEquals(tilt, mapboxMap.cameraPosition.tilt, 0.1) + } + } + + executePluginTest(pluginAction, + PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity, false, null, null, false)) + } + + @Test + @Ignore + fun animators_cancelTiltWhileTracking() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.cameraMode = CameraMode.TRACKING + plugin.tiltWhileTracking(30.0) + uiController.loopMainThreadForAtLeast(DEFAULT_TRACKING_TILT_ANIMATION_DURATION / 2) + plugin.cancelTiltWhileTrackingAnimation() + uiController.loopMainThreadForAtLeast(DEFAULT_TRACKING_TILT_ANIMATION_DURATION / 2) + + assertEquals(30.0 / 2.0, mapboxMap.cameraPosition.tilt, 3.0) + } + } + + executePluginTest(pluginAction, PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity)) + } + + @Test + fun cameraPositionAdjustedToTrackingModeWhenPluginEnabled() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.cameraMode = CameraMode.TRACKING_GPS + plugin.forceLocationUpdate(location) + plugin.isLocationLayerEnabled = false + mapboxMap.moveCamera(CameraUpdateFactory.newLatLng(LatLng(51.0, 17.0))) + mapboxMap.moveCamera(CameraUpdateFactory.bearingTo(90.0)) + plugin.isLocationLayerEnabled = true + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + + assertEquals(location.bearing.toDouble(), mapboxMap.cameraPosition.bearing, 0.1) + assertEquals(location.latitude, mapboxMap.cameraPosition.target.latitude, 0.1) + assertEquals(location.longitude, mapboxMap.cameraPosition.target.longitude, 0.1) + } + } + + executePluginTest(pluginAction, PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity)) + } + + @Test + fun onPluginInitialized_defaultCompassEngineIsProvided() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + assertTrue(plugin.compassEngine is LocationLayerCompassEngine) + } + } + + executePluginTest(pluginAction, PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity)) + } + + @After + fun afterTest() { + Timber.e("@After: ${nameRule.methodName} - unregister idle resource") + IdlingRegistry.getInstance().unregister(idlingResource) + IdlingRegistry.getInstance().unregister(styleChangeIdlingResource) + } + + private fun executePluginTest(listener: GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin>, + pluginProvider: GenericPluginAction.PluginProvider<LocationLayerPlugin> = PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity)) { + onView(withId(R.id.content)).perform(GenericPluginAction(fragment.view as MapView, mapboxMap, pluginProvider, listener)) + } +}
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/locationlayer/LocationLayerTest.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/locationlayer/LocationLayerTest.kt new file mode 100644 index 0000000000..121c8f2d22 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/locationlayer/LocationLayerTest.kt @@ -0,0 +1,356 @@ +package com.mapbox.mapboxsdk.plugins.locationlayer + +import android.Manifest +import android.R +import android.content.Context +import android.location.Location +import android.support.test.espresso.Espresso.onView +import android.support.test.espresso.IdlingRegistry +import android.support.test.espresso.UiController +import android.support.test.espresso.assertion.ViewAssertions.matches +import android.support.test.espresso.matcher.ViewMatchers.isDisplayed +import android.support.test.espresso.matcher.ViewMatchers.withId +import android.support.test.filters.LargeTest +import android.support.test.rule.ActivityTestRule +import android.support.test.rule.GrantPermissionRule +import android.support.test.rule.GrantPermissionRule.grant +import android.support.test.runner.AndroidJUnit4 +import com.mapbox.mapboxsdk.camera.CameraUpdateFactory +import com.mapbox.mapboxsdk.constants.Style +import com.mapbox.mapboxsdk.geometry.LatLng +import com.mapbox.mapboxsdk.maps.MapboxMap +import com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerConstants.* +import com.mapbox.mapboxsdk.plugins.locationlayer.modes.RenderMode +import com.mapbox.mapboxsdk.plugins.utils.* +import com.mapbox.mapboxsdk.plugins.utils.MapboxTestingUtils.Companion.MAPBOX_HEAVY_STYLE +import com.mapbox.mapboxsdk.plugins.utils.MapboxTestingUtils.Companion.pushSourceUpdates +import com.mapbox.mapboxsdk.plugins.utils.PluginGenerationUtil.Companion.MAP_CONNECTION_DELAY +import com.mapbox.mapboxsdk.plugins.utils.PluginGenerationUtil.Companion.MAP_RENDER_DELAY +import com.mapbox.mapboxsdk.style.sources.GeoJsonSource +import com.mapbox.mapboxsdk.testapp.activity.SingleActivity +import org.hamcrest.CoreMatchers.`is` +import org.hamcrest.CoreMatchers.notNullValue +import org.hamcrest.Matchers.equalTo +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestName +import org.junit.runner.RunWith +import timber.log.Timber + +@RunWith(AndroidJUnit4::class) +@LargeTest +class LocationLayerTest { + + @Rule + @JvmField + val activityRule = ActivityTestRule(SingleActivity::class.java) + + @Rule + @JvmField + val nameRule = TestName() + + @Rule + @JvmField + val permissionRule: GrantPermissionRule = grant(Manifest.permission.ACCESS_FINE_LOCATION) + + private lateinit var idlingResource: OnMapReadyIdlingResource + private lateinit var styleChangeIdlingResource: StyleChangeIdlingResource + private lateinit var mapboxMap: MapboxMap + private val location: Location by lazy { + val initLocation = Location("test") + initLocation.latitude = 15.0 + initLocation.longitude = 17.0 + initLocation.accuracy = 2000f + initLocation + } + + @Before + fun beforeTest() { + Timber.e("@Before: ${nameRule.methodName} - register idle resource") + // If idlingResource is null, throw Kotlin exception + idlingResource = OnMapReadyIdlingResource(activityRule.activity) + styleChangeIdlingResource = StyleChangeIdlingResource() + IdlingRegistry.getInstance().register(idlingResource) + IdlingRegistry.getInstance().register(styleChangeIdlingResource) + onView(withId(android.R.id.content)).check(matches(isDisplayed())) + mapboxMap = idlingResource.mapboxMap + } + + // + // Location Source + // + + @Test + fun renderModeNormal_sourceDoesGetAdded() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.renderMode = RenderMode.NORMAL + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + assertThat(mapboxMap.getSource(LOCATION_SOURCE), notNullValue()) + } + } + executePluginTest(pluginAction) + } + + // + // Location Layers + // + + @Test + fun renderModeNormal_trackingNormalLayersDoGetAdded() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.renderMode = RenderMode.NORMAL + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + assertThat(mapboxMap.isLayerVisible(FOREGROUND_LAYER), `is`(true)) + assertThat(mapboxMap.isLayerVisible(BACKGROUND_LAYER), `is`(true)) + assertThat(mapboxMap.isLayerVisible(SHADOW_LAYER), `is`(true)) + assertThat(mapboxMap.isLayerVisible(ACCURACY_LAYER), `is`(true)) + assertThat(mapboxMap.isLayerVisible(BEARING_LAYER), `is`(false)) + } + } + executePluginTest(pluginAction) + } + + @Test + fun renderModeCompass_bearingLayersDoGetAdded() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.renderMode = RenderMode.COMPASS + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + assertThat(mapboxMap.isLayerVisible(FOREGROUND_LAYER), `is`(true)) + assertThat(mapboxMap.isLayerVisible(BACKGROUND_LAYER), `is`(true)) + assertThat(mapboxMap.isLayerVisible(SHADOW_LAYER), `is`(true)) + assertThat(mapboxMap.isLayerVisible(ACCURACY_LAYER), `is`(true)) + assertThat(mapboxMap.isLayerVisible(BEARING_LAYER), `is`(true)) + } + } + executePluginTest(pluginAction) + } + + @Test + fun renderModeGps_navigationLayersDoGetAdded() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.renderMode = RenderMode.GPS + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + assertThat(mapboxMap.isLayerVisible(FOREGROUND_LAYER), `is`(true)) + assertThat(mapboxMap.isLayerVisible(BACKGROUND_LAYER), `is`(true)) + assertThat(mapboxMap.isLayerVisible(SHADOW_LAYER), `is`(false)) + assertThat(mapboxMap.isLayerVisible(ACCURACY_LAYER), `is`(false)) + assertThat(mapboxMap.isLayerVisible(BEARING_LAYER), `is`(false)) + } + } + executePluginTest(pluginAction) + } + + @Test + fun dontShowPuckWhenRenderModeSetAndPluginDisabled() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.forceLocationUpdate(location) + plugin.isLocationLayerEnabled = false + plugin.renderMode = RenderMode.GPS + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + assertThat(mapboxMap.isLayerVisible(FOREGROUND_LAYER), `is`(false)) + assertThat(mapboxMap.isLayerVisible(BACKGROUND_LAYER), `is`(false)) + assertThat(mapboxMap.isLayerVisible(SHADOW_LAYER), `is`(false)) + assertThat(mapboxMap.isLayerVisible(ACCURACY_LAYER), `is`(false)) + assertThat(mapboxMap.isLayerVisible(BEARING_LAYER), `is`(false)) + } + } + executePluginTest(pluginAction) + } + + @Test + fun whenLocationLayerPluginDisabled_doesSetAllLayersToVisibilityNone() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.renderMode = RenderMode.NORMAL + plugin.forceLocationUpdate(location) + plugin.isLocationLayerEnabled = false + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + + // Check that all layers visibilities are set to none + assertThat(mapboxMap.isLayerVisible(FOREGROUND_LAYER), `is`(false)) + assertThat(mapboxMap.isLayerVisible(BACKGROUND_LAYER), `is`(false)) + assertThat(mapboxMap.isLayerVisible(SHADOW_LAYER), `is`(false)) + assertThat(mapboxMap.isLayerVisible(ACCURACY_LAYER), `is`(false)) + assertThat(mapboxMap.isLayerVisible(BEARING_LAYER), `is`(false)) + } + } + executePluginTest(pluginAction) + } + + @Test + fun onMapChange_locationLayerLayersDoGetRedrawn() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.renderMode = RenderMode.NORMAL + plugin.forceLocationUpdate(location) + mapboxMap.setStyleUrl(Style.LIGHT) + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(MAP_CONNECTION_DELAY) + + assertThat(plugin.renderMode, `is`(equalTo(RenderMode.NORMAL))) + + // Check that the Source has been re-added to the new map style + val source: GeoJsonSource? = mapboxMap.getSourceAs(LOCATION_SOURCE) + assertThat(source, notNullValue()) + + // Check that all layers visibilities are set to visible + assertThat(mapboxMap.isLayerVisible(FOREGROUND_LAYER), `is`(true)) + assertThat(mapboxMap.isLayerVisible(BACKGROUND_LAYER), `is`(true)) + assertThat(mapboxMap.isLayerVisible(SHADOW_LAYER), `is`(true)) + assertThat(mapboxMap.isLayerVisible(ACCURACY_LAYER), `is`(true)) + assertThat(mapboxMap.isLayerVisible(BEARING_LAYER), `is`(false)) + } + } + executePluginTest(pluginAction) + } + +// +// Stale state test +// + + @Test + fun whenStyleChanged_continuesUsingStaleIcons() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.applyStyle(LocationLayerOptions.builder(context).staleStateTimeout(100).build()) + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(200) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + + assertThat(mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0].getBooleanProperty(PROPERTY_LOCATION_STALE), `is`(true)) + + mapboxMap.setStyleUrl(Style.LIGHT) + uiController.loopMainThreadForAtLeast(MAP_CONNECTION_DELAY) + + assertThat(mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0].getBooleanProperty(PROPERTY_LOCATION_STALE), `is`(true)) + } + } + executePluginTest(pluginAction) + } + + @Test + fun whenStyleChanged_staleStateChanges() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + plugin.applyStyle(LocationLayerOptions.builder(context).staleStateTimeout(1).build()) + styleChangeIdlingResource.waitForStyle(idlingResource.mapView, mapboxMap, MAPBOX_HEAVY_STYLE) + pushSourceUpdates(styleChangeIdlingResource) { + plugin.forceLocationUpdate(location) + } + } + } + executePluginTest(pluginAction) + + // Waiting for style to finish loading while pushing updates + onView(withId(R.id.content)).check(matches(isDisplayed())) + } + + @Test + fun whenStyleChanged_layerVisibilityUpdates() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + styleChangeIdlingResource.waitForStyle(idlingResource.mapView, mapboxMap, MAPBOX_HEAVY_STYLE) + var show = true + pushSourceUpdates(styleChangeIdlingResource) { + plugin.isLocationLayerEnabled = show + show = !show + } + + uiController.loopMainThreadForAtLeast(MAP_CONNECTION_DELAY) + } + } + executePluginTest(pluginAction) + + // Waiting for style to finish loading while pushing updates + onView(withId(R.id.content)).check(matches(isDisplayed())) + } + + @Test + fun accuracy_visibleWithNewLocation() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + mapboxMap.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(location), 16.0)) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY + ACCURACY_RADIUS_ANIMATION_DURATION) + + assertEquals(Utils.calculateZoomLevelRadius(mapboxMap, location) /*meters projected to radius on zoom 16*/, + mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0] + .getNumberProperty(PROPERTY_ACCURACY_RADIUS).toFloat(), 0.1f) + } + } + executePluginTest(pluginAction) + } + + @Test + fun accuracy_visibleWhenCameraEased() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + mapboxMap.easeCamera(CameraUpdateFactory.newLatLngZoom(LatLng(location), 16.0), 300) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY + 300) + + assertEquals(Utils.calculateZoomLevelRadius(mapboxMap, location) /*meters projected to radius on zoom 16*/, + mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0] + .getNumberProperty(PROPERTY_ACCURACY_RADIUS).toFloat(), 0.1f) + } + } + executePluginTest(pluginAction) + } + + @Test + fun accuracy_visibleWhenCameraMoved() { + val pluginAction = object : GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin> { + override fun onGenericPluginAction(plugin: LocationLayerPlugin, mapboxMap: MapboxMap, + uiController: UiController, context: Context) { + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + plugin.forceLocationUpdate(location) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + mapboxMap.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(location), 16.0)) + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY + 300) + + assertEquals(Utils.calculateZoomLevelRadius(mapboxMap, location) /*meters projected to radius on zoom 16*/, + mapboxMap.querySourceFeatures(LOCATION_SOURCE)[0] + .getNumberProperty(PROPERTY_ACCURACY_RADIUS).toFloat(), 0.1f) + } + } + executePluginTest(pluginAction) + } + + @After + fun afterTest() { + Timber.e("@After: ${nameRule.methodName} - unregister idle resource") + IdlingRegistry.getInstance().unregister(idlingResource) + IdlingRegistry.getInstance().unregister(styleChangeIdlingResource) + } + + private fun executePluginTest(listener: GenericPluginAction.OnPerformGenericPluginAction<LocationLayerPlugin>) { + onView(withId(android.R.id.content)).perform(GenericPluginAction(idlingResource.mapView, mapboxMap, PluginGenerationUtil.getLocationLayerPluginProvider(activityRule.activity), listener)) + } +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/utils/GenericPluginAction.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/utils/GenericPluginAction.kt new file mode 100644 index 0000000000..210f7b4758 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/utils/GenericPluginAction.kt @@ -0,0 +1,48 @@ +package com.mapbox.mapboxsdk.plugins.utils + +import android.content.Context +import android.support.test.espresso.UiController +import android.support.test.espresso.ViewAction +import android.support.test.espresso.matcher.ViewMatchers.isDisplayed +import android.view.View +import com.mapbox.mapboxsdk.maps.MapView +import com.mapbox.mapboxsdk.maps.MapboxMap +import com.mapbox.mapboxsdk.plugins.utils.PluginGenerationUtil.Companion.MAP_RENDER_DELAY +import org.hamcrest.Matcher + +class GenericPluginAction<T>(private val mapView: MapView, private val mapboxMap: MapboxMap, private val pluginProvider: PluginProvider<T>, + private val onPerformGenericPluginAction: OnPerformGenericPluginAction<T>) : ViewAction { + + override fun getConstraints(): Matcher<View> { + return isDisplayed() + } + + override fun getDescription(): String { + return javaClass.simpleName + } + + override fun perform(uiController: UiController, view: View) { + val plugin = pluginProvider.providePlugin(mapView, mapboxMap, view.context) + + // ensuring that the asynchronous renderer has time to render data we want to test + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + while (!pluginProvider.isPluginDataReady(plugin, mapboxMap)) { + uiController.loopMainThreadForAtLeast(MAP_RENDER_DELAY) + } + + onPerformGenericPluginAction.onGenericPluginAction( + plugin, + mapboxMap, + uiController, + view.context) + } + + interface OnPerformGenericPluginAction<in T> { + fun onGenericPluginAction(plugin: T, mapboxMap: MapboxMap, uiController: UiController, context: Context) + } + + interface PluginProvider<T> { + fun providePlugin(mapView: MapView, mapboxMap: MapboxMap, context: Context): T + fun isPluginDataReady(plugin: T, mapboxMap: MapboxMap): Boolean + } +}
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/utils/MapboxTestingUtils.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/utils/MapboxTestingUtils.kt new file mode 100644 index 0000000000..b65cb3278b --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/utils/MapboxTestingUtils.kt @@ -0,0 +1,79 @@ +package com.mapbox.mapboxsdk.plugins.utils + +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.location.Location +import android.os.Handler +import android.os.Looper +import com.mapbox.geojson.Feature +import com.mapbox.mapboxsdk.geometry.LatLng +import com.mapbox.mapboxsdk.maps.MapboxMap +import com.mapbox.mapboxsdk.style.layers.Property +import com.mapbox.mapboxsdk.style.sources.GeoJsonSource + +fun MapboxMap.querySourceFeatures(sourceId: String): List<Feature> { + return this.getSourceAs<GeoJsonSource>(sourceId)?.querySourceFeatures(null) as List<Feature> +} + +fun MapboxMap.queryRenderedFeatures(location: Location, layerId: String): List<Feature> { + val latLng = LatLng(location.latitude, location.longitude) + val point = this.projection.toScreenLocation(latLng) + return this.queryRenderedFeatures(point, layerId) +} + +fun MapboxMap.isLayerVisible(layerId: String): Boolean { + return this.getLayer(layerId)?.visibility?.value?.equals(Property.VISIBLE)!! +} + +class MapboxTestingUtils { + companion object { + + /** + * Used to increase style load time for stress testing. + */ + const val MAPBOX_HEAVY_STYLE = "asset://heavy_style.json" + + private const val DATA_PUSH_INTERVAL = 1L + + /** + * Pushes data updates every [DATA_PUSH_INTERVAL] milliseconds until the style has been loaded, + * checked with [StyleChangeIdlingResource]. + */ + fun pushSourceUpdates(styleChangeIdlingResource: StyleChangeIdlingResource, update: () -> Unit) { + val mainHandler = Handler(Looper.getMainLooper()) + val runnable = object : Runnable { + override fun run() { + update.invoke() + if (!styleChangeIdlingResource.isIdleNow) { + mainHandler.postDelayed(this, DATA_PUSH_INTERVAL) + } + } + } + + if (!styleChangeIdlingResource.isIdleNow) { + if (Looper.myLooper() == Looper.getMainLooper()) { + runnable.run() + } else { + mainHandler.post(runnable) + } + } + } + } +} + +fun MapboxMap.addImageFromDrawable(string: String, drawable: Drawable) { + val bitmapFromDrawable = getBitmapFromDrawable(drawable) + this.addImage(string, bitmapFromDrawable) +} + +private fun getBitmapFromDrawable(drawable: Drawable): Bitmap { + if (drawable is BitmapDrawable) return drawable.bitmap + val bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, + drawable.intrinsicHeight, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + drawable.setBounds(0, 0, canvas.width, canvas.height) + drawable.draw(canvas) + return bitmap +}
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/utils/OnMapFragmentReadyIdlingResource.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/utils/OnMapFragmentReadyIdlingResource.kt new file mode 100644 index 0000000000..fa6b732770 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/utils/OnMapFragmentReadyIdlingResource.kt @@ -0,0 +1,39 @@ +package com.mapbox.mapboxsdk.plugins.utils + +import android.os.Handler +import android.os.Looper +import android.support.test.espresso.IdlingResource + +import com.mapbox.mapboxsdk.maps.MapboxMap +import com.mapbox.mapboxsdk.maps.OnMapReadyCallback +import com.mapbox.mapboxsdk.maps.SupportMapFragment + +class OnMapFragmentReadyIdlingResource(fragment: SupportMapFragment?) : IdlingResource, OnMapReadyCallback { + + lateinit var mapboxMap: MapboxMap + + private var resourceCallback: IdlingResource.ResourceCallback? = null + + init { + Handler(Looper.getMainLooper()).post { + fragment?.getMapAsync(this) + } + } + + override fun getName(): String { + return javaClass.simpleName + } + + override fun isIdleNow(): Boolean { + return this::mapboxMap.isInitialized + } + + override fun registerIdleTransitionCallback(resourceCallback: IdlingResource.ResourceCallback) { + this.resourceCallback = resourceCallback + } + + override fun onMapReady(mapboxMap: MapboxMap) { + this.mapboxMap = mapboxMap + resourceCallback?.onTransitionToIdle() + } +}
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/utils/OnMapReadyIdlingResource.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/utils/OnMapReadyIdlingResource.java new file mode 100644 index 0000000000..f084343594 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/utils/OnMapReadyIdlingResource.java @@ -0,0 +1,63 @@ +package com.mapbox.mapboxsdk.plugins.utils; + +import android.app.Activity; +import android.os.Handler; +import android.os.Looper; +import android.support.test.espresso.IdlingResource; + +import com.mapbox.mapboxsdk.maps.MapView; +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; + +import java.lang.reflect.Field; + +public class OnMapReadyIdlingResource implements IdlingResource, OnMapReadyCallback { + + private MapboxMap mapboxMap; + private MapView mapView; + private IdlingResource.ResourceCallback resourceCallback; + + public OnMapReadyIdlingResource(Activity activity) { + new Handler(Looper.getMainLooper()).post(() -> { + try { + Field field = activity.getClass().getDeclaredField("mapView"); + field.setAccessible(true); + mapView = ((MapView) field.get(activity)); + mapView.getMapAsync(this); + } catch (Exception err) { + throw new RuntimeException(err); + } + }); + } + + @Override + public String getName() { + return getClass().getSimpleName(); + } + + @Override + public boolean isIdleNow() { + return mapboxMap != null; + } + + @Override + public void registerIdleTransitionCallback(ResourceCallback resourceCallback) { + this.resourceCallback = resourceCallback; + } + + public MapView getMapView() { + return mapView; + } + + public MapboxMap getMapboxMap() { + return mapboxMap; + } + + @Override + public void onMapReady(MapboxMap mapboxMap) { + this.mapboxMap = mapboxMap; + if (resourceCallback != null) { + resourceCallback.onTransitionToIdle(); + } + } +}
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/utils/PluginGenerationUtil.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/utils/PluginGenerationUtil.kt new file mode 100644 index 0000000000..9049a28298 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/utils/PluginGenerationUtil.kt @@ -0,0 +1,51 @@ +package com.mapbox.mapboxsdk.plugins.utils + +import android.content.Context +import android.support.v7.app.AppCompatActivity +import com.mapbox.android.core.location.LocationEngine +import com.mapbox.mapboxsdk.maps.MapView +import com.mapbox.mapboxsdk.maps.MapboxMap +import com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerOptions +import com.mapbox.mapboxsdk.plugins.locationlayer.LocationLayerPlugin + +class PluginGenerationUtil { + companion object { + fun getLocationLayerPluginProvider(activity: AppCompatActivity, + useDefaultEngine: Boolean = false, + engine: LocationEngine? = null, + options: LocationLayerOptions? = null, + registerLifecycleObserver: Boolean = true) + : GenericPluginAction.PluginProvider<LocationLayerPlugin> { + return object : GenericPluginAction.PluginProvider<LocationLayerPlugin> { + override fun providePlugin(mapView: MapView, mapboxMap: MapboxMap, context: Context): LocationLayerPlugin { + val plugin = if (useDefaultEngine) { + if (options != null) { + LocationLayerPlugin(mapView, mapboxMap, options) + } else { + LocationLayerPlugin(mapView, mapboxMap) + } + } else { + if (options != null) { + LocationLayerPlugin(mapView, mapboxMap, engine, options) + } else { + LocationLayerPlugin(mapView, mapboxMap, engine) + } + } + + if (registerLifecycleObserver) { + activity.lifecycle.addObserver(plugin) + } + + return plugin + } + + override fun isPluginDataReady(plugin: LocationLayerPlugin, mapboxMap: MapboxMap): Boolean { + return mapboxMap.getSource("mapbox-location-source") != null + } + } + } + + const val MAP_RENDER_DELAY = 250L + const val MAP_CONNECTION_DELAY = 1000L + } +}
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/utils/StyleChangeIdlingResource.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/utils/StyleChangeIdlingResource.kt new file mode 100644 index 0000000000..ab2c855c65 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/utils/StyleChangeIdlingResource.kt @@ -0,0 +1,46 @@ +package com.mapbox.mapboxsdk.plugins.utils + +import android.support.test.espresso.IdlingResource +import com.mapbox.mapboxsdk.maps.MapView +import com.mapbox.mapboxsdk.maps.MapboxMap + +/** + * Resource, that's idling until the provided style is loaded. + * Remember to add any espresso action (like view assertion) after the [waitForStyle] call + * for the test to keep running. + */ +class StyleChangeIdlingResource : IdlingResource { + + private var callback: IdlingResource.ResourceCallback? = null + private var isIdle = true + + override fun getName(): String { + return javaClass.simpleName + } + + override fun isIdleNow(): Boolean { + return isIdle + } + + override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback?) { + this.callback = callback + } + + private fun setIdle() { + isIdle = true + callback?.onTransitionToIdle() + } + + fun waitForStyle(mapView: MapView, mapboxMap: MapboxMap, styleUrl: String) { + isIdle = false + mapView.addOnMapChangedListener(object : MapView.OnMapChangedListener { + override fun onMapChanged(change: Int) { + if (change == MapView.DID_FINISH_LOADING_STYLE) { + mapView.removeOnMapChangedListener(this) + setIdle() + } + } + }) + mapboxMap.setStyleUrl(styleUrl) + } +}
\ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/utils/TestLifecycleOwner.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/utils/TestLifecycleOwner.kt new file mode 100644 index 0000000000..ccb8da17a7 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/plugins/utils/TestLifecycleOwner.kt @@ -0,0 +1,19 @@ +package com.mapbox.mapboxsdk.plugins.utils + +import android.arch.lifecycle.Lifecycle +import android.arch.lifecycle.LifecycleOwner +import android.arch.lifecycle.LifecycleRegistry + +class TestLifecycleOwner : LifecycleOwner { + private val lifecycleRegistry = LifecycleRegistry(this) + + override fun getLifecycle() = lifecycleRegistry + + fun markState(state: Lifecycle.State) { + lifecycleRegistry.markState(state) + } + + fun handleLifecycleEvent(event: Lifecycle.Event) { + lifecycleRegistry.handleLifecycleEvent(event) + } +}
\ No newline at end of file |