[google_maps] Prepares packages to endorse web. (#4064)

Prepares the google_maps packages to endorse the Web platform:

* `[google_maps_flutter]` Changes `example/integration_test` to run in the web.
  * Splits the only file there in 3 for them to be slightly more manageable.
  * (Does not require publishing)
* `[google_maps_flutter_platform_interface]` Adds a test coming from the core plugin to the unit tests of this package.
  * (Does not require publishing)
* `[google_maps_flutter_web]` Changes to add an "inspector" object, and to conform with the tests in the core package.
  * Implements a `GoogleMapsInspectorPlatform` to allow integration tests to inspect parts of the internal state of a gmap.
  * Fires a `MapStyleException` when an invalid JSON is used in `setMapStyle` (was `FormatException` previously), to conform with the expected behavior in the core plugin tests.
  * (Requires publishing)

## Issues

* Part of: https://github.com/flutter/flutter/issues/80688
This commit is contained in:
David Iglesias
2023-05-30 17:38:25 -07:00
committed by GitHub
parent 6df913c2ad
commit 95bb7930e1
18 changed files with 1558 additions and 1278 deletions

View File

@ -0,0 +1,439 @@
// 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.
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:integration_test/integration_test.dart';
import 'shared.dart';
/// Integration Tests that only need a standard [GoogleMapController].
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
runTests();
}
void runTests() {
testWidgets('testInitialCenterLocationAtCenter', (WidgetTester tester) async {
await tester.binding.setSurfaceSize(const Size(800, 600));
final Completer<GoogleMapController> mapControllerCompleter =
Completer<GoogleMapController>();
final Key key = GlobalKey();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
mapControllerCompleter.complete(controller);
},
),
),
);
final GoogleMapController mapController =
await mapControllerCompleter.future;
await tester.pumpAndSettle();
// TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen
// in `mapRendered`.
// https://github.com/flutter/flutter/issues/54758
await Future<void>.delayed(const Duration(seconds: 1));
final ScreenCoordinate coordinate =
await mapController.getScreenCoordinate(kInitialCameraPosition.target);
final Rect rect = tester.getRect(find.byKey(key));
if (isIOS || isWeb) {
// On iOS, the coordinate value from the GoogleMapSdk doesn't include the devicePixelRatio`.
// So we don't need to do the conversion like we did below for other platforms.
expect(coordinate.x, (rect.center.dx - rect.topLeft.dx).round());
expect(coordinate.y, (rect.center.dy - rect.topLeft.dy).round());
} else {
expect(
coordinate.x,
((rect.center.dx - rect.topLeft.dx) *
// TODO(pdblasi-google): Update `window` usages to new API after 3.9.0 is in stable. https://github.com/flutter/flutter/issues/122912
// ignore: deprecated_member_use
tester.binding.window.devicePixelRatio)
.round());
expect(
coordinate.y,
((rect.center.dy - rect.topLeft.dy) *
// TODO(pdblasi-google): Update `window` usages to new API after 3.9.0 is in stable. https://github.com/flutter/flutter/issues/122912
// ignore: deprecated_member_use
tester.binding.window.devicePixelRatio)
.round());
}
await tester.binding.setSurfaceSize(null);
},
// Android doesn't like the layout required for the web, so we skip web in this test.
// The equivalent web test already exists here:
// https://github.com/flutter/packages/blob/c43cc13498a1a1c4f3d1b8af2add9ce7c15bd6d0/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/projection_test.dart#L78
skip: isWeb);
testWidgets('testGetVisibleRegion', (WidgetTester tester) async {
final Key key = GlobalKey();
final LatLngBounds zeroLatLngBounds = LatLngBounds(
southwest: const LatLng(0, 0), northeast: const LatLng(0, 0));
final Completer<GoogleMapController> mapControllerCompleter =
Completer<GoogleMapController>();
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
mapControllerCompleter.complete(controller);
},
),
);
await tester.pumpAndSettle();
final GoogleMapController mapController =
await mapControllerCompleter.future;
// Wait for the visible region to be non-zero.
final LatLngBounds firstVisibleRegion =
await waitForValueMatchingPredicate<LatLngBounds>(
tester,
() => mapController.getVisibleRegion(),
(LatLngBounds bounds) => bounds != zeroLatLngBounds) ??
zeroLatLngBounds;
expect(firstVisibleRegion, isNot(zeroLatLngBounds));
expect(firstVisibleRegion.contains(kInitialMapCenter), isTrue);
// Making a new `LatLngBounds` about (10, 10) distance south west to the `firstVisibleRegion`.
// The size of the `LatLngBounds` is 10 by 10.
final LatLng southWest = LatLng(firstVisibleRegion.southwest.latitude - 20,
firstVisibleRegion.southwest.longitude - 20);
final LatLng northEast = LatLng(firstVisibleRegion.southwest.latitude - 10,
firstVisibleRegion.southwest.longitude - 10);
final LatLng newCenter = LatLng(
(northEast.latitude + southWest.latitude) / 2,
(northEast.longitude + southWest.longitude) / 2,
);
expect(firstVisibleRegion.contains(northEast), isFalse);
expect(firstVisibleRegion.contains(southWest), isFalse);
final LatLngBounds latLngBounds =
LatLngBounds(southwest: southWest, northeast: northEast);
// TODO(iskakaushik): non-zero padding is needed for some device configurations
// https://github.com/flutter/flutter/issues/30575
const double padding = 0;
await mapController
.moveCamera(CameraUpdate.newLatLngBounds(latLngBounds, padding));
await tester.pumpAndSettle(const Duration(seconds: 3));
final LatLngBounds secondVisibleRegion =
await mapController.getVisibleRegion();
expect(secondVisibleRegion, isNot(zeroLatLngBounds));
expect(firstVisibleRegion, isNot(secondVisibleRegion));
expect(secondVisibleRegion.contains(newCenter), isTrue);
});
testWidgets('testSetMapStyle valid Json String', (WidgetTester tester) async {
final Key key = GlobalKey();
final Completer<GoogleMapController> controllerCompleter =
Completer<GoogleMapController>();
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
controllerCompleter.complete(controller);
},
),
);
final GoogleMapController controller = await controllerCompleter.future;
const String mapStyle =
'[{"elementType":"geometry","stylers":[{"color":"#242f3e"}]}]';
await controller.setMapStyle(mapStyle);
});
testWidgets('testSetMapStyle invalid Json String',
(WidgetTester tester) async {
final Key key = GlobalKey();
final Completer<GoogleMapController> controllerCompleter =
Completer<GoogleMapController>();
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
controllerCompleter.complete(controller);
},
),
);
final GoogleMapController controller = await controllerCompleter.future;
try {
await controller.setMapStyle('invalid_value');
fail('expected MapStyleException');
} on MapStyleException catch (e) {
expect(e.cause, isNotNull);
}
});
testWidgets('testSetMapStyle null string', (WidgetTester tester) async {
final Key key = GlobalKey();
final Completer<GoogleMapController> controllerCompleter =
Completer<GoogleMapController>();
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
controllerCompleter.complete(controller);
},
),
);
final GoogleMapController controller = await controllerCompleter.future;
await controller.setMapStyle(null);
});
testWidgets('testGetLatLng', (WidgetTester tester) async {
final Key key = GlobalKey();
final Completer<GoogleMapController> controllerCompleter =
Completer<GoogleMapController>();
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
controllerCompleter.complete(controller);
},
),
);
final GoogleMapController controller = await controllerCompleter.future;
await tester.pumpAndSettle();
// TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen
// in `mapRendered`.
// https://github.com/flutter/flutter/issues/54758
await Future<void>.delayed(const Duration(seconds: 1));
final LatLngBounds visibleRegion = await controller.getVisibleRegion();
final LatLng topLeft =
await controller.getLatLng(const ScreenCoordinate(x: 0, y: 0));
final LatLng northWest = LatLng(
visibleRegion.northeast.latitude,
visibleRegion.southwest.longitude,
);
expect(topLeft, northWest);
});
testWidgets('testGetZoomLevel', (WidgetTester tester) async {
final Key key = GlobalKey();
final Completer<GoogleMapController> controllerCompleter =
Completer<GoogleMapController>();
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
controllerCompleter.complete(controller);
},
),
);
final GoogleMapController controller = await controllerCompleter.future;
await tester.pumpAndSettle();
// TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen
// in `mapRendered`.
// https://github.com/flutter/flutter/issues/54758
await Future<void>.delayed(const Duration(seconds: 1));
double zoom = await controller.getZoomLevel();
expect(zoom, kInitialZoomLevel);
await controller.moveCamera(CameraUpdate.zoomTo(7));
await tester.pumpAndSettle();
zoom = await controller.getZoomLevel();
expect(zoom, equals(7));
});
testWidgets('testScreenCoordinate', (WidgetTester tester) async {
final Key key = GlobalKey();
final Completer<GoogleMapController> controllerCompleter =
Completer<GoogleMapController>();
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
controllerCompleter.complete(controller);
},
),
);
final GoogleMapController controller = await controllerCompleter.future;
await tester.pumpAndSettle();
// TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen
// in `mapRendered`.
// https://github.com/flutter/flutter/issues/54758
await Future<void>.delayed(const Duration(seconds: 1));
final LatLngBounds visibleRegion = await controller.getVisibleRegion();
final LatLng northWest = LatLng(
visibleRegion.northeast.latitude,
visibleRegion.southwest.longitude,
);
final ScreenCoordinate topLeft =
await controller.getScreenCoordinate(northWest);
expect(topLeft, const ScreenCoordinate(x: 0, y: 0));
});
testWidgets('testResizeWidget', (WidgetTester tester) async {
final Completer<GoogleMapController> controllerCompleter =
Completer<GoogleMapController>();
await pumpMap(
tester,
GoogleMap(
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) async {
controllerCompleter.complete(controller);
},
),
const Size(100, 100),
);
final GoogleMapController controller = await controllerCompleter.future;
await pumpMap(
tester,
GoogleMap(
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) async {
// fail!
fail('The map should not get recreated!');
// controllerCompleter.complete(controller);
},
),
const Size(400, 400),
);
await tester.pumpAndSettle();
// TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen
// in `mapRendered`.
// https://github.com/flutter/flutter/issues/54758
await Future<void>.delayed(const Duration(seconds: 1));
// Simple call to make sure that the app hasn't crashed.
final LatLngBounds bounds1 = await controller.getVisibleRegion();
final LatLngBounds bounds2 = await controller.getVisibleRegion();
expect(bounds1, bounds2);
});
testWidgets('testToggleInfoWindow', (WidgetTester tester) async {
const Marker marker = Marker(
markerId: MarkerId('marker'),
infoWindow: InfoWindow(title: 'InfoWindow'));
final Set<Marker> markers = <Marker>{marker};
final Completer<GoogleMapController> controllerCompleter =
Completer<GoogleMapController>();
await pumpMap(
tester,
GoogleMap(
initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)),
markers: markers,
onMapCreated: (GoogleMapController googleMapController) {
controllerCompleter.complete(googleMapController);
},
),
);
final GoogleMapController controller = await controllerCompleter.future;
bool iwVisibleStatus =
await controller.isMarkerInfoWindowShown(marker.markerId);
expect(iwVisibleStatus, false);
await controller.showMarkerInfoWindow(marker.markerId);
// The Maps SDK doesn't always return true for whether it is shown
// immediately after showing it, so wait for it to report as shown.
iwVisibleStatus = await waitForValueMatchingPredicate<bool>(
tester,
() => controller.isMarkerInfoWindowShown(marker.markerId),
(bool visible) => visible) ??
false;
expect(iwVisibleStatus, true);
await controller.hideMarkerInfoWindow(marker.markerId);
iwVisibleStatus = await controller.isMarkerInfoWindowShown(marker.markerId);
expect(iwVisibleStatus, false);
});
testWidgets('testTakeSnapshot', (WidgetTester tester) async {
final Completer<GoogleMapController> controllerCompleter =
Completer<GoogleMapController>();
await pumpMap(
tester,
GoogleMap(
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
controllerCompleter.complete(controller);
},
),
);
await tester.pumpAndSettle(const Duration(seconds: 3));
final GoogleMapController controller = await controllerCompleter.future;
final Uint8List? bytes = await controller.takeSnapshot();
expect(bytes?.isNotEmpty, true);
},
// TODO(cyanglaz): un-skip the test when we can test this on CI with API key enabled.
// https://github.com/flutter/flutter/issues/57057
skip: isAndroid || isWeb);
}
/// Repeatedly checks an asynchronous value against a test condition.
///
/// This function waits one frame between each check, returning the value if it
/// passes the predicate before [maxTries] is reached.
///
/// Returns null if the predicate is never satisfied.
///
/// This is useful for cases where the Maps SDK has some internally
/// asynchronous operation that we don't have visibility into (e.g., native UI
/// animations).
Future<T?> waitForValueMatchingPredicate<T>(WidgetTester tester,
Future<T> Function() getValue, bool Function(T) predicate,
{int maxTries = 100}) async {
for (int i = 0; i < maxTries; i++) {
final T value = await getValue();
if (predicate(value)) {
return value;
}
await tester.pump();
}
return null;
}

