diff --git a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md
index 82f2fc94e86..2e0f8c666c6 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md
+++ b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.5.13
+
+* Adds Advanced markers support.
+
## 0.5.12+2
* Fix broken cameraTargetBounds option on web.
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/README.md b/packages/google_maps_flutter/google_maps_flutter_web/README.md
index 9a92c70bad1..2dd51dcaf8d 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/README.md
+++ b/packages/google_maps_flutter/google_maps_flutter_web/README.md
@@ -32,7 +32,7 @@ Modify the `
` tag of your `web/index.html` to load the Google Maps JavaScr
The Google Maps Web SDK splits some of its functionality in [separate libraries](https://developers.google.com/maps/documentation/javascript/libraries#libraries-for-dynamic-library-import).
If your app needs the `drawing` library (to draw polygons, rectangles, polylines,
-circles or markers on a map), include it like this:
+circles or legacy markers on a map), include it like this:
```html
+```
+
To request multiple libraries, separate them with commas:
```html
```
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/advanced_marker_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/advanced_marker_test.dart
new file mode 100644
index 00000000000..95ee0f0deed
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/advanced_marker_test.dart
@@ -0,0 +1,223 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:js_interop';
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:google_maps/google_maps.dart' as gmaps;
+import 'package:google_maps_flutter_web/google_maps_flutter_web.dart';
+import 'package:google_maps_flutter_web/src/utils.dart';
+import 'package:integration_test/integration_test.dart';
+
+/// Test Markers
+void main() {
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+ // Since onTap/DragEnd events happen asynchronously, we need to store when the event
+ // is fired. We use a completer so the test can wait for the future to be completed.
+ late Completer methodCalledCompleter;
+
+ /// This is the future value of the [methodCalledCompleter]. Reinitialized
+ /// in the [setUp] method, and completed (as `true`) by [onTap] and [onDragEnd]
+ /// when those methods are called from the MarkerController.
+ late Future methodCalled;
+
+ void onTap() {
+ methodCalledCompleter.complete(true);
+ }
+
+ void onDragStart(gmaps.LatLng _) {
+ methodCalledCompleter.complete(true);
+ }
+
+ void onDrag(gmaps.LatLng _) {
+ methodCalledCompleter.complete(true);
+ }
+
+ void onDragEnd(gmaps.LatLng _) {
+ methodCalledCompleter.complete(true);
+ }
+
+ setUp(() {
+ methodCalledCompleter = Completer();
+ methodCalled = methodCalledCompleter.future;
+ });
+
+ group('MarkerController', () {
+ late gmaps.AdvancedMarkerElement marker;
+
+ setUp(() {
+ marker = gmaps.AdvancedMarkerElement();
+ });
+
+ testWidgets('onTap gets called', (WidgetTester tester) async {
+ AdvancedMarkerController(marker: marker, onTap: onTap);
+
+ // Trigger a click event...
+ gmaps.event.trigger(
+ marker,
+ 'click',
+ gmaps.MapMouseEvent(),
+ );
+
+ // The event handling is now truly async. Wait for it...
+ expect(await methodCalled, isTrue);
+ });
+
+ testWidgets('onDragStart gets called', (WidgetTester tester) async {
+ AdvancedMarkerController(marker: marker, onDragStart: onDragStart);
+
+ // Trigger a drag end event...
+ gmaps.event.trigger(
+ marker,
+ 'dragstart',
+ gmaps.MapMouseEvent()..latLng = gmaps.LatLng(0, 0),
+ );
+
+ expect(await methodCalled, isTrue);
+ });
+
+ testWidgets('onDrag gets called', (WidgetTester tester) async {
+ AdvancedMarkerController(marker: marker, onDrag: onDrag);
+
+ // Trigger a drag end event...
+ gmaps.event.trigger(
+ marker,
+ 'drag',
+ gmaps.MapMouseEvent()..latLng = gmaps.LatLng(0, 0),
+ );
+
+ expect(await methodCalled, isTrue);
+ });
+
+ testWidgets('onDragEnd gets called', (WidgetTester tester) async {
+ AdvancedMarkerController(marker: marker, onDragEnd: onDragEnd);
+
+ // Trigger a drag end event...
+ gmaps.event.trigger(
+ marker,
+ 'dragend',
+ gmaps.MapMouseEvent()..latLng = gmaps.LatLng(0, 0),
+ );
+
+ expect(await methodCalled, isTrue);
+ });
+
+ testWidgets('update', (WidgetTester tester) async {
+ final AdvancedMarkerController controller =
+ AdvancedMarkerController(marker: marker);
+ final gmaps.AdvancedMarkerElementOptions options =
+ gmaps.AdvancedMarkerElementOptions()
+ ..collisionBehavior =
+ gmaps.CollisionBehavior.OPTIONAL_AND_HIDES_LOWER_PRIORITY
+ ..gmpDraggable = true
+ ..position = gmaps.LatLng(42, 54);
+
+ expect(marker.collisionBehavior, gmaps.CollisionBehavior.REQUIRED);
+ expect(marker.gmpDraggable, isFalse);
+
+ controller.update(options);
+
+ expect(marker.gmpDraggable, isTrue);
+ expect(
+ marker.collisionBehavior,
+ gmaps.CollisionBehavior.OPTIONAL_AND_HIDES_LOWER_PRIORITY,
+ );
+ final JSAny? position = marker.position;
+ expect(position, isNotNull);
+ expect(position is gmaps.LatLngLiteral, isTrue);
+ expect((position! as gmaps.LatLngLiteral).lat, equals(42));
+ expect((position as gmaps.LatLngLiteral).lng, equals(54));
+ });
+
+ testWidgets('infoWindow null, showInfoWindow.',
+ (WidgetTester tester) async {
+ final AdvancedMarkerController controller =
+ AdvancedMarkerController(marker: marker);
+
+ controller.showInfoWindow();
+
+ expect(controller.infoWindowShown, isFalse);
+ });
+
+ testWidgets('showInfoWindow', (WidgetTester tester) async {
+ final gmaps.InfoWindow infoWindow = gmaps.InfoWindow();
+ final gmaps.Map map = gmaps.Map(createDivElement());
+ marker.map = map;
+ final AdvancedMarkerController controller = AdvancedMarkerController(
+ marker: marker,
+ infoWindow: infoWindow,
+ );
+
+ controller.showInfoWindow();
+
+ expect(infoWindow.get('map'), map);
+ expect(controller.infoWindowShown, isTrue);
+ });
+
+ testWidgets('hideInfoWindow', (WidgetTester tester) async {
+ final gmaps.InfoWindow infoWindow = gmaps.InfoWindow();
+ final gmaps.Map map = gmaps.Map(createDivElement());
+ marker.map = map;
+ final AdvancedMarkerController controller = AdvancedMarkerController(
+ marker: marker,
+ infoWindow: infoWindow,
+ );
+
+ controller.hideInfoWindow();
+
+ expect(infoWindow.get('map'), isNull);
+ expect(controller.infoWindowShown, isFalse);
+ });
+
+ group('remove', () {
+ late AdvancedMarkerController controller;
+
+ setUp(() {
+ final gmaps.InfoWindow infoWindow = gmaps.InfoWindow();
+ final gmaps.Map map = gmaps.Map(createDivElement());
+ marker.map = map;
+ controller =
+ AdvancedMarkerController(marker: marker, infoWindow: infoWindow);
+ });
+
+ testWidgets('drops gmaps instance', (WidgetTester tester) async {
+ controller.remove();
+
+ expect(controller.marker, isNull);
+ });
+
+ testWidgets('cannot call update after remove',
+ (WidgetTester tester) async {
+ final gmaps.AdvancedMarkerElementOptions options =
+ gmaps.AdvancedMarkerElementOptions()..gmpDraggable = true;
+
+ controller.remove();
+
+ expect(() {
+ controller.update(options);
+ }, throwsAssertionError);
+ });
+
+ testWidgets('cannot call showInfoWindow after remove',
+ (WidgetTester tester) async {
+ controller.remove();
+
+ expect(() {
+ controller.showInfoWindow();
+ }, throwsAssertionError);
+ });
+
+ testWidgets('cannot call hideInfoWindow after remove',
+ (WidgetTester tester) async {
+ controller.remove();
+
+ expect(() {
+ controller.hideInfoWindow();
+ }, throwsAssertionError);
+ });
+ });
+ });
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/advanced_markers_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/advanced_markers_test.dart
new file mode 100644
index 00000000000..61cb1deff60
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/advanced_markers_test.dart
@@ -0,0 +1,619 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:js_interop';
+import 'dart:typed_data';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:google_maps/google_maps.dart' as gmaps;
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+import 'package:google_maps_flutter_web/google_maps_flutter_web.dart';
+import 'package:google_maps_flutter_web/src/marker_clustering.dart';
+import 'package:google_maps_flutter_web/src/utils.dart';
+import 'package:http/http.dart' as http;
+import 'package:integration_test/integration_test.dart';
+import 'package:web/src/dom.dart' as dom;
+import 'package:web/web.dart';
+
+import 'resources/icon_image_base64.dart';
+
+void main() {
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+ group('MarkersController', () {
+ late StreamController> events;
+ late MarkersController controller;
+ late ClusterManagersController
+ clusterManagersController;
+ late gmaps.Map map;
+
+ setUp(() {
+ events = StreamController>();
+
+ clusterManagersController =
+ ClusterManagersController(
+ stream: events);
+ controller = AdvancedMarkersController(
+ stream: events,
+ clusterManagersController: clusterManagersController,
+ );
+ map = gmaps.Map(createDivElement());
+ clusterManagersController.bindToMap(123, map);
+ controller.bindToMap(123, map);
+ });
+
+ testWidgets('addMarkers', (WidgetTester tester) async {
+ final Set markers = {
+ AdvancedMarker(markerId: const MarkerId('1')),
+ AdvancedMarker(markerId: const MarkerId('2')),
+ };
+
+ await controller.addMarkers(markers);
+
+ expect(controller.markers.length, 2);
+ expect(controller.markers, contains(const MarkerId('1')));
+ expect(controller.markers, contains(const MarkerId('2')));
+ expect(controller.markers, isNot(contains(const MarkerId('66'))));
+ });
+
+ testWidgets('changeMarkers', (WidgetTester tester) async {
+ gmaps.AdvancedMarkerElement? marker;
+ gmaps.LatLngLiteral? position;
+
+ final Set markers = {
+ AdvancedMarker(markerId: const MarkerId('1')),
+ };
+ await controller.addMarkers(markers);
+
+ marker = controller.markers[const MarkerId('1')]?.marker;
+ expect(marker, isNotNull);
+ expect(marker!.gmpDraggable, isFalse);
+
+ // By default, markers fall in LatLng(0, 0).
+ position = marker.position! as gmaps.LatLngLiteral;
+ expect(position, isNotNull);
+ expect(position.lat, equals(0));
+ expect(position.lng, equals(0));
+
+ // Update the marker with draggable and position.
+ final Set updatedMarkers = {
+ AdvancedMarker(
+ markerId: const MarkerId('1'),
+ draggable: true,
+ position: const LatLng(42, 54),
+ ),
+ };
+ await controller.changeMarkers(updatedMarkers);
+ expect(controller.markers.length, 1);
+
+ marker = controller.markers[const MarkerId('1')]?.marker;
+ expect(marker, isNotNull);
+ expect(marker!.gmpDraggable, isTrue);
+
+ position = marker.position! as gmaps.LatLngLiteral;
+ expect(position, isNotNull);
+ expect(position.lat, equals(42));
+ expect(position.lng, equals(54));
+ });
+
+ testWidgets(
+ 'changeMarkers resets marker position if not passed when updating!',
+ (WidgetTester tester) async {
+ gmaps.AdvancedMarkerElement? marker;
+ gmaps.LatLngLiteral? position;
+
+ final Set markers = {
+ AdvancedMarker(
+ markerId: const MarkerId('1'),
+ position: const LatLng(42, 54),
+ ),
+ };
+ await controller.addMarkers(markers);
+
+ marker = controller.markers[const MarkerId('1')]?.marker;
+ expect(marker, isNotNull);
+ expect(marker!.gmpDraggable, isFalse);
+
+ position = marker.position! as gmaps.LatLngLiteral;
+ expect(position, isNotNull);
+ expect(position.lat, equals(42));
+ expect(position.lng, equals(54));
+
+ // Update the marker without position.
+ final Set updatedMarkers = {
+ AdvancedMarker(
+ markerId: const MarkerId('1'),
+ draggable: true,
+ ),
+ };
+ await controller.changeMarkers(updatedMarkers);
+ expect(controller.markers.length, 1);
+
+ marker = controller.markers[const MarkerId('1')]?.marker;
+ expect(marker, isNotNull);
+ expect(marker!.gmpDraggable, isTrue);
+
+ position = marker.position! as gmaps.LatLngLiteral;
+ expect(position, isNotNull);
+ expect(position.lat, equals(0));
+ expect(position.lng, equals(0));
+ });
+
+ testWidgets('removeMarkers', (WidgetTester tester) async {
+ final Set markers = {
+ AdvancedMarker(markerId: const MarkerId('1')),
+ AdvancedMarker(markerId: const MarkerId('2')),
+ AdvancedMarker(markerId: const MarkerId('3')),
+ };
+
+ await controller.addMarkers(markers);
+
+ expect(controller.markers.length, 3);
+
+ // Remove some markers.
+ final Set markerIdsToRemove = {
+ const MarkerId('1'),
+ const MarkerId('3'),
+ };
+
+ controller.removeMarkers(markerIdsToRemove);
+
+ expect(controller.markers.length, 1);
+ expect(controller.markers, isNot(contains(const MarkerId('1'))));
+ expect(controller.markers, contains(const MarkerId('2')));
+ expect(controller.markers, isNot(contains(const MarkerId('3'))));
+ });
+
+ testWidgets('InfoWindow show/hide', (WidgetTester tester) async {
+ final Set markers = {
+ AdvancedMarker(
+ markerId: const MarkerId('1'),
+ infoWindow: const InfoWindow(title: 'Title', snippet: 'Snippet'),
+ ),
+ };
+
+ await controller.addMarkers(markers);
+
+ expect(controller.markers[const MarkerId('1')]?.infoWindowShown, isFalse);
+
+ controller.showMarkerInfoWindow(const MarkerId('1'));
+
+ expect(controller.markers[const MarkerId('1')]?.infoWindowShown, isTrue);
+
+ controller.hideMarkerInfoWindow(const MarkerId('1'));
+
+ expect(controller.markers[const MarkerId('1')]?.infoWindowShown, isFalse);
+ });
+
+ testWidgets('only single InfoWindow is visible',
+ (WidgetTester tester) async {
+ final Set markers = {
+ AdvancedMarker(
+ markerId: const MarkerId('1'),
+ infoWindow: const InfoWindow(title: 'Title', snippet: 'Snippet'),
+ ),
+ AdvancedMarker(
+ markerId: const MarkerId('2'),
+ infoWindow: const InfoWindow(title: 'Title', snippet: 'Snippet'),
+ ),
+ };
+ await controller.addMarkers(markers);
+
+ expect(controller.markers[const MarkerId('1')]?.infoWindowShown, isFalse);
+ expect(controller.markers[const MarkerId('2')]?.infoWindowShown, isFalse);
+
+ controller.showMarkerInfoWindow(const MarkerId('1'));
+
+ expect(controller.markers[const MarkerId('1')]?.infoWindowShown, isTrue);
+ expect(controller.markers[const MarkerId('2')]?.infoWindowShown, isFalse);
+
+ controller.showMarkerInfoWindow(const MarkerId('2'));
+
+ expect(controller.markers[const MarkerId('1')]?.infoWindowShown, isFalse);
+ expect(controller.markers[const MarkerId('2')]?.infoWindowShown, isTrue);
+ });
+
+ testWidgets('markers with custom asset icon work',
+ (WidgetTester tester) async {
+ final Set markers = {
+ AdvancedMarker(
+ markerId: const MarkerId('1'),
+ icon: AssetMapBitmap(
+ 'assets/red_square.png',
+ imagePixelRatio: 1.0,
+ ),
+ ),
+ };
+
+ await controller.addMarkers(markers);
+
+ expect(controller.markers.length, 1);
+ final HTMLImageElement? icon = controller
+ .markers[const MarkerId('1')]?.marker?.content as HTMLImageElement?;
+ expect(icon, isNotNull);
+
+ final String assetUrl = icon!.src;
+ expect(assetUrl, endsWith('assets/red_square.png'));
+
+ // Asset size is 48x48 physical pixels.
+ expect(icon.style.width, '48px');
+ expect(icon.style.height, '48px');
+ });
+
+ testWidgets('markers with custom asset icon and pixel ratio work',
+ (WidgetTester tester) async {
+ final Set markers = {
+ AdvancedMarker(
+ markerId: const MarkerId('1'),
+ icon: AssetMapBitmap(
+ 'assets/red_square.png',
+ imagePixelRatio: 2.0,
+ ),
+ ),
+ };
+
+ await controller.addMarkers(markers);
+
+ expect(controller.markers.length, 1);
+ final HTMLImageElement? icon = controller
+ .markers[const MarkerId('1')]?.marker?.content as HTMLImageElement?;
+ expect(icon, isNotNull);
+
+ final String assetUrl = icon!.src;
+ expect(assetUrl, endsWith('assets/red_square.png'));
+
+ // Asset size is 48x48 physical pixels, and with pixel ratio 2.0 it
+ // should be drawn with size 24x24 logical pixels.
+ expect(icon.style.width, '24px');
+ expect(icon.style.height, '24px');
+ });
+
+ testWidgets('markers with custom asset icon with width and height work',
+ (WidgetTester tester) async {
+ final Set markers = {
+ AdvancedMarker(
+ markerId: const MarkerId('1'),
+ icon: AssetMapBitmap(
+ 'assets/red_square.png',
+ imagePixelRatio: 2.0,
+ width: 64,
+ height: 64,
+ )),
+ };
+
+ await controller.addMarkers(markers);
+
+ expect(controller.markers.length, 1);
+ final HTMLImageElement? icon = controller
+ .markers[const MarkerId('1')]?.marker?.content as HTMLImageElement?;
+ expect(icon, isNotNull);
+
+ final String assetUrl = icon!.src;
+ expect(assetUrl, endsWith('assets/red_square.png'));
+
+ // Asset size is 48x48 physical pixels,
+ // and scaled to requested 64x64 size.
+ expect(icon.style.width, '64px');
+ expect(icon.style.height, '64px');
+ });
+
+ testWidgets('markers with missing asset icon should not set size',
+ (WidgetTester tester) async {
+ final Set markers = {
+ AdvancedMarker(
+ markerId: const MarkerId('1'),
+ icon: AssetMapBitmap(
+ 'assets/broken_asset_name.png',
+ imagePixelRatio: 2.0,
+ )),
+ };
+
+ await controller.addMarkers(markers);
+
+ expect(controller.markers.length, 1);
+ final HTMLImageElement? icon = controller
+ .markers[const MarkerId('1')]?.marker?.content as HTMLImageElement?;
+ expect(icon, isNotNull);
+
+ final String assetUrl = icon!.src;
+ expect(assetUrl, endsWith('assets/broken_asset_name.png'));
+
+ // For invalid assets, the size and scaledSize should be null.
+ expect(icon.style.width, isEmpty);
+ expect(icon.style.height, isEmpty);
+ });
+
+ testWidgets('markers with custom bitmap icon work',
+ (WidgetTester tester) async {
+ final Uint8List bytes = const Base64Decoder().convert(iconImageBase64);
+ final Set markers = {
+ AdvancedMarker(
+ markerId: const MarkerId('1'),
+ icon: BytesMapBitmap(
+ bytes,
+ imagePixelRatio: tester.view.devicePixelRatio,
+ ),
+ ),
+ };
+
+ await controller.addMarkers(markers);
+
+ expect(controller.markers.length, 1);
+ final HTMLImageElement? icon = controller
+ .markers[const MarkerId('1')]?.marker?.content as HTMLImageElement?;
+ expect(icon, isNotNull);
+
+ final String blobUrl = icon!.src;
+ expect(blobUrl, startsWith('blob:'));
+
+ final http.Response response = await http.get(Uri.parse(blobUrl));
+ expect(
+ response.bodyBytes,
+ bytes,
+ reason:
+ 'Bytes from the Icon blob must match bytes used to create AdvancedMarker',
+ );
+
+ // Icon size is 16x16 pixels, this should be automatically read from the
+ // bitmap and set to the icon size scaled to 8x8 using the
+ // given imagePixelRatio.
+ final int expectedSize = 16 ~/ tester.view.devicePixelRatio;
+ expect(icon.style.width, '${expectedSize}px');
+ expect(icon.style.height, '${expectedSize}px');
+ });
+
+ testWidgets('markers with custom bitmap icon and pixel ratio work',
+ (WidgetTester tester) async {
+ final Uint8List bytes = const Base64Decoder().convert(iconImageBase64);
+ final Set markers = {
+ AdvancedMarker(
+ markerId: const MarkerId('1'),
+ icon: BytesMapBitmap(
+ bytes,
+ imagePixelRatio: 1,
+ ),
+ ),
+ };
+
+ await controller.addMarkers(markers);
+
+ expect(controller.markers.length, 1);
+ final HTMLImageElement? icon = controller
+ .markers[const MarkerId('1')]?.marker?.content as HTMLImageElement?;
+ expect(icon, isNotNull);
+
+ // Icon size is 16x16 pixels, this should be automatically read from the
+ // bitmap and set to the icon size and should not be changed as
+ // image pixel ratio is set to 1.0.
+ expect(icon!.style.width, '16px');
+ expect(icon.style.height, '16px');
+ });
+
+ testWidgets('markers with custom bitmap icon pass size to sdk',
+ (WidgetTester tester) async {
+ final Uint8List bytes = const Base64Decoder().convert(iconImageBase64);
+ final Set markers = {
+ AdvancedMarker(
+ markerId: const MarkerId('1'),
+ icon: BytesMapBitmap(
+ bytes,
+ width: 20,
+ height: 30,
+ ),
+ ),
+ };
+
+ await controller.addMarkers(markers);
+
+ expect(controller.markers.length, 1);
+ final HTMLImageElement? icon = controller
+ .markers[const MarkerId('1')]?.marker?.content as HTMLImageElement?;
+ expect(icon, isNotNull);
+ expect(icon!.style.width, '20px');
+ expect(icon.style.height, '30px');
+ });
+
+ testWidgets('markers created with pin config and colored glyph work',
+ (WidgetTester widgetTester) async {
+ final Set markers = {
+ AdvancedMarker(
+ markerId: const MarkerId('1'),
+ icon: BitmapDescriptor.pinConfig(
+ backgroundColor: const Color(0xFF00FF00),
+ borderColor: const Color(0xFFFF0000),
+ glyph: const CircleGlyph(color: Color(0xFFFFFFFF)),
+ ),
+ ),
+ };
+ await controller.addMarkers(markers);
+ expect(controller.markers.length, 1);
+
+ final HTMLDivElement? icon = controller
+ .markers[const MarkerId('1')]?.marker?.content as HTMLDivElement?;
+ expect(icon, isNotNull);
+
+ // Query nodes and check colors. This is a bit fragile as it depends on
+ // the implementation details of the icon which is not part of the public
+ // API.
+ final NodeList backgroundNodes =
+ icon!.querySelectorAll("[class*='maps-pin-view-background']");
+ final NodeList borderNodes =
+ icon.querySelectorAll("[class*='maps-pin-view-border']");
+ final NodeList glyphNodes =
+ icon.querySelectorAll("[class*='maps-pin-view-default-glyph']");
+
+ expect(backgroundNodes.length, 1);
+ expect(borderNodes.length, 1);
+ expect(glyphNodes.length, 1);
+
+ expect(
+ (backgroundNodes.item(0)! as dom.Element)
+ .getAttribute('fill')
+ ?.toUpperCase(),
+ '#00FF00',
+ );
+ expect(
+ (borderNodes.item(0)! as dom.Element)
+ .getAttribute('fill')
+ ?.toUpperCase(),
+ '#FF0000',
+ );
+ expect(
+ (glyphNodes.item(0)! as dom.Element)
+ .getAttribute('fill')
+ ?.toUpperCase(),
+ '#FFFFFF',
+ );
+ });
+
+ testWidgets('markers created with text glyph work',
+ (WidgetTester widgetTester) async {
+ final Set markers = {
+ AdvancedMarker(
+ markerId: const MarkerId('1'),
+ icon: BitmapDescriptor.pinConfig(
+ backgroundColor: Colors.black,
+ borderColor: Colors.black,
+ glyph: const TextGlyph(
+ text: 'Hey',
+ textColor: Color(0xFF0000FF),
+ ),
+ ),
+ ),
+ };
+ await controller.addMarkers(markers);
+ expect(controller.markers.length, 1);
+
+ final HTMLDivElement? icon = controller
+ .markers[const MarkerId('1')]?.marker?.content as HTMLDivElement?;
+ expect(icon, isNotNull);
+
+ // Query pin nodes and find text element. This is a bit fragile as it
+ // depends on the implementation details of the icon which is not part of
+ // the public API.
+ dom.Element? paragraphElement;
+ final NodeList paragraphs = icon!.querySelectorAll('p');
+ for (int i = 0; i < paragraphs.length; i++) {
+ final dom.Element? paragraph = paragraphs.item(i) as dom.Element?;
+ if (paragraph?.innerHTML.toString() == 'Hey') {
+ paragraphElement = paragraph;
+ break;
+ }
+ }
+
+ expect(paragraphElement, isNotNull);
+ expect(paragraphElement!.innerHTML.toString(), 'Hey');
+
+ expect(
+ paragraphElement.getAttribute('style')?.toLowerCase(),
+ contains('color: #0000ff'),
+ );
+ });
+
+ testWidgets('markers created with bitmap glyph work',
+ (WidgetTester widgetTester) async {
+ final Set markers = {
+ AdvancedMarker(
+ markerId: const MarkerId('1'),
+ icon: BitmapDescriptor.pinConfig(
+ backgroundColor: Colors.black,
+ borderColor: Colors.black,
+ glyph: BitmapGlyph(
+ bitmap: await BitmapDescriptor.asset(
+ const ImageConfiguration(
+ size: Size.square(12),
+ ),
+ 'assets/red_square.png',
+ ),
+ ),
+ ),
+ ),
+ };
+ await controller.addMarkers(markers);
+ expect(controller.markers.length, 1);
+
+ final HTMLDivElement? icon = controller
+ .markers[const MarkerId('1')]?.marker?.content as HTMLDivElement?;
+ expect(icon, isNotNull);
+
+ // Query pin nodes and find text element. This is a bit fragile as it
+ // depends on the implementation details of the icon which is not part of
+ // the public API.
+ HTMLImageElement? imgElement;
+ final NodeList imgElements = icon!.querySelectorAll('img');
+ for (int i = 0; i < imgElements.length; i++) {
+ final dom.Element? img = imgElements.item(i) as dom.Element?;
+ final String src = (img! as HTMLImageElement).src;
+ if (src.endsWith('assets/red_square.png')) {
+ imgElement = img as HTMLImageElement;
+ break;
+ }
+ }
+
+ expect(imgElement, isNotNull);
+ expect(imgElement!.src, endsWith('assets/red_square.png'));
+ expect(
+ imgElement.getAttribute('style')?.toLowerCase(),
+ contains('width: 12.0px; height: 12.0px;'),
+ );
+ });
+
+ testWidgets('InfoWindow snippet can have links',
+ (WidgetTester tester) async {
+ final Set markers = {
+ AdvancedMarker(
+ markerId: const MarkerId('1'),
+ infoWindow: const InfoWindow(
+ title: 'title for test',
+ snippet: 'Go to Google >>>',
+ ),
+ ),
+ };
+
+ await controller.addMarkers(markers);
+
+ expect(controller.markers.length, 1);
+ final HTMLElement? content = controller
+ .markers[const MarkerId('1')]?.infoWindow?.content as HTMLElement?;
+ expect(content, isNotNull);
+
+ final String innerHtml = (content!.innerHTML as JSString).toDart;
+ expect(innerHtml, contains('title for test'));
+ expect(
+ innerHtml,
+ contains(
+ 'Go to Google >>>',
+ ));
+ });
+
+ testWidgets('InfoWindow content is clickable', (WidgetTester tester) async {
+ final Set markers = {
+ AdvancedMarker(
+ markerId: const MarkerId('1'),
+ infoWindow: const InfoWindow(
+ title: 'title for test',
+ snippet: 'some snippet',
+ ),
+ ),
+ };
+
+ await controller.addMarkers(markers);
+
+ expect(controller.markers.length, 1);
+ final HTMLElement? content = controller
+ .markers[const MarkerId('1')]?.infoWindow?.content as HTMLElement?;
+
+ content?.click();
+
+ final MapEvent