Merge matrix-react-sdk into element-web
Merge remote-tracking branch 'repomerge/t3chguy/repomerge' into t3chguy/repo-merge Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render, screen } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import LiveDurationDropdown, {
|
||||
DEFAULT_DURATION_MS,
|
||||
} from "../../../../../src/components/views/location/LiveDurationDropdown";
|
||||
import { mockPlatformPeg } from "../../../../test-utils";
|
||||
|
||||
mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });
|
||||
|
||||
describe("<LiveDurationDropdown />", () => {
|
||||
const defaultProps = {
|
||||
timeout: DEFAULT_DURATION_MS,
|
||||
onChange: jest.fn(),
|
||||
};
|
||||
const renderComponent = (props = {}) => render(<LiveDurationDropdown {...defaultProps} {...props} />);
|
||||
|
||||
const getOption = (duration: string) => screen.getByRole("option", { name: `Share for ${duration}` });
|
||||
const getSelectedOption = (duration: string) => screen.getByRole("button", { name: `Share for ${duration}` });
|
||||
const openDropdown = async () => {
|
||||
await userEvent.click(screen.getByRole("button"));
|
||||
};
|
||||
|
||||
it("renders timeout as selected option", () => {
|
||||
renderComponent();
|
||||
expect(getSelectedOption("15m")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders non-default timeout as selected option", () => {
|
||||
const timeout = 1234567;
|
||||
renderComponent({ timeout });
|
||||
expect(getSelectedOption("21m")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders a dropdown option for a non-default timeout value", async () => {
|
||||
const timeout = 1234567;
|
||||
renderComponent({ timeout });
|
||||
await openDropdown();
|
||||
expect(getOption("21m")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("updates value on option selection", async () => {
|
||||
const onChange = jest.fn();
|
||||
renderComponent({ onChange });
|
||||
|
||||
const ONE_HOUR = 3600000;
|
||||
|
||||
await openDropdown();
|
||||
await userEvent.click(getOption("1h"));
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith(ONE_HOUR);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { act, fireEvent, render, RenderResult } from "jest-matrix-react";
|
||||
import * as maplibregl from "maplibre-gl";
|
||||
import { RoomMember, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { mocked } from "jest-mock";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import LocationPicker from "../../../../../src/components/views/location/LocationPicker";
|
||||
import { LocationShareType } from "../../../../../src/components/views/location/shareLocation";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import { getMockClientWithEventEmitter, mockPlatformPeg } from "../../../../test-utils";
|
||||
import { findMapStyleUrl, LocationShareError } from "../../../../../src/utils/location";
|
||||
|
||||
jest.mock("../../../../../src/utils/location/findMapStyleUrl", () => ({
|
||||
findMapStyleUrl: jest.fn().mockReturnValue("tileserver.com"),
|
||||
}));
|
||||
|
||||
// dropdown uses this
|
||||
mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });
|
||||
|
||||
describe("LocationPicker", () => {
|
||||
describe("<LocationPicker />", () => {
|
||||
const roomId = "!room:server.org";
|
||||
const userId = "@user:server.org";
|
||||
const sender = new RoomMember(roomId, userId);
|
||||
const defaultProps = {
|
||||
sender,
|
||||
shareType: LocationShareType.Own,
|
||||
onChoose: jest.fn(),
|
||||
onFinished: jest.fn(),
|
||||
};
|
||||
const mockClient = getMockClientWithEventEmitter({
|
||||
isGuest: jest.fn(),
|
||||
getClientWellKnown: jest.fn(),
|
||||
});
|
||||
const getComponent = (props = {}): RenderResult =>
|
||||
render(<LocationPicker {...defaultProps} {...props} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<MatrixClientContext.Provider value={mockClient}>{children}</MatrixClientContext.Provider>
|
||||
),
|
||||
});
|
||||
|
||||
const mapOptions = { container: {} as unknown as HTMLElement, style: "" };
|
||||
const mockMap = new maplibregl.Map(mapOptions);
|
||||
const mockGeolocate = new maplibregl.GeolocateControl({});
|
||||
const mockMarker = new maplibregl.Marker();
|
||||
|
||||
const mockGeolocationPosition = {
|
||||
coords: {
|
||||
latitude: 43.2,
|
||||
longitude: 12.4,
|
||||
altitude: 12.3,
|
||||
accuracy: 21,
|
||||
},
|
||||
timestamp: 123,
|
||||
};
|
||||
const mockClickEvent = {
|
||||
lngLat: {
|
||||
lat: 43.2,
|
||||
lng: 12.4,
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(logger, "error").mockRestore();
|
||||
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient as unknown as MatrixClient);
|
||||
jest.clearAllMocks();
|
||||
mocked(mockMap).addControl.mockReset();
|
||||
mocked(findMapStyleUrl).mockReturnValue("tileserver.com");
|
||||
});
|
||||
|
||||
it("displays error when map emits an error", () => {
|
||||
// suppress expected error log
|
||||
jest.spyOn(logger, "error").mockImplementation(() => {});
|
||||
const { getByTestId, getByText } = getComponent();
|
||||
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
mocked(mockMap).emit("error", { error: "Something went wrong" });
|
||||
});
|
||||
|
||||
expect(getByTestId("map-rendering-error")).toBeInTheDocument();
|
||||
expect(
|
||||
getByText(
|
||||
"This homeserver is not configured correctly to display maps, " +
|
||||
"or the configured map server may be unreachable.",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("displays error when map display is not configured properly", () => {
|
||||
// suppress expected error log
|
||||
jest.spyOn(logger, "error").mockImplementation(() => {});
|
||||
mocked(findMapStyleUrl).mockImplementation(() => {
|
||||
throw new Error(LocationShareError.MapStyleUrlNotConfigured);
|
||||
});
|
||||
|
||||
const { getByText } = getComponent();
|
||||
|
||||
expect(getByText("This homeserver is not configured to display maps.")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("displays error when WebGl is not enabled", () => {
|
||||
// suppress expected error log
|
||||
jest.spyOn(logger, "error").mockImplementation(() => {});
|
||||
mocked(findMapStyleUrl).mockImplementation(() => {
|
||||
throw new Error("Failed to initialize WebGL");
|
||||
});
|
||||
|
||||
const { getByText } = getComponent();
|
||||
|
||||
expect(
|
||||
getByText("WebGL is required to display maps, please enable it in your browser settings."),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("displays error when map setup throws", () => {
|
||||
// suppress expected error log
|
||||
jest.spyOn(logger, "error").mockImplementation(() => {});
|
||||
|
||||
// throw an error
|
||||
mocked(mockMap).addControl.mockImplementation(() => {
|
||||
throw new Error("oups");
|
||||
});
|
||||
|
||||
const { getByText } = getComponent();
|
||||
|
||||
expect(
|
||||
getByText(
|
||||
"This homeserver is not configured correctly to display maps, " +
|
||||
"or the configured map server may be unreachable.",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("initiates map with geolocation", () => {
|
||||
getComponent();
|
||||
|
||||
expect(mockMap.addControl).toHaveBeenCalledWith(mockGeolocate);
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
mocked(mockMap).emit("load");
|
||||
});
|
||||
|
||||
expect(mockGeolocate.trigger).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const testUserLocationShareTypes = (shareType: LocationShareType.Own | LocationShareType.Live) => {
|
||||
describe("user location behaviours", () => {
|
||||
it("closes and displays error when geolocation errors", () => {
|
||||
// suppress expected error log
|
||||
jest.spyOn(logger, "error").mockImplementation(() => {});
|
||||
const onFinished = jest.fn();
|
||||
getComponent({ onFinished, shareType });
|
||||
|
||||
expect(mockMap.addControl).toHaveBeenCalledWith(mockGeolocate);
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
mockMap.emit("load");
|
||||
// @ts-ignore
|
||||
mockGeolocate.emit("error", {});
|
||||
});
|
||||
|
||||
// dialog is closed on error
|
||||
expect(onFinished).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sets position on geolocate event", () => {
|
||||
const { container, getByTestId } = getComponent({ shareType });
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
mocked(mockGeolocate).emit("geolocate", mockGeolocationPosition);
|
||||
});
|
||||
|
||||
// marker added
|
||||
expect(maplibregl.Marker).toHaveBeenCalled();
|
||||
expect(mockMarker.setLngLat).toHaveBeenCalledWith(new maplibregl.LngLat(12.4, 43.2));
|
||||
// submit button is enabled when position is truthy
|
||||
expect(getByTestId("location-picker-submit-button")).not.toBeDisabled();
|
||||
expect(container.querySelector(".mx_BaseAvatar")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("disables submit button until geolocation completes", () => {
|
||||
const onChoose = jest.fn();
|
||||
const { getByTestId } = getComponent({ shareType, onChoose });
|
||||
|
||||
// button is disabled
|
||||
expect(getByTestId("location-picker-submit-button")).toBeDisabled();
|
||||
fireEvent.click(getByTestId("location-picker-submit-button"));
|
||||
// nothing happens on button click
|
||||
expect(onChoose).not.toHaveBeenCalled();
|
||||
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
mocked(mockGeolocate).emit("geolocate", mockGeolocationPosition);
|
||||
});
|
||||
|
||||
// submit button is enabled when position is truthy
|
||||
expect(getByTestId("location-picker-submit-button")).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it("submits location", () => {
|
||||
const onChoose = jest.fn();
|
||||
const { getByTestId } = getComponent({ onChoose, shareType });
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
mocked(mockGeolocate).emit("geolocate", mockGeolocationPosition);
|
||||
// make sure button is enabled
|
||||
});
|
||||
|
||||
fireEvent.click(getByTestId("location-picker-submit-button"));
|
||||
// content of this call is tested in LocationShareMenu-test
|
||||
expect(onChoose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe("for Own location share type", () => {
|
||||
testUserLocationShareTypes(LocationShareType.Own);
|
||||
});
|
||||
|
||||
describe("for Live location share type", () => {
|
||||
const shareType = LocationShareType.Live;
|
||||
testUserLocationShareTypes(shareType);
|
||||
|
||||
it("renders live duration dropdown with default option", () => {
|
||||
const { getByText } = getComponent({ shareType });
|
||||
expect(getByText("Share for 15m")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("updates selected duration", () => {
|
||||
const { getByText, getByLabelText } = getComponent({ shareType });
|
||||
|
||||
// open dropdown
|
||||
fireEvent.click(getByLabelText("Share for 15m"));
|
||||
|
||||
fireEvent.click(getByText("Share for 1h"));
|
||||
|
||||
// value updated
|
||||
expect(getByText("Share for 1h")).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("for Pin drop location share type", () => {
|
||||
const shareType = LocationShareType.Pin;
|
||||
it("initiates map with geolocation", () => {
|
||||
getComponent({ shareType });
|
||||
|
||||
expect(mockMap.addControl).toHaveBeenCalledWith(mockGeolocate);
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
mocked(mockMap).emit("load");
|
||||
});
|
||||
|
||||
expect(mockGeolocate.trigger).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("removes geolocation control on geolocation error", () => {
|
||||
// suppress expected error log
|
||||
jest.spyOn(logger, "error").mockImplementation(() => {});
|
||||
const onFinished = jest.fn();
|
||||
getComponent({ onFinished, shareType });
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
mockMap.emit("load");
|
||||
// @ts-ignore
|
||||
mockGeolocate.emit("error", {});
|
||||
});
|
||||
|
||||
expect(mockMap.removeControl).toHaveBeenCalledWith(mockGeolocate);
|
||||
// dialog is not closed
|
||||
expect(onFinished).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not set position on geolocate event", () => {
|
||||
mocked(maplibregl.Marker).mockClear();
|
||||
const { container } = getComponent({ shareType });
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
mocked(mockGeolocate).emit("geolocate", mockGeolocationPosition);
|
||||
});
|
||||
|
||||
// marker not added
|
||||
expect(container.querySelector("mx_Marker")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("sets position on click event", () => {
|
||||
const { container } = getComponent({ shareType });
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
mocked(mockMap).emit("click", mockClickEvent);
|
||||
});
|
||||
|
||||
// marker added
|
||||
expect(maplibregl.Marker).toHaveBeenCalled();
|
||||
expect(mockMarker.setLngLat).toHaveBeenCalledWith(new maplibregl.LngLat(12.4, 43.2));
|
||||
|
||||
// marker is set, icon not avatar
|
||||
expect(container.querySelector(".mx_Marker_icon")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("submits location", () => {
|
||||
const onChoose = jest.fn();
|
||||
const { getByTestId } = getComponent({ onChoose, shareType });
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
mocked(mockMap).emit("click", mockClickEvent);
|
||||
});
|
||||
|
||||
fireEvent.click(getByTestId("location-picker-submit-button"));
|
||||
|
||||
// content of this call is tested in LocationShareMenu-test
|
||||
expect(onChoose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,465 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { mocked } from "jest-mock";
|
||||
import { RoomMember, RelationType, MatrixClient, M_ASSET, LocationAssetType } from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { act, fireEvent, render, RenderResult } from "jest-matrix-react";
|
||||
import * as maplibregl from "maplibre-gl";
|
||||
|
||||
import LocationShareMenu from "../../../../../src/components/views/location/LocationShareMenu";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import { ChevronFace } from "../../../../../src/components/structures/ContextMenu";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import { LocationShareType } from "../../../../../src/components/views/location/shareLocation";
|
||||
import {
|
||||
flushPromisesWithFakeTimers,
|
||||
getMockClientWithEventEmitter,
|
||||
mockClientMethodsUser,
|
||||
setupAsyncStoreWithClient,
|
||||
} from "../../../../test-utils";
|
||||
import Modal from "../../../../../src/Modal";
|
||||
import { DEFAULT_DURATION_MS } from "../../../../../src/components/views/location/LiveDurationDropdown";
|
||||
import { OwnBeaconStore } from "../../../../../src/stores/OwnBeaconStore";
|
||||
import { SettingLevel } from "../../../../../src/settings/SettingLevel";
|
||||
import QuestionDialog from "../../../../../src/components/views/dialogs/QuestionDialog";
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
jest.mock("../../../../../src/utils/location/findMapStyleUrl", () => ({
|
||||
findMapStyleUrl: jest.fn().mockReturnValue("test"),
|
||||
}));
|
||||
|
||||
jest.mock("../../../../../src/settings/SettingsStore", () => ({
|
||||
getValue: jest.fn(),
|
||||
setValue: jest.fn(),
|
||||
monitorSetting: jest.fn(),
|
||||
watchSetting: jest.fn(),
|
||||
unwatchSetting: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../../../../../src/stores/OwnProfileStore", () => ({
|
||||
OwnProfileStore: {
|
||||
instance: {
|
||||
displayName: "Ernie",
|
||||
getHttpAvatarUrl: jest.fn().mockReturnValue("image.com/img"),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock("../../../../../src/Modal", () => ({
|
||||
createDialog: jest.fn(),
|
||||
on: jest.fn(),
|
||||
off: jest.fn(),
|
||||
ModalManagerEvent: { Opened: "opened" },
|
||||
}));
|
||||
|
||||
describe("<LocationShareMenu />", () => {
|
||||
const userId = "@ernie:server.org";
|
||||
const mockClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(userId),
|
||||
getClientWellKnown: jest.fn().mockResolvedValue({
|
||||
map_style_url: "maps.com",
|
||||
}),
|
||||
sendMessage: jest.fn(),
|
||||
unstable_createLiveBeacon: jest.fn().mockResolvedValue({ event_id: "1" }),
|
||||
unstable_setLiveBeacon: jest.fn().mockResolvedValue({ event_id: "1" }),
|
||||
getVisibleRooms: jest.fn().mockReturnValue([]),
|
||||
});
|
||||
|
||||
const defaultProps = {
|
||||
menuPosition: {
|
||||
top: 1,
|
||||
left: 1,
|
||||
chevronFace: ChevronFace.Bottom,
|
||||
},
|
||||
onFinished: jest.fn(),
|
||||
openMenu: jest.fn(),
|
||||
roomId: "!room:server.org",
|
||||
sender: new RoomMember("!room:server.org", userId),
|
||||
};
|
||||
|
||||
const mockGeolocate = new maplibregl.GeolocateControl({});
|
||||
jest.spyOn(mockGeolocate, "on");
|
||||
const mapOptions = { container: {} as unknown as HTMLElement, style: "" };
|
||||
const mockMap = new maplibregl.Map(mapOptions);
|
||||
jest.spyOn(mockMap, "on");
|
||||
|
||||
const position = {
|
||||
coords: {
|
||||
latitude: -36.24484561954707,
|
||||
longitude: 175.46884959563613,
|
||||
accuracy: 10,
|
||||
},
|
||||
timestamp: 1646305006802,
|
||||
type: "geolocate",
|
||||
};
|
||||
|
||||
const makeOwnBeaconStore = async () => {
|
||||
const store = OwnBeaconStore.instance;
|
||||
|
||||
await setupAsyncStoreWithClient(store, mockClient);
|
||||
return store;
|
||||
};
|
||||
|
||||
const getComponent = (props = {}): RenderResult =>
|
||||
render(<LocationShareMenu {...defaultProps} {...props} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<MatrixClientContext.Provider value={mockClient}>{children}</MatrixClientContext.Provider>
|
||||
),
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(logger, "error").mockRestore();
|
||||
mocked(SettingsStore).getValue.mockReturnValue(false);
|
||||
mockClient.sendMessage.mockClear();
|
||||
mockClient.unstable_createLiveBeacon.mockClear().mockResolvedValue({ event_id: "1" });
|
||||
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient as unknown as MatrixClient);
|
||||
mocked(Modal).createDialog.mockClear();
|
||||
|
||||
jest.clearAllMocks();
|
||||
|
||||
await makeOwnBeaconStore();
|
||||
});
|
||||
|
||||
const getBackButton = (getByLabelText: RenderResult["getByLabelText"]) => getByLabelText("Back");
|
||||
|
||||
const getCancelButton = (getByLabelText: RenderResult["getByLabelText"]) => getByLabelText("Close");
|
||||
|
||||
const setLocationGeolocate = () => {
|
||||
// get the callback LocationShareMenu registered for geolocate
|
||||
expect(mocked(mockGeolocate.on)).toHaveBeenCalledWith("geolocate", expect.any(Function));
|
||||
const [, onGeolocateCallback] = mocked(mockGeolocate.on).mock.calls.find(([event]) => event === "geolocate")!;
|
||||
|
||||
// set the location
|
||||
onGeolocateCallback(position);
|
||||
};
|
||||
|
||||
const setLocationClick = () => {
|
||||
// get the callback LocationShareMenu registered for geolocate
|
||||
expect(mocked(mockMap.on)).toHaveBeenCalledWith("click", expect.any(Function));
|
||||
const [, onMapClickCallback] = mocked(mockMap.on).mock.calls.find(([event]) => event === "click")!;
|
||||
|
||||
const event = {
|
||||
lngLat: { lng: position.coords.longitude, lat: position.coords.latitude },
|
||||
} as unknown as maplibregl.MapMouseEvent;
|
||||
// set the location
|
||||
onMapClickCallback(event);
|
||||
};
|
||||
|
||||
const shareTypeLabels: Record<LocationShareType, string> = {
|
||||
[LocationShareType.Own]: "My current location",
|
||||
[LocationShareType.Live]: "My live location",
|
||||
[LocationShareType.Pin]: "Drop a Pin",
|
||||
};
|
||||
const setShareType = (getByText: RenderResult["getByText"], shareType: LocationShareType) => {
|
||||
fireEvent.click(getByText(shareTypeLabels[shareType]));
|
||||
};
|
||||
|
||||
describe("when only Own share type is enabled", () => {
|
||||
beforeEach(() => enableSettings([]));
|
||||
|
||||
it("renders own and live location options", () => {
|
||||
const { getByText } = getComponent();
|
||||
expect(getByText(shareTypeLabels[LocationShareType.Own])).toBeInTheDocument();
|
||||
expect(getByText(shareTypeLabels[LocationShareType.Live])).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders back button from location picker screen", () => {
|
||||
const { getByText, getByLabelText } = getComponent();
|
||||
setShareType(getByText, LocationShareType.Own);
|
||||
|
||||
expect(getBackButton(getByLabelText)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("clicking cancel button from location picker closes dialog", () => {
|
||||
const onFinished = jest.fn();
|
||||
const { getByLabelText } = getComponent({ onFinished });
|
||||
|
||||
fireEvent.click(getCancelButton(getByLabelText));
|
||||
|
||||
expect(onFinished).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("creates static own location share event on submission", () => {
|
||||
const onFinished = jest.fn();
|
||||
const { getByText } = getComponent({ onFinished });
|
||||
|
||||
setShareType(getByText, LocationShareType.Own);
|
||||
|
||||
setLocationGeolocate();
|
||||
|
||||
fireEvent.click(getByText("Share location"));
|
||||
|
||||
expect(onFinished).toHaveBeenCalled();
|
||||
const [messageRoomId, relation, messageBody] = mockClient.sendMessage.mock.calls[0];
|
||||
expect(messageRoomId).toEqual(defaultProps.roomId);
|
||||
expect(relation).toEqual(null);
|
||||
expect(messageBody).toEqual(
|
||||
expect.objectContaining({
|
||||
[M_ASSET.name]: {
|
||||
type: LocationAssetType.Self,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with pin drop share type enabled", () => {
|
||||
it("renders share type switch with own and pin drop options", () => {
|
||||
const { getByText } = getComponent();
|
||||
expect(document.querySelector(".mx_LocationPicker")).not.toBeInTheDocument();
|
||||
|
||||
expect(getByText(shareTypeLabels[LocationShareType.Own])).toBeInTheDocument();
|
||||
expect(getByText(shareTypeLabels[LocationShareType.Pin])).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render back button on share type screen", () => {
|
||||
const { queryByLabelText } = getComponent();
|
||||
expect(queryByLabelText("Back")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("clicking cancel button from share type screen closes dialog", () => {
|
||||
const onFinished = jest.fn();
|
||||
const { getByLabelText } = getComponent({ onFinished });
|
||||
|
||||
fireEvent.click(getCancelButton(getByLabelText));
|
||||
|
||||
expect(onFinished).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("selecting own location share type advances to location picker", () => {
|
||||
const { getByText } = getComponent();
|
||||
|
||||
setShareType(getByText, LocationShareType.Own);
|
||||
|
||||
expect(document.querySelector(".mx_LocationPicker")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("clicking back button from location picker screen goes back to share screen", () => {
|
||||
const onFinished = jest.fn();
|
||||
const { getByText, getByLabelText } = getComponent({ onFinished });
|
||||
|
||||
// advance to location picker
|
||||
setShareType(getByText, LocationShareType.Own);
|
||||
|
||||
expect(document.querySelector(".mx_LocationPicker")).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(getBackButton(getByLabelText));
|
||||
|
||||
// back to share type
|
||||
expect(getByText("What location type do you want to share?")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("creates pin drop location share event on submission", () => {
|
||||
const onFinished = jest.fn();
|
||||
const { getByText } = getComponent({ onFinished });
|
||||
|
||||
// advance to location picker
|
||||
setShareType(getByText, LocationShareType.Pin);
|
||||
|
||||
setLocationClick();
|
||||
|
||||
fireEvent.click(getByText("Share location"));
|
||||
|
||||
expect(onFinished).toHaveBeenCalled();
|
||||
const [messageRoomId, relation, messageBody] = mockClient.sendMessage.mock.calls[0];
|
||||
expect(messageRoomId).toEqual(defaultProps.roomId);
|
||||
expect(relation).toEqual(null);
|
||||
expect(messageBody).toEqual(
|
||||
expect.objectContaining({
|
||||
[M_ASSET.name]: {
|
||||
type: LocationAssetType.Pin,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with live location disabled", () => {
|
||||
beforeEach(() => enableSettings([]));
|
||||
|
||||
it("goes to labs flag screen after live options is clicked", () => {
|
||||
const onFinished = jest.fn();
|
||||
const { getByText, getByTestId } = getComponent({ onFinished });
|
||||
|
||||
setShareType(getByText, LocationShareType.Live);
|
||||
|
||||
expect(getByTestId("location-picker-enable-live-share")).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("disables OK button when labs flag is not enabled", () => {
|
||||
const { getByText } = getComponent();
|
||||
|
||||
setShareType(getByText, LocationShareType.Live);
|
||||
|
||||
expect(getByText("OK").hasAttribute("disabled")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("enables OK button when labs flag is toggled to enabled", () => {
|
||||
const { getByText, getByLabelText } = getComponent();
|
||||
|
||||
setShareType(getByText, LocationShareType.Live);
|
||||
|
||||
fireEvent.click(getByLabelText("Enable live location sharing"));
|
||||
|
||||
expect(getByText("OK").hasAttribute("disabled")).toBeFalsy();
|
||||
});
|
||||
|
||||
it("enables live share setting on ok button submit", () => {
|
||||
const { getByText, getByLabelText } = getComponent();
|
||||
|
||||
setShareType(getByText, LocationShareType.Live);
|
||||
|
||||
fireEvent.click(getByLabelText("Enable live location sharing"));
|
||||
|
||||
fireEvent.click(getByText("OK"));
|
||||
|
||||
expect(SettingsStore.setValue).toHaveBeenCalledWith(
|
||||
"feature_location_share_live",
|
||||
null,
|
||||
SettingLevel.DEVICE,
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("navigates to location picker when live share is enabled in settings store", () => {
|
||||
// @ts-ignore
|
||||
mocked(SettingsStore.watchSetting).mockImplementation((featureName, roomId, callback) => {
|
||||
callback(featureName, roomId, SettingLevel.DEVICE, "", "");
|
||||
window.setTimeout(() => {
|
||||
callback(featureName, roomId, SettingLevel.DEVICE, "", "");
|
||||
}, 1000);
|
||||
});
|
||||
mocked(SettingsStore.getValue).mockReturnValue(false);
|
||||
const { getByText, getByLabelText } = getComponent();
|
||||
|
||||
setShareType(getByText, LocationShareType.Live);
|
||||
|
||||
// we're on enable live share screen
|
||||
expect(getByLabelText("Enable live location sharing")).toBeInTheDocument();
|
||||
|
||||
act(() => {
|
||||
mocked(SettingsStore.getValue).mockReturnValue(true);
|
||||
// advance so watchSetting will update the value
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
// advanced to location picker
|
||||
expect(document.querySelector(".mx_LocationPicker")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Live location share", () => {
|
||||
beforeEach(() => enableSettings(["feature_location_share_live"]));
|
||||
|
||||
it("does not display live location share option when composer has a relation", () => {
|
||||
const relation = {
|
||||
rel_type: RelationType.Thread,
|
||||
event_id: "12345",
|
||||
};
|
||||
const { queryByText } = getComponent({ relation });
|
||||
|
||||
expect(queryByText(shareTypeLabels[LocationShareType.Live])).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("creates beacon info event on submission", async () => {
|
||||
const onFinished = jest.fn();
|
||||
const { getByText } = getComponent({ onFinished });
|
||||
|
||||
// advance to location picker
|
||||
setShareType(getByText, LocationShareType.Live);
|
||||
setLocationGeolocate();
|
||||
|
||||
fireEvent.click(getByText("Share location"));
|
||||
|
||||
// flush stopping existing beacons promises
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
expect(onFinished).toHaveBeenCalled();
|
||||
const [eventRoomId, eventContent] = mockClient.unstable_createLiveBeacon.mock.calls[0];
|
||||
expect(eventRoomId).toEqual(defaultProps.roomId);
|
||||
expect(eventContent).toEqual(
|
||||
expect.objectContaining({
|
||||
// default timeout
|
||||
timeout: DEFAULT_DURATION_MS,
|
||||
description: `Ernie's live location`,
|
||||
live: true,
|
||||
[M_ASSET.name]: {
|
||||
type: LocationAssetType.Self,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("opens error dialog when beacon creation fails", async () => {
|
||||
// stub logger to keep console clean from expected error
|
||||
const logSpy = jest.spyOn(logger, "error").mockReturnValue(undefined);
|
||||
const error = new Error("oh no");
|
||||
mockClient.unstable_createLiveBeacon.mockRejectedValue(error);
|
||||
const { getByText } = getComponent();
|
||||
|
||||
// advance to location picker
|
||||
setShareType(getByText, LocationShareType.Live);
|
||||
setLocationGeolocate();
|
||||
|
||||
fireEvent.click(getByText("Share location"));
|
||||
|
||||
await flushPromisesWithFakeTimers();
|
||||
await flushPromisesWithFakeTimers();
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
expect(logSpy).toHaveBeenCalledWith("We couldn't start sharing your live location", error);
|
||||
expect(mocked(Modal).createDialog).toHaveBeenCalledWith(
|
||||
QuestionDialog,
|
||||
expect.objectContaining({
|
||||
button: "Try again",
|
||||
description: "Element could not send your location. Please try again later.",
|
||||
title: `We couldn't send your location`,
|
||||
cancelButton: "Cancel",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("opens error dialog when beacon creation fails with permission error", async () => {
|
||||
// stub logger to keep console clean from expected error
|
||||
const logSpy = jest.spyOn(logger, "error").mockReturnValue(undefined);
|
||||
const error = { errcode: "M_FORBIDDEN" } as unknown as Error;
|
||||
mockClient.unstable_createLiveBeacon.mockRejectedValue(error);
|
||||
const { getByText } = getComponent();
|
||||
|
||||
// advance to location picker
|
||||
setShareType(getByText, LocationShareType.Live);
|
||||
setLocationGeolocate();
|
||||
|
||||
fireEvent.click(getByText("Share location"));
|
||||
|
||||
await flushPromisesWithFakeTimers();
|
||||
await flushPromisesWithFakeTimers();
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
expect(logSpy).toHaveBeenCalledWith("Insufficient permissions to start sharing your live location", error);
|
||||
expect(mocked(Modal).createDialog).toHaveBeenCalledWith(
|
||||
QuestionDialog,
|
||||
expect.objectContaining({
|
||||
button: "OK",
|
||||
description: "You need to have the right permissions in order to share locations in this room.",
|
||||
title: `You don't have permission to share locations`,
|
||||
hasCancelButton: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function enableSettings(settings: string[]) {
|
||||
mocked(SettingsStore).getValue.mockReturnValue(false);
|
||||
mocked(SettingsStore).getValue.mockImplementation((settingName: string): any => settings.includes(settingName));
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render, RenderResult } from "jest-matrix-react";
|
||||
import { RoomMember, LocationAssetType } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import LocationViewDialog from "../../../../../src/components/views/location/LocationViewDialog";
|
||||
import { TILE_SERVER_WK_KEY } from "../../../../../src/utils/WellKnownUtils";
|
||||
import { getMockClientWithEventEmitter, makeLocationEvent } from "../../../../test-utils";
|
||||
|
||||
describe("<LocationViewDialog />", () => {
|
||||
const roomId = "!room:server";
|
||||
const userId = "@user:server";
|
||||
const mockClient = getMockClientWithEventEmitter({
|
||||
getClientWellKnown: jest.fn().mockReturnValue({
|
||||
[TILE_SERVER_WK_KEY.name]: { map_style_url: "maps.com" },
|
||||
}),
|
||||
isGuest: jest.fn().mockReturnValue(false),
|
||||
});
|
||||
const defaultEvent = makeLocationEvent("geo:51.5076,-0.1276", LocationAssetType.Pin);
|
||||
const defaultProps = {
|
||||
matrixClient: mockClient,
|
||||
mxEvent: defaultEvent,
|
||||
onFinished: jest.fn(),
|
||||
};
|
||||
const getComponent = (props = {}): RenderResult => render(<LocationViewDialog {...defaultProps} {...props} />);
|
||||
|
||||
it("renders map correctly", () => {
|
||||
const { container } = getComponent();
|
||||
expect(container.querySelector(".mx_Map")).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders marker correctly for self share", () => {
|
||||
const selfShareEvent = makeLocationEvent("geo:51.5076,-0.1276", LocationAssetType.Self);
|
||||
const member = new RoomMember(roomId, userId);
|
||||
// @ts-ignore cheat assignment to property
|
||||
selfShareEvent.sender = member;
|
||||
const { container } = getComponent({ mxEvent: selfShareEvent });
|
||||
expect(container.querySelector(".mx_BaseAvatar")?.getAttribute("title")).toEqual(userId);
|
||||
});
|
||||
});
|
||||
269
test/unit-tests/components/views/location/Map-test.tsx
Normal file
269
test/unit-tests/components/views/location/Map-test.tsx
Normal file
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { act, fireEvent, getByTestId, render } from "jest-matrix-react";
|
||||
import * as maplibregl from "maplibre-gl";
|
||||
import { ClientEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import Map from "../../../../../src/components/views/location/Map";
|
||||
import { getMockClientWithEventEmitter, getMockGeolocationPositionError } from "../../../../test-utils";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import { TILE_SERVER_WK_KEY } from "../../../../../src/utils/WellKnownUtils";
|
||||
import Modal from "../../../../../src/Modal";
|
||||
import ErrorDialog from "../../../../../src/components/views/dialogs/ErrorDialog";
|
||||
|
||||
describe("<Map />", () => {
|
||||
const defaultProps = {
|
||||
centerGeoUri: "geo:52,41",
|
||||
id: "test-123",
|
||||
onError: jest.fn(),
|
||||
onClick: jest.fn(),
|
||||
};
|
||||
const matrixClient = getMockClientWithEventEmitter({
|
||||
getClientWellKnown: jest.fn().mockReturnValue({
|
||||
[TILE_SERVER_WK_KEY.name]: { map_style_url: "maps.com" },
|
||||
}),
|
||||
});
|
||||
const getComponent = (props = {}, renderingFn?: any) =>
|
||||
(renderingFn ?? render)(
|
||||
<MatrixClientContext.Provider value={matrixClient}>
|
||||
<Map {...defaultProps} {...props} />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
matrixClient.getClientWellKnown.mockReturnValue({
|
||||
[TILE_SERVER_WK_KEY.name]: { map_style_url: "maps.com" },
|
||||
});
|
||||
|
||||
jest.spyOn(logger, "error").mockRestore();
|
||||
mocked(maplibregl.GeolocateControl).mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.spyOn(logger, "error").mockRestore();
|
||||
});
|
||||
|
||||
const mapOptions = { container: {} as unknown as HTMLElement, style: "" };
|
||||
const mockMap = new maplibregl.Map(mapOptions);
|
||||
|
||||
it("renders", () => {
|
||||
const { container } = getComponent();
|
||||
expect(container.firstChild).not.toBeNull();
|
||||
});
|
||||
|
||||
describe("onClientWellKnown emits", () => {
|
||||
it("updates map style when style url is truthy", () => {
|
||||
getComponent();
|
||||
|
||||
act(() => {
|
||||
matrixClient.emit(ClientEvent.ClientWellKnown, {
|
||||
[TILE_SERVER_WK_KEY.name]: { map_style_url: "new.maps.com" },
|
||||
});
|
||||
});
|
||||
|
||||
expect(mockMap.setStyle).toHaveBeenCalledWith("new.maps.com");
|
||||
});
|
||||
|
||||
it("does not update map style when style url is truthy", () => {
|
||||
getComponent();
|
||||
|
||||
act(() => {
|
||||
matrixClient.emit(ClientEvent.ClientWellKnown, {
|
||||
[TILE_SERVER_WK_KEY.name]: { map_style_url: undefined },
|
||||
});
|
||||
});
|
||||
|
||||
expect(mockMap.setStyle).not.toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
|
||||
describe("map centering", () => {
|
||||
it("does not try to center when no center uri provided", () => {
|
||||
getComponent({ centerGeoUri: null });
|
||||
expect(mockMap.setCenter).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sets map center to centerGeoUri", () => {
|
||||
getComponent({ centerGeoUri: "geo:51,42" });
|
||||
expect(mockMap.setCenter).toHaveBeenCalledWith({ lat: 51, lon: 42 });
|
||||
});
|
||||
|
||||
it("handles invalid centerGeoUri", () => {
|
||||
const logSpy = jest.spyOn(logger, "error").mockImplementation();
|
||||
getComponent({ centerGeoUri: "123 Sesame Street" });
|
||||
expect(mockMap.setCenter).not.toHaveBeenCalled();
|
||||
expect(logSpy).toHaveBeenCalledWith("Could not set map center");
|
||||
});
|
||||
|
||||
it("updates map center when centerGeoUri prop changes", () => {
|
||||
const { rerender } = getComponent({ centerGeoUri: "geo:51,42" });
|
||||
|
||||
getComponent({ centerGeoUri: "geo:53,45" }, rerender);
|
||||
getComponent({ centerGeoUri: "geo:56,47" }, rerender);
|
||||
expect(mockMap.setCenter).toHaveBeenCalledWith({ lat: 51, lon: 42 });
|
||||
expect(mockMap.setCenter).toHaveBeenCalledWith({ lat: 53, lon: 45 });
|
||||
expect(mockMap.setCenter).toHaveBeenCalledWith({ lat: 56, lon: 47 });
|
||||
});
|
||||
});
|
||||
|
||||
describe("map bounds", () => {
|
||||
it("does not try to fit map bounds when no bounds provided", () => {
|
||||
getComponent({ bounds: null });
|
||||
expect(mockMap.fitBounds).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("fits map to bounds", () => {
|
||||
const bounds = { north: 51, south: 50, east: 42, west: 41 };
|
||||
getComponent({ bounds });
|
||||
expect(mockMap.fitBounds).toHaveBeenCalledWith(
|
||||
new maplibregl.LngLatBounds([bounds.west, bounds.south], [bounds.east, bounds.north]),
|
||||
{ padding: 100, maxZoom: 15 },
|
||||
);
|
||||
});
|
||||
|
||||
it("handles invalid bounds", () => {
|
||||
const logSpy = jest.spyOn(logger, "error").mockImplementation();
|
||||
const bounds = { north: "a", south: "b", east: 42, west: 41 };
|
||||
getComponent({ bounds });
|
||||
expect(mockMap.fitBounds).not.toHaveBeenCalled();
|
||||
expect(logSpy).toHaveBeenCalledWith("Invalid map bounds");
|
||||
});
|
||||
|
||||
it("updates map bounds when bounds prop changes", () => {
|
||||
const { rerender } = getComponent({ centerGeoUri: "geo:51,42" });
|
||||
|
||||
const bounds = { north: 51, south: 50, east: 42, west: 41 };
|
||||
const bounds2 = { north: 53, south: 51, east: 45, west: 44 };
|
||||
|
||||
getComponent({ bounds }, rerender);
|
||||
getComponent({ bounds: bounds2 }, rerender);
|
||||
expect(mockMap.fitBounds).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("children", () => {
|
||||
it("renders without children", () => {
|
||||
const component = getComponent({ children: null });
|
||||
// no error
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders children with map renderProp", () => {
|
||||
const children = ({ map }: { map: maplibregl.Map }) => (
|
||||
<div data-testid="test-child" data-map={map}>
|
||||
Hello, world
|
||||
</div>
|
||||
);
|
||||
|
||||
const { container } = getComponent({ children });
|
||||
|
||||
// passes the map instance to the react children
|
||||
expect(getByTestId(container, "test-child").dataset.map).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("onClick", () => {
|
||||
it("eats clicks to maplibre attribution button", () => {
|
||||
const onClick = jest.fn();
|
||||
getComponent({ onClick });
|
||||
|
||||
act(() => {
|
||||
// this is added to the dom by maplibregl
|
||||
// which is mocked
|
||||
// just fake the target
|
||||
const fakeEl = document.createElement("div");
|
||||
fakeEl.className = "maplibregl-ctrl-attrib-button";
|
||||
fireEvent.click(fakeEl);
|
||||
});
|
||||
|
||||
expect(onClick).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calls onClick", () => {
|
||||
const onClick = jest.fn();
|
||||
const { container } = getComponent({ onClick });
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(container.firstChild);
|
||||
});
|
||||
|
||||
expect(onClick).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
51
test/unit-tests/components/views/location/MapError-test.tsx
Normal file
51
test/unit-tests/components/views/location/MapError-test.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render, RenderResult } from "jest-matrix-react";
|
||||
|
||||
import { MapError, MapErrorProps } from "../../../../../src/components/views/location/MapError";
|
||||
import { LocationShareError } from "../../../../../src/utils/location";
|
||||
|
||||
describe("<MapError />", () => {
|
||||
const defaultProps = {
|
||||
onFinished: jest.fn(),
|
||||
error: LocationShareError.MapStyleUrlNotConfigured,
|
||||
className: "test",
|
||||
};
|
||||
const getComponent = (props: Partial<MapErrorProps> = {}): RenderResult =>
|
||||
render(<MapError {...defaultProps} {...props} />);
|
||||
|
||||
it("renders correctly for MapStyleUrlNotConfigured", () => {
|
||||
const { container } = getComponent();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders correctly for MapStyleUrlNotReachable", () => {
|
||||
const { container } = getComponent({
|
||||
error: LocationShareError.MapStyleUrlNotReachable,
|
||||
});
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("does not render button when onFinished falsy", () => {
|
||||
const { queryByText } = getComponent({
|
||||
error: LocationShareError.MapStyleUrlNotReachable,
|
||||
onFinished: undefined,
|
||||
});
|
||||
// no button
|
||||
expect(queryByText("OK")).toBeFalsy();
|
||||
});
|
||||
|
||||
it("applies class when isMinimised is truthy", () => {
|
||||
const { container } = getComponent({
|
||||
isMinimised: true,
|
||||
});
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
42
test/unit-tests/components/views/location/Marker-test.tsx
Normal file
42
test/unit-tests/components/views/location/Marker-test.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { getByTestId, render } from "jest-matrix-react";
|
||||
|
||||
import Marker from "../../../../../src/components/views/location/Marker";
|
||||
|
||||
describe("<Marker />", () => {
|
||||
const defaultProps = {
|
||||
id: "abc123",
|
||||
};
|
||||
const getComponent = (props = {}) => render(<Marker {...defaultProps} {...props} />);
|
||||
|
||||
it("renders with location icon when no room member", () => {
|
||||
const { asFragment } = getComponent();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("does not try to use member color without room member", () => {
|
||||
const { container } = getComponent({ useMemberColor: true });
|
||||
expect(container.querySelector(".mx_Marker.mx_Marker_defaultColor")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("uses member color class", () => {
|
||||
const member = new RoomMember("!room:server", "@user:server");
|
||||
const { container } = getComponent({ useMemberColor: true, roomMember: member });
|
||||
expect(container.querySelector(".mx_Marker.mx_Username_color3")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders member avatar when roomMember is truthy", () => {
|
||||
const member = new RoomMember("!room:server", "@user:server");
|
||||
const { container } = getComponent({ roomMember: member });
|
||||
expect(getByTestId(container, "avatar-img")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render } from "jest-matrix-react";
|
||||
import { mocked } from "jest-mock";
|
||||
import * as maplibregl from "maplibre-gl";
|
||||
|
||||
import SmartMarker from "../../../../../src/components/views/location/SmartMarker";
|
||||
|
||||
jest.mock("../../../../../src/utils/location/findMapStyleUrl", () => ({
|
||||
findMapStyleUrl: jest.fn().mockReturnValue("tileserver.com"),
|
||||
}));
|
||||
|
||||
describe("<SmartMarker />", () => {
|
||||
const mapOptions = { container: {} as unknown as HTMLElement, style: "" };
|
||||
const mockMap = new maplibregl.Map(mapOptions);
|
||||
const mockMarker = new maplibregl.Marker();
|
||||
|
||||
const defaultProps = {
|
||||
map: mockMap,
|
||||
geoUri: "geo:43.2,54.6",
|
||||
};
|
||||
const getComponent = (props = {}): JSX.Element => <SmartMarker {...defaultProps} {...props} />;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it("creates a marker on mount", () => {
|
||||
const { container } = render(getComponent());
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
// marker added only once
|
||||
expect(maplibregl.Marker).toHaveBeenCalledTimes(1);
|
||||
// set to correct position
|
||||
expect(mockMarker.setLngLat).toHaveBeenCalledWith({ lon: 54.6, lat: 43.2 });
|
||||
// added to map
|
||||
expect(mockMarker.addTo).toHaveBeenCalledWith(mockMap);
|
||||
});
|
||||
|
||||
it("updates marker position on change", () => {
|
||||
const { rerender } = render(getComponent({ geoUri: "geo:40,50" }));
|
||||
|
||||
rerender(getComponent({ geoUri: "geo:41,51" }));
|
||||
rerender(getComponent({ geoUri: "geo:42,52" }));
|
||||
|
||||
// marker added only once
|
||||
expect(maplibregl.Marker).toHaveBeenCalledTimes(1);
|
||||
// set positions
|
||||
expect(mocked(mockMarker.setLngLat)).toHaveBeenCalledWith({ lat: 40, lon: 50 });
|
||||
expect(mocked(mockMarker.setLngLat)).toHaveBeenCalledWith({ lat: 41, lon: 51 });
|
||||
expect(mocked(mockMarker.setLngLat)).toHaveBeenCalledWith({ lat: 42, lon: 52 });
|
||||
});
|
||||
|
||||
it("removes marker on unmount", () => {
|
||||
const { unmount, container } = render(getComponent());
|
||||
expect(container).toMatchSnapshot();
|
||||
|
||||
unmount();
|
||||
expect(mockMarker.remove).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022, 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import * as maplibregl from "maplibre-gl";
|
||||
import { render, screen } from "jest-matrix-react";
|
||||
|
||||
import ZoomButtons from "../../../../../src/components/views/location/ZoomButtons";
|
||||
|
||||
describe("<ZoomButtons />", () => {
|
||||
const mapOptions = { container: {} as unknown as HTMLElement, style: "" };
|
||||
const mockMap = new maplibregl.Map(mapOptions);
|
||||
const defaultProps = {
|
||||
map: mockMap,
|
||||
};
|
||||
const getComponent = (props = {}) => render(<ZoomButtons {...defaultProps} {...props} />);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it("renders buttons", () => {
|
||||
const component = getComponent();
|
||||
expect(component.asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("calls map zoom in on zoom in click", () => {
|
||||
const component = getComponent();
|
||||
screen.getByTestId("map-zoom-in-button").click();
|
||||
|
||||
expect(mockMap.zoomIn).toHaveBeenCalled();
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it("calls map zoom out on zoom out click", () => {
|
||||
const component = getComponent();
|
||||
screen.getByTestId("map-zoom-out-button").click();
|
||||
|
||||
expect(mockMap.zoomOut).toHaveBeenCalled();
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LocationPicker <LocationPicker /> for Live location share type updates selected duration 1`] = `
|
||||
<div
|
||||
data-testid="live-duration-option-3600000"
|
||||
>
|
||||
Share for 1h
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,58 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<LocationShareMenu /> with live location disabled goes to labs flag screen after live options is clicked 1`] = `
|
||||
<div
|
||||
class="mx_EnableLiveShare"
|
||||
data-testid="location-picker-enable-live-share"
|
||||
>
|
||||
<div
|
||||
class="mx_StyledLiveBeaconIcon mx_EnableLiveShare_icon"
|
||||
/>
|
||||
<h3
|
||||
class="mx_Heading_h3 mx_EnableLiveShare_heading"
|
||||
>
|
||||
Live location sharing
|
||||
</h3>
|
||||
<p
|
||||
class="mx_EnableLiveShare_description"
|
||||
>
|
||||
Please note: this is a labs feature using a temporary implementation. This means you will not be able to delete your location history, and advanced users will be able to see your location history even after you stop sharing your live location with this room.
|
||||
</p>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
data-testid="enable-live-share-toggle"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_label"
|
||||
>
|
||||
<div
|
||||
id="mx_LabelledToggleSwitch_vY7Q4uEh9K38"
|
||||
>
|
||||
Enable live location sharing
|
||||
</div>
|
||||
</span>
|
||||
<div
|
||||
aria-checked="false"
|
||||
aria-disabled="false"
|
||||
aria-labelledby="mx_LabelledToggleSwitch_vY7Q4uEh9K38"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
aria-disabled="true"
|
||||
class="mx_AccessibleButton mx_EnableLiveShare_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary mx_AccessibleButton_disabled"
|
||||
data-testid="enable-live-share-submit"
|
||||
disabled=""
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
OK
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,50 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<LocationViewDialog /> renders map correctly 1`] = `
|
||||
<div
|
||||
class="mx_Map mx_LocationViewDialog_map"
|
||||
id="mx_Map_mx_LocationViewDialog_$2"
|
||||
>
|
||||
<span>
|
||||
<div
|
||||
class="mx_Marker mx_Marker_defaultColor"
|
||||
id="mx_LocationViewDialog_$2-marker"
|
||||
>
|
||||
<div
|
||||
class="mx_Marker_border"
|
||||
>
|
||||
<div
|
||||
class="mx_Marker_icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
<div
|
||||
class="mx_ZoomButtons"
|
||||
>
|
||||
<div
|
||||
aria-describedby="floating-ui-6"
|
||||
aria-label="Zoom in"
|
||||
class="mx_AccessibleButton mx_ZoomButtons_button"
|
||||
data-testid="map-zoom-in-button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ZoomButtons_icon"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Zoom out"
|
||||
class="mx_AccessibleButton mx_ZoomButtons_button"
|
||||
data-testid="map-zoom-out-button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ZoomButtons_icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,91 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<MapError /> applies class when isMinimised is truthy 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_MapError test mx_MapError_isMinimised"
|
||||
data-testid="map-rendering-error"
|
||||
>
|
||||
<div
|
||||
class="mx_MapError_icon"
|
||||
/>
|
||||
<h3
|
||||
class="mx_Heading_h3 mx_MapError_heading"
|
||||
>
|
||||
Unable to load map
|
||||
</h3>
|
||||
<p
|
||||
class="mx_MapError_message"
|
||||
>
|
||||
This homeserver is not configured to display maps.
|
||||
</p>
|
||||
<button
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
OK
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<MapError /> renders correctly for MapStyleUrlNotConfigured 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_MapError test"
|
||||
data-testid="map-rendering-error"
|
||||
>
|
||||
<div
|
||||
class="mx_MapError_icon"
|
||||
/>
|
||||
<h3
|
||||
class="mx_Heading_h3 mx_MapError_heading"
|
||||
>
|
||||
Unable to load map
|
||||
</h3>
|
||||
<p
|
||||
class="mx_MapError_message"
|
||||
>
|
||||
This homeserver is not configured to display maps.
|
||||
</p>
|
||||
<button
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
OK
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<MapError /> renders correctly for MapStyleUrlNotReachable 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_MapError test"
|
||||
data-testid="map-rendering-error"
|
||||
>
|
||||
<div
|
||||
class="mx_MapError_icon"
|
||||
/>
|
||||
<h3
|
||||
class="mx_Heading_h3 mx_MapError_heading"
|
||||
>
|
||||
Unable to load map
|
||||
</h3>
|
||||
<p
|
||||
class="mx_MapError_message"
|
||||
>
|
||||
This homeserver is not configured correctly to display maps, or the configured map server may be unreachable.
|
||||
</p>
|
||||
<button
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
OK
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,18 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Marker /> renders with location icon when no room member 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_Marker mx_Marker_defaultColor"
|
||||
id="abc123"
|
||||
>
|
||||
<div
|
||||
class="mx_Marker_border"
|
||||
>
|
||||
<div
|
||||
class="mx_Marker_icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@@ -0,0 +1,37 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<SmartMarker /> creates a marker on mount 1`] = `
|
||||
<div>
|
||||
<span>
|
||||
<div
|
||||
class="mx_Marker mx_Marker_defaultColor"
|
||||
>
|
||||
<div
|
||||
class="mx_Marker_border"
|
||||
>
|
||||
<div
|
||||
class="mx_Marker_icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<SmartMarker /> removes marker on unmount 1`] = `
|
||||
<div>
|
||||
<span>
|
||||
<div
|
||||
class="mx_Marker mx_Marker_defaultColor"
|
||||
>
|
||||
<div
|
||||
class="mx_Marker_border"
|
||||
>
|
||||
<div
|
||||
class="mx_Marker_icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,32 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<ZoomButtons /> renders buttons 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_ZoomButtons"
|
||||
>
|
||||
<div
|
||||
aria-label="Zoom in"
|
||||
class="mx_AccessibleButton mx_ZoomButtons_button"
|
||||
data-testid="map-zoom-in-button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ZoomButtons_icon"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Zoom out"
|
||||
class="mx_AccessibleButton mx_ZoomButtons_button"
|
||||
data-testid="map-zoom-out-button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ZoomButtons_icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import {
|
||||
ContentHelpers,
|
||||
MatrixClient,
|
||||
LegacyLocationEventContent,
|
||||
MLocationEventContent,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { doMaybeLocalRoomAction } from "../../../../../src/utils/local-room";
|
||||
import {
|
||||
LocationShareType,
|
||||
shareLocation,
|
||||
ShareLocationFn,
|
||||
} from "../../../../../src/components/views/location/shareLocation";
|
||||
|
||||
jest.mock("../../../../../src/utils/local-room", () => ({
|
||||
doMaybeLocalRoomAction: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("shareLocation", () => {
|
||||
const roomId = "!room:example.com";
|
||||
const shareType = LocationShareType.Pin;
|
||||
const content = { test: "location content" } as unknown as LegacyLocationEventContent & MLocationEventContent;
|
||||
let client: MatrixClient;
|
||||
let shareLocationFn: ShareLocationFn;
|
||||
|
||||
beforeEach(() => {
|
||||
const makeLocationContent = jest.spyOn(ContentHelpers, "makeLocationContent");
|
||||
client = {
|
||||
sendMessage: jest.fn(),
|
||||
} as unknown as MatrixClient;
|
||||
|
||||
mocked(makeLocationContent).mockReturnValue(content);
|
||||
mocked(doMaybeLocalRoomAction).mockImplementation(
|
||||
<T>(roomId: string, fn: (actualRoomId: string) => Promise<T>, client?: MatrixClient) => {
|
||||
return fn(roomId);
|
||||
},
|
||||
);
|
||||
|
||||
shareLocationFn = shareLocation(client, roomId, shareType, undefined, () => {});
|
||||
});
|
||||
|
||||
it("should forward the call to doMaybeLocalRoomAction", () => {
|
||||
shareLocationFn({ uri: "https://example.com/" });
|
||||
expect(client.sendMessage).toHaveBeenCalledWith(roomId, null, content);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user