View File

@ -0,0 +1,536 @@
// 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.
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
import 'package:integration_test/integration_test.dart';
import 'shared.dart';
/// Integration Tests that use the [GoogleMapsInspectorPlatform].
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
runTests();
}
void runTests() {
GoogleMapsFlutterPlatform.instance.enableDebugInspection();
final GoogleMapsInspectorPlatform inspector =
GoogleMapsInspectorPlatform.instance!;
testWidgets('testCompassToggle', (WidgetTester tester) async {
final Key key = GlobalKey();
final Completer<int> mapIdCompleter = Completer<int>();
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
compassEnabled: false,
onMapCreated: (GoogleMapController controller) {
mapIdCompleter.complete(controller.mapId);
},
),
);
final int mapId = await mapIdCompleter.future;
bool compassEnabled = await inspector.isCompassEnabled(mapId: mapId);
expect(compassEnabled, false);
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
fail('OnMapCreated should get called only once.');
},
),
);
compassEnabled = await inspector.isCompassEnabled(mapId: mapId);
expect(compassEnabled, !kIsWeb);
});
testWidgets('testMapToolbarToggle', (WidgetTester tester) async {
final Key key = GlobalKey();
final Completer<int> mapIdCompleter = Completer<int>();
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
mapToolbarEnabled: false,
onMapCreated: (GoogleMapController controller) {
mapIdCompleter.complete(controller.mapId);
},
),
);
final int mapId = await mapIdCompleter.future;
bool mapToolbarEnabled = await inspector.isMapToolbarEnabled(mapId: mapId);
expect(mapToolbarEnabled, false);
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
fail('OnMapCreated should get called only once.');
},
),
);
mapToolbarEnabled = await inspector.isMapToolbarEnabled(mapId: mapId);
expect(mapToolbarEnabled, isAndroid);
});
testWidgets('updateMinMaxZoomLevels', (WidgetTester tester) async {
// The behaviors of setting min max zoom level on iOS and Android are different.
// On iOS, when we get the min or max zoom level after setting the preference, the
// min and max will be exactly the same as the value we set; on Android however,
// the values we get do not equal to the value we set.
//
// Also, when we call zoomTo to set the zoom, on Android, it usually
// honors the preferences that we set and the zoom cannot pass beyond the boundary.
// On iOS, on the other hand, zoomTo seems to override the preferences.
//
// Thus we test iOS and Android a little differently here.
final Key key = GlobalKey();
final Completer<GoogleMapController> controllerCompleter =
Completer<GoogleMapController>();
const MinMaxZoomPreference initialZoomLevel = MinMaxZoomPreference(4, 8);
const MinMaxZoomPreference finalZoomLevel = MinMaxZoomPreference(6, 10);
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
minMaxZoomPreference: initialZoomLevel,
onMapCreated: (GoogleMapController c) async {
controllerCompleter.complete(c);
},
),
);
final GoogleMapController controller = await controllerCompleter.future;
if (isIOS) {
final MinMaxZoomPreference zoomLevel =
await inspector.getMinMaxZoomLevels(mapId: controller.mapId);
expect(zoomLevel, equals(initialZoomLevel));
} else if (isAndroid) {
await controller.moveCamera(CameraUpdate.zoomTo(15));
await tester.pumpAndSettle();
double? zoomLevel = await controller.getZoomLevel();
expect(zoomLevel, equals(initialZoomLevel.maxZoom));
await controller.moveCamera(CameraUpdate.zoomTo(1));
await tester.pumpAndSettle();
zoomLevel = await controller.getZoomLevel();
expect(zoomLevel, equals(initialZoomLevel.minZoom));
}
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
minMaxZoomPreference: finalZoomLevel,
onMapCreated: (GoogleMapController controller) {
fail('OnMapCreated should get called only once.');
},
),
);
if (isIOS) {
final MinMaxZoomPreference zoomLevel =
await inspector.getMinMaxZoomLevels(mapId: controller.mapId);
expect(zoomLevel, equals(finalZoomLevel));
} else {
await controller.moveCamera(CameraUpdate.zoomTo(15));
await tester.pumpAndSettle();
double? zoomLevel = await controller.getZoomLevel();
expect(zoomLevel, equals(finalZoomLevel.maxZoom));
await controller.moveCamera(CameraUpdate.zoomTo(1));
await tester.pumpAndSettle();
zoomLevel = await controller.getZoomLevel();
expect(zoomLevel, equals(finalZoomLevel.minZoom));
}
});
testWidgets('testZoomGesturesEnabled', (WidgetTester tester) async {
final Key key = GlobalKey();
final Completer<int> mapIdCompleter = Completer<int>();
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
zoomGesturesEnabled: false,
onMapCreated: (GoogleMapController controller) {
mapIdCompleter.complete(controller.mapId);
},
),
);
final int mapId = await mapIdCompleter.future;
bool zoomGesturesEnabled =
await inspector.areZoomGesturesEnabled(mapId: mapId);
expect(zoomGesturesEnabled, false);
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
fail('OnMapCreated should get called only once.');
},
),
);
zoomGesturesEnabled = await inspector.areZoomGesturesEnabled(mapId: mapId);
expect(zoomGesturesEnabled, true);
});
testWidgets('testZoomControlsEnabled', (WidgetTester tester) async {
final Key key = GlobalKey();
final Completer<int> mapIdCompleter = Completer<int>();
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
mapIdCompleter.complete(controller.mapId);
},
),
);
final int mapId = await mapIdCompleter.future;
bool zoomControlsEnabled =
await inspector.areZoomControlsEnabled(mapId: mapId);
expect(zoomControlsEnabled, !isIOS);
/// Zoom Controls functionality is not available on iOS at the moment.
if (!isIOS) {
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
zoomControlsEnabled: false,
onMapCreated: (GoogleMapController controller) {
fail('OnMapCreated should get called only once.');
},
),
);
zoomControlsEnabled =
await inspector.areZoomControlsEnabled(mapId: mapId);
expect(zoomControlsEnabled, false);
}
});
testWidgets('testLiteModeEnabled', (WidgetTester tester) async {
final Key key = GlobalKey();
final Completer<int> mapIdCompleter = Completer<int>();
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
mapIdCompleter.complete(controller.mapId);
},
),
);
final int mapId = await mapIdCompleter.future;
bool liteModeEnabled = await inspector.isLiteModeEnabled(mapId: mapId);
expect(liteModeEnabled, false);
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
liteModeEnabled: true,
onMapCreated: (GoogleMapController controller) {
fail('OnMapCreated should get called only once.');
},
),
);
liteModeEnabled = await inspector.isLiteModeEnabled(mapId: mapId);
expect(liteModeEnabled, true);
}, skip: !isAndroid);
testWidgets('testRotateGesturesEnabled', (WidgetTester tester) async {
final Key key = GlobalKey();
final Completer<int> mapIdCompleter = Completer<int>();
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
rotateGesturesEnabled: false,
onMapCreated: (GoogleMapController controller) {
mapIdCompleter.complete(controller.mapId);
},
),
);
final int mapId = await mapIdCompleter.future;
bool rotateGesturesEnabled =
await inspector.areRotateGesturesEnabled(mapId: mapId);
expect(rotateGesturesEnabled, false);
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
fail('OnMapCreated should get called only once.');
},
),
);
rotateGesturesEnabled =
await inspector.areRotateGesturesEnabled(mapId: mapId);
expect(rotateGesturesEnabled, !isWeb);
});
testWidgets('testTiltGesturesEnabled', (WidgetTester tester) async {
final Key key = GlobalKey();
final Completer<int> mapIdCompleter = Completer<int>();
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
tiltGesturesEnabled: false,
onMapCreated: (GoogleMapController controller) {
mapIdCompleter.complete(controller.mapId);
},
),
);
final int mapId = await mapIdCompleter.future;
bool tiltGesturesEnabled =
await inspector.areTiltGesturesEnabled(mapId: mapId);
expect(tiltGesturesEnabled, false);
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
fail('OnMapCreated should get called only once.');
},
),
);
tiltGesturesEnabled = await inspector.areTiltGesturesEnabled(mapId: mapId);
expect(tiltGesturesEnabled, !isWeb);
});
testWidgets('testScrollGesturesEnabled', (WidgetTester tester) async {
final Key key = GlobalKey();
final Completer<int> mapIdCompleter = Completer<int>();
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
scrollGesturesEnabled: false,
onMapCreated: (GoogleMapController controller) {
mapIdCompleter.complete(controller.mapId);
},
),
);
final int mapId = await mapIdCompleter.future;
bool scrollGesturesEnabled =
await inspector.areScrollGesturesEnabled(mapId: mapId);
expect(scrollGesturesEnabled, false);
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
fail('OnMapCreated should get called only once.');
},
),
);
scrollGesturesEnabled =
await inspector.areScrollGesturesEnabled(mapId: mapId);
expect(scrollGesturesEnabled, true);
});
testWidgets('testTraffic', (WidgetTester tester) async {
final Key key = GlobalKey();
final Completer<int> mapIdCompleter = Completer<int>();
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
trafficEnabled: true,
onMapCreated: (GoogleMapController controller) {
mapIdCompleter.complete(controller.mapId);
},
),
);
final int mapId = await mapIdCompleter.future;
bool isTrafficEnabled = await inspector.isTrafficEnabled(mapId: mapId);
expect(isTrafficEnabled, true);
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
fail('OnMapCreated should get called only once.');
},
),
);
isTrafficEnabled = await inspector.isTrafficEnabled(mapId: mapId);
expect(isTrafficEnabled, false);
});
testWidgets('testBuildings', (WidgetTester tester) async {
final Key key = GlobalKey();
final Completer<int> mapIdCompleter = Completer<int>();
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
mapIdCompleter.complete(controller.mapId);
},
),
);
final int mapId = await mapIdCompleter.future;
final bool isBuildingsEnabled =
await inspector.areBuildingsEnabled(mapId: mapId);
expect(isBuildingsEnabled, !isWeb);
});
// Location button tests are skipped in Android because we don't have location permission to test.
// Location button tests are skipped in Web because the functionality is not implemented.
group('MyLocationButton', () {
testWidgets('testMyLocationButtonToggle', (WidgetTester tester) async {
final Key key = GlobalKey();
final Completer<int> mapIdCompleter = Completer<int>();
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
mapIdCompleter.complete(controller.mapId);
},
),
);
final int mapId = await mapIdCompleter.future;
bool myLocationButtonEnabled =
await inspector.isMyLocationButtonEnabled(mapId: mapId);
expect(myLocationButtonEnabled, true);
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
myLocationButtonEnabled: false,
onMapCreated: (GoogleMapController controller) {
fail('OnMapCreated should get called only once.');
},
),
);
myLocationButtonEnabled =
await inspector.isMyLocationButtonEnabled(mapId: mapId);
expect(myLocationButtonEnabled, false);
});
testWidgets('testMyLocationButton initial value false',
(WidgetTester tester) async {
final Key key = GlobalKey();
final Completer<int> mapIdCompleter = Completer<int>();
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
myLocationButtonEnabled: false,
onMapCreated: (GoogleMapController controller) {
mapIdCompleter.complete(controller.mapId);
},
),
);
final int mapId = await mapIdCompleter.future;
final bool myLocationButtonEnabled =
await inspector.isMyLocationButtonEnabled(mapId: mapId);
expect(myLocationButtonEnabled, false);
});
testWidgets('testMyLocationButton initial value true',
(WidgetTester tester) async {
final Key key = GlobalKey();
final Completer<int> mapIdCompleter = Completer<int>();
await pumpMap(
tester,
GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
mapIdCompleter.complete(controller.mapId);
},
),
);
final int mapId = await mapIdCompleter.future;
final bool myLocationButtonEnabled =
await inspector.isMyLocationButtonEnabled(mapId: mapId);
expect(myLocationButtonEnabled, true);
});
}, skip: !isIOS);
}

