You've already forked matrix-react-sdk
mirror of
https://github.com/matrix-org/matrix-react-sdk.git
synced 2025-08-09 08:42:50 +03:00
Merge branch 'develop' into travis/remove-skinning
This commit is contained in:
@@ -26,6 +26,7 @@ import RoomContext, { TimelineRenderingType } from '../../../../src/contexts/Roo
|
||||
import { createAudioContext } from '../../../../src/audio/compat';
|
||||
import { findByTestId, flushPromises } from '../../../test-utils';
|
||||
import PlaybackWaveform from '../../../../src/components/views/audio_messages/PlaybackWaveform';
|
||||
import SeekBar from "../../../../src/components/views/audio_messages/SeekBar";
|
||||
|
||||
jest.mock('../../../../src/audio/compat', () => ({
|
||||
createAudioContext: jest.fn(),
|
||||
@@ -55,7 +56,7 @@ describe('<RecordingPlayback />', () => {
|
||||
const mockChannelData = new Float32Array();
|
||||
|
||||
const defaultRoom = { roomId: '!room:server.org', timelineRenderingType: TimelineRenderingType.File };
|
||||
const getComponent = (props: { playback: Playback }, room = defaultRoom) =>
|
||||
const getComponent = (props: React.ComponentProps<typeof RecordingPlayback>, room = defaultRoom) =>
|
||||
mount(<RecordingPlayback {...props} />, {
|
||||
wrappingComponent: RoomContext.Provider,
|
||||
wrappingComponentProps: { value: room },
|
||||
@@ -127,34 +128,19 @@ describe('<RecordingPlayback />', () => {
|
||||
expect(playback.toggle).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([
|
||||
[TimelineRenderingType.Notification],
|
||||
[TimelineRenderingType.File],
|
||||
[TimelineRenderingType.Pinned],
|
||||
])('does not render waveform when timeline rendering type for room is %s', (timelineRenderingType) => {
|
||||
it('should render a seek bar by default', () => {
|
||||
const playback = new Playback(new ArrayBuffer(8));
|
||||
const room = {
|
||||
...defaultRoom,
|
||||
timelineRenderingType,
|
||||
};
|
||||
const component = getComponent({ playback }, room);
|
||||
const component = getComponent({ playback });
|
||||
|
||||
expect(component.find(PlaybackWaveform).length).toBeFalsy();
|
||||
expect(component.find(SeekBar).length).toBeTruthy();
|
||||
});
|
||||
|
||||
it.each([
|
||||
[TimelineRenderingType.Room],
|
||||
[TimelineRenderingType.Thread],
|
||||
[TimelineRenderingType.ThreadsList],
|
||||
[TimelineRenderingType.Search],
|
||||
])('renders waveform when timeline rendering type for room is %s', (timelineRenderingType) => {
|
||||
it('should render a waveform when requested', () => {
|
||||
const playback = new Playback(new ArrayBuffer(8));
|
||||
const room = {
|
||||
...defaultRoom,
|
||||
timelineRenderingType,
|
||||
};
|
||||
const component = getComponent({ playback }, room);
|
||||
const component = getComponent({ playback, withWaveform: true });
|
||||
|
||||
expect(component.find(PlaybackWaveform).length).toBeTruthy();
|
||||
expect(component.find(SeekBar).length).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
@@ -17,16 +17,22 @@ limitations under the License.
|
||||
import React from 'react';
|
||||
import { mocked } from 'jest-mock';
|
||||
import { mount } from 'enzyme';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Beacon } from 'matrix-js-sdk/src/matrix';
|
||||
|
||||
import LeftPanelLiveShareWarning from '../../../../src/components/views/beacon/LeftPanelLiveShareWarning';
|
||||
import { OwnBeaconStore, OwnBeaconStoreEvent } from '../../../../src/stores/OwnBeaconStore';
|
||||
import { flushPromises } from '../../../test-utils';
|
||||
import { flushPromises, makeBeaconInfoEvent } from '../../../test-utils';
|
||||
import dispatcher from '../../../../src/dispatcher/dispatcher';
|
||||
import { Action } from '../../../../src/dispatcher/actions';
|
||||
|
||||
jest.mock('../../../../src/stores/OwnBeaconStore', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const EventEmitter = require("events");
|
||||
class MockOwnBeaconStore extends EventEmitter {
|
||||
public hasLiveBeacons = jest.fn().mockReturnValue(false);
|
||||
public getLiveBeaconIdsWithWireError = jest.fn().mockReturnValue([]);
|
||||
public getBeaconById = jest.fn();
|
||||
public getLiveBeaconIds = jest.fn().mockReturnValue([]);
|
||||
}
|
||||
return {
|
||||
// @ts-ignore
|
||||
@@ -43,32 +49,136 @@ describe('<LeftPanelLiveShareWarning />', () => {
|
||||
const getComponent = (props = {}) =>
|
||||
mount(<LeftPanelLiveShareWarning {...defaultProps} {...props} />);
|
||||
|
||||
const roomId1 = '!room1:server';
|
||||
const roomId2 = '!room2:server';
|
||||
const aliceId = '@alive:server';
|
||||
|
||||
const now = 1647270879403;
|
||||
const HOUR_MS = 3600000;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(global.Date, 'now').mockReturnValue(now);
|
||||
jest.spyOn(dispatcher, 'dispatch').mockClear().mockImplementation(() => { });
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.spyOn(global.Date, 'now').mockRestore();
|
||||
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
// 12h old, 12h left
|
||||
const beacon1 = new Beacon(makeBeaconInfoEvent(aliceId,
|
||||
roomId1,
|
||||
{ timeout: HOUR_MS * 24, timestamp: now - 12 * HOUR_MS },
|
||||
'$1',
|
||||
));
|
||||
// 10h left
|
||||
const beacon2 = new Beacon(makeBeaconInfoEvent(aliceId,
|
||||
roomId2,
|
||||
{ timeout: HOUR_MS * 10, timestamp: now },
|
||||
'$2',
|
||||
));
|
||||
|
||||
it('renders nothing when user has no live beacons', () => {
|
||||
const component = getComponent();
|
||||
expect(component.html()).toBe(null);
|
||||
});
|
||||
|
||||
describe('when user has live location monitor', () => {
|
||||
beforeAll(() => {
|
||||
mocked(OwnBeaconStore.instance).getBeaconById.mockImplementation(beaconId => {
|
||||
if (beaconId === beacon1.identifier) {
|
||||
return beacon1;
|
||||
}
|
||||
return beacon2;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
mocked(OwnBeaconStore.instance).isMonitoringLiveLocation = true;
|
||||
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithWireError.mockReturnValue([]);
|
||||
mocked(OwnBeaconStore.instance).getLiveBeaconIds.mockReturnValue([beacon2.identifier, beacon1.identifier]);
|
||||
});
|
||||
|
||||
it('renders correctly when not minimized', () => {
|
||||
const component = getComponent();
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('goes to room of latest beacon when clicked', () => {
|
||||
const component = getComponent();
|
||||
const dispatchSpy = jest.spyOn(dispatcher, 'dispatch');
|
||||
|
||||
act(() => {
|
||||
component.simulate('click');
|
||||
});
|
||||
|
||||
expect(dispatchSpy).toHaveBeenCalledWith({
|
||||
action: Action.ViewRoom,
|
||||
metricsTrigger: undefined,
|
||||
// latest beacon's room
|
||||
room_id: roomId2,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders correctly when minimized', () => {
|
||||
const component = getComponent({ isMinimized: true });
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders wire error', () => {
|
||||
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithWireError.mockReturnValue([beacon1.identifier]);
|
||||
const component = getComponent();
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('goes to room of latest beacon with wire error when clicked', () => {
|
||||
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithWireError.mockReturnValue([beacon1.identifier]);
|
||||
const component = getComponent();
|
||||
const dispatchSpy = jest.spyOn(dispatcher, 'dispatch');
|
||||
|
||||
act(() => {
|
||||
component.simulate('click');
|
||||
});
|
||||
|
||||
expect(dispatchSpy).toHaveBeenCalledWith({
|
||||
action: Action.ViewRoom,
|
||||
metricsTrigger: undefined,
|
||||
// error beacon's room
|
||||
room_id: roomId1,
|
||||
});
|
||||
});
|
||||
|
||||
it('goes back to default style when wire errors are cleared', () => {
|
||||
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithWireError.mockReturnValue([beacon1.identifier]);
|
||||
const component = getComponent();
|
||||
// error mode
|
||||
expect(component.find('.mx_LeftPanelLiveShareWarning').at(0).text()).toEqual(
|
||||
'An error occured whilst sharing your live location',
|
||||
);
|
||||
|
||||
act(() => {
|
||||
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithWireError.mockReturnValue([]);
|
||||
OwnBeaconStore.instance.emit(OwnBeaconStoreEvent.WireError, 'abc');
|
||||
});
|
||||
|
||||
component.setProps({});
|
||||
|
||||
// default mode
|
||||
expect(component.find('.mx_LeftPanelLiveShareWarning').at(0).text()).toEqual(
|
||||
'You are sharing your live location',
|
||||
);
|
||||
});
|
||||
|
||||
it('removes itself when user stops having live beacons', async () => {
|
||||
const component = getComponent({ isMinimized: true });
|
||||
// started out rendered
|
||||
expect(component.html()).toBeTruthy();
|
||||
|
||||
mocked(OwnBeaconStore.instance).isMonitoringLiveLocation = false;
|
||||
OwnBeaconStore.instance.emit(OwnBeaconStoreEvent.MonitoringLivePosition);
|
||||
act(() => {
|
||||
mocked(OwnBeaconStore.instance).isMonitoringLiveLocation = false;
|
||||
OwnBeaconStore.instance.emit(OwnBeaconStoreEvent.MonitoringLivePosition);
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
component.setProps({});
|
||||
|
@@ -25,6 +25,7 @@ import { OwnBeaconStore, OwnBeaconStoreEvent } from '../../../../src/stores/OwnB
|
||||
import {
|
||||
advanceDateAndTime,
|
||||
findByTestId,
|
||||
flushPromisesWithFakeTimers,
|
||||
getMockClientWithEventEmitter,
|
||||
makeBeaconInfoEvent,
|
||||
mockGeolocation,
|
||||
@@ -95,10 +96,11 @@ describe('<RoomLiveShareWarning />', () => {
|
||||
beforeEach(() => {
|
||||
mockGeolocation();
|
||||
jest.spyOn(global.Date, 'now').mockReturnValue(now);
|
||||
mockClient.unstable_setLiveBeacon.mockClear();
|
||||
mockClient.unstable_setLiveBeacon.mockReset().mockResolvedValue({ event_id: '1' });
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
jest.spyOn(OwnBeaconStore.instance, 'hasWireErrors').mockRestore();
|
||||
await resetAsyncStoreWithClient(OwnBeaconStore.instance);
|
||||
});
|
||||
|
||||
@@ -236,13 +238,37 @@ describe('<RoomLiveShareWarning />', () => {
|
||||
const component = getComponent({ roomId: room2Id });
|
||||
|
||||
act(() => {
|
||||
findByTestId(component, 'room-live-share-stop-sharing').at(0).simulate('click');
|
||||
findByTestId(component, 'room-live-share-primary-button').at(0).simulate('click');
|
||||
component.setProps({});
|
||||
});
|
||||
|
||||
expect(mockClient.unstable_setLiveBeacon).toHaveBeenCalledTimes(2);
|
||||
expect(component.find('Spinner').length).toBeTruthy();
|
||||
expect(findByTestId(component, 'room-live-share-stop-sharing').at(0).props().disabled).toBeTruthy();
|
||||
expect(findByTestId(component, 'room-live-share-primary-button').at(0).props().disabled).toBeTruthy();
|
||||
});
|
||||
|
||||
it('displays error when stop sharing fails', async () => {
|
||||
const component = getComponent({ roomId: room1Id });
|
||||
|
||||
// fail first time
|
||||
mockClient.unstable_setLiveBeacon
|
||||
.mockRejectedValueOnce(new Error('oups'))
|
||||
.mockResolvedValue(({ event_id: '1' }));
|
||||
|
||||
await act(async () => {
|
||||
findByTestId(component, 'room-live-share-primary-button').at(0).simulate('click');
|
||||
await flushPromisesWithFakeTimers();
|
||||
});
|
||||
component.setProps({});
|
||||
|
||||
expect(component.html()).toMatchSnapshot();
|
||||
|
||||
act(() => {
|
||||
findByTestId(component, 'room-live-share-primary-button').at(0).simulate('click');
|
||||
component.setProps({});
|
||||
});
|
||||
|
||||
expect(mockClient.unstable_setLiveBeacon).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('displays again with correct state after stopping a beacon', () => {
|
||||
@@ -251,7 +277,7 @@ describe('<RoomLiveShareWarning />', () => {
|
||||
|
||||
// stop the beacon
|
||||
act(() => {
|
||||
findByTestId(component, 'room-live-share-stop-sharing').at(0).simulate('click');
|
||||
findByTestId(component, 'room-live-share-primary-button').at(0).simulate('click');
|
||||
});
|
||||
// time travel until room1Beacon1 is expired
|
||||
act(() => {
|
||||
@@ -267,9 +293,83 @@ describe('<RoomLiveShareWarning />', () => {
|
||||
});
|
||||
|
||||
// button not disabled and expiry time shown
|
||||
expect(findByTestId(component, 'room-live-share-stop-sharing').at(0).props().disabled).toBeFalsy();
|
||||
expect(findByTestId(component, 'room-live-share-primary-button').at(0).props().disabled).toBeFalsy();
|
||||
expect(findByTestId(component, 'room-live-share-expiry').text()).toEqual('1h left');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with wire errors', () => {
|
||||
it('displays wire error when mounted with wire errors', async () => {
|
||||
const hasWireErrorsSpy = jest.spyOn(OwnBeaconStore.instance, 'hasWireErrors').mockReturnValue(true);
|
||||
const component = getComponent({ roomId: room2Id });
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
expect(hasWireErrorsSpy).toHaveBeenCalledWith(room2Id);
|
||||
});
|
||||
|
||||
it('displays wire error when wireError event is emitted and beacons have errors', async () => {
|
||||
const hasWireErrorsSpy = jest.spyOn(OwnBeaconStore.instance, 'hasWireErrors').mockReturnValue(false);
|
||||
const component = getComponent({ roomId: room2Id });
|
||||
|
||||
// update mock and emit event
|
||||
act(() => {
|
||||
hasWireErrorsSpy.mockReturnValue(true);
|
||||
OwnBeaconStore.instance.emit(OwnBeaconStoreEvent.WireError, room2Beacon1.getType());
|
||||
});
|
||||
component.setProps({});
|
||||
|
||||
// renders wire error ui
|
||||
expect(component.find('.mx_RoomLiveShareWarning_label').text()).toEqual(
|
||||
'An error occured whilst sharing your live location, please try again',
|
||||
);
|
||||
expect(findByTestId(component, 'room-live-share-wire-error-close-button').length).toBeTruthy();
|
||||
});
|
||||
|
||||
it('stops displaying wire error when errors are cleared', async () => {
|
||||
const hasWireErrorsSpy = jest.spyOn(OwnBeaconStore.instance, 'hasWireErrors').mockReturnValue(true);
|
||||
const component = getComponent({ roomId: room2Id });
|
||||
|
||||
// update mock and emit event
|
||||
act(() => {
|
||||
hasWireErrorsSpy.mockReturnValue(false);
|
||||
OwnBeaconStore.instance.emit(OwnBeaconStoreEvent.WireError, room2Beacon1.getType());
|
||||
});
|
||||
component.setProps({});
|
||||
|
||||
// renders error-free ui
|
||||
expect(component.find('.mx_RoomLiveShareWarning_label').text()).toEqual(
|
||||
'You are sharing your live location',
|
||||
);
|
||||
expect(findByTestId(component, 'room-live-share-wire-error-close-button').length).toBeFalsy();
|
||||
});
|
||||
|
||||
it('clicking retry button resets wire errors', async () => {
|
||||
jest.spyOn(OwnBeaconStore.instance, 'hasWireErrors').mockReturnValue(true);
|
||||
const resetErrorSpy = jest.spyOn(OwnBeaconStore.instance, 'resetWireError');
|
||||
|
||||
const component = getComponent({ roomId: room2Id });
|
||||
|
||||
act(() => {
|
||||
findByTestId(component, 'room-live-share-primary-button').at(0).simulate('click');
|
||||
});
|
||||
|
||||
expect(resetErrorSpy).toHaveBeenCalledWith(room2Beacon1.getType());
|
||||
expect(resetErrorSpy).toHaveBeenCalledWith(room2Beacon2.getType());
|
||||
});
|
||||
|
||||
it('clicking close button stops beacons', async () => {
|
||||
jest.spyOn(OwnBeaconStore.instance, 'hasWireErrors').mockReturnValue(true);
|
||||
const stopBeaconSpy = jest.spyOn(OwnBeaconStore.instance, 'stopBeacon');
|
||||
|
||||
const component = getComponent({ roomId: room2Id });
|
||||
|
||||
act(() => {
|
||||
findByTestId(component, 'room-live-share-wire-error-close-button').at(0).simulate('click');
|
||||
});
|
||||
|
||||
expect(stopBeaconSpy).toHaveBeenCalledWith(room2Beacon1.getType());
|
||||
expect(stopBeaconSpy).toHaveBeenCalledWith(room2Beacon2.getType());
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -4,23 +4,73 @@ exports[`<LeftPanelLiveShareWarning /> when user has live location monitor rende
|
||||
<LeftPanelLiveShareWarning
|
||||
isMinimized={true}
|
||||
>
|
||||
<div
|
||||
<AccessibleButton
|
||||
className="mx_LeftPanelLiveShareWarning mx_LeftPanelLiveShareWarning__minimized"
|
||||
element="div"
|
||||
onClick={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
title="You are sharing your live location"
|
||||
>
|
||||
<div
|
||||
height={10}
|
||||
/>
|
||||
</div>
|
||||
className="mx_AccessibleButton mx_LeftPanelLiveShareWarning mx_LeftPanelLiveShareWarning__minimized"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
title="You are sharing your live location"
|
||||
>
|
||||
<div
|
||||
height={10}
|
||||
/>
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
</LeftPanelLiveShareWarning>
|
||||
`;
|
||||
|
||||
exports[`<LeftPanelLiveShareWarning /> when user has live location monitor renders correctly when not minimized 1`] = `
|
||||
<LeftPanelLiveShareWarning>
|
||||
<div
|
||||
<AccessibleButton
|
||||
className="mx_LeftPanelLiveShareWarning"
|
||||
element="div"
|
||||
onClick={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
You are sharing your live location
|
||||
</div>
|
||||
<div
|
||||
className="mx_AccessibleButton mx_LeftPanelLiveShareWarning"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
You are sharing your live location
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
</LeftPanelLiveShareWarning>
|
||||
`;
|
||||
|
||||
exports[`<LeftPanelLiveShareWarning /> when user has live location monitor renders wire error 1`] = `
|
||||
<LeftPanelLiveShareWarning>
|
||||
<AccessibleButton
|
||||
className="mx_LeftPanelLiveShareWarning mx_LeftPanelLiveShareWarning__error"
|
||||
element="div"
|
||||
onClick={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<div
|
||||
className="mx_AccessibleButton mx_LeftPanelLiveShareWarning mx_LeftPanelLiveShareWarning__error"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
An error occured whilst sharing your live location
|
||||
</div>
|
||||
</AccessibleButton>
|
||||
</LeftPanelLiveShareWarning>
|
||||
`;
|
||||
|
@@ -1,5 +1,86 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<RoomLiveShareWarning /> when user has live beacons and geolocation is available renders correctly with one live beacon in room 1`] = `"<div class=\\"mx_RoomLiveShareWarning\\"><div class=\\"mx_StyledLiveBeaconIcon mx_RoomLiveShareWarning_icon\\"></div><span class=\\"mx_RoomLiveShareWarning_label\\">You are sharing your live location</span><span data-test-id=\\"room-live-share-expiry\\" class=\\"mx_RoomLiveShareWarning_expiry\\">1h left</span><button data-test-id=\\"room-live-share-stop-sharing\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger\\">Stop sharing</button></div>"`;
|
||||
exports[`<RoomLiveShareWarning /> when user has live beacons and geolocation is available renders correctly with one live beacon in room 1`] = `"<div class=\\"mx_RoomLiveShareWarning\\"><div class=\\"mx_StyledLiveBeaconIcon mx_RoomLiveShareWarning_icon\\"></div><span class=\\"mx_RoomLiveShareWarning_label\\">You are sharing your live location</span><span data-test-id=\\"room-live-share-expiry\\" class=\\"mx_RoomLiveShareWarning_expiry\\">1h left</span><button data-test-id=\\"room-live-share-primary-button\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger\\">Stop sharing</button></div>"`;
|
||||
|
||||
exports[`<RoomLiveShareWarning /> when user has live beacons and geolocation is available renders correctly with two live beacons in room 1`] = `"<div class=\\"mx_RoomLiveShareWarning\\"><div class=\\"mx_StyledLiveBeaconIcon mx_RoomLiveShareWarning_icon\\"></div><span class=\\"mx_RoomLiveShareWarning_label\\">You are sharing your live location</span><span data-test-id=\\"room-live-share-expiry\\" class=\\"mx_RoomLiveShareWarning_expiry\\">12h left</span><button data-test-id=\\"room-live-share-stop-sharing\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger\\">Stop sharing</button></div>"`;
|
||||
exports[`<RoomLiveShareWarning /> when user has live beacons and geolocation is available renders correctly with two live beacons in room 1`] = `"<div class=\\"mx_RoomLiveShareWarning\\"><div class=\\"mx_StyledLiveBeaconIcon mx_RoomLiveShareWarning_icon\\"></div><span class=\\"mx_RoomLiveShareWarning_label\\">You are sharing your live location</span><span data-test-id=\\"room-live-share-expiry\\" class=\\"mx_RoomLiveShareWarning_expiry\\">12h left</span><button data-test-id=\\"room-live-share-primary-button\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger\\">Stop sharing</button></div>"`;
|
||||
|
||||
exports[`<RoomLiveShareWarning /> when user has live beacons and geolocation is available stopping beacons displays error when stop sharing fails 1`] = `"<div class=\\"mx_RoomLiveShareWarning\\"><div class=\\"mx_StyledLiveBeaconIcon mx_RoomLiveShareWarning_icon mx_StyledLiveBeaconIcon_error\\"></div><span class=\\"mx_RoomLiveShareWarning_label\\">An error occurred while stopping your live location, please try again</span><button data-test-id=\\"room-live-share-primary-button\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger\\">Retry</button></div>"`;
|
||||
|
||||
exports[`<RoomLiveShareWarning /> when user has live beacons and geolocation is available with wire errors displays wire error when mounted with wire errors 1`] = `
|
||||
<RoomLiveShareWarning
|
||||
roomId="$room2:server.org"
|
||||
>
|
||||
<RoomLiveShareWarningInner
|
||||
liveBeaconIds={
|
||||
Array [
|
||||
"org.matrix.msc3489.beacon_info.@alice:server.org.3",
|
||||
"org.matrix.msc3489.beacon_info.@alice:server.org.4",
|
||||
]
|
||||
}
|
||||
roomId="$room2:server.org"
|
||||
>
|
||||
<div
|
||||
className="mx_RoomLiveShareWarning"
|
||||
>
|
||||
<StyledLiveBeaconIcon
|
||||
className="mx_RoomLiveShareWarning_icon"
|
||||
withError={true}
|
||||
>
|
||||
<div
|
||||
className="mx_StyledLiveBeaconIcon mx_RoomLiveShareWarning_icon mx_StyledLiveBeaconIcon_error"
|
||||
/>
|
||||
</StyledLiveBeaconIcon>
|
||||
<span
|
||||
className="mx_RoomLiveShareWarning_label"
|
||||
>
|
||||
An error occured whilst sharing your live location, please try again
|
||||
</span>
|
||||
<AccessibleButton
|
||||
data-test-id="room-live-share-primary-button"
|
||||
disabled={false}
|
||||
element="button"
|
||||
kind="danger"
|
||||
onClick={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<button
|
||||
className="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger"
|
||||
data-test-id="room-live-share-primary-button"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
className="mx_RoomLiveShareWarning_closeButton"
|
||||
data-test-id="room-live-share-wire-error-close-button"
|
||||
element="button"
|
||||
onClick={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
title="Stop sharing and close"
|
||||
>
|
||||
<button
|
||||
className="mx_AccessibleButton mx_RoomLiveShareWarning_closeButton"
|
||||
data-test-id="room-live-share-wire-error-close-button"
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
title="Stop sharing and close"
|
||||
>
|
||||
<div
|
||||
className="mx_RoomLiveShareWarning_closeButtonIcon"
|
||||
/>
|
||||
</button>
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</RoomLiveShareWarningInner>
|
||||
</RoomLiveShareWarning>
|
||||
`;
|
||||
|
@@ -107,7 +107,7 @@ describe('<RoomPreviewBar />', () => {
|
||||
const component = getComponent({ joining: true });
|
||||
|
||||
expect(isSpinnerRendered(component)).toBeTruthy();
|
||||
expect(getMessage(component).textContent).toEqual('Joining room …');
|
||||
expect(getMessage(component).textContent).toEqual('Joining …');
|
||||
});
|
||||
it('renders rejecting message', () => {
|
||||
const component = getComponent({ rejecting: true });
|
||||
|
@@ -54,11 +54,11 @@ exports[`<RoomPreviewBar /> with an error renders other errors 1`] = `
|
||||
RoomPreviewBar-test-room is not accessible at this time.
|
||||
</h3>
|
||||
<p>
|
||||
Try again later, or ask a room admin to check if you have access.
|
||||
Try again later, or ask a room or space admin to check if you have access.
|
||||
</p>
|
||||
<p>
|
||||
<span>
|
||||
Something_else was returned while trying to access the room. If you think you're seeing this message in error, please
|
||||
Something_else was returned while trying to access the room or space. If you think you're seeing this message in error, please
|
||||
<a
|
||||
href="https://github.com/vector-im/element-web/issues/new/choose"
|
||||
rel="noreferrer noopener"
|
||||
@@ -80,7 +80,7 @@ exports[`<RoomPreviewBar /> with an error renders room not found error 1`] = `
|
||||
RoomPreviewBar-test-room does not exist.
|
||||
</h3>
|
||||
<p>
|
||||
This room doesn't exist. Are you sure you're at the right place?
|
||||
Are you sure you're at the right place?
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
@@ -93,7 +93,7 @@ exports[`<RoomPreviewBar /> with an invite with an invited email when client fai
|
||||
Something went wrong with your invite to RoomPreviewBar-test-room
|
||||
</h3>
|
||||
<p>
|
||||
An error (unknown error code) was returned while trying to validate your invite. You could try to pass this information on to a room admin.
|
||||
An error (unknown error code) was returned while trying to validate your invite. You could try to pass this information on to the person who invited you.
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
@@ -14,7 +14,14 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { Room, Beacon, BeaconEvent, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import {
|
||||
Room,
|
||||
Beacon,
|
||||
BeaconEvent,
|
||||
MatrixEvent,
|
||||
RoomStateEvent,
|
||||
RoomMember,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { makeBeaconContent } from "matrix-js-sdk/src/content-helpers";
|
||||
import { M_BEACON, M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
@@ -23,6 +30,7 @@ import { OwnBeaconStore, OwnBeaconStoreEvent } from "../../src/stores/OwnBeaconS
|
||||
import {
|
||||
advanceDateAndTime,
|
||||
flushPromisesWithFakeTimers,
|
||||
makeMembershipEvent,
|
||||
resetAsyncStoreWithClient,
|
||||
setupAsyncStoreWithClient,
|
||||
} from "../test-utils";
|
||||
@@ -158,7 +166,7 @@ describe('OwnBeaconStore', () => {
|
||||
geolocation = mockGeolocation();
|
||||
mockClient.getVisibleRooms.mockReturnValue([]);
|
||||
mockClient.unstable_setLiveBeacon.mockClear().mockResolvedValue({ event_id: '1' });
|
||||
mockClient.sendEvent.mockClear().mockResolvedValue({ event_id: '1' });
|
||||
mockClient.sendEvent.mockReset().mockResolvedValue({ event_id: '1' });
|
||||
jest.spyOn(global.Date, 'now').mockReturnValue(now);
|
||||
jest.spyOn(OwnBeaconStore.instance, 'emit').mockRestore();
|
||||
jest.spyOn(logger, 'error').mockRestore();
|
||||
@@ -243,6 +251,7 @@ describe('OwnBeaconStore', () => {
|
||||
|
||||
expect(removeSpy.mock.calls[0]).toEqual(expect.arrayContaining([BeaconEvent.LivenessChange]));
|
||||
expect(removeSpy.mock.calls[1]).toEqual(expect.arrayContaining([BeaconEvent.New]));
|
||||
expect(removeSpy.mock.calls[2]).toEqual(expect.arrayContaining([RoomStateEvent.Members]));
|
||||
});
|
||||
|
||||
it('destroys beacons', async () => {
|
||||
@@ -509,6 +518,112 @@ describe('OwnBeaconStore', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('on room membership changes', () => {
|
||||
it('ignores events for rooms without beacons', async () => {
|
||||
const membershipEvent = makeMembershipEvent(room2Id, aliceId);
|
||||
// no beacons for room2
|
||||
const [, room2] = makeRoomsWithStateEvents([
|
||||
alicesRoom1BeaconInfo,
|
||||
]);
|
||||
const store = await makeOwnBeaconStore();
|
||||
const emitSpy = jest.spyOn(store, 'emit');
|
||||
const oldLiveBeaconIds = store.getLiveBeaconIds();
|
||||
|
||||
mockClient.emit(
|
||||
RoomStateEvent.Members,
|
||||
membershipEvent,
|
||||
room2.currentState,
|
||||
new RoomMember(room2Id, aliceId),
|
||||
);
|
||||
|
||||
expect(emitSpy).not.toHaveBeenCalled();
|
||||
// strictly equal
|
||||
expect(store.getLiveBeaconIds()).toBe(oldLiveBeaconIds);
|
||||
});
|
||||
|
||||
it('ignores events for membership changes that are not current user', async () => {
|
||||
// bob joins room1
|
||||
const membershipEvent = makeMembershipEvent(room1Id, bobId);
|
||||
const member = new RoomMember(room1Id, bobId);
|
||||
member.setMembershipEvent(membershipEvent);
|
||||
|
||||
const [room1] = makeRoomsWithStateEvents([
|
||||
alicesRoom1BeaconInfo,
|
||||
]);
|
||||
const store = await makeOwnBeaconStore();
|
||||
const emitSpy = jest.spyOn(store, 'emit');
|
||||
const oldLiveBeaconIds = store.getLiveBeaconIds();
|
||||
|
||||
mockClient.emit(
|
||||
RoomStateEvent.Members,
|
||||
membershipEvent,
|
||||
room1.currentState,
|
||||
member,
|
||||
);
|
||||
|
||||
expect(emitSpy).not.toHaveBeenCalled();
|
||||
// strictly equal
|
||||
expect(store.getLiveBeaconIds()).toBe(oldLiveBeaconIds);
|
||||
});
|
||||
|
||||
it('ignores events for membership changes that are not leave/ban', async () => {
|
||||
// alice joins room1
|
||||
const membershipEvent = makeMembershipEvent(room1Id, aliceId);
|
||||
const member = new RoomMember(room1Id, aliceId);
|
||||
member.setMembershipEvent(membershipEvent);
|
||||
|
||||
const [room1] = makeRoomsWithStateEvents([
|
||||
alicesRoom1BeaconInfo,
|
||||
alicesRoom2BeaconInfo,
|
||||
]);
|
||||
const store = await makeOwnBeaconStore();
|
||||
const emitSpy = jest.spyOn(store, 'emit');
|
||||
const oldLiveBeaconIds = store.getLiveBeaconIds();
|
||||
|
||||
mockClient.emit(
|
||||
RoomStateEvent.Members,
|
||||
membershipEvent,
|
||||
room1.currentState,
|
||||
member,
|
||||
);
|
||||
|
||||
expect(emitSpy).not.toHaveBeenCalled();
|
||||
// strictly equal
|
||||
expect(store.getLiveBeaconIds()).toBe(oldLiveBeaconIds);
|
||||
});
|
||||
|
||||
it('destroys and removes beacons when current user leaves room', async () => {
|
||||
// alice leaves room1
|
||||
const membershipEvent = makeMembershipEvent(room1Id, aliceId, 'leave');
|
||||
const member = new RoomMember(room1Id, aliceId);
|
||||
member.setMembershipEvent(membershipEvent);
|
||||
|
||||
const [room1] = makeRoomsWithStateEvents([
|
||||
alicesRoom1BeaconInfo,
|
||||
alicesRoom2BeaconInfo,
|
||||
]);
|
||||
const store = await makeOwnBeaconStore();
|
||||
const room1BeaconInstance = store.beacons.get(alicesRoom1BeaconInfo.getType());
|
||||
const beaconDestroySpy = jest.spyOn(room1BeaconInstance, 'destroy');
|
||||
const emitSpy = jest.spyOn(store, 'emit');
|
||||
|
||||
mockClient.emit(
|
||||
RoomStateEvent.Members,
|
||||
membershipEvent,
|
||||
room1.currentState,
|
||||
member,
|
||||
);
|
||||
|
||||
expect(emitSpy).toHaveBeenCalledWith(
|
||||
OwnBeaconStoreEvent.LivenessChange,
|
||||
// other rooms beacons still live
|
||||
[alicesRoom2BeaconInfo.getType()],
|
||||
);
|
||||
expect(beaconDestroySpy).toHaveBeenCalledTimes(1);
|
||||
expect(store.getLiveBeaconIds(room1Id)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stopBeacon()', () => {
|
||||
beforeEach(() => {
|
||||
makeRoomsWithStateEvents([
|
||||
@@ -581,7 +696,7 @@ describe('OwnBeaconStore', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('sending positions', () => {
|
||||
describe('publishing positions', () => {
|
||||
it('stops watching position when user has no more live beacons', async () => {
|
||||
// geolocation is only going to emit 1 position
|
||||
geolocation.watchPosition.mockImplementation(
|
||||
@@ -710,6 +825,141 @@ describe('OwnBeaconStore', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('when publishing position fails', () => {
|
||||
beforeEach(() => {
|
||||
geolocation.watchPosition.mockImplementation(
|
||||
watchPositionMockImplementation([0, 1000, 3000, 3000, 3000]),
|
||||
);
|
||||
|
||||
// eat expected console error logs
|
||||
jest.spyOn(logger, 'error').mockImplementation(() => { });
|
||||
});
|
||||
|
||||
// we need to advance time and then flush promises
|
||||
// individually for each call to sendEvent
|
||||
// otherwise the sendEvent doesn't reject/resolve and update state
|
||||
// before the next call
|
||||
// advance and flush every 1000ms
|
||||
// until given ms is 'elapsed'
|
||||
const advanceAndFlushPromises = async (timeMs: number) => {
|
||||
while (timeMs > 0) {
|
||||
jest.advanceTimersByTime(1000);
|
||||
await flushPromisesWithFakeTimers();
|
||||
timeMs -= 1000;
|
||||
}
|
||||
};
|
||||
|
||||
it('continues publishing positions after one publish error', async () => {
|
||||
// fail to send first event, then succeed
|
||||
mockClient.sendEvent.mockRejectedValueOnce(new Error('oups')).mockResolvedValue({ event_id: '1' });
|
||||
makeRoomsWithStateEvents([
|
||||
alicesRoom1BeaconInfo,
|
||||
]);
|
||||
const store = await makeOwnBeaconStore();
|
||||
// wait for store to settle
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
await advanceAndFlushPromises(50000);
|
||||
|
||||
// called for each position from watchPosition
|
||||
expect(mockClient.sendEvent).toHaveBeenCalledTimes(5);
|
||||
expect(store.beaconHasWireError(alicesRoom1BeaconInfo.getType())).toBe(false);
|
||||
expect(store.hasWireErrors()).toBe(false);
|
||||
});
|
||||
|
||||
it('continues publishing positions when a beacon fails intermittently', async () => {
|
||||
// every second event rejects
|
||||
// meaning this beacon has more errors than the threshold
|
||||
// but they are not consecutive
|
||||
mockClient.sendEvent
|
||||
.mockRejectedValueOnce(new Error('oups'))
|
||||
.mockResolvedValueOnce({ event_id: '1' })
|
||||
.mockRejectedValueOnce(new Error('oups'))
|
||||
.mockResolvedValueOnce({ event_id: '1' })
|
||||
.mockRejectedValueOnce(new Error('oups'));
|
||||
|
||||
makeRoomsWithStateEvents([
|
||||
alicesRoom1BeaconInfo,
|
||||
]);
|
||||
const store = await makeOwnBeaconStore();
|
||||
const emitSpy = jest.spyOn(store, 'emit');
|
||||
// wait for store to settle
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
await advanceAndFlushPromises(50000);
|
||||
|
||||
// called for each position from watchPosition
|
||||
expect(mockClient.sendEvent).toHaveBeenCalledTimes(5);
|
||||
expect(store.beaconHasWireError(alicesRoom1BeaconInfo.getType())).toBe(false);
|
||||
expect(store.hasWireErrors()).toBe(false);
|
||||
expect(emitSpy).not.toHaveBeenCalledWith(
|
||||
OwnBeaconStoreEvent.WireError, alicesRoom1BeaconInfo.getType(),
|
||||
);
|
||||
});
|
||||
|
||||
it('stops publishing positions when a beacon fails consistently', async () => {
|
||||
// always fails to send events
|
||||
mockClient.sendEvent.mockRejectedValue(new Error('oups'));
|
||||
makeRoomsWithStateEvents([
|
||||
alicesRoom1BeaconInfo,
|
||||
]);
|
||||
const store = await makeOwnBeaconStore();
|
||||
const emitSpy = jest.spyOn(store, 'emit');
|
||||
// wait for store to settle
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
// 5 positions from watchPosition in this period
|
||||
await advanceAndFlushPromises(50000);
|
||||
|
||||
// only two allowed failures
|
||||
expect(mockClient.sendEvent).toHaveBeenCalledTimes(2);
|
||||
expect(store.beaconHasWireError(alicesRoom1BeaconInfo.getType())).toBe(true);
|
||||
expect(store.hasWireErrors()).toBe(true);
|
||||
expect(emitSpy).toHaveBeenCalledWith(
|
||||
OwnBeaconStoreEvent.WireError, alicesRoom1BeaconInfo.getType(),
|
||||
);
|
||||
});
|
||||
|
||||
it('restarts publishing a beacon after resetting wire error', async () => {
|
||||
// always fails to send events
|
||||
mockClient.sendEvent.mockRejectedValue(new Error('oups'));
|
||||
makeRoomsWithStateEvents([
|
||||
alicesRoom1BeaconInfo,
|
||||
]);
|
||||
const store = await makeOwnBeaconStore();
|
||||
const emitSpy = jest.spyOn(store, 'emit');
|
||||
// wait for store to settle
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
// 3 positions from watchPosition in this period
|
||||
await advanceAndFlushPromises(4000);
|
||||
|
||||
// only two allowed failures
|
||||
expect(mockClient.sendEvent).toHaveBeenCalledTimes(2);
|
||||
expect(store.beaconHasWireError(alicesRoom1BeaconInfo.getType())).toBe(true);
|
||||
expect(store.hasWireErrors()).toBe(true);
|
||||
expect(store.hasWireErrors(room1Id)).toBe(true);
|
||||
expect(emitSpy).toHaveBeenCalledWith(
|
||||
OwnBeaconStoreEvent.WireError, alicesRoom1BeaconInfo.getType(),
|
||||
);
|
||||
|
||||
// reset emitSpy mock counts to asser on wireError again
|
||||
emitSpy.mockClear();
|
||||
store.resetWireError(alicesRoom1BeaconInfo.getType());
|
||||
|
||||
expect(store.beaconHasWireError(alicesRoom1BeaconInfo.getType())).toBe(false);
|
||||
|
||||
// 2 more positions from watchPosition in this period
|
||||
await advanceAndFlushPromises(10000);
|
||||
|
||||
// 2 from before, 2 new ones
|
||||
expect(mockClient.sendEvent).toHaveBeenCalledTimes(4);
|
||||
expect(emitSpy).toHaveBeenCalledWith(
|
||||
OwnBeaconStoreEvent.WireError, alicesRoom1BeaconInfo.getType(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('publishes subsequent positions', async () => {
|
||||
// modern fake timers + debounce + promises are not friends
|
||||
// just testing that positions are published
|
||||
|
@@ -2,6 +2,7 @@ export * from './beacon';
|
||||
export * from './client';
|
||||
export * from './location';
|
||||
export * from './platform';
|
||||
export * from './room';
|
||||
export * from './test-utils';
|
||||
export * from './voice';
|
||||
export * from './wrappers';
|
||||
|
34
test/test-utils/room.ts
Normal file
34
test/test-utils/room.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
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 {
|
||||
EventType,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { mkEvent } from "./test-utils";
|
||||
|
||||
export const makeMembershipEvent = (
|
||||
roomId: string, userId: string, membership = 'join',
|
||||
) => mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomMember,
|
||||
room: roomId,
|
||||
user: userId,
|
||||
skey: userId,
|
||||
content: { membership },
|
||||
ts: Date.now(),
|
||||
});
|
||||
|
@@ -16,7 +16,11 @@ limitations under the License.
|
||||
|
||||
import { Beacon } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { msUntilExpiry, sortBeaconsByLatestExpiry } from "../../../src/utils/beacon";
|
||||
import {
|
||||
msUntilExpiry,
|
||||
sortBeaconsByLatestExpiry,
|
||||
sortBeaconsByLatestCreation,
|
||||
} from "../../../src/utils/beacon";
|
||||
import { makeBeaconInfoEvent } from "../../test-utils";
|
||||
|
||||
describe('beacon utils', () => {
|
||||
@@ -80,4 +84,35 @@ describe('beacon utils', () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sortBeaconsByLatestCreation()', () => {
|
||||
const roomId = '!room:server';
|
||||
const aliceId = '@alive:server';
|
||||
|
||||
// 12h old, 12h left
|
||||
const beacon1 = new Beacon(makeBeaconInfoEvent(aliceId,
|
||||
roomId,
|
||||
{ timeout: HOUR_MS * 24, timestamp: now - 12 * HOUR_MS },
|
||||
'$1',
|
||||
));
|
||||
// 10h left
|
||||
const beacon2 = new Beacon(makeBeaconInfoEvent(aliceId,
|
||||
roomId,
|
||||
{ timeout: HOUR_MS * 10, timestamp: now },
|
||||
'$2',
|
||||
));
|
||||
|
||||
// 1ms left
|
||||
const beacon3 = new Beacon(makeBeaconInfoEvent(aliceId,
|
||||
roomId,
|
||||
{ timeout: HOUR_MS + 1, timestamp: now - HOUR_MS },
|
||||
'$3',
|
||||
));
|
||||
|
||||
it('sorts beacons by descending creation time', () => {
|
||||
expect([beacon1, beacon2, beacon3].sort(sortBeaconsByLatestCreation)).toEqual([
|
||||
beacon2, beacon3, beacon1,
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user