From 754405dc0707e0181c700947b4595f7f54b62440 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Fri, 16 Jun 2023 08:43:21 -0400 Subject: [PATCH] [google_map_flutter] Fix map object regression due to async changes (#4171) In #4067 I changed several implicitly unawaited futures to `await`, not noticing that doing so would change the timing on updating fields that were used to compute diffs for future updates. Since the update flow is unawaited at higher levels, this meant that multiple calls to update map objects in rapid succession would compute incorrect diffs due to using the wrong base objects. This fixes that by restoring the old flow using `unawaited`. Adds new tests that introduce synthetic awaits into the fake platform interface object and perform multiple updates in a row that should have different base object sets, which fail without the fix. In order to simplify adding that behavior to the fake, and to pay down technical debt in the unit tests, I replaced the old test fake that is based on method channel interception (which predates the federation of the plugin and is implicitly relying on the method channel implementation from a different package never changing) with a fake platform interface implementation (substantially simplifying the fake). Fixes https://github.com/flutter/flutter/issues/128042 --- .../google_maps_flutter/CHANGELOG.md | 4 +- .../lib/src/google_map.dart | 20 +- .../google_maps_flutter/pubspec.yaml | 2 +- .../test/circle_updates_test.dart | 152 +++--- .../fake_google_maps_flutter_platform.dart | 303 +++++++++++ .../test/fake_maps_controllers.dart | 485 ------------------ .../test/google_map_test.dart | 160 +++--- .../test/map_creation_test.dart | 229 +-------- .../test/marker_updates_test.dart | 153 +++--- .../test/polygon_updates_test.dart | 252 ++++----- .../test/polyline_updates_test.dart | 170 +++--- .../test/tile_overlay_updates_test.dart | 161 +----- .../example/pubspec.yaml | 7 + 13 files changed, 783 insertions(+), 1315 deletions(-) create mode 100644 packages/google_maps_flutter/google_maps_flutter/test/fake_google_maps_flutter_platform.dart delete mode 100644 packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md index e244734a43..6edda3e080 100644 --- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md @@ -1,5 +1,7 @@ -## NEXT +## 2.3.1 +* Fixes a regression from 2.2.8 that could cause incorrect handling of a + rapid series of map object updates. * Fixes stale ignore: prefer_const_constructors. * Updates minimum supported SDK version to Flutter 3.10/Dart 3.0. diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart index e1b710c307..08c2286527 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart @@ -360,41 +360,41 @@ class _GoogleMapState extends State { return; } final GoogleMapController controller = await _controller.future; - await controller._updateMapConfiguration(updates); + unawaited(controller._updateMapConfiguration(updates)); _mapConfiguration = newConfig; } Future _updateMarkers() async { final GoogleMapController controller = await _controller.future; - await controller._updateMarkers( - MarkerUpdates.from(_markers.values.toSet(), widget.markers)); + unawaited(controller._updateMarkers( + MarkerUpdates.from(_markers.values.toSet(), widget.markers))); _markers = keyByMarkerId(widget.markers); } Future _updatePolygons() async { final GoogleMapController controller = await _controller.future; - await controller._updatePolygons( - PolygonUpdates.from(_polygons.values.toSet(), widget.polygons)); + unawaited(controller._updatePolygons( + PolygonUpdates.from(_polygons.values.toSet(), widget.polygons))); _polygons = keyByPolygonId(widget.polygons); } Future _updatePolylines() async { final GoogleMapController controller = await _controller.future; - await controller._updatePolylines( - PolylineUpdates.from(_polylines.values.toSet(), widget.polylines)); + unawaited(controller._updatePolylines( + PolylineUpdates.from(_polylines.values.toSet(), widget.polylines))); _polylines = keyByPolylineId(widget.polylines); } Future _updateCircles() async { final GoogleMapController controller = await _controller.future; - await controller._updateCircles( - CircleUpdates.from(_circles.values.toSet(), widget.circles)); + unawaited(controller._updateCircles( + CircleUpdates.from(_circles.values.toSet(), widget.circles))); _circles = keyByCircleId(widget.circles); } Future _updateTileOverlays() async { final GoogleMapController controller = await _controller.future; - await controller._updateTileOverlays(widget.tileOverlays); + unawaited(controller._updateTileOverlays(widget.tileOverlays)); } Future onPlatformViewCreated(int id) async { diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml index 91bc476fda..e56ff513e7 100644 --- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter description: A Flutter plugin for integrating Google Maps in iOS and Android applications. repository: https://github.com/flutter/packages/tree/main/packages/google_maps_flutter/google_maps_flutter issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 2.3.0 +version: 2.3.1 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/packages/google_maps_flutter/google_maps_flutter/test/circle_updates_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/circle_updates_test.dart index 459e16b60c..f94caf1f58 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/circle_updates_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/circle_updates_test.dart @@ -2,12 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/services.dart'; import 'package:flutter/widgets.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 'fake_maps_controllers.dart'; +import 'fake_google_maps_flutter_platform.dart'; Widget _mapWithCircles(Set circles) { return Directionality( @@ -20,36 +20,24 @@ Widget _mapWithCircles(Set circles) { } void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - final FakePlatformViewsController fakePlatformViewsController = - FakePlatformViewsController(); - - setUpAll(() { - _ambiguate(TestDefaultBinaryMessengerBinding.instance)! - .defaultBinaryMessenger - .setMockMethodCallHandler( - SystemChannels.platform_views, - fakePlatformViewsController.fakePlatformViewsMethodHandler, - ); - }); + late FakeGoogleMapsFlutterPlatform platform; setUp(() { - fakePlatformViewsController.reset(); + platform = FakeGoogleMapsFlutterPlatform(); + GoogleMapsFlutterPlatform.instance = platform; }); testWidgets('Initializing a circle', (WidgetTester tester) async { const Circle c1 = Circle(circleId: CircleId('circle_1')); await tester.pumpWidget(_mapWithCircles({c1})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.circlesToAdd.length, 1); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.circleUpdates.last.circlesToAdd.length, 1); - final Circle initializedCircle = platformGoogleMap.circlesToAdd.first; + final Circle initializedCircle = map.circleUpdates.last.circlesToAdd.first; expect(initializedCircle, equals(c1)); - expect(platformGoogleMap.circleIdsToRemove.isEmpty, true); - expect(platformGoogleMap.circlesToChange.isEmpty, true); + expect(map.circleUpdates.last.circleIdsToRemove.isEmpty, true); + expect(map.circleUpdates.last.circlesToChange.isEmpty, true); }); testWidgets('Adding a circle', (WidgetTester tester) async { @@ -59,16 +47,15 @@ void main() { await tester.pumpWidget(_mapWithCircles({c1})); await tester.pumpWidget(_mapWithCircles({c1, c2})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.circlesToAdd.length, 1); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.circleUpdates.last.circlesToAdd.length, 1); - final Circle addedCircle = platformGoogleMap.circlesToAdd.first; + final Circle addedCircle = map.circleUpdates.last.circlesToAdd.first; expect(addedCircle, equals(c2)); - expect(platformGoogleMap.circleIdsToRemove.isEmpty, true); + expect(map.circleUpdates.last.circleIdsToRemove.isEmpty, true); - expect(platformGoogleMap.circlesToChange.isEmpty, true); + expect(map.circleUpdates.last.circlesToChange.isEmpty, true); }); testWidgets('Removing a circle', (WidgetTester tester) async { @@ -77,13 +64,12 @@ void main() { await tester.pumpWidget(_mapWithCircles({c1})); await tester.pumpWidget(_mapWithCircles({})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.circleIdsToRemove.length, 1); - expect(platformGoogleMap.circleIdsToRemove.first, equals(c1.circleId)); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.circleUpdates.last.circleIdsToRemove.length, 1); + expect(map.circleUpdates.last.circleIdsToRemove.first, equals(c1.circleId)); - expect(platformGoogleMap.circlesToChange.isEmpty, true); - expect(platformGoogleMap.circlesToAdd.isEmpty, true); + expect(map.circleUpdates.last.circlesToChange.isEmpty, true); + expect(map.circleUpdates.last.circlesToAdd.isEmpty, true); }); testWidgets('Updating a circle', (WidgetTester tester) async { @@ -93,13 +79,12 @@ void main() { await tester.pumpWidget(_mapWithCircles({c1})); await tester.pumpWidget(_mapWithCircles({c2})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.circlesToChange.length, 1); - expect(platformGoogleMap.circlesToChange.first, equals(c2)); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.circleUpdates.last.circlesToChange.length, 1); + expect(map.circleUpdates.last.circlesToChange.first, equals(c2)); - expect(platformGoogleMap.circleIdsToRemove.isEmpty, true); - expect(platformGoogleMap.circlesToAdd.isEmpty, true); + expect(map.circleUpdates.last.circleIdsToRemove.isEmpty, true); + expect(map.circleUpdates.last.circlesToAdd.isEmpty, true); }); testWidgets('Updating a circle', (WidgetTester tester) async { @@ -109,11 +94,10 @@ void main() { await tester.pumpWidget(_mapWithCircles({c1})); await tester.pumpWidget(_mapWithCircles({c2})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.circlesToChange.length, 1); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.circleUpdates.last.circlesToChange.length, 1); - final Circle update = platformGoogleMap.circlesToChange.first; + final Circle update = map.circleUpdates.last.circlesToChange.first; expect(update, equals(c2)); expect(update.radius, 10); }); @@ -129,12 +113,11 @@ void main() { await tester.pumpWidget(_mapWithCircles(prev)); await tester.pumpWidget(_mapWithCircles(cur)); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.circlesToChange, cur); - expect(platformGoogleMap.circleIdsToRemove.isEmpty, true); - expect(platformGoogleMap.circlesToAdd.isEmpty, true); + expect(map.circleUpdates.last.circlesToChange, cur); + expect(map.circleUpdates.last.circleIdsToRemove.isEmpty, true); + expect(map.circleUpdates.last.circlesToAdd.isEmpty, true); }); testWidgets('Multi Update', (WidgetTester tester) async { @@ -150,16 +133,15 @@ void main() { await tester.pumpWidget(_mapWithCircles(prev)); await tester.pumpWidget(_mapWithCircles(cur)); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.circlesToChange.length, 1); - expect(platformGoogleMap.circlesToAdd.length, 1); - expect(platformGoogleMap.circleIdsToRemove.length, 1); + expect(map.circleUpdates.last.circlesToChange.length, 1); + expect(map.circleUpdates.last.circlesToAdd.length, 1); + expect(map.circleUpdates.last.circleIdsToRemove.length, 1); - expect(platformGoogleMap.circlesToChange.first, equals(c2)); - expect(platformGoogleMap.circlesToAdd.first, equals(c1)); - expect(platformGoogleMap.circleIdsToRemove.first, equals(c3.circleId)); + expect(map.circleUpdates.last.circlesToChange.first, equals(c2)); + expect(map.circleUpdates.last.circlesToAdd.first, equals(c1)); + expect(map.circleUpdates.last.circleIdsToRemove.first, equals(c3.circleId)); }); testWidgets('Partial Update', (WidgetTester tester) async { @@ -173,12 +155,11 @@ void main() { await tester.pumpWidget(_mapWithCircles(prev)); await tester.pumpWidget(_mapWithCircles(cur)); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.circlesToChange, {c3}); - expect(platformGoogleMap.circleIdsToRemove.isEmpty, true); - expect(platformGoogleMap.circlesToAdd.isEmpty, true); + expect(map.circleUpdates.last.circlesToChange, {c3}); + expect(map.circleUpdates.last.circleIdsToRemove.isEmpty, true); + expect(map.circleUpdates.last.circlesToAdd.isEmpty, true); }); testWidgets('Update non platform related attr', (WidgetTester tester) async { @@ -190,17 +171,42 @@ void main() { await tester.pumpWidget(_mapWithCircles(prev)); await tester.pumpWidget(_mapWithCircles(cur)); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.circlesToChange.isEmpty, true); - expect(platformGoogleMap.circleIdsToRemove.isEmpty, true); - expect(platformGoogleMap.circlesToAdd.isEmpty, true); + expect(map.circleUpdates.last.circlesToChange.isEmpty, true); + expect(map.circleUpdates.last.circleIdsToRemove.isEmpty, true); + expect(map.circleUpdates.last.circlesToAdd.isEmpty, true); + }); + + testWidgets('multi-update with delays', (WidgetTester tester) async { + platform.simulatePlatformDelay = true; + + const Circle c1 = Circle(circleId: CircleId('circle_1')); + const Circle c2 = Circle(circleId: CircleId('circle_2')); + const Circle c3 = Circle(circleId: CircleId('circle_3'), radius: 1); + const Circle c3updated = Circle(circleId: CircleId('circle_3'), radius: 10); + + // First remove one and add another, then update the new one. + await tester.pumpWidget(_mapWithCircles({c1, c2})); + await tester.pumpWidget(_mapWithCircles({c1, c3})); + await tester.pumpWidget(_mapWithCircles({c1, c3updated})); + + final PlatformMapStateRecorder map = platform.lastCreatedMap; + + expect(map.circleUpdates.length, 3); + + expect(map.circleUpdates[0].circlesToChange.isEmpty, true); + expect(map.circleUpdates[0].circlesToAdd, {c1, c2}); + expect(map.circleUpdates[0].circleIdsToRemove.isEmpty, true); + + expect(map.circleUpdates[1].circlesToChange.isEmpty, true); + expect(map.circleUpdates[1].circlesToAdd, {c3}); + expect(map.circleUpdates[1].circleIdsToRemove, {c2.circleId}); + + expect(map.circleUpdates[2].circlesToChange, {c3updated}); + expect(map.circleUpdates[2].circlesToAdd.isEmpty, true); + expect(map.circleUpdates[2].circleIdsToRemove.isEmpty, true); + + await tester.pumpAndSettle(); }); } - -/// This allows a value of type T or T? to be treated as a value of type T?. -/// -/// We use this so that APIs that have become non-nullable can still be used -/// with `!` and `?` on the stable branch. -T? _ambiguate(T? value) => value; diff --git a/packages/google_maps_flutter/google_maps_flutter/test/fake_google_maps_flutter_platform.dart b/packages/google_maps_flutter/google_maps_flutter/test/fake_google_maps_flutter_platform.dart new file mode 100644 index 0000000000..22447ba5ec --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/test/fake_google_maps_flutter_platform.dart @@ -0,0 +1,303 @@ +// 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/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; +import 'package:stream_transform/stream_transform.dart'; + +// A dummy implementation of the platform interface for tests. +class FakeGoogleMapsFlutterPlatform extends GoogleMapsFlutterPlatform { + FakeGoogleMapsFlutterPlatform(); + + /// The IDs passed to each call to buildView, in call order. + List createdIds = []; + + /// A map of creation IDs to fake map instances. + Map mapInstances = + {}; + + PlatformMapStateRecorder get lastCreatedMap => mapInstances[createdIds.last]!; + + /// Whether to add a small delay to async calls to simulate more realistic + /// async behavior (simulating the platform channel calls most + /// implementations will do). + /// + /// When true, requires tests to `pumpAndSettle` at the end of the test + /// to avoid exceptions. + bool simulatePlatformDelay = false; + + /// Whether `dispose` has been called. + bool disposed = false; + + /// Stream controller to inject events for testing. + final StreamController> mapEventStreamController = + StreamController>.broadcast(); + + @override + Future init(int mapId) async {} + + @override + Future updateMapConfiguration( + MapConfiguration update, { + required int mapId, + }) async { + mapInstances[mapId]?.mapConfiguration = update; + await _fakeDelay(); + } + + @override + Future updateMarkers( + MarkerUpdates markerUpdates, { + required int mapId, + }) async { + mapInstances[mapId]?.markerUpdates.add(markerUpdates); + await _fakeDelay(); + } + + @override + Future updatePolygons( + PolygonUpdates polygonUpdates, { + required int mapId, + }) async { + mapInstances[mapId]?.polygonUpdates.add(polygonUpdates); + await _fakeDelay(); + } + + @override + Future updatePolylines( + PolylineUpdates polylineUpdates, { + required int mapId, + }) async { + mapInstances[mapId]?.polylineUpdates.add(polylineUpdates); + await _fakeDelay(); + } + + @override + Future updateCircles( + CircleUpdates circleUpdates, { + required int mapId, + }) async { + mapInstances[mapId]?.circleUpdates.add(circleUpdates); + await _fakeDelay(); + } + + @override + Future updateTileOverlays({ + required Set newTileOverlays, + required int mapId, + }) async { + mapInstances[mapId]?.tileOverlaySets.add(newTileOverlays); + await _fakeDelay(); + } + + @override + Future clearTileCache( + TileOverlayId tileOverlayId, { + required int mapId, + }) async {} + + @override + Future animateCamera( + CameraUpdate cameraUpdate, { + required int mapId, + }) async {} + + @override + Future moveCamera( + CameraUpdate cameraUpdate, { + required int mapId, + }) async {} + + @override + Future setMapStyle( + String? mapStyle, { + required int mapId, + }) async {} + + @override + Future getVisibleRegion({ + required int mapId, + }) async { + return LatLngBounds( + southwest: const LatLng(0, 0), northeast: const LatLng(0, 0)); + } + + @override + Future getScreenCoordinate( + LatLng latLng, { + required int mapId, + }) async { + return const ScreenCoordinate(x: 0, y: 0); + } + + @override + Future getLatLng( + ScreenCoordinate screenCoordinate, { + required int mapId, + }) async { + return const LatLng(0, 0); + } + + @override + Future showMarkerInfoWindow( + MarkerId markerId, { + required int mapId, + }) async {} + + @override + Future hideMarkerInfoWindow( + MarkerId markerId, { + required int mapId, + }) async {} + + @override + Future isMarkerInfoWindowShown( + MarkerId markerId, { + required int mapId, + }) async { + return false; + } + + @override + Future getZoomLevel({ + required int mapId, + }) async { + return 0.0; + } + + @override + Future takeSnapshot({ + required int mapId, + }) async { + return null; + } + + @override + Stream onCameraMoveStarted({required int mapId}) { + return mapEventStreamController.stream.whereType(); + } + + @override + Stream onCameraMove({required int mapId}) { + return mapEventStreamController.stream.whereType(); + } + + @override + Stream onCameraIdle({required int mapId}) { + return mapEventStreamController.stream.whereType(); + } + + @override + Stream onMarkerTap({required int mapId}) { + return mapEventStreamController.stream.whereType(); + } + + @override + Stream onInfoWindowTap({required int mapId}) { + return mapEventStreamController.stream.whereType(); + } + + @override + Stream onMarkerDragStart({required int mapId}) { + return mapEventStreamController.stream.whereType(); + } + + @override + Stream onMarkerDrag({required int mapId}) { + return mapEventStreamController.stream.whereType(); + } + + @override + Stream onMarkerDragEnd({required int mapId}) { + return mapEventStreamController.stream.whereType(); + } + + @override + Stream onPolylineTap({required int mapId}) { + return mapEventStreamController.stream.whereType(); + } + + @override + Stream onPolygonTap({required int mapId}) { + return mapEventStreamController.stream.whereType(); + } + + @override + Stream onCircleTap({required int mapId}) { + return mapEventStreamController.stream.whereType(); + } + + @override + Stream onTap({required int mapId}) { + return mapEventStreamController.stream.whereType(); + } + + @override + Stream onLongPress({required int mapId}) { + return mapEventStreamController.stream.whereType(); + } + + @override + void dispose({required int mapId}) { + disposed = true; + } + + @override + Widget buildViewWithConfiguration( + int creationId, + PlatformViewCreatedCallback onPlatformViewCreated, { + required MapWidgetConfiguration widgetConfiguration, + MapObjects mapObjects = const MapObjects(), + MapConfiguration mapConfiguration = const MapConfiguration(), + }) { + final PlatformMapStateRecorder? instance = mapInstances[creationId]; + if (instance == null) { + createdIds.add(creationId); + mapInstances[creationId] = PlatformMapStateRecorder( + widgetConfiguration: widgetConfiguration, + mapConfiguration: mapConfiguration, + mapObjects: mapObjects); + onPlatformViewCreated(creationId); + } + return Container(); + } + + Future _fakeDelay() async { + if (!simulatePlatformDelay) { + return; + } + return Future.delayed(const Duration(microseconds: 1)); + } +} + +/// A fake implementation of a native map, which stores all the updates it is +/// sent for inspection in tests. +class PlatformMapStateRecorder { + PlatformMapStateRecorder({ + required this.widgetConfiguration, + this.mapObjects = const MapObjects(), + this.mapConfiguration = const MapConfiguration(), + }) { + markerUpdates.add(MarkerUpdates.from(const {}, mapObjects.markers)); + polygonUpdates + .add(PolygonUpdates.from(const {}, mapObjects.polygons)); + polylineUpdates + .add(PolylineUpdates.from(const {}, mapObjects.polylines)); + circleUpdates.add(CircleUpdates.from(const {}, mapObjects.circles)); + tileOverlaySets.add(mapObjects.tileOverlays); + } + + MapWidgetConfiguration widgetConfiguration; + MapObjects mapObjects; + MapConfiguration mapConfiguration; + + final List markerUpdates = []; + final List polygonUpdates = []; + final List polylineUpdates = []; + final List circleUpdates = []; + final List> tileOverlaySets = >[]; +} diff --git a/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart b/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart deleted file mode 100644 index c28ff1f4f5..0000000000 --- a/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart +++ /dev/null @@ -1,485 +0,0 @@ -// 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:typed_data'; - -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:google_maps_flutter/google_maps_flutter.dart'; - -class FakePlatformGoogleMap { - FakePlatformGoogleMap(int id, Map params) - : cameraPosition = - CameraPosition.fromMap(params['initialCameraPosition']), - channel = MethodChannel('plugins.flutter.io/google_maps_$id') { - _ambiguate(TestDefaultBinaryMessengerBinding.instance)! - .defaultBinaryMessenger - .setMockMethodCallHandler(channel, onMethodCall); - updateOptions(params['options'] as Map); - updateMarkers(params); - updatePolygons(params); - updatePolylines(params); - updateCircles(params); - updateTileOverlays(Map.castFrom(params)); - } - - MethodChannel channel; - - CameraPosition? cameraPosition; - - bool? compassEnabled; - - bool? mapToolbarEnabled; - - CameraTargetBounds? cameraTargetBounds; - - MapType? mapType; - - MinMaxZoomPreference? minMaxZoomPreference; - - bool? rotateGesturesEnabled; - - bool? scrollGesturesEnabled; - - bool? tiltGesturesEnabled; - - bool? zoomGesturesEnabled; - - bool? zoomControlsEnabled; - - bool? liteModeEnabled; - - bool? trackCameraPosition; - - bool? myLocationEnabled; - - bool? trafficEnabled; - - bool? buildingsEnabled; - - bool? myLocationButtonEnabled; - - List? padding; - - Set markerIdsToRemove = {}; - - Set markersToAdd = {}; - - Set markersToChange = {}; - - Set polygonIdsToRemove = {}; - - Set polygonsToAdd = {}; - - Set polygonsToChange = {}; - - Set polylineIdsToRemove = {}; - - Set polylinesToAdd = {}; - - Set polylinesToChange = {}; - - Set circleIdsToRemove = {}; - - Set circlesToAdd = {}; - - Set circlesToChange = {}; - - Set tileOverlayIdsToRemove = {}; - - Set tileOverlaysToAdd = {}; - - Set tileOverlaysToChange = {}; - - Future onMethodCall(MethodCall call) { - switch (call.method) { - case 'map#update': - final Map arguments = - (call.arguments as Map).cast(); - updateOptions(arguments['options']! as Map); - return Future.sync(() {}); - case 'markers#update': - updateMarkers(call.arguments as Map?); - return Future.sync(() {}); - case 'polygons#update': - updatePolygons(call.arguments as Map?); - return Future.sync(() {}); - case 'polylines#update': - updatePolylines(call.arguments as Map?); - return Future.sync(() {}); - case 'tileOverlays#update': - updateTileOverlays(Map.castFrom( - call.arguments as Map)); - return Future.sync(() {}); - case 'circles#update': - updateCircles(call.arguments as Map?); - return Future.sync(() {}); - default: - return Future.sync(() {}); - } - } - - void updateMarkers(Map? markerUpdates) { - if (markerUpdates == null) { - return; - } - markersToAdd = _deserializeMarkers(markerUpdates['markersToAdd']); - markerIdsToRemove = _deserializeMarkerIds( - markerUpdates['markerIdsToRemove'] as List?); - markersToChange = _deserializeMarkers(markerUpdates['markersToChange']); - } - - Set _deserializeMarkerIds(List? markerIds) { - if (markerIds == null) { - return {}; - } - return markerIds - .map((dynamic markerId) => MarkerId(markerId as String)) - .toSet(); - } - - Set _deserializeMarkers(dynamic markers) { - if (markers == null) { - return {}; - } - final List markersData = markers as List; - final Set result = {}; - for (final Map markerData - in markersData.cast>()) { - final String markerId = markerData['markerId'] as String; - final double alpha = markerData['alpha'] as double; - final bool draggable = markerData['draggable'] as bool; - final bool visible = markerData['visible'] as bool; - - final dynamic infoWindowData = markerData['infoWindow']; - InfoWindow infoWindow = InfoWindow.noText; - if (infoWindowData != null) { - final Map infoWindowMap = - infoWindowData as Map; - infoWindow = InfoWindow( - title: infoWindowMap['title'] as String?, - snippet: infoWindowMap['snippet'] as String?, - ); - } - - result.add(Marker( - markerId: MarkerId(markerId), - draggable: draggable, - visible: visible, - infoWindow: infoWindow, - alpha: alpha, - )); - } - - return result; - } - - void updatePolygons(Map? polygonUpdates) { - if (polygonUpdates == null) { - return; - } - polygonsToAdd = _deserializePolygons(polygonUpdates['polygonsToAdd']); - polygonIdsToRemove = _deserializePolygonIds( - polygonUpdates['polygonIdsToRemove'] as List?); - polygonsToChange = _deserializePolygons(polygonUpdates['polygonsToChange']); - } - - Set _deserializePolygonIds(List? polygonIds) { - if (polygonIds == null) { - return {}; - } - return polygonIds - .map((dynamic polygonId) => PolygonId(polygonId as String)) - .toSet(); - } - - Set _deserializePolygons(dynamic polygons) { - if (polygons == null) { - return {}; - } - final List polygonsData = polygons as List; - final Set result = {}; - for (final Map polygonData - in polygonsData.cast>()) { - final String polygonId = polygonData['polygonId'] as String; - final bool visible = polygonData['visible'] as bool; - final bool geodesic = polygonData['geodesic'] as bool; - final List points = - _deserializePoints(polygonData['points'] as List); - final List> holes = - _deserializeHoles(polygonData['holes'] as List); - - result.add(Polygon( - polygonId: PolygonId(polygonId), - visible: visible, - geodesic: geodesic, - points: points, - holes: holes, - )); - } - - return result; - } - - // Converts a list of points expressed as two-element lists of doubles into - // a list of `LatLng`s. All list items are assumed to be non-null. - List _deserializePoints(List points) { - return points.map((dynamic item) { - final List list = item as List; - return LatLng(list[0]! as double, list[1]! as double); - }).toList(); - } - - List> _deserializeHoles(List holes) { - return holes.map>((dynamic hole) { - return _deserializePoints(hole as List); - }).toList(); - } - - void updatePolylines(Map? polylineUpdates) { - if (polylineUpdates == null) { - return; - } - polylinesToAdd = _deserializePolylines(polylineUpdates['polylinesToAdd']); - polylineIdsToRemove = _deserializePolylineIds( - polylineUpdates['polylineIdsToRemove'] as List?); - polylinesToChange = - _deserializePolylines(polylineUpdates['polylinesToChange']); - } - - Set _deserializePolylineIds(List? polylineIds) { - if (polylineIds == null) { - return {}; - } - return polylineIds - .map((dynamic polylineId) => PolylineId(polylineId as String)) - .toSet(); - } - - Set _deserializePolylines(dynamic polylines) { - if (polylines == null) { - return {}; - } - final List polylinesData = polylines as List; - final Set result = {}; - for (final Map polylineData - in polylinesData.cast>()) { - final String polylineId = polylineData['polylineId'] as String; - final bool visible = polylineData['visible'] as bool; - final bool geodesic = polylineData['geodesic'] as bool; - final List points = - _deserializePoints(polylineData['points'] as List); - - result.add(Polyline( - polylineId: PolylineId(polylineId), - visible: visible, - geodesic: geodesic, - points: points, - )); - } - - return result; - } - - void updateCircles(Map? circleUpdates) { - if (circleUpdates == null) { - return; - } - circlesToAdd = _deserializeCircles(circleUpdates['circlesToAdd']); - circleIdsToRemove = _deserializeCircleIds( - circleUpdates['circleIdsToRemove'] as List?); - circlesToChange = _deserializeCircles(circleUpdates['circlesToChange']); - } - - void updateTileOverlays(Map updateTileOverlayUpdates) { - final List>? tileOverlaysToAddList = - updateTileOverlayUpdates['tileOverlaysToAdd'] != null - ? List.castFrom>( - updateTileOverlayUpdates['tileOverlaysToAdd'] as List) - : null; - final List? tileOverlayIdsToRemoveList = - updateTileOverlayUpdates['tileOverlayIdsToRemove'] != null - ? List.castFrom( - updateTileOverlayUpdates['tileOverlayIdsToRemove'] - as List) - : null; - final List>? tileOverlaysToChangeList = - updateTileOverlayUpdates['tileOverlaysToChange'] != null - ? List.castFrom>( - updateTileOverlayUpdates['tileOverlaysToChange'] - as List) - : null; - tileOverlaysToAdd = _deserializeTileOverlays(tileOverlaysToAddList); - tileOverlayIdsToRemove = - _deserializeTileOverlayIds(tileOverlayIdsToRemoveList); - tileOverlaysToChange = _deserializeTileOverlays(tileOverlaysToChangeList); - } - - Set _deserializeCircleIds(List? circleIds) { - if (circleIds == null) { - return {}; - } - return circleIds - .map((dynamic circleId) => CircleId(circleId as String)) - .toSet(); - } - - Set _deserializeCircles(dynamic circles) { - if (circles == null) { - return {}; - } - final List circlesData = circles as List; - final Set result = {}; - for (final Map circleData - in circlesData.cast>()) { - final String circleId = circleData['circleId'] as String; - final bool visible = circleData['visible'] as bool; - final double radius = circleData['radius'] as double; - - result.add(Circle( - circleId: CircleId(circleId), - visible: visible, - radius: radius, - )); - } - - return result; - } - - Set _deserializeTileOverlayIds(List? tileOverlayIds) { - if (tileOverlayIds == null || tileOverlayIds.isEmpty) { - return {}; - } - return tileOverlayIds - .map((String tileOverlayId) => TileOverlayId(tileOverlayId)) - .toSet(); - } - - Set _deserializeTileOverlays( - List>? tileOverlays) { - if (tileOverlays == null || tileOverlays.isEmpty) { - return {}; - } - final Set result = {}; - for (final Map tileOverlayData in tileOverlays) { - final String tileOverlayId = tileOverlayData['tileOverlayId'] as String; - final bool fadeIn = tileOverlayData['fadeIn'] as bool; - final double transparency = tileOverlayData['transparency'] as double; - final int zIndex = tileOverlayData['zIndex'] as int; - final bool visible = tileOverlayData['visible'] as bool; - - result.add(TileOverlay( - tileOverlayId: TileOverlayId(tileOverlayId), - fadeIn: fadeIn, - transparency: transparency, - zIndex: zIndex, - visible: visible, - )); - } - - return result; - } - - void updateOptions(Map options) { - if (options.containsKey('compassEnabled')) { - compassEnabled = options['compassEnabled'] as bool?; - } - if (options.containsKey('mapToolbarEnabled')) { - mapToolbarEnabled = options['mapToolbarEnabled'] as bool?; - } - if (options.containsKey('cameraTargetBounds')) { - final List boundsList = - options['cameraTargetBounds'] as List; - cameraTargetBounds = boundsList[0] == null - ? CameraTargetBounds.unbounded - : CameraTargetBounds(LatLngBounds.fromList(boundsList[0])); - } - if (options.containsKey('mapType')) { - mapType = MapType.values[options['mapType'] as int]; - } - if (options.containsKey('minMaxZoomPreference')) { - final List minMaxZoomList = - options['minMaxZoomPreference'] as List; - minMaxZoomPreference = MinMaxZoomPreference( - minMaxZoomList[0] as double?, minMaxZoomList[1] as double?); - } - if (options.containsKey('rotateGesturesEnabled')) { - rotateGesturesEnabled = options['rotateGesturesEnabled'] as bool?; - } - if (options.containsKey('scrollGesturesEnabled')) { - scrollGesturesEnabled = options['scrollGesturesEnabled'] as bool?; - } - if (options.containsKey('tiltGesturesEnabled')) { - tiltGesturesEnabled = options['tiltGesturesEnabled'] as bool?; - } - if (options.containsKey('trackCameraPosition')) { - trackCameraPosition = options['trackCameraPosition'] as bool?; - } - if (options.containsKey('zoomGesturesEnabled')) { - zoomGesturesEnabled = options['zoomGesturesEnabled'] as bool?; - } - if (options.containsKey('zoomControlsEnabled')) { - zoomControlsEnabled = options['zoomControlsEnabled'] as bool?; - } - if (options.containsKey('liteModeEnabled')) { - liteModeEnabled = options['liteModeEnabled'] as bool?; - } - if (options.containsKey('myLocationEnabled')) { - myLocationEnabled = options['myLocationEnabled'] as bool?; - } - if (options.containsKey('myLocationButtonEnabled')) { - myLocationButtonEnabled = options['myLocationButtonEnabled'] as bool?; - } - if (options.containsKey('trafficEnabled')) { - trafficEnabled = options['trafficEnabled'] as bool?; - } - if (options.containsKey('buildingsEnabled')) { - buildingsEnabled = options['buildingsEnabled'] as bool?; - } - if (options.containsKey('padding')) { - padding = options['padding'] as List?; - } - } -} - -class FakePlatformViewsController { - FakePlatformGoogleMap? lastCreatedView; - - Future fakePlatformViewsMethodHandler(MethodCall call) { - switch (call.method) { - case 'create': - final Map args = - call.arguments as Map; - final Map params = - _decodeParams(args['params'] as Uint8List)!; - lastCreatedView = FakePlatformGoogleMap( - args['id'] as int, - params, - ); - return Future.sync(() => 1); - default: - return Future.sync(() {}); - } - } - - void reset() { - lastCreatedView = null; - } -} - -Map? _decodeParams(Uint8List paramsMessage) { - final ByteBuffer buffer = paramsMessage.buffer; - final ByteData messageBytes = buffer.asByteData( - paramsMessage.offsetInBytes, - paramsMessage.lengthInBytes, - ); - return const StandardMessageCodec().decodeMessage(messageBytes) - as Map?; -} - -/// This allows a value of type T or T? to be treated as a value of type T?. -/// -/// We use this so that APIs that have become non-nullable can still be used -/// with `!` and `?` on the stable branch. -T? _ambiguate(T? value) => value; diff --git a/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart index 99b12988f3..7005a8d3ab 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart @@ -2,30 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/services.dart'; import 'package:flutter/widgets.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 'fake_maps_controllers.dart'; +import 'fake_google_maps_flutter_platform.dart'; void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - final FakePlatformViewsController fakePlatformViewsController = - FakePlatformViewsController(); - - setUpAll(() { - _ambiguate(TestDefaultBinaryMessengerBinding.instance)! - .defaultBinaryMessenger - .setMockMethodCallHandler( - SystemChannels.platform_views, - fakePlatformViewsController.fakePlatformViewsMethodHandler, - ); - }); + late FakeGoogleMapsFlutterPlatform platform; setUp(() { - fakePlatformViewsController.reset(); + platform = FakeGoogleMapsFlutterPlatform(); + GoogleMapsFlutterPlatform.instance = platform; }); testWidgets('Initial camera position', (WidgetTester tester) async { @@ -38,10 +27,9 @@ void main() { ), ); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.cameraPosition, + expect(map.widgetConfiguration.initialCameraPosition, const CameraPosition(target: LatLng(10.0, 15.0))); }); @@ -65,10 +53,9 @@ void main() { ), ); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.cameraPosition, + expect(map.widgetConfiguration.initialCameraPosition, const CameraPosition(target: LatLng(10.0, 15.0))); }); @@ -83,10 +70,9 @@ void main() { ), ); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.compassEnabled, false); + expect(map.mapConfiguration.compassEnabled, false); await tester.pumpWidget( const Directionality( @@ -97,7 +83,7 @@ void main() { ), ); - expect(platformGoogleMap.compassEnabled, true); + expect(map.mapConfiguration.compassEnabled, true); }); testWidgets('Can update mapToolbarEnabled', (WidgetTester tester) async { @@ -111,10 +97,9 @@ void main() { ), ); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.mapToolbarEnabled, false); + expect(map.mapConfiguration.mapToolbarEnabled, false); await tester.pumpWidget( const Directionality( @@ -125,7 +110,7 @@ void main() { ), ); - expect(platformGoogleMap.mapToolbarEnabled, true); + expect(map.mapConfiguration.mapToolbarEnabled, true); }); testWidgets('Can update cameraTargetBounds', (WidgetTester tester) async { @@ -145,11 +130,10 @@ void main() { ), ); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; expect( - platformGoogleMap.cameraTargetBounds, + map.mapConfiguration.cameraTargetBounds, CameraTargetBounds( LatLngBounds( southwest: const LatLng(10.0, 20.0), @@ -174,7 +158,7 @@ void main() { ); expect( - platformGoogleMap.cameraTargetBounds, + map.mapConfiguration.cameraTargetBounds, CameraTargetBounds( LatLngBounds( southwest: const LatLng(16.0, 20.0), @@ -194,10 +178,9 @@ void main() { ), ); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.mapType, MapType.hybrid); + expect(map.mapConfiguration.mapType, MapType.hybrid); await tester.pumpWidget( const Directionality( @@ -209,7 +192,7 @@ void main() { ), ); - expect(platformGoogleMap.mapType, MapType.satellite); + expect(map.mapConfiguration.mapType, MapType.satellite); }); testWidgets('Can update minMaxZoom', (WidgetTester tester) async { @@ -223,10 +206,9 @@ void main() { ), ); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.minMaxZoomPreference, + expect(map.mapConfiguration.minMaxZoomPreference, const MinMaxZoomPreference(1.0, 3.0)); await tester.pumpWidget( @@ -238,8 +220,8 @@ void main() { ), ); - expect( - platformGoogleMap.minMaxZoomPreference, MinMaxZoomPreference.unbounded); + expect(map.mapConfiguration.minMaxZoomPreference, + MinMaxZoomPreference.unbounded); }); testWidgets('Can update rotateGesturesEnabled', (WidgetTester tester) async { @@ -253,10 +235,9 @@ void main() { ), ); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.rotateGesturesEnabled, false); + expect(map.mapConfiguration.rotateGesturesEnabled, false); await tester.pumpWidget( const Directionality( @@ -267,7 +248,7 @@ void main() { ), ); - expect(platformGoogleMap.rotateGesturesEnabled, true); + expect(map.mapConfiguration.rotateGesturesEnabled, true); }); testWidgets('Can update scrollGesturesEnabled', (WidgetTester tester) async { @@ -281,10 +262,9 @@ void main() { ), ); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.scrollGesturesEnabled, false); + expect(map.mapConfiguration.scrollGesturesEnabled, false); await tester.pumpWidget( const Directionality( @@ -295,7 +275,7 @@ void main() { ), ); - expect(platformGoogleMap.scrollGesturesEnabled, true); + expect(map.mapConfiguration.scrollGesturesEnabled, true); }); testWidgets('Can update tiltGesturesEnabled', (WidgetTester tester) async { @@ -309,10 +289,9 @@ void main() { ), ); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.tiltGesturesEnabled, false); + expect(map.mapConfiguration.tiltGesturesEnabled, false); await tester.pumpWidget( const Directionality( @@ -323,7 +302,7 @@ void main() { ), ); - expect(platformGoogleMap.tiltGesturesEnabled, true); + expect(map.mapConfiguration.tiltGesturesEnabled, true); }); testWidgets('Can update trackCameraPosition', (WidgetTester tester) async { @@ -336,10 +315,9 @@ void main() { ), ); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.trackCameraPosition, false); + expect(map.mapConfiguration.trackCameraPosition, false); await tester.pumpWidget( Directionality( @@ -352,7 +330,7 @@ void main() { ), ); - expect(platformGoogleMap.trackCameraPosition, true); + expect(map.mapConfiguration.trackCameraPosition, true); }); testWidgets('Can update zoomGesturesEnabled', (WidgetTester tester) async { @@ -366,10 +344,9 @@ void main() { ), ); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.zoomGesturesEnabled, false); + expect(map.mapConfiguration.zoomGesturesEnabled, false); await tester.pumpWidget( const Directionality( @@ -380,7 +357,7 @@ void main() { ), ); - expect(platformGoogleMap.zoomGesturesEnabled, true); + expect(map.mapConfiguration.zoomGesturesEnabled, true); }); testWidgets('Can update zoomControlsEnabled', (WidgetTester tester) async { @@ -394,10 +371,9 @@ void main() { ), ); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.zoomControlsEnabled, false); + expect(map.mapConfiguration.zoomControlsEnabled, false); await tester.pumpWidget( const Directionality( @@ -408,7 +384,7 @@ void main() { ), ); - expect(platformGoogleMap.zoomControlsEnabled, true); + expect(map.mapConfiguration.zoomControlsEnabled, true); }); testWidgets('Can update myLocationEnabled', (WidgetTester tester) async { @@ -421,10 +397,9 @@ void main() { ), ); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.myLocationEnabled, false); + expect(map.mapConfiguration.myLocationEnabled, false); await tester.pumpWidget( const Directionality( @@ -436,7 +411,7 @@ void main() { ), ); - expect(platformGoogleMap.myLocationEnabled, true); + expect(map.mapConfiguration.myLocationEnabled, true); }); testWidgets('Can update myLocationButtonEnabled', @@ -450,10 +425,9 @@ void main() { ), ); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.myLocationButtonEnabled, true); + expect(map.mapConfiguration.myLocationButtonEnabled, true); await tester.pumpWidget( const Directionality( @@ -465,7 +439,7 @@ void main() { ), ); - expect(platformGoogleMap.myLocationButtonEnabled, false); + expect(map.mapConfiguration.myLocationButtonEnabled, false); }); testWidgets('Is default padding 0', (WidgetTester tester) async { @@ -478,10 +452,9 @@ void main() { ), ); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.padding, [0, 0, 0, 0]); + expect(map.mapConfiguration.padding, EdgeInsets.zero); }); testWidgets('Can update padding', (WidgetTester tester) async { @@ -494,10 +467,9 @@ void main() { ), ); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.padding, [0, 0, 0, 0]); + expect(map.mapConfiguration.padding, EdgeInsets.zero); await tester.pumpWidget( const Directionality( @@ -509,7 +481,8 @@ void main() { ), ); - expect(platformGoogleMap.padding, [20, 10, 40, 30]); + expect(map.mapConfiguration.padding, + const EdgeInsets.fromLTRB(10, 20, 30, 40)); await tester.pumpWidget( const Directionality( @@ -521,7 +494,8 @@ void main() { ), ); - expect(platformGoogleMap.padding, [60, 50, 80, 70]); + expect(map.mapConfiguration.padding, + const EdgeInsets.fromLTRB(50, 60, 70, 80)); }); testWidgets('Can update traffic', (WidgetTester tester) async { @@ -534,10 +508,9 @@ void main() { ), ); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.trafficEnabled, false); + expect(map.mapConfiguration.trafficEnabled, false); await tester.pumpWidget( const Directionality( @@ -549,7 +522,7 @@ void main() { ), ); - expect(platformGoogleMap.trafficEnabled, true); + expect(map.mapConfiguration.trafficEnabled, true); }); testWidgets('Can update buildings', (WidgetTester tester) async { @@ -563,10 +536,9 @@ void main() { ), ); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.buildingsEnabled, false); + expect(map.mapConfiguration.buildingsEnabled, false); await tester.pumpWidget( const Directionality( @@ -577,12 +549,6 @@ void main() { ), ); - expect(platformGoogleMap.buildingsEnabled, true); + expect(map.mapConfiguration.buildingsEnabled, true); }); } - -/// This allows a value of type T or T? to be treated as a value of type T?. -/// -/// We use this so that APIs that have become non-nullable can still be used -/// with `!` and `?` on the stable branch. -T? _ambiguate(T? value) => value; diff --git a/packages/google_maps_flutter/google_maps_flutter/test/map_creation_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/map_creation_test.dart index 7f88b60ad6..eb7e038c04 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/map_creation_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/map_creation_test.dart @@ -2,22 +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 'package:flutter/services.dart'; import 'package:flutter/widgets.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:stream_transform/stream_transform.dart'; + +import 'fake_google_maps_flutter_platform.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - late TestGoogleMapsFlutterPlatform platform; + late FakeGoogleMapsFlutterPlatform platform; setUp(() { // Use a mock platform so we never need to hit the MethodChannel code. - platform = TestGoogleMapsFlutterPlatform(); + platform = FakeGoogleMapsFlutterPlatform(); GoogleMapsFlutterPlatform.instance = platform; }); @@ -66,222 +64,3 @@ void main() { expect(platform.disposed, true); }); } - -// A dummy implementation of the platform interface for tests. -class TestGoogleMapsFlutterPlatform extends GoogleMapsFlutterPlatform { - TestGoogleMapsFlutterPlatform(); - - // The IDs passed to each call to buildView, in call order. - List createdIds = []; - - // Whether `dispose` has been called. - bool disposed = false; - - // Stream controller to inject events for testing. - final StreamController> mapEventStreamController = - StreamController>.broadcast(); - - @override - Future init(int mapId) async {} - - @override - Future updateMapConfiguration( - MapConfiguration update, { - required int mapId, - }) async {} - - @override - Future updateMarkers( - MarkerUpdates markerUpdates, { - required int mapId, - }) async {} - - @override - Future updatePolygons( - PolygonUpdates polygonUpdates, { - required int mapId, - }) async {} - - @override - Future updatePolylines( - PolylineUpdates polylineUpdates, { - required int mapId, - }) async {} - - @override - Future updateCircles( - CircleUpdates circleUpdates, { - required int mapId, - }) async {} - - @override - Future updateTileOverlays({ - required Set newTileOverlays, - required int mapId, - }) async {} - - @override - Future clearTileCache( - TileOverlayId tileOverlayId, { - required int mapId, - }) async {} - - @override - Future animateCamera( - CameraUpdate cameraUpdate, { - required int mapId, - }) async {} - - @override - Future moveCamera( - CameraUpdate cameraUpdate, { - required int mapId, - }) async {} - - @override - Future setMapStyle( - String? mapStyle, { - required int mapId, - }) async {} - - @override - Future getVisibleRegion({ - required int mapId, - }) async { - return LatLngBounds( - southwest: const LatLng(0, 0), northeast: const LatLng(0, 0)); - } - - @override - Future getScreenCoordinate( - LatLng latLng, { - required int mapId, - }) async { - return const ScreenCoordinate(x: 0, y: 0); - } - - @override - Future getLatLng( - ScreenCoordinate screenCoordinate, { - required int mapId, - }) async { - return const LatLng(0, 0); - } - - @override - Future showMarkerInfoWindow( - MarkerId markerId, { - required int mapId, - }) async {} - - @override - Future hideMarkerInfoWindow( - MarkerId markerId, { - required int mapId, - }) async {} - - @override - Future isMarkerInfoWindowShown( - MarkerId markerId, { - required int mapId, - }) async { - return false; - } - - @override - Future getZoomLevel({ - required int mapId, - }) async { - return 0.0; - } - - @override - Future takeSnapshot({ - required int mapId, - }) async { - return null; - } - - @override - Stream onCameraMoveStarted({required int mapId}) { - return mapEventStreamController.stream.whereType(); - } - - @override - Stream onCameraMove({required int mapId}) { - return mapEventStreamController.stream.whereType(); - } - - @override - Stream onCameraIdle({required int mapId}) { - return mapEventStreamController.stream.whereType(); - } - - @override - Stream onMarkerTap({required int mapId}) { - return mapEventStreamController.stream.whereType(); - } - - @override - Stream onInfoWindowTap({required int mapId}) { - return mapEventStreamController.stream.whereType(); - } - - @override - Stream onMarkerDragStart({required int mapId}) { - return mapEventStreamController.stream.whereType(); - } - - @override - Stream onMarkerDrag({required int mapId}) { - return mapEventStreamController.stream.whereType(); - } - - @override - Stream onMarkerDragEnd({required int mapId}) { - return mapEventStreamController.stream.whereType(); - } - - @override - Stream onPolylineTap({required int mapId}) { - return mapEventStreamController.stream.whereType(); - } - - @override - Stream onPolygonTap({required int mapId}) { - return mapEventStreamController.stream.whereType(); - } - - @override - Stream onCircleTap({required int mapId}) { - return mapEventStreamController.stream.whereType(); - } - - @override - Stream onTap({required int mapId}) { - return mapEventStreamController.stream.whereType(); - } - - @override - Stream onLongPress({required int mapId}) { - return mapEventStreamController.stream.whereType(); - } - - @override - void dispose({required int mapId}) { - disposed = true; - } - - @override - Widget buildViewWithConfiguration( - int creationId, - PlatformViewCreatedCallback onPlatformViewCreated, { - required MapWidgetConfiguration widgetConfiguration, - MapObjects mapObjects = const MapObjects(), - MapConfiguration mapConfiguration = const MapConfiguration(), - }) { - onPlatformViewCreated(0); - createdIds.add(creationId); - return Container(); - } -} diff --git a/packages/google_maps_flutter/google_maps_flutter/test/marker_updates_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/marker_updates_test.dart index 75a153e0ea..9f65f5d3bf 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/marker_updates_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/marker_updates_test.dart @@ -2,12 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/services.dart'; import 'package:flutter/widgets.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 'fake_maps_controllers.dart'; +import 'fake_google_maps_flutter_platform.dart'; Widget _mapWithMarkers(Set markers) { return Directionality( @@ -20,36 +20,24 @@ Widget _mapWithMarkers(Set markers) { } void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - final FakePlatformViewsController fakePlatformViewsController = - FakePlatformViewsController(); - - setUpAll(() { - _ambiguate(TestDefaultBinaryMessengerBinding.instance)! - .defaultBinaryMessenger - .setMockMethodCallHandler( - SystemChannels.platform_views, - fakePlatformViewsController.fakePlatformViewsMethodHandler, - ); - }); + late FakeGoogleMapsFlutterPlatform platform; setUp(() { - fakePlatformViewsController.reset(); + platform = FakeGoogleMapsFlutterPlatform(); + GoogleMapsFlutterPlatform.instance = platform; }); testWidgets('Initializing a marker', (WidgetTester tester) async { const Marker m1 = Marker(markerId: MarkerId('marker_1')); await tester.pumpWidget(_mapWithMarkers({m1})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.markersToAdd.length, 1); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.markerUpdates.last.markersToAdd.length, 1); - final Marker initializedMarker = platformGoogleMap.markersToAdd.first; + final Marker initializedMarker = map.markerUpdates.last.markersToAdd.first; expect(initializedMarker, equals(m1)); - expect(platformGoogleMap.markerIdsToRemove.isEmpty, true); - expect(platformGoogleMap.markersToChange.isEmpty, true); + expect(map.markerUpdates.last.markerIdsToRemove.isEmpty, true); + expect(map.markerUpdates.last.markersToChange.isEmpty, true); }); testWidgets('Adding a marker', (WidgetTester tester) async { @@ -59,16 +47,15 @@ void main() { await tester.pumpWidget(_mapWithMarkers({m1})); await tester.pumpWidget(_mapWithMarkers({m1, m2})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.markersToAdd.length, 1); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.markerUpdates.last.markersToAdd.length, 1); - final Marker addedMarker = platformGoogleMap.markersToAdd.first; + final Marker addedMarker = map.markerUpdates.last.markersToAdd.first; expect(addedMarker, equals(m2)); - expect(platformGoogleMap.markerIdsToRemove.isEmpty, true); + expect(map.markerUpdates.last.markerIdsToRemove.isEmpty, true); - expect(platformGoogleMap.markersToChange.isEmpty, true); + expect(map.markerUpdates.last.markersToChange.isEmpty, true); }); testWidgets('Removing a marker', (WidgetTester tester) async { @@ -77,13 +64,12 @@ void main() { await tester.pumpWidget(_mapWithMarkers({m1})); await tester.pumpWidget(_mapWithMarkers({})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.markerIdsToRemove.length, 1); - expect(platformGoogleMap.markerIdsToRemove.first, equals(m1.markerId)); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.markerUpdates.last.markerIdsToRemove.length, 1); + expect(map.markerUpdates.last.markerIdsToRemove.first, equals(m1.markerId)); - expect(platformGoogleMap.markersToChange.isEmpty, true); - expect(platformGoogleMap.markersToAdd.isEmpty, true); + expect(map.markerUpdates.last.markersToChange.isEmpty, true); + expect(map.markerUpdates.last.markersToAdd.isEmpty, true); }); testWidgets('Updating a marker', (WidgetTester tester) async { @@ -93,13 +79,12 @@ void main() { await tester.pumpWidget(_mapWithMarkers({m1})); await tester.pumpWidget(_mapWithMarkers({m2})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.markersToChange.length, 1); - expect(platformGoogleMap.markersToChange.first, equals(m2)); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.markerUpdates.last.markersToChange.length, 1); + expect(map.markerUpdates.last.markersToChange.first, equals(m2)); - expect(platformGoogleMap.markerIdsToRemove.isEmpty, true); - expect(platformGoogleMap.markersToAdd.isEmpty, true); + expect(map.markerUpdates.last.markerIdsToRemove.isEmpty, true); + expect(map.markerUpdates.last.markersToAdd.isEmpty, true); }); testWidgets('Updating a marker', (WidgetTester tester) async { @@ -112,11 +97,10 @@ void main() { await tester.pumpWidget(_mapWithMarkers({m1})); await tester.pumpWidget(_mapWithMarkers({m2})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.markersToChange.length, 1); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.markerUpdates.last.markersToChange.length, 1); - final Marker update = platformGoogleMap.markersToChange.first; + final Marker update = map.markerUpdates.last.markersToChange.first; expect(update, equals(m2)); expect(update.infoWindow.snippet, 'changed'); }); @@ -132,12 +116,11 @@ void main() { await tester.pumpWidget(_mapWithMarkers(prev)); await tester.pumpWidget(_mapWithMarkers(cur)); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.markersToChange, cur); - expect(platformGoogleMap.markerIdsToRemove.isEmpty, true); - expect(platformGoogleMap.markersToAdd.isEmpty, true); + expect(map.markerUpdates.last.markersToChange, cur); + expect(map.markerUpdates.last.markerIdsToRemove.isEmpty, true); + expect(map.markerUpdates.last.markersToAdd.isEmpty, true); }); testWidgets('Multi Update', (WidgetTester tester) async { @@ -153,16 +136,15 @@ void main() { await tester.pumpWidget(_mapWithMarkers(prev)); await tester.pumpWidget(_mapWithMarkers(cur)); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.markersToChange.length, 1); - expect(platformGoogleMap.markersToAdd.length, 1); - expect(platformGoogleMap.markerIdsToRemove.length, 1); + expect(map.markerUpdates.last.markersToChange.length, 1); + expect(map.markerUpdates.last.markersToAdd.length, 1); + expect(map.markerUpdates.last.markerIdsToRemove.length, 1); - expect(platformGoogleMap.markersToChange.first, equals(m2)); - expect(platformGoogleMap.markersToAdd.first, equals(m1)); - expect(platformGoogleMap.markerIdsToRemove.first, equals(m3.markerId)); + expect(map.markerUpdates.last.markersToChange.first, equals(m2)); + expect(map.markerUpdates.last.markersToAdd.first, equals(m1)); + expect(map.markerUpdates.last.markerIdsToRemove.first, equals(m3.markerId)); }); testWidgets('Partial Update', (WidgetTester tester) async { @@ -176,12 +158,11 @@ void main() { await tester.pumpWidget(_mapWithMarkers(prev)); await tester.pumpWidget(_mapWithMarkers(cur)); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.markersToChange, {m3}); - expect(platformGoogleMap.markerIdsToRemove.isEmpty, true); - expect(platformGoogleMap.markersToAdd.isEmpty, true); + expect(map.markerUpdates.last.markersToChange, {m3}); + expect(map.markerUpdates.last.markerIdsToRemove.isEmpty, true); + expect(map.markerUpdates.last.markersToAdd.isEmpty, true); }); testWidgets('Update non platform related attr', (WidgetTester tester) async { @@ -196,17 +177,43 @@ void main() { await tester.pumpWidget(_mapWithMarkers(prev)); await tester.pumpWidget(_mapWithMarkers(cur)); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.markersToChange.isEmpty, true); - expect(platformGoogleMap.markerIdsToRemove.isEmpty, true); - expect(platformGoogleMap.markersToAdd.isEmpty, true); + expect(map.markerUpdates.last.markersToChange.isEmpty, true); + expect(map.markerUpdates.last.markerIdsToRemove.isEmpty, true); + expect(map.markerUpdates.last.markersToAdd.isEmpty, true); + }); + + testWidgets('multi-update with delays', (WidgetTester tester) async { + platform.simulatePlatformDelay = true; + + const Marker m1 = Marker(markerId: MarkerId('marker_1')); + const Marker m2 = Marker(markerId: MarkerId('marker_2')); + const Marker m3 = Marker(markerId: MarkerId('marker_3')); + const Marker m3updated = + Marker(markerId: MarkerId('marker_3'), draggable: true); + + // First remove one and add another, then update the new one. + await tester.pumpWidget(_mapWithMarkers({m1, m2})); + await tester.pumpWidget(_mapWithMarkers({m1, m3})); + await tester.pumpWidget(_mapWithMarkers({m1, m3updated})); + + final PlatformMapStateRecorder map = platform.lastCreatedMap; + + expect(map.markerUpdates.length, 3); + + expect(map.markerUpdates[0].markersToChange.isEmpty, true); + expect(map.markerUpdates[0].markersToAdd, {m1, m2}); + expect(map.markerUpdates[0].markerIdsToRemove.isEmpty, true); + + expect(map.markerUpdates[1].markersToChange.isEmpty, true); + expect(map.markerUpdates[1].markersToAdd, {m3}); + expect(map.markerUpdates[1].markerIdsToRemove, {m2.markerId}); + + expect(map.markerUpdates[2].markersToChange, {m3updated}); + expect(map.markerUpdates[2].markersToAdd.isEmpty, true); + expect(map.markerUpdates[2].markerIdsToRemove.isEmpty, true); + + await tester.pumpAndSettle(); }); } - -/// This allows a value of type T or T? to be treated as a value of type T?. -/// -/// We use this so that APIs that have become non-nullable can still be used -/// with `!` and `?` on the stable branch. -T? _ambiguate(T? value) => value; diff --git a/packages/google_maps_flutter/google_maps_flutter/test/polygon_updates_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/polygon_updates_test.dart index 152cbddfc3..08910fa5cc 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/polygon_updates_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/polygon_updates_test.dart @@ -2,12 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/services.dart'; import 'package:flutter/widgets.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 'fake_maps_controllers.dart'; +import 'fake_google_maps_flutter_platform.dart'; Widget _mapWithPolygons(Set polygons) { return Directionality( @@ -43,36 +43,25 @@ Polygon _polygonWithPointsAndHole(PolygonId polygonId) { } void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - final FakePlatformViewsController fakePlatformViewsController = - FakePlatformViewsController(); - - setUpAll(() { - _ambiguate(TestDefaultBinaryMessengerBinding.instance)! - .defaultBinaryMessenger - .setMockMethodCallHandler( - SystemChannels.platform_views, - fakePlatformViewsController.fakePlatformViewsMethodHandler, - ); - }); + late FakeGoogleMapsFlutterPlatform platform; setUp(() { - fakePlatformViewsController.reset(); + platform = FakeGoogleMapsFlutterPlatform(); + GoogleMapsFlutterPlatform.instance = platform; }); testWidgets('Initializing a polygon', (WidgetTester tester) async { const Polygon p1 = Polygon(polygonId: PolygonId('polygon_1')); await tester.pumpWidget(_mapWithPolygons({p1})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.polygonsToAdd.length, 1); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.polygonUpdates.last.polygonsToAdd.length, 1); - final Polygon initializedPolygon = platformGoogleMap.polygonsToAdd.first; + final Polygon initializedPolygon = + map.polygonUpdates.last.polygonsToAdd.first; expect(initializedPolygon, equals(p1)); - expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); - expect(platformGoogleMap.polygonsToChange.isEmpty, true); + expect(map.polygonUpdates.last.polygonIdsToRemove.isEmpty, true); + expect(map.polygonUpdates.last.polygonsToChange.isEmpty, true); }); testWidgets('Adding a polygon', (WidgetTester tester) async { @@ -82,16 +71,15 @@ void main() { await tester.pumpWidget(_mapWithPolygons({p1})); await tester.pumpWidget(_mapWithPolygons({p1, p2})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.polygonsToAdd.length, 1); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.polygonUpdates.last.polygonsToAdd.length, 1); - final Polygon addedPolygon = platformGoogleMap.polygonsToAdd.first; + final Polygon addedPolygon = map.polygonUpdates.last.polygonsToAdd.first; expect(addedPolygon, equals(p2)); - expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); + expect(map.polygonUpdates.last.polygonIdsToRemove.isEmpty, true); - expect(platformGoogleMap.polygonsToChange.isEmpty, true); + expect(map.polygonUpdates.last.polygonsToChange.isEmpty, true); }); testWidgets('Removing a polygon', (WidgetTester tester) async { @@ -100,13 +88,13 @@ void main() { await tester.pumpWidget(_mapWithPolygons({p1})); await tester.pumpWidget(_mapWithPolygons({})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.polygonIdsToRemove.length, 1); - expect(platformGoogleMap.polygonIdsToRemove.first, equals(p1.polygonId)); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.polygonUpdates.last.polygonIdsToRemove.length, 1); + expect( + map.polygonUpdates.last.polygonIdsToRemove.first, equals(p1.polygonId)); - expect(platformGoogleMap.polygonsToChange.isEmpty, true); - expect(platformGoogleMap.polygonsToAdd.isEmpty, true); + expect(map.polygonUpdates.last.polygonsToChange.isEmpty, true); + expect(map.polygonUpdates.last.polygonsToAdd.isEmpty, true); }); testWidgets('Updating a polygon', (WidgetTester tester) async { @@ -117,13 +105,12 @@ void main() { await tester.pumpWidget(_mapWithPolygons({p1})); await tester.pumpWidget(_mapWithPolygons({p2})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.polygonsToChange.length, 1); - expect(platformGoogleMap.polygonsToChange.first, equals(p2)); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.polygonUpdates.last.polygonsToChange.length, 1); + expect(map.polygonUpdates.last.polygonsToChange.first, equals(p2)); - expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); - expect(platformGoogleMap.polygonsToAdd.isEmpty, true); + expect(map.polygonUpdates.last.polygonIdsToRemove.isEmpty, true); + expect(map.polygonUpdates.last.polygonsToAdd.isEmpty, true); }); testWidgets('Mutate a polygon', (WidgetTester tester) async { @@ -137,13 +124,12 @@ void main() { p1.points.add(const LatLng(1.0, 1.0)); await tester.pumpWidget(_mapWithPolygons({p1})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.polygonsToChange.length, 1); - expect(platformGoogleMap.polygonsToChange.first, equals(p1)); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.polygonUpdates.last.polygonsToChange.length, 1); + expect(map.polygonUpdates.last.polygonsToChange.first, equals(p1)); - expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); - expect(platformGoogleMap.polygonsToAdd.isEmpty, true); + expect(map.polygonUpdates.last.polygonIdsToRemove.isEmpty, true); + expect(map.polygonUpdates.last.polygonsToAdd.isEmpty, true); }); testWidgets('Multi Update', (WidgetTester tester) async { @@ -157,12 +143,11 @@ void main() { await tester.pumpWidget(_mapWithPolygons(prev)); await tester.pumpWidget(_mapWithPolygons(cur)); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.polygonsToChange, cur); - expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); - expect(platformGoogleMap.polygonsToAdd.isEmpty, true); + expect(map.polygonUpdates.last.polygonsToChange, cur); + expect(map.polygonUpdates.last.polygonIdsToRemove.isEmpty, true); + expect(map.polygonUpdates.last.polygonsToAdd.isEmpty, true); }); testWidgets('Multi Update', (WidgetTester tester) async { @@ -178,16 +163,16 @@ void main() { await tester.pumpWidget(_mapWithPolygons(prev)); await tester.pumpWidget(_mapWithPolygons(cur)); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.polygonsToChange.length, 1); - expect(platformGoogleMap.polygonsToAdd.length, 1); - expect(platformGoogleMap.polygonIdsToRemove.length, 1); + expect(map.polygonUpdates.last.polygonsToChange.length, 1); + expect(map.polygonUpdates.last.polygonsToAdd.length, 1); + expect(map.polygonUpdates.last.polygonIdsToRemove.length, 1); - expect(platformGoogleMap.polygonsToChange.first, equals(p2)); - expect(platformGoogleMap.polygonsToAdd.first, equals(p1)); - expect(platformGoogleMap.polygonIdsToRemove.first, equals(p3.polygonId)); + expect(map.polygonUpdates.last.polygonsToChange.first, equals(p2)); + expect(map.polygonUpdates.last.polygonsToAdd.first, equals(p1)); + expect( + map.polygonUpdates.last.polygonIdsToRemove.first, equals(p3.polygonId)); }); testWidgets('Partial Update', (WidgetTester tester) async { @@ -201,12 +186,11 @@ void main() { await tester.pumpWidget(_mapWithPolygons(prev)); await tester.pumpWidget(_mapWithPolygons(cur)); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.polygonsToChange, {p3}); - expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); - expect(platformGoogleMap.polygonsToAdd.isEmpty, true); + expect(map.polygonUpdates.last.polygonsToChange, {p3}); + expect(map.polygonUpdates.last.polygonIdsToRemove.isEmpty, true); + expect(map.polygonUpdates.last.polygonsToAdd.isEmpty, true); }); testWidgets('Update non platform related attr', (WidgetTester tester) async { @@ -218,12 +202,11 @@ void main() { await tester.pumpWidget(_mapWithPolygons(prev)); await tester.pumpWidget(_mapWithPolygons(cur)); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.polygonsToChange.isEmpty, true); - expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); - expect(platformGoogleMap.polygonsToAdd.isEmpty, true); + expect(map.polygonUpdates.last.polygonsToChange.isEmpty, true); + expect(map.polygonUpdates.last.polygonIdsToRemove.isEmpty, true); + expect(map.polygonUpdates.last.polygonsToAdd.isEmpty, true); }); testWidgets('Initializing a polygon with points and hole', @@ -231,14 +214,14 @@ void main() { final Polygon p1 = _polygonWithPointsAndHole(const PolygonId('polygon_1')); await tester.pumpWidget(_mapWithPolygons({p1})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.polygonsToAdd.length, 1); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.polygonUpdates.last.polygonsToAdd.length, 1); - final Polygon initializedPolygon = platformGoogleMap.polygonsToAdd.first; + final Polygon initializedPolygon = + map.polygonUpdates.last.polygonsToAdd.first; expect(initializedPolygon, equals(p1)); - expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); - expect(platformGoogleMap.polygonsToChange.isEmpty, true); + expect(map.polygonUpdates.last.polygonIdsToRemove.isEmpty, true); + expect(map.polygonUpdates.last.polygonsToChange.isEmpty, true); }); testWidgets('Adding a polygon with points and hole', @@ -249,16 +232,15 @@ void main() { await tester.pumpWidget(_mapWithPolygons({p1})); await tester.pumpWidget(_mapWithPolygons({p1, p2})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.polygonsToAdd.length, 1); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.polygonUpdates.last.polygonsToAdd.length, 1); - final Polygon addedPolygon = platformGoogleMap.polygonsToAdd.first; + final Polygon addedPolygon = map.polygonUpdates.last.polygonsToAdd.first; expect(addedPolygon, equals(p2)); - expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); + expect(map.polygonUpdates.last.polygonIdsToRemove.isEmpty, true); - expect(platformGoogleMap.polygonsToChange.isEmpty, true); + expect(map.polygonUpdates.last.polygonsToChange.isEmpty, true); }); testWidgets('Removing a polygon with points and hole', @@ -268,13 +250,13 @@ void main() { await tester.pumpWidget(_mapWithPolygons({p1})); await tester.pumpWidget(_mapWithPolygons({})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.polygonIdsToRemove.length, 1); - expect(platformGoogleMap.polygonIdsToRemove.first, equals(p1.polygonId)); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.polygonUpdates.last.polygonIdsToRemove.length, 1); + expect( + map.polygonUpdates.last.polygonIdsToRemove.first, equals(p1.polygonId)); - expect(platformGoogleMap.polygonsToChange.isEmpty, true); - expect(platformGoogleMap.polygonsToAdd.isEmpty, true); + expect(map.polygonUpdates.last.polygonsToChange.isEmpty, true); + expect(map.polygonUpdates.last.polygonsToAdd.isEmpty, true); }); testWidgets('Updating a polygon by adding points and hole', @@ -285,13 +267,12 @@ void main() { await tester.pumpWidget(_mapWithPolygons({p1})); await tester.pumpWidget(_mapWithPolygons({p2})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.polygonsToChange.length, 1); - expect(platformGoogleMap.polygonsToChange.first, equals(p2)); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.polygonUpdates.last.polygonsToChange.length, 1); + expect(map.polygonUpdates.last.polygonsToChange.first, equals(p2)); - expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); - expect(platformGoogleMap.polygonsToAdd.isEmpty, true); + expect(map.polygonUpdates.last.polygonIdsToRemove.isEmpty, true); + expect(map.polygonUpdates.last.polygonsToAdd.isEmpty, true); }); testWidgets('Mutate a polygon with points and holes', @@ -311,13 +292,12 @@ void main() { ..addAll(>[_rectPoints(size: 1)]); await tester.pumpWidget(_mapWithPolygons({p1})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.polygonsToChange.length, 1); - expect(platformGoogleMap.polygonsToChange.first, equals(p1)); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.polygonUpdates.last.polygonsToChange.length, 1); + expect(map.polygonUpdates.last.polygonsToChange.first, equals(p1)); - expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); - expect(platformGoogleMap.polygonsToAdd.isEmpty, true); + expect(map.polygonUpdates.last.polygonIdsToRemove.isEmpty, true); + expect(map.polygonUpdates.last.polygonsToAdd.isEmpty, true); }); testWidgets('Multi Update polygons with points and hole', @@ -339,12 +319,11 @@ void main() { await tester.pumpWidget(_mapWithPolygons(prev)); await tester.pumpWidget(_mapWithPolygons(cur)); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.polygonsToChange, cur); - expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); - expect(platformGoogleMap.polygonsToAdd.isEmpty, true); + expect(map.polygonUpdates.last.polygonsToChange, cur); + expect(map.polygonUpdates.last.polygonIdsToRemove.isEmpty, true); + expect(map.polygonUpdates.last.polygonsToAdd.isEmpty, true); }); testWidgets('Multi Update polygons with points and hole', @@ -368,16 +347,16 @@ void main() { await tester.pumpWidget(_mapWithPolygons(prev)); await tester.pumpWidget(_mapWithPolygons(cur)); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.polygonsToChange.length, 1); - expect(platformGoogleMap.polygonsToAdd.length, 1); - expect(platformGoogleMap.polygonIdsToRemove.length, 1); + expect(map.polygonUpdates.last.polygonsToChange.length, 1); + expect(map.polygonUpdates.last.polygonsToAdd.length, 1); + expect(map.polygonUpdates.last.polygonIdsToRemove.length, 1); - expect(platformGoogleMap.polygonsToChange.first, equals(p2)); - expect(platformGoogleMap.polygonsToAdd.first, equals(p1)); - expect(platformGoogleMap.polygonIdsToRemove.first, equals(p3.polygonId)); + expect(map.polygonUpdates.last.polygonsToChange.first, equals(p2)); + expect(map.polygonUpdates.last.polygonsToAdd.first, equals(p1)); + expect( + map.polygonUpdates.last.polygonIdsToRemove.first, equals(p3.polygonId)); }); testWidgets('Partial Update polygons with points and hole', @@ -399,17 +378,44 @@ void main() { await tester.pumpWidget(_mapWithPolygons(prev)); await tester.pumpWidget(_mapWithPolygons(cur)); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.polygonsToChange, {p3}); - expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true); - expect(platformGoogleMap.polygonsToAdd.isEmpty, true); + expect(map.polygonUpdates.last.polygonsToChange, {p3}); + expect(map.polygonUpdates.last.polygonIdsToRemove.isEmpty, true); + expect(map.polygonUpdates.last.polygonsToAdd.isEmpty, true); + }); + + testWidgets('multi-update with delays', (WidgetTester tester) async { + platform.simulatePlatformDelay = true; + + const Polygon p1 = Polygon(polygonId: PolygonId('polygon_1')); + const Polygon p2 = Polygon(polygonId: PolygonId('polygon_2')); + const Polygon p3 = + Polygon(polygonId: PolygonId('polygon_3'), strokeWidth: 1); + const Polygon p3updated = + Polygon(polygonId: PolygonId('polygon_3'), strokeWidth: 2); + + // First remove one and add another, then update the new one. + await tester.pumpWidget(_mapWithPolygons({p1, p2})); + await tester.pumpWidget(_mapWithPolygons({p1, p3})); + await tester.pumpWidget(_mapWithPolygons({p1, p3updated})); + + final PlatformMapStateRecorder map = platform.lastCreatedMap; + + expect(map.polygonUpdates.length, 3); + + expect(map.polygonUpdates[0].polygonsToChange.isEmpty, true); + expect(map.polygonUpdates[0].polygonsToAdd, {p1, p2}); + expect(map.polygonUpdates[0].polygonIdsToRemove.isEmpty, true); + + expect(map.polygonUpdates[1].polygonsToChange.isEmpty, true); + expect(map.polygonUpdates[1].polygonsToAdd, {p3}); + expect(map.polygonUpdates[1].polygonIdsToRemove, {p2.polygonId}); + + expect(map.polygonUpdates[2].polygonsToChange, {p3updated}); + expect(map.polygonUpdates[2].polygonsToAdd.isEmpty, true); + expect(map.polygonUpdates[2].polygonIdsToRemove.isEmpty, true); + + await tester.pumpAndSettle(); }); } - -/// This allows a value of type T or T? to be treated as a value of type T?. -/// -/// We use this so that APIs that have become non-nullable can still be used -/// with `!` and `?` on the stable branch. -T? _ambiguate(T? value) => value; diff --git a/packages/google_maps_flutter/google_maps_flutter/test/polyline_updates_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/polyline_updates_test.dart index 03b6c62019..cac311f7f2 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/polyline_updates_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/polyline_updates_test.dart @@ -2,12 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/services.dart'; import 'package:flutter/widgets.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 'fake_maps_controllers.dart'; +import 'fake_google_maps_flutter_platform.dart'; Widget _mapWithPolylines(Set polylines) { return Directionality( @@ -20,36 +20,25 @@ Widget _mapWithPolylines(Set polylines) { } void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - final FakePlatformViewsController fakePlatformViewsController = - FakePlatformViewsController(); - - setUpAll(() { - _ambiguate(TestDefaultBinaryMessengerBinding.instance)! - .defaultBinaryMessenger - .setMockMethodCallHandler( - SystemChannels.platform_views, - fakePlatformViewsController.fakePlatformViewsMethodHandler, - ); - }); + late FakeGoogleMapsFlutterPlatform platform; setUp(() { - fakePlatformViewsController.reset(); + platform = FakeGoogleMapsFlutterPlatform(); + GoogleMapsFlutterPlatform.instance = platform; }); testWidgets('Initializing a polyline', (WidgetTester tester) async { const Polyline p1 = Polyline(polylineId: PolylineId('polyline_1')); await tester.pumpWidget(_mapWithPolylines({p1})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.polylinesToAdd.length, 1); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.polylineUpdates.last.polylinesToAdd.length, 1); - final Polyline initializedPolyline = platformGoogleMap.polylinesToAdd.first; + final Polyline initializedPolyline = + map.polylineUpdates.last.polylinesToAdd.first; expect(initializedPolyline, equals(p1)); - expect(platformGoogleMap.polylineIdsToRemove.isEmpty, true); - expect(platformGoogleMap.polylinesToChange.isEmpty, true); + expect(map.polylineUpdates.last.polylineIdsToRemove.isEmpty, true); + expect(map.polylineUpdates.last.polylinesToChange.isEmpty, true); }); testWidgets('Adding a polyline', (WidgetTester tester) async { @@ -59,16 +48,16 @@ void main() { await tester.pumpWidget(_mapWithPolylines({p1})); await tester.pumpWidget(_mapWithPolylines({p1, p2})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.polylinesToAdd.length, 1); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.polylineUpdates.last.polylinesToAdd.length, 1); - final Polyline addedPolyline = platformGoogleMap.polylinesToAdd.first; + final Polyline addedPolyline = + map.polylineUpdates.last.polylinesToAdd.first; expect(addedPolyline, equals(p2)); - expect(platformGoogleMap.polylineIdsToRemove.isEmpty, true); + expect(map.polylineUpdates.last.polylineIdsToRemove.isEmpty, true); - expect(platformGoogleMap.polylinesToChange.isEmpty, true); + expect(map.polylineUpdates.last.polylinesToChange.isEmpty, true); }); testWidgets('Removing a polyline', (WidgetTester tester) async { @@ -77,13 +66,13 @@ void main() { await tester.pumpWidget(_mapWithPolylines({p1})); await tester.pumpWidget(_mapWithPolylines({})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.polylineIdsToRemove.length, 1); - expect(platformGoogleMap.polylineIdsToRemove.first, equals(p1.polylineId)); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.polylineUpdates.last.polylineIdsToRemove.length, 1); + expect(map.polylineUpdates.last.polylineIdsToRemove.first, + equals(p1.polylineId)); - expect(platformGoogleMap.polylinesToChange.isEmpty, true); - expect(platformGoogleMap.polylinesToAdd.isEmpty, true); + expect(map.polylineUpdates.last.polylinesToChange.isEmpty, true); + expect(map.polylineUpdates.last.polylinesToAdd.isEmpty, true); }); testWidgets('Updating a polyline', (WidgetTester tester) async { @@ -94,13 +83,12 @@ void main() { await tester.pumpWidget(_mapWithPolylines({p1})); await tester.pumpWidget(_mapWithPolylines({p2})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.polylinesToChange.length, 1); - expect(platformGoogleMap.polylinesToChange.first, equals(p2)); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.polylineUpdates.last.polylinesToChange.length, 1); + expect(map.polylineUpdates.last.polylinesToChange.first, equals(p2)); - expect(platformGoogleMap.polylineIdsToRemove.isEmpty, true); - expect(platformGoogleMap.polylinesToAdd.isEmpty, true); + expect(map.polylineUpdates.last.polylineIdsToRemove.isEmpty, true); + expect(map.polylineUpdates.last.polylinesToAdd.isEmpty, true); }); testWidgets('Updating a polyline', (WidgetTester tester) async { @@ -111,11 +99,10 @@ void main() { await tester.pumpWidget(_mapWithPolylines({p1})); await tester.pumpWidget(_mapWithPolylines({p2})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.polylinesToChange.length, 1); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.polylineUpdates.last.polylinesToChange.length, 1); - final Polyline update = platformGoogleMap.polylinesToChange.first; + final Polyline update = map.polylineUpdates.last.polylinesToChange.first; expect(update, equals(p2)); expect(update.geodesic, true); }); @@ -131,13 +118,12 @@ void main() { p1.points.add(const LatLng(1.0, 1.0)); await tester.pumpWidget(_mapWithPolylines({p1})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.polylinesToChange.length, 1); - expect(platformGoogleMap.polylinesToChange.first, equals(p1)); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.polylineUpdates.last.polylinesToChange.length, 1); + expect(map.polylineUpdates.last.polylinesToChange.first, equals(p1)); - expect(platformGoogleMap.polylineIdsToRemove.isEmpty, true); - expect(platformGoogleMap.polylinesToAdd.isEmpty, true); + expect(map.polylineUpdates.last.polylineIdsToRemove.isEmpty, true); + expect(map.polylineUpdates.last.polylinesToAdd.isEmpty, true); }); testWidgets('Multi Update', (WidgetTester tester) async { @@ -151,12 +137,11 @@ void main() { await tester.pumpWidget(_mapWithPolylines(prev)); await tester.pumpWidget(_mapWithPolylines(cur)); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.polylinesToChange, cur); - expect(platformGoogleMap.polylineIdsToRemove.isEmpty, true); - expect(platformGoogleMap.polylinesToAdd.isEmpty, true); + expect(map.polylineUpdates.last.polylinesToChange, cur); + expect(map.polylineUpdates.last.polylineIdsToRemove.isEmpty, true); + expect(map.polylineUpdates.last.polylinesToAdd.isEmpty, true); }); testWidgets('Multi Update', (WidgetTester tester) async { @@ -172,16 +157,16 @@ void main() { await tester.pumpWidget(_mapWithPolylines(prev)); await tester.pumpWidget(_mapWithPolylines(cur)); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.polylinesToChange.length, 1); - expect(platformGoogleMap.polylinesToAdd.length, 1); - expect(platformGoogleMap.polylineIdsToRemove.length, 1); + expect(map.polylineUpdates.last.polylinesToChange.length, 1); + expect(map.polylineUpdates.last.polylinesToAdd.length, 1); + expect(map.polylineUpdates.last.polylineIdsToRemove.length, 1); - expect(platformGoogleMap.polylinesToChange.first, equals(p2)); - expect(platformGoogleMap.polylinesToAdd.first, equals(p1)); - expect(platformGoogleMap.polylineIdsToRemove.first, equals(p3.polylineId)); + expect(map.polylineUpdates.last.polylinesToChange.first, equals(p2)); + expect(map.polylineUpdates.last.polylinesToAdd.first, equals(p1)); + expect(map.polylineUpdates.last.polylineIdsToRemove.first, + equals(p3.polylineId)); }); testWidgets('Partial Update', (WidgetTester tester) async { @@ -195,12 +180,11 @@ void main() { await tester.pumpWidget(_mapWithPolylines(prev)); await tester.pumpWidget(_mapWithPolylines(cur)); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.polylinesToChange, {p3}); - expect(platformGoogleMap.polylineIdsToRemove.isEmpty, true); - expect(platformGoogleMap.polylinesToAdd.isEmpty, true); + expect(map.polylineUpdates.last.polylinesToChange, {p3}); + expect(map.polylineUpdates.last.polylineIdsToRemove.isEmpty, true); + expect(map.polylineUpdates.last.polylinesToAdd.isEmpty, true); }); testWidgets('Update non platform related attr', (WidgetTester tester) async { @@ -212,17 +196,45 @@ void main() { await tester.pumpWidget(_mapWithPolylines(prev)); await tester.pumpWidget(_mapWithPolylines(cur)); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; + final PlatformMapStateRecorder map = platform.lastCreatedMap; - expect(platformGoogleMap.polylinesToChange.isEmpty, true); - expect(platformGoogleMap.polylineIdsToRemove.isEmpty, true); - expect(platformGoogleMap.polylinesToAdd.isEmpty, true); + expect(map.polylineUpdates.last.polylinesToChange.isEmpty, true); + expect(map.polylineUpdates.last.polylineIdsToRemove.isEmpty, true); + expect(map.polylineUpdates.last.polylinesToAdd.isEmpty, true); + }); + + testWidgets('multi-update with delays', (WidgetTester tester) async { + platform.simulatePlatformDelay = true; + + const Polyline p1 = Polyline(polylineId: PolylineId('polyline_1')); + const Polyline p2 = Polyline(polylineId: PolylineId('polyline_2')); + const Polyline p3 = + Polyline(polylineId: PolylineId('polyline_3'), width: 1); + const Polyline p3updated = + Polyline(polylineId: PolylineId('polyline_3'), width: 2); + + // First remove one and add another, then update the new one. + await tester.pumpWidget(_mapWithPolylines({p1, p2})); + await tester.pumpWidget(_mapWithPolylines({p1, p3})); + await tester.pumpWidget(_mapWithPolylines({p1, p3updated})); + + final PlatformMapStateRecorder map = platform.lastCreatedMap; + + expect(map.polylineUpdates.length, 3); + + expect(map.polylineUpdates[0].polylinesToChange.isEmpty, true); + expect(map.polylineUpdates[0].polylinesToAdd, {p1, p2}); + expect(map.polylineUpdates[0].polylineIdsToRemove.isEmpty, true); + + expect(map.polylineUpdates[1].polylinesToChange.isEmpty, true); + expect(map.polylineUpdates[1].polylinesToAdd, {p3}); + expect(map.polylineUpdates[1].polylineIdsToRemove, + {p2.polylineId}); + + expect(map.polylineUpdates[2].polylinesToChange, {p3updated}); + expect(map.polylineUpdates[2].polylinesToAdd.isEmpty, true); + expect(map.polylineUpdates[2].polylineIdsToRemove.isEmpty, true); + + await tester.pumpAndSettle(); }); } - -/// This allows a value of type T or T? to be treated as a value of type T?. -/// -/// We use this so that APIs that have become non-nullable can still be used -/// with `!` and `?` on the stable branch. -T? _ambiguate(T? value) => value; diff --git a/packages/google_maps_flutter/google_maps_flutter/test/tile_overlay_updates_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/tile_overlay_updates_test.dart index e4e4514dd5..c2faca593b 100644 --- a/packages/google_maps_flutter/google_maps_flutter/test/tile_overlay_updates_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter/test/tile_overlay_updates_test.dart @@ -2,12 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/services.dart'; import 'package:flutter/widgets.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 'fake_maps_controllers.dart'; +import 'fake_google_maps_flutter_platform.dart'; Widget _mapWithTileOverlays(Set tileOverlays) { return Directionality( @@ -20,20 +20,11 @@ Widget _mapWithTileOverlays(Set tileOverlays) { } void main() { - final FakePlatformViewsController fakePlatformViewsController = - FakePlatformViewsController(); - - setUpAll(() { - _ambiguate(TestDefaultBinaryMessengerBinding.instance)! - .defaultBinaryMessenger - .setMockMethodCallHandler( - SystemChannels.platform_views, - fakePlatformViewsController.fakePlatformViewsMethodHandler, - ); - }); + late FakeGoogleMapsFlutterPlatform platform; setUp(() { - fakePlatformViewsController.reset(); + platform = FakeGoogleMapsFlutterPlatform(); + GoogleMapsFlutterPlatform.instance = platform; }); testWidgets('Initializing a tile overlay', (WidgetTester tester) async { @@ -41,15 +32,8 @@ void main() { TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_1')); await tester.pumpWidget(_mapWithTileOverlays({t1})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.tileOverlaysToAdd.length, 1); - - final TileOverlay initializedTileOverlay = - platformGoogleMap.tileOverlaysToAdd.first; - expect(initializedTileOverlay, equals(t1)); - expect(platformGoogleMap.tileOverlayIdsToRemove.isEmpty, true); - expect(platformGoogleMap.tileOverlaysToChange.isEmpty, true); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.tileOverlaySets.last, equals({t1})); }); testWidgets('Adding a tile overlay', (WidgetTester tester) async { @@ -61,16 +45,8 @@ void main() { await tester.pumpWidget(_mapWithTileOverlays({t1})); await tester.pumpWidget(_mapWithTileOverlays({t1, t2})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.tileOverlaysToAdd.length, 1); - - final TileOverlay addedTileOverlay = - platformGoogleMap.tileOverlaysToAdd.first; - expect(addedTileOverlay, equals(t2)); - expect(platformGoogleMap.tileOverlayIdsToRemove.isEmpty, true); - - expect(platformGoogleMap.tileOverlaysToChange.isEmpty, true); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.tileOverlaySets.last, equals({t1, t2})); }); testWidgets('Removing a tile overlay', (WidgetTester tester) async { @@ -80,14 +56,8 @@ void main() { await tester.pumpWidget(_mapWithTileOverlays({t1})); await tester.pumpWidget(_mapWithTileOverlays({})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.tileOverlayIdsToRemove.length, 1); - expect(platformGoogleMap.tileOverlayIdsToRemove.first, - equals(t1.tileOverlayId)); - - expect(platformGoogleMap.tileOverlaysToChange.isEmpty, true); - expect(platformGoogleMap.tileOverlaysToAdd.isEmpty, true); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.tileOverlaySets.last, equals({})); }); testWidgets('Updating a tile overlay', (WidgetTester tester) async { @@ -99,112 +69,7 @@ void main() { await tester.pumpWidget(_mapWithTileOverlays({t1})); await tester.pumpWidget(_mapWithTileOverlays({t2})); - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.tileOverlaysToChange.length, 1); - expect(platformGoogleMap.tileOverlaysToChange.first, equals(t2)); - - expect(platformGoogleMap.tileOverlayIdsToRemove.isEmpty, true); - expect(platformGoogleMap.tileOverlaysToAdd.isEmpty, true); - }); - - testWidgets('Updating a tile overlay', (WidgetTester tester) async { - const TileOverlay t1 = - TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_1')); - const TileOverlay t2 = - TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_1'), zIndex: 10); - - await tester.pumpWidget(_mapWithTileOverlays({t1})); - await tester.pumpWidget(_mapWithTileOverlays({t2})); - - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - expect(platformGoogleMap.tileOverlaysToChange.length, 1); - - final TileOverlay update = platformGoogleMap.tileOverlaysToChange.first; - expect(update, equals(t2)); - expect(update.zIndex, 10); - }); - - testWidgets('Multi Update', (WidgetTester tester) async { - TileOverlay t1 = - const TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_1')); - TileOverlay t2 = - const TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_2')); - final Set prev = {t1, t2}; - t1 = const TileOverlay( - tileOverlayId: TileOverlayId('tile_overlay_1'), visible: false); - t2 = const TileOverlay( - tileOverlayId: TileOverlayId('tile_overlay_2'), zIndex: 10); - final Set cur = {t1, t2}; - - await tester.pumpWidget(_mapWithTileOverlays(prev)); - await tester.pumpWidget(_mapWithTileOverlays(cur)); - - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - - expect(platformGoogleMap.tileOverlaysToChange, cur); - expect(platformGoogleMap.tileOverlayIdsToRemove.isEmpty, true); - expect(platformGoogleMap.tileOverlaysToAdd.isEmpty, true); - }); - - testWidgets('Multi Update', (WidgetTester tester) async { - TileOverlay t2 = - const TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_2')); - const TileOverlay t3 = - TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_3')); - final Set prev = {t2, t3}; - - // t1 is added, t2 is updated, t3 is removed. - const TileOverlay t1 = - TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_1')); - t2 = const TileOverlay( - tileOverlayId: TileOverlayId('tile_overlay_2'), zIndex: 10); - final Set cur = {t1, t2}; - - await tester.pumpWidget(_mapWithTileOverlays(prev)); - await tester.pumpWidget(_mapWithTileOverlays(cur)); - - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - - expect(platformGoogleMap.tileOverlaysToChange.length, 1); - expect(platformGoogleMap.tileOverlaysToAdd.length, 1); - expect(platformGoogleMap.tileOverlayIdsToRemove.length, 1); - - expect(platformGoogleMap.tileOverlaysToChange.first, equals(t2)); - expect(platformGoogleMap.tileOverlaysToAdd.first, equals(t1)); - expect(platformGoogleMap.tileOverlayIdsToRemove.first, - equals(t3.tileOverlayId)); - }); - - testWidgets('Partial Update', (WidgetTester tester) async { - const TileOverlay t1 = - TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_1')); - const TileOverlay t2 = - TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_2')); - TileOverlay t3 = - const TileOverlay(tileOverlayId: TileOverlayId('tile_overlay_3')); - final Set prev = {t1, t2, t3}; - t3 = const TileOverlay( - tileOverlayId: TileOverlayId('tile_overlay_3'), zIndex: 10); - final Set cur = {t1, t2, t3}; - - await tester.pumpWidget(_mapWithTileOverlays(prev)); - await tester.pumpWidget(_mapWithTileOverlays(cur)); - - final FakePlatformGoogleMap platformGoogleMap = - fakePlatformViewsController.lastCreatedView!; - - expect(platformGoogleMap.tileOverlaysToChange, {t3}); - expect(platformGoogleMap.tileOverlayIdsToRemove.isEmpty, true); - expect(platformGoogleMap.tileOverlaysToAdd.isEmpty, true); + final PlatformMapStateRecorder map = platform.lastCreatedMap; + expect(map.tileOverlaySets.last, equals({t2})); }); } - -/// This allows a value of type T or T? to be treated as a value of type T?. -/// -/// We use this so that APIs that have become non-nullable can still be used -/// with `!` and `?` on the stable branch. -T? _ambiguate(T? value) => value; 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 525576f013..dab6b69b98 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 @@ -25,3 +25,10 @@ dev_dependencies: integration_test: sdk: flutter mockito: 5.4.1 + +dependency_overrides: + # Override the google_maps_flutter dependency on google_maps_flutter_web. + # TODO(ditman): Unwind the circular dependency. This will create problems + # if we need to make a breaking change to google_maps_flutter_web. + google_maps_flutter_web: + path: ../