View File

@ -0,0 +1,53 @@
// 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.
// This file contains shared definitions used across multiple test scenarios.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
/// Initial map center
const LatLng kInitialMapCenter = LatLng(0, 0);
/// Initial zoom level
const double kInitialZoomLevel = 5;
/// Initial camera position
const CameraPosition kInitialCameraPosition =
CameraPosition(target: kInitialMapCenter, zoom: kInitialZoomLevel);
/// True if the test is running in an iOS device
final bool isIOS = defaultTargetPlatform == TargetPlatform.iOS;
/// True if the test is running in an Android device
final bool isAndroid =
defaultTargetPlatform == TargetPlatform.android && !kIsWeb;
/// True if the test is running in a web browser.
const bool isWeb = kIsWeb;
/// Pumps a [map] widget in [tester] of a certain [size], then waits until it settles.
Future<void> pumpMap(WidgetTester tester, GoogleMap map,
[Size size = const Size.square(200)]) async {
await tester.pumpWidget(wrapMap(map, size));
await tester.pumpAndSettle();
}
/// Wraps a [map] in a bunch of widgets so it renders in all platforms.
///
/// An optional [size] can be passed.
Widget wrapMap(GoogleMap map, [Size size = const Size.square(200)]) {
return MaterialApp(
home: Scaffold(
body: Center(
child: SizedBox.fromSize(
size: size,
child: map,
),
),
),
);
}

