Apply prettier formatting

This commit is contained in:
Michael Weimann 2022-12-12 12:24:14 +01:00
parent 1cac306093
commit 526645c791
No known key found for this signature in database
GPG key ID: 53F535A266BB9584
1576 changed files with 65385 additions and 62478 deletions

View file

@ -68,16 +68,18 @@ describe("CallEvent", () => {
alice = mkRoomMember(room.roomId, "@alice:example.org");
bob = mkRoomMember(room.roomId, "@bob:example.org");
jest.spyOn(room, "getMember").mockImplementation(
userId => [alice, bob].find(member => member.userId === userId) ?? null,
(userId) => [alice, bob].find((member) => member.userId === userId) ?? null,
);
client.getRoom.mockImplementation(roomId => roomId === room.roomId ? room : null);
client.getRoom.mockImplementation((roomId) => (roomId === room.roomId ? room : null));
client.getRooms.mockReturnValue([room]);
client.reEmitter.reEmit(room, [RoomStateEvent.Events]);
await Promise.all([CallStore.instance, WidgetMessagingStore.instance].map(
store => setupAsyncStoreWithClient(store, client),
));
await Promise.all(
[CallStore.instance, WidgetMessagingStore.instance].map((store) =>
setupAsyncStoreWithClient(store, client),
),
);
MockedCall.create(room, "1");
const maybeCall = CallStore.instance.getCall(room.roomId);
@ -99,7 +101,9 @@ describe("CallEvent", () => {
jest.restoreAllMocks();
});
const renderEvent = () => { render(<CallEvent mxEvent={call.event} />); };
const renderEvent = () => {
render(<CallEvent mxEvent={call.event} />);
};
it("shows a message and duration if the call was ended", () => {
jest.advanceTimersByTime(90000);
@ -121,7 +125,10 @@ describe("CallEvent", () => {
it("shows call details and connection controls if the call is loaded", async () => {
jest.advanceTimersByTime(90000);
call.participants = new Map([[alice, new Set(["a"])], [bob, new Set(["b"])]]);
call.participants = new Map([
[alice, new Set(["a"])],
[bob, new Set(["b"])],
]);
renderEvent();
screen.getByText("@alice:example.org started a video call");
@ -132,11 +139,13 @@ describe("CallEvent", () => {
const dispatcherSpy = jest.fn();
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
fireEvent.click(screen.getByRole("button", { name: "Join" }));
await waitFor(() => expect(dispatcherSpy).toHaveBeenCalledWith({
action: Action.ViewRoom,
room_id: room.roomId,
view_call: true,
}));
await waitFor(() =>
expect(dispatcherSpy).toHaveBeenCalledWith({
action: Action.ViewRoom,
room_id: room.roomId,
view_call: true,
}),
);
defaultDispatcher.unregister(dispatcherRef);
await act(() => call.connect());

View file

@ -31,7 +31,7 @@ describe("DateSeparator", () => {
const HOUR_MS = 3600000;
const DAY_MS = HOUR_MS * 24;
// Friday Dec 17 2021, 9:09am
const now = '2021-12-17T08:09:00.000Z';
const now = "2021-12-17T08:09:00.000Z";
const nowMs = 1639728540000;
const defaultProps = {
ts: nowMs,
@ -47,23 +47,23 @@ describe("DateSeparator", () => {
const mockClient = getMockClientWithEventEmitter({});
const getComponent = (props = {}) =>
render((
render(
<MatrixClientContext.Provider value={mockClient}>
<DateSeparator {...defaultProps} {...props} />
</MatrixClientContext.Provider>
));
</MatrixClientContext.Provider>,
);
type TestCase = [string, number, string];
const testCases: TestCase[] = [
['the exact same moment', nowMs, 'Today'],
['same day as current day', nowMs - HOUR_MS, 'Today'],
['day before the current day', nowMs - (HOUR_MS * 12), 'Yesterday'],
['2 days ago', nowMs - DAY_MS * 2, 'Wednesday'],
['144 hours ago', nowMs - HOUR_MS * 144, 'Sat, Dec 11 2021'],
["the exact same moment", nowMs, "Today"],
["same day as current day", nowMs - HOUR_MS, "Today"],
["day before the current day", nowMs - HOUR_MS * 12, "Yesterday"],
["2 days ago", nowMs - DAY_MS * 2, "Wednesday"],
["144 hours ago", nowMs - HOUR_MS * 144, "Sat, Dec 11 2021"],
[
'6 days ago, but less than 144h',
new Date('Saturday Dec 11 2021 23:59:00 GMT+0100 (Central European Standard Time)').getTime(),
'Saturday',
"6 days ago, but less than 144h",
new Date("Saturday Dec 11 2021 23:59:00 GMT+0100 (Central European Standard Time)").getTime(),
"Saturday",
],
];
@ -80,24 +80,25 @@ describe("DateSeparator", () => {
global.Date = RealDate;
});
it('renders the date separator correctly', () => {
it("renders the date separator correctly", () => {
const { asFragment } = getComponent();
expect(asFragment()).toMatchSnapshot();
expect(SettingsStore.getValue).toHaveBeenCalledWith(UIFeature.TimelineEnableRelativeDates);
});
it.each(testCases)('formats date correctly when current time is %s', (_d, ts, result) => {
it.each(testCases)("formats date correctly when current time is %s", (_d, ts, result) => {
expect(getComponent({ ts, forExport: false }).container.textContent).toEqual(result);
});
describe('when forExport is true', () => {
it.each(testCases)('formats date in full when current time is %s', (_d, ts) => {
expect(getComponent({ ts, forExport: true }).container.textContent)
.toEqual(formatFullDateNoTime(new Date(ts)));
describe("when forExport is true", () => {
it.each(testCases)("formats date in full when current time is %s", (_d, ts) => {
expect(getComponent({ ts, forExport: true }).container.textContent).toEqual(
formatFullDateNoTime(new Date(ts)),
);
});
});
describe('when Settings.TimelineEnableRelativeDates is falsy', () => {
describe("when Settings.TimelineEnableRelativeDates is falsy", () => {
beforeEach(() => {
(SettingsStore.getValue as jest.Mock) = jest.fn((arg) => {
if (arg === UIFeature.TimelineEnableRelativeDates) {
@ -105,13 +106,14 @@ describe("DateSeparator", () => {
}
});
});
it.each(testCases)('formats date in full when current time is %s', (_d, ts) => {
expect(getComponent({ ts, forExport: false }).container.textContent)
.toEqual(formatFullDateNoTime(new Date(ts)));
it.each(testCases)("formats date in full when current time is %s", (_d, ts) => {
expect(getComponent({ ts, forExport: false }).container.textContent).toEqual(
formatFullDateNoTime(new Date(ts)),
);
});
});
describe('when feature_jump_to_date is enabled', () => {
describe("when feature_jump_to_date is enabled", () => {
beforeEach(() => {
mocked(SettingsStore).getValue.mockImplementation((arg): any => {
if (arg === "feature_jump_to_date") {
@ -119,7 +121,7 @@ describe("DateSeparator", () => {
}
});
});
it('renders the date separator correctly', () => {
it("renders the date separator correctly", () => {
const { asFragment } = getComponent();
expect(asFragment()).toMatchSnapshot();
});

View file

@ -14,22 +14,24 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { mocked } from "jest-mock";
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
import { render, screen } from '@testing-library/react';
import { render, screen } from "@testing-library/react";
import EncryptionEvent from "../../../../src/components/views/messages/EncryptionEvent";
import { createTestClient, mkMessage } from "../../../test-utils";
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
import { LocalRoom } from '../../../../src/models/LocalRoom';
import DMRoomMap from '../../../../src/utils/DMRoomMap';
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import { LocalRoom } from "../../../../src/models/LocalRoom";
import DMRoomMap from "../../../../src/utils/DMRoomMap";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
const renderEncryptionEvent = (client: MatrixClient, event: MatrixEvent) => {
render(<MatrixClientContext.Provider value={client}>
<EncryptionEvent mxEvent={event} />
</MatrixClientContext.Provider>);
render(
<MatrixClientContext.Provider value={client}>
<EncryptionEvent mxEvent={event} />
</MatrixClientContext.Provider>,
);
};
const checkTexts = (title: string, subTitle: string) => {
@ -69,8 +71,8 @@ describe("EncryptionEvent", () => {
renderEncryptionEvent(client, event);
checkTexts(
"Encryption enabled",
"Messages in this room are end-to-end encrypted. "
+ "When people join, you can verify them in their profile, just tap on their avatar.",
"Messages in this room are end-to-end encrypted. " +
"When people join, you can verify them in their profile, just tap on their avatar.",
);
});
@ -83,10 +85,7 @@ describe("EncryptionEvent", () => {
it("should show the expected texts", () => {
renderEncryptionEvent(client, event);
checkTexts(
"Encryption enabled",
"Some encryption parameters have been changed.",
);
checkTexts("Encryption enabled", "Some encryption parameters have been changed.");
});
});

View file

@ -14,68 +14,58 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import maplibregl from 'maplibre-gl';
import {
BeaconEvent,
getBeaconInfoIdentifier,
RelationType,
MatrixEvent,
EventType,
} from 'matrix-js-sdk/src/matrix';
import { Relations } from 'matrix-js-sdk/src/models/relations';
import { M_BEACON } from 'matrix-js-sdk/src/@types/beacon';
import { mount } from "enzyme";
import { act } from "react-dom/test-utils";
import maplibregl from "maplibre-gl";
import { BeaconEvent, getBeaconInfoIdentifier, RelationType, MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
import { Relations } from "matrix-js-sdk/src/models/relations";
import { M_BEACON } from "matrix-js-sdk/src/@types/beacon";
import MBeaconBody from '../../../../src/components/views/messages/MBeaconBody';
import MBeaconBody from "../../../../src/components/views/messages/MBeaconBody";
import {
getMockClientWithEventEmitter,
makeBeaconEvent,
makeBeaconInfoEvent,
makeRoomWithBeacons,
makeRoomWithStateEvents,
} from '../../../test-utils';
import { RoomPermalinkCreator } from '../../../../src/utils/permalinks/Permalinks';
import { MediaEventHelper } from '../../../../src/utils/MediaEventHelper';
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
import Modal from '../../../../src/Modal';
import { TILE_SERVER_WK_KEY } from '../../../../src/utils/WellKnownUtils';
import { MapError } from '../../../../src/components/views/location/MapError';
import * as mapUtilHooks from '../../../../src/utils/location/useMap';
import { LocationShareError } from '../../../../src/utils/location';
} from "../../../test-utils";
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
import { MediaEventHelper } from "../../../../src/utils/MediaEventHelper";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import Modal from "../../../../src/Modal";
import { TILE_SERVER_WK_KEY } from "../../../../src/utils/WellKnownUtils";
import { MapError } from "../../../../src/components/views/location/MapError";
import * as mapUtilHooks from "../../../../src/utils/location/useMap";
import { LocationShareError } from "../../../../src/utils/location";
describe('<MBeaconBody />', () => {
describe("<MBeaconBody />", () => {
// 14.03.2022 16:15
const now = 1647270879403;
// stable date for snapshots
jest.spyOn(global.Date, 'now').mockReturnValue(now);
const roomId = '!room:server';
const aliceId = '@alice:server';
jest.spyOn(global.Date, "now").mockReturnValue(now);
const roomId = "!room:server";
const aliceId = "@alice:server";
const mockMap = new maplibregl.Map();
const mockMarker = new maplibregl.Marker();
const mockClient = getMockClientWithEventEmitter({
getClientWellKnown: jest.fn().mockReturnValue({
[TILE_SERVER_WK_KEY.name]: { map_style_url: 'maps.com' },
[TILE_SERVER_WK_KEY.name]: { map_style_url: "maps.com" },
}),
getUserId: jest.fn().mockReturnValue(aliceId),
getRoom: jest.fn(),
redactEvent: jest.fn(),
});
const defaultEvent = makeBeaconInfoEvent(aliceId,
roomId,
{ isLive: true },
'$alice-room1-1',
);
const defaultEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: true }, "$alice-room1-1");
const defaultProps = {
mxEvent: defaultEvent,
highlights: [],
highlightLink: '',
highlightLink: "",
onHeightChanged: jest.fn(),
onMessageAllowed: jest.fn(),
// we dont use these and they pollute the snapshots
@ -89,7 +79,7 @@ describe('<MBeaconBody />', () => {
wrappingComponentProps: { value: mockClient },
});
const modalSpy = jest.spyOn(Modal, 'createDialog').mockReturnValue(undefined);
const modalSpy = jest.spyOn(Modal, "createDialog").mockReturnValue(undefined);
beforeAll(() => {
maplibregl.AttributionControl = jest.fn();
@ -100,73 +90,66 @@ describe('<MBeaconBody />', () => {
});
const testBeaconStatuses = () => {
it('renders stopped beacon UI for an explicitly stopped beacon', () => {
const beaconInfoEvent = makeBeaconInfoEvent(aliceId,
roomId,
{ isLive: false },
'$alice-room1-1',
);
it("renders stopped beacon UI for an explicitly stopped beacon", () => {
const beaconInfoEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: false }, "$alice-room1-1");
makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient });
const component = getComponent({ mxEvent: beaconInfoEvent });
expect(component.text()).toEqual("Live location ended");
});
it('renders stopped beacon UI for an expired beacon', () => {
const beaconInfoEvent = makeBeaconInfoEvent(aliceId,
it("renders stopped beacon UI for an expired beacon", () => {
const beaconInfoEvent = makeBeaconInfoEvent(
aliceId,
roomId,
// puts this beacons live period in the past
{ isLive: true, timestamp: now - 600000, timeout: 500 },
'$alice-room1-1',
"$alice-room1-1",
);
makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient });
const component = getComponent({ mxEvent: beaconInfoEvent });
expect(component.text()).toEqual("Live location ended");
});
it('renders loading beacon UI for a beacon that has not started yet', () => {
it("renders loading beacon UI for a beacon that has not started yet", () => {
const beaconInfoEvent = makeBeaconInfoEvent(
aliceId,
roomId,
// puts this beacons start timestamp in the future
{ isLive: true, timestamp: now + 60000, timeout: 500 },
'$alice-room1-1',
"$alice-room1-1",
);
makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient });
const component = getComponent({ mxEvent: beaconInfoEvent });
expect(component.text()).toEqual("Loading live location...");
});
it('does not open maximised map when on click when beacon is stopped', () => {
const beaconInfoEvent = makeBeaconInfoEvent(aliceId,
it("does not open maximised map when on click when beacon is stopped", () => {
const beaconInfoEvent = makeBeaconInfoEvent(
aliceId,
roomId,
// puts this beacons live period in the past
{ isLive: true, timestamp: now - 600000, timeout: 500 },
'$alice-room1-1',
"$alice-room1-1",
);
makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient });
const component = getComponent({ mxEvent: beaconInfoEvent });
act(() => {
component.find('.mx_MBeaconBody_map').at(0).simulate('click');
component.find(".mx_MBeaconBody_map").at(0).simulate("click");
});
expect(modalSpy).not.toHaveBeenCalled();
});
it('renders stopped UI when a beacon event is not the latest beacon for a user', () => {
it("renders stopped UI when a beacon event is not the latest beacon for a user", () => {
const aliceBeaconInfo1 = makeBeaconInfoEvent(
aliceId,
roomId,
// this one is a little older
{ isLive: true, timestamp: now - 500 },
'$alice-room1-1',
"$alice-room1-1",
);
aliceBeaconInfo1.event.origin_server_ts = now - 500;
const aliceBeaconInfo2 = makeBeaconInfoEvent(
aliceId,
roomId,
{ isLive: true },
'$alice-room1-2',
);
const aliceBeaconInfo2 = makeBeaconInfoEvent(aliceId, roomId, { isLive: true }, "$alice-room1-2");
makeRoomWithStateEvents([aliceBeaconInfo1, aliceBeaconInfo2], { roomId, mockClient });
@ -175,21 +158,16 @@ describe('<MBeaconBody />', () => {
expect(component.text()).toEqual("Live location ended");
});
it('renders stopped UI when a beacon event is replaced', () => {
it("renders stopped UI when a beacon event is replaced", () => {
const aliceBeaconInfo1 = makeBeaconInfoEvent(
aliceId,
roomId,
// this one is a little older
{ isLive: true, timestamp: now - 500 },
'$alice-room1-1',
"$alice-room1-1",
);
aliceBeaconInfo1.event.origin_server_ts = now - 500;
const aliceBeaconInfo2 = makeBeaconInfoEvent(
aliceId,
roomId,
{ isLive: true },
'$alice-room1-2',
);
const aliceBeaconInfo2 = makeBeaconInfoEvent(aliceId, roomId, { isLive: true }, "$alice-room1-2");
const room = makeRoomWithStateEvents([aliceBeaconInfo1], { roomId, mockClient });
const component = getComponent({ mxEvent: aliceBeaconInfo1 });
@ -210,14 +188,9 @@ describe('<MBeaconBody />', () => {
testBeaconStatuses();
describe('on liveness change', () => {
it('renders stopped UI when a beacon stops being live', () => {
const aliceBeaconInfo = makeBeaconInfoEvent(
aliceId,
roomId,
{ isLive: true },
'$alice-room1-1',
);
describe("on liveness change", () => {
it("renders stopped UI when a beacon stops being live", () => {
const aliceBeaconInfo = makeBeaconInfoEvent(aliceId, roomId, { isLive: true }, "$alice-room1-1");
const room = makeRoomWithStateEvents([aliceBeaconInfo], { roomId, mockClient });
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo));
@ -236,97 +209,96 @@ describe('<MBeaconBody />', () => {
});
});
describe('latestLocationState', () => {
const aliceBeaconInfo = makeBeaconInfoEvent(
aliceId,
roomId,
{ isLive: true },
'$alice-room1-1',
);
describe("latestLocationState", () => {
const aliceBeaconInfo = makeBeaconInfoEvent(aliceId, roomId, { isLive: true }, "$alice-room1-1");
const location1 = makeBeaconEvent(
aliceId, { beaconInfoId: aliceBeaconInfo.getId(), geoUri: 'geo:51,41', timestamp: now + 1 },
);
const location2 = makeBeaconEvent(
aliceId, { beaconInfoId: aliceBeaconInfo.getId(), geoUri: 'geo:52,42', timestamp: now + 10000 },
);
const location1 = makeBeaconEvent(aliceId, {
beaconInfoId: aliceBeaconInfo.getId(),
geoUri: "geo:51,41",
timestamp: now + 1,
});
const location2 = makeBeaconEvent(aliceId, {
beaconInfoId: aliceBeaconInfo.getId(),
geoUri: "geo:52,42",
timestamp: now + 10000,
});
it('renders a live beacon without a location correctly', () => {
it("renders a live beacon without a location correctly", () => {
makeRoomWithStateEvents([aliceBeaconInfo], { roomId, mockClient });
const component = getComponent({ mxEvent: aliceBeaconInfo });
expect(component.text()).toEqual("Loading live location...");
});
it('does nothing on click when a beacon has no location', () => {
it("does nothing on click when a beacon has no location", () => {
makeRoomWithStateEvents([aliceBeaconInfo], { roomId, mockClient });
const component = getComponent({ mxEvent: aliceBeaconInfo });
act(() => {
component.find('.mx_MBeaconBody_map').at(0).simulate('click');
component.find(".mx_MBeaconBody_map").at(0).simulate("click");
});
expect(modalSpy).not.toHaveBeenCalled();
});
it('renders a live beacon with a location correctly', () => {
it("renders a live beacon with a location correctly", () => {
const room = makeRoomWithStateEvents([aliceBeaconInfo], { roomId, mockClient });
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo));
beaconInstance.addLocations([location1]);
const component = getComponent({ mxEvent: aliceBeaconInfo });
expect(component.find('Map').length).toBeTruthy;
expect(component.find("Map").length).toBeTruthy;
});
it('opens maximised map view on click when beacon has a live location', () => {
it("opens maximised map view on click when beacon has a live location", () => {
const room = makeRoomWithStateEvents([aliceBeaconInfo], { roomId, mockClient });
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo));
beaconInstance.addLocations([location1]);
const component = getComponent({ mxEvent: aliceBeaconInfo });
act(() => {
component.find('Map').simulate('click');
component.find("Map").simulate("click");
});
// opens modal
expect(modalSpy).toHaveBeenCalled();
});
it('does nothing on click when a beacon has no location', () => {
it("does nothing on click when a beacon has no location", () => {
makeRoomWithStateEvents([aliceBeaconInfo], { roomId, mockClient });
const component = getComponent({ mxEvent: aliceBeaconInfo });
act(() => {
component.find('.mx_MBeaconBody_map').at(0).simulate('click');
component.find(".mx_MBeaconBody_map").at(0).simulate("click");
});
expect(modalSpy).not.toHaveBeenCalled();
});
it('renders a live beacon with a location correctly', () => {
it("renders a live beacon with a location correctly", () => {
const room = makeRoomWithStateEvents([aliceBeaconInfo], { roomId, mockClient });
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo));
beaconInstance.addLocations([location1]);
const component = getComponent({ mxEvent: aliceBeaconInfo });
expect(component.find('Map').length).toBeTruthy;
expect(component.find("Map").length).toBeTruthy;
});
it('opens maximised map view on click when beacon has a live location', () => {
it("opens maximised map view on click when beacon has a live location", () => {
const room = makeRoomWithStateEvents([aliceBeaconInfo], { roomId, mockClient });
const beaconInstance = room.currentState.beacons.get(getBeaconInfoIdentifier(aliceBeaconInfo));
beaconInstance.addLocations([location1]);
const component = getComponent({ mxEvent: aliceBeaconInfo });
act(() => {
component.find('Map').simulate('click');
component.find("Map").simulate("click");
});
// opens modal
expect(modalSpy).toHaveBeenCalled();
});
it('updates latest location', () => {
it("updates latest location", () => {
const room = makeRoomWithStateEvents([aliceBeaconInfo], { roomId, mockClient });
const component = getComponent({ mxEvent: aliceBeaconInfo });
@ -349,33 +321,30 @@ describe('<MBeaconBody />', () => {
});
});
describe('redaction', () => {
describe("redaction", () => {
const makeEvents = (): {
beaconInfoEvent: MatrixEvent;
location1: MatrixEvent;
location2: MatrixEvent;
} => {
const beaconInfoEvent = makeBeaconInfoEvent(
aliceId,
roomId,
{ isLive: true },
'$alice-room1-1',
);
const beaconInfoEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: true }, "$alice-room1-1");
const location1 = makeBeaconEvent(
aliceId, { beaconInfoId: beaconInfoEvent.getId(), geoUri: 'geo:51,41', timestamp: now + 1 },
aliceId,
{ beaconInfoId: beaconInfoEvent.getId(), geoUri: "geo:51,41", timestamp: now + 1 },
roomId,
);
location1.event.event_id = '1';
location1.event.event_id = "1";
const location2 = makeBeaconEvent(
aliceId, { beaconInfoId: beaconInfoEvent.getId(), geoUri: 'geo:52,42', timestamp: now + 10000 },
aliceId,
{ beaconInfoId: beaconInfoEvent.getId(), geoUri: "geo:52,42", timestamp: now + 10000 },
roomId,
);
location2.event.event_id = '2';
location2.event.event_id = "2";
return { beaconInfoEvent, location1, location2 };
};
const redactionEvent = new MatrixEvent({ type: EventType.RoomRedaction, content: { reason: 'test reason' } });
const redactionEvent = new MatrixEvent({ type: EventType.RoomRedaction, content: { reason: "test reason" } });
const setupRoomWithBeacon = (beaconInfoEvent, locationEvents: MatrixEvent[] = []) => {
const room = makeRoomWithStateEvents([beaconInfoEvent], { roomId, mockClient });
@ -384,14 +353,14 @@ describe('<MBeaconBody />', () => {
};
const mockGetRelationsForEvent = (locationEvents: MatrixEvent[] = []) => {
const relations = new Relations(RelationType.Reference, M_BEACON.name, mockClient);
jest.spyOn(relations, 'getRelations').mockReturnValue(locationEvents);
jest.spyOn(relations, "getRelations").mockReturnValue(locationEvents);
const getRelationsForEvent = jest.fn().mockReturnValue(relations);
return getRelationsForEvent;
};
it('does nothing when getRelationsForEvent is falsy', () => {
it("does nothing when getRelationsForEvent is falsy", () => {
const { beaconInfoEvent, location1, location2 } = makeEvents();
setupRoomWithBeacon(beaconInfoEvent, [location1, location2]);
@ -405,10 +374,10 @@ describe('<MBeaconBody />', () => {
expect(mockClient.redactEvent).not.toHaveBeenCalled();
});
it('cleans up redaction listener on unmount', () => {
it("cleans up redaction listener on unmount", () => {
const { beaconInfoEvent, location1, location2 } = makeEvents();
setupRoomWithBeacon(beaconInfoEvent, [location1, location2]);
const removeListenerSpy = jest.spyOn(beaconInfoEvent, 'removeListener');
const removeListenerSpy = jest.spyOn(beaconInfoEvent, "removeListener");
const component = getComponent({ mxEvent: beaconInfoEvent });
@ -419,7 +388,7 @@ describe('<MBeaconBody />', () => {
expect(removeListenerSpy).toHaveBeenCalled();
});
it('does nothing when beacon has no related locations', async () => {
it("does nothing when beacon has no related locations", async () => {
const { beaconInfoEvent } = makeEvents();
// no locations
setupRoomWithBeacon(beaconInfoEvent, []);
@ -432,12 +401,14 @@ describe('<MBeaconBody />', () => {
});
expect(getRelationsForEvent).toHaveBeenCalledWith(
beaconInfoEvent.getId(), RelationType.Reference, M_BEACON.name,
beaconInfoEvent.getId(),
RelationType.Reference,
M_BEACON.name,
);
expect(mockClient.redactEvent).not.toHaveBeenCalled();
});
it('redacts related locations on beacon redaction', async () => {
it("redacts related locations on beacon redaction", async () => {
const { beaconInfoEvent, location1, location2 } = makeEvents();
setupRoomWithBeacon(beaconInfoEvent, [location1, location2]);
@ -450,43 +421,36 @@ describe('<MBeaconBody />', () => {
});
expect(getRelationsForEvent).toHaveBeenCalledWith(
beaconInfoEvent.getId(), RelationType.Reference, M_BEACON.name,
beaconInfoEvent.getId(),
RelationType.Reference,
M_BEACON.name,
);
expect(mockClient.redactEvent).toHaveBeenCalledTimes(2);
expect(mockClient.redactEvent).toHaveBeenCalledWith(
roomId,
location1.getId(),
undefined,
{ reason: 'test reason' },
);
expect(mockClient.redactEvent).toHaveBeenCalledWith(
roomId,
location2.getId(),
undefined,
{ reason: 'test reason' },
);
expect(mockClient.redactEvent).toHaveBeenCalledWith(roomId, location1.getId(), undefined, {
reason: "test reason",
});
expect(mockClient.redactEvent).toHaveBeenCalledWith(roomId, location2.getId(), undefined, {
reason: "test reason",
});
});
});
describe('when map display is not configured', () => {
describe("when map display is not configured", () => {
beforeEach(() => {
// mock map utils to raise MapStyleUrlNotConfigured error
jest.spyOn(mapUtilHooks, 'useMap').mockImplementation(
({ onError }) => {
onError(new Error(LocationShareError.MapStyleUrlNotConfigured));
return mockMap;
});
jest.spyOn(mapUtilHooks, "useMap").mockImplementation(({ onError }) => {
onError(new Error(LocationShareError.MapStyleUrlNotConfigured));
return mockMap;
});
});
it('renders maps unavailable error for a live beacon with location', () => {
const beaconInfoEvent = makeBeaconInfoEvent(aliceId,
roomId,
{ isLive: true },
'$alice-room1-1',
);
const location1 = makeBeaconEvent(
aliceId, { beaconInfoId: beaconInfoEvent.getId(), geoUri: 'geo:51,41', timestamp: now + 1 },
);
it("renders maps unavailable error for a live beacon with location", () => {
const beaconInfoEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: true }, "$alice-room1-1");
const location1 = makeBeaconEvent(aliceId, {
beaconInfoId: beaconInfoEvent.getId(),
geoUri: "geo:51,41",
timestamp: now + 1,
});
makeRoomWithBeacons(roomId, mockClient, [beaconInfoEvent], [location1]);

View file

@ -48,8 +48,8 @@ describe("<MImageBody/>", () => {
getIgnoredUsers: jest.fn(),
getVersions: jest.fn().mockResolvedValue({
unstable_features: {
'org.matrix.msc3882': true,
'org.matrix.msc3886': true,
"org.matrix.msc3882": true,
"org.matrix.msc3886": true,
},
}),
});
@ -75,11 +75,13 @@ describe("<MImageBody/>", () => {
it("should show error when encrypted media cannot be downloaded", async () => {
fetchMock.getOnce(url, { status: 500 });
render(<MImageBody
{...props}
mxEvent={encryptedMediaEvent}
mediaEventHelper={new MediaEventHelper(encryptedMediaEvent)}
/>);
render(
<MImageBody
{...props}
mxEvent={encryptedMediaEvent}
mediaEventHelper={new MediaEventHelper(encryptedMediaEvent)}
/>,
);
await screen.findByText("Error downloading image");
});
@ -88,11 +90,13 @@ describe("<MImageBody/>", () => {
fetchMock.getOnce(url, "thisistotallyanencryptedpng");
mocked(encrypt.decryptAttachment).mockRejectedValue(new Error("Failed to decrypt"));
render(<MImageBody
{...props}
mxEvent={encryptedMediaEvent}
mediaEventHelper={new MediaEventHelper(encryptedMediaEvent)}
/>);
render(
<MImageBody
{...props}
mxEvent={encryptedMediaEvent}
mediaEventHelper={new MediaEventHelper(encryptedMediaEvent)}
/>,
);
await screen.findByText("Error decrypting image");
});

View file

@ -14,33 +14,40 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import TestRenderer from 'react-test-renderer';
import { EventEmitter } from 'events';
import { MatrixEvent, EventType } from 'matrix-js-sdk/src/matrix';
import { CryptoEvent } from 'matrix-js-sdk/src/crypto';
import { UserTrustLevel } from 'matrix-js-sdk/src/crypto/CrossSigning';
import { VerificationRequest } from 'matrix-js-sdk/src/crypto/verification/request/VerificationRequest';
import React from "react";
import TestRenderer from "react-test-renderer";
import { EventEmitter } from "events";
import { MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
import { UserTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
import MKeyVerificationConclusion from '../../../../src/components/views/messages/MKeyVerificationConclusion';
import { getMockClientWithEventEmitter } from '../../../test-utils';
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import MKeyVerificationConclusion from "../../../../src/components/views/messages/MKeyVerificationConclusion";
import { getMockClientWithEventEmitter } from "../../../test-utils";
const trustworthy = ({ isCrossSigningVerified: () => true }) as unknown as UserTrustLevel;
const untrustworthy = ({ isCrossSigningVerified: () => false }) as unknown as UserTrustLevel;
const trustworthy = { isCrossSigningVerified: () => true } as unknown as UserTrustLevel;
const untrustworthy = { isCrossSigningVerified: () => false } as unknown as UserTrustLevel;
describe("MKeyVerificationConclusion", () => {
const userId = '@user:server';
const userId = "@user:server";
const mockClient = getMockClientWithEventEmitter({
getRoom: jest.fn(),
getUserId: jest.fn().mockReturnValue(userId),
checkUserTrust: jest.fn(),
});
const getMockVerificationRequest = (
{ pending, cancelled, done, otherUserId }:
{ pending?: boolean, cancelled?: boolean, done?: boolean, otherUserId?: string },
) => {
const getMockVerificationRequest = ({
pending,
cancelled,
done,
otherUserId,
}: {
pending?: boolean;
cancelled?: boolean;
done?: boolean;
otherUserId?: string;
}) => {
class MockVerificationRequest extends EventEmitter {
constructor(
public readonly pending: boolean,
@ -60,41 +67,33 @@ describe("MKeyVerificationConclusion", () => {
});
afterAll(() => {
jest.spyOn(MatrixClientPeg, 'get').mockRestore();
jest.spyOn(MatrixClientPeg, "get").mockRestore();
});
it("shouldn't render if there's no verificationRequest", () => {
const event = new MatrixEvent({});
const renderer = TestRenderer.create(
<MKeyVerificationConclusion mxEvent={event} />,
);
const renderer = TestRenderer.create(<MKeyVerificationConclusion mxEvent={event} />);
expect(renderer.toJSON()).toBeNull();
});
it("shouldn't render if the verificationRequest is pending", () => {
const event = new MatrixEvent({});
event.verificationRequest = getMockVerificationRequest({ pending: true });
const renderer = TestRenderer.create(
<MKeyVerificationConclusion mxEvent={event} />,
);
const renderer = TestRenderer.create(<MKeyVerificationConclusion mxEvent={event} />);
expect(renderer.toJSON()).toBeNull();
});
it("shouldn't render if the event type is cancel but the request type isn't", () => {
const event = new MatrixEvent({ type: EventType.KeyVerificationCancel });
event.verificationRequest = getMockVerificationRequest({ cancelled: false });
const renderer = TestRenderer.create(
<MKeyVerificationConclusion mxEvent={event} />,
);
const renderer = TestRenderer.create(<MKeyVerificationConclusion mxEvent={event} />);
expect(renderer.toJSON()).toBeNull();
});
it("shouldn't render if the event type is done but the request type isn't", () => {
const event = new MatrixEvent({ type: "m.key.verification.done" });
event.verificationRequest = getMockVerificationRequest({ done: false });
const renderer = TestRenderer.create(
<MKeyVerificationConclusion mxEvent={event} />,
);
const renderer = TestRenderer.create(<MKeyVerificationConclusion mxEvent={event} />);
expect(renderer.toJSON()).toBeNull();
});
@ -103,9 +102,7 @@ describe("MKeyVerificationConclusion", () => {
const event = new MatrixEvent({ type: "m.key.verification.done" });
event.verificationRequest = getMockVerificationRequest({ done: true });
const renderer = TestRenderer.create(
<MKeyVerificationConclusion mxEvent={event} />,
);
const renderer = TestRenderer.create(<MKeyVerificationConclusion mxEvent={event} />);
expect(renderer.toJSON()).toBeNull();
});
@ -114,9 +111,7 @@ describe("MKeyVerificationConclusion", () => {
const event = new MatrixEvent({ type: "m.key.verification.done" });
event.verificationRequest = getMockVerificationRequest({ done: true, otherUserId: "@someuser:domain" });
const renderer = TestRenderer.create(
<MKeyVerificationConclusion mxEvent={event} />,
);
const renderer = TestRenderer.create(<MKeyVerificationConclusion mxEvent={event} />);
expect(renderer.toJSON()).toBeNull();
mockClient.checkUserTrust.mockReturnValue(trustworthy);

View file

@ -14,33 +14,33 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount } from "enzyme";
import { LocationAssetType } from "matrix-js-sdk/src/@types/location";
import { ClientEvent, RoomMember } from 'matrix-js-sdk/src/matrix';
import maplibregl from 'maplibre-gl';
import { logger } from 'matrix-js-sdk/src/logger';
import { act } from 'react-dom/test-utils';
import { SyncState } from 'matrix-js-sdk/src/sync';
import { ClientEvent, RoomMember } from "matrix-js-sdk/src/matrix";
import maplibregl from "maplibre-gl";
import { logger } from "matrix-js-sdk/src/logger";
import { act } from "react-dom/test-utils";
import { SyncState } from "matrix-js-sdk/src/sync";
import MLocationBody from "../../../../src/components/views/messages/MLocationBody";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
import { MediaEventHelper } from "../../../../src/utils/MediaEventHelper";
import Modal from '../../../../src/Modal';
import Modal from "../../../../src/Modal";
import SdkConfig from "../../../../src/SdkConfig";
import { TILE_SERVER_WK_KEY } from '../../../../src/utils/WellKnownUtils';
import { TILE_SERVER_WK_KEY } from "../../../../src/utils/WellKnownUtils";
import { makeLocationEvent } from "../../../test-utils/location";
import { getMockClientWithEventEmitter } from '../../../test-utils';
import { getMockClientWithEventEmitter } from "../../../test-utils";
describe("MLocationBody", () => {
describe('<MLocationBody>', () => {
const roomId = '!room:server';
const userId = '@user:server';
describe("<MLocationBody>", () => {
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' },
[TILE_SERVER_WK_KEY.name]: { map_style_url: "maps.com" },
}),
isGuest: jest.fn().mockReturnValue(false),
});
@ -48,26 +48,27 @@ describe("MLocationBody", () => {
const defaultProps = {
mxEvent: defaultEvent,
highlights: [],
highlightLink: '',
highlightLink: "",
onHeightChanged: jest.fn(),
onMessageAllowed: jest.fn(),
permalinkCreator: {} as RoomPermalinkCreator,
mediaEventHelper: {} as MediaEventHelper,
};
const getComponent = (props = {}) => mount(<MLocationBody {...defaultProps} {...props} />, {
wrappingComponent: MatrixClientContext.Provider,
wrappingComponentProps: { value: mockClient },
});
const getComponent = (props = {}) =>
mount(<MLocationBody {...defaultProps} {...props} />, {
wrappingComponent: MatrixClientContext.Provider,
wrappingComponentProps: { value: mockClient },
});
const getMapErrorComponent = () => {
const mockMap = new maplibregl.Map();
mockClient.getClientWellKnown.mockReturnValue({
[TILE_SERVER_WK_KEY.name]: { map_style_url: 'bad-tile-server.com' },
[TILE_SERVER_WK_KEY.name]: { map_style_url: "bad-tile-server.com" },
});
const component = getComponent();
// simulate error initialising map in maplibregl
// @ts-ignore
mockMap.emit('error', { status: 404 });
mockMap.emit("error", { status: 404 });
return component;
};
@ -80,33 +81,33 @@ describe("MLocationBody", () => {
jest.clearAllMocks();
});
describe('with error', () => {
describe("with error", () => {
let sdkConfigSpy;
beforeEach(() => {
// eat expected errors to keep console clean
jest.spyOn(logger, 'error').mockImplementation(() => { });
jest.spyOn(logger, "error").mockImplementation(() => {});
mockClient.getClientWellKnown.mockReturnValue({});
sdkConfigSpy = jest.spyOn(SdkConfig, 'get').mockReturnValue({});
sdkConfigSpy = jest.spyOn(SdkConfig, "get").mockReturnValue({});
});
afterAll(() => {
sdkConfigSpy.mockRestore();
jest.spyOn(logger, 'error').mockRestore();
jest.spyOn(logger, "error").mockRestore();
});
it('displays correct fallback content without error style when map_style_url is not configured', () => {
it("displays correct fallback content without error style when map_style_url is not configured", () => {
const component = getComponent();
expect(component.find(".mx_EventTile_body")).toMatchSnapshot();
});
it('displays correct fallback content when map_style_url is misconfigured', () => {
it("displays correct fallback content when map_style_url is misconfigured", () => {
const component = getMapErrorComponent();
component.setProps({});
expect(component.find(".mx_EventTile_body")).toMatchSnapshot();
});
it('should clear the error on reconnect', () => {
it("should clear the error on reconnect", () => {
const component = getMapErrorComponent();
expect((component.state() as React.ComponentState).error).toBeDefined();
mockClient.emit(ClientEvent.Sync, SyncState.Reconnecting, SyncState.Error);
@ -114,57 +115,58 @@ describe("MLocationBody", () => {
});
});
describe('without error', () => {
describe("without error", () => {
beforeEach(() => {
mockClient.getClientWellKnown.mockReturnValue({
[TILE_SERVER_WK_KEY.name]: { map_style_url: 'maps.com' },
[TILE_SERVER_WK_KEY.name]: { map_style_url: "maps.com" },
});
// MLocationBody uses random number for map id
// stabilise for test
jest.spyOn(global.Math, 'random').mockReturnValue(0.123456);
jest.spyOn(global.Math, "random").mockReturnValue(0.123456);
});
afterAll(() => {
jest.spyOn(global.Math, 'random').mockRestore();
jest.spyOn(global.Math, "random").mockRestore();
});
it('renders map correctly', () => {
it("renders map correctly", () => {
const mockMap = new maplibregl.Map();
const component = getComponent();
expect(component).toMatchSnapshot();
// map was centered
expect(mockMap.setCenter).toHaveBeenCalledWith({
lat: 51.5076, lon: -0.1276,
lat: 51.5076,
lon: -0.1276,
});
});
it('opens map dialog on click', () => {
const modalSpy = jest.spyOn(Modal, 'createDialog').mockReturnValue(undefined);
it("opens map dialog on click", () => {
const modalSpy = jest.spyOn(Modal, "createDialog").mockReturnValue(undefined);
const component = getComponent();
act(() => {
component.find('Map').at(0).simulate('click');
component.find("Map").at(0).simulate("click");
});
expect(modalSpy).toHaveBeenCalled();
});
it('renders marker correctly for a non-self share', () => {
it("renders marker correctly for a non-self share", () => {
const mockMap = new maplibregl.Map();
const component = getComponent();
expect(component.find('SmartMarker').at(0).props()).toEqual(
expect(component.find("SmartMarker").at(0).props()).toEqual(
expect.objectContaining({
map: mockMap,
geoUri: 'geo:51.5076,-0.1276',
geoUri: "geo:51.5076,-0.1276",
roomMember: undefined,
}),
);
});
it('renders marker correctly for a self share', () => {
it("renders marker correctly for a self share", () => {
const selfShareEvent = makeLocationEvent("geo:51.5076,-0.1276", LocationAssetType.Self);
const member = new RoomMember(roomId, userId);
// @ts-ignore cheat assignment to property
@ -172,9 +174,7 @@ describe("MLocationBody", () => {
const component = getComponent({ mxEvent: selfShareEvent });
// render self locations with user avatars
expect(component.find('SmartMarker').at(0).props()['roomMember']).toEqual(
member,
);
expect(component.find("SmartMarker").at(0).props()["roomMember"]).toEqual(member);
});
});
});

View file

@ -49,7 +49,7 @@ const CHECKED = "mx_MPollBody_option_checked";
const mockClient = getMockClientWithEventEmitter({
getUserId: jest.fn().mockReturnValue("@me:example.com"),
sendEvent: jest.fn().mockReturnValue(Promise.resolve({ "event_id": "fake_send_id" })),
sendEvent: jest.fn().mockReturnValue(Promise.resolve({ event_id: "fake_send_id" })),
getRoom: jest.fn(),
});
@ -76,9 +76,7 @@ describe("MPollBody", () => {
const ev2 = responseEvent();
const badEvent = badResponseEvent();
const voteRelations = new RelatedRelations([
newVoteRelations([ev1, badEvent, ev2]),
]);
const voteRelations = new RelatedRelations([newVoteRelations([ev1, badEvent, ev2])]);
expect(
allVotes(
{ getRoomId: () => "$room" } as MatrixEvent,
@ -87,21 +85,13 @@ describe("MPollBody", () => {
new RelatedRelations([newEndRelations([])]),
),
).toEqual([
new UserVote(
ev1.getTs(),
ev1.getSender(),
ev1.getContent()[M_POLL_RESPONSE.name].answers,
),
new UserVote(ev1.getTs(), ev1.getSender(), ev1.getContent()[M_POLL_RESPONSE.name].answers),
new UserVote(
badEvent.getTs(),
badEvent.getSender(),
[], // should be spoiled
),
new UserVote(
ev2.getTs(),
ev2.getSender(),
ev2.getContent()[M_POLL_RESPONSE.name].answers,
),
new UserVote(ev2.getTs(), ev2.getSender(), ev2.getContent()[M_POLL_RESPONSE.name].answers),
]);
});
@ -117,13 +107,7 @@ describe("MPollBody", () => {
setRedactionAllowedForMeOnly(mockClient);
expect(
pollEndTs(
{ getRoomId: () => "$room" } as MatrixEvent,
mockClient,
endRelations,
),
).toBe(12);
expect(pollEndTs({ getRoomId: () => "$room" } as MatrixEvent, mockClient, endRelations)).toBe(12);
});
it("ignores unauthorised end poll event when finding end ts", () => {
@ -138,13 +122,7 @@ describe("MPollBody", () => {
setRedactionAllowedForMeOnly(mockClient);
expect(
pollEndTs(
{ getRoomId: () => "$room" } as MatrixEvent,
mockClient,
endRelations,
),
).toBe(13);
expect(pollEndTs({ getRoomId: () => "$room" } as MatrixEvent, mockClient, endRelations)).toBe(13);
});
it("counts only votes before the end poll event", () => {
@ -157,18 +135,9 @@ describe("MPollBody", () => {
responseEvent("ps@matrix.org", "wings", 19),
]),
]);
const endRelations = new RelatedRelations([
newEndRelations([
endEvent("@me:example.com", 25),
]),
]);
const endRelations = new RelatedRelations([newEndRelations([endEvent("@me:example.com", 25)])]);
expect(
allVotes(
{ getRoomId: () => "$room" } as MatrixEvent,
MatrixClientPeg.get(),
voteRelations,
endRelations,
),
allVotes({ getRoomId: () => "$room" } as MatrixEvent, MatrixClientPeg.get(), voteRelations, endRelations),
).toEqual([
new UserVote(13, "sf@matrix.org", ["wings"]),
new UserVote(13, "id@matrix.org", ["wings"]),
@ -184,8 +153,7 @@ describe("MPollBody", () => {
expect(votesCount(body, "italian")).toBe("");
expect(votesCount(body, "wings")).toBe("");
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("No votes cast");
expect(body.find('h2').html())
.toEqual("<h2>What should we order for the party?</h2>");
expect(body.find("h2").html()).toEqual("<h2>What should we order for the party?</h2>");
});
it("finds votes from multiple people", () => {
@ -210,9 +178,7 @@ describe("MPollBody", () => {
responseEvent("@catrd:example.com", "poutine"),
responseEvent("@dune2:example.com", "wings"),
];
const ends = [
endEvent("@notallowed:example.com", 12),
];
const ends = [endEvent("@notallowed:example.com", 12)];
const body = newMPollBody(votes, ends);
// Even though an end event was sent, we render the poll as unfinished
@ -236,27 +202,23 @@ describe("MPollBody", () => {
expect(votesCount(body, "poutine")).toBe("");
expect(votesCount(body, "italian")).toBe("");
expect(votesCount(body, "wings")).toBe("");
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe(
"4 votes cast. Vote to see the results");
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("4 votes cast. Vote to see the results");
});
it("hides a single vote if I have not voted", () => {
const votes = [
responseEvent("@alice:example.com", "pizza"),
];
const votes = [responseEvent("@alice:example.com", "pizza")];
const body = newMPollBody(votes);
expect(votesCount(body, "pizza")).toBe("");
expect(votesCount(body, "poutine")).toBe("");
expect(votesCount(body, "italian")).toBe("");
expect(votesCount(body, "wings")).toBe("");
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe(
"1 vote cast. Vote to see the results");
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("1 vote cast. Vote to see the results");
});
it("takes someone's most recent vote if they voted several times", () => {
const votes = [
responseEvent("@me:example.com", "pizza", 12),
responseEvent("@me:example.com", "wings", 20), // latest me
responseEvent("@me:example.com", "wings", 20), // latest me
responseEvent("@qbert:example.com", "pizza", 14),
responseEvent("@qbert:example.com", "poutine", 16), // latest qbert
responseEvent("@qbert:example.com", "wings", 15),
@ -321,8 +283,7 @@ describe("MPollBody", () => {
const votes = [responseEvent("@me:example.com", "pizza", 100)];
const body = newMPollBody(votes);
const props: IBodyProps = body.instance().props as IBodyProps;
const voteRelations = props!.getRelationsForEvent!(
"$mypoll", "m.reference", M_POLL_RESPONSE.name);
const voteRelations = props!.getRelationsForEvent!("$mypoll", "m.reference", M_POLL_RESPONSE.name);
expect(voteRelations).toBeDefined();
clickRadio(body, "pizza");
@ -343,8 +304,7 @@ describe("MPollBody", () => {
const votes = [responseEvent("@me:example.com", "pizza")];
const body = newMPollBody(votes);
const props: IBodyProps = body.instance().props as IBodyProps;
const voteRelations = props!.getRelationsForEvent!(
"$mypoll", "m.reference", M_POLL_RESPONSE.name);
const voteRelations = props!.getRelationsForEvent!("$mypoll", "m.reference", M_POLL_RESPONSE.name);
expect(voteRelations).toBeDefined();
clickRadio(body, "pizza");
@ -369,10 +329,7 @@ describe("MPollBody", () => {
it("highlights my vote even if I did it on another device", () => {
// Given I voted italian
const votes = [
responseEvent("@me:example.com", "italian"),
responseEvent("@nf:example.com", "wings"),
];
const votes = [responseEvent("@me:example.com", "italian"), responseEvent("@nf:example.com", "wings")];
const body = newMPollBody(votes);
// But I didn't click anything locally
@ -384,10 +341,7 @@ describe("MPollBody", () => {
it("ignores extra answers", () => {
// When cb votes for 2 things, we consider the first only
const votes = [
responseEvent("@cb:example.com", ["pizza", "wings"]),
responseEvent("@me:example.com", "wings"),
];
const votes = [responseEvent("@cb:example.com", ["pizza", "wings"]), responseEvent("@me:example.com", "wings")];
const body = newMPollBody(votes);
expect(votesCount(body, "pizza")).toBe("1 vote");
expect(votesCount(body, "poutine")).toBe("0 votes");
@ -470,14 +424,12 @@ describe("MPollBody", () => {
it("renders the first 20 answers if 21 were given", () => {
const answers = Array.from(Array(21).keys()).map((i) => {
return { "id": `id${i}`, [M_TEXT.name]: `Name ${i}` };
return { id: `id${i}`, [M_TEXT.name]: `Name ${i}` };
});
const votes = [];
const ends = [];
const body = newMPollBody(votes, ends, answers);
expect(
body.find('.mx_MPollBody_option').length,
).toBe(20);
expect(body.find(".mx_MPollBody_option").length).toBe(20);
});
it("hides scores if I voted but the poll is undisclosed", () => {
@ -493,8 +445,7 @@ describe("MPollBody", () => {
expect(votesCount(body, "poutine")).toBe("");
expect(votesCount(body, "italian")).toBe("");
expect(votesCount(body, "wings")).toBe("");
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe(
"Results will be visible when the poll is ended");
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Results will be visible when the poll is ended");
});
it("highlights my vote if the poll is undisclosed", () => {
@ -522,16 +473,13 @@ describe("MPollBody", () => {
responseEvent("@catrd:example.com", "poutine"),
responseEvent("@dune2:example.com", "wings"),
];
const ends = [
endEvent("@me:example.com", 12),
];
const ends = [endEvent("@me:example.com", 12)];
const body = newMPollBody(votes, ends, null, false);
expect(endedVotesCount(body, "pizza")).toBe("3 votes");
expect(endedVotesCount(body, "poutine")).toBe("1 vote");
expect(endedVotesCount(body, "italian")).toBe("0 votes");
expect(endedVotesCount(body, "wings")).toBe("1 vote");
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe(
"Final result based on 5 votes");
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Final result based on 5 votes");
});
it("sends a vote event when I choose an option", () => {
@ -548,9 +496,7 @@ describe("MPollBody", () => {
clickRadio(body, "wings");
clickRadio(body, "wings");
clickRadio(body, "wings");
expect(mockClient.sendEvent).toHaveBeenCalledWith(
...expectedResponseEventCall("wings"),
);
expect(mockClient.sendEvent).toHaveBeenCalledWith(...expectedResponseEventCall("wings"));
});
it("sends no vote event when I click what I already chose", () => {
@ -576,13 +522,8 @@ describe("MPollBody", () => {
});
it("sends no events when I click in an ended poll", () => {
const ends = [
endEvent("@me:example.com", 25),
];
const votes = [
responseEvent("@uy:example.com", "wings", 15),
responseEvent("@uy:example.com", "poutine", 15),
];
const ends = [endEvent("@me:example.com", 25)];
const votes = [responseEvent("@uy:example.com", "wings", 15), responseEvent("@uy:example.com", "poutine", 15)];
const body = newMPollBody(votes, ends);
clickEndedOption(body, "wings");
clickEndedOption(body, "italian");
@ -622,9 +563,7 @@ describe("MPollBody", () => {
responseEvent("@fa:example.com", "poutine", 18),
responseEvent("@of:example.com", "poutine", 31), // Late
];
const ends = [
endEvent("@me:example.com", 25),
];
const ends = [endEvent("@me:example.com", 25)];
expect(runFindTopAnswer(votes, ends)).toEqual("Italian, Pizza and Poutine");
});
@ -646,7 +585,7 @@ describe("MPollBody", () => {
it("counts votes as normal if the poll is ended", () => {
const votes = [
responseEvent("@me:example.com", "pizza", 12),
responseEvent("@me:example.com", "wings", 20), // latest me
responseEvent("@me:example.com", "wings", 20), // latest me
responseEvent("@qbert:example.com", "pizza", 14),
responseEvent("@qbert:example.com", "poutine", 16), // latest qbert
responseEvent("@qbert:example.com", "wings", 15),
@ -657,9 +596,7 @@ describe("MPollBody", () => {
expect(endedVotesCount(body, "poutine")).toBe("1 vote");
expect(endedVotesCount(body, "italian")).toBe("0 votes");
expect(endedVotesCount(body, "wings")).toBe("1 vote");
expect(
body.find(".mx_MPollBody_totalVotes").text(),
).toBe("Final result based on 2 votes");
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Final result based on 2 votes");
});
it("counts a single vote as normal if the poll is ended", () => {
@ -670,9 +607,7 @@ describe("MPollBody", () => {
expect(endedVotesCount(body, "poutine")).toBe("1 vote");
expect(endedVotesCount(body, "italian")).toBe("0 votes");
expect(endedVotesCount(body, "wings")).toBe("0 votes");
expect(
body.find(".mx_MPollBody_totalVotes").text(),
).toBe("Final result based on 1 vote");
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Final result based on 1 vote");
});
it("shows ended vote counts of different numbers", () => {
@ -692,18 +627,16 @@ describe("MPollBody", () => {
expect(endedVotesCount(body, "poutine")).toBe("0 votes");
expect(endedVotesCount(body, "italian")).toBe("0 votes");
expect(endedVotesCount(body, "wings")).toBe("3 votes");
expect(
body.find(".mx_MPollBody_totalVotes").text(),
).toBe("Final result based on 5 votes");
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Final result based on 5 votes");
});
it("ignores votes that arrived after poll ended", () => {
const votes = [
responseEvent("@sd:example.com", "wings", 30), // Late
responseEvent("@sd:example.com", "wings", 30), // Late
responseEvent("@ff:example.com", "wings", 20),
responseEvent("@ut:example.com", "wings", 14),
responseEvent("@iu:example.com", "wings", 15),
responseEvent("@jf:example.com", "wings", 35), // Late
responseEvent("@jf:example.com", "wings", 35), // Late
responseEvent("@wf:example.com", "pizza", 15),
responseEvent("@ld:example.com", "pizza", 15),
];
@ -714,23 +647,21 @@ describe("MPollBody", () => {
expect(endedVotesCount(body, "poutine")).toBe("0 votes");
expect(endedVotesCount(body, "italian")).toBe("0 votes");
expect(endedVotesCount(body, "wings")).toBe("3 votes");
expect(
body.find(".mx_MPollBody_totalVotes").text(),
).toBe("Final result based on 5 votes");
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Final result based on 5 votes");
});
it("counts votes that arrived after an unauthorised poll end event", () => {
const votes = [
responseEvent("@sd:example.com", "wings", 30), // Late
responseEvent("@sd:example.com", "wings", 30), // Late
responseEvent("@ff:example.com", "wings", 20),
responseEvent("@ut:example.com", "wings", 14),
responseEvent("@iu:example.com", "wings", 15),
responseEvent("@jf:example.com", "wings", 35), // Late
responseEvent("@jf:example.com", "wings", 35), // Late
responseEvent("@wf:example.com", "pizza", 15),
responseEvent("@ld:example.com", "pizza", 15),
];
const ends = [
endEvent("@unauthorised:example.com", 5), // Should be ignored
endEvent("@unauthorised:example.com", 5), // Should be ignored
endEvent("@me:example.com", 25),
];
const body = newMPollBody(votes, ends);
@ -739,9 +670,7 @@ describe("MPollBody", () => {
expect(endedVotesCount(body, "poutine")).toBe("0 votes");
expect(endedVotesCount(body, "italian")).toBe("0 votes");
expect(endedVotesCount(body, "wings")).toBe("3 votes");
expect(
body.find(".mx_MPollBody_totalVotes").text(),
).toBe("Final result based on 5 votes");
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Final result based on 5 votes");
});
it("ignores votes that arrived after the first end poll event", () => {
@ -749,11 +678,11 @@ describe("MPollBody", () => {
// "Votes sent on or before the end event's timestamp are valid votes"
const votes = [
responseEvent("@sd:example.com", "wings", 30), // Late
responseEvent("@sd:example.com", "wings", 30), // Late
responseEvent("@ff:example.com", "wings", 20),
responseEvent("@ut:example.com", "wings", 14),
responseEvent("@iu:example.com", "wings", 25), // Just on time
responseEvent("@jf:example.com", "wings", 35), // Late
responseEvent("@iu:example.com", "wings", 25), // Just on time
responseEvent("@jf:example.com", "wings", 35), // Late
responseEvent("@wf:example.com", "pizza", 15),
responseEvent("@ld:example.com", "pizza", 15),
];
@ -768,9 +697,7 @@ describe("MPollBody", () => {
expect(endedVotesCount(body, "poutine")).toBe("0 votes");
expect(endedVotesCount(body, "italian")).toBe("0 votes");
expect(endedVotesCount(body, "wings")).toBe("3 votes");
expect(
body.find(".mx_MPollBody_totalVotes").text(),
).toBe("Final result based on 5 votes");
expect(body.find(".mx_MPollBody_totalVotes").text()).toBe("Final result based on 5 votes");
});
it("highlights the winning vote in an ended poll", () => {
@ -788,12 +715,8 @@ describe("MPollBody", () => {
expect(endedVoteChecked(body, "pizza")).toBe(false);
// Double-check by looking for the endedOptionWinner class
expect(
endedVoteDiv(body, "wings").hasClass("mx_MPollBody_endedOptionWinner"),
).toBe(true);
expect(
endedVoteDiv(body, "pizza").hasClass("mx_MPollBody_endedOptionWinner"),
).toBe(false);
expect(endedVoteDiv(body, "wings").hasClass("mx_MPollBody_endedOptionWinner")).toBe(true);
expect(endedVoteDiv(body, "pizza").hasClass("mx_MPollBody_endedOptionWinner")).toBe(false);
});
it("highlights multiple winning votes", () => {
@ -836,9 +759,9 @@ describe("MPollBody", () => {
it("says poll is not ended if asking for relations returns undefined", () => {
const pollEvent = new MatrixEvent({
"event_id": "$mypoll",
"room_id": "#myroom:example.com",
"content": newPollStart([]),
event_id: "$mypoll",
room_id: "#myroom:example.com",
content: newPollStart([]),
});
mockClient.getRoom.mockImplementation((_roomId) => {
return {
@ -849,45 +772,38 @@ describe("MPollBody", () => {
},
} as unknown as Room;
});
const getRelationsForEvent =
(eventId: string, relationType: string, eventType: string) => {
expect(eventId).toBe("$mypoll");
expect(relationType).toBe("m.reference");
expect(M_POLL_END.matches(eventType)).toBe(true);
return undefined;
};
expect(
isPollEnded(
pollEvent,
MatrixClientPeg.get(),
getRelationsForEvent,
),
).toBe(false);
const getRelationsForEvent = (eventId: string, relationType: string, eventType: string) => {
expect(eventId).toBe("$mypoll");
expect(relationType).toBe("m.reference");
expect(M_POLL_END.matches(eventType)).toBe(true);
return undefined;
};
expect(isPollEnded(pollEvent, MatrixClientPeg.get(), getRelationsForEvent)).toBe(false);
});
it("Displays edited content and new answer IDs if the poll has been edited", () => {
const pollEvent = new MatrixEvent({
"type": M_POLL_START.name,
"event_id": "$mypoll",
"room_id": "#myroom:example.com",
"content": newPollStart(
type: M_POLL_START.name,
event_id: "$mypoll",
room_id: "#myroom:example.com",
content: newPollStart(
[
{ "id": "o1", [M_TEXT.name]: "old answer 1" },
{ "id": "o2", [M_TEXT.name]: "old answer 2" },
{ id: "o1", [M_TEXT.name]: "old answer 1" },
{ id: "o2", [M_TEXT.name]: "old answer 2" },
],
"old question",
),
});
const replacingEvent = new MatrixEvent({
"type": M_POLL_START.name,
"event_id": "$mypollreplacement",
"room_id": "#myroom:example.com",
"content": {
type: M_POLL_START.name,
event_id: "$mypollreplacement",
room_id: "#myroom:example.com",
content: {
"m.new_content": newPollStart(
[
{ "id": "n1", [M_TEXT.name]: "new answer 1" },
{ "id": "n2", [M_TEXT.name]: "new answer 2" },
{ "id": "n3", [M_TEXT.name]: "new answer 3" },
{ id: "n1", [M_TEXT.name]: "new answer 1" },
{ id: "n2", [M_TEXT.name]: "new answer 2" },
{ id: "n3", [M_TEXT.name]: "new answer 3" },
],
"new question",
),
@ -895,18 +811,15 @@ describe("MPollBody", () => {
});
pollEvent.makeReplaced(replacingEvent);
const body = newMPollBodyFromEvent(pollEvent, []);
expect(body.find('h2').html())
.toEqual(
"<h2>new question"
+ "<span class=\"mx_MPollBody_edited\"> (edited)</span>"
+ "</h2>",
);
expect(body.find("h2").html()).toEqual(
"<h2>new question" + '<span class="mx_MPollBody_edited"> (edited)</span>' + "</h2>",
);
const inputs = body.find('input[type="radio"]');
expect(inputs).toHaveLength(3);
expect(inputs.at(0).prop("value")).toEqual("n1");
expect(inputs.at(1).prop("value")).toEqual("n2");
expect(inputs.at(2).prop("value")).toEqual("n3");
const options = body.find('.mx_MPollBody_optionText');
const options = body.find(".mx_MPollBody_optionText");
expect(options).toHaveLength(3);
expect(options.at(0).text()).toEqual("new answer 1");
expect(options.at(1).text()).toEqual("new answer 2");
@ -1027,10 +940,7 @@ function newEndRelations(relationEvents: Array<MatrixEvent>): Relations {
return newRelations(relationEvents, M_POLL_END.name);
}
function newRelations(
relationEvents: Array<MatrixEvent>,
eventType: string,
): Relations {
function newRelations(relationEvents: Array<MatrixEvent>, eventType: string): Relations {
const voteRelations = new Relations("m.reference", eventType, null);
for (const ev of relationEvents) {
voteRelations.addEvent(ev);
@ -1045,10 +955,10 @@ function newMPollBody(
disclosed = true,
): ReactWrapper {
const mxEvent = new MatrixEvent({
"type": M_POLL_START.name,
"event_id": "$mypoll",
"room_id": "#myroom:example.com",
"content": newPollStart(answers, null, disclosed),
type: M_POLL_START.name,
event_id: "$mypoll",
room_id: "#myroom:example.com",
content: newPollStart(answers, null, disclosed),
});
return newMPollBodyFromEvent(mxEvent, relationEvents, endEvents);
}
@ -1060,10 +970,10 @@ function newMPollBodyFromEvent(
): ReactWrapper {
const voteRelations = newVoteRelations(relationEvents);
const endRelations = newEndRelations(endEvents);
return mount(<MPollBody
mxEvent={mxEvent}
getRelationsForEvent={
(eventId: string, relationType: string, eventType: string) => {
return mount(
<MPollBody
mxEvent={mxEvent}
getRelationsForEvent={(eventId: string, relationType: string, eventType: string) => {
expect(eventId).toBe("$mypoll");
expect(relationType).toBe("m.reference");
if (M_POLL_RESPONSE.matches(eventType)) {
@ -1073,22 +983,22 @@ function newMPollBodyFromEvent(
} else {
fail("Unexpected eventType: " + eventType);
}
}
}
// We don't use any of these props, but they're required.
highlightLink="unused"
highlights={[]}
mediaEventHelper={null}
onHeightChanged={() => {}}
onMessageAllowed={() => {}}
permalinkCreator={null}
/>, {
wrappingComponent: MatrixClientContext.Provider,
wrappingComponentProps: {
value: mockClient,
}}
// We don't use any of these props, but they're required.
highlightLink="unused"
highlights={[]}
mediaEventHelper={null}
onHeightChanged={() => {}}
onMessageAllowed={() => {}}
permalinkCreator={null}
/>,
{
wrappingComponent: MatrixClientContext.Provider,
wrappingComponentProps: {
value: mockClient,
},
},
});
);
}
function clickRadio(wrapper: ReactWrapper, value: string) {
@ -1104,21 +1014,15 @@ function clickEndedOption(wrapper: ReactWrapper, value: string) {
}
function voteButton(wrapper: ReactWrapper, value: string): ReactWrapper {
return wrapper.find(
`div.mx_MPollBody_option`,
).findWhere(w => w.key() === value);
return wrapper.find(`div.mx_MPollBody_option`).findWhere((w) => w.key() === value);
}
function votesCount(wrapper: ReactWrapper, value: string): string {
return wrapper.find(
`StyledRadioButton[value="${value}"] .mx_MPollBody_optionVoteCount`,
).text();
return wrapper.find(`StyledRadioButton[value="${value}"] .mx_MPollBody_optionVoteCount`).text();
}
function endedVoteChecked(wrapper: ReactWrapper, value: string): boolean {
return endedVoteDiv(wrapper, value)
.closest(".mx_MPollBody_option")
.hasClass("mx_MPollBody_option_checked");
return endedVoteDiv(wrapper, value).closest(".mx_MPollBody_option").hasClass("mx_MPollBody_option_checked");
}
function endedVoteDiv(wrapper: ReactWrapper, value: string): ReactWrapper {
@ -1126,22 +1030,16 @@ function endedVoteDiv(wrapper: ReactWrapper, value: string): ReactWrapper {
}
function endedVotesCount(wrapper: ReactWrapper, value: string): string {
return wrapper.find(
`div[data-value="${value}"] .mx_MPollBody_optionVoteCount`,
).text();
return wrapper.find(`div[data-value="${value}"] .mx_MPollBody_optionVoteCount`).text();
}
function newPollStart(
answers?: POLL_ANSWER[],
question?: string,
disclosed = true,
): M_POLL_START_EVENT_CONTENT {
function newPollStart(answers?: POLL_ANSWER[], question?: string, disclosed = true): M_POLL_START_EVENT_CONTENT {
if (!answers) {
answers = [
{ "id": "pizza", [M_TEXT.name]: "Pizza" },
{ "id": "poutine", [M_TEXT.name]: "Poutine" },
{ "id": "italian", [M_TEXT.name]: "Italian" },
{ "id": "wings", [M_TEXT.name]: "Wings" },
{ id: "pizza", [M_TEXT.name]: "Pizza" },
{ id: "poutine", [M_TEXT.name]: "Poutine" },
{ id: "italian", [M_TEXT.name]: "Italian" },
{ id: "wings", [M_TEXT.name]: "Wings" },
];
}
@ -1149,43 +1047,35 @@ function newPollStart(
question = "What should we order for the party?";
}
const answersFallback = answers
.map((a, i) => `${i + 1}. ${a[M_TEXT.name]}`)
.join("\n");
const answersFallback = answers.map((a, i) => `${i + 1}. ${a[M_TEXT.name]}`).join("\n");
const fallback = `${question}\n${answersFallback}`;
return {
[M_POLL_START.name]: {
"question": {
question: {
[M_TEXT.name]: question,
},
"kind": (
disclosed
? M_POLL_KIND_DISCLOSED.name
: M_POLL_KIND_UNDISCLOSED.name
),
"answers": answers,
kind: disclosed ? M_POLL_KIND_DISCLOSED.name : M_POLL_KIND_UNDISCLOSED.name,
answers: answers,
},
[M_TEXT.name]: fallback,
};
}
function badResponseEvent(): MatrixEvent {
return new MatrixEvent(
{
"event_id": nextId(),
"type": M_POLL_RESPONSE.name,
"sender": "@malicious:example.com",
"content": {
"m.relates_to": {
"rel_type": "m.reference",
"event_id": "$mypoll",
},
// Does not actually contain a response
return new MatrixEvent({
event_id: nextId(),
type: M_POLL_RESPONSE.name,
sender: "@malicious:example.com",
content: {
"m.relates_to": {
rel_type: "m.reference",
event_id: "$mypoll",
},
// Does not actually contain a response
},
);
});
}
function responseEvent(
@ -1194,116 +1084,103 @@ function responseEvent(
ts = 0,
): MatrixEvent {
const ans = typeof answers === "string" ? [answers] : answers;
return new MatrixEvent(
{
"event_id": nextId(),
"room_id": "#myroom:example.com",
"origin_server_ts": ts,
"type": M_POLL_RESPONSE.name,
"sender": sender,
"content": {
"m.relates_to": {
"rel_type": "m.reference",
"event_id": "$mypoll",
},
[M_POLL_RESPONSE.name]: {
"answers": ans,
},
return new MatrixEvent({
event_id: nextId(),
room_id: "#myroom:example.com",
origin_server_ts: ts,
type: M_POLL_RESPONSE.name,
sender: sender,
content: {
"m.relates_to": {
rel_type: "m.reference",
event_id: "$mypoll",
},
[M_POLL_RESPONSE.name]: {
answers: ans,
},
},
);
});
}
function expectedResponseEvent(answer: string) {
return {
"content": {
content: {
[M_POLL_RESPONSE.name]: {
"answers": [answer],
answers: [answer],
},
"m.relates_to": {
"event_id": "$mypoll",
"rel_type": "m.reference",
event_id: "$mypoll",
rel_type: "m.reference",
},
},
"roomId": "#myroom:example.com",
"eventType": M_POLL_RESPONSE.name,
"txnId": undefined,
"callback": undefined,
roomId: "#myroom:example.com",
eventType: M_POLL_RESPONSE.name,
txnId: undefined,
callback: undefined,
};
}
function expectedResponseEventCall(answer: string) {
const {
content, roomId, eventType,
} = expectedResponseEvent(answer);
return [
roomId, eventType, content,
];
const { content, roomId, eventType } = expectedResponseEvent(answer);
return [roomId, eventType, content];
}
function endEvent(
sender = "@me:example.com",
ts = 0,
): MatrixEvent {
return new MatrixEvent(
{
"event_id": nextId(),
"room_id": "#myroom:example.com",
"origin_server_ts": ts,
"type": M_POLL_END.name,
"sender": sender,
"content": {
"m.relates_to": {
"rel_type": "m.reference",
"event_id": "$mypoll",
},
[M_POLL_END.name]: {},
[M_TEXT.name]: "The poll has ended. Something.",
function endEvent(sender = "@me:example.com", ts = 0): MatrixEvent {
return new MatrixEvent({
event_id: nextId(),
room_id: "#myroom:example.com",
origin_server_ts: ts,
type: M_POLL_END.name,
sender: sender,
content: {
"m.relates_to": {
rel_type: "m.reference",
event_id: "$mypoll",
},
[M_POLL_END.name]: {},
[M_TEXT.name]: "The poll has ended. Something.",
},
);
});
}
function runIsPollEnded(ends: MatrixEvent[]) {
const pollEvent = new MatrixEvent({
"event_id": "$mypoll",
"room_id": "#myroom:example.com",
"type": M_POLL_START.name,
"content": newPollStart(),
event_id: "$mypoll",
room_id: "#myroom:example.com",
type: M_POLL_START.name,
content: newPollStart(),
});
setRedactionAllowedForMeOnly(mockClient);
const getRelationsForEvent =
(eventId: string, relationType: string, eventType: string) => {
expect(eventId).toBe("$mypoll");
expect(relationType).toBe("m.reference");
expect(M_POLL_END.matches(eventType)).toBe(true);
return newEndRelations(ends);
};
const getRelationsForEvent = (eventId: string, relationType: string, eventType: string) => {
expect(eventId).toBe("$mypoll");
expect(relationType).toBe("m.reference");
expect(M_POLL_END.matches(eventType)).toBe(true);
return newEndRelations(ends);
};
return isPollEnded(pollEvent, mockClient, getRelationsForEvent);
}
function runFindTopAnswer(votes: MatrixEvent[], ends: MatrixEvent[]) {
const pollEvent = new MatrixEvent({
"event_id": "$mypoll",
"room_id": "#myroom:example.com",
"type": M_POLL_START.name,
"content": newPollStart(),
event_id: "$mypoll",
room_id: "#myroom:example.com",
type: M_POLL_START.name,
content: newPollStart(),
});
const getRelationsForEvent =
(eventId: string, relationType: string, eventType: string) => {
expect(eventId).toBe("$mypoll");
expect(relationType).toBe("m.reference");
if (M_POLL_RESPONSE.matches(eventType)) {
return newVoteRelations(votes);
} else if (M_POLL_END.matches(eventType)) {
return newEndRelations(ends);
} else {
fail(`eventType should be end or vote but was ${eventType}`);
}
};
const getRelationsForEvent = (eventId: string, relationType: string, eventType: string) => {
expect(eventId).toBe("$mypoll");
expect(relationType).toBe("m.reference");
if (M_POLL_RESPONSE.matches(eventType)) {
return newVoteRelations(votes);
} else if (M_POLL_END.matches(eventType)) {
return newEndRelations(ends);
} else {
fail(`eventType should be end or vote but was ${eventType}`);
}
};
return findTopAnswer(pollEvent, MatrixClientPeg.get(), getRelationsForEvent);
}

View file

@ -14,25 +14,26 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { MatrixEvent } from 'matrix-js-sdk/src/matrix';
import { render, RenderResult } from '@testing-library/react';
import React from "react";
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import { render, RenderResult } from "@testing-library/react";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
import { MediaEventHelper } from "../../../../src/utils/MediaEventHelper";
import { getMockClientWithEventEmitter } from '../../../test-utils';
import MVideoBody from '../../../../src/components/views/messages/MVideoBody';
import { getMockClientWithEventEmitter } from "../../../test-utils";
import MVideoBody from "../../../../src/components/views/messages/MVideoBody";
jest.mock(
"../../../../src/customisations/Media",
() => {
return { mediaFromContent: () => { return { isEncrypted: false }; } };
},
);
jest.mock("../../../../src/customisations/Media", () => {
return {
mediaFromContent: () => {
return { isEncrypted: false };
},
};
});
describe("MVideoBody", () => {
it('does not crash when given a portrait image', () => {
it("does not crash when given a portrait image", () => {
// Check for an unreliable crash caused by a fractional-sized
// image dimension being used for a CanvasImageData.
const { asFragment } = makeMVideoBody(720, 1280);
@ -68,7 +69,7 @@ function makeMVideoBody(w: number, h: number): RenderResult {
const defaultProps = {
mxEvent: event,
highlights: [],
highlightLink: '',
highlightLink: "",
onHeightChanged: jest.fn(),
onMessageAllowed: jest.fn(),
permalinkCreator: {} as RoomPermalinkCreator,

View file

@ -14,57 +14,50 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import { act } from 'react-test-renderer';
import {
EventType,
EventStatus,
MatrixEvent,
MatrixEventEvent,
MsgType,
Room,
} from 'matrix-js-sdk/src/matrix';
import { FeatureSupport, Thread } from 'matrix-js-sdk/src/models/thread';
import React from "react";
import { render, fireEvent } from "@testing-library/react";
import { act } from "react-test-renderer";
import { EventType, EventStatus, MatrixEvent, MatrixEventEvent, MsgType, Room } from "matrix-js-sdk/src/matrix";
import { FeatureSupport, Thread } from "matrix-js-sdk/src/models/thread";
import MessageActionBar from '../../../../src/components/views/messages/MessageActionBar';
import MessageActionBar from "../../../../src/components/views/messages/MessageActionBar";
import {
getMockClientWithEventEmitter,
mockClientMethodsUser,
mockClientMethodsEvents,
makeBeaconInfoEvent,
} from '../../../test-utils';
import { RoomPermalinkCreator } from '../../../../src/utils/permalinks/Permalinks';
import RoomContext, { TimelineRenderingType } from '../../../../src/contexts/RoomContext';
import { IRoomState } from '../../../../src/components/structures/RoomView';
import dispatcher from '../../../../src/dispatcher/dispatcher';
import SettingsStore from '../../../../src/settings/SettingsStore';
import { Action } from '../../../../src/dispatcher/actions';
import { UserTab } from '../../../../src/components/views/dialogs/UserTab';
} from "../../../test-utils";
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
import RoomContext, { TimelineRenderingType } from "../../../../src/contexts/RoomContext";
import { IRoomState } from "../../../../src/components/structures/RoomView";
import dispatcher from "../../../../src/dispatcher/dispatcher";
import SettingsStore from "../../../../src/settings/SettingsStore";
import { Action } from "../../../../src/dispatcher/actions";
import { UserTab } from "../../../../src/components/views/dialogs/UserTab";
jest.mock('../../../../src/dispatcher/dispatcher');
jest.mock("../../../../src/dispatcher/dispatcher");
describe('<MessageActionBar />', () => {
const userId = '@alice:server.org';
const roomId = '!room:server.org';
describe("<MessageActionBar />", () => {
const userId = "@alice:server.org";
const roomId = "!room:server.org";
const alicesMessageEvent = new MatrixEvent({
type: EventType.RoomMessage,
sender: userId,
room_id: roomId,
content: {
msgtype: MsgType.Text,
body: 'Hello',
body: "Hello",
},
event_id: "$alices_message",
});
const bobsMessageEvent = new MatrixEvent({
type: EventType.RoomMessage,
sender: '@bob:server.org',
sender: "@bob:server.org",
room_id: roomId,
content: {
msgtype: MsgType.Text,
body: 'I am bob',
body: "I am bob",
},
event_id: "$bobs_message",
});
@ -84,7 +77,7 @@ describe('<MessageActionBar />', () => {
const localStorageMock = (() => {
let store = {};
return {
getItem: jest.fn().mockImplementation(key => store[key] ?? null),
getItem: jest.fn().mockImplementation((key) => store[key] ?? null),
setItem: jest.fn().mockImplementation((key, value) => {
store[key] = value;
}),
@ -94,13 +87,13 @@ describe('<MessageActionBar />', () => {
removeItem: jest.fn().mockImplementation((key) => delete store[key]),
};
})();
Object.defineProperty(window, 'localStorage', {
Object.defineProperty(window, "localStorage", {
value: localStorageMock,
writable: true,
});
const room = new Room(roomId, client, userId);
jest.spyOn(room, 'getPendingEvents').mockReturnValue([]);
jest.spyOn(room, "getPendingEvents").mockReturnValue([]);
client.getRoom.mockReturnValue(room);
@ -121,22 +114,23 @@ describe('<MessageActionBar />', () => {
render(
<RoomContext.Provider value={{ ...defaultRoomContext, ...roomContext }}>
<MessageActionBar {...defaultProps} {...props} />
</RoomContext.Provider>);
</RoomContext.Provider>,
);
beforeEach(() => {
jest.clearAllMocks();
alicesMessageEvent.setStatus(EventStatus.SENT);
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false);
jest.spyOn(SettingsStore, 'setValue').mockResolvedValue(undefined);
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
jest.spyOn(SettingsStore, "setValue").mockResolvedValue(undefined);
});
afterAll(() => {
jest.spyOn(SettingsStore, 'getValue').mockRestore();
jest.spyOn(SettingsStore, 'setValue').mockRestore();
jest.spyOn(SettingsStore, "getValue").mockRestore();
jest.spyOn(SettingsStore, "setValue").mockRestore();
});
it('kills event listeners on unmount', () => {
const offSpy = jest.spyOn(alicesMessageEvent, 'off').mockClear();
it("kills event listeners on unmount", () => {
const offSpy = jest.spyOn(alicesMessageEvent, "off").mockClear();
const wrapper = getComponent({ mxEvent: alicesMessageEvent });
act(() => {
@ -150,24 +144,24 @@ describe('<MessageActionBar />', () => {
expect(client.decryptEventIfNeeded).toHaveBeenCalled();
});
describe('decryption', () => {
it('decrypts event if needed', () => {
describe("decryption", () => {
it("decrypts event if needed", () => {
getComponent({ mxEvent: alicesMessageEvent });
expect(client.decryptEventIfNeeded).toHaveBeenCalled();
});
it('updates component on decrypted event', () => {
it("updates component on decrypted event", () => {
const decryptingEvent = new MatrixEvent({
type: EventType.RoomMessageEncrypted,
sender: userId,
room_id: roomId,
content: {},
});
jest.spyOn(decryptingEvent, 'isBeingDecrypted').mockReturnValue(true);
jest.spyOn(decryptingEvent, "isBeingDecrypted").mockReturnValue(true);
const { queryByLabelText } = getComponent({ mxEvent: decryptingEvent });
// still encrypted event is not actionable => no reply button
expect(queryByLabelText('Reply')).toBeFalsy();
expect(queryByLabelText("Reply")).toBeFalsy();
act(() => {
// ''decrypt'' the event
@ -177,46 +171,46 @@ describe('<MessageActionBar />', () => {
});
// new available actions after decryption
expect(queryByLabelText('Reply')).toBeTruthy();
expect(queryByLabelText("Reply")).toBeTruthy();
});
});
describe('status', () => {
it('updates component when event status changes', () => {
describe("status", () => {
it("updates component when event status changes", () => {
alicesMessageEvent.setStatus(EventStatus.QUEUED);
const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
// pending event status, cancel action available
expect(queryByLabelText('Delete')).toBeTruthy();
expect(queryByLabelText("Delete")).toBeTruthy();
act(() => {
alicesMessageEvent.setStatus(EventStatus.SENT);
});
// event is sent, no longer cancelable
expect(queryByLabelText('Delete')).toBeFalsy();
expect(queryByLabelText("Delete")).toBeFalsy();
});
});
describe('redaction', () => {
describe("redaction", () => {
// this doesn't do what it's supposed to
// because beforeRedaction event is fired... before redaction
// event is unchanged at point when this component updates
// TODO file bug
xit('updates component on before redaction event', () => {
xit("updates component on before redaction event", () => {
const event = new MatrixEvent({
type: EventType.RoomMessage,
sender: userId,
room_id: roomId,
content: {
msgtype: MsgType.Text,
body: 'Hello',
body: "Hello",
},
});
const { queryByLabelText } = getComponent({ mxEvent: event });
// no pending redaction => no delete button
expect(queryByLabelText('Delete')).toBeFalsy();
expect(queryByLabelText("Delete")).toBeFalsy();
act(() => {
const redactionEvent = new MatrixEvent({
@ -229,110 +223,110 @@ describe('<MessageActionBar />', () => {
});
// updated with local redaction event, delete now available
expect(queryByLabelText('Delete')).toBeTruthy();
expect(queryByLabelText("Delete")).toBeTruthy();
});
});
describe('options button', () => {
it('renders options menu', () => {
describe("options button", () => {
it("renders options menu", () => {
const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
expect(queryByLabelText('Options')).toBeTruthy();
expect(queryByLabelText("Options")).toBeTruthy();
});
it('opens message context menu on click', () => {
it("opens message context menu on click", () => {
const { getByTestId, queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
act(() => {
fireEvent.click(queryByLabelText('Options'));
fireEvent.click(queryByLabelText("Options"));
});
expect(getByTestId('mx_MessageContextMenu')).toBeTruthy();
expect(getByTestId("mx_MessageContextMenu")).toBeTruthy();
});
});
describe('reply button', () => {
it('renders reply button on own actionable event', () => {
describe("reply button", () => {
it("renders reply button on own actionable event", () => {
const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
expect(queryByLabelText('Reply')).toBeTruthy();
expect(queryByLabelText("Reply")).toBeTruthy();
});
it('renders reply button on others actionable event', () => {
it("renders reply button on others actionable event", () => {
const { queryByLabelText } = getComponent({ mxEvent: bobsMessageEvent }, { canSendMessages: true });
expect(queryByLabelText('Reply')).toBeTruthy();
expect(queryByLabelText("Reply")).toBeTruthy();
});
it('does not render reply button on non-actionable event', () => {
it("does not render reply button on non-actionable event", () => {
// redacted event is not actionable
const { queryByLabelText } = getComponent({ mxEvent: redactedEvent });
expect(queryByLabelText('Reply')).toBeFalsy();
expect(queryByLabelText("Reply")).toBeFalsy();
});
it('does not render reply button when user cannot send messaged', () => {
it("does not render reply button when user cannot send messaged", () => {
// redacted event is not actionable
const { queryByLabelText } = getComponent({ mxEvent: redactedEvent }, { canSendMessages: false });
expect(queryByLabelText('Reply')).toBeFalsy();
expect(queryByLabelText("Reply")).toBeFalsy();
});
it('dispatches reply event on click', () => {
it("dispatches reply event on click", () => {
const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
act(() => {
fireEvent.click(queryByLabelText('Reply'));
fireEvent.click(queryByLabelText("Reply"));
});
expect(dispatcher.dispatch).toHaveBeenCalledWith({
action: 'reply_to_event',
action: "reply_to_event",
event: alicesMessageEvent,
context: TimelineRenderingType.Room,
});
});
});
describe('react button', () => {
it('renders react button on own actionable event', () => {
describe("react button", () => {
it("renders react button on own actionable event", () => {
const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
expect(queryByLabelText('React')).toBeTruthy();
expect(queryByLabelText("React")).toBeTruthy();
});
it('renders react button on others actionable event', () => {
it("renders react button on others actionable event", () => {
const { queryByLabelText } = getComponent({ mxEvent: bobsMessageEvent });
expect(queryByLabelText('React')).toBeTruthy();
expect(queryByLabelText("React")).toBeTruthy();
});
it('does not render react button on non-actionable event', () => {
it("does not render react button on non-actionable event", () => {
// redacted event is not actionable
const { queryByLabelText } = getComponent({ mxEvent: redactedEvent });
expect(queryByLabelText('React')).toBeFalsy();
expect(queryByLabelText("React")).toBeFalsy();
});
it('does not render react button when user cannot react', () => {
it("does not render react button when user cannot react", () => {
// redacted event is not actionable
const { queryByLabelText } = getComponent({ mxEvent: redactedEvent }, { canReact: false });
expect(queryByLabelText('React')).toBeFalsy();
expect(queryByLabelText("React")).toBeFalsy();
});
it('opens reaction picker on click', () => {
it("opens reaction picker on click", () => {
const { queryByLabelText, getByTestId } = getComponent({ mxEvent: alicesMessageEvent });
act(() => {
fireEvent.click(queryByLabelText('React'));
fireEvent.click(queryByLabelText("React"));
});
expect(getByTestId('mx_EmojiPicker')).toBeTruthy();
expect(getByTestId("mx_EmojiPicker")).toBeTruthy();
});
});
describe('cancel button', () => {
it('renders cancel button for an event with a cancelable status', () => {
describe("cancel button", () => {
it("renders cancel button for an event with a cancelable status", () => {
alicesMessageEvent.setStatus(EventStatus.QUEUED);
const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
expect(queryByLabelText('Delete')).toBeTruthy();
expect(queryByLabelText("Delete")).toBeTruthy();
});
it('renders cancel button for an event with a pending edit', () => {
it("renders cancel button for an event with a pending edit", () => {
const event = new MatrixEvent({
type: EventType.RoomMessage,
sender: userId,
room_id: roomId,
content: {
msgtype: MsgType.Text,
body: 'Hello',
body: "Hello",
},
});
event.setStatus(EventStatus.SENT);
@ -342,23 +336,23 @@ describe('<MessageActionBar />', () => {
room_id: roomId,
content: {
msgtype: MsgType.Text,
body: 'replacing event body',
body: "replacing event body",
},
});
replacingEvent.setStatus(EventStatus.QUEUED);
event.makeReplaced(replacingEvent);
const { queryByLabelText } = getComponent({ mxEvent: event });
expect(queryByLabelText('Delete')).toBeTruthy();
expect(queryByLabelText("Delete")).toBeTruthy();
});
it('renders cancel button for an event with a pending redaction', () => {
it("renders cancel button for an event with a pending redaction", () => {
const event = new MatrixEvent({
type: EventType.RoomMessage,
sender: userId,
room_id: roomId,
content: {
msgtype: MsgType.Text,
body: 'Hello',
body: "Hello",
},
});
event.setStatus(EventStatus.SENT);
@ -372,45 +366,45 @@ describe('<MessageActionBar />', () => {
event.markLocallyRedacted(redactionEvent);
const { queryByLabelText } = getComponent({ mxEvent: event });
expect(queryByLabelText('Delete')).toBeTruthy();
expect(queryByLabelText("Delete")).toBeTruthy();
});
it('renders cancel and retry button for an event with NOT_SENT status', () => {
it("renders cancel and retry button for an event with NOT_SENT status", () => {
alicesMessageEvent.setStatus(EventStatus.NOT_SENT);
const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
expect(queryByLabelText('Retry')).toBeTruthy();
expect(queryByLabelText('Delete')).toBeTruthy();
expect(queryByLabelText("Retry")).toBeTruthy();
expect(queryByLabelText("Delete")).toBeTruthy();
});
it.todo('unsends event on cancel click');
it.todo('retrys event on retry click');
it.todo("unsends event on cancel click");
it.todo("retrys event on retry click");
});
describe('thread button', () => {
describe("thread button", () => {
beforeEach(() => {
Thread.setServerSideSupport(FeatureSupport.Stable);
});
describe('when threads feature is not enabled', () => {
it('does not render thread button when threads does not have server support', () => {
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false);
describe("when threads feature is not enabled", () => {
it("does not render thread button when threads does not have server support", () => {
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
Thread.setServerSideSupport(FeatureSupport.None);
const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
expect(queryByLabelText('Reply in thread')).toBeFalsy();
expect(queryByLabelText("Reply in thread")).toBeFalsy();
});
it('renders thread button when threads has server support', () => {
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false);
it("renders thread button when threads has server support", () => {
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
expect(queryByLabelText('Reply in thread')).toBeTruthy();
expect(queryByLabelText("Reply in thread")).toBeTruthy();
});
it('opens user settings on click', () => {
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false);
it("opens user settings on click", () => {
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
const { getByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
act(() => {
fireEvent.click(getByLabelText('Reply in thread'));
fireEvent.click(getByLabelText("Reply in thread"));
});
expect(dispatcher.dispatch).toHaveBeenCalledWith({
@ -420,27 +414,27 @@ describe('<MessageActionBar />', () => {
});
});
describe('when threads feature is enabled', () => {
describe("when threads feature is enabled", () => {
beforeEach(() => {
jest.spyOn(SettingsStore, 'getValue').mockImplementation(setting => setting === 'feature_thread');
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => setting === "feature_thread");
});
it('renders thread button on own actionable event', () => {
it("renders thread button on own actionable event", () => {
const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
expect(queryByLabelText('Reply in thread')).toBeTruthy();
expect(queryByLabelText("Reply in thread")).toBeTruthy();
});
it('does not render thread button for a beacon_info event', () => {
it("does not render thread button for a beacon_info event", () => {
const beaconInfoEvent = makeBeaconInfoEvent(userId, roomId);
const { queryByLabelText } = getComponent({ mxEvent: beaconInfoEvent });
expect(queryByLabelText('Reply in thread')).toBeFalsy();
expect(queryByLabelText("Reply in thread")).toBeFalsy();
});
it('opens thread on click', () => {
it("opens thread on click", () => {
const { getByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
act(() => {
fireEvent.click(getByLabelText('Reply in thread'));
fireEvent.click(getByLabelText("Reply in thread"));
});
expect(dispatcher.dispatch).toHaveBeenCalledWith({
@ -450,26 +444,26 @@ describe('<MessageActionBar />', () => {
});
});
it('opens parent thread for a thread reply message', () => {
it("opens parent thread for a thread reply message", () => {
const threadReplyEvent = new MatrixEvent({
type: EventType.RoomMessage,
sender: userId,
room_id: roomId,
content: {
msgtype: MsgType.Text,
body: 'this is a thread reply',
body: "this is a thread reply",
},
});
// mock the thread stuff
jest.spyOn(threadReplyEvent, 'isThreadRoot', 'get').mockReturnValue(false);
jest.spyOn(threadReplyEvent, "isThreadRoot", "get").mockReturnValue(false);
// set alicesMessageEvent as the root event
jest.spyOn(threadReplyEvent, 'getThread').mockReturnValue(
{ rootEvent: alicesMessageEvent } as unknown as Thread,
);
jest.spyOn(threadReplyEvent, "getThread").mockReturnValue({
rootEvent: alicesMessageEvent,
} as unknown as Thread);
const { getByLabelText } = getComponent({ mxEvent: threadReplyEvent });
act(() => {
fireEvent.click(getByLabelText('Reply in thread'));
fireEvent.click(getByLabelText("Reply in thread"));
});
expect(dispatcher.dispatch).toHaveBeenCalledWith({
@ -484,113 +478,115 @@ describe('<MessageActionBar />', () => {
});
});
describe('favourite button', () => {
describe("favourite button", () => {
//for multiple event usecase
const favButton = (evt: MatrixEvent) => {
return getComponent({ mxEvent: evt }).getByTestId(evt.getId());
};
describe('when favourite_messages feature is enabled', () => {
describe("when favourite_messages feature is enabled", () => {
beforeEach(() => {
jest.spyOn(SettingsStore, 'getValue')
.mockImplementation(setting => setting === 'feature_favourite_messages');
jest.spyOn(SettingsStore, "getValue").mockImplementation(
(setting) => setting === "feature_favourite_messages",
);
localStorageMock.clear();
});
it('renders favourite button on own actionable event', () => {
it("renders favourite button on own actionable event", () => {
const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
expect(queryByLabelText('Favourite')).toBeTruthy();
expect(queryByLabelText("Favourite")).toBeTruthy();
});
it('renders favourite button on other actionable events', () => {
it("renders favourite button on other actionable events", () => {
const { queryByLabelText } = getComponent({ mxEvent: bobsMessageEvent });
expect(queryByLabelText('Favourite')).toBeTruthy();
expect(queryByLabelText("Favourite")).toBeTruthy();
});
it('does not render Favourite button on non-actionable event', () => {
it("does not render Favourite button on non-actionable event", () => {
//redacted event is not actionable
const { queryByLabelText } = getComponent({ mxEvent: redactedEvent });
expect(queryByLabelText('Favourite')).toBeFalsy();
expect(queryByLabelText("Favourite")).toBeFalsy();
});
it('remembers favourited state of multiple events, and handles the localStorage of the events accordingly',
() => {
const alicesAction = favButton(alicesMessageEvent);
const bobsAction = favButton(bobsMessageEvent);
it("remembers favourited state of multiple events, and handles the localStorage of the events accordingly", () => {
const alicesAction = favButton(alicesMessageEvent);
const bobsAction = favButton(bobsMessageEvent);
//default state before being clicked
expect(alicesAction.classList).not.toContain('mx_MessageActionBar_favouriteButton_fillstar');
expect(bobsAction.classList).not.toContain('mx_MessageActionBar_favouriteButton_fillstar');
expect(localStorageMock.getItem('io_element_favouriteMessages')).toBeNull();
//default state before being clicked
expect(alicesAction.classList).not.toContain("mx_MessageActionBar_favouriteButton_fillstar");
expect(bobsAction.classList).not.toContain("mx_MessageActionBar_favouriteButton_fillstar");
expect(localStorageMock.getItem("io_element_favouriteMessages")).toBeNull();
//if only alice's event is fired
act(() => {
fireEvent.click(alicesAction);
});
expect(alicesAction.classList).toContain('mx_MessageActionBar_favouriteButton_fillstar');
expect(bobsAction.classList).not.toContain('mx_MessageActionBar_favouriteButton_fillstar');
expect(localStorageMock.setItem)
.toHaveBeenCalledWith('io_element_favouriteMessages', '["$alices_message"]');
//when bob's event is fired,both should be styled and stored in localStorage
act(() => {
fireEvent.click(bobsAction);
});
expect(alicesAction.classList).toContain('mx_MessageActionBar_favouriteButton_fillstar');
expect(bobsAction.classList).toContain('mx_MessageActionBar_favouriteButton_fillstar');
expect(localStorageMock.setItem)
.toHaveBeenCalledWith('io_element_favouriteMessages', '["$alices_message","$bobs_message"]');
//finally, at this point the localStorage should contain the two eventids
expect(localStorageMock.getItem('io_element_favouriteMessages'))
.toEqual('["$alices_message","$bobs_message"]');
//if decided to unfavourite bob's event by clicking again
act(() => {
fireEvent.click(bobsAction);
});
expect(bobsAction.classList).not.toContain('mx_MessageActionBar_favouriteButton_fillstar');
expect(alicesAction.classList).toContain('mx_MessageActionBar_favouriteButton_fillstar');
expect(localStorageMock.getItem('io_element_favouriteMessages')).toEqual('["$alices_message"]');
//if only alice's event is fired
act(() => {
fireEvent.click(alicesAction);
});
expect(alicesAction.classList).toContain("mx_MessageActionBar_favouriteButton_fillstar");
expect(bobsAction.classList).not.toContain("mx_MessageActionBar_favouriteButton_fillstar");
expect(localStorageMock.setItem).toHaveBeenCalledWith(
"io_element_favouriteMessages",
'["$alices_message"]',
);
//when bob's event is fired,both should be styled and stored in localStorage
act(() => {
fireEvent.click(bobsAction);
});
expect(alicesAction.classList).toContain("mx_MessageActionBar_favouriteButton_fillstar");
expect(bobsAction.classList).toContain("mx_MessageActionBar_favouriteButton_fillstar");
expect(localStorageMock.setItem).toHaveBeenCalledWith(
"io_element_favouriteMessages",
'["$alices_message","$bobs_message"]',
);
//finally, at this point the localStorage should contain the two eventids
expect(localStorageMock.getItem("io_element_favouriteMessages")).toEqual(
'["$alices_message","$bobs_message"]',
);
//if decided to unfavourite bob's event by clicking again
act(() => {
fireEvent.click(bobsAction);
});
expect(bobsAction.classList).not.toContain("mx_MessageActionBar_favouriteButton_fillstar");
expect(alicesAction.classList).toContain("mx_MessageActionBar_favouriteButton_fillstar");
expect(localStorageMock.getItem("io_element_favouriteMessages")).toEqual('["$alices_message"]');
});
});
describe('when favourite_messages feature is disabled', () => {
it('does not render', () => {
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false);
describe("when favourite_messages feature is disabled", () => {
it("does not render", () => {
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
const { queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
expect(queryByLabelText('Favourite')).toBeFalsy();
expect(queryByLabelText("Favourite")).toBeFalsy();
});
});
});
it.each([
["React"],
["Reply"],
["Reply in thread"],
["Favourite"],
["Edit"],
])("does not show context menu when right-clicking", (buttonLabel: string) => {
// For favourite button
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(true);
it.each([["React"], ["Reply"], ["Reply in thread"], ["Favourite"], ["Edit"]])(
"does not show context menu when right-clicking",
(buttonLabel: string) => {
// For favourite button
jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
const event = new MouseEvent("contextmenu", {
bubbles: true,
cancelable: true,
});
event.stopPropagation = jest.fn();
event.preventDefault = jest.fn();
const event = new MouseEvent("contextmenu", {
bubbles: true,
cancelable: true,
});
event.stopPropagation = jest.fn();
event.preventDefault = jest.fn();
const { queryByTestId, queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
act(() => {
fireEvent(queryByLabelText(buttonLabel), event);
});
expect(event.stopPropagation).toHaveBeenCalled();
expect(event.preventDefault).toHaveBeenCalled();
expect(queryByTestId("mx_MessageContextMenu")).toBeFalsy();
});
const { queryByTestId, queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });
act(() => {
fireEvent(queryByLabelText(buttonLabel), event);
});
expect(event.stopPropagation).toHaveBeenCalled();
expect(event.preventDefault).toHaveBeenCalled();
expect(queryByTestId("mx_MessageContextMenu")).toBeFalsy();
},
);
it("does shows context menu when right-clicking options", () => {
const { queryByTestId, queryByLabelText } = getComponent({ mxEvent: alicesMessageEvent });

View file

@ -26,11 +26,11 @@ import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalink
jest.mock("../../../../src/components/views/messages/UnknownBody", () => ({
__esModule: true,
default: () => (<div data-testid="unknown-body" />),
default: () => <div data-testid="unknown-body" />,
}));
jest.mock("../../../../src/voice-broadcast/components/VoiceBroadcastBody", () => ({
VoiceBroadcastBody: () => (<div data-testid="voice-broadcast-body" />),
VoiceBroadcastBody: () => <div data-testid="voice-broadcast-body" />,
}));
describe("MessageEvent", () => {
@ -39,11 +39,13 @@ describe("MessageEvent", () => {
let event: MatrixEvent;
const renderMessageEvent = (): RenderResult => {
return render(<MessageEvent
mxEvent={event}
onHeightChanged={jest.fn()}
permalinkCreator={new RoomPermalinkCreator(room)}
/>);
return render(
<MessageEvent
mxEvent={event}
onHeightChanged={jest.fn()}
permalinkCreator={new RoomPermalinkCreator(room)}
/>,
);
};
beforeEach(() => {

View file

@ -31,7 +31,7 @@ import { MediaEventHelper } from "../../../../src/utils/MediaEventHelper";
describe("<TextualBody />", () => {
afterEach(() => {
jest.spyOn(MatrixClientPeg, 'get').mockRestore();
jest.spyOn(MatrixClientPeg, "get").mockRestore();
});
const defaultRoom = mkStubRoom("room_id", "test room", undefined);
@ -58,7 +58,7 @@ describe("<TextualBody />", () => {
const defaultProps = {
mxEvent: defaultEvent,
highlights: [],
highlightLink: '',
highlightLink: "",
onMessageAllowed: jest.fn(),
onHeightChanged: jest.fn(),
permalinkCreator: new RoomPermalinkCreator(defaultRoom),
@ -107,7 +107,7 @@ describe("<TextualBody />", () => {
const wrapper = getComponent({ mxEvent: ev });
expect(wrapper.text()).toBe(ev.getContent().body);
const content = wrapper.find(".mx_EventTile_body");
expect(content.html()).toBe(`<span class="mx_EventTile_body" dir="auto">${ ev.getContent().body }</span>`);
expect(content.html()).toBe(`<span class="mx_EventTile_body" dir="auto">${ev.getContent().body}</span>`);
});
describe("renders plain-text m.text correctly", () => {
@ -130,7 +130,7 @@ describe("<TextualBody />", () => {
const wrapper = getComponent({ mxEvent: ev });
expect(wrapper.text()).toBe(ev.getContent().body);
const content = wrapper.find(".mx_EventTile_body");
expect(content.html()).toBe(`<span class="mx_EventTile_body" dir="auto">${ ev.getContent().body }</span>`);
expect(content.html()).toBe(`<span class="mx_EventTile_body" dir="auto">${ev.getContent().body}</span>`);
});
// If pills were rendered within a Portal/same shadow DOM then it'd be easier to test
@ -149,9 +149,11 @@ describe("<TextualBody />", () => {
const wrapper = getComponent({ mxEvent: ev });
expect(wrapper.text()).toBe(ev.getContent().body);
const content = wrapper.find(".mx_EventTile_body");
expect(content.html()).toBe('<span class="mx_EventTile_body" dir="auto">' +
'Visit <a href="https://matrix.org/" class="linkified" target="_blank" rel="noreferrer noopener">' +
'https://matrix.org/</a></span>');
expect(content.html()).toBe(
'<span class="mx_EventTile_body" dir="auto">' +
'Visit <a href="https://matrix.org/" class="linkified" target="_blank" rel="noreferrer noopener">' +
"https://matrix.org/</a></span>",
);
});
});
@ -188,8 +190,11 @@ describe("<TextualBody />", () => {
const wrapper = getComponent({ mxEvent: ev }, matrixClient);
expect(wrapper.text()).toBe("foo baz bar del u");
const content = wrapper.find(".mx_EventTile_body");
expect(content.html()).toBe('<span class="mx_EventTile_body markdown-body" dir="auto">' +
ev.getContent().formatted_body + '</span>');
expect(content.html()).toBe(
'<span class="mx_EventTile_body markdown-body" dir="auto">' +
ev.getContent().formatted_body +
"</span>",
);
});
it("spoilers get injected properly into the DOM", () => {
@ -201,7 +206,7 @@ describe("<TextualBody />", () => {
body: "Hey [Spoiler for movie](mxc://someserver/somefile)",
msgtype: "m.text",
format: "org.matrix.custom.html",
formatted_body: "Hey <span data-mx-spoiler=\"movie\">the movie was awesome</span>",
formatted_body: 'Hey <span data-mx-spoiler="movie">the movie was awesome</span>',
},
event: true,
});
@ -209,12 +214,14 @@ describe("<TextualBody />", () => {
const wrapper = getComponent({ mxEvent: ev }, matrixClient);
expect(wrapper.text()).toBe("Hey (movie) the movie was awesome");
const content = wrapper.find(".mx_EventTile_body");
expect(content.html()).toBe('<span class="mx_EventTile_body markdown-body" dir="auto">' +
'Hey <span>' +
'<span class="mx_EventTile_spoiler">' +
'<span class="mx_EventTile_spoiler_reason">(movie)</span>&nbsp;' +
'<span class="mx_EventTile_spoiler_content"><span>the movie was awesome</span></span>' +
'</span></span></span>');
expect(content.html()).toBe(
'<span class="mx_EventTile_body markdown-body" dir="auto">' +
"Hey <span>" +
'<span class="mx_EventTile_spoiler">' +
'<span class="mx_EventTile_spoiler_reason">(movie)</span>&nbsp;' +
'<span class="mx_EventTile_spoiler_content"><span>the movie was awesome</span></span>' +
"</span></span></span>",
);
});
it("linkification is not applied to code blocks", () => {
@ -247,7 +254,7 @@ describe("<TextualBody />", () => {
body: "Hey User",
msgtype: "m.text",
format: "org.matrix.custom.html",
formatted_body: "Hey <a href=\"https://matrix.to/#/@user:server\">Member</a>",
formatted_body: 'Hey <a href="https://matrix.to/#/@user:server">Member</a>',
},
event: true,
});
@ -290,8 +297,8 @@ describe("<TextualBody />", () => {
msgtype: "m.text",
format: "org.matrix.custom.html",
formatted_body:
"An <a href=\"https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com/" +
"$16085560162aNpaH:example.com?via=example.com\">event link</a> with text",
'An <a href="https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com/' +
'$16085560162aNpaH:example.com?via=example.com">event link</a> with text',
},
event: true,
});
@ -301,9 +308,9 @@ describe("<TextualBody />", () => {
const content = wrapper.find(".mx_EventTile_body");
expect(content.html()).toBe(
'<span class="mx_EventTile_body markdown-body" dir="auto">' +
'An <a href="https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com/' +
'$16085560162aNpaH:example.com?via=example.com" ' +
'rel="noreferrer noopener">event link</a> with text</span>',
'An <a href="https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com/' +
'$16085560162aNpaH:example.com?via=example.com" ' +
'rel="noreferrer noopener">event link</a> with text</span>',
);
});
@ -319,8 +326,8 @@ describe("<TextualBody />", () => {
msgtype: "m.text",
format: "org.matrix.custom.html",
formatted_body:
"A <a href=\"https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com" +
"?via=example.com&amp;via=bob.com\">room link</a> with vias",
'A <a href="https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com' +
'?via=example.com&amp;via=bob.com">room link</a> with vias',
},
event: true,
});
@ -330,17 +337,17 @@ describe("<TextualBody />", () => {
const content = wrapper.find(".mx_EventTile_body");
expect(content.html()).toBe(
'<span class="mx_EventTile_body markdown-body" dir="auto">' +
'A <span><bdi><a class="mx_Pill mx_RoomPill" ' +
'href="https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com' +
'?via=example.com&amp;via=bob.com"' +
'><img class="mx_BaseAvatar mx_BaseAvatar_image" ' +
'src="mxc://avatar.url/room.png" ' +
'style="width: 16px; height: 16px;" alt="" data-testid="avatar-img" aria-hidden="true">' +
'<span class="mx_Pill_linkText">room name</span></a></bdi></span> with vias</span>',
'A <span><bdi><a class="mx_Pill mx_RoomPill" ' +
'href="https://matrix.to/#/!ZxbRYPQXDXKGmDnJNg:example.com' +
'?via=example.com&amp;via=bob.com"' +
'><img class="mx_BaseAvatar mx_BaseAvatar_image" ' +
'src="mxc://avatar.url/room.png" ' +
'style="width: 16px; height: 16px;" alt="" data-testid="avatar-img" aria-hidden="true">' +
'<span class="mx_Pill_linkText">room name</span></a></bdi></span> with vias</span>',
);
});
it('renders formatted body without html corretly', () => {
it("renders formatted body without html corretly", () => {
const ev = mkEvent({
type: "m.room.message",
room: "room_id",
@ -358,15 +365,13 @@ describe("<TextualBody />", () => {
const content = wrapper.find(".mx_EventTile_body");
expect(content.html()).toBe(
'<span class="mx_EventTile_body" dir="auto">' +
'escaped *markdown*' +
'</span>',
'<span class="mx_EventTile_body" dir="auto">' + "escaped *markdown*" + "</span>",
);
});
});
it("renders url previews correctly", () => {
languageHandler.setMissingEntryGenerator(key => key.split('|', 2)[1]);
languageHandler.setMissingEntryGenerator((key) => key.split("|", 2)[1]);
const matrixClient = getMockClientWithEventEmitter({
getRoom: () => mkStubRoom("room_id", "room name", undefined),
@ -408,21 +413,24 @@ describe("<TextualBody />", () => {
},
event: true,
});
jest.spyOn(ev, 'replacingEventDate').mockReturnValue(new Date(1993, 7, 3));
jest.spyOn(ev, "replacingEventDate").mockReturnValue(new Date(1993, 7, 3));
ev.makeReplaced(ev2);
wrapper.setProps({
mxEvent: ev,
replacingEventId: ev.getId(),
}, () => {
expect(wrapper.text()).toBe(ev2.getContent()["m.new_content"].body + "(edited)");
wrapper.setProps(
{
mxEvent: ev,
replacingEventId: ev.getId(),
},
() => {
expect(wrapper.text()).toBe(ev2.getContent()["m.new_content"].body + "(edited)");
// XXX: this is to give TextualBody enough time for state to settle
wrapper.setState({}, () => {
widgets = wrapper.find("LinkPreviewGroup");
// at this point we should have exactly two links (not the matrix.org one anymore)
expect(widgets.at(0).prop("links")).toEqual(["https://vector.im/", "https://riot.im/"]);
});
});
// XXX: this is to give TextualBody enough time for state to settle
wrapper.setState({}, () => {
widgets = wrapper.find("LinkPreviewGroup");
// at this point we should have exactly two links (not the matrix.org one anymore)
expect(widgets.at(0).prop("links")).toEqual(["https://vector.im/", "https://riot.im/"]);
});
},
);
});
});

View file

@ -14,20 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { render } from '@testing-library/react';
import React from "react";
import { render } from "@testing-library/react";
import MediaProcessingError from '../../../../../src/components/views/messages/shared/MediaProcessingError';
import MediaProcessingError from "../../../../../src/components/views/messages/shared/MediaProcessingError";
describe('<MediaProcessingError />', () => {
describe("<MediaProcessingError />", () => {
const defaultProps = {
className: 'test-classname',
children: 'Something went wrong',
className: "test-classname",
children: "Something went wrong",
};
const getComponent = (props = {}) =>
render(<MediaProcessingError {...defaultProps} {...props} />);
const getComponent = (props = {}) => render(<MediaProcessingError {...defaultProps} {...props} />);
it('renders', () => {
it("renders", () => {
const { container } = getComponent();
expect(container).toMatchSnapshot();
});