Apply strictNullChecks to src/utils/*!exportUtils (#10455

* Apply `strictNullChecks` to `src/utils/exportUtils`

* strict fix

* fix strictNullChecks issues in some utils

* fix error message

* test coverage

* lint

* more strictNullChecks

* small optimisation for getUniqueRoomsWithIndividuals

* tidy

* test coverage
This commit is contained in:
Kerry 2023-04-03 20:26:55 +12:00 committed by GitHub
parent 4ed6e39067
commit 81a4498a8f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 143 additions and 81 deletions

View file

@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { createRef } from "react";
import { import {
AuthType, AuthType,
IAuthData, IAuthData,
@ -23,8 +24,8 @@ import {
IStageStatus, IStageStatus,
} from "matrix-js-sdk/src/interactive-auth"; } from "matrix-js-sdk/src/interactive-auth";
import { MatrixClient } from "matrix-js-sdk/src/client"; import { MatrixClient } from "matrix-js-sdk/src/client";
import React, { createRef } from "react";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { UIAResponse } from "matrix-js-sdk/src/@types/uia";
import getEntryComponentForLoginType, { IStageComponent } from "../views/auth/InteractiveAuthEntryComponents"; import getEntryComponentForLoginType, { IStageComponent } from "../views/auth/InteractiveAuthEntryComponents";
import Spinner from "../views/elements/Spinner"; import Spinner from "../views/elements/Spinner";
@ -39,7 +40,7 @@ type InteractiveAuthCallbackSuccess = (
type InteractiveAuthCallbackFailure = (success: false, response: IAuthData | Error) => void; type InteractiveAuthCallbackFailure = (success: false, response: IAuthData | Error) => void;
export type InteractiveAuthCallback = InteractiveAuthCallbackSuccess & InteractiveAuthCallbackFailure; export type InteractiveAuthCallback = InteractiveAuthCallbackSuccess & InteractiveAuthCallbackFailure;
interface IProps { export interface InteractiveAuthProps<T> {
// matrix client to use for UI auth requests // matrix client to use for UI auth requests
matrixClient: MatrixClient; matrixClient: MatrixClient;
// response from initial request. If not supplied, will do a request on mount. // response from initial request. If not supplied, will do a request on mount.
@ -61,7 +62,7 @@ interface IProps {
continueText?: string; continueText?: string;
continueKind?: string; continueKind?: string;
// callback // callback
makeRequest(auth: IAuthData | null): Promise<IAuthData>; makeRequest(auth?: IAuthData): Promise<UIAResponse<T>>;
// callback called when the auth process has finished, // callback called when the auth process has finished,
// successfully or unsuccessfully. // successfully or unsuccessfully.
// @param {boolean} status True if the operation requiring // @param {boolean} status True if the operation requiring
@ -92,14 +93,14 @@ interface IState {
submitButtonEnabled: boolean; submitButtonEnabled: boolean;
} }
export default class InteractiveAuthComponent extends React.Component<IProps, IState> { export default class InteractiveAuthComponent<T> extends React.Component<InteractiveAuthProps<T>, IState> {
private readonly authLogic: InteractiveAuth; private readonly authLogic: InteractiveAuth;
private readonly intervalId: number | null = null; private readonly intervalId: number | null = null;
private readonly stageComponent = createRef<IStageComponent>(); private readonly stageComponent = createRef<IStageComponent>();
private unmounted = false; private unmounted = false;
public constructor(props: IProps) { public constructor(props: InteractiveAuthProps<T>) {
super(props); super(props);
this.state = { this.state = {

View file

@ -22,7 +22,11 @@ import { AuthType, IAuthData } from "matrix-js-sdk/src/interactive-auth";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton"; import AccessibleButton from "../elements/AccessibleButton";
import InteractiveAuth, { ERROR_USER_CANCELLED, InteractiveAuthCallback } from "../../structures/InteractiveAuth"; import InteractiveAuth, {
ERROR_USER_CANCELLED,
InteractiveAuthCallback,
InteractiveAuthProps,
} from "../../structures/InteractiveAuth";
import { SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents"; import { SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
import BaseDialog from "./BaseDialog"; import BaseDialog from "./BaseDialog";
@ -37,17 +41,11 @@ type DialogAesthetics = Partial<{
}; };
}>; }>;
export interface InteractiveAuthDialogProps { export interface InteractiveAuthDialogProps<T = unknown>
extends Pick<InteractiveAuthProps<T>, "makeRequest" | "authData"> {
// matrix client to use for UI auth requests // matrix client to use for UI auth requests
matrixClient: MatrixClient; matrixClient: MatrixClient;
// response from initial request. If not supplied, will do a request on
// mount.
authData?: IAuthData;
// callback
makeRequest: (auth: IAuthData) => Promise<IAuthData>;
// Optional title and body to show when not showing a particular stage // Optional title and body to show when not showing a particular stage
title?: string; title?: string;
body?: string; body?: string;
@ -83,8 +81,8 @@ interface IState {
uiaStagePhase: number | null; uiaStagePhase: number | null;
} }
export default class InteractiveAuthDialog extends React.Component<InteractiveAuthDialogProps, IState> { export default class InteractiveAuthDialog<T> extends React.Component<InteractiveAuthDialogProps<T>, IState> {
public constructor(props: InteractiveAuthDialogProps) { public constructor(props: InteractiveAuthDialogProps<T>) {
super(props); super(props);
this.state = { this.state = {

View file

@ -20,7 +20,6 @@ import { logger } from "matrix-js-sdk/src/logger";
import { IClientWellKnown } from "matrix-js-sdk/src/matrix"; import { IClientWellKnown } from "matrix-js-sdk/src/matrix";
import { _t, UserFriendlyError } from "../languageHandler"; import { _t, UserFriendlyError } from "../languageHandler";
import { makeType } from "./TypeUtils";
import SdkConfig from "../SdkConfig"; import SdkConfig from "../SdkConfig";
import { ValidatedServerConfig } from "./ValidatedServerConfig"; import { ValidatedServerConfig } from "./ValidatedServerConfig";
@ -43,7 +42,7 @@ export default class AutoDiscoveryUtils {
* @param {string | Error} error The error to check * @param {string | Error} error The error to check
* @returns {boolean} True if the error is a liveliness error. * @returns {boolean} True if the error is a liveliness error.
*/ */
public static isLivelinessError(error: string | Error): boolean { public static isLivelinessError(error?: string | Error | null): boolean {
if (!error) return false; if (!error) return false;
return !!LIVELINESS_DISCOVERY_ERRORS.find((e) => return !!LIVELINESS_DISCOVERY_ERRORS.find((e) =>
typeof error === "string" ? e === error : e === error.message, typeof error === "string" ? e === error : e === error.message,
@ -197,7 +196,7 @@ export default class AutoDiscoveryUtils {
): ValidatedServerConfig { ): ValidatedServerConfig {
if (!discoveryResult || !discoveryResult["m.homeserver"]) { if (!discoveryResult || !discoveryResult["m.homeserver"]) {
// This shouldn't happen without major misconfiguration, so we'll log a bit of information // This shouldn't happen without major misconfiguration, so we'll log a bit of information
// in the log so we can find this bit of codee but otherwise tell teh user "it broke". // in the log so we can find this bit of code but otherwise tell the user "it broke".
logger.error("Ended up in a state of not knowing which homeserver to connect to."); logger.error("Ended up in a state of not knowing which homeserver to connect to.");
throw new UserFriendlyError("Unexpected error resolving homeserver configuration"); throw new UserFriendlyError("Unexpected error resolving homeserver configuration");
} }
@ -216,7 +215,7 @@ export default class AutoDiscoveryUtils {
// of Element. // of Element.
let preferredIdentityUrl = defaultConfig && defaultConfig["isUrl"]; let preferredIdentityUrl = defaultConfig && defaultConfig["isUrl"];
if (isResult && isResult.state === AutoDiscovery.SUCCESS) { if (isResult && isResult.state === AutoDiscovery.SUCCESS) {
preferredIdentityUrl = isResult["base_url"]; preferredIdentityUrl = isResult["base_url"] ?? undefined;
} else if (isResult && isResult.state !== AutoDiscovery.PROMPT) { } else if (isResult && isResult.state !== AutoDiscovery.PROMPT) {
logger.error("Error determining preferred identity server URL:", isResult); logger.error("Error determining preferred identity server URL:", isResult);
if (isResult.state === AutoDiscovery.FAIL_ERROR) { if (isResult.state === AutoDiscovery.FAIL_ERROR) {
@ -244,6 +243,12 @@ export default class AutoDiscoveryUtils {
} }
const preferredHomeserverUrl = hsResult["base_url"]; const preferredHomeserverUrl = hsResult["base_url"];
if (!preferredHomeserverUrl) {
logger.error("No homeserver URL configured");
throw new UserFriendlyError("Unexpected error resolving homeserver configuration");
}
let preferredHomeserverName = serverName ? serverName : hsResult["server_name"]; let preferredHomeserverName = serverName ? serverName : hsResult["server_name"];
const url = new URL(preferredHomeserverUrl); const url = new URL(preferredHomeserverUrl);
@ -255,7 +260,7 @@ export default class AutoDiscoveryUtils {
throw new UserFriendlyError("Unexpected error resolving homeserver configuration"); throw new UserFriendlyError("Unexpected error resolving homeserver configuration");
} }
return makeType(ValidatedServerConfig, { return {
hsUrl: preferredHomeserverUrl, hsUrl: preferredHomeserverUrl,
hsName: preferredHomeserverName, hsName: preferredHomeserverName,
hsNameIsDifferent: url.hostname !== preferredHomeserverName, hsNameIsDifferent: url.hostname !== preferredHomeserverName,
@ -263,6 +268,6 @@ export default class AutoDiscoveryUtils {
isDefault: false, isDefault: false,
warning: hsResult.error, warning: hsResult.error,
isNameResolvable: !isSynthetic, isNameResolvable: !isSynthetic,
}); } as ValidatedServerConfig;
} }
} }

View file

@ -103,8 +103,6 @@ export default class DMRoomMap {
} }
private onAccountData = (ev: MatrixEvent): void => { private onAccountData = (ev: MatrixEvent): void => {
console.log("onAccountData");
if (ev.getType() == EventType.Direct) { if (ev.getType() == EventType.Direct) {
this.setMDirectFromContent(ev.getContent()); this.setMDirectFromContent(ev.getContent());
this.userToRooms = null; this.userToRooms = null;
@ -207,13 +205,16 @@ export default class DMRoomMap {
public getUniqueRoomsWithIndividuals(): { [userId: string]: Room } { public getUniqueRoomsWithIndividuals(): { [userId: string]: Room } {
if (!this.roomToUser) return {}; // No rooms means no map. if (!this.roomToUser) return {}; // No rooms means no map.
return Object.keys(this.roomToUser) // map roomToUser to valid rooms with two participants
.map((r) => ({ userId: this.getUserIdForRoomId(r), room: this.matrixClient.getRoom(r) })) return Object.keys(this.roomToUser).reduce((acc, roomId: string) => {
.filter((r) => r.userId && r.room?.getInvitedAndJoinedMemberCount() === 2) const userId = this.getUserIdForRoomId(roomId);
.reduce((obj, r) => { const room = this.matrixClient.getRoom(roomId);
obj[r.userId] = r.room; const hasTwoMembers = room?.getInvitedAndJoinedMemberCount() === 2;
return obj; if (userId && room && hasTwoMembers) {
}, {} as Record<string, Room>); acc[userId] = room;
}
return acc;
}, {} as Record<string, Room>);
} }
/** /**
@ -236,9 +237,7 @@ export default class DMRoomMap {
// to avoid multiple devices fighting to correct // to avoid multiple devices fighting to correct
// the account data, only try to send the corrected // the account data, only try to send the corrected
// version once. // version once.
logger.warn( logger.warn(`Invalid m.direct account data detected (self-chats that shouldn't be), patching it up.`);
`Invalid m.direct account data detected ` + `(self-chats that shouldn't be), patching it up.`,
);
if (neededPatching && !this.hasSentOutPatchDirectAccountDataPatch) { if (neededPatching && !this.hasSentOutPatchDirectAccountDataPatch) {
this.hasSentOutPatchDirectAccountDataPatch = true; this.hasSentOutPatchDirectAccountDataPatch = true;
this.matrixClient.setAccountData(EventType.Direct, userToRooms); this.matrixClient.setAccountData(EventType.Direct, userToRooms);

View file

@ -21,8 +21,8 @@ import SdkConfig from "../SdkConfig";
import { MatrixClientPeg } from "../MatrixClientPeg"; import { MatrixClientPeg } from "../MatrixClientPeg";
import { Policies } from "../Terms"; import { Policies } from "../Terms";
export function getDefaultIdentityServerUrl(): string { export function getDefaultIdentityServerUrl(): string | undefined {
return SdkConfig.get("validated_server_config").isUrl; return SdkConfig.get("validated_server_config")?.isUrl;
} }
export function setToDefaultIdentityServer(): void { export function setToDefaultIdentityServer(): void {

View file

@ -102,7 +102,10 @@ export class MediaEventHelper implements IDestroyable {
} }
} }
return fetch(this.media.thumbnailHttp).then((r) => r.blob()); const thumbnailHttp = this.media.thumbnailHttp;
if (!thumbnailHttp) return Promise.resolve(null);
return fetch(thumbnailHttp).then((r) => r.blob());
}; };
public static isEligible(event: MatrixEvent): boolean { public static isEligible(event: MatrixEvent): boolean {

View file

@ -1,30 +0,0 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* Creates a class of a given type using the objects defined. This
* is a stopgap function while we don't have TypeScript interfaces.
* In future, we'd define the `type` as an interface and just cast
* it instead of cheating like we are here.
* @param {Type} Type The type of class to construct.
* @param {*} opts The options (properties) to set on the object.
* @returns {*} The created object.
*/
export function makeType<T>(Type: { new (): T }, opts: Partial<T>): T {
const c = new Type();
Object.assign(c, opts);
return c;
}

View file

@ -24,7 +24,7 @@ type FunctionWithUIA<R, A> = (auth?: IAuthData, ...args: A[]) => Promise<UIAResp
export function wrapRequestWithDialog<R, A = any>( export function wrapRequestWithDialog<R, A = any>(
requestFunction: FunctionWithUIA<R, A>, requestFunction: FunctionWithUIA<R, A>,
opts: Omit<InteractiveAuthDialogProps, "makeRequest" | "onFinished">, opts: Omit<InteractiveAuthDialogProps<R>, "makeRequest" | "onFinished">,
): (...args: A[]) => Promise<R> { ): (...args: A[]) => Promise<R> {
return async function (...args): Promise<R> { return async function (...args): Promise<R> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View file

@ -119,6 +119,7 @@ export default class WidgetUtils {
if ( if (
testUrl.protocol === scalarUrl.protocol && testUrl.protocol === scalarUrl.protocol &&
testUrl.host === scalarUrl.host && testUrl.host === scalarUrl.host &&
scalarUrl.pathname &&
testUrl.pathname?.startsWith(scalarUrl.pathname) testUrl.pathname?.startsWith(scalarUrl.pathname)
) { ) {
return true; return true;

View file

@ -61,6 +61,12 @@ export async function leaveRoomBehaviour(roomId: string, retry = true, spinner =
} }
const room = cli.getRoom(roomId); const room = cli.getRoom(roomId);
// should not encounter this
if (!room) {
throw new Error(`Expected to find room for id ${roomId}`);
}
// await any queued messages being sent so that they do not fail // await any queued messages being sent so that they do not fail
await Promise.all( await Promise.all(
room room

View file

@ -85,12 +85,14 @@ export const createMapSiteLinkFromEvent = (event: MatrixEvent): string | null =>
if (mLocation !== undefined) { if (mLocation !== undefined) {
const uri = mLocation["uri"]; const uri = mLocation["uri"];
if (uri !== undefined) { if (uri !== undefined) {
return makeMapSiteLink(parseGeoUri(uri)); const geoCoords = parseGeoUri(uri);
return geoCoords ? makeMapSiteLink(geoCoords) : null;
} }
} else { } else {
const geoUri = content["geo_uri"]; const geoUri = content["geo_uri"];
if (geoUri) { if (geoUri) {
return makeMapSiteLink(parseGeoUri(geoUri)); const geoCoords = parseGeoUri(geoUri);
return geoCoords ? makeMapSiteLink(geoCoords) : null;
} }
} }
return null; return null;

View file

@ -28,16 +28,23 @@ export const parseGeoUri = (uri: string): GeolocationCoordinates | undefined =>
if (!m) return; if (!m) return;
const parts = m[1].split(";"); const parts = m[1].split(";");
const coords = parts[0].split(","); const coords = parts[0].split(",");
let uncertainty: number | null; let uncertainty: number | null | undefined = undefined;
for (const param of parts.slice(1)) { for (const param of parts.slice(1)) {
const m = param.match(/u=(.*)/); const m = param.match(/u=(.*)/);
if (m) uncertainty = parse(m[1]); if (m) uncertainty = parse(m[1]);
} }
const latitude = parse(coords[0]);
const longitude = parse(coords[1]);
if (latitude === null || longitude === null) {
return;
}
return { return {
latitude: parse(coords[0]), latitude: latitude!,
longitude: parse(coords[1]), longitude: longitude!,
altitude: parse(coords[2]), altitude: parse(coords[2]),
accuracy: uncertainty, accuracy: uncertainty!,
altitudeAccuracy: null, altitudeAccuracy: null,
heading: null, heading: null,
speed: null, speed: null,

View file

@ -120,7 +120,7 @@ export const reorderLexicographically = (
// verify the right move would be sufficient // verify the right move would be sufficient
if ( if (
rightBoundIdx === newOrder.length - 1 && rightBoundIdx === newOrder.length - 1 &&
(newOrder[rightBoundIdx] ? stringToBase(newOrder[rightBoundIdx].order) : BigInt(Number.MAX_VALUE)) - (newOrder[rightBoundIdx]?.order ? stringToBase(newOrder[rightBoundIdx].order!) : BigInt(Number.MAX_VALUE)) -
prevBase <= prevBase <=
rightBoundIdx - toIndex rightBoundIdx - toIndex
) { ) {

View file

@ -65,6 +65,9 @@ export const lookupThreePids = async (
if (threePids.length === 0) return []; if (threePids.length === 0) return [];
const token = await client.identityServer.getAccessToken(); const token = await client.identityServer.getAccessToken();
if (!token) return [];
const lookedUp = await client.bulkLookupThreePids( const lookedUp = await client.bulkLookupThreePids(
threePids.map((t) => [t.isEmail ? "email" : "msisdn", t.userId]), threePids.map((t) => [t.isEmail ? "email" : "msisdn", t.userId]),
token, token,

View file

@ -47,7 +47,6 @@ import { MapperOpts } from "matrix-js-sdk/src/event-mapper";
import type { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall"; import type { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall";
import { MatrixClientPeg as peg } from "../../src/MatrixClientPeg"; import { MatrixClientPeg as peg } from "../../src/MatrixClientPeg";
import { makeType } from "../../src/utils/TypeUtils";
import { ValidatedServerConfig } from "../../src/utils/ValidatedServerConfig"; import { ValidatedServerConfig } from "../../src/utils/ValidatedServerConfig";
import { EnhancedMap } from "../../src/utils/maps"; import { EnhancedMap } from "../../src/utils/maps";
import { AsyncStoreWithClient } from "../../src/stores/AsyncStoreWithClient"; import { AsyncStoreWithClient } from "../../src/stores/AsyncStoreWithClient";
@ -591,13 +590,13 @@ export function mkStubRoom(
} as unknown as Room; } as unknown as Room;
} }
export function mkServerConfig(hsUrl: string, isUrl: string) { export function mkServerConfig(hsUrl: string, isUrl: string): ValidatedServerConfig {
return makeType(ValidatedServerConfig, { return {
hsUrl, hsUrl,
hsName: "TEST_ENVIRONMENT", hsName: "TEST_ENVIRONMENT",
hsNameIsDifferent: false, // yes, we lie hsNameIsDifferent: false, // yes, we lie
isUrl, isUrl,
}); } as ValidatedServerConfig;
} }
// These methods make some use of some private methods on the AsyncStoreWithClient to simplify getting into a consistent // These methods make some use of some private methods on the AsyncStoreWithClient to simplify getting into a consistent

View file

@ -16,7 +16,7 @@ limitations under the License.
import { mocked, Mocked } from "jest-mock"; import { mocked, Mocked } from "jest-mock";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { ClientEvent, EventType, IContent, MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix"; import { ClientEvent, EventType, IContent, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
import DMRoomMap from "../../src/utils/DMRoomMap"; import DMRoomMap from "../../src/utils/DMRoomMap";
import { mkEvent, stubClient } from "../test-utils"; import { mkEvent, stubClient } from "../test-utils";
@ -137,4 +137,56 @@ describe("DMRoomMap", () => {
expect(dmRoomMap.getRoomIds()).toEqual(new Set([roomId1, roomId2, roomId4])); expect(dmRoomMap.getRoomIds()).toEqual(new Set([roomId1, roomId2, roomId4]));
}); });
}); });
describe("getUniqueRoomsWithIndividuals()", () => {
const bigRoom = {
roomId: "!bigRoom:server.org",
getInvitedAndJoinedMemberCount: jest.fn().mockReturnValue(5000),
} as unknown as Room;
const dmWithBob = {
roomId: "!dmWithBob:server.org",
getInvitedAndJoinedMemberCount: jest.fn().mockReturnValue(2),
} as unknown as Room;
const dmWithCharlie = {
roomId: "!dmWithCharlie:server.org",
getInvitedAndJoinedMemberCount: jest.fn().mockReturnValue(2),
} as unknown as Room;
const smallRoom = {
roomId: "!smallRoom:server.org",
getInvitedAndJoinedMemberCount: jest.fn().mockReturnValue(3),
} as unknown as Room;
const mDirectContent = {
"@bob:server.org": [bigRoom.roomId, dmWithBob.roomId, smallRoom.roomId],
"@charlie:server.org": [dmWithCharlie.roomId, smallRoom.roomId],
};
beforeEach(() => {
client.getAccountData.mockReturnValue(mkMDirectEvent(mDirectContent));
client.getRoom.mockImplementation((roomId: string) =>
[bigRoom, smallRoom, dmWithCharlie, dmWithBob].find((room) => room.roomId === roomId),
);
});
it("returns an empty object when room map has not been populated", () => {
const instance = new DMRoomMap(client);
expect(instance.getUniqueRoomsWithIndividuals()).toEqual({});
});
it("returns map of users to rooms with 2 members", () => {
const dmRoomMap = new DMRoomMap(client);
dmRoomMap.start();
expect(dmRoomMap.getUniqueRoomsWithIndividuals()).toEqual({
"@bob:server.org": dmWithBob,
"@charlie:server.org": dmWithCharlie,
});
});
it("excludes rooms that are not found by matrixClient", () => {
client.getRoom.mockReset().mockReturnValue(undefined);
const dmRoomMap = new DMRoomMap(client);
dmRoomMap.start();
expect(dmRoomMap.getUniqueRoomsWithIndividuals()).toEqual({});
});
});
}); });

View file

@ -31,15 +31,23 @@ describe("createMapSiteLinkFromEvent", () => {
).toBeNull(); ).toBeNull();
}); });
it("returns OpenStreetMap link if event contains m.location", () => { it("returns OpenStreetMap link if event contains m.location with valid uri", () => {
expect(createMapSiteLinkFromEvent(makeLocationEvent("geo:51.5076,-0.1276"))).toEqual( expect(createMapSiteLinkFromEvent(makeLocationEvent("geo:51.5076,-0.1276"))).toEqual(
"https://www.openstreetmap.org/" + "?mlat=51.5076&mlon=-0.1276" + "#map=16/51.5076/-0.1276", "https://www.openstreetmap.org/" + "?mlat=51.5076&mlon=-0.1276" + "#map=16/51.5076/-0.1276",
); );
}); });
it("returns null if event contains m.location with invalid uri", () => {
expect(createMapSiteLinkFromEvent(makeLocationEvent("123 Sesame St"))).toBeNull();
});
it("returns OpenStreetMap link if event contains geo_uri", () => { it("returns OpenStreetMap link if event contains geo_uri", () => {
expect(createMapSiteLinkFromEvent(makeLegacyLocationEvent("geo:51.5076,-0.1276"))).toEqual( expect(createMapSiteLinkFromEvent(makeLegacyLocationEvent("geo:51.5076,-0.1276"))).toEqual(
"https://www.openstreetmap.org/" + "?mlat=51.5076&mlon=-0.1276" + "#map=16/51.5076/-0.1276", "https://www.openstreetmap.org/" + "?mlat=51.5076&mlon=-0.1276" + "#map=16/51.5076/-0.1276",
); );
}); });
it("returns null if event contains an invalid geo_uri", () => {
expect(createMapSiteLinkFromEvent(makeLegacyLocationEvent("123 Sesame St"))).toBeNull();
});
}); });

View file

@ -21,6 +21,14 @@ describe("parseGeoUri", () => {
expect(parseGeoUri("")).toBeFalsy(); expect(parseGeoUri("")).toBeFalsy();
}); });
it("returns undefined if latitude is not a number", () => {
expect(parseGeoUri("geo:ABCD,16.3695,183")).toBeUndefined();
});
it("returns undefined if longitude is not a number", () => {
expect(parseGeoUri("geo:48.2010,EFGH,183")).toBeUndefined();
});
// We use some examples from the spec, but don't check semantics // We use some examples from the spec, but don't check semantics
// like two textually-different URIs being equal, since we are // like two textually-different URIs being equal, since we are
// just a humble parser. // just a humble parser.