View File

@ -0,0 +1,251 @@
// 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.
import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
import 'package:integration_test/integration_test.dart';
import 'shared.dart';
/// Integration Tests for the Tiles feature. These also use the [GoogleMapsInspectorPlatform].
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
runTests();
}
void runTests() {
GoogleMapsFlutterPlatform.instance.enableDebugInspection();
final GoogleMapsInspectorPlatform inspector =
GoogleMapsInspectorPlatform.instance!;
group('Tiles', () {
testWidgets(
'set tileOverlay correctly',
(WidgetTester tester) async {
final Completer<int> mapIdCompleter = Completer<int>();
final TileOverlay tileOverlay1 = TileOverlay(
tileOverlayId: const TileOverlayId('tile_overlay_1'),
tileProvider: _DebugTileProvider(),
zIndex: 2,
transparency: 0.2,
);
final TileOverlay tileOverlay2 = TileOverlay(
tileOverlayId: const TileOverlayId('tile_overlay_2'),
tileProvider: _DebugTileProvider(),
zIndex: 1,
visible: false,
transparency: 0.3,
fadeIn: false,
);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: GoogleMap(
initialCameraPosition: kInitialCameraPosition,
tileOverlays: <TileOverlay>{tileOverlay1, tileOverlay2},
onMapCreated: (GoogleMapController controller) {
mapIdCompleter.complete(controller.mapId);
},
),
),
);
await tester.pumpAndSettle(const Duration(seconds: 3));
final int mapId = await mapIdCompleter.future;
final TileOverlay tileOverlayInfo1 = (await inspector
.getTileOverlayInfo(tileOverlay1.mapsId, mapId: mapId))!;
final TileOverlay tileOverlayInfo2 = (await inspector
.getTileOverlayInfo(tileOverlay2.mapsId, mapId: mapId))!;
expect(tileOverlayInfo1.visible, isTrue);
expect(tileOverlayInfo1.fadeIn, isTrue);
expect(tileOverlayInfo1.transparency,
moreOrLessEquals(0.2, epsilon: 0.001));
expect(tileOverlayInfo1.zIndex, 2);
expect(tileOverlayInfo2.visible, isFalse);
expect(tileOverlayInfo2.fadeIn, isFalse);
expect(tileOverlayInfo2.transparency,
moreOrLessEquals(0.3, epsilon: 0.001));
expect(tileOverlayInfo2.zIndex, 1);
},
);
testWidgets(
'update tileOverlays correctly',
(WidgetTester tester) async {
final Completer<int> mapIdCompleter = Completer<int>();
final Key key = GlobalKey();
final TileOverlay tileOverlay1 = TileOverlay(
tileOverlayId: const TileOverlayId('tile_overlay_1'),
tileProvider: _DebugTileProvider(),
zIndex: 2,
transparency: 0.2,
);
final TileOverlay tileOverlay2 = TileOverlay(
tileOverlayId: const TileOverlayId('tile_overlay_2'),
tileProvider: _DebugTileProvider(),
zIndex: 3,
transparency: 0.5,
);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
tileOverlays: <TileOverlay>{tileOverlay1, tileOverlay2},
onMapCreated: (GoogleMapController controller) {
mapIdCompleter.complete(controller.mapId);
},
),
),
);
final int mapId = await mapIdCompleter.future;
final TileOverlay tileOverlay1New = TileOverlay(
tileOverlayId: const TileOverlayId('tile_overlay_1'),
tileProvider: _DebugTileProvider(),
zIndex: 1,
visible: false,
transparency: 0.3,
fadeIn: false,
);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
tileOverlays: <TileOverlay>{tileOverlay1New},
onMapCreated: (GoogleMapController controller) {
fail('update: OnMapCreated should get called only once.');
},
),
),
);
await tester.pumpAndSettle(const Duration(seconds: 3));
final TileOverlay tileOverlayInfo1 = (await inspector
.getTileOverlayInfo(tileOverlay1.mapsId, mapId: mapId))!;
final TileOverlay? tileOverlayInfo2 = await inspector
.getTileOverlayInfo(tileOverlay2.mapsId, mapId: mapId);
expect(tileOverlayInfo1.visible, isFalse);
expect(tileOverlayInfo1.fadeIn, isFalse);
expect(tileOverlayInfo1.transparency,
moreOrLessEquals(0.3, epsilon: 0.001));
expect(tileOverlayInfo1.zIndex, 1);
expect(tileOverlayInfo2, isNull);
},
);
testWidgets(
'remove tileOverlays correctly',
(WidgetTester tester) async {
final Completer<int> mapIdCompleter = Completer<int>();
final Key key = GlobalKey();
final TileOverlay tileOverlay1 = TileOverlay(
tileOverlayId: const TileOverlayId('tile_overlay_1'),
tileProvider: _DebugTileProvider(),
zIndex: 2,
transparency: 0.2,
);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
tileOverlays: <TileOverlay>{tileOverlay1},
onMapCreated: (GoogleMapController controller) {
mapIdCompleter.complete(controller.mapId);
},
),
),
);
final int mapId = await mapIdCompleter.future;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: GoogleMap(
key: key,
initialCameraPosition: kInitialCameraPosition,
onMapCreated: (GoogleMapController controller) {
fail('OnMapCreated should get called only once.');
},
),
),
);
await tester.pumpAndSettle(const Duration(seconds: 3));
final TileOverlay? tileOverlayInfo1 = await inspector
.getTileOverlayInfo(tileOverlay1.mapsId, mapId: mapId);
expect(tileOverlayInfo1, isNull);
},
);
}, skip: isWeb /* Tiles not supported on the web */);
}
class _DebugTileProvider implements TileProvider {
_DebugTileProvider() {
boxPaint.isAntiAlias = true;
boxPaint.color = Colors.blue;
boxPaint.strokeWidth = 2.0;
boxPaint.style = PaintingStyle.stroke;
}
static const int width = 100;
static const int height = 100;
static final Paint boxPaint = Paint();
static const TextStyle textStyle = TextStyle(
color: Colors.red,
fontSize: 20,
);
@override
Future<Tile> getTile(int x, int y, int? zoom) async {
final ui.PictureRecorder recorder = ui.PictureRecorder();
final Canvas canvas = Canvas(recorder);
final TextSpan textSpan = TextSpan(
text: '$x,$y',
style: textStyle,
);
final TextPainter textPainter = TextPainter(
text: textSpan,
textDirection: TextDirection.ltr,
);
textPainter.layout(
maxWidth: width.toDouble(),
);
textPainter.paint(canvas, Offset.zero);
canvas.drawRect(
Rect.fromLTRB(0, 0, width.toDouble(), width.toDouble()), boxPaint);
final ui.Picture picture = recorder.endRecording();
final Uint8List byteData = await picture
.toImage(width, height)
.then((ui.Image image) =>
image.toByteData(format: ui.ImageByteFormat.png))
.then((ByteData? byteData) => byteData!.buffer.asUint8List());
return Tile(width, height, byteData);
}
}

