2022-04-12 00:40:06 +08:00
|
|
|
/*
|
2024-09-09 21:57:16 +08:00
|
|
|
Copyright 2024 New Vector Ltd.
|
2022-04-12 00:40:06 +08:00
|
|
|
Copyright 2022 The Matrix.org Foundation C.I.C.
|
|
|
|
|
2024-09-09 21:57:16 +08:00
|
|
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
|
|
|
Please see LICENSE files in the repository root for full details.
|
2022-04-12 00:40:06 +08:00
|
|
|
*/
|
|
|
|
|
2022-12-12 19:24:14 +08:00
|
|
|
import React from "react";
|
2023-02-22 18:52:55 +08:00
|
|
|
import { act, fireEvent, getByTestId, render } from "@testing-library/react";
|
2022-12-19 07:17:15 +08:00
|
|
|
import * as maplibregl from "maplibre-gl";
|
2022-12-12 19:24:14 +08:00
|
|
|
import { ClientEvent } from "matrix-js-sdk/src/matrix";
|
|
|
|
import { logger } from "matrix-js-sdk/src/logger";
|
2023-02-07 11:51:58 +08:00
|
|
|
import { mocked } from "jest-mock";
|
2022-12-12 19:24:14 +08:00
|
|
|
|
|
|
|
import Map from "../../../../src/components/views/location/Map";
|
2023-02-07 11:51:58 +08:00
|
|
|
import { getMockClientWithEventEmitter, getMockGeolocationPositionError } from "../../../test-utils";
|
2022-12-12 19:24:14 +08:00
|
|
|
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
|
|
|
|
import { TILE_SERVER_WK_KEY } from "../../../../src/utils/WellKnownUtils";
|
2023-02-07 11:51:58 +08:00
|
|
|
import Modal from "../../../../src/Modal";
|
|
|
|
import ErrorDialog from "../../../../src/components/views/dialogs/ErrorDialog";
|
2022-12-12 19:24:14 +08:00
|
|
|
|
|
|
|
describe("<Map />", () => {
|
2022-04-12 00:40:06 +08:00
|
|
|
const defaultProps = {
|
2022-12-12 19:24:14 +08:00
|
|
|
centerGeoUri: "geo:52,41",
|
|
|
|
id: "test-123",
|
2022-04-12 00:40:06 +08:00
|
|
|
onError: jest.fn(),
|
|
|
|
onClick: jest.fn(),
|
|
|
|
};
|
|
|
|
const matrixClient = getMockClientWithEventEmitter({
|
|
|
|
getClientWellKnown: jest.fn().mockReturnValue({
|
2022-12-12 19:24:14 +08:00
|
|
|
[TILE_SERVER_WK_KEY.name]: { map_style_url: "maps.com" },
|
2022-04-12 00:40:06 +08:00
|
|
|
}),
|
|
|
|
});
|
2022-12-29 23:35:19 +08:00
|
|
|
const getComponent = (props = {}, renderingFn?: any) =>
|
|
|
|
(renderingFn ?? render)(
|
|
|
|
<MatrixClientContext.Provider value={matrixClient}>
|
|
|
|
<Map {...defaultProps} {...props} />
|
|
|
|
</MatrixClientContext.Provider>,
|
|
|
|
);
|
2022-04-12 00:40:06 +08:00
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
jest.clearAllMocks();
|
|
|
|
matrixClient.getClientWellKnown.mockReturnValue({
|
2022-12-12 19:24:14 +08:00
|
|
|
[TILE_SERVER_WK_KEY.name]: { map_style_url: "maps.com" },
|
2022-04-12 00:40:06 +08:00
|
|
|
});
|
|
|
|
|
2022-12-12 19:24:14 +08:00
|
|
|
jest.spyOn(logger, "error").mockRestore();
|
2023-02-07 11:51:58 +08:00
|
|
|
mocked(maplibregl.GeolocateControl).mockClear();
|
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
jest.spyOn(logger, "error").mockRestore();
|
2022-04-12 00:40:06 +08:00
|
|
|
});
|
|
|
|
|
2022-12-19 07:17:15 +08:00
|
|
|
const mapOptions = { container: {} as unknown as HTMLElement, style: "" };
|
|
|
|
const mockMap = new maplibregl.Map(mapOptions);
|
2022-04-12 00:40:06 +08:00
|
|
|
|
2022-12-12 19:24:14 +08:00
|
|
|
it("renders", () => {
|
2022-12-29 23:35:19 +08:00
|
|
|
const { container } = getComponent();
|
|
|
|
expect(container.firstChild).not.toBeNull();
|
2022-04-12 00:40:06 +08:00
|
|
|
});
|
|
|
|
|
2022-12-12 19:24:14 +08:00
|
|
|
describe("onClientWellKnown emits", () => {
|
|
|
|
it("updates map style when style url is truthy", () => {
|
2022-04-12 00:40:06 +08:00
|
|
|
getComponent();
|
|
|
|
|
|
|
|
act(() => {
|
|
|
|
matrixClient.emit(ClientEvent.ClientWellKnown, {
|
2022-12-12 19:24:14 +08:00
|
|
|
[TILE_SERVER_WK_KEY.name]: { map_style_url: "new.maps.com" },
|
2022-04-12 00:40:06 +08:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-12-12 19:24:14 +08:00
|
|
|
expect(mockMap.setStyle).toHaveBeenCalledWith("new.maps.com");
|
2022-04-12 00:40:06 +08:00
|
|
|
});
|
|
|
|
|
2022-12-12 19:24:14 +08:00
|
|
|
it("does not update map style when style url is truthy", () => {
|
2022-04-12 00:40:06 +08:00
|
|
|
getComponent();
|
|
|
|
|
|
|
|
act(() => {
|
|
|
|
matrixClient.emit(ClientEvent.ClientWellKnown, {
|
2022-04-14 21:14:05 +08:00
|
|
|
[TILE_SERVER_WK_KEY.name]: { map_style_url: undefined },
|
2022-04-12 00:40:06 +08:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(mockMap.setStyle).not.toHaveBeenCalledWith();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-12-12 19:24:14 +08:00
|
|
|
describe("map centering", () => {
|
|
|
|
it("does not try to center when no center uri provided", () => {
|
2022-04-12 00:40:06 +08:00
|
|
|
getComponent({ centerGeoUri: null });
|
|
|
|
expect(mockMap.setCenter).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
2022-12-12 19:24:14 +08:00
|
|
|
it("sets map center to centerGeoUri", () => {
|
|
|
|
getComponent({ centerGeoUri: "geo:51,42" });
|
2022-04-12 00:40:06 +08:00
|
|
|
expect(mockMap.setCenter).toHaveBeenCalledWith({ lat: 51, lon: 42 });
|
|
|
|
});
|
|
|
|
|
2022-12-12 19:24:14 +08:00
|
|
|
it("handles invalid centerGeoUri", () => {
|
|
|
|
const logSpy = jest.spyOn(logger, "error").mockImplementation();
|
|
|
|
getComponent({ centerGeoUri: "123 Sesame Street" });
|
2022-04-12 00:40:06 +08:00
|
|
|
expect(mockMap.setCenter).not.toHaveBeenCalled();
|
2022-12-12 19:24:14 +08:00
|
|
|
expect(logSpy).toHaveBeenCalledWith("Could not set map center");
|
2022-04-12 00:40:06 +08:00
|
|
|
});
|
|
|
|
|
2022-12-12 19:24:14 +08:00
|
|
|
it("updates map center when centerGeoUri prop changes", () => {
|
2022-12-29 23:35:19 +08:00
|
|
|
const { rerender } = getComponent({ centerGeoUri: "geo:51,42" });
|
2022-04-12 00:40:06 +08:00
|
|
|
|
2022-12-29 23:35:19 +08:00
|
|
|
getComponent({ centerGeoUri: "geo:53,45" }, rerender);
|
|
|
|
getComponent({ centerGeoUri: "geo:56,47" }, rerender);
|
2022-04-12 00:40:06 +08:00
|
|
|
expect(mockMap.setCenter).toHaveBeenCalledWith({ lat: 51, lon: 42 });
|
|
|
|
expect(mockMap.setCenter).toHaveBeenCalledWith({ lat: 53, lon: 45 });
|
|
|
|
expect(mockMap.setCenter).toHaveBeenCalledWith({ lat: 56, lon: 47 });
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-12-12 19:24:14 +08:00
|
|
|
describe("map bounds", () => {
|
|
|
|
it("does not try to fit map bounds when no bounds provided", () => {
|
2022-04-19 19:35:39 +08:00
|
|
|
getComponent({ bounds: null });
|
|
|
|
expect(mockMap.fitBounds).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
2022-12-12 19:24:14 +08:00
|
|
|
it("fits map to bounds", () => {
|
2022-04-19 19:35:39 +08:00
|
|
|
const bounds = { north: 51, south: 50, east: 42, west: 41 };
|
|
|
|
getComponent({ bounds });
|
2022-12-12 19:24:14 +08:00
|
|
|
expect(mockMap.fitBounds).toHaveBeenCalledWith(
|
|
|
|
new maplibregl.LngLatBounds([bounds.west, bounds.south], [bounds.east, bounds.north]),
|
|
|
|
{ padding: 100, maxZoom: 15 },
|
|
|
|
);
|
2022-04-19 19:35:39 +08:00
|
|
|
});
|
|
|
|
|
2022-12-12 19:24:14 +08:00
|
|
|
it("handles invalid bounds", () => {
|
|
|
|
const logSpy = jest.spyOn(logger, "error").mockImplementation();
|
|
|
|
const bounds = { north: "a", south: "b", east: 42, west: 41 };
|
2022-04-19 19:35:39 +08:00
|
|
|
getComponent({ bounds });
|
|
|
|
expect(mockMap.fitBounds).not.toHaveBeenCalled();
|
2022-12-12 19:24:14 +08:00
|
|
|
expect(logSpy).toHaveBeenCalledWith("Invalid map bounds");
|
2022-04-19 19:35:39 +08:00
|
|
|
});
|
|
|
|
|
2022-12-12 19:24:14 +08:00
|
|
|
it("updates map bounds when bounds prop changes", () => {
|
2022-12-29 23:35:19 +08:00
|
|
|
const { rerender } = getComponent({ centerGeoUri: "geo:51,42" });
|
2022-04-19 19:35:39 +08:00
|
|
|
|
|
|
|
const bounds = { north: 51, south: 50, east: 42, west: 41 };
|
|
|
|
const bounds2 = { north: 53, south: 51, east: 45, west: 44 };
|
2022-12-29 23:35:19 +08:00
|
|
|
|
|
|
|
getComponent({ bounds }, rerender);
|
|
|
|
getComponent({ bounds: bounds2 }, rerender);
|
2022-04-19 19:35:39 +08:00
|
|
|
expect(mockMap.fitBounds).toHaveBeenCalledTimes(2);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-12-12 19:24:14 +08:00
|
|
|
describe("children", () => {
|
|
|
|
it("renders without children", () => {
|
2022-04-12 00:40:06 +08:00
|
|
|
const component = getComponent({ children: null });
|
|
|
|
// no error
|
|
|
|
expect(component).toBeTruthy();
|
|
|
|
});
|
|
|
|
|
2022-12-12 19:24:14 +08:00
|
|
|
it("renders children with map renderProp", () => {
|
2022-12-29 23:35:19 +08:00
|
|
|
const children = ({ map }: { map: maplibregl.Map }) => (
|
|
|
|
<div data-testid="test-child" data-map={map}>
|
2022-12-12 19:24:14 +08:00
|
|
|
Hello, world
|
|
|
|
</div>
|
|
|
|
);
|
2022-04-12 00:40:06 +08:00
|
|
|
|
2022-12-29 23:35:19 +08:00
|
|
|
const { container } = getComponent({ children });
|
2022-04-12 00:40:06 +08:00
|
|
|
|
2022-12-29 23:35:19 +08:00
|
|
|
// passes the map instance to the react children
|
|
|
|
expect(getByTestId(container, "test-child").dataset.map).toBeTruthy();
|
2022-04-12 00:40:06 +08:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2022-12-12 19:24:14 +08:00
|
|
|
describe("onClick", () => {
|
|
|
|
it("eats clicks to maplibre attribution button", () => {
|
2022-04-12 00:40:06 +08:00
|
|
|
const onClick = jest.fn();
|
2022-12-29 23:35:19 +08:00
|
|
|
getComponent({ onClick });
|
2022-04-12 00:40:06 +08:00
|
|
|
|
|
|
|
act(() => {
|
|
|
|
// this is added to the dom by maplibregl
|
|
|
|
// which is mocked
|
|
|
|
// just fake the target
|
2022-12-12 19:24:14 +08:00
|
|
|
const fakeEl = document.createElement("div");
|
|
|
|
fakeEl.className = "maplibregl-ctrl-attrib-button";
|
2022-12-29 23:35:19 +08:00
|
|
|
fireEvent.click(fakeEl);
|
2022-04-12 00:40:06 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
expect(onClick).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
2022-12-12 19:24:14 +08:00
|
|
|
it("calls onClick", () => {
|
2022-04-12 00:40:06 +08:00
|
|
|
const onClick = jest.fn();
|
2022-12-29 23:35:19 +08:00
|
|
|
const { container } = getComponent({ onClick });
|
2022-04-12 00:40:06 +08:00
|
|
|
|
|
|
|
act(() => {
|
2022-12-29 23:35:19 +08:00
|
|
|
fireEvent.click(container.firstChild);
|
2022-04-12 00:40:06 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
expect(onClick).toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
});
|
2023-02-07 11:51:58 +08:00
|
|
|
|
|
|
|
describe("geolocate", () => {
|
|
|
|
it("does not add a geolocate control when allowGeolocate is falsy", () => {
|
|
|
|
getComponent({ allowGeolocate: false });
|
|
|
|
|
|
|
|
// didn't create a geolocation control
|
|
|
|
expect(maplibregl.GeolocateControl).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
|
|
|
|
it("creates a geolocate control and adds it to the map when allowGeolocate is truthy", () => {
|
|
|
|
getComponent({ allowGeolocate: true });
|
|
|
|
|
|
|
|
// didn't create a geolocation control
|
|
|
|
expect(maplibregl.GeolocateControl).toHaveBeenCalledWith({
|
|
|
|
positionOptions: {
|
|
|
|
enableHighAccuracy: true,
|
|
|
|
},
|
|
|
|
trackUserLocation: false,
|
|
|
|
});
|
|
|
|
|
|
|
|
// mocked maplibregl shares mock for each mocked instance
|
|
|
|
// so we can assert the geolocate control was added using this static mock
|
|
|
|
const mockGeolocate = new maplibregl.GeolocateControl({});
|
|
|
|
expect(mockMap.addControl).toHaveBeenCalledWith(mockGeolocate);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("logs and opens a dialog on a geolocation error", () => {
|
|
|
|
const mockGeolocate = new maplibregl.GeolocateControl({});
|
|
|
|
jest.spyOn(mockGeolocate, "on");
|
|
|
|
const logSpy = jest.spyOn(logger, "error").mockImplementation(() => {});
|
|
|
|
jest.spyOn(Modal, "createDialog");
|
|
|
|
|
|
|
|
const { rerender } = getComponent({ allowGeolocate: true });
|
|
|
|
|
|
|
|
// wait for component to settle
|
|
|
|
getComponent({ allowGeolocate: true }, rerender);
|
|
|
|
expect(mockGeolocate.on).toHaveBeenCalledWith("error", expect.any(Function));
|
|
|
|
const error = getMockGeolocationPositionError(1, "Test");
|
|
|
|
|
|
|
|
// @ts-ignore pretend to have geolocate emit an error
|
|
|
|
mockGeolocate.emit("error", error);
|
|
|
|
|
|
|
|
expect(logSpy).toHaveBeenCalledWith("Could not fetch location", error);
|
|
|
|
|
|
|
|
expect(Modal.createDialog).toHaveBeenCalledWith(ErrorDialog, {
|
|
|
|
title: "Could not fetch location",
|
|
|
|
description:
|
|
|
|
"Element was denied permission to fetch your location. Please allow location access in your browser settings.",
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it("unsubscribes from geolocate errors on destroy", () => {
|
|
|
|
const mockGeolocate = new maplibregl.GeolocateControl({});
|
|
|
|
jest.spyOn(mockGeolocate, "on");
|
|
|
|
jest.spyOn(mockGeolocate, "off");
|
|
|
|
jest.spyOn(Modal, "createDialog");
|
|
|
|
|
|
|
|
const { unmount } = getComponent({ allowGeolocate: true });
|
|
|
|
|
|
|
|
expect(mockGeolocate.on).toHaveBeenCalled();
|
|
|
|
|
|
|
|
unmount();
|
|
|
|
|
|
|
|
expect(mockGeolocate.off).toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
});
|
2022-04-12 00:40:06 +08:00
|
|
|
});
|