210 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
			
		
		
	
	
			210 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
/*
 | 
						|
Copyright 2022 The Matrix.org Foundation C.I.C.
 | 
						|
 | 
						|
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.
 | 
						|
*/
 | 
						|
 | 
						|
import { MockedObject } from "jest-mock";
 | 
						|
import { makeBeaconInfoContent, makeBeaconContent } from "matrix-js-sdk/src/content-helpers";
 | 
						|
import { MatrixClient, MatrixEvent, Beacon, getBeaconInfoIdentifier } from "matrix-js-sdk/src/matrix";
 | 
						|
import { M_BEACON, M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
 | 
						|
import { LocationAssetType } from "matrix-js-sdk/src/@types/location";
 | 
						|
 | 
						|
import { getMockGeolocationPositionError } from "./location";
 | 
						|
import { makeRoomWithStateEvents } from "./room";
 | 
						|
 | 
						|
type InfoContentProps = {
 | 
						|
    timeout: number;
 | 
						|
    isLive?: boolean;
 | 
						|
    assetType?: LocationAssetType;
 | 
						|
    description?: string;
 | 
						|
    timestamp?: number;
 | 
						|
};
 | 
						|
const DEFAULT_INFO_CONTENT_PROPS: InfoContentProps = {
 | 
						|
    timeout: 3600000,
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Create an m.beacon_info event
 | 
						|
 * all required properties are mocked
 | 
						|
 * override with contentProps
 | 
						|
 */
 | 
						|
export const makeBeaconInfoEvent = (
 | 
						|
    sender: string,
 | 
						|
    roomId: string,
 | 
						|
    contentProps: Partial<InfoContentProps> = {},
 | 
						|
    eventId?: string,
 | 
						|
): MatrixEvent => {
 | 
						|
    const { timeout, isLive, description, assetType, timestamp } = {
 | 
						|
        ...DEFAULT_INFO_CONTENT_PROPS,
 | 
						|
        ...contentProps,
 | 
						|
    };
 | 
						|
    const event = new MatrixEvent({
 | 
						|
        type: M_BEACON_INFO.name,
 | 
						|
        room_id: roomId,
 | 
						|
        state_key: sender,
 | 
						|
        sender,
 | 
						|
        content: makeBeaconInfoContent(timeout, isLive, description, assetType, timestamp),
 | 
						|
    });
 | 
						|
 | 
						|
    event.event.origin_server_ts = Date.now();
 | 
						|
 | 
						|
    // live beacons use the beacon_info event id
 | 
						|
    // set or default this
 | 
						|
    event.replaceLocalEventId(eventId || `$${Math.random()}-${Math.random()}`);
 | 
						|
 | 
						|
    return event;
 | 
						|
};
 | 
						|
 | 
						|
type ContentProps = {
 | 
						|
    geoUri: string;
 | 
						|
    timestamp: number;
 | 
						|
    beaconInfoId: string;
 | 
						|
    description?: string;
 | 
						|
};
 | 
						|
const DEFAULT_CONTENT_PROPS: ContentProps = {
 | 
						|
    geoUri: "geo:-36.24484561954707,175.46884959563613;u=10",
 | 
						|
    timestamp: 123,
 | 
						|
    beaconInfoId: "$123",
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Create an m.beacon event
 | 
						|
 * all required properties are mocked
 | 
						|
 * override with contentProps
 | 
						|
 */
 | 
						|
export const makeBeaconEvent = (
 | 
						|
    sender: string,
 | 
						|
    contentProps: Partial<ContentProps> = {},
 | 
						|
    roomId?: string,
 | 
						|
): MatrixEvent => {
 | 
						|
    const { geoUri, timestamp, beaconInfoId, description } = {
 | 
						|
        ...DEFAULT_CONTENT_PROPS,
 | 
						|
        ...contentProps,
 | 
						|
    };
 | 
						|
 | 
						|
    return new MatrixEvent({
 | 
						|
        type: M_BEACON.name,
 | 
						|
        room_id: roomId,
 | 
						|
        sender,
 | 
						|
        content: makeBeaconContent(geoUri, timestamp, beaconInfoId, description),
 | 
						|
    });
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Create a mock geolocation position
 | 
						|
 * defaults all required properties
 | 
						|
 */
 | 
						|
export const makeGeolocationPosition = ({
 | 
						|
    timestamp,
 | 
						|
    coords,
 | 
						|
}: {
 | 
						|
    timestamp?: number;
 | 
						|
    coords?: Partial<GeolocationCoordinates>;
 | 
						|
}): GeolocationPosition => ({
 | 
						|
    timestamp: timestamp ?? 1647256791840,
 | 
						|
    coords: {
 | 
						|
        accuracy: 1,
 | 
						|
        latitude: 54.001927,
 | 
						|
        longitude: -8.253491,
 | 
						|
        altitude: null,
 | 
						|
        altitudeAccuracy: null,
 | 
						|
        heading: null,
 | 
						|
        speed: null,
 | 
						|
        ...coords,
 | 
						|
    },
 | 
						|
});
 | 
						|
 | 
						|
/**
 | 
						|
 * Creates a basic mock of Geolocation
 | 
						|
 * sets navigator.geolocation to the mock
 | 
						|
 * and returns mock
 | 
						|
 */
 | 
						|
export const mockGeolocation = (): MockedObject<Geolocation> => {
 | 
						|
    const mockGeolocation = {
 | 
						|
        clearWatch: jest.fn(),
 | 
						|
        getCurrentPosition: jest.fn().mockImplementation((callback) => callback(makeGeolocationPosition({}))),
 | 
						|
        watchPosition: jest.fn().mockImplementation((callback) => callback(makeGeolocationPosition({}))),
 | 
						|
    } as unknown as MockedObject<Geolocation>;
 | 
						|
 | 
						|
    // jest jsdom does not provide geolocation
 | 
						|
    // @ts-ignore illegal assignment to readonly property
 | 
						|
    navigator.geolocation = mockGeolocation;
 | 
						|
 | 
						|
    return mockGeolocation;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Creates a mock watchPosition implementation
 | 
						|
 * that calls success callback at the provided delays
 | 
						|
 * ```
 | 
						|
 * geolocation.watchPosition.mockImplementation([0, 1000, 5000, 50])
 | 
						|
 * ```
 | 
						|
 * will call the provided handler with a mock position at
 | 
						|
 * next tick, 1000ms, 6000ms, 6050ms
 | 
						|
 *
 | 
						|
 * to produce errors provide an array of error codes
 | 
						|
 * that will be applied to the delay with the same index
 | 
						|
 * eg:
 | 
						|
 * ```
 | 
						|
 * // return two good positions, then a permission denied error
 | 
						|
 * geolocation.watchPosition.mockImplementation(watchPositionMockImplementation(
 | 
						|
 *      [0, 1000, 3000], [0, 0, 1]),
 | 
						|
 * );
 | 
						|
 * ```
 | 
						|
 * See for error codes: https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPositionError
 | 
						|
 */
 | 
						|
export const watchPositionMockImplementation = (delays: number[], errorCodes: number[] = []) => {
 | 
						|
    return (callback: PositionCallback, error: PositionErrorCallback): number => {
 | 
						|
        const position = makeGeolocationPosition({});
 | 
						|
 | 
						|
        let totalDelay = 0;
 | 
						|
        delays.map((delayMs, index) => {
 | 
						|
            totalDelay += delayMs;
 | 
						|
            const timeout = window.setTimeout(() => {
 | 
						|
                if (errorCodes[index]) {
 | 
						|
                    error(getMockGeolocationPositionError(errorCodes[index], "error message"));
 | 
						|
                } else {
 | 
						|
                    callback({ ...position, timestamp: position.timestamp + totalDelay });
 | 
						|
                }
 | 
						|
            }, totalDelay);
 | 
						|
            return timeout;
 | 
						|
        });
 | 
						|
 | 
						|
        return totalDelay;
 | 
						|
    };
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Creates a room with beacon events
 | 
						|
 * sets given locations on beacons
 | 
						|
 * returns beacons
 | 
						|
 */
 | 
						|
export const makeRoomWithBeacons = (
 | 
						|
    roomId: string,
 | 
						|
    mockClient: MockedObject<MatrixClient>,
 | 
						|
    beaconInfoEvents: MatrixEvent[],
 | 
						|
    locationEvents?: MatrixEvent[],
 | 
						|
): Beacon[] => {
 | 
						|
    const room = makeRoomWithStateEvents(beaconInfoEvents, { roomId, mockClient });
 | 
						|
    const beacons = beaconInfoEvents.map((event) => room.currentState.beacons.get(getBeaconInfoIdentifier(event))!);
 | 
						|
    if (locationEvents) {
 | 
						|
        beacons.forEach((beacon) => {
 | 
						|
            // this filtering happens in roomState, which is bypassed here
 | 
						|
            const validLocationEvents = locationEvents?.filter((event) => event.getSender() === beacon.beaconInfoOwner);
 | 
						|
            beacon.addLocations(validLocationEvents);
 | 
						|
        });
 | 
						|
    }
 | 
						|
    return beacons;
 | 
						|
};
 |