View File

@ -0,0 +1,22 @@
#!/usr/bin/bash
# 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.
if pgrep -lf chromedriver > /dev/null; then
echo "chromedriver is running."
if [ $# -eq 0 ]; then
echo "No target specified, running all tests..."
find integration_test/ -iname *_test.dart | xargs -n1 -i -t flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target='{}'
else
echo "Running test target: $1..."
set -x
flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_test.dart --target=$1
fi
else
echo "chromedriver is not running."
echo "Please, check the README.md for instructions on how to use run_test.sh"
fi

View File

@ -1,12 +1,8 @@
// 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.
// ignore:unnecessary_import
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
@ -171,6 +167,26 @@ void main() {
<dynamic>['fromAssetImage', 'some/path.png', 1.0]),
isA<BitmapDescriptor>());
});
test('mipmaps determines dpi', () async {
const ImageConfiguration imageConfiguration = ImageConfiguration(
devicePixelRatio: 3,
);
final BitmapDescriptor mip = await BitmapDescriptor.fromAssetImage(
imageConfiguration,
'red_square.png',
);
final BitmapDescriptor scaled = await BitmapDescriptor.fromAssetImage(
imageConfiguration,
'red_square.png',
mipmaps: false,
);
expect((mip.toJson() as List<dynamic>)[2], 1);
expect((scaled.toJson() as List<dynamic>)[2], 3);
});
test('name cannot be null or empty', () {
expect(() {
BitmapDescriptor.fromJson(<dynamic>['fromAssetImage', null, 1.0]);

View File

@ -1,3 +1,10 @@
## 0.5.0
* **BREAKING CHANGE:** Fires a `MapStyleException` when an invalid JSON is used
in `setMapStyle` (was `FormatException` previously).
* Implements a `GoogleMapsInspectorPlatform` to allow integration tests to inspect
parts of the internal state of a map.
## 0.4.0+9
* Removes obsolete null checks on non-nullable values.

View File

@ -1,7 +1,9 @@
// Mocks generated by Mockito 5.4.0 from annotations
// Mocks generated by Mockito 5.4.1 from annotations
// in google_maps_flutter_web_integration_tests/integration_test/google_maps_controller_test.dart.
// Do not manually edit this file.
// @dart=2.19
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:google_maps/google_maps.dart' as _i2;
import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'

View File

@ -197,6 +197,15 @@ void main() {
expect(style.stylers?.length, 1);
expect(getProperty<String>(style.stylers![0]!, 'color'), '#6b9a76');
});
testWidgets('throws MapStyleException for invalid styles',
(WidgetTester tester) async {
plugin.debugSetMapById(<int, GoogleMapController>{0: controller});
expect(() async {
await plugin.setMapStyle('invalid_style', mapId: 0);
}, throwsA(isA<MapStyleException>()));
});
});
group('Noop methods:', () {

View File

@ -1,13 +1,15 @@
// Mocks generated by Mockito 5.4.0 from annotations
// Mocks generated by Mockito 5.4.1 from annotations
// in google_maps_flutter_web_integration_tests/integration_test/google_maps_plugin_test.dart.
// Do not manually edit this file.
// @dart=2.19
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _i2;
import 'dart:async' as _i3;
import 'package:google_maps/google_maps.dart' as _i5;
import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'
as _i3;
as _i2;
import 'package:google_maps_flutter_web/google_maps_flutter_web.dart' as _i4;
import 'package:mockito/mockito.dart' as _i1;
@ -22,9 +24,9 @@ import 'package:mockito/mockito.dart' as _i1;
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class
class _FakeStreamController_0<T> extends _i1.SmartFake
implements _i2.StreamController<T> {
_FakeStreamController_0(
class _FakeMapConfiguration_0 extends _i1.SmartFake
implements _i2.MapConfiguration {
_FakeMapConfiguration_0(
Object parent,
Invocation parentInvocation,
) : super(
@ -33,8 +35,9 @@ class _FakeStreamController_0<T> extends _i1.SmartFake
);
}
class _FakeLatLngBounds_1 extends _i1.SmartFake implements _i3.LatLngBounds {
_FakeLatLngBounds_1(
class _FakeStreamController_1<T> extends _i1.SmartFake
implements _i3.StreamController<T> {
_FakeStreamController_1(
Object parent,
Invocation parentInvocation,
) : super(
@ -43,9 +46,8 @@ class _FakeLatLngBounds_1 extends _i1.SmartFake implements _i3.LatLngBounds {
);
}
class _FakeScreenCoordinate_2 extends _i1.SmartFake
implements _i3.ScreenCoordinate {
_FakeScreenCoordinate_2(
class _FakeLatLngBounds_2 extends _i1.SmartFake implements _i2.LatLngBounds {
_FakeLatLngBounds_2(
Object parent,
Invocation parentInvocation,
) : super(
@ -54,8 +56,19 @@ class _FakeScreenCoordinate_2 extends _i1.SmartFake
);
}
class _FakeLatLng_3 extends _i1.SmartFake implements _i3.LatLng {
_FakeLatLng_3(
class _FakeScreenCoordinate_3 extends _i1.SmartFake
implements _i2.ScreenCoordinate {
_FakeScreenCoordinate_3(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}
class _FakeLatLng_4 extends _i1.SmartFake implements _i2.LatLng {
_FakeLatLng_4(
Object parent,
Invocation parentInvocation,
) : super(
@ -70,24 +83,36 @@ class _FakeLatLng_3 extends _i1.SmartFake implements _i3.LatLng {
class MockGoogleMapController extends _i1.Mock
implements _i4.GoogleMapController {
@override
_i2.StreamController<_i3.MapEvent<Object?>> get stream => (super.noSuchMethod(
_i2.MapConfiguration get configuration => (super.noSuchMethod(
Invocation.getter(#configuration),
returnValue: _FakeMapConfiguration_0(
this,
Invocation.getter(#configuration),
),
returnValueForMissingStub: _FakeMapConfiguration_0(
this,
Invocation.getter(#configuration),
),
) as _i2.MapConfiguration);
@override
_i3.StreamController<_i2.MapEvent<Object?>> get stream => (super.noSuchMethod(
Invocation.getter(#stream),
returnValue: _FakeStreamController_0<_i3.MapEvent<Object?>>(
returnValue: _FakeStreamController_1<_i2.MapEvent<Object?>>(
this,
Invocation.getter(#stream),
),
returnValueForMissingStub:
_FakeStreamController_0<_i3.MapEvent<Object?>>(
_FakeStreamController_1<_i2.MapEvent<Object?>>(
this,
Invocation.getter(#stream),
),
) as _i2.StreamController<_i3.MapEvent<Object?>>);
) as _i3.StreamController<_i2.MapEvent<Object?>>);
@override
_i2.Stream<_i3.MapEvent<Object?>> get events => (super.noSuchMethod(
_i3.Stream<_i2.MapEvent<Object?>> get events => (super.noSuchMethod(
Invocation.getter(#events),
returnValue: _i2.Stream<_i3.MapEvent<Object?>>.empty(),
returnValueForMissingStub: _i2.Stream<_i3.MapEvent<Object?>>.empty(),
) as _i2.Stream<_i3.MapEvent<Object?>>);
returnValue: _i3.Stream<_i2.MapEvent<Object?>>.empty(),
returnValueForMissingStub: _i3.Stream<_i2.MapEvent<Object?>>.empty(),
) as _i3.Stream<_i2.MapEvent<Object?>>);
@override
bool get isInitialized => (super.noSuchMethod(
Invocation.getter(#isInitialized),
@ -125,7 +150,7 @@ class MockGoogleMapController extends _i1.Mock
returnValueForMissingStub: null,
);
@override
void updateMapConfiguration(_i3.MapConfiguration? update) =>
void updateMapConfiguration(_i2.MapConfiguration? update) =>
super.noSuchMethod(
Invocation.method(
#updateMapConfiguration,
@ -142,12 +167,12 @@ class MockGoogleMapController extends _i1.Mock
returnValueForMissingStub: null,
);
@override
_i2.Future<_i3.LatLngBounds> getVisibleRegion() => (super.noSuchMethod(
_i3.Future<_i2.LatLngBounds> getVisibleRegion() => (super.noSuchMethod(
Invocation.method(
#getVisibleRegion,
[],
),
returnValue: _i2.Future<_i3.LatLngBounds>.value(_FakeLatLngBounds_1(
returnValue: _i3.Future<_i2.LatLngBounds>.value(_FakeLatLngBounds_2(
this,
Invocation.method(
#getVisibleRegion,
@ -155,23 +180,23 @@ class MockGoogleMapController extends _i1.Mock
),
)),
returnValueForMissingStub:
_i2.Future<_i3.LatLngBounds>.value(_FakeLatLngBounds_1(
_i3.Future<_i2.LatLngBounds>.value(_FakeLatLngBounds_2(
this,
Invocation.method(
#getVisibleRegion,
[],
),
)),
) as _i2.Future<_i3.LatLngBounds>);
) as _i3.Future<_i2.LatLngBounds>);
@override
_i2.Future<_i3.ScreenCoordinate> getScreenCoordinate(_i3.LatLng? latLng) =>
_i3.Future<_i2.ScreenCoordinate> getScreenCoordinate(_i2.LatLng? latLng) =>
(super.noSuchMethod(
Invocation.method(
#getScreenCoordinate,
[latLng],
),
returnValue:
_i2.Future<_i3.ScreenCoordinate>.value(_FakeScreenCoordinate_2(
_i3.Future<_i2.ScreenCoordinate>.value(_FakeScreenCoordinate_3(
this,
Invocation.method(
#getScreenCoordinate,
@ -179,57 +204,57 @@ class MockGoogleMapController extends _i1.Mock
),
)),
returnValueForMissingStub:
_i2.Future<_i3.ScreenCoordinate>.value(_FakeScreenCoordinate_2(
_i3.Future<_i2.ScreenCoordinate>.value(_FakeScreenCoordinate_3(
this,
Invocation.method(
#getScreenCoordinate,
[latLng],
),
)),
) as _i2.Future<_i3.ScreenCoordinate>);
) as _i3.Future<_i2.ScreenCoordinate>);
@override
_i2.Future<_i3.LatLng> getLatLng(_i3.ScreenCoordinate? screenCoordinate) =>
_i3.Future<_i2.LatLng> getLatLng(_i2.ScreenCoordinate? screenCoordinate) =>
(super.noSuchMethod(
Invocation.method(
#getLatLng,
[screenCoordinate],
),
returnValue: _i2.Future<_i3.LatLng>.value(_FakeLatLng_3(
returnValue: _i3.Future<_i2.LatLng>.value(_FakeLatLng_4(
this,
Invocation.method(
#getLatLng,
[screenCoordinate],
),
)),
returnValueForMissingStub: _i2.Future<_i3.LatLng>.value(_FakeLatLng_3(
returnValueForMissingStub: _i3.Future<_i2.LatLng>.value(_FakeLatLng_4(
this,
Invocation.method(
#getLatLng,
[screenCoordinate],
),
)),
) as _i2.Future<_i3.LatLng>);
) as _i3.Future<_i2.LatLng>);
@override
_i2.Future<void> moveCamera(_i3.CameraUpdate? cameraUpdate) =>
_i3.Future<void> moveCamera(_i2.CameraUpdate? cameraUpdate) =>
(super.noSuchMethod(
Invocation.method(
#moveCamera,
[cameraUpdate],
),
returnValue: _i2.Future<void>.value(),
returnValueForMissingStub: _i2.Future<void>.value(),
) as _i2.Future<void>);
returnValue: _i3.Future<void>.value(),
returnValueForMissingStub: _i3.Future<void>.value(),
) as _i3.Future<void>);
@override
_i2.Future<double> getZoomLevel() => (super.noSuchMethod(
_i3.Future<double> getZoomLevel() => (super.noSuchMethod(
Invocation.method(
#getZoomLevel,
[],
),
returnValue: _i2.Future<double>.value(0.0),
returnValueForMissingStub: _i2.Future<double>.value(0.0),
) as _i2.Future<double>);
returnValue: _i3.Future<double>.value(0.0),
returnValueForMissingStub: _i3.Future<double>.value(0.0),
) as _i3.Future<double>);
@override
void updateCircles(_i3.CircleUpdates? updates) => super.noSuchMethod(
void updateCircles(_i2.CircleUpdates? updates) => super.noSuchMethod(
Invocation.method(
#updateCircles,
[updates],
@ -237,7 +262,7 @@ class MockGoogleMapController extends _i1.Mock
returnValueForMissingStub: null,
);
@override
void updatePolygons(_i3.PolygonUpdates? updates) => super.noSuchMethod(
void updatePolygons(_i2.PolygonUpdates? updates) => super.noSuchMethod(
Invocation.method(
#updatePolygons,
[updates],
@ -245,7 +270,7 @@ class MockGoogleMapController extends _i1.Mock
returnValueForMissingStub: null,
);
@override
void updatePolylines(_i3.PolylineUpdates? updates) => super.noSuchMethod(
void updatePolylines(_i2.PolylineUpdates? updates) => super.noSuchMethod(
Invocation.method(
#updatePolylines,
[updates],
@ -253,7 +278,7 @@ class MockGoogleMapController extends _i1.Mock
returnValueForMissingStub: null,
);
@override
void updateMarkers(_i3.MarkerUpdates? updates) => super.noSuchMethod(
void updateMarkers(_i2.MarkerUpdates? updates) => super.noSuchMethod(
Invocation.method(
#updateMarkers,
[updates],
@ -261,7 +286,7 @@ class MockGoogleMapController extends _i1.Mock
returnValueForMissingStub: null,
);
@override
void showInfoWindow(_i3.MarkerId? markerId) => super.noSuchMethod(
void showInfoWindow(_i2.MarkerId? markerId) => super.noSuchMethod(
Invocation.method(
#showInfoWindow,
[markerId],
@ -269,7 +294,7 @@ class MockGoogleMapController extends _i1.Mock
returnValueForMissingStub: null,
);
@override
void hideInfoWindow(_i3.MarkerId? markerId) => super.noSuchMethod(
void hideInfoWindow(_i2.MarkerId? markerId) => super.noSuchMethod(
Invocation.method(
#hideInfoWindow,
[markerId],
@ -277,7 +302,7 @@ class MockGoogleMapController extends _i1.Mock
returnValueForMissingStub: null,
);
@override
bool isInfoWindowShown(_i3.MarkerId? markerId) => (super.noSuchMethod(
bool isInfoWindowShown(_i2.MarkerId? markerId) => (super.noSuchMethod(
Invocation.method(
#isInfoWindowShown,
[markerId],

View File

@ -22,7 +22,7 @@ dev_dependencies:
google_maps: ^6.1.0
google_maps_flutter: # Used for projection_test.dart
path: ../../google_maps_flutter
http: ^0.13.0
http: ^1.0.0
integration_test:
sdk: flutter
mockito: 5.4.1

View File

@ -19,6 +19,7 @@ import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platf
import 'package:sanitize_html/sanitize_html.dart';
import 'package:stream_transform/stream_transform.dart';
import 'src/google_maps_inspector_web.dart';
import 'src/shims/dart_ui.dart' as ui; // Conditionally imports dart:ui in web
import 'src/third_party/to_screen_location/to_screen_location.dart';
import 'src/types.dart';

View File

@ -126,25 +126,30 @@ bool _isJsonMapStyle(Map<String, Object?> value) {
List<gmaps.MapTypeStyle> _mapStyles(String? mapStyleJson) {
List<gmaps.MapTypeStyle> styles = <gmaps.MapTypeStyle>[];
if (mapStyleJson != null) {
styles = (json.decode(mapStyleJson, reviver: (Object? key, Object? value) {
if (value is Map && _isJsonMapStyle(value as Map<String, Object?>)) {
List<Object?> stylers = <Object?>[];
if (value['stylers'] != null) {
stylers = (value['stylers']! as List<Object?>)
.map<Object?>((Object? e) => e != null ? jsify(e) : null)
.toList();
try {
styles =
(json.decode(mapStyleJson, reviver: (Object? key, Object? value) {
if (value is Map && _isJsonMapStyle(value as Map<String, Object?>)) {
List<Object?> stylers = <Object?>[];
if (value['stylers'] != null) {
stylers = (value['stylers']! as List<Object?>)
.map<Object?>((Object? e) => e != null ? jsify(e) : null)
.toList();
}
return gmaps.MapTypeStyle()
..elementType = value['elementType'] as String?
..featureType = value['featureType'] as String?
..stylers = stylers;
}
return gmaps.MapTypeStyle()
..elementType = value['elementType'] as String?
..featureType = value['featureType'] as String?
..stylers = stylers;
}
return value;
}) as List<Object?>)
.where((Object? element) => element != null)
.cast<gmaps.MapTypeStyle>()
.toList();
// .toList calls are required so the JS API understands the underlying data structure.
return value;
}) as List<Object?>)
.where((Object? element) => element != null)
.cast<gmaps.MapTypeStyle>()
.toList();
// .toList calls are required so the JS API understands the underlying data structure.
} on FormatException catch (e) {
throw MapStyleException(e.message);
}
}
return styles;
}

View File

@ -53,11 +53,19 @@ class GoogleMapController {
final Set<Polygon> _polygons;
final Set<Polyline> _polylines;
final Set<Circle> _circles;
// The configuraiton passed by the user, before converting to gmaps.
// The configuration passed by the user, before converting to gmaps.
// Caching this allows us to re-create the map faithfully when needed.
MapConfiguration _lastMapConfiguration = const MapConfiguration();
List<gmaps.MapTypeStyle> _lastStyles = const <gmaps.MapTypeStyle>[];
/// Configuration accessor for the [GoogleMapsInspectorWeb].
///
/// Returns the latest value of [MapConfiguration] set by the programmer.
///
/// This should only be used by an inspector instance created when a test
/// calls [GoogleMapsPlugin.enableDebugInspection].
MapConfiguration get configuration => _lastMapConfiguration;
// Creates the 'viewType' for the _widget
String _getViewType(int mapId) => 'plugins.flutter.io/google_maps_$mapId';

View File

@ -26,7 +26,7 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform {
// Convenience getter for a stream of events filtered by their mapId.
Stream<MapEvent<Object?>> _events(int mapId) => _map(mapId).events;
// Convenience getter for a map controller by its mapId.
/// Retrieve a map controller by its mapId.
GoogleMapController _map(int mapId) {
final GoogleMapController? controller = _mapById[mapId];
assert(controller != null,
@ -327,4 +327,13 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform {
return mapController.widget!;
}
/// Populates [GoogleMapsFlutterInspectorPlatform.instance] to allow
/// inspecting the platform map state.
@override
void enableDebugInspection() {
GoogleMapsInspectorPlatform.instance = GoogleMapsInspectorWeb(
(int mapId) => _map(mapId).configuration,
);
}
}

View File

@ -0,0 +1,88 @@
// 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.
import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
/// Function that gets the [MapConfiguration] for a given `mapId`.
typedef ConfigurationProvider = MapConfiguration Function(int mapId);
/// This platform implementation allows inspecting the running maps.
class GoogleMapsInspectorWeb extends GoogleMapsInspectorPlatform {
/// Build an "inspector" that is able to look into maps.
GoogleMapsInspectorWeb(ConfigurationProvider configurationProvider)
: _configurationProvider = configurationProvider;
final ConfigurationProvider _configurationProvider;
@override
Future<bool> areBuildingsEnabled({required int mapId}) async {
return false; // Not supported on the web
}
@override
Future<bool> areRotateGesturesEnabled({required int mapId}) async {
return false; // Not supported on the web
}
@override
Future<bool> areScrollGesturesEnabled({required int mapId}) async {
return _configurationProvider(mapId).scrollGesturesEnabled ?? false;
}
@override
Future<bool> areTiltGesturesEnabled({required int mapId}) async {
return false; // Not supported on the web
}
@override
Future<bool> areZoomControlsEnabled({required int mapId}) async {
return _configurationProvider(mapId).zoomControlsEnabled ?? false;
}
@override
Future<bool> areZoomGesturesEnabled({required int mapId}) async {
return _configurationProvider(mapId).zoomGesturesEnabled ?? false;
}
@override
Future<MinMaxZoomPreference> getMinMaxZoomLevels({required int mapId}) async {
final MapConfiguration config = _configurationProvider(mapId);
assert(config.minMaxZoomPreference != null);
return config.minMaxZoomPreference!;
}
@override
Future<TileOverlay?> getTileOverlayInfo(
TileOverlayId tileOverlayId, {
required int mapId,
}) async {
return null; // Custom tiles not supported on the web
}
@override
Future<bool> isCompassEnabled({required int mapId}) async {
return false; // There's no compass on the web
}
@override
Future<bool> isLiteModeEnabled({required int mapId}) async {
return false; // There's no lite mode on the web
}
@override
Future<bool> isMapToolbarEnabled({required int mapId}) async {
return false; // There's no Map Toolbar on the web
}
@override
Future<bool> isMyLocationButtonEnabled({required int mapId}) async {
return false; // My Location widget not supported on the web
}
@override
Future<bool> isTrafficEnabled({required int mapId}) async {
return _configurationProvider(mapId).trafficEnabled ?? false;
}
}

View File

@ -2,7 +2,7 @@ name: google_maps_flutter_web
description: Web platform implementation of google_maps_flutter
repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter_web
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22
version: 0.4.0+9
version: 0.5.0
environment:
sdk: ">=2.18.0 <4.0.0"