-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Basic InfoWindow implementation (#41)
- Loading branch information
Showing
3 changed files
with
213 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
import { Popup, PopupOptions } from "maplibre-gl"; | ||
import { LatLngToLngLat } from "./googleCommon"; | ||
|
||
class MigrationInfoWindow { | ||
#popup: Popup; | ||
|
||
constructor(options?) { | ||
const maplibreOptions: PopupOptions = {}; | ||
|
||
// maxWidth - set MapLibre 'maxWidth' option | ||
if ("maxWidth" in options) { | ||
maplibreOptions.maxWidth = options.maxWidth + "px"; | ||
} | ||
|
||
this.#popup = new Popup(maplibreOptions); | ||
|
||
// content can be string, HTMLElement, or string containing HTML | ||
if ("content" in options) { | ||
if (typeof options.content === "string") { | ||
if (this._containsOnlyHTMLElements(options.content)) { | ||
this.#popup.setHTML(options.content); | ||
} else { | ||
this.#popup.setText(options.content); | ||
} | ||
} else if (options.content instanceof HTMLElement) { | ||
this.#popup.setDOMContent(options.content); | ||
} | ||
} | ||
|
||
if ("position" in options) { | ||
this.setPosition(options.position); | ||
} | ||
|
||
// TODO: | ||
// pixelOffset - set 'offset' MapLibre option | ||
// minWidth - set by HTMLElement.style.minWidth | ||
// ariaLabel - add to DOM element | ||
} | ||
|
||
// Google: | ||
// - both Marker and LatLng popup/infowindow -> set Marker or LatLng in InfoWindow class and then call InfoWindow.open | ||
// MapLibre: | ||
// - Marker popup/infowindow -> Marker.setPopup then Marker.togglePopup (to open Popup) | ||
// - LatLng popup/infowindow -> Popup.setLngLat (in options when creating popup) then Popup.addTo | ||
open(options?, anchor?) { | ||
if (anchor || options.anchor) { | ||
// Marker specific info window | ||
const marker = anchor !== undefined ? anchor._getMarker() : options.anchor._getMarker(); | ||
marker.setPopup(this.#popup); | ||
if (!this.#popup.isOpen()) { | ||
marker.togglePopup(); | ||
} | ||
} else if (options.map) { | ||
// LatLng specific info window | ||
this.#popup.addTo(options.map._getMap()); | ||
} | ||
|
||
// TODO: shouldFocus - focusAfterOpening, use focus method on DOM element | ||
} | ||
|
||
setPosition(position?) { | ||
if (position) { | ||
const lnglat = LatLngToLngLat(position); | ||
this.#popup.setLngLat(lnglat); | ||
} | ||
} | ||
|
||
// Internal method for manually getting the private #marker property | ||
_getPopup() { | ||
return this.#popup; | ||
} | ||
|
||
// Internal method for checking if a string contains valid HTML | ||
_containsOnlyHTMLElements(str: string): boolean { | ||
// Regular expression to match complete HTML elements (opening and closing tags with content) | ||
const htmlElementRegex = /<([a-z][a-z0-9]*)\b[^>]*>(.*?)<\/\1>/gi; | ||
|
||
// Check if the string contains any complete HTML elements | ||
const hasHTMLElements = str.match(htmlElementRegex) !== null; | ||
|
||
// Check if the string contains any text outside of HTML elements | ||
const textOutsideElements = str.replace(htmlElementRegex, "").trim(); | ||
return hasHTMLElements && textOutsideElements.length === 0; | ||
} | ||
} | ||
|
||
export { MigrationInfoWindow }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
import { MigrationMap } from "../src/maps"; | ||
import { MigrationMarker } from "../src/markers"; | ||
import { MigrationInfoWindow } from "../src/infoWindow"; | ||
|
||
// Mock maplibre because it requires a valid DOM container to create a Map | ||
// We don't need to verify maplibre itself, we just need to verify that | ||
// the values we pass to our google migration classes get transformed | ||
// correctly and our called | ||
jest.mock("maplibre-gl"); | ||
import { Marker, Popup, PopupOptions } from "maplibre-gl"; | ||
|
||
const testLat = 30.268193; // Austin, TX :) | ||
const testLng = -97.7457518; | ||
|
||
afterEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
test("should set marker options", () => { | ||
const testInfoWindow = new MigrationInfoWindow({ | ||
maxWidth: 100, | ||
position: { lat: testLat, lng: testLng }, | ||
}); | ||
|
||
const expectedMaplibreOptions: PopupOptions = { | ||
maxWidth: "100px", | ||
}; | ||
|
||
expect(testInfoWindow).not.toBeNull(); | ||
expect(Popup).toHaveBeenCalledTimes(1); | ||
expect(Popup).toHaveBeenCalledWith(expectedMaplibreOptions); | ||
expect(Popup.prototype.setLngLat).toHaveBeenCalledTimes(1); | ||
expect(Popup.prototype.setLngLat).toHaveBeenCalledWith([testLng, testLat]); | ||
}); | ||
|
||
test("should set marker content option with string", () => { | ||
const testString = "Hello World!"; | ||
const testInfoWindow = new MigrationInfoWindow({ | ||
content: testString, | ||
}); | ||
|
||
expect(testInfoWindow).not.toBeNull(); | ||
expect(Popup).toHaveBeenCalledTimes(1); | ||
expect(Popup.prototype.setText).toHaveBeenCalledTimes(1); | ||
expect(Popup.prototype.setText).toHaveBeenCalledWith(testString); | ||
}); | ||
|
||
test("should set marker content option with string containing HTML", () => { | ||
const htmlString = "<h1>Hello World!</h1>"; | ||
const testInfoWindow = new MigrationInfoWindow({ | ||
content: htmlString, | ||
}); | ||
|
||
expect(testInfoWindow).not.toBeNull(); | ||
expect(Popup).toHaveBeenCalledTimes(1); | ||
expect(Popup.prototype.setHTML).toHaveBeenCalledTimes(1); | ||
expect(Popup.prototype.setHTML).toHaveBeenCalledWith(htmlString); | ||
}); | ||
|
||
test("should set marker content option with HTML elements", () => { | ||
const h1Element = document.createElement("h1"); | ||
h1Element.textContent = "Hello World!"; | ||
const testInfoWindow = new MigrationInfoWindow({ | ||
content: h1Element, | ||
}); | ||
|
||
expect(testInfoWindow).not.toBeNull(); | ||
expect(Popup).toHaveBeenCalledTimes(1); | ||
expect(Popup.prototype.setDOMContent).toHaveBeenCalledTimes(1); | ||
expect(Popup.prototype.setDOMContent).toHaveBeenCalledWith(h1Element); | ||
}); | ||
|
||
test("should call open method on marker with anchor option", () => { | ||
const testInfoWindow = new MigrationInfoWindow({}); | ||
const testMarker = new MigrationMarker({}); | ||
|
||
testInfoWindow.open({ | ||
anchor: testMarker, | ||
}); | ||
|
||
expect(testInfoWindow).not.toBeNull(); | ||
expect(testMarker).not.toBeNull(); | ||
expect(Marker.prototype.setPopup).toHaveBeenCalledTimes(1); | ||
expect(Marker.prototype.setPopup).toHaveBeenCalledWith(testInfoWindow._getPopup()); | ||
expect(Marker.prototype.togglePopup).toHaveBeenCalledTimes(1); | ||
expect(Popup.prototype.isOpen).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
test("should call open method on marker with anchor parameter", () => { | ||
const testInfoWindow = new MigrationInfoWindow({}); | ||
const testMarker = new MigrationMarker({}); | ||
|
||
testInfoWindow.open(undefined, testMarker); | ||
|
||
expect(testInfoWindow).not.toBeNull(); | ||
expect(testMarker).not.toBeNull(); | ||
expect(Marker.prototype.setPopup).toHaveBeenCalledTimes(1); | ||
expect(Marker.prototype.setPopup).toHaveBeenCalledWith(testInfoWindow._getPopup()); | ||
expect(Marker.prototype.togglePopup).toHaveBeenCalledTimes(1); | ||
expect(Popup.prototype.isOpen).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
test("should call open method on marker with lat lng set and map option", () => { | ||
const testInfoWindow = new MigrationInfoWindow({}); | ||
const testMarker = new MigrationMarker({}); | ||
const testMap = new MigrationMap(null, {}); | ||
|
||
testInfoWindow.setPosition({ lat: testLat, lng: testLng }); | ||
testInfoWindow.open({ | ||
map: testMap, | ||
}); | ||
|
||
expect(testInfoWindow).not.toBeNull(); | ||
expect(testMarker).not.toBeNull(); | ||
expect(testMap).not.toBeNull(); | ||
expect(Popup.prototype.addTo).toHaveBeenCalledTimes(1); | ||
expect(Popup.prototype.addTo).toHaveBeenCalledWith(testMap._getMap()); | ||
}); |