diff --git a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart index 8479b25f0b..92c873bd4d 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart @@ -2,1211 +2,20 @@ // 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:io'; -import 'dart:typed_data'; -import 'dart:ui' as ui; - -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'; -const LatLng _kInitialMapCenter = LatLng(0, 0); -const double _kInitialZoomLevel = 5; -const CameraPosition _kInitialCameraPosition = - CameraPosition(target: _kInitialMapCenter, zoom: _kInitialZoomLevel); +import 'src/maps_controller.dart' as maps_controller; +import 'src/maps_inspector.dart' as maps_inspector; +import 'src/tiles_inspector.dart' as tiles_inspector; +/// Recombine all test files in `src` into a single test app. +/// +/// This is done to ensure that all the integration tests run in the same FTL app, +/// rather than spinning multiple different tasks. void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - GoogleMapsFlutterPlatform.instance.enableDebugInspection(); - // Repeatedly checks an asynchronous value against a test condition, waiting - // one frame between each check, returing 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 waitForValueMatchingPredicate(WidgetTester tester, - Future 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; - } - - testWidgets('testCompassToggle', (WidgetTester tester) async { - final Key key = GlobalKey(); - final Completer mapIdCompleter = Completer(); - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: GoogleMap( - key: key, - initialCameraPosition: _kInitialCameraPosition, - compassEnabled: false, - onMapCreated: (GoogleMapController controller) { - mapIdCompleter.complete(controller.mapId); - }, - ), - )); - - final int mapId = await mapIdCompleter.future; - final GoogleMapsInspectorPlatform inspector = - GoogleMapsInspectorPlatform.instance!; - bool compassEnabled = await inspector.isCompassEnabled(mapId: mapId); - expect(compassEnabled, false); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: GoogleMap( - key: key, - initialCameraPosition: _kInitialCameraPosition, - onMapCreated: (GoogleMapController controller) { - fail('OnMapCreated should get called only once.'); - }, - ), - )); - - compassEnabled = await inspector.isCompassEnabled(mapId: mapId); - expect(compassEnabled, true); - }); - - testWidgets('testMapToolbarToggle', (WidgetTester tester) async { - final Key key = GlobalKey(); - final Completer mapIdCompleter = Completer(); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: GoogleMap( - key: key, - initialCameraPosition: _kInitialCameraPosition, - mapToolbarEnabled: false, - onMapCreated: (GoogleMapController controller) { - mapIdCompleter.complete(controller.mapId); - }, - ), - )); - - final int mapId = await mapIdCompleter.future; - final GoogleMapsInspectorPlatform inspector = - GoogleMapsInspectorPlatform.instance!; - bool mapToolbarEnabled = await inspector.isMapToolbarEnabled(mapId: mapId); - expect(mapToolbarEnabled, false); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: GoogleMap( - key: key, - initialCameraPosition: _kInitialCameraPosition, - onMapCreated: (GoogleMapController controller) { - fail('OnMapCreated should get called only once.'); - }, - ), - )); - - mapToolbarEnabled = await inspector.isMapToolbarEnabled(mapId: mapId); - expect(mapToolbarEnabled, Platform.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 controllerCompleter = - Completer(); - - const MinMaxZoomPreference initialZoomLevel = MinMaxZoomPreference(4, 8); - const MinMaxZoomPreference finalZoomLevel = MinMaxZoomPreference(6, 10); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: GoogleMap( - key: key, - initialCameraPosition: _kInitialCameraPosition, - minMaxZoomPreference: initialZoomLevel, - onMapCreated: (GoogleMapController c) async { - controllerCompleter.complete(c); - }, - ), - )); - - final GoogleMapController controller = await controllerCompleter.future; - final GoogleMapsInspectorPlatform inspector = - GoogleMapsInspectorPlatform.instance!; - - if (Platform.isIOS) { - final MinMaxZoomPreference zoomLevel = - await inspector.getMinMaxZoomLevels(mapId: controller.mapId); - expect(zoomLevel, equals(initialZoomLevel)); - } else if (Platform.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 tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: GoogleMap( - key: key, - initialCameraPosition: _kInitialCameraPosition, - minMaxZoomPreference: finalZoomLevel, - onMapCreated: (GoogleMapController controller) { - fail('OnMapCreated should get called only once.'); - }, - ), - )); - - if (Platform.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 mapIdCompleter = Completer(); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: GoogleMap( - key: key, - initialCameraPosition: _kInitialCameraPosition, - zoomGesturesEnabled: false, - onMapCreated: (GoogleMapController controller) { - mapIdCompleter.complete(controller.mapId); - }, - ), - )); - - final int mapId = await mapIdCompleter.future; - final GoogleMapsInspectorPlatform inspector = - GoogleMapsInspectorPlatform.instance!; - bool zoomGesturesEnabled = - await inspector.areZoomGesturesEnabled(mapId: mapId); - expect(zoomGesturesEnabled, false); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: 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 mapIdCompleter = Completer(); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: GoogleMap( - key: key, - initialCameraPosition: _kInitialCameraPosition, - onMapCreated: (GoogleMapController controller) { - mapIdCompleter.complete(controller.mapId); - }, - ), - )); - - final int mapId = await mapIdCompleter.future; - final GoogleMapsInspectorPlatform inspector = - GoogleMapsInspectorPlatform.instance!; - bool zoomControlsEnabled = - await inspector.areZoomControlsEnabled(mapId: mapId); - expect(zoomControlsEnabled, !Platform.isIOS); - - /// Zoom Controls functionality is not available on iOS at the moment. - if (Platform.isAndroid) { - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: 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 mapIdCompleter = Completer(); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: GoogleMap( - key: key, - initialCameraPosition: _kInitialCameraPosition, - onMapCreated: (GoogleMapController controller) { - mapIdCompleter.complete(controller.mapId); - }, - ), - )); - - final int mapId = await mapIdCompleter.future; - final GoogleMapsInspectorPlatform inspector = - GoogleMapsInspectorPlatform.instance!; - bool liteModeEnabled = await inspector.isLiteModeEnabled(mapId: mapId); - expect(liteModeEnabled, false); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: 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: !Platform.isAndroid); - - testWidgets('testRotateGesturesEnabled', (WidgetTester tester) async { - final Key key = GlobalKey(); - final Completer mapIdCompleter = Completer(); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: GoogleMap( - key: key, - initialCameraPosition: _kInitialCameraPosition, - rotateGesturesEnabled: false, - onMapCreated: (GoogleMapController controller) { - mapIdCompleter.complete(controller.mapId); - }, - ), - )); - - final int mapId = await mapIdCompleter.future; - final GoogleMapsInspectorPlatform inspector = - GoogleMapsInspectorPlatform.instance!; - bool rotateGesturesEnabled = - await inspector.areRotateGesturesEnabled(mapId: mapId); - expect(rotateGesturesEnabled, false); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: GoogleMap( - key: key, - initialCameraPosition: _kInitialCameraPosition, - onMapCreated: (GoogleMapController controller) { - fail('OnMapCreated should get called only once.'); - }, - ), - )); - - rotateGesturesEnabled = - await inspector.areRotateGesturesEnabled(mapId: mapId); - expect(rotateGesturesEnabled, true); - }); - - testWidgets('testTiltGesturesEnabled', (WidgetTester tester) async { - final Key key = GlobalKey(); - final Completer mapIdCompleter = Completer(); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: GoogleMap( - key: key, - initialCameraPosition: _kInitialCameraPosition, - tiltGesturesEnabled: false, - onMapCreated: (GoogleMapController controller) { - mapIdCompleter.complete(controller.mapId); - }, - ), - )); - - final int mapId = await mapIdCompleter.future; - final GoogleMapsInspectorPlatform inspector = - GoogleMapsInspectorPlatform.instance!; - bool tiltGesturesEnabled = - await inspector.areTiltGesturesEnabled(mapId: mapId); - expect(tiltGesturesEnabled, false); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: GoogleMap( - key: key, - initialCameraPosition: _kInitialCameraPosition, - onMapCreated: (GoogleMapController controller) { - fail('OnMapCreated should get called only once.'); - }, - ), - )); - - tiltGesturesEnabled = await inspector.areTiltGesturesEnabled(mapId: mapId); - expect(tiltGesturesEnabled, true); - }); - - testWidgets('testScrollGesturesEnabled', (WidgetTester tester) async { - final Key key = GlobalKey(); - final Completer mapIdCompleter = Completer(); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: GoogleMap( - key: key, - initialCameraPosition: _kInitialCameraPosition, - scrollGesturesEnabled: false, - onMapCreated: (GoogleMapController controller) { - mapIdCompleter.complete(controller.mapId); - }, - ), - )); - - final int mapId = await mapIdCompleter.future; - final GoogleMapsInspectorPlatform inspector = - GoogleMapsInspectorPlatform.instance!; - bool scrollGesturesEnabled = - await inspector.areScrollGesturesEnabled(mapId: mapId); - expect(scrollGesturesEnabled, false); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: 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('testInitialCenterLocationAtCenter', (WidgetTester tester) async { - await tester.binding.setSurfaceSize(const Size(800, 600)); - - final Completer mapControllerCompleter = - Completer(); - 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.delayed(const Duration(seconds: 1)); - - final ScreenCoordinate coordinate = - await mapController.getScreenCoordinate(_kInitialCameraPosition.target); - final Rect rect = tester.getRect(find.byKey(key)); - if (Platform.isIOS) { - // 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); - }); - - 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 mapControllerCompleter = - Completer(); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: 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( - 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('testTraffic', (WidgetTester tester) async { - final Key key = GlobalKey(); - final Completer mapIdCompleter = Completer(); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: GoogleMap( - key: key, - initialCameraPosition: _kInitialCameraPosition, - trafficEnabled: true, - onMapCreated: (GoogleMapController controller) { - mapIdCompleter.complete(controller.mapId); - }, - ), - )); - - final int mapId = await mapIdCompleter.future; - final GoogleMapsInspectorPlatform inspector = - GoogleMapsInspectorPlatform.instance!; - bool isTrafficEnabled = await inspector.isTrafficEnabled(mapId: mapId); - expect(isTrafficEnabled, true); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: 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 mapIdCompleter = Completer(); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: GoogleMap( - key: key, - initialCameraPosition: _kInitialCameraPosition, - onMapCreated: (GoogleMapController controller) { - mapIdCompleter.complete(controller.mapId); - }, - ), - )); - - final int mapId = await mapIdCompleter.future; - final GoogleMapsInspectorPlatform inspector = - GoogleMapsInspectorPlatform.instance!; - final bool isBuildingsEnabled = - await inspector.areBuildingsEnabled(mapId: mapId); - expect(isBuildingsEnabled, true); - }); - - // Location button tests are skipped in Android because we don't have location permission to test. - testWidgets('testMyLocationButtonToggle', (WidgetTester tester) async { - final Key key = GlobalKey(); - final Completer mapIdCompleter = Completer(); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: GoogleMap( - key: key, - initialCameraPosition: _kInitialCameraPosition, - onMapCreated: (GoogleMapController controller) { - mapIdCompleter.complete(controller.mapId); - }, - ), - )); - - final int mapId = await mapIdCompleter.future; - final GoogleMapsInspectorPlatform inspector = - GoogleMapsInspectorPlatform.instance!; - bool myLocationButtonEnabled = - await inspector.isMyLocationButtonEnabled(mapId: mapId); - expect(myLocationButtonEnabled, true); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: 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); - }, skip: Platform.isAndroid); - - testWidgets('testMyLocationButton initial value false', - (WidgetTester tester) async { - final Key key = GlobalKey(); - final Completer mapIdCompleter = Completer(); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: GoogleMap( - key: key, - initialCameraPosition: _kInitialCameraPosition, - myLocationButtonEnabled: false, - onMapCreated: (GoogleMapController controller) { - mapIdCompleter.complete(controller.mapId); - }, - ), - )); - - final int mapId = await mapIdCompleter.future; - final GoogleMapsInspectorPlatform inspector = - GoogleMapsInspectorPlatform.instance!; - final bool myLocationButtonEnabled = - await inspector.isMyLocationButtonEnabled(mapId: mapId); - expect(myLocationButtonEnabled, false); - }, skip: Platform.isAndroid); - - testWidgets('testMyLocationButton initial value true', - (WidgetTester tester) async { - final Key key = GlobalKey(); - final Completer mapIdCompleter = Completer(); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: GoogleMap( - key: key, - initialCameraPosition: _kInitialCameraPosition, - onMapCreated: (GoogleMapController controller) { - mapIdCompleter.complete(controller.mapId); - }, - ), - )); - - final int mapId = await mapIdCompleter.future; - final GoogleMapsInspectorPlatform inspector = - GoogleMapsInspectorPlatform.instance!; - final bool myLocationButtonEnabled = - await inspector.isMyLocationButtonEnabled(mapId: mapId); - expect(myLocationButtonEnabled, true); - }, skip: Platform.isAndroid); - - testWidgets('testSetMapStyle valid Json String', (WidgetTester tester) async { - final Key key = GlobalKey(); - final Completer controllerCompleter = - Completer(); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: 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 controllerCompleter = - Completer(); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: 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 controllerCompleter = - Completer(); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: 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 controllerCompleter = - Completer(); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: 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.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 controllerCompleter = - Completer(); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: 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.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 controllerCompleter = - Completer(); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: 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.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 controllerCompleter = - Completer(); - final GoogleMap map = GoogleMap( - initialCameraPosition: _kInitialCameraPosition, - onMapCreated: (GoogleMapController controller) async { - controllerCompleter.complete(controller); - }, - ); - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: MaterialApp( - home: Scaffold( - body: SizedBox(height: 100, width: 100, child: map))))); - final GoogleMapController controller = await controllerCompleter.future; - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: MaterialApp( - home: Scaffold( - body: SizedBox(height: 400, width: 400, child: map))))); - - 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.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 markers = {marker}; - - final Completer controllerCompleter = - Completer(); - - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: 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( - 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('fromAssetImage', (WidgetTester tester) async { - const double pixelRatio = 2; - const ImageConfiguration imageConfiguration = - ImageConfiguration(devicePixelRatio: pixelRatio); - 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)[2], 1); - expect((scaled.toJson() as List)[2], 2); - }); - - testWidgets('testTakeSnapshot', (WidgetTester tester) async { - final Completer controllerCompleter = - Completer(); - - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: 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: Platform.isAndroid); - - testWidgets( - 'set tileOverlay correctly', - (WidgetTester tester) async { - final Completer mapIdCompleter = Completer(); - 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: {tileOverlay1, tileOverlay2}, - onMapCreated: (GoogleMapController controller) { - mapIdCompleter.complete(controller.mapId); - }, - ), - ), - ); - await tester.pumpAndSettle(const Duration(seconds: 3)); - - final int mapId = await mapIdCompleter.future; - final GoogleMapsInspectorPlatform inspector = - GoogleMapsInspectorPlatform.instance!; - - 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 mapIdCompleter = Completer(); - 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: {tileOverlay1, tileOverlay2}, - onMapCreated: (GoogleMapController controller) { - mapIdCompleter.complete(controller.mapId); - }, - ), - ), - ); - - final int mapId = await mapIdCompleter.future; - final GoogleMapsInspectorPlatform inspector = - GoogleMapsInspectorPlatform.instance!; - - 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: {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 mapIdCompleter = Completer(); - 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: {tileOverlay1}, - onMapCreated: (GoogleMapController controller) { - mapIdCompleter.complete(controller.mapId); - }, - ), - ), - ); - - final int mapId = await mapIdCompleter.future; - final GoogleMapsInspectorPlatform inspector = - GoogleMapsInspectorPlatform.instance!; - - 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); - }, - ); -} - -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 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); - } + maps_controller.runTests(); + maps_inspector.runTests(); + tiles_inspector.runTests(); } diff --git a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_controller.dart b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_controller.dart new file mode 100644 index 0000000000..7b056b0d37 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_controller.dart @@ -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 mapControllerCompleter = + Completer(); + 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.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 mapControllerCompleter = + Completer(); + + 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( + 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 controllerCompleter = + Completer(); + + 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 controllerCompleter = + Completer(); + + 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 controllerCompleter = + Completer(); + + 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 controllerCompleter = + Completer(); + + 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.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 controllerCompleter = + Completer(); + + 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.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 controllerCompleter = + Completer(); + + 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.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 controllerCompleter = + Completer(); + + 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.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 markers = {marker}; + + final Completer controllerCompleter = + Completer(); + + 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( + 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 controllerCompleter = + Completer(); + + 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 waitForValueMatchingPredicate(WidgetTester tester, + Future 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; +} diff --git a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_inspector.dart b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_inspector.dart new file mode 100644 index 0000000000..efa4baaea6 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/maps_inspector.dart @@ -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 mapIdCompleter = Completer(); + 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 mapIdCompleter = Completer(); + + 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 controllerCompleter = + Completer(); + + 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 mapIdCompleter = Completer(); + + 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 mapIdCompleter = Completer(); + + 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 mapIdCompleter = Completer(); + + 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 mapIdCompleter = Completer(); + + 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 mapIdCompleter = Completer(); + + 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 mapIdCompleter = Completer(); + + 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 mapIdCompleter = Completer(); + + 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 mapIdCompleter = Completer(); + + 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 mapIdCompleter = Completer(); + + 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 mapIdCompleter = Completer(); + + 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 mapIdCompleter = Completer(); + + 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); +} diff --git a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/shared.dart b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/shared.dart new file mode 100644 index 0000000000..013565a2fd --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/shared.dart @@ -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 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, + ), + ), + ), + ); +} diff --git a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/tiles_inspector.dart b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/tiles_inspector.dart new file mode 100644 index 0000000000..9c9914d893 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/src/tiles_inspector.dart @@ -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 mapIdCompleter = Completer(); + 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: {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 mapIdCompleter = Completer(); + 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: {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: {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 mapIdCompleter = Completer(); + 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: {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 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); + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter/example/run_test.sh b/packages/google_maps_flutter/google_maps_flutter/example/run_test.sh new file mode 100755 index 0000000000..aa52974f31 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/example/run_test.sh @@ -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 + diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/bitmap_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/bitmap_test.dart index 2499e87bb6..469f9820ef 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/bitmap_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/bitmap_test.dart @@ -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() { ['fromAssetImage', 'some/path.png', 1.0]), isA()); }); + + 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)[2], 1); + expect((scaled.toJson() as List)[2], 3); + }); + test('name cannot be null or empty', () { expect(() { BitmapDescriptor.fromJson(['fromAssetImage', null, 1.0]); diff --git a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md index b42ce52c3d..e5ce58486c 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md @@ -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. diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart index db4130f58b..4bc5f365d2 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_controller_test.mocks.dart @@ -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' diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart index 9bd1a68c62..c773596d9b 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.dart @@ -197,6 +197,15 @@ void main() { expect(style.stylers?.length, 1); expect(getProperty(style.stylers![0]!, 'color'), '#6b9a76'); }); + + testWidgets('throws MapStyleException for invalid styles', + (WidgetTester tester) async { + plugin.debugSetMapById({0: controller}); + + expect(() async { + await plugin.setMapStyle('invalid_style', mapId: 0); + }, throwsA(isA())); + }); }); group('Noop methods:', () { diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart index f00ad0a6e5..36e6052a21 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/google_maps_plugin_test.mocks.dart @@ -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 extends _i1.SmartFake - implements _i2.StreamController { - _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 extends _i1.SmartFake ); } -class _FakeLatLngBounds_1 extends _i1.SmartFake implements _i3.LatLngBounds { - _FakeLatLngBounds_1( +class _FakeStreamController_1 extends _i1.SmartFake + implements _i3.StreamController { + _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> 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> get stream => (super.noSuchMethod( Invocation.getter(#stream), - returnValue: _FakeStreamController_0<_i3.MapEvent>( + returnValue: _FakeStreamController_1<_i2.MapEvent>( this, Invocation.getter(#stream), ), returnValueForMissingStub: - _FakeStreamController_0<_i3.MapEvent>( + _FakeStreamController_1<_i2.MapEvent>( this, Invocation.getter(#stream), ), - ) as _i2.StreamController<_i3.MapEvent>); + ) as _i3.StreamController<_i2.MapEvent>); @override - _i2.Stream<_i3.MapEvent> get events => (super.noSuchMethod( + _i3.Stream<_i2.MapEvent> get events => (super.noSuchMethod( Invocation.getter(#events), - returnValue: _i2.Stream<_i3.MapEvent>.empty(), - returnValueForMissingStub: _i2.Stream<_i3.MapEvent>.empty(), - ) as _i2.Stream<_i3.MapEvent>); + returnValue: _i3.Stream<_i2.MapEvent>.empty(), + returnValueForMissingStub: _i3.Stream<_i2.MapEvent>.empty(), + ) as _i3.Stream<_i2.MapEvent>); @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 moveCamera(_i3.CameraUpdate? cameraUpdate) => + _i3.Future moveCamera(_i2.CameraUpdate? cameraUpdate) => (super.noSuchMethod( Invocation.method( #moveCamera, [cameraUpdate], ), - returnValue: _i2.Future.value(), - returnValueForMissingStub: _i2.Future.value(), - ) as _i2.Future); + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); @override - _i2.Future getZoomLevel() => (super.noSuchMethod( + _i3.Future getZoomLevel() => (super.noSuchMethod( Invocation.method( #getZoomLevel, [], ), - returnValue: _i2.Future.value(0.0), - returnValueForMissingStub: _i2.Future.value(0.0), - ) as _i2.Future); + returnValue: _i3.Future.value(0.0), + returnValueForMissingStub: _i3.Future.value(0.0), + ) as _i3.Future); @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], diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml index aef98f1744..fb4c691d2f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/example/pubspec.yaml @@ -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 diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart index 0650184a14..df0b8de4c4 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/google_maps_flutter_web.dart @@ -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'; diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart index 3840ef21cc..fb656a135f 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart @@ -126,25 +126,30 @@ bool _isJsonMapStyle(Map value) { List _mapStyles(String? mapStyleJson) { List styles = []; if (mapStyleJson != null) { - styles = (json.decode(mapStyleJson, reviver: (Object? key, Object? value) { - if (value is Map && _isJsonMapStyle(value as Map)) { - List stylers = []; - if (value['stylers'] != null) { - stylers = (value['stylers']! as List) - .map((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)) { + List stylers = []; + if (value['stylers'] != null) { + stylers = (value['stylers']! as List) + .map((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) - .where((Object? element) => element != null) - .cast() - .toList(); - // .toList calls are required so the JS API understands the underlying data structure. + return value; + }) as List) + .where((Object? element) => element != null) + .cast() + .toList(); + // .toList calls are required so the JS API understands the underlying data structure. + } on FormatException catch (e) { + throw MapStyleException(e.message); + } } return styles; } diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart index a659fb2188..41342bf5cf 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_controller.dart @@ -53,11 +53,19 @@ class GoogleMapController { final Set _polygons; final Set _polylines; final Set _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 _lastStyles = const []; + /// 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'; diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart index b64f99501f..049a6a25de 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart @@ -26,7 +26,7 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { // Convenience getter for a stream of events filtered by their mapId. Stream> _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, + ); + } } diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_inspector_web.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_inspector_web.dart new file mode 100644 index 0000000000..6d95531523 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_inspector_web.dart @@ -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 areBuildingsEnabled({required int mapId}) async { + return false; // Not supported on the web + } + + @override + Future areRotateGesturesEnabled({required int mapId}) async { + return false; // Not supported on the web + } + + @override + Future areScrollGesturesEnabled({required int mapId}) async { + return _configurationProvider(mapId).scrollGesturesEnabled ?? false; + } + + @override + Future areTiltGesturesEnabled({required int mapId}) async { + return false; // Not supported on the web + } + + @override + Future areZoomControlsEnabled({required int mapId}) async { + return _configurationProvider(mapId).zoomControlsEnabled ?? false; + } + + @override + Future areZoomGesturesEnabled({required int mapId}) async { + return _configurationProvider(mapId).zoomGesturesEnabled ?? false; + } + + @override + Future getMinMaxZoomLevels({required int mapId}) async { + final MapConfiguration config = _configurationProvider(mapId); + assert(config.minMaxZoomPreference != null); + + return config.minMaxZoomPreference!; + } + + @override + Future getTileOverlayInfo( + TileOverlayId tileOverlayId, { + required int mapId, + }) async { + return null; // Custom tiles not supported on the web + } + + @override + Future isCompassEnabled({required int mapId}) async { + return false; // There's no compass on the web + } + + @override + Future isLiteModeEnabled({required int mapId}) async { + return false; // There's no lite mode on the web + } + + @override + Future isMapToolbarEnabled({required int mapId}) async { + return false; // There's no Map Toolbar on the web + } + + @override + Future isMyLocationButtonEnabled({required int mapId}) async { + return false; // My Location widget not supported on the web + } + + @override + Future isTrafficEnabled({required int mapId}) async { + return _configurationProvider(mapId).trafficEnabled ?? false; + } +} diff --git a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml index c88b2aefab..c72d96eeaa 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml @@ -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"