Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
[google_maps_flutter_android] Improve test implementations.
  • Loading branch information
jokerttu committed Apr 30, 2024
commit 99fabf8868dc7d7e85c437c4d7a042f60f061b67
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,9 @@ void addClusterManager(Object clusterManagerData) {
throw new IllegalArgumentException("clusterManagerId was null");
}
ClusterManager<MarkerBuilder> clusterManager =
new ClusterManager<>(context, googleMap, markerManager);
ClusterRenderer clusterRenderer = new ClusterRenderer(context, googleMap, clusterManager, this);
new ClusterManager<MarkerBuilder>(context, googleMap, markerManager);
ClusterRenderer<MarkerBuilder> clusterRenderer =
new ClusterRenderer<MarkerBuilder>(context, googleMap, clusterManager, this);
clusterManager.setRenderer(clusterRenderer);
initListenersForClusterManager(clusterManager, this, clusterItemClickListener);
clusterManagerIdToManager.put(clusterManagerId, clusterManager);
Expand Down Expand Up @@ -209,28 +210,28 @@ public boolean onClusterClick(Cluster<MarkerBuilder> cluster) {
* ClusterRenderer builds marker options for new markers to be rendered to the map. After cluster
* item (marker) is rendered, it is sent to the listeners for control.
*/
private static class ClusterRenderer extends DefaultClusterRenderer<MarkerBuilder> {
private static class ClusterRenderer<T extends MarkerBuilder> extends DefaultClusterRenderer<T> {
private final ClusterManagersController clusterManagersController;

public ClusterRenderer(
Context context,
GoogleMap map,
ClusterManager<MarkerBuilder> clusterManager,
ClusterManager<T> clusterManager,
ClusterManagersController clusterManagersController) {
super(context, map, clusterManager);
this.clusterManagersController = clusterManagersController;
}

@Override
protected void onBeforeClusterItemRendered(
@NonNull MarkerBuilder item, @NonNull MarkerOptions markerOptions) {
@NonNull T item, @NonNull MarkerOptions markerOptions) {
// Builds new markerOptions for new marker created by the ClusterRenderer under
// ClusterManager.
item.update(markerOptions);
}

@Override
protected void onClusterItemRendered(@NonNull MarkerBuilder item, @NonNull Marker marker) {
protected void onClusterItemRendered(@NonNull T item, @NonNull Marker marker) {
super.onClusterItemRendered(item, marker);
clusterManagersController.onClusterItemRendered(item, marker);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
import java.util.Objects;

/** Controller of a single GoogleMaps MapView instance. */
final class GoogleMapController
class GoogleMapController
implements DefaultLifecycleObserver,
ActivityPluginBinding.OnSaveInstanceStateListener,
GoogleMapOptionsSink,
Expand Down Expand Up @@ -124,6 +124,35 @@ final class GoogleMapController
this.tileOverlaysController = new TileOverlaysController(methodChannel);
}

// Constructor for testing purposes only
@VisibleForTesting
GoogleMapController(
int id,
Context context,
MethodChannel methodChannel,
LifecycleProvider lifecycleProvider,
GoogleMapOptions options,
ClusterManagersController clusterManagersController,
MarkersController markersController,
PolygonsController polygonsController,
PolylinesController polylinesController,
CirclesController circlesController,
TileOverlaysController tileOverlaysController) {
this.id = id;
this.context = context;
this.methodChannel = methodChannel;
this.options = options;
this.mapView = new MapView(context, options);
this.density = context.getResources().getDisplayMetrics().density;
this.lifecycleProvider = lifecycleProvider;
this.clusterManagersController = clusterManagersController;
this.markersController = markersController;
this.polygonsController = polygonsController;
this.polylinesController = polylinesController;
this.circlesController = circlesController;
this.tileOverlaysController = tileOverlaysController;
}

@Override
public View getView() {
return mapView;
Expand Down Expand Up @@ -660,7 +689,8 @@ private void setGoogleMapListener(@Nullable GoogleMapListener listener) {
googleMap.setOnMapLongClickListener(listener);
}

private void setMarkerCollectionListener(@Nullable GoogleMapListener listener) {
@VisibleForTesting
public void setMarkerCollectionListener(@Nullable GoogleMapListener listener) {
if (googleMap == null) {
Log.v(TAG, "Controller was disposed before GoogleMap was ready.");
return;
Expand All @@ -671,7 +701,8 @@ private void setMarkerCollectionListener(@Nullable GoogleMapListener listener) {
markerCollection.setOnInfoWindowClickListener(listener);
}

private void setClusterItemClickListener(
@VisibleForTesting
public void setClusterItemClickListener(
@Nullable ClusterManager.OnClusterItemClickListener<MarkerBuilder> listener) {
if (googleMap == null) {
Log.v(TAG, "Controller was disposed before GoogleMap was ready.");
Expand All @@ -681,7 +712,8 @@ private void setClusterItemClickListener(
clusterManagersController.setClusterItemClickListener(listener);
}

private void setClusterItemRenderedListener(
@VisibleForTesting
public void setClusterItemRenderedListener(
@Nullable ClusterManagersController.OnClusterItemRendered<MarkerBuilder> listener) {
if (googleMap == null) {
Log.v(TAG, "Controller was disposed before GoogleMap was ready.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

class MarkersController {
private final HashMap<String, MarkerBuilder> markerIdToMarkerBuilder;
Expand Down Expand Up @@ -241,9 +242,9 @@ private void changeMarker(Object marker) {
String clusterManagerId = getClusterManagerId(marker);
String oldClusterManagerId = markerBuilder.clusterManagerId();

// If the cluster ID on the updated marker has changed, the marker needs to be removed and re-added.
if (!((clusterManagerId == oldClusterManagerId)
|| (clusterManagerId != null && clusterManagerId.equals(oldClusterManagerId)))) {
// If the cluster ID on the updated marker has changed, the marker needs to
// be removed and re-added to update its cluster manager state.
if (!(Objects.equals(clusterManagerId, oldClusterManagerId))) {
removeMarker(markerId);
addMarker(marker);
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package io.flutter.plugins.googlemaps;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;

import android.content.Context;
import android.os.Build;
import androidx.test.core.app.ApplicationProvider;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.maps.android.clustering.algo.StaticCluster;
import com.google.maps.android.collections.MarkerManager;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodCodec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

@RunWith(RobolectricTestRunner.class)
@Config(sdk = Build.VERSION_CODES.P)
public class ClusterManagersControllerTest {
private Context context;
private MethodChannel methodChannel;
private ClusterManagersController controller;
private GoogleMap googleMap;
private MarkerManager markerManager;
private MarkerManager.Collection markerCollection;

@Before
public void setUp() {
context = ApplicationProvider.getApplicationContext();
methodChannel =
spy(new MethodChannel(mock(BinaryMessenger.class), "no-name", mock(MethodCodec.class)));
controller = spy(new ClusterManagersController(methodChannel, context));
googleMap = mock(GoogleMap.class);
markerManager = new MarkerManager(googleMap);
markerCollection = markerManager.newCollection();
controller.init(googleMap, markerManager);
}

@Test
@SuppressWarnings("unchecked")
public void AddClusterManagersAndMarkers() throws InterruptedException {
final String clusterManagerId = "cm_1";
final String markerId1 = "mid_1";
final String markerId2 = "mid_2";

final LatLng latLng1 = new LatLng(1.1, 2.2);
final LatLng latLng2 = new LatLng(3.3, 4.4);

final List<Double> location1 = new ArrayList<>();
location1.add(latLng1.latitude);
location1.add(latLng1.longitude);

final List<Double> location2 = new ArrayList<>();
location2.add(latLng2.latitude);
location2.add(latLng2.longitude);

when(googleMap.getCameraPosition())
.thenReturn(CameraPosition.builder().target(new LatLng(0, 0)).build());
Map<String, Object> initialClusterManager = new HashMap<>();
initialClusterManager.put("clusterManagerId", clusterManagerId);
List<Object> clusterManagersToAdd = new ArrayList<>();
clusterManagersToAdd.add(initialClusterManager);
controller.addClusterManagers(clusterManagersToAdd);

MarkerBuilder markerBuilder1 = new MarkerBuilder(markerId1, clusterManagerId);
MarkerBuilder markerBuilder2 = new MarkerBuilder(markerId2, clusterManagerId);

final Map<String, Object> markerData1 =
createMarkerData(markerId1, location1, clusterManagerId);
final Map<String, Object> markerData2 =
createMarkerData(markerId2, location2, clusterManagerId);

Convert.interpretMarkerOptions(markerData1, markerBuilder1);
Convert.interpretMarkerOptions(markerData2, markerBuilder2);

controller.addItem(markerBuilder1);
controller.addItem(markerBuilder2);

final MethodChannel.Result clusterResult1 = mock(MethodChannel.Result.class);

controller.getClustersWithClusterManagerId(clusterManagerId, clusterResult1);

ArgumentCaptor<Object> resultCaptor1 = ArgumentCaptor.forClass(Object.class);
Mockito.verify(clusterResult1, times(1)).success(resultCaptor1.capture());
Object capturedResult1 = resultCaptor1.getValue();

assertTrue(
"The captured result should be an instance of List", capturedResult1 instanceof List);

List<?> resultList1 = (List<?>) capturedResult1;
assertEquals("Amount of clusters should be 1", 1, resultList1.size());

Map<String, Object> clusterData = (Map<String, Object>) resultList1.get(0);
assertEquals(
"Incorrect cluster manager ID", clusterManagerId, clusterData.get("clusterManagerId"));
assertNotNull("Cluster bounds should not be null", clusterData.get("bounds"));
assertNotNull("Cluster position should not be null", clusterData.get("position"));
List<String> markerIds = (List<String>) clusterData.get("markerIds");
assertTrue("Marker IDs should contain markerId1", markerIds.contains(markerId1));
assertTrue("Marker IDs should contain markerId2", markerIds.contains(markerId2));
assertEquals("Cluster should contain exactly 2 markers", 2, markerIds.size());
}

@Test
@SuppressWarnings("unchecked")
public void OnClusterClickCallsMethodChannel() throws InterruptedException {
String clusterManagerId = "cm_1";
LatLng clusterPosition = new LatLng(43.00, -87.90);
LatLng markerPosition1 = new LatLng(43.05, -87.95);
LatLng markerPosition2 = new LatLng(43.02, -87.92);

StaticCluster<MarkerBuilder> cluster = new StaticCluster<>(clusterPosition);

MarkerBuilder marker1 = new MarkerBuilder("m_1", clusterManagerId);
marker1.setPosition(markerPosition1);
cluster.add(marker1);

MarkerBuilder marker2 = new MarkerBuilder("m_2", clusterManagerId);
marker2.setPosition(markerPosition2);
cluster.add(marker2);

controller.onClusterClick(cluster);
Mockito.verify(methodChannel)
.invokeMethod("cluster#onTap", Convert.clusterToJson(clusterManagerId, cluster));
}

@Test
public void RemoveClusterManagers() {
final String clusterManagerId = "cm_1";

when(googleMap.getCameraPosition())
.thenReturn(CameraPosition.builder().target(new LatLng(0, 0)).build());
Map<String, Object> initialClusterManager = new HashMap<>();
initialClusterManager.put("clusterManagerId", clusterManagerId);
List<Object> clusterManagersToAdd = new ArrayList<>();
clusterManagersToAdd.add(initialClusterManager);
controller.addClusterManagers(clusterManagersToAdd);

final MethodChannel.Result clusterResult1 = mock(MethodChannel.Result.class);
controller.getClustersWithClusterManagerId(clusterManagerId, clusterResult1);
// Verify that fetching the cluster data success and therefore ClusterManager is added.
Mockito.verify(clusterResult1, times(1)).success(any());

controller.removeClusterManagers(Arrays.asList(clusterManagerId));
final MethodChannel.Result clusterResult2 = mock(MethodChannel.Result.class);
controller.getClustersWithClusterManagerId(clusterManagerId, clusterResult2);

// Verify that fetching the cluster data fails and therefore ClusterManager is removed.
Mockito.verify(clusterResult2, times(1)).error(any(), any(), any());
}

private Map<String, Object> createMarkerData(
String markerId, List<Double> location, String clusterManagerId) {
Map<String, Object> markerData = new HashMap<>();
markerData.put("markerId", markerId);
markerData.put("position", location);
markerData.put("clusterManagerId", clusterManagerId);
return markerData;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,18 @@ public void ConvertToPointsConvertsThePointsWithFullPrecision() {
@Test
public void ConvertClustersToJsonReturnsCorrectData() {
String clusterManagerId = "cm_1";
StaticCluster<MarkerBuilder> cluster = new StaticCluster<>(new LatLng(43.00, -87.90));
LatLng clusterPosition = new LatLng(43.00, -87.90);
LatLng markerPosition1 = new LatLng(43.05, -87.95);
LatLng markerPosition2 = new LatLng(43.02, -87.92);

StaticCluster<MarkerBuilder> cluster = new StaticCluster<>(clusterPosition);

MarkerBuilder marker1 = new MarkerBuilder("m_1", clusterManagerId);
marker1.setPosition(new LatLng(43.05, -87.95));
marker1.setPosition(markerPosition1);
cluster.add(marker1);

MarkerBuilder marker2 = new MarkerBuilder("m_2", clusterManagerId);
marker2.setPosition(new LatLng(43.02, -87.92));
marker2.setPosition(markerPosition2);
cluster.add(marker2);

Set<Cluster<MarkerBuilder>> clusters = new HashSet<>();
Expand All @@ -60,19 +64,21 @@ public void ConvertClustersToJsonReturnsCorrectData() {

List<?> position = (List<?>) clusterData.get("position");
Assert.assertTrue(position instanceof List);
Assert.assertEquals(43.00, (double) position.get(0), 1e-15);
Assert.assertEquals(-87.90, (double) position.get(1), 1e-15);
Assert.assertEquals(clusterPosition.latitude, (double) position.get(0), 1e-15);
Assert.assertEquals(clusterPosition.longitude, (double) position.get(1), 1e-15);

Map<?, ?> bounds = (Map<?, ?>) clusterData.get("bounds");
Assert.assertTrue(bounds instanceof Map);
List<?> southwest = (List<?>) bounds.get("southwest");
List<?> northeast = (List<?>) bounds.get("northeast");
Assert.assertTrue(southwest instanceof List);
Assert.assertTrue(northeast instanceof List);
Assert.assertEquals(43.02, (double) southwest.get(0), 1e-15);
Assert.assertEquals(-87.95, (double) southwest.get(1), 1e-15);
Assert.assertEquals(43.05, (double) northeast.get(0), 1e-15);
Assert.assertEquals(-87.92, (double) northeast.get(1), 1e-15);

// bounding data should combine data from marker positions markerPosition1 and markerPosition2
Assert.assertEquals(markerPosition2.latitude, (double) southwest.get(0), 1e-15);
Assert.assertEquals(markerPosition1.longitude, (double) southwest.get(1), 1e-15);
Assert.assertEquals(markerPosition1.latitude, (double) northeast.get(0), 1e-15);
Assert.assertEquals(markerPosition2.longitude, (double) northeast.get(1), 1e-15);

Object markerIds = clusterData.get("markerIds");
Assert.assertTrue(markerIds instanceof List);
Expand Down
Loading