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

@ -14,43 +14,44 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { screen, render, fireEvent, waitFor, within, act } from '@testing-library/react';
import React from "react";
import { screen, render, fireEvent, waitFor, within, act } from "@testing-library/react";
import * as TestUtils from '../../test-utils';
import AutocompleteProvider from '../../../src/autocomplete/AutocompleteProvider';
import { ICompletion } from '../../../src/autocomplete/Autocompleter';
import * as TestUtils from "../../test-utils";
import AutocompleteProvider from "../../../src/autocomplete/AutocompleteProvider";
import { ICompletion } from "../../../src/autocomplete/Autocompleter";
import { AutocompleteInput } from "../../../src/components/structures/AutocompleteInput";
describe('AutocompleteInput', () => {
describe("AutocompleteInput", () => {
const mockCompletion: ICompletion[] = [
{ type: 'user', completion: 'user_1', completionId: '@user_1:host.local', range: { start: 1, end: 1 } },
{ type: 'user', completion: 'user_2', completionId: '@user_2:host.local', range: { start: 1, end: 1 } },
{ type: "user", completion: "user_1", completionId: "@user_1:host.local", range: { start: 1, end: 1 } },
{ type: "user", completion: "user_2", completionId: "@user_2:host.local", range: { start: 1, end: 1 } },
];
const constructMockProvider = (data: ICompletion[]) => ({
getCompletions: jest.fn().mockImplementation(async () => data),
}) as unknown as AutocompleteProvider;
const constructMockProvider = (data: ICompletion[]) =>
({
getCompletions: jest.fn().mockImplementation(async () => data),
} as unknown as AutocompleteProvider);
beforeEach(() => {
TestUtils.stubClient();
});
const getEditorInput = () => {
const input = screen.getByTestId('autocomplete-input');
const input = screen.getByTestId("autocomplete-input");
expect(input).toBeDefined();
return input;
};
it('should render suggestions when a query is set', async () => {
it("should render suggestions when a query is set", async () => {
const mockProvider = constructMockProvider(mockCompletion);
const onSelectionChangeMock = jest.fn();
render(
<AutocompleteInput
provider={mockProvider}
placeholder='Search ...'
placeholder="Search ..."
selection={[]}
onSelectionChange={onSelectionChangeMock}
/>,
@ -60,45 +61,45 @@ describe('AutocompleteInput', () => {
act(() => {
fireEvent.focus(input);
fireEvent.change(input, { target: { value: 'user' } });
fireEvent.change(input, { target: { value: "user" } });
});
await waitFor(() => expect(mockProvider.getCompletions).toHaveBeenCalledTimes(1));
expect(screen.getByTestId('autocomplete-matches').childNodes).toHaveLength(mockCompletion.length);
expect(screen.getByTestId("autocomplete-matches").childNodes).toHaveLength(mockCompletion.length);
});
it('should render selected items passed in via props', () => {
it("should render selected items passed in via props", () => {
const mockProvider = constructMockProvider(mockCompletion);
const onSelectionChangeMock = jest.fn();
render(
<AutocompleteInput
provider={mockProvider}
placeholder='Search ...'
placeholder="Search ..."
selection={mockCompletion}
onSelectionChange={onSelectionChangeMock}
/>,
);
const editor = screen.getByTestId('autocomplete-editor');
const editor = screen.getByTestId("autocomplete-editor");
const selection = within(editor).getAllByTestId("autocomplete-selection-item", { exact: false });
expect(selection).toHaveLength(mockCompletion.length);
});
it('should call onSelectionChange() when an item is removed from selection', () => {
it("should call onSelectionChange() when an item is removed from selection", () => {
const mockProvider = constructMockProvider(mockCompletion);
const onSelectionChangeMock = jest.fn();
render(
<AutocompleteInput
provider={mockProvider}
placeholder='Search ...'
placeholder="Search ..."
selection={mockCompletion}
onSelectionChange={onSelectionChangeMock}
/>,
);
const editor = screen.getByTestId('autocomplete-editor');
const editor = screen.getByTestId("autocomplete-editor");
const removeButtons = within(editor).getAllByTestId("autocomplete-selection-remove-button", { exact: false });
expect(removeButtons).toHaveLength(mockCompletion.length);
@ -110,39 +111,35 @@ describe('AutocompleteInput', () => {
expect(onSelectionChangeMock).toHaveBeenCalledWith([mockCompletion[1]]);
});
it('should render custom selection element when renderSelection() is defined', () => {
it("should render custom selection element when renderSelection() is defined", () => {
const mockProvider = constructMockProvider(mockCompletion);
const onSelectionChangeMock = jest.fn();
const renderSelection = () => (
<span data-testid='custom-selection-element'>custom selection element</span>
);
const renderSelection = () => <span data-testid="custom-selection-element">custom selection element</span>;
render(
<AutocompleteInput
provider={mockProvider}
placeholder='Search ...'
placeholder="Search ..."
selection={mockCompletion}
onSelectionChange={onSelectionChangeMock}
renderSelection={renderSelection}
/>,
);
expect(screen.getAllByTestId('custom-selection-element')).toHaveLength(mockCompletion.length);
expect(screen.getAllByTestId("custom-selection-element")).toHaveLength(mockCompletion.length);
});
it('should render custom suggestion element when renderSuggestion() is defined', async () => {
it("should render custom suggestion element when renderSuggestion() is defined", async () => {
const mockProvider = constructMockProvider(mockCompletion);
const onSelectionChangeMock = jest.fn();
const renderSuggestion = () => (
<span data-testid='custom-suggestion-element'>custom suggestion element</span>
);
const renderSuggestion = () => <span data-testid="custom-suggestion-element">custom suggestion element</span>;
render(
<AutocompleteInput
provider={mockProvider}
placeholder='Search ...'
placeholder="Search ..."
selection={mockCompletion}
onSelectionChange={onSelectionChangeMock}
renderSuggestion={renderSuggestion}
@ -153,21 +150,21 @@ describe('AutocompleteInput', () => {
act(() => {
fireEvent.focus(input);
fireEvent.change(input, { target: { value: 'user' } });
fireEvent.change(input, { target: { value: "user" } });
});
await waitFor(() => expect(mockProvider.getCompletions).toHaveBeenCalledTimes(1));
expect(screen.getAllByTestId('custom-suggestion-element')).toHaveLength(mockCompletion.length);
expect(screen.getAllByTestId("custom-suggestion-element")).toHaveLength(mockCompletion.length);
});
it('should mark selected suggestions as selected', async () => {
it("should mark selected suggestions as selected", async () => {
const mockProvider = constructMockProvider(mockCompletion);
const onSelectionChangeMock = jest.fn();
const { container } = render(
<AutocompleteInput
provider={mockProvider}
placeholder='Search ...'
placeholder="Search ..."
selection={mockCompletion}
onSelectionChange={onSelectionChangeMock}
/>,
@ -177,23 +174,23 @@ describe('AutocompleteInput', () => {
act(() => {
fireEvent.focus(input);
fireEvent.change(input, { target: { value: 'user' } });
fireEvent.change(input, { target: { value: "user" } });
});
await waitFor(() => expect(mockProvider.getCompletions).toHaveBeenCalledTimes(1));
const suggestions = await within(container).findAllByTestId('autocomplete-suggestion-item', { exact: false });
const suggestions = await within(container).findAllByTestId("autocomplete-suggestion-item", { exact: false });
expect(suggestions).toHaveLength(mockCompletion.length);
suggestions.map(suggestion => expect(suggestion).toHaveClass('mx_AutocompleteInput_suggestion--selected'));
suggestions.map((suggestion) => expect(suggestion).toHaveClass("mx_AutocompleteInput_suggestion--selected"));
});
it('should remove the last added selection when backspace is pressed in empty input', () => {
it("should remove the last added selection when backspace is pressed in empty input", () => {
const mockProvider = constructMockProvider(mockCompletion);
const onSelectionChangeMock = jest.fn();
render(
<AutocompleteInput
provider={mockProvider}
placeholder='Search ...'
placeholder="Search ..."
selection={mockCompletion}
onSelectionChange={onSelectionChangeMock}
/>,
@ -202,20 +199,20 @@ describe('AutocompleteInput', () => {
const input = getEditorInput();
act(() => {
fireEvent.keyDown(input, { key: 'Backspace' });
fireEvent.keyDown(input, { key: "Backspace" });
});
expect(onSelectionChangeMock).toHaveBeenCalledWith([mockCompletion[0]]);
});
it('should toggle a selected item when a suggestion is clicked', async () => {
it("should toggle a selected item when a suggestion is clicked", async () => {
const mockProvider = constructMockProvider(mockCompletion);
const onSelectionChangeMock = jest.fn();
const { container } = render(
<AutocompleteInput
provider={mockProvider}
placeholder='Search ...'
placeholder="Search ..."
selection={[]}
onSelectionChange={onSelectionChangeMock}
/>,
@ -225,10 +222,10 @@ describe('AutocompleteInput', () => {
act(() => {
fireEvent.focus(input);
fireEvent.change(input, { target: { value: 'user' } });
fireEvent.change(input, { target: { value: "user" } });
});
const suggestions = await within(container).findAllByTestId('autocomplete-suggestion-item', { exact: false });
const suggestions = await within(container).findAllByTestId("autocomplete-suggestion-item", { exact: false });
act(() => {
fireEvent.mouseDown(suggestions[0]);

View file

@ -85,4 +85,3 @@ describe("ContextMenu", () => {
});
});
});

View file

@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixClient, MatrixEvent } from 'matrix-js-sdk/src/matrix';
import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { CallState } from "matrix-js-sdk/src/webrtc/call";
import { stubClient } from '../../test-utils';
import { MatrixClientPeg } from '../../../src/MatrixClientPeg';
import { stubClient } from "../../test-utils";
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
import LegacyCallEventGrouper, { CustomCallState } from "../../../src/components/structures/LegacyCallEventGrouper";
const MY_USER_ID = "@me:here";
@ -27,7 +27,7 @@ const THEIR_USER_ID = "@they:here";
let client: MatrixClient;
describe('LegacyCallEventGrouper', () => {
describe("LegacyCallEventGrouper", () => {
beforeEach(() => {
stubClient();
client = MatrixClientPeg.get();

View file

@ -15,13 +15,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import ReactDOM from "react-dom";
import { EventEmitter } from "events";
import { Room, RoomMember } from 'matrix-js-sdk/src/matrix';
import FakeTimers from '@sinonjs/fake-timers';
import { render } from '@testing-library/react';
import { Thread } from 'matrix-js-sdk/src/models/thread';
import { Room, RoomMember } from "matrix-js-sdk/src/matrix";
import FakeTimers from "@sinonjs/fake-timers";
import { render } from "@testing-library/react";
import { Thread } from "matrix-js-sdk/src/models/thread";
import MessagePanel, { shouldFormContinuation } from "../../../src/components/structures/MessagePanel";
import SettingsStore from "../../../src/settings/SettingsStore";
@ -34,21 +34,21 @@ import {
makeBeaconInfoEvent,
mockClientMethodsEvents,
mockClientMethodsUser,
} from '../../test-utils';
import ResizeNotifier from '../../../src/utils/ResizeNotifier';
import { IRoomState } from '../../../src/components/structures/RoomView';
} from "../../test-utils";
import ResizeNotifier from "../../../src/utils/ResizeNotifier";
import { IRoomState } from "../../../src/components/structures/RoomView";
jest.mock('../../../src/utils/beacon', () => ({
jest.mock("../../../src/utils/beacon", () => ({
useBeacon: jest.fn(),
}));
const roomId = "!roomId:server_name";
describe('MessagePanel', function() {
describe("MessagePanel", function () {
let clock = null;
const realSetTimeout = window.setTimeout;
const events = mkEvents();
const userId = '@me:here';
const userId = "@me:here";
const client = getMockClientWithEventEmitter({
...mockClientMethodsUser(userId),
...mockClientMethodsEvents(),
@ -61,22 +61,22 @@ describe('MessagePanel', function() {
const room = new Room(roomId, client, userId);
const bobMember = new RoomMember(roomId, '@bob:id');
bobMember.name = 'Bob';
jest.spyOn(bobMember, 'getAvatarUrl').mockReturnValue('avatar.jpeg');
jest.spyOn(bobMember, 'getMxcAvatarUrl').mockReturnValue('mxc://avatar.url/image.png');
const bobMember = new RoomMember(roomId, "@bob:id");
bobMember.name = "Bob";
jest.spyOn(bobMember, "getAvatarUrl").mockReturnValue("avatar.jpeg");
jest.spyOn(bobMember, "getMxcAvatarUrl").mockReturnValue("mxc://avatar.url/image.png");
const alice = "@alice:example.org";
const aliceMember = new RoomMember(roomId, alice);
aliceMember.name = 'Alice';
jest.spyOn(aliceMember, 'getAvatarUrl').mockReturnValue('avatar.jpeg');
jest.spyOn(aliceMember, 'getMxcAvatarUrl').mockReturnValue('mxc://avatar.url/image.png');
aliceMember.name = "Alice";
jest.spyOn(aliceMember, "getAvatarUrl").mockReturnValue("avatar.jpeg");
jest.spyOn(aliceMember, "getMxcAvatarUrl").mockReturnValue("mxc://avatar.url/image.png");
const defaultProps = {
resizeNotifier: new EventEmitter as unknown as ResizeNotifier,
resizeNotifier: new EventEmitter() as unknown as ResizeNotifier,
callEventGroupers: new Map(),
room,
className: 'cls',
className: "cls",
events: [],
};
@ -95,24 +95,26 @@ describe('MessagePanel', function() {
showHiddenEvents: false,
} as unknown as IRoomState;
const getComponent = (props = {}, roomContext: Partial<IRoomState> = {}) =>
const getComponent = (props = {}, roomContext: Partial<IRoomState> = {}) => (
<MatrixClientContext.Provider value={client}>
<RoomContext.Provider value={{ ...defaultRoomContext, ...roomContext }}>
<MessagePanel {...defaultProps} {...props} />
</RoomContext.Provider>);
</MatrixClientContext.Provider>;
</RoomContext.Provider>
);
</MatrixClientContext.Provider>
);
beforeEach(function() {
beforeEach(function () {
jest.clearAllMocks();
// HACK: We assume all settings want to be disabled
jest.spyOn(SettingsStore, 'getValue').mockImplementation((arg) => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((arg) => {
return arg === "showDisplaynameChanges";
});
DMRoomMap.makeShared();
});
afterEach(function() {
afterEach(function () {
if (clock) {
clock.uninstall();
clock = null;
@ -123,11 +125,14 @@ describe('MessagePanel', function() {
const events = [];
const ts0 = Date.now();
for (let i = 0; i < 10; i++) {
events.push(TestUtilsMatrix.mkMessage(
{
event: true, room: "!room:id", user: "@user:id",
events.push(
TestUtilsMatrix.mkMessage({
event: true,
room: "!room:id",
user: "@user:id",
ts: ts0 + i * 1000,
}));
}),
);
}
return events;
}
@ -135,13 +140,16 @@ describe('MessagePanel', function() {
// Just to avoid breaking Dateseparator tests that might run at 00hrs
function mkOneDayEvents() {
const events = [];
const ts0 = Date.parse('09 May 2004 00:12:00 GMT');
const ts0 = Date.parse("09 May 2004 00:12:00 GMT");
for (let i = 0; i < 10; i++) {
events.push(TestUtilsMatrix.mkMessage(
{
event: true, room: "!room:id", user: "@user:id",
events.push(
TestUtilsMatrix.mkMessage({
event: true,
room: "!room:id",
user: "@user:id",
ts: ts0 + i * 1000,
}));
}),
);
}
return events;
}
@ -152,26 +160,38 @@ describe('MessagePanel', function() {
const ts0 = Date.now();
let i = 0;
events.push(TestUtilsMatrix.mkMessage({
event: true, room: "!room:id", user: "@user:id",
ts: ts0 + ++i * 1000,
}));
events.push(
TestUtilsMatrix.mkMessage({
event: true,
room: "!room:id",
user: "@user:id",
ts: ts0 + ++i * 1000,
}),
);
for (i = 0; i < 10; i++) {
events.push(TestUtilsMatrix.mkMembership({
event: true, room: "!room:id", user: "@user:id",
target: bobMember,
ts: ts0 + i*1000,
mship: 'join',
prevMship: 'join',
name: 'A user',
}));
events.push(
TestUtilsMatrix.mkMembership({
event: true,
room: "!room:id",
user: "@user:id",
target: bobMember,
ts: ts0 + i * 1000,
mship: "join",
prevMship: "join",
name: "A user",
}),
);
}
events.push(TestUtilsMatrix.mkMessage({
event: true, room: "!room:id", user: "@user:id",
ts: ts0 + ++i*1000,
}));
events.push(
TestUtilsMatrix.mkMessage({
event: true,
room: "!room:id",
user: "@user:id",
ts: ts0 + ++i * 1000,
}),
);
return events;
}
@ -184,14 +204,18 @@ describe('MessagePanel', function() {
let i = 0;
for (i = 0; i < 10; i++) {
events.push(TestUtilsMatrix.mkMembership({
event: true, room: "!room:id", user: "@user:id",
target: bobMember,
ts: ts0 + i * 1000,
mship: 'join',
prevMship: 'join',
name: 'A user',
}));
events.push(
TestUtilsMatrix.mkMembership({
event: true,
room: "!room:id",
user: "@user:id",
target: bobMember,
ts: ts0 + i * 1000,
mship: "join",
prevMship: "join",
name: "A user",
}),
);
}
return events;
@ -227,8 +251,8 @@ describe('MessagePanel', function() {
user: alice,
target: aliceMember,
ts: ts0 + 1,
mship: 'join',
name: 'Alice',
mship: "join",
name: "Alice",
}),
mkEvent({
event: true,
@ -236,7 +260,7 @@ describe('MessagePanel', function() {
room: roomId,
user: alice,
content: {
"join_rule": "invite",
join_rule: "invite",
},
ts: ts0 + 2,
}),
@ -246,7 +270,7 @@ describe('MessagePanel', function() {
room: roomId,
user: alice,
content: {
"history_visibility": "invited",
history_visibility: "invited",
},
ts: ts0 + 3,
}),
@ -256,7 +280,7 @@ describe('MessagePanel', function() {
room: roomId,
user: alice,
content: {
"algorithm": "m.megolm.v1.aes-sha2",
algorithm: "m.megolm.v1.aes-sha2",
},
ts: ts0 + 4,
}),
@ -267,8 +291,8 @@ describe('MessagePanel', function() {
skey: "@bob:example.org",
target: bobMember,
ts: ts0 + 5,
mship: 'invite',
name: 'Bob',
mship: "invite",
name: "Bob",
}),
];
}
@ -300,52 +324,58 @@ describe('MessagePanel', function() {
return rmContainer && rmContainer.children.length > 0;
}
it('should show the events', function() {
it("should show the events", function () {
const { container } = render(getComponent({ events }));
// just check we have the right number of tiles for now
const tiles = container.getElementsByClassName('mx_EventTile');
const tiles = container.getElementsByClassName("mx_EventTile");
expect(tiles.length).toEqual(10);
});
it('should collapse adjacent member events', function() {
it("should collapse adjacent member events", function () {
const { container } = render(getComponent({ events: mkMelsEvents() }));
// just check we have the right number of tiles for now
const tiles = container.getElementsByClassName('mx_EventTile');
const tiles = container.getElementsByClassName("mx_EventTile");
expect(tiles.length).toEqual(2);
const summaryTiles = container.getElementsByClassName('mx_GenericEventListSummary');
const summaryTiles = container.getElementsByClassName("mx_GenericEventListSummary");
expect(summaryTiles.length).toEqual(1);
});
it('should insert the read-marker in the right place', function() {
const { container } = render(getComponent({
events, readMarkerEventId: events[4].getId(), readMarkerVisible: true,
}));
it("should insert the read-marker in the right place", function () {
const { container } = render(
getComponent({
events,
readMarkerEventId: events[4].getId(),
readMarkerVisible: true,
}),
);
const tiles = container.getElementsByClassName('mx_EventTile');
const tiles = container.getElementsByClassName("mx_EventTile");
// find the <li> which wraps the read marker
const [rm] = container.getElementsByClassName('mx_RoomView_myReadMarker_container');
const [rm] = container.getElementsByClassName("mx_RoomView_myReadMarker_container");
// it should follow the <li> which wraps the event tile for event 4
const eventContainer = ReactDOM.findDOMNode(tiles[4]);
expect(rm.previousSibling).toEqual(eventContainer);
});
it('should show the read-marker that fall in summarised events after the summary', function() {
it("should show the read-marker that fall in summarised events after the summary", function () {
const melsEvents = mkMelsEvents();
const { container } = render(getComponent({
events: melsEvents,
readMarkerEventId: melsEvents[4].getId(),
readMarkerVisible: true,
}));
const { container } = render(
getComponent({
events: melsEvents,
readMarkerEventId: melsEvents[4].getId(),
readMarkerVisible: true,
}),
);
const [summary] = container.getElementsByClassName('mx_GenericEventListSummary');
const [summary] = container.getElementsByClassName("mx_GenericEventListSummary");
// find the <li> which wraps the read marker
const [rm] = container.getElementsByClassName('mx_RoomView_myReadMarker_container');
const [rm] = container.getElementsByClassName("mx_RoomView_myReadMarker_container");
expect(rm.previousSibling).toEqual(summary);
@ -353,19 +383,21 @@ describe('MessagePanel', function() {
expect(isReadMarkerVisible(rm)).toBeTruthy();
});
it('should hide the read-marker at the end of summarised events', function() {
it("should hide the read-marker at the end of summarised events", function () {
const melsEvents = mkMelsEventsOnly();
const { container } = render(getComponent({
events: melsEvents,
readMarkerEventId: melsEvents[9].getId(),
readMarkerVisible: true,
}));
const { container } = render(
getComponent({
events: melsEvents,
readMarkerEventId: melsEvents[9].getId(),
readMarkerVisible: true,
}),
);
const [summary] = container.getElementsByClassName('mx_GenericEventListSummary');
const [summary] = container.getElementsByClassName("mx_GenericEventListSummary");
// find the <li> which wraps the read marker
const [rm] = container.getElementsByClassName('mx_RoomView_myReadMarker_container');
const [rm] = container.getElementsByClassName("mx_RoomView_myReadMarker_container");
expect(rm.previousSibling).toEqual(summary);
@ -373,34 +405,38 @@ describe('MessagePanel', function() {
expect(isReadMarkerVisible(rm)).toBeFalsy();
});
it('shows a ghost read-marker when the read-marker moves', function(done) {
it("shows a ghost read-marker when the read-marker moves", function (done) {
// fake the clock so that we can test the velocity animation.
clock = FakeTimers.install();
const { container, rerender } = render(<div>
{ getComponent({
events,
readMarkerEventId: events[4].getId(),
readMarkerVisible: true,
}) }
</div>);
const { container, rerender } = render(
<div>
{getComponent({
events,
readMarkerEventId: events[4].getId(),
readMarkerVisible: true,
})}
</div>,
);
const tiles = container.getElementsByClassName('mx_EventTile');
const tiles = container.getElementsByClassName("mx_EventTile");
// find the <li> which wraps the read marker
const [rm] = container.getElementsByClassName('mx_RoomView_myReadMarker_container');
const [rm] = container.getElementsByClassName("mx_RoomView_myReadMarker_container");
expect(rm.previousSibling).toEqual(tiles[4]);
rerender(<div>
{ getComponent({
events,
readMarkerEventId: events[6].getId(),
readMarkerVisible: true,
}) }
</div>);
rerender(
<div>
{getComponent({
events,
readMarkerEventId: events[6].getId(),
readMarkerVisible: true,
})}
</div>,
);
// now there should be two RM containers
const readMarkers = container.getElementsByClassName('mx_RoomView_myReadMarker_container');
const readMarkers = container.getElementsByClassName("mx_RoomView_myReadMarker_container");
expect(readMarkers.length).toEqual(2);
@ -420,72 +456,72 @@ describe('MessagePanel', function() {
clock.tick(1000);
realSetTimeout(() => {
// the ghost should now have finished
expect(hr.style.opacity).toEqual('0');
expect(hr.style.opacity).toEqual("0");
done();
}, 100);
}, 100);
});
it('should collapse creation events', function() {
it("should collapse creation events", function () {
const events = mkCreationEvents();
TestUtilsMatrix.upsertRoomStateEvents(room, events);
const { container } = render(getComponent({ events }));
const createEvent = events.find(event => event.getType() === 'm.room.create');
const encryptionEvent = events.find(event => event.getType() === 'm.room.encryption');
const createEvent = events.find((event) => event.getType() === "m.room.create");
const encryptionEvent = events.find((event) => event.getType() === "m.room.encryption");
// we expect that
// - the room creation event, the room encryption event, and Alice inviting Bob,
// should be outside of the room creation summary
// - all other events should be inside the room creation summary
const tiles = container.getElementsByClassName('mx_EventTile');
const tiles = container.getElementsByClassName("mx_EventTile");
expect(tiles[0].getAttribute('data-event-id')).toEqual(createEvent.getId());
expect(tiles[1].getAttribute('data-event-id')).toEqual(encryptionEvent.getId());
expect(tiles[0].getAttribute("data-event-id")).toEqual(createEvent.getId());
expect(tiles[1].getAttribute("data-event-id")).toEqual(encryptionEvent.getId());
const [summaryTile] = container.getElementsByClassName('mx_GenericEventListSummary');
const [summaryTile] = container.getElementsByClassName("mx_GenericEventListSummary");
const summaryEventTiles = summaryTile.getElementsByClassName('mx_EventTile');
const summaryEventTiles = summaryTile.getElementsByClassName("mx_EventTile");
// every event except for the room creation, room encryption, and Bob's
// invite event should be in the event summary
expect(summaryEventTiles.length).toEqual(tiles.length - 3);
});
it('should not collapse beacons as part of creation events', function() {
it("should not collapse beacons as part of creation events", function () {
const events = mkCreationEvents();
const creationEvent = events.find(event => event.getType() === 'm.room.create');
const beaconInfoEvent = makeBeaconInfoEvent(
creationEvent.getSender(),
creationEvent.getRoomId(),
{ isLive: true },
);
const creationEvent = events.find((event) => event.getType() === "m.room.create");
const beaconInfoEvent = makeBeaconInfoEvent(creationEvent.getSender(), creationEvent.getRoomId(), {
isLive: true,
});
const combinedEvents = [...events, beaconInfoEvent];
TestUtilsMatrix.upsertRoomStateEvents(room, combinedEvents);
const { container } = render(getComponent({ events: combinedEvents }));
const [summaryTile] = container.getElementsByClassName('mx_GenericEventListSummary');
const [summaryTile] = container.getElementsByClassName("mx_GenericEventListSummary");
// beacon body is not in the summary
expect(summaryTile.getElementsByClassName('mx_MBeaconBody').length).toBe(0);
expect(summaryTile.getElementsByClassName("mx_MBeaconBody").length).toBe(0);
// beacon tile is rendered
expect(container.getElementsByClassName('mx_MBeaconBody').length).toBe(1);
expect(container.getElementsByClassName("mx_MBeaconBody").length).toBe(1);
});
it('should hide read-marker at the end of creation event summary', function() {
it("should hide read-marker at the end of creation event summary", function () {
const events = mkCreationEvents();
TestUtilsMatrix.upsertRoomStateEvents(room, events);
const { container } = render(getComponent({
events,
readMarkerEventId: events[5].getId(),
readMarkerVisible: true,
}));
const { container } = render(
getComponent({
events,
readMarkerEventId: events[5].getId(),
readMarkerVisible: true,
}),
);
// find the <li> which wraps the read marker
const [rm] = container.getElementsByClassName('mx_RoomView_myReadMarker_container');
const [rm] = container.getElementsByClassName("mx_RoomView_myReadMarker_container");
const [messageList] = container.getElementsByClassName('mx_RoomView_MessageList');
const [messageList] = container.getElementsByClassName("mx_RoomView_MessageList");
const rows = messageList.children;
expect(rows.length).toEqual(7); // 6 events + the NewRoomIntro
expect(rm.previousSibling).toEqual(rows[5]);
@ -494,22 +530,22 @@ describe('MessagePanel', function() {
expect(isReadMarkerVisible(rm)).toBeFalsy();
});
it('should render Date separators for the events', function() {
it("should render Date separators for the events", function () {
const events = mkOneDayEvents();
const { queryAllByRole } = render(getComponent({ events }));
const dates = queryAllByRole('separator');
const dates = queryAllByRole("separator");
expect(dates.length).toEqual(1);
});
it('appends events into summaries during forward pagination without changing key', () => {
it("appends events into summaries during forward pagination without changing key", () => {
const events = mkMelsEvents().slice(1, 11);
const { container, rerender } = render(getComponent({ events }));
let els = container.getElementsByClassName('mx_GenericEventListSummary');
let els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(1);
expect(els[0].getAttribute('data-testid')).toEqual("eventlistsummary-" + events[0].getId());
expect(els[0].getAttribute("data-scroll-tokens").split(',').length).toEqual(10);
expect(els[0].getAttribute("data-testid")).toEqual("eventlistsummary-" + events[0].getId());
expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(10);
const updatedEvents = [
...events,
@ -519,27 +555,27 @@ describe('MessagePanel', function() {
user: "@user:id",
target: bobMember,
ts: Date.now(),
mship: 'join',
prevMship: 'join',
name: 'A user',
mship: "join",
prevMship: "join",
name: "A user",
}),
];
rerender(getComponent({ events: updatedEvents }));
els = container.getElementsByClassName('mx_GenericEventListSummary');
els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(1);
expect(els[0].getAttribute('data-testid')).toEqual("eventlistsummary-" + events[0].getId());
expect(els[0].getAttribute("data-scroll-tokens").split(',').length).toEqual(11);
expect(els[0].getAttribute("data-testid")).toEqual("eventlistsummary-" + events[0].getId());
expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(11);
});
it('prepends events into summaries during backward pagination without changing key', () => {
it("prepends events into summaries during backward pagination without changing key", () => {
const events = mkMelsEvents().slice(1, 11);
const { container, rerender } = render(getComponent({ events }));
let els = container.getElementsByClassName('mx_GenericEventListSummary');
let els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(1);
expect(els[0].getAttribute('data-testid')).toEqual("eventlistsummary-" + events[0].getId());
expect(els[0].getAttribute("data-scroll-tokens").split(',').length).toEqual(10);
expect(els[0].getAttribute("data-testid")).toEqual("eventlistsummary-" + events[0].getId());
expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(10);
const updatedEvents = [
TestUtilsMatrix.mkMembership({
@ -548,28 +584,28 @@ describe('MessagePanel', function() {
user: "@user:id",
target: bobMember,
ts: Date.now(),
mship: 'join',
prevMship: 'join',
name: 'A user',
mship: "join",
prevMship: "join",
name: "A user",
}),
...events,
];
rerender(getComponent({ events: updatedEvents }));
els = container.getElementsByClassName('mx_GenericEventListSummary');
els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(1);
expect(els[0].getAttribute('data-testid')).toEqual("eventlistsummary-" + events[0].getId());
expect(els[0].getAttribute("data-scroll-tokens").split(',').length).toEqual(11);
expect(els[0].getAttribute("data-testid")).toEqual("eventlistsummary-" + events[0].getId());
expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(11);
});
it('assigns different keys to summaries that get split up', () => {
it("assigns different keys to summaries that get split up", () => {
const events = mkMelsEvents().slice(1, 11);
const { container, rerender } = render(getComponent({ events }));
let els = container.getElementsByClassName('mx_GenericEventListSummary');
let els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(1);
expect(els[0].getAttribute('data-testid')).toEqual(`eventlistsummary-${events[0].getId()}`);
expect(els[0].getAttribute("data-scroll-tokens").split(',').length).toEqual(10);
expect(els[0].getAttribute("data-testid")).toEqual(`eventlistsummary-${events[0].getId()}`);
expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(10);
const updatedEvents = [
...events.slice(0, 5),
@ -584,13 +620,13 @@ describe('MessagePanel', function() {
rerender(getComponent({ events: updatedEvents }));
// summaries split becuase room messages are not summarised
els = container.getElementsByClassName('mx_GenericEventListSummary');
els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(2);
expect(els[0].getAttribute('data-testid')).toEqual(`eventlistsummary-${events[0].getId()}`);
expect(els[0].getAttribute("data-scroll-tokens").split(',').length).toEqual(5);
expect(els[0].getAttribute("data-testid")).toEqual(`eventlistsummary-${events[0].getId()}`);
expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(5);
expect(els[1].getAttribute('data-testid')).toEqual(`eventlistsummary-${events[5].getId()}`);
expect(els[1].getAttribute("data-scroll-tokens").split(',').length).toEqual(5);
expect(els[1].getAttribute("data-testid")).toEqual(`eventlistsummary-${events[5].getId()}`);
expect(els[1].getAttribute("data-scroll-tokens").split(",").length).toEqual(5);
});
// We test this because setting lookups can be *slow*, and we don't want
@ -638,9 +674,9 @@ describe('MessagePanel', function() {
];
const { container } = render(getComponent({ events }, { showHiddenEvents: true }));
const els = container.getElementsByClassName('mx_GenericEventListSummary');
const els = container.getElementsByClassName("mx_GenericEventListSummary");
expect(els.length).toEqual(1);
expect(els[0].getAttribute("data-scroll-tokens").split(',').length).toEqual(3);
expect(els[0].getAttribute("data-scroll-tokens").split(",").length).toEqual(3);
});
});

View file

@ -55,8 +55,8 @@ describe("RightPanel", () => {
});
afterEach(async () => {
const roomChanged = new Promise<void>(resolve => {
const ref = dis.register(payload => {
const roomChanged = new Promise<void>((resolve) => {
const ref = dis.register((payload) => {
if (payload.action === Action.ActiveRoomChanged) {
dis.unregister(ref);
resolve();
@ -84,12 +84,11 @@ describe("RightPanel", () => {
await RightPanelStore.instance.onReady();
};
const waitForRpsUpdate = () =>
new Promise<void>(resolve => RightPanelStore.instance.once(UPDATE_EVENT, resolve));
const waitForRpsUpdate = () => new Promise<void>((resolve) => RightPanelStore.instance.once(UPDATE_EVENT, resolve));
it("navigates from room summary to member list", async () => {
const r1 = mkRoom(cli, "r1");
cli.getRoom.mockImplementation(roomId => roomId === "r1" ? r1 : null);
cli.getRoom.mockImplementation((roomId) => (roomId === "r1" ? r1 : null));
// Set up right panel state
const realGetValue = SettingsStore.getValue;
@ -127,7 +126,7 @@ describe("RightPanel", () => {
const r1 = mkRoom(cli, "r1");
const r2 = mkRoom(cli, "r2");
cli.getRoom.mockImplementation(roomId => {
cli.getRoom.mockImplementation((roomId) => {
if (roomId === "r1") return r1;
if (roomId === "r2") return r2;
return null;
@ -168,7 +167,7 @@ describe("RightPanel", () => {
// We want to verify that as we change to room 2, we should always have
// the correct right panel state for whichever room we are showing.
const instance = wrapper.find(_RightPanel).instance() as _RightPanel;
const rendered = new Promise<void>(resolve => {
const rendered = new Promise<void>((resolve) => {
jest.spyOn(instance, "render").mockImplementation(() => {
const { props, state } = instance;
if (props.room.roomId === "r2" && state.phase === RightPanelPhases.RoomMemberList) {

View file

@ -63,7 +63,7 @@ describe("<RoomSearchView/>", () => {
it("should show a spinner before the promise resolves", async () => {
const deferred = defer<ISearchResults>();
render((
render(
<RoomSearchView
term="search term"
scope={SearchScope.All}
@ -72,50 +72,57 @@ describe("<RoomSearchView/>", () => {
permalinkCreator={permalinkCreator}
className="someClass"
onUpdate={jest.fn()}
/>
));
/>,
);
await screen.findByTestId("messagePanelSearchSpinner");
});
it("should render results when the promise resolves", async () => {
render((
render(
<MatrixClientContext.Provider value={client}>
<RoomSearchView
term="search term"
scope={SearchScope.All}
promise={Promise.resolve<ISearchResults>({
results: [
SearchResult.fromJson({
rank: 1,
result: {
room_id: room.roomId,
event_id: "$2",
sender: client.getUserId(),
origin_server_ts: 1,
content: { body: "Foo Test Bar", msgtype: "m.text" },
type: EventType.RoomMessage,
},
context: {
profile_info: {},
events_before: [{
SearchResult.fromJson(
{
rank: 1,
result: {
room_id: room.roomId,
event_id: "$1",
event_id: "$2",
sender: client.getUserId(),
origin_server_ts: 1,
content: { body: "Before", msgtype: "m.text" },
content: { body: "Foo Test Bar", msgtype: "m.text" },
type: EventType.RoomMessage,
}],
events_after: [{
room_id: room.roomId,
event_id: "$3",
sender: client.getUserId(),
origin_server_ts: 1,
content: { body: "After", msgtype: "m.text" },
type: EventType.RoomMessage,
}],
},
context: {
profile_info: {},
events_before: [
{
room_id: room.roomId,
event_id: "$1",
sender: client.getUserId(),
origin_server_ts: 1,
content: { body: "Before", msgtype: "m.text" },
type: EventType.RoomMessage,
},
],
events_after: [
{
room_id: room.roomId,
event_id: "$3",
sender: client.getUserId(),
origin_server_ts: 1,
content: { body: "After", msgtype: "m.text" },
type: EventType.RoomMessage,
},
],
},
},
}, eventMapper),
eventMapper,
),
],
highlights: [],
count: 1,
@ -125,8 +132,8 @@ describe("<RoomSearchView/>", () => {
className="someClass"
onUpdate={jest.fn()}
/>
</MatrixClientContext.Provider>
));
</MatrixClientContext.Provider>,
);
await screen.findByText("Before");
await screen.findByText("Foo Test Bar");
@ -134,29 +141,32 @@ describe("<RoomSearchView/>", () => {
});
it("should highlight words correctly", async () => {
render((
render(
<MatrixClientContext.Provider value={client}>
<RoomSearchView
term="search term"
scope={SearchScope.Room}
promise={Promise.resolve<ISearchResults>({
results: [
SearchResult.fromJson({
rank: 1,
result: {
room_id: room.roomId,
event_id: "$2",
sender: client.getUserId(),
origin_server_ts: 1,
content: { body: "Foo Test Bar", msgtype: "m.text" },
type: EventType.RoomMessage,
SearchResult.fromJson(
{
rank: 1,
result: {
room_id: room.roomId,
event_id: "$2",
sender: client.getUserId(),
origin_server_ts: 1,
content: { body: "Foo Test Bar", msgtype: "m.text" },
type: EventType.RoomMessage,
},
context: {
profile_info: {},
events_before: [],
events_after: [],
},
},
context: {
profile_info: {},
events_before: [],
events_after: [],
},
}, eventMapper),
eventMapper,
),
],
highlights: ["test"],
count: 1,
@ -166,8 +176,8 @@ describe("<RoomSearchView/>", () => {
className="someClass"
onUpdate={jest.fn()}
/>
</MatrixClientContext.Provider>
));
</MatrixClientContext.Provider>,
);
const text = await screen.findByText("Test");
expect(text).toHaveClass("mx_EventTile_searchHighlight");
@ -176,22 +186,25 @@ describe("<RoomSearchView/>", () => {
it("should show spinner above results when backpaginating", async () => {
const searchResults: ISearchResults = {
results: [
SearchResult.fromJson({
rank: 1,
result: {
room_id: room.roomId,
event_id: "$2",
sender: client.getUserId(),
origin_server_ts: 1,
content: { body: "Foo Test Bar", msgtype: "m.text" },
type: EventType.RoomMessage,
SearchResult.fromJson(
{
rank: 1,
result: {
room_id: room.roomId,
event_id: "$2",
sender: client.getUserId(),
origin_server_ts: 1,
content: { body: "Foo Test Bar", msgtype: "m.text" },
type: EventType.RoomMessage,
},
context: {
profile_info: {},
events_before: [],
events_after: [],
},
},
context: {
profile_info: {},
events_before: [],
events_after: [],
},
}, eventMapper),
eventMapper,
),
],
highlights: ["test"],
next_batch: "next_batch",
@ -202,27 +215,30 @@ describe("<RoomSearchView/>", () => {
...searchResults,
results: [
...searchResults.results,
SearchResult.fromJson({
rank: 1,
result: {
room_id: room.roomId,
event_id: "$4",
sender: client.getUserId(),
origin_server_ts: 4,
content: { body: "Potato", msgtype: "m.text" },
type: EventType.RoomMessage,
SearchResult.fromJson(
{
rank: 1,
result: {
room_id: room.roomId,
event_id: "$4",
sender: client.getUserId(),
origin_server_ts: 4,
content: { body: "Potato", msgtype: "m.text" },
type: EventType.RoomMessage,
},
context: {
profile_info: {},
events_before: [],
events_after: [],
},
},
context: {
profile_info: {},
events_before: [],
events_after: [],
},
}, eventMapper),
eventMapper,
),
],
next_batch: undefined,
});
render((
render(
<MatrixClientContext.Provider value={client}>
<RoomSearchView
term="search term"
@ -233,8 +249,8 @@ describe("<RoomSearchView/>", () => {
className="someClass"
onUpdate={jest.fn()}
/>
</MatrixClientContext.Provider>
));
</MatrixClientContext.Provider>,
);
await screen.findByRole("progressbar");
await screen.findByText("Potato");
@ -244,7 +260,7 @@ describe("<RoomSearchView/>", () => {
it("should handle resolutions after unmounting sanely", async () => {
const deferred = defer<ISearchResults>();
const { unmount } = render((
const { unmount } = render(
<MatrixClientContext.Provider value={client}>
<RoomSearchView
term="search term"
@ -255,8 +271,8 @@ describe("<RoomSearchView/>", () => {
className="someClass"
onUpdate={jest.fn()}
/>
</MatrixClientContext.Provider>
));
</MatrixClientContext.Provider>,
);
unmount();
deferred.resolve({
@ -268,7 +284,7 @@ describe("<RoomSearchView/>", () => {
it("should handle rejections after unmounting sanely", async () => {
const deferred = defer<ISearchResults>();
const { unmount } = render((
const { unmount } = render(
<MatrixClientContext.Provider value={client}>
<RoomSearchView
term="search term"
@ -279,8 +295,8 @@ describe("<RoomSearchView/>", () => {
className="someClass"
onUpdate={jest.fn()}
/>
</MatrixClientContext.Provider>
));
</MatrixClientContext.Provider>,
);
unmount();
deferred.reject({
@ -292,7 +308,7 @@ describe("<RoomSearchView/>", () => {
it("should show modal if error is encountered", async () => {
const deferred = defer<ISearchResults>();
render((
render(
<MatrixClientContext.Provider value={client}>
<RoomSearchView
term="search term"
@ -303,8 +319,8 @@ describe("<RoomSearchView/>", () => {
className="someClass"
onUpdate={jest.fn()}
/>
</MatrixClientContext.Provider>
));
</MatrixClientContext.Provider>,
);
deferred.reject(new Error("Some error"));
await screen.findByText("Search failed");

View file

@ -85,7 +85,7 @@ describe("RoomStatusBar", () => {
expect(pendingEvents[2].threadRootId).toBe(rootEvent.getId());
// Filters out the non thread events
expect(pendingEvents.every(ev => ev.getId() !== event.getId())).toBe(true);
expect(pendingEvents.every((ev) => ev.getId() !== event.getId())).toBe(true);
});
});
});

View file

@ -24,7 +24,7 @@ describe("RoomStatusBarUnsentMessages", () => {
const title = "test title";
const description = "test description";
const buttonsText = "test buttons";
const buttons = <div>{ buttonsText }</div>;
const buttons = <div>{buttonsText}</div>;
beforeEach(() => {
render(

View file

@ -75,7 +75,7 @@ describe("RoomView", () => {
stores.client = cli;
stores.rightPanelStore.useUnitTestClient(cli);
jest.spyOn(VoipUserMapper.sharedInstance(), 'getVirtualRoomForRoom').mockResolvedValue(null);
jest.spyOn(VoipUserMapper.sharedInstance(), "getVirtualRoomForRoom").mockResolvedValue(null);
});
afterEach(async () => {
@ -85,7 +85,7 @@ describe("RoomView", () => {
const mountRoomView = async (): Promise<ReactWrapper> => {
if (stores.roomViewStore.getRoomId() !== room.roomId) {
const switchedRoom = new Promise<void>(resolve => {
const switchedRoom = new Promise<void>((resolve) => {
const subFn = () => {
if (stores.roomViewStore.getRoomId()) {
stores.roomViewStore.off(UPDATE_EVENT, subFn);
@ -121,7 +121,7 @@ describe("RoomView", () => {
const renderRoomView = async (): Promise<ReturnType<typeof render>> => {
if (stores.roomViewStore.getRoomId() !== room.roomId) {
const switchedRoom = new Promise<void>(resolve => {
const switchedRoom = new Promise<void>((resolve) => {
const subFn = () => {
if (stores.roomViewStore.getRoomId()) {
stores.roomViewStore.off(UPDATE_EVENT, subFn);
@ -180,13 +180,15 @@ describe("RoomView", () => {
cli.isRoomEncrypted.mockReturnValue(true);
// and fake an encryption event into the room to prompt it to re-check
room.addLiveEvents([new MatrixEvent({
type: "m.room.encryption",
sender: cli.getUserId()!,
content: {},
event_id: "someid",
room_id: room.roomId,
})]);
room.addLiveEvents([
new MatrixEvent({
type: "m.room.encryption",
sender: cli.getUserId()!,
content: {},
event_id: "someid",
room_id: room.roomId,
}),
]);
// URL previews should now be disabled
expect(roomViewInstance.state.showUrlPreview).toBe(false);
@ -200,13 +202,13 @@ describe("RoomView", () => {
expect(roomViewInstance.state.liveTimeline).not.toEqual(oldTimeline);
});
describe('with virtual rooms', () => {
describe("with virtual rooms", () => {
it("checks for a virtual room on initial load", async () => {
const { container } = await renderRoomView();
expect(VoipUserMapper.sharedInstance().getVirtualRoomForRoom).toHaveBeenCalledWith(room.roomId);
// quick check that rendered without error
expect(container.querySelector('.mx_ErrorBoundary')).toBeFalsy();
expect(container.querySelector(".mx_ErrorBoundary")).toBeFalsy();
});
it("checks for a virtual room on room event", async () => {
@ -307,7 +309,7 @@ describe("RoomView", () => {
it("clicking retry should set the room state to new dispatch a local room event", async () => {
jest.spyOn(defaultDispatcher, "dispatch");
const { getByText } = await renderRoomView();
fireEvent.click(getByText('Retry'));
fireEvent.click(getByText("Retry"));
expect(localRoom.state).toBe(LocalRoomState.NEW);
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({
action: "local_room_event",

View file

@ -53,17 +53,17 @@ describe("SpaceHierarchy", () => {
it("shows room", () => {
showRoom(client, hierarchy, "room-id2");
expect(dispatcher.dispatch).toHaveBeenCalledWith({
"action": Action.ViewRoom,
"should_peek": true,
"room_alias": "canonical-alias",
"room_id": "room-id2",
"via_servers": [],
"oob_data": {
action: Action.ViewRoom,
should_peek: true,
room_alias: "canonical-alias",
room_id: "room-id2",
via_servers: [],
oob_data: {
avatarUrl: undefined,
name: "canonical-alias",
},
"roomType": undefined,
"metricsTrigger": "RoomDirectory",
roomType: undefined,
metricsTrigger: "RoomDirectory",
});
});
});

View file

@ -14,31 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { fireEvent, render } from "@testing-library/react";
import { act } from 'react-dom/test-utils';
import { act } from "react-dom/test-utils";
import TabbedView, { Tab, TabLocation } from "../../../src/components/structures/TabbedView";
describe('<TabbedView />', () => {
const generalTab = new Tab(
'GENERAL',
'General',
'general',
<div>general</div>,
);
const labsTab = new Tab(
'LABS',
'Labs',
'labs',
<div>labs</div>,
);
const securityTab = new Tab(
'SECURITY',
'Security',
'security',
<div>security</div>,
);
describe("<TabbedView />", () => {
const generalTab = new Tab("GENERAL", "General", "general", <div>general</div>);
const labsTab = new Tab("LABS", "Labs", "labs", <div>labs</div>);
const securityTab = new Tab("SECURITY", "Security", "security", <div>security</div>);
const defaultProps = {
tabLocation: TabLocation.LEFT,
tabs: [generalTab, labsTab, securityTab],
@ -47,39 +32,39 @@ describe('<TabbedView />', () => {
const getTabTestId = (tab: Tab): string => `settings-tab-${tab.id}`;
const getActiveTab = (container: HTMLElement): Element | undefined =>
container.getElementsByClassName('mx_TabbedView_tabLabel_active')[0];
container.getElementsByClassName("mx_TabbedView_tabLabel_active")[0];
const getActiveTabBody = (container: HTMLElement): Element | undefined =>
container.getElementsByClassName('mx_TabbedView_tabPanel')[0];
container.getElementsByClassName("mx_TabbedView_tabPanel")[0];
it('renders tabs', () => {
it("renders tabs", () => {
const { container } = render(getComponent());
expect(container).toMatchSnapshot();
});
it('renders first tab as active tab when no initialTabId', () => {
it("renders first tab as active tab when no initialTabId", () => {
const { container } = render(getComponent());
expect(getActiveTab(container).textContent).toEqual(generalTab.label);
expect(getActiveTabBody(container).textContent).toEqual('general');
expect(getActiveTabBody(container).textContent).toEqual("general");
});
it('renders first tab as active tab when initialTabId is not valid', () => {
const { container } = render(getComponent({ initialTabId: 'bad-tab-id' }));
it("renders first tab as active tab when initialTabId is not valid", () => {
const { container } = render(getComponent({ initialTabId: "bad-tab-id" }));
expect(getActiveTab(container).textContent).toEqual(generalTab.label);
expect(getActiveTabBody(container).textContent).toEqual('general');
expect(getActiveTabBody(container).textContent).toEqual("general");
});
it('renders initialTabId tab as active when valid', () => {
it("renders initialTabId tab as active when valid", () => {
const { container } = render(getComponent({ initialTabId: securityTab.id }));
expect(getActiveTab(container).textContent).toEqual(securityTab.label);
expect(getActiveTabBody(container).textContent).toEqual('security');
expect(getActiveTabBody(container).textContent).toEqual("security");
});
it('renders without error when there are no tabs', () => {
it("renders without error when there are no tabs", () => {
const { container } = render(getComponent({ tabs: [] }));
expect(container).toMatchSnapshot();
});
it('sets active tab on tab click', () => {
it("sets active tab on tab click", () => {
const { container, getByTestId } = render(getComponent());
act(() => {
@ -87,10 +72,10 @@ describe('<TabbedView />', () => {
});
expect(getActiveTab(container).textContent).toEqual(securityTab.label);
expect(getActiveTabBody(container).textContent).toEqual('security');
expect(getActiveTabBody(container).textContent).toEqual("security");
});
it('calls onchange on on tab click', () => {
it("calls onchange on on tab click", () => {
const onChange = jest.fn();
const { getByTestId } = render(getComponent({ onChange }));
@ -101,7 +86,7 @@ describe('<TabbedView />', () => {
expect(onChange).toHaveBeenCalledWith(securityTab.id);
});
it('keeps same tab active when order of tabs changes', () => {
it("keeps same tab active when order of tabs changes", () => {
// start with middle tab active
const { container, rerender } = render(getComponent({ initialTabId: labsTab.id }));
@ -113,7 +98,7 @@ describe('<TabbedView />', () => {
expect(getActiveTab(container).textContent).toEqual(labsTab.label);
});
it('does not reactivate inititalTabId on rerender', () => {
it("does not reactivate inititalTabId on rerender", () => {
const { container, getByTestId, rerender } = render(getComponent());
expect(getActiveTab(container).textContent).toEqual(generalTab.label);

View file

@ -14,22 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import { mocked } from "jest-mock";
import 'focus-visible'; // to fix context menus
import "focus-visible"; // to fix context menus
import ThreadPanel, { ThreadFilterType, ThreadPanelHeader } from '../../../src/components/structures/ThreadPanel';
import { _t } from '../../../src/languageHandler';
import ResizeNotifier from '../../../src/utils/ResizeNotifier';
import { RoomPermalinkCreator } from '../../../src/utils/permalinks/Permalinks';
import { createTestClient, mkStubRoom } from '../../test-utils';
import ThreadPanel, { ThreadFilterType, ThreadPanelHeader } from "../../../src/components/structures/ThreadPanel";
import { _t } from "../../../src/languageHandler";
import ResizeNotifier from "../../../src/utils/ResizeNotifier";
import { RoomPermalinkCreator } from "../../../src/utils/permalinks/Permalinks";
import { createTestClient, mkStubRoom } from "../../test-utils";
import { shouldShowFeedback } from "../../../src/utils/Feedback";
import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
jest.mock("../../../src/utils/Feedback");
describe('ThreadPanel', () => {
describe("ThreadPanel", () => {
describe("Feedback prompt", () => {
const cli = createTestClient();
const room = mkStubRoom("!room:server", "room", cli);
@ -38,59 +38,66 @@ describe('ThreadPanel', () => {
it("should show feedback prompt if feedback is enabled", () => {
mocked(shouldShowFeedback).mockReturnValue(true);
render(<MatrixClientContext.Provider value={cli}>
<ThreadPanel
roomId="!room:server"
onClose={jest.fn()}
resizeNotifier={new ResizeNotifier()}
permalinkCreator={new RoomPermalinkCreator(room)}
/>
</MatrixClientContext.Provider>);
render(
<MatrixClientContext.Provider value={cli}>
<ThreadPanel
roomId="!room:server"
onClose={jest.fn()}
resizeNotifier={new ResizeNotifier()}
permalinkCreator={new RoomPermalinkCreator(room)}
/>
</MatrixClientContext.Provider>,
);
expect(screen.queryByText("Give feedback")).toBeTruthy();
});
it("should hide feedback prompt if feedback is disabled", () => {
mocked(shouldShowFeedback).mockReturnValue(false);
render(<MatrixClientContext.Provider value={cli}>
<ThreadPanel
roomId="!room:server"
onClose={jest.fn()}
resizeNotifier={new ResizeNotifier()}
permalinkCreator={new RoomPermalinkCreator(room)}
/>
</MatrixClientContext.Provider>);
render(
<MatrixClientContext.Provider value={cli}>
<ThreadPanel
roomId="!room:server"
onClose={jest.fn()}
resizeNotifier={new ResizeNotifier()}
permalinkCreator={new RoomPermalinkCreator(room)}
/>
</MatrixClientContext.Provider>,
);
expect(screen.queryByText("Give feedback")).toBeFalsy();
});
});
describe('Header', () => {
it('expect that All filter for ThreadPanelHeader properly renders Show: All threads', () => {
describe("Header", () => {
it("expect that All filter for ThreadPanelHeader properly renders Show: All threads", () => {
const { asFragment } = render(
<ThreadPanelHeader
empty={false}
filterOption={ThreadFilterType.All}
setFilterOption={() => undefined} />,
setFilterOption={() => undefined}
/>,
);
expect(asFragment()).toMatchSnapshot();
});
it('expect that My filter for ThreadPanelHeader properly renders Show: My threads', () => {
it("expect that My filter for ThreadPanelHeader properly renders Show: My threads", () => {
const { asFragment } = render(
<ThreadPanelHeader
empty={false}
filterOption={ThreadFilterType.My}
setFilterOption={() => undefined} />,
setFilterOption={() => undefined}
/>,
);
expect(asFragment()).toMatchSnapshot();
});
it('expect that ThreadPanelHeader properly opens a context menu when clicked on the button', () => {
it("expect that ThreadPanelHeader properly opens a context menu when clicked on the button", () => {
const { container } = render(
<ThreadPanelHeader
empty={false}
filterOption={ThreadFilterType.All}
setFilterOption={() => undefined} />,
setFilterOption={() => undefined}
/>,
);
const found = container.querySelector(".mx_ThreadPanel_dropdown");
expect(found).toBeTruthy();
@ -99,18 +106,19 @@ describe('ThreadPanel', () => {
expect(screen.queryByRole("menu")).toBeTruthy();
});
it('expect that ThreadPanelHeader has the correct option selected in the context menu', () => {
it("expect that ThreadPanelHeader has the correct option selected in the context menu", () => {
const { container } = render(
<ThreadPanelHeader
empty={false}
filterOption={ThreadFilterType.All}
setFilterOption={() => undefined} />,
setFilterOption={() => undefined}
/>,
);
fireEvent.click(container.querySelector(".mx_ThreadPanel_dropdown"));
const found = screen.queryAllByRole("menuitemradio");
expect(found).toHaveLength(2);
const foundButton = screen.queryByRole("menuitemradio", { checked: true });
expect(foundButton.textContent).toEqual(`${_t("All threads")}${_t('Shows all threads from current room')}`);
expect(foundButton.textContent).toEqual(`${_t("All threads")}${_t("Shows all threads from current room")}`);
expect(foundButton).toMatchSnapshot();
});
});

View file

@ -51,27 +51,25 @@ describe("ThreadView", () => {
const [event, setEvent] = useState(rootEvent);
changeEvent = setEvent;
return <MatrixClientContext.Provider value={mockClient}>
<RoomContext.Provider value={getRoomContext(room, {
canSendMessages: true,
})}>
<ThreadView
room={room}
onClose={jest.fn()}
mxEvent={event}
resizeNotifier={new ResizeNotifier()}
/>
</RoomContext.Provider>,
</MatrixClientContext.Provider>;
return (
<MatrixClientContext.Provider value={mockClient}>
<RoomContext.Provider
value={getRoomContext(room, {
canSendMessages: true,
})}
>
<ThreadView room={room} onClose={jest.fn()} mxEvent={event} resizeNotifier={new ResizeNotifier()} />
</RoomContext.Provider>
,
</MatrixClientContext.Provider>
);
}
async function getComponent(): Promise<RenderResult> {
const renderResult = render(
<TestThreadView />,
);
const renderResult = render(<TestThreadView />);
await waitFor(() => {
expect(() => getByTestId(renderResult.container, 'spinner')).toThrow();
expect(() => getByTestId(renderResult.container, "spinner")).toThrow();
});
return renderResult;
@ -92,9 +90,12 @@ describe("ThreadView", () => {
"event_id": rootEvent.getId(),
"is_falling_back": true,
"m.in_reply_to": {
"event_id": rootEvent.getThread().lastReply((ev: MatrixEvent) => {
return ev.isRelation(THREAD_RELATION_TYPE.name);
}).getId(),
event_id: rootEvent
.getThread()
.lastReply((ev: MatrixEvent) => {
return ev.isRelation(THREAD_RELATION_TYPE.name);
})
.getId(),
},
"rel_type": RelationType.Thread,
},
@ -133,7 +134,9 @@ describe("ThreadView", () => {
await sendMessage(container, "Hello world!");
expect(mockClient.sendMessage).toHaveBeenCalledWith(
ROOM_ID, rootEvent.getId(), expectedMessageBody(rootEvent, "Hello world!"),
ROOM_ID,
rootEvent.getId(),
expectedMessageBody(rootEvent, "Hello world!"),
);
});
@ -154,7 +157,9 @@ describe("ThreadView", () => {
await sendMessage(container, "yolo");
expect(mockClient.sendMessage).toHaveBeenCalledWith(
ROOM_ID, rootEvent2.getId(), expectedMessageBody(rootEvent2, "yolo"),
ROOM_ID,
rootEvent2.getId(),
expectedMessageBody(rootEvent2, "yolo"),
);
});

View file

@ -17,7 +17,7 @@ limitations under the License.
import { render, RenderResult } from "@testing-library/react";
// eslint-disable-next-line deprecate/import
import { mount, ReactWrapper } from "enzyme";
import { MessageEvent } from 'matrix-events-sdk';
import { MessageEvent } from "matrix-events-sdk";
import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts";
import {
EventTimelineSet,
@ -28,7 +28,7 @@ import {
Room,
RoomEvent,
TimelineWindow,
} from 'matrix-js-sdk/src/matrix';
} from "matrix-js-sdk/src/matrix";
import { EventTimeline } from "matrix-js-sdk/src/models/event-timeline";
import {
FeatureSupport,
@ -37,13 +37,13 @@ import {
ThreadEvent,
ThreadFilterType,
} from "matrix-js-sdk/src/models/thread";
import React from 'react';
import React from "react";
import TimelinePanel from '../../../src/components/structures/TimelinePanel';
import TimelinePanel from "../../../src/components/structures/TimelinePanel";
import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
import { MatrixClientPeg } from '../../../src/MatrixClientPeg';
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
import SettingsStore from "../../../src/settings/SettingsStore";
import { isCallEvent } from '../../../src/components/structures/LegacyCallEventGrouper';
import { isCallEvent } from "../../../src/components/structures/LegacyCallEventGrouper";
import { flushPromises, mkRoom, stubClient } from "../../test-utils";
const newReceipt = (eventId: string, userId: string, readTs: number, fullyReadTs: number): MatrixEvent => {
@ -81,13 +81,15 @@ const renderPanel = (room: Room, events: MatrixEvent[]): RenderResult => {
const mockEvents = (room: Room, count = 2): MatrixEvent[] => {
const events: MatrixEvent[] = [];
for (let index = 0; index < count; index++) {
events.push(new MatrixEvent({
room_id: room.roomId,
event_id: `${room.roomId}_event_${index}`,
type: EventType.RoomMessage,
user_id: "userId",
content: MessageEvent.from(`Event${index}`).serialize().content,
}));
events.push(
new MatrixEvent({
room_id: room.roomId,
event_id: `${room.roomId}_event_${index}`,
type: EventType.RoomMessage,
user_id: "userId",
content: MessageEvent.from(`Event${index}`).serialize().content,
}),
);
}
return events;
@ -100,13 +102,13 @@ const setupTestData = (): [MatrixClient, Room, MatrixEvent[]] => {
return [client, room, events];
};
describe('TimelinePanel', () => {
describe("TimelinePanel", () => {
beforeEach(() => {
stubClient();
});
describe('read receipts and markers', () => {
it('should forget the read marker when asked to', () => {
describe("read receipts and markers", () => {
it("should forget the read marker when asked to", () => {
const cli = MatrixClientPeg.get();
const readMarkersSent: string[] = [];
@ -131,22 +133,19 @@ describe('TimelinePanel', () => {
const roomId = "#room:example.com";
const userId = cli.credentials.userId!;
const room = new Room(
roomId,
cli,
userId,
{ pendingEventOrdering: PendingEventOrdering.Detached },
);
const room = new Room(roomId, cli, userId, { pendingEventOrdering: PendingEventOrdering.Detached });
// Create a TimelinePanel with ev0 already present
const timelineSet = new EventTimelineSet(room, {});
timelineSet.addLiveEvent(ev0);
const component: ReactWrapper<TimelinePanel> = mount(<TimelinePanel
timelineSet={timelineSet}
manageReadMarkers={true}
manageReadReceipts={true}
eventId={ev0.getId()}
/>);
const component: ReactWrapper<TimelinePanel> = mount(
<TimelinePanel
timelineSet={timelineSet}
manageReadMarkers={true}
manageReadReceipts={true}
eventId={ev0.getId()}
/>,
);
const timelinePanel = component.instance() as TimelinePanel;
// An event arrived, and we read it
@ -208,8 +207,8 @@ describe('TimelinePanel', () => {
expect(props.onEventScrolledIntoView).toHaveBeenCalledWith(events[1].getId());
});
describe('onRoomTimeline', () => {
it('ignores events for other timelines', () => {
describe("onRoomTimeline", () => {
it("ignores events for other timelines", () => {
const [client, room, events] = setupTestData();
const otherTimelineSet = { room: room as Room } as EventTimelineSet;
@ -220,7 +219,7 @@ describe('TimelinePanel', () => {
onEventScrolledIntoView: jest.fn(),
};
const paginateSpy = jest.spyOn(TimelineWindow.prototype, 'paginate').mockClear();
const paginateSpy = jest.spyOn(TimelineWindow.prototype, "paginate").mockClear();
render(<TimelinePanel {...props} />);
@ -231,12 +230,12 @@ describe('TimelinePanel', () => {
expect(paginateSpy).not.toHaveBeenCalled();
});
it('ignores timeline updates without a live event', () => {
it("ignores timeline updates without a live event", () => {
const [client, room, events] = setupTestData();
const props = getProps(room, events);
const paginateSpy = jest.spyOn(TimelineWindow.prototype, 'paginate').mockClear();
const paginateSpy = jest.spyOn(TimelineWindow.prototype, "paginate").mockClear();
render(<TimelinePanel {...props} />);
@ -247,12 +246,12 @@ describe('TimelinePanel', () => {
expect(paginateSpy).not.toHaveBeenCalled();
});
it('ignores timeline where toStartOfTimeline is true', () => {
it("ignores timeline where toStartOfTimeline is true", () => {
const [client, room, events] = setupTestData();
const props = getProps(room, events);
const paginateSpy = jest.spyOn(TimelineWindow.prototype, 'paginate').mockClear();
const paginateSpy = jest.spyOn(TimelineWindow.prototype, "paginate").mockClear();
render(<TimelinePanel {...props} />);
@ -264,12 +263,12 @@ describe('TimelinePanel', () => {
expect(paginateSpy).not.toHaveBeenCalled();
});
it('advances the timeline window', () => {
it("advances the timeline window", () => {
const [client, room, events] = setupTestData();
const props = getProps(room, events);
const paginateSpy = jest.spyOn(TimelineWindow.prototype, 'paginate').mockClear();
const paginateSpy = jest.spyOn(TimelineWindow.prototype, "paginate").mockClear();
render(<TimelinePanel {...props} />);
@ -280,7 +279,7 @@ describe('TimelinePanel', () => {
expect(paginateSpy).toHaveBeenCalledWith(EventTimeline.FORWARDS, 1, false);
});
it('advances the overlay timeline window', async () => {
it("advances the overlay timeline window", async () => {
const [client, room, events] = setupTestData();
const virtualRoom = mkRoom(client, "virtualRoomId");
@ -292,7 +291,7 @@ describe('TimelinePanel', () => {
overlayTimelineSet,
};
const paginateSpy = jest.spyOn(TimelineWindow.prototype, 'paginate').mockClear();
const paginateSpy = jest.spyOn(TimelineWindow.prototype, "paginate").mockClear();
render(<TimelinePanel {...props} />);
@ -306,25 +305,21 @@ describe('TimelinePanel', () => {
});
});
describe('with overlayTimeline', () => {
it('renders merged timeline', () => {
describe("with overlayTimeline", () => {
it("renders merged timeline", () => {
const [client, room, events] = setupTestData();
const virtualRoom = mkRoom(client, "virtualRoomId");
const virtualCallInvite = new MatrixEvent({
type: 'm.call.invite',
type: "m.call.invite",
room_id: virtualRoom.roomId,
event_id: `virtualCallEvent1`,
});
const virtualCallMetaEvent = new MatrixEvent({
type: 'org.matrix.call.sdp_stream_metadata_changed',
type: "org.matrix.call.sdp_stream_metadata_changed",
room_id: virtualRoom.roomId,
event_id: `virtualCallEvent2`,
});
const virtualEvents = [
virtualCallInvite,
...mockEvents(virtualRoom),
virtualCallMetaEvent,
];
const virtualEvents = [virtualCallInvite, ...mockEvents(virtualRoom), virtualCallMetaEvent];
const { timelineSet: overlayTimelineSet } = getProps(virtualRoom, virtualEvents);
const props = {
@ -335,8 +330,8 @@ describe('TimelinePanel', () => {
const { container } = render(<TimelinePanel {...props} />);
const eventTiles = container.querySelectorAll('.mx_EventTile');
const eventTileIds = [...eventTiles].map(tileElement => tileElement.getAttribute('data-event-id'));
const eventTiles = container.querySelectorAll(".mx_EventTile");
const eventTileIds = [...eventTiles].map((tileElement) => tileElement.getAttribute("data-event-id"));
expect(eventTileIds).toEqual([
// main timeline events are included
events[1].getId(),
@ -368,16 +363,22 @@ describe('TimelinePanel', () => {
});
room = new Room("roomId", client, "userId");
allThreads = new EventTimelineSet(room, {
pendingEvents: false,
}, undefined, undefined, ThreadFilterType.All);
allThreads = new EventTimelineSet(
room,
{
pendingEvents: false,
},
undefined,
undefined,
ThreadFilterType.All,
);
const timeline = new EventTimeline(allThreads);
allThreads.getLiveTimeline = () => timeline;
allThreads.getTimelineForEvent = () => timeline;
reply1 = new MatrixEvent({
room_id: room.roomId,
event_id: 'event_reply_1',
event_id: "event_reply_1",
type: EventType.RoomMessage,
user_id: "userId",
content: MessageEvent.from(`ReplyEvent1`).serialize().content,
@ -385,7 +386,7 @@ describe('TimelinePanel', () => {
reply2 = new MatrixEvent({
room_id: room.roomId,
event_id: 'event_reply_2',
event_id: "event_reply_2",
type: EventType.RoomMessage,
user_id: "userId",
content: MessageEvent.from(`ReplyEvent2`).serialize().content,
@ -393,7 +394,7 @@ describe('TimelinePanel', () => {
root = new MatrixEvent({
room_id: room.roomId,
event_id: 'event_root_1',
event_id: "event_root_1",
type: EventType.RoomMessage,
user_id: "userId",
content: MessageEvent.from(`RootEvent`).serialize().content,
@ -410,13 +411,13 @@ describe('TimelinePanel', () => {
roomId === room.roomId ? eventMap[eventId]?.event : {};
});
it('updates thread previews', async () => {
it("updates thread previews", async () => {
root.setUnsigned({
"m.relations": {
[THREAD_RELATION_TYPE.name]: {
"latest_event": reply1.event,
"count": 1,
"current_user_participated": true,
latest_event: reply1.event,
count: 1,
current_user_participated: true,
},
},
});
@ -432,11 +433,7 @@ describe('TimelinePanel', () => {
const dom = render(
<MatrixClientContext.Provider value={client}>
<TimelinePanel
timelineSet={allThreads}
manageReadReceipts
sendReadReceiptOnLoad
/>
<TimelinePanel timelineSet={allThreads} manageReadReceipts sendReadReceiptOnLoad />
</MatrixClientContext.Provider>,
);
await dom.findByText("RootEvent");
@ -446,9 +443,9 @@ describe('TimelinePanel', () => {
root.setUnsigned({
"m.relations": {
[THREAD_RELATION_TYPE.name]: {
"latest_event": reply2.event,
"count": 2,
"current_user_participated": true,
latest_event: reply2.event,
count: 2,
current_user_participated: true,
},
},
});
@ -460,13 +457,13 @@ describe('TimelinePanel', () => {
expect(replyToEvent).toHaveBeenCalled();
});
it('ignores thread updates for unknown threads', async () => {
it("ignores thread updates for unknown threads", async () => {
root.setUnsigned({
"m.relations": {
[THREAD_RELATION_TYPE.name]: {
"latest_event": reply1.event,
"count": 1,
"current_user_participated": true,
latest_event: reply1.event,
count: 1,
current_user_participated: true,
},
},
});
@ -499,11 +496,7 @@ describe('TimelinePanel', () => {
const dom = render(
<MatrixClientContext.Provider value={client}>
<TimelinePanel
timelineSet={allThreads}
manageReadReceipts
sendReadReceiptOnLoad
/>
<TimelinePanel timelineSet={allThreads} manageReadReceipts sendReadReceiptOnLoad />
</MatrixClientContext.Provider>,
);
await dom.findByText("RootEvent");

View file

@ -42,10 +42,7 @@ describe("<UserMenu>", () => {
client.getUserId() || "",
client.getDeviceId() || "",
);
voiceBroadcastRecording = new VoiceBroadcastRecording(
voiceBroadcastInfoEvent,
client,
);
voiceBroadcastRecording = new VoiceBroadcastRecording(voiceBroadcastInfoEvent, client);
});
beforeEach(() => {

View file

@ -100,11 +100,9 @@ describe("<ForgotPassword>", () => {
describe("when starting a password reset flow", () => {
beforeEach(() => {
renderResult = render(<ForgotPassword
serverConfig={serverConfig}
onComplete={onComplete}
onLoginClick={onLoginClick}
/>);
renderResult = render(
<ForgotPassword serverConfig={serverConfig} onComplete={onComplete} onLoginClick={onLoginClick} />,
);
});
it("should show the email input and mention the homeserver", () => {
@ -115,11 +113,9 @@ describe("<ForgotPassword>", () => {
describe("and updating the server config", () => {
beforeEach(() => {
serverConfig.hsName = "example2.com";
renderResult.rerender(<ForgotPassword
serverConfig={serverConfig}
onComplete={onComplete}
onLoginClick={onLoginClick}
/>);
renderResult.rerender(
<ForgotPassword serverConfig={serverConfig} onComplete={onComplete} onLoginClick={onLoginClick} />,
);
});
it("should show the new homeserver server name", () => {
@ -171,10 +167,12 @@ describe("<ForgotPassword>", () => {
});
it("should show an info about that", () => {
expect(screen.getByText(
"Cannot reach homeserver: "
+ "Ensure you have a stable internet connection, or get in touch with the server admin",
)).toBeInTheDocument();
expect(
screen.getByText(
"Cannot reach homeserver: " +
"Ensure you have a stable internet connection, or get in touch with the server admin",
),
).toBeInTheDocument();
});
});

View file

@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { fireEvent, render, screen, waitForElementToBeRemoved } from "@testing-library/react";
import { mocked, MockedObject } from 'jest-mock';
import { mocked, MockedObject } from "jest-mock";
import { createClient, MatrixClient } from "matrix-js-sdk/src/matrix";
import fetchMock from "fetch-mock-jest";
import SdkConfig from '../../../../src/SdkConfig';
import SdkConfig from "../../../../src/SdkConfig";
import { mkServerConfig, mockPlatformPeg, unmockPlatformPeg } from "../../../test-utils";
import Login from "../../../../src/components/structures/auth/Login";
import BasePlatform from "../../../../src/BasePlatform";
@ -29,7 +29,7 @@ jest.mock("matrix-js-sdk/src/matrix");
jest.useRealTimers();
describe('Login', function() {
describe("Login", function () {
let platform: MockedObject<BasePlatform>;
const mockClient = mocked({
@ -37,14 +37,14 @@ describe('Login', function() {
loginFlows: jest.fn(),
} as unknown as MatrixClient);
beforeEach(function() {
beforeEach(function () {
SdkConfig.put({
brand: "test-brand",
disable_custom_urls: true,
});
mockClient.login.mockClear().mockResolvedValue({});
mockClient.loginFlows.mockClear().mockResolvedValue({ flows: [{ type: "m.login.password" }] });
mocked(createClient).mockImplementation(opts => {
mocked(createClient).mockImplementation((opts) => {
mockClient.idBaseUrl = opts.idBaseUrl;
mockClient.baseUrl = opts.baseUrl;
return mockClient;
@ -58,26 +58,28 @@ describe('Login', function() {
});
});
afterEach(function() {
afterEach(function () {
fetchMock.restore();
SdkConfig.unset(); // we touch the config, so clean up
unmockPlatformPeg();
});
function getRawComponent(hsUrl = "https://matrix.org", isUrl = "https://vector.im") {
return <Login
serverConfig={mkServerConfig(hsUrl, isUrl)}
onLoggedIn={() => { }}
onRegisterClick={() => { }}
onServerConfigChange={() => { }}
/>;
return (
<Login
serverConfig={mkServerConfig(hsUrl, isUrl)}
onLoggedIn={() => {}}
onRegisterClick={() => {}}
onServerConfigChange={() => {}}
/>
);
}
function getComponent(hsUrl?: string, isUrl?: string) {
return render(getRawComponent(hsUrl, isUrl));
}
it('should show form with change server link', async () => {
it("should show form with change server link", async () => {
SdkConfig.put({
brand: "test-brand",
disable_custom_urls: false,
@ -90,7 +92,7 @@ describe('Login', function() {
expect(container.querySelector(".mx_ServerPicker_change")).toBeTruthy();
});
it('should show form without change server link when custom URLs disabled', async () => {
it("should show form without change server link when custom URLs disabled", async () => {
const { container } = getComponent();
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading..."));
@ -122,19 +124,25 @@ describe('Login', function() {
it("should show multiple SSO buttons if multiple identity_providers are available", async () => {
mockClient.loginFlows.mockResolvedValue({
flows: [{
"type": "m.login.sso",
"identity_providers": [{
id: "a",
name: "Provider 1",
}, {
id: "b",
name: "Provider 2",
}, {
id: "c",
name: "Provider 3",
}],
}],
flows: [
{
type: "m.login.sso",
identity_providers: [
{
id: "a",
name: "Provider 1",
},
{
id: "b",
name: "Provider 2",
},
{
id: "c",
name: "Provider 3",
},
],
},
],
});
const { container } = getComponent();
@ -146,9 +154,11 @@ describe('Login', function() {
it("should show single SSO button if identity_providers is null", async () => {
mockClient.loginFlows.mockResolvedValue({
flows: [{
"type": "m.login.sso",
}],
flows: [
{
type: "m.login.sso",
},
],
});
const { container } = getComponent();
@ -160,9 +170,11 @@ describe('Login', function() {
it("should handle serverConfig updates correctly", async () => {
mockClient.loginFlows.mockResolvedValue({
flows: [{
"type": "m.login.sso",
}],
flows: [
{
type: "m.login.sso",
},
],
});
const { container, rerender } = render(getRawComponent());

View file

@ -15,37 +15,42 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { fireEvent, render, screen, waitForElementToBeRemoved } from "@testing-library/react";
import { createClient, MatrixClient } from 'matrix-js-sdk/src/matrix';
import { MatrixError } from 'matrix-js-sdk/src/http-api/errors';
import { mocked } from 'jest-mock';
import { createClient, MatrixClient } from "matrix-js-sdk/src/matrix";
import { MatrixError } from "matrix-js-sdk/src/http-api/errors";
import { mocked } from "jest-mock";
import fetchMock from "fetch-mock-jest";
import SdkConfig, { DEFAULTS } from '../../../../src/SdkConfig';
import SdkConfig, { DEFAULTS } from "../../../../src/SdkConfig";
import { mkServerConfig, mockPlatformPeg, unmockPlatformPeg } from "../../../test-utils";
import Registration from "../../../../src/components/structures/auth/Registration";
jest.mock('matrix-js-sdk/src/matrix');
jest.mock("matrix-js-sdk/src/matrix");
jest.useFakeTimers();
describe('Registration', function() {
describe("Registration", function () {
const registerRequest = jest.fn();
const mockClient = mocked({
registerRequest,
loginFlows: jest.fn(),
} as unknown as MatrixClient);
beforeEach(function() {
beforeEach(function () {
SdkConfig.put({
...DEFAULTS,
disable_custom_urls: true,
});
mockClient.registerRequest.mockRejectedValueOnce(new MatrixError({
flows: [{ stages: [] }],
}, 401));
mockClient.registerRequest.mockRejectedValueOnce(
new MatrixError(
{
flows: [{ stages: [] }],
},
401,
),
);
mockClient.loginFlows.mockClear().mockResolvedValue({ flows: [{ type: "m.login.password" }] });
mocked(createClient).mockImplementation(opts => {
mocked(createClient).mockImplementation((opts) => {
mockClient.idBaseUrl = opts.idBaseUrl;
mockClient.baseUrl = opts.baseUrl;
return mockClient;
@ -59,14 +64,14 @@ describe('Registration', function() {
});
});
afterEach(function() {
afterEach(function () {
fetchMock.restore();
SdkConfig.unset(); // we touch the config, so clean up
unmockPlatformPeg();
});
const defaultProps = {
defaultDeviceDisplayName: 'test-device-display-name',
defaultDeviceDisplayName: "test-device-display-name",
makeRegistrationUrl: jest.fn(),
onLoggedIn: jest.fn(),
onLoginClick: jest.fn(),
@ -74,22 +79,19 @@ describe('Registration', function() {
};
function getRawComponent(hsUrl = "https://matrix.org", isUrl = "https://vector.im") {
return <Registration
{...defaultProps}
serverConfig={mkServerConfig(hsUrl, isUrl)}
/>;
return <Registration {...defaultProps} serverConfig={mkServerConfig(hsUrl, isUrl)} />;
}
function getComponent(hsUrl?: string, isUrl?: string) {
return render(getRawComponent(hsUrl, isUrl));
}
it('should show server picker', async function() {
it("should show server picker", async function () {
const { container } = getComponent();
expect(container.querySelector(".mx_ServerPicker")).toBeTruthy();
});
it('should show form when custom URLs disabled', async function() {
it("should show form when custom URLs disabled", async function () {
const { container } = getComponent();
await waitForElementToBeRemoved(() => screen.queryAllByLabelText("Loading..."));
expect(container.querySelector("form")).toBeTruthy();
@ -106,9 +108,11 @@ describe('Registration', function() {
it("should handle serverConfig updates correctly", async () => {
mockClient.loginFlows.mockResolvedValue({
flows: [{
"type": "m.login.sso",
}],
flows: [
{
type: "m.login.sso",
},
],
});
const { container, rerender } = render(getRawComponent());

View file

@ -21,11 +21,15 @@ describe("Validation", () => {
const handler = withValidation({
rules: [],
});
return expect(handler({
value: "value",
focused: true,
})).resolves.toEqual(expect.objectContaining({
valid: true,
}));
return expect(
handler({
value: "value",
focused: true,
}),
).resolves.toEqual(
expect.objectContaining({
valid: true,
}),
);
});
});

View file

@ -14,28 +14,28 @@ 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 { mocked } from 'jest-mock';
import { logger } from 'matrix-js-sdk/src/logger';
import { act } from 'react-dom/test-utils';
import { mount } from "enzyme";
import { mocked } from "jest-mock";
import { logger } from "matrix-js-sdk/src/logger";
import { act } from "react-dom/test-utils";
import RecordingPlayback, { PlaybackLayout } from '../../../../src/components/views/audio_messages/RecordingPlayback';
import { Playback } from '../../../../src/audio/Playback';
import RoomContext, { TimelineRenderingType } from '../../../../src/contexts/RoomContext';
import { createAudioContext } from '../../../../src/audio/compat';
import { findByTestId, flushPromises } from '../../../test-utils';
import PlaybackWaveform from '../../../../src/components/views/audio_messages/PlaybackWaveform';
import RecordingPlayback, { PlaybackLayout } from "../../../../src/components/views/audio_messages/RecordingPlayback";
import { Playback } from "../../../../src/audio/Playback";
import RoomContext, { TimelineRenderingType } from "../../../../src/contexts/RoomContext";
import { createAudioContext } from "../../../../src/audio/compat";
import { findByTestId, flushPromises } from "../../../test-utils";
import PlaybackWaveform from "../../../../src/components/views/audio_messages/PlaybackWaveform";
import SeekBar from "../../../../src/components/views/audio_messages/SeekBar";
import PlaybackClock from "../../../../src/components/views/audio_messages/PlaybackClock";
jest.mock('../../../../src/audio/compat', () => ({
jest.mock("../../../../src/audio/compat", () => ({
createAudioContext: jest.fn(),
decodeOgg: jest.fn().mockResolvedValue({}),
}));
describe('<RecordingPlayback />', () => {
describe("<RecordingPlayback />", () => {
const mockAudioBufferSourceNode = {
addEventListener: jest.fn(),
connect: jest.fn(),
@ -57,7 +57,7 @@ describe('<RecordingPlayback />', () => {
const mockChannelData = new Float32Array();
const defaultRoom = { roomId: '!room:server.org', timelineRenderingType: TimelineRenderingType.File };
const defaultRoom = { roomId: "!room:server.org", timelineRenderingType: TimelineRenderingType.File };
const getComponent = (props: React.ComponentProps<typeof RecordingPlayback>, room = defaultRoom) =>
mount(<RecordingPlayback {...props} />, {
wrappingComponent: RoomContext.Provider,
@ -65,29 +65,27 @@ describe('<RecordingPlayback />', () => {
});
beforeEach(() => {
jest.spyOn(logger, 'error').mockRestore();
jest.spyOn(logger, "error").mockRestore();
mockAudioBuffer.getChannelData.mockClear().mockReturnValue(mockChannelData);
mockAudioContext.decodeAudioData.mockReset().mockImplementation(
(_b, callback) => callback(mockAudioBuffer),
);
mockAudioContext.decodeAudioData.mockReset().mockImplementation((_b, callback) => callback(mockAudioBuffer));
mocked(createAudioContext).mockReturnValue(mockAudioContext as unknown as AudioContext);
});
const getPlayButton = component => findByTestId(component, 'play-pause-button').at(0);
const getPlayButton = (component) => findByTestId(component, "play-pause-button").at(0);
it('renders recording playback', () => {
it("renders recording playback", () => {
const playback = new Playback(new ArrayBuffer(8));
const component = getComponent({ playback });
expect(component).toBeTruthy();
});
it('disables play button while playback is decoding', async () => {
it("disables play button while playback is decoding", async () => {
const playback = new Playback(new ArrayBuffer(8));
const component = getComponent({ playback });
expect(getPlayButton(component).props().disabled).toBeTruthy();
});
it('enables play button when playback is finished decoding', async () => {
it("enables play button when playback is finished decoding", async () => {
const playback = new Playback(new ArrayBuffer(8));
const component = getComponent({ playback });
await flushPromises();
@ -95,43 +93,41 @@ describe('<RecordingPlayback />', () => {
expect(getPlayButton(component).props().disabled).toBeFalsy();
});
it('displays error when playback decoding fails', async () => {
it("displays error when playback decoding fails", async () => {
// stub logger to keep console clean from expected error
jest.spyOn(logger, 'error').mockReturnValue(undefined);
jest.spyOn(logger, 'warn').mockReturnValue(undefined);
mockAudioContext.decodeAudioData.mockImplementation(
(_b, _cb, error) => error(new Error('oh no')),
);
jest.spyOn(logger, "error").mockReturnValue(undefined);
jest.spyOn(logger, "warn").mockReturnValue(undefined);
mockAudioContext.decodeAudioData.mockImplementation((_b, _cb, error) => error(new Error("oh no")));
const playback = new Playback(new ArrayBuffer(8));
const component = getComponent({ playback });
await flushPromises();
expect(component.find('.text-warning').length).toBeFalsy();
expect(component.find(".text-warning").length).toBeFalsy();
});
it('displays pre-prepared playback with correct playback phase', async () => {
it("displays pre-prepared playback with correct playback phase", async () => {
const playback = new Playback(new ArrayBuffer(8));
await playback.prepare();
const component = getComponent({ playback });
// playback already decoded, button is not disabled
expect(getPlayButton(component).props().disabled).toBeFalsy();
expect(component.find('.text-warning').length).toBeFalsy();
expect(component.find(".text-warning").length).toBeFalsy();
});
it('toggles playback on play pause button click', async () => {
it("toggles playback on play pause button click", async () => {
const playback = new Playback(new ArrayBuffer(8));
jest.spyOn(playback, 'toggle').mockResolvedValue(undefined);
jest.spyOn(playback, "toggle").mockResolvedValue(undefined);
await playback.prepare();
const component = getComponent({ playback });
act(() => {
getPlayButton(component).simulate('click');
getPlayButton(component).simulate("click");
});
expect(playback.toggle).toHaveBeenCalled();
});
describe('Composer Layout', () => {
it('should have a waveform, no seek bar, and clock', () => {
describe("Composer Layout", () => {
it("should have a waveform, no seek bar, and clock", () => {
const playback = new Playback(new ArrayBuffer(8));
const component = getComponent({ playback, layout: PlaybackLayout.Composer });
@ -141,8 +137,8 @@ describe('<RecordingPlayback />', () => {
});
});
describe('Timeline Layout', () => {
it('should have a waveform, a seek bar, and clock', () => {
describe("Timeline Layout", () => {
it("should have a waveform, a seek bar, and clock", () => {
const playback = new Playback(new ArrayBuffer(8));
const component = getComponent({ playback, layout: PlaybackLayout.Timeline });
@ -151,7 +147,7 @@ describe('<RecordingPlayback />', () => {
expect(component.find(SeekBar).length).toBeTruthy();
});
it('should be the default', () => {
it("should be the default", () => {
const playback = new Playback(new ArrayBuffer(8));
const component = getComponent({ playback }); // no layout set for test

View file

@ -30,9 +30,10 @@ describe("SeekBar", () => {
beforeEach(() => {
seekBarRef = createRef();
jest.spyOn(window, "requestAnimationFrame").mockImplementation(
(callback: FrameRequestCallback) => { frameRequestCallback = callback; return 0; },
);
jest.spyOn(window, "requestAnimationFrame").mockImplementation((callback: FrameRequestCallback) => {
frameRequestCallback = callback;
return 0;
});
playback = createTestPlayback();
});

View file

@ -36,14 +36,11 @@ describe("MemberAvatar", () => {
let member: RoomMember;
function getComponent(props) {
return <RoomContext.Provider value={getRoomContext(room, {})}>
<MemberAvatar
member={null}
width={35}
height={35}
{...props}
/>
</RoomContext.Provider>;
return (
<RoomContext.Provider value={getRoomContext(room, {})}>
<MemberAvatar member={null} width={35} height={35} {...props} />
</RoomContext.Provider>
);
}
beforeEach(() => {

View file

@ -14,32 +14,28 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { fireEvent, render } from "@testing-library/react";
import {
Beacon,
RoomMember,
MatrixEvent,
} from 'matrix-js-sdk/src/matrix';
import { LocationAssetType } from 'matrix-js-sdk/src/@types/location';
import { act } from 'react-dom/test-utils';
import { Beacon, RoomMember, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { LocationAssetType } from "matrix-js-sdk/src/@types/location";
import { act } from "react-dom/test-utils";
import BeaconListItem from '../../../../src/components/views/beacon/BeaconListItem';
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
import BeaconListItem from "../../../../src/components/views/beacon/BeaconListItem";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import {
getMockClientWithEventEmitter,
makeBeaconEvent,
makeBeaconInfoEvent,
makeRoomWithBeacons,
} from '../../../test-utils';
} from "../../../test-utils";
describe('<BeaconListItem />', () => {
describe("<BeaconListItem />", () => {
// 14.03.2022 16:15
const now = 1647270879403;
// go back in time to create beacons and locations in the past
jest.spyOn(global.Date, 'now').mockReturnValue(now - 600000);
const roomId = '!room:server';
const aliceId = '@alice:server';
jest.spyOn(global.Date, "now").mockReturnValue(now - 600000);
const roomId = "!room:server";
const aliceId = "@alice:server";
const mockClient = getMockClientWithEventEmitter({
getUserId: jest.fn().mockReturnValue(aliceId),
@ -47,36 +43,41 @@ describe('<BeaconListItem />', () => {
isGuest: jest.fn().mockReturnValue(false),
});
const aliceBeaconEvent = makeBeaconInfoEvent(aliceId,
roomId,
{ isLive: true },
'$alice-room1-1',
);
const alicePinBeaconEvent = makeBeaconInfoEvent(aliceId,
const aliceBeaconEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: true }, "$alice-room1-1");
const alicePinBeaconEvent = makeBeaconInfoEvent(
aliceId,
roomId,
{ isLive: true, assetType: LocationAssetType.Pin, description: "Alice's car" },
'$alice-room1-1',
"$alice-room1-1",
);
const pinBeaconWithoutDescription = makeBeaconInfoEvent(aliceId,
const pinBeaconWithoutDescription = makeBeaconInfoEvent(
aliceId,
roomId,
{ isLive: true, assetType: LocationAssetType.Pin },
'$alice-room1-1',
"$alice-room1-1",
);
const aliceLocation1 = makeBeaconEvent(
aliceId, { beaconInfoId: aliceBeaconEvent.getId(), geoUri: 'geo:51,41', timestamp: now - 1 },
);
const aliceLocation2 = makeBeaconEvent(
aliceId, { beaconInfoId: aliceBeaconEvent.getId(), geoUri: 'geo:52,42', timestamp: now - 500000 },
);
const aliceLocation1 = makeBeaconEvent(aliceId, {
beaconInfoId: aliceBeaconEvent.getId(),
geoUri: "geo:51,41",
timestamp: now - 1,
});
const aliceLocation2 = makeBeaconEvent(aliceId, {
beaconInfoId: aliceBeaconEvent.getId(),
geoUri: "geo:52,42",
timestamp: now - 500000,
});
const defaultProps = {
beacon: new Beacon(aliceBeaconEvent),
};
const getComponent = (props = {}) => render(<MatrixClientContext.Provider value={mockClient}>
<BeaconListItem {...defaultProps} {...props} />
</MatrixClientContext.Provider>);
const getComponent = (props = {}) =>
render(
<MatrixClientContext.Provider value={mockClient}>
<BeaconListItem {...defaultProps} {...props} />
</MatrixClientContext.Provider>,
);
const setupRoomWithBeacons = (beaconInfoEvents: MatrixEvent[], locationEvents?: MatrixEvent[]): Beacon[] => {
const beacons = makeRoomWithBeacons(roomId, mockClient, beaconInfoEvents, locationEvents);
@ -84,102 +85,101 @@ describe('<BeaconListItem />', () => {
const member = new RoomMember(roomId, aliceId);
member.name = `Alice`;
const room = mockClient.getRoom(roomId);
jest.spyOn(room, 'getMember').mockReturnValue(member);
jest.spyOn(room, "getMember").mockReturnValue(member);
return beacons;
};
beforeEach(() => {
jest.clearAllMocks();
jest.spyOn(Date, 'now').mockReturnValue(now);
jest.spyOn(Date, "now").mockReturnValue(now);
});
it('renders null when beacon is not live', () => {
const notLiveBeacon = makeBeaconInfoEvent(aliceId,
roomId,
{ isLive: false },
);
it("renders null when beacon is not live", () => {
const notLiveBeacon = makeBeaconInfoEvent(aliceId, roomId, { isLive: false });
const [beacon] = setupRoomWithBeacons([notLiveBeacon]);
const { container } = getComponent({ beacon });
expect(container.innerHTML).toBeFalsy();
});
it('renders null when beacon has no location', () => {
it("renders null when beacon has no location", () => {
const [beacon] = setupRoomWithBeacons([aliceBeaconEvent]);
const { container } = getComponent({ beacon });
expect(container.innerHTML).toBeFalsy();
});
describe('when a beacon is live and has locations', () => {
it('renders beacon info', () => {
describe("when a beacon is live and has locations", () => {
it("renders beacon info", () => {
const [beacon] = setupRoomWithBeacons([alicePinBeaconEvent], [aliceLocation1]);
const { asFragment } = getComponent({ beacon });
expect(asFragment()).toMatchSnapshot();
});
describe('non-self beacons', () => {
it('uses beacon description as beacon name', () => {
describe("non-self beacons", () => {
it("uses beacon description as beacon name", () => {
const [beacon] = setupRoomWithBeacons([alicePinBeaconEvent], [aliceLocation1]);
const { container } = getComponent({ beacon });
expect(container.querySelector('.mx_BeaconStatus_label')).toHaveTextContent("Alice's car");
expect(container.querySelector(".mx_BeaconStatus_label")).toHaveTextContent("Alice's car");
});
it('uses beacon owner mxid as beacon name for a beacon without description', () => {
it("uses beacon owner mxid as beacon name for a beacon without description", () => {
const [beacon] = setupRoomWithBeacons([pinBeaconWithoutDescription], [aliceLocation1]);
const { container } = getComponent({ beacon });
expect(container.querySelector('.mx_BeaconStatus_label')).toHaveTextContent(aliceId);
expect(container.querySelector(".mx_BeaconStatus_label")).toHaveTextContent(aliceId);
});
it('renders location icon', () => {
it("renders location icon", () => {
const [beacon] = setupRoomWithBeacons([alicePinBeaconEvent], [aliceLocation1]);
const { container } = getComponent({ beacon });
expect(container.querySelector('.mx_StyledLiveBeaconIcon')).toBeTruthy();
expect(container.querySelector(".mx_StyledLiveBeaconIcon")).toBeTruthy();
});
});
describe('self locations', () => {
it('renders beacon owner avatar', () => {
describe("self locations", () => {
it("renders beacon owner avatar", () => {
const [beacon] = setupRoomWithBeacons([aliceBeaconEvent], [aliceLocation1]);
const { container } = getComponent({ beacon });
expect(container.querySelector('.mx_BaseAvatar')).toBeTruthy();
expect(container.querySelector(".mx_BaseAvatar")).toBeTruthy();
});
it('uses beacon owner name as beacon name', () => {
it("uses beacon owner name as beacon name", () => {
const [beacon] = setupRoomWithBeacons([aliceBeaconEvent], [aliceLocation1]);
const { container } = getComponent({ beacon });
expect(container.querySelector('.mx_BeaconStatus_label')).toHaveTextContent("Alice");
expect(container.querySelector(".mx_BeaconStatus_label")).toHaveTextContent("Alice");
});
});
describe('on location updates', () => {
it('updates last updated time on location updated', () => {
describe("on location updates", () => {
it("updates last updated time on location updated", () => {
const [beacon] = setupRoomWithBeacons([aliceBeaconEvent], [aliceLocation2]);
const { container } = getComponent({ beacon });
expect(container.querySelector('.mx_BeaconListItem_lastUpdated'))
.toHaveTextContent('Updated 9 minutes ago');
expect(container.querySelector(".mx_BeaconListItem_lastUpdated")).toHaveTextContent(
"Updated 9 minutes ago",
);
// update to a newer location
act(() => {
beacon.addLocations([aliceLocation1]);
});
expect(container.querySelector('.mx_BeaconListItem_lastUpdated'))
.toHaveTextContent('Updated a few seconds ago');
expect(container.querySelector(".mx_BeaconListItem_lastUpdated")).toHaveTextContent(
"Updated a few seconds ago",
);
});
});
describe('interactions', () => {
it('does not call onClick handler when clicking share button', () => {
describe("interactions", () => {
it("does not call onClick handler when clicking share button", () => {
const [beacon] = setupRoomWithBeacons([alicePinBeaconEvent], [aliceLocation1]);
const onClick = jest.fn();
const { getByTestId } = getComponent({ beacon, onClick });
fireEvent.click(getByTestId('open-location-in-osm'));
fireEvent.click(getByTestId("open-location-in-osm"));
expect(onClick).not.toHaveBeenCalled();
});
it('calls onClick handler when clicking outside of share buttons', () => {
it("calls onClick handler when clicking outside of share buttons", () => {
const [beacon] = setupRoomWithBeacons([alicePinBeaconEvent], [aliceLocation1]);
const onClick = jest.fn();
const { container } = getComponent({ beacon, onClick });

View file

@ -14,36 +14,30 @@ 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 maplibregl from 'maplibre-gl';
import { act } from 'react-dom/test-utils';
import {
Beacon,
Room,
RoomMember,
MatrixEvent,
getBeaconInfoIdentifier,
} from 'matrix-js-sdk/src/matrix';
import { mount } from "enzyme";
import maplibregl from "maplibre-gl";
import { act } from "react-dom/test-utils";
import { Beacon, Room, RoomMember, MatrixEvent, getBeaconInfoIdentifier } from "matrix-js-sdk/src/matrix";
import BeaconMarker from '../../../../src/components/views/beacon/BeaconMarker';
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
import BeaconMarker from "../../../../src/components/views/beacon/BeaconMarker";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import {
getMockClientWithEventEmitter,
makeBeaconEvent,
makeBeaconInfoEvent,
makeRoomWithStateEvents,
} from '../../../test-utils';
import { TILE_SERVER_WK_KEY } from '../../../../src/utils/WellKnownUtils';
} from "../../../test-utils";
import { TILE_SERVER_WK_KEY } from "../../../../src/utils/WellKnownUtils";
describe('<BeaconMarker />', () => {
describe("<BeaconMarker />", () => {
// 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 aliceMember = new RoomMember(roomId, aliceId);
@ -51,7 +45,7 @@ describe('<BeaconMarker />', () => {
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(),
@ -62,27 +56,23 @@ describe('<BeaconMarker />', () => {
// as we update room state
const setupRoom = (stateEvents: MatrixEvent[] = []): Room => {
const room1 = makeRoomWithStateEvents(stateEvents, { roomId, mockClient });
jest.spyOn(room1, 'getMember').mockReturnValue(aliceMember);
jest.spyOn(room1, "getMember").mockReturnValue(aliceMember);
return room1;
};
const defaultEvent = makeBeaconInfoEvent(aliceId,
roomId,
{ isLive: true },
'$alice-room1-1',
);
const notLiveEvent = makeBeaconInfoEvent(aliceId,
roomId,
{ isLive: false },
'$alice-room1-2',
);
const defaultEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: true }, "$alice-room1-1");
const notLiveEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: false }, "$alice-room1-2");
const location1 = makeBeaconEvent(
aliceId, { beaconInfoId: defaultEvent.getId(), geoUri: 'geo:51,41', timestamp: now + 1 },
);
const location2 = makeBeaconEvent(
aliceId, { beaconInfoId: defaultEvent.getId(), geoUri: 'geo:52,42', timestamp: now + 10000 },
);
const location1 = makeBeaconEvent(aliceId, {
beaconInfoId: defaultEvent.getId(),
geoUri: "geo:51,41",
timestamp: now + 1,
});
const location2 = makeBeaconEvent(aliceId, {
beaconInfoId: defaultEvent.getId(),
geoUri: "geo:52,42",
timestamp: now + 10000,
});
const defaultProps = {
map: mockMap,
@ -99,21 +89,21 @@ describe('<BeaconMarker />', () => {
jest.clearAllMocks();
});
it('renders nothing when beacon is not live', () => {
it("renders nothing when beacon is not live", () => {
const room = setupRoom([notLiveEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(notLiveEvent));
const component = getComponent({ beacon });
expect(component.html()).toBe(null);
});
it('renders nothing when beacon has no location', () => {
it("renders nothing when beacon has no location", () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
const component = getComponent({ beacon });
expect(component.html()).toBe(null);
});
it('renders marker when beacon has location', () => {
it("renders marker when beacon has location", () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
beacon.addLocations([location1]);
@ -121,12 +111,12 @@ describe('<BeaconMarker />', () => {
expect(component).toMatchSnapshot();
});
it('updates with new locations', () => {
it("updates with new locations", () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
beacon.addLocations([location1]);
const component = getComponent({ beacon });
expect(component.find('SmartMarker').props()['geoUri']).toEqual('geo:51,41');
expect(component.find("SmartMarker").props()["geoUri"]).toEqual("geo:51,41");
act(() => {
beacon.addLocations([location2]);
@ -134,6 +124,6 @@ describe('<BeaconMarker />', () => {
component.setProps({});
// updated to latest location
expect(component.find('SmartMarker').props()['geoUri']).toEqual('geo:52,42');
expect(component.find("SmartMarker").props()["geoUri"]).toEqual("geo:52,42");
});
});

View file

@ -14,75 +14,75 @@ 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 { Beacon } from 'matrix-js-sdk/src/matrix';
import { mount } from "enzyme";
import { Beacon } from "matrix-js-sdk/src/matrix";
import BeaconStatus from '../../../../src/components/views/beacon/BeaconStatus';
import { BeaconDisplayStatus } from '../../../../src/components/views/beacon/displayStatus';
import { findByTestId, makeBeaconInfoEvent } from '../../../test-utils';
import BeaconStatus from "../../../../src/components/views/beacon/BeaconStatus";
import { BeaconDisplayStatus } from "../../../../src/components/views/beacon/displayStatus";
import { findByTestId, makeBeaconInfoEvent } from "../../../test-utils";
describe('<BeaconStatus />', () => {
describe("<BeaconStatus />", () => {
const defaultProps = {
displayStatus: BeaconDisplayStatus.Loading,
label: 'test label',
label: "test label",
withIcon: true,
};
const getComponent = (props = {}) =>
mount(<BeaconStatus {...defaultProps} {...props} />);
const getComponent = (props = {}) => mount(<BeaconStatus {...defaultProps} {...props} />);
it('renders loading state', () => {
it("renders loading state", () => {
const component = getComponent({ displayStatus: BeaconDisplayStatus.Loading });
expect(component).toMatchSnapshot();
});
it('renders stopped state', () => {
it("renders stopped state", () => {
const component = getComponent({ displayStatus: BeaconDisplayStatus.Stopped });
expect(component).toMatchSnapshot();
});
it('renders without icon', () => {
it("renders without icon", () => {
const component = getComponent({ withIcon: false, displayStatus: BeaconDisplayStatus.Stopped });
expect(component.find('StyledLiveBeaconIcon').length).toBeFalsy();
expect(component.find("StyledLiveBeaconIcon").length).toBeFalsy();
});
describe('active state', () => {
it('renders without children', () => {
describe("active state", () => {
it("renders without children", () => {
// mock for stable snapshot
jest.spyOn(Date, 'now').mockReturnValue(123456789);
const beacon = new Beacon(makeBeaconInfoEvent('@user:server', '!room:server', { isLive: false }, '$1'));
jest.spyOn(Date, "now").mockReturnValue(123456789);
const beacon = new Beacon(makeBeaconInfoEvent("@user:server", "!room:server", { isLive: false }, "$1"));
const component = getComponent({ beacon, displayStatus: BeaconDisplayStatus.Active });
expect(component).toMatchSnapshot();
});
it('renders with children', () => {
const beacon = new Beacon(makeBeaconInfoEvent('@user:server', '!room:sever', { isLive: false }));
it("renders with children", () => {
const beacon = new Beacon(makeBeaconInfoEvent("@user:server", "!room:sever", { isLive: false }));
const component = getComponent({
beacon,
children: <span data-test-id='test'>test</span>,
children: <span data-test-id="test">test</span>,
displayStatus: BeaconDisplayStatus.Active,
});
expect(findByTestId(component, 'test-child')).toMatchSnapshot();
expect(findByTestId(component, "test-child")).toMatchSnapshot();
});
it('renders static remaining time when displayLiveTimeRemaining is falsy', () => {
it("renders static remaining time when displayLiveTimeRemaining is falsy", () => {
// mock for stable snapshot
jest.spyOn(Date, 'now').mockReturnValue(123456789);
const beacon = new Beacon(makeBeaconInfoEvent('@user:server', '!room:server', { isLive: false }, '$1'));
jest.spyOn(Date, "now").mockReturnValue(123456789);
const beacon = new Beacon(makeBeaconInfoEvent("@user:server", "!room:server", { isLive: false }, "$1"));
const component = getComponent({ beacon, displayStatus: BeaconDisplayStatus.Active });
expect(component.text().includes('Live until 11:17')).toBeTruthy();
expect(component.text().includes("Live until 11:17")).toBeTruthy();
});
it('renders live time remaining when displayLiveTimeRemaining is truthy', () => {
it("renders live time remaining when displayLiveTimeRemaining is truthy", () => {
// mock for stable snapshot
jest.spyOn(Date, 'now').mockReturnValue(123456789);
const beacon = new Beacon(makeBeaconInfoEvent('@user:server', '!room:server', { isLive: false }, '$1'));
jest.spyOn(Date, "now").mockReturnValue(123456789);
const beacon = new Beacon(makeBeaconInfoEvent("@user:server", "!room:server", { isLive: false }, "$1"));
const component = getComponent({
beacon, displayStatus: BeaconDisplayStatus.Active,
beacon,
displayStatus: BeaconDisplayStatus.Active,
displayLiveTimeRemaining: true,
});
expect(component.text().includes('1h left')).toBeTruthy();
expect(component.text().includes("1h left")).toBeTruthy();
});
});
});

View file

@ -14,21 +14,15 @@ 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, ReactWrapper } from 'enzyme';
import { act } from 'react-dom/test-utils';
import {
MatrixClient,
MatrixEvent,
Room,
RoomMember,
getBeaconInfoIdentifier,
} from 'matrix-js-sdk/src/matrix';
import maplibregl from 'maplibre-gl';
import { mocked } from 'jest-mock';
import { mount, ReactWrapper } from "enzyme";
import { act } from "react-dom/test-utils";
import { MatrixClient, MatrixEvent, Room, RoomMember, getBeaconInfoIdentifier } from "matrix-js-sdk/src/matrix";
import maplibregl from "maplibre-gl";
import { mocked } from "jest-mock";
import BeaconViewDialog from '../../../../src/components/views/beacon/BeaconViewDialog';
import BeaconViewDialog from "../../../../src/components/views/beacon/BeaconViewDialog";
import {
findByAttr,
findByTestId,
@ -37,26 +31,26 @@ import {
makeBeaconInfoEvent,
makeRoomWithBeacons,
makeRoomWithStateEvents,
} from '../../../test-utils';
import { TILE_SERVER_WK_KEY } from '../../../../src/utils/WellKnownUtils';
import { OwnBeaconStore } from '../../../../src/stores/OwnBeaconStore';
import { BeaconDisplayStatus } from '../../../../src/components/views/beacon/displayStatus';
import BeaconListItem from '../../../../src/components/views/beacon/BeaconListItem';
} from "../../../test-utils";
import { TILE_SERVER_WK_KEY } from "../../../../src/utils/WellKnownUtils";
import { OwnBeaconStore } from "../../../../src/stores/OwnBeaconStore";
import { BeaconDisplayStatus } from "../../../../src/components/views/beacon/displayStatus";
import BeaconListItem from "../../../../src/components/views/beacon/BeaconListItem";
describe('<BeaconViewDialog />', () => {
describe("<BeaconViewDialog />", () => {
// 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';
const bobId = '@bob:server';
jest.spyOn(global.Date, "now").mockReturnValue(now);
const roomId = "!room:server";
const aliceId = "@alice:server";
const bobId = "@bob:server";
const aliceMember = new RoomMember(roomId, aliceId);
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(bobId),
getRoom: jest.fn(),
@ -70,20 +64,18 @@ describe('<BeaconViewDialog />', () => {
// as we update room state
const setupRoom = (stateEvents: MatrixEvent[] = []): Room => {
const room1 = makeRoomWithStateEvents(stateEvents, { roomId, mockClient });
jest.spyOn(room1, 'getMember').mockReturnValue(aliceMember);
jest.spyOn(room1, "getMember").mockReturnValue(aliceMember);
return room1;
};
const defaultEvent = makeBeaconInfoEvent(aliceId,
roomId,
{ isLive: true },
'$alice-room1-1',
);
const defaultEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: true }, "$alice-room1-1");
const location1 = makeBeaconEvent(
aliceId, { beaconInfoId: defaultEvent.getId(), geoUri: 'geo:51,41', timestamp: now + 1 },
);
const location1 = makeBeaconEvent(aliceId, {
beaconInfoId: defaultEvent.getId(),
geoUri: "geo:51,41",
timestamp: now + 1,
});
const defaultProps = {
onFinished: jest.fn(),
@ -91,74 +83,73 @@ describe('<BeaconViewDialog />', () => {
matrixClient: mockClient as MatrixClient,
};
const getComponent = (props = {}) =>
mount(<BeaconViewDialog {...defaultProps} {...props} />);
const getComponent = (props = {}) => mount(<BeaconViewDialog {...defaultProps} {...props} />);
const openSidebar = (component: ReactWrapper) => act(() => {
findByTestId(component, 'beacon-view-dialog-open-sidebar').at(0).simulate('click');
component.setProps({});
});
const openSidebar = (component: ReactWrapper) =>
act(() => {
findByTestId(component, "beacon-view-dialog-open-sidebar").at(0).simulate("click");
component.setProps({});
});
beforeAll(() => {
maplibregl.AttributionControl = jest.fn();
});
beforeEach(() => {
jest.spyOn(OwnBeaconStore.instance, 'getLiveBeaconIds').mockRestore();
jest.spyOn(OwnBeaconStore.instance, 'getBeaconById').mockRestore();
jest.spyOn(global.Date, 'now').mockReturnValue(now);
jest.spyOn(OwnBeaconStore.instance, "getLiveBeaconIds").mockRestore();
jest.spyOn(OwnBeaconStore.instance, "getBeaconById").mockRestore();
jest.spyOn(global.Date, "now").mockReturnValue(now);
jest.clearAllMocks();
});
it('renders a map with markers', () => {
it("renders a map with markers", () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
beacon.addLocations([location1]);
const component = getComponent();
expect(component.find('Map').props()).toEqual(expect.objectContaining({
centerGeoUri: 'geo:51,41',
interactive: true,
}));
expect(component.find('SmartMarker').length).toEqual(1);
expect(component.find("Map").props()).toEqual(
expect.objectContaining({
centerGeoUri: "geo:51,41",
interactive: true,
}),
);
expect(component.find("SmartMarker").length).toEqual(1);
});
it('does not render any own beacon status when user is not live sharing', () => {
it("does not render any own beacon status when user is not live sharing", () => {
// default event belongs to alice, we are bob
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
beacon.addLocations([location1]);
const component = getComponent();
expect(component.find('DialogOwnBeaconStatus').html()).toBeNull();
expect(component.find("DialogOwnBeaconStatus").html()).toBeNull();
});
it('renders own beacon status when user is live sharing', () => {
it("renders own beacon status when user is live sharing", () => {
// default event belongs to alice
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
beacon.addLocations([location1]);
// mock own beacon store to show default event as alice's live beacon
jest.spyOn(OwnBeaconStore.instance, 'getLiveBeaconIds').mockReturnValue([beacon.identifier]);
jest.spyOn(OwnBeaconStore.instance, 'getBeaconById').mockReturnValue(beacon);
jest.spyOn(OwnBeaconStore.instance, "getLiveBeaconIds").mockReturnValue([beacon.identifier]);
jest.spyOn(OwnBeaconStore.instance, "getBeaconById").mockReturnValue(beacon);
const component = getComponent();
expect(component.find('MemberAvatar').length).toBeTruthy();
expect(component.find('OwnBeaconStatus').props()).toEqual({
beacon, displayStatus: BeaconDisplayStatus.Active,
className: 'mx_DialogOwnBeaconStatus_status',
expect(component.find("MemberAvatar").length).toBeTruthy();
expect(component.find("OwnBeaconStatus").props()).toEqual({
beacon,
displayStatus: BeaconDisplayStatus.Active,
className: "mx_DialogOwnBeaconStatus_status",
});
});
it('updates markers on changes to beacons', () => {
it("updates markers on changes to beacons", () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
beacon.addLocations([location1]);
const component = getComponent();
expect(component.find('BeaconMarker').length).toEqual(1);
expect(component.find("BeaconMarker").length).toEqual(1);
const anotherBeaconEvent = makeBeaconInfoEvent(bobId,
roomId,
{ isLive: true },
'$bob-room1-1',
);
const anotherBeaconEvent = makeBeaconInfoEvent(bobId, roomId, { isLive: true }, "$bob-room1-1");
act(() => {
// emits RoomStateEvent.BeaconLiveness
@ -168,21 +159,17 @@ describe('<BeaconViewDialog />', () => {
component.setProps({});
// two markers now!
expect(component.find('BeaconMarker').length).toEqual(2);
expect(component.find("BeaconMarker").length).toEqual(2);
});
it('does not update bounds or center on changing beacons', () => {
it("does not update bounds or center on changing beacons", () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
beacon.addLocations([location1]);
const component = getComponent();
expect(component.find('BeaconMarker').length).toEqual(1);
expect(component.find("BeaconMarker").length).toEqual(1);
const anotherBeaconEvent = makeBeaconInfoEvent(bobId,
roomId,
{ isLive: true },
'$bob-room1-1',
);
const anotherBeaconEvent = makeBeaconInfoEvent(bobId, roomId, { isLive: true }, "$bob-room1-1");
act(() => {
// emits RoomStateEvent.BeaconLiveness
@ -196,7 +183,7 @@ describe('<BeaconViewDialog />', () => {
expect(mockMap.fitBounds).toHaveBeenCalledTimes(1);
});
it('renders a fallback when there are no locations', () => {
it("renders a fallback when there are no locations", () => {
// this is a cornercase, should not be a reachable state in UI anymore
const onFinished = jest.fn();
const room = setupRoom([defaultEvent]);
@ -204,30 +191,26 @@ describe('<BeaconViewDialog />', () => {
const component = getComponent({ onFinished });
// map placeholder
expect(findByTestId(component, 'beacon-view-dialog-map-fallback')).toMatchSnapshot();
expect(findByTestId(component, "beacon-view-dialog-map-fallback")).toMatchSnapshot();
act(() => {
findByTestId(component, 'beacon-view-dialog-fallback-close').at(0).simulate('click');
findByTestId(component, "beacon-view-dialog-fallback-close").at(0).simulate("click");
});
expect(onFinished).toHaveBeenCalled();
});
it('renders map without markers when no live beacons remain', () => {
it("renders map without markers when no live beacons remain", () => {
const onFinished = jest.fn();
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
beacon.addLocations([location1]);
const component = getComponent({ onFinished });
expect(component.find('BeaconMarker').length).toEqual(1);
expect(component.find("BeaconMarker").length).toEqual(1);
// this will replace the defaultEvent
// leading to no more live beacons
const anotherBeaconEvent = makeBeaconInfoEvent(aliceId,
roomId,
{ isLive: false },
'$alice-room1-2',
);
const anotherBeaconEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: false }, "$alice-room1-2");
expect(mockMap.setCenter).toHaveBeenCalledWith({ lat: 51, lon: 41 });
// reset call counts
@ -242,16 +225,16 @@ describe('<BeaconViewDialog />', () => {
component.setProps({});
// no more avatars
expect(component.find('MemberAvatar').length).toBeFalsy();
expect(component.find("MemberAvatar").length).toBeFalsy();
// map still rendered
expect(component.find('Map').length).toBeTruthy();
expect(component.find("Map").length).toBeTruthy();
// map location unchanged
expect(mockMap.setCenter).not.toHaveBeenCalled();
expect(mockMap.fitBounds).not.toHaveBeenCalled();
});
describe('sidebar', () => {
it('opens sidebar on view list button click', () => {
describe("sidebar", () => {
it("opens sidebar on view list button click", () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
beacon.addLocations([location1]);
@ -259,10 +242,10 @@ describe('<BeaconViewDialog />', () => {
openSidebar(component);
expect(component.find('DialogSidebar').length).toBeTruthy();
expect(component.find("DialogSidebar").length).toBeTruthy();
});
it('closes sidebar on close button click', () => {
it("closes sidebar on close button click", () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
beacon.addLocations([location1]);
@ -271,34 +254,35 @@ describe('<BeaconViewDialog />', () => {
// open the sidebar
openSidebar(component);
expect(component.find('DialogSidebar').length).toBeTruthy();
expect(component.find("DialogSidebar").length).toBeTruthy();
// now close it
act(() => {
findByAttr('data-testid')(component, 'dialog-sidebar-close').at(0).simulate('click');
findByAttr("data-testid")(component, "dialog-sidebar-close").at(0).simulate("click");
component.setProps({});
});
expect(component.find('DialogSidebar').length).toBeFalsy();
expect(component.find("DialogSidebar").length).toBeFalsy();
});
});
describe('focused beacons', () => {
const beacon2Event = makeBeaconInfoEvent(bobId,
roomId,
{ isLive: true },
'$bob-room1-2',
);
describe("focused beacons", () => {
const beacon2Event = makeBeaconInfoEvent(bobId, roomId, { isLive: true }, "$bob-room1-2");
const location2 = makeBeaconEvent(
bobId, { beaconInfoId: beacon2Event.getId(), geoUri: 'geo:33,22', timestamp: now + 1 },
);
const location2 = makeBeaconEvent(bobId, {
beaconInfoId: beacon2Event.getId(),
geoUri: "geo:33,22",
timestamp: now + 1,
});
const fitBoundsOptions = { maxZoom: 15, padding: 100 };
it('opens map with both beacons in view on first load without initialFocusedBeacon', () => {
it("opens map with both beacons in view on first load without initialFocusedBeacon", () => {
const [beacon1, beacon2] = makeRoomWithBeacons(
roomId, mockClient, [defaultEvent, beacon2Event], [location1, location2],
roomId,
mockClient,
[defaultEvent, beacon2Event],
[location1, location2],
);
getComponent({ beacons: [beacon1, beacon2] });
@ -308,15 +292,19 @@ describe('<BeaconViewDialog />', () => {
// only called once
expect(mockMap.setCenter).toHaveBeenCalledTimes(1);
// bounds fit both beacons, only called once
expect(mockMap.fitBounds).toHaveBeenCalledWith(new maplibregl.LngLatBounds(
[22, 33], [41, 51],
), fitBoundsOptions);
expect(mockMap.fitBounds).toHaveBeenCalledWith(
new maplibregl.LngLatBounds([22, 33], [41, 51]),
fitBoundsOptions,
);
expect(mockMap.fitBounds).toHaveBeenCalledTimes(1);
});
it('opens map with both beacons in view on first load with an initially focused beacon', () => {
it("opens map with both beacons in view on first load with an initially focused beacon", () => {
const [beacon1, beacon2] = makeRoomWithBeacons(
roomId, mockClient, [defaultEvent, beacon2Event], [location1, location2],
roomId,
mockClient,
[defaultEvent, beacon2Event],
[location1, location2],
);
getComponent({ beacons: [beacon1, beacon2], initialFocusedBeacon: beacon1 });
@ -326,15 +314,19 @@ describe('<BeaconViewDialog />', () => {
// only called once
expect(mockMap.setCenter).toHaveBeenCalledTimes(1);
// bounds fit both beacons, only called once
expect(mockMap.fitBounds).toHaveBeenCalledWith(new maplibregl.LngLatBounds(
[22, 33], [41, 51],
), fitBoundsOptions);
expect(mockMap.fitBounds).toHaveBeenCalledWith(
new maplibregl.LngLatBounds([22, 33], [41, 51]),
fitBoundsOptions,
);
expect(mockMap.fitBounds).toHaveBeenCalledTimes(1);
});
it('focuses on beacon location on sidebar list item click', () => {
it("focuses on beacon location on sidebar list item click", () => {
const [beacon1, beacon2] = makeRoomWithBeacons(
roomId, mockClient, [defaultEvent, beacon2Event], [location1, location2],
roomId,
mockClient,
[defaultEvent, beacon2Event],
[location1, location2],
);
const component = getComponent({ beacons: [beacon1, beacon2] });
@ -346,7 +338,7 @@ describe('<BeaconViewDialog />', () => {
act(() => {
// click on the first beacon in the list
component.find(BeaconListItem).at(0).simulate('click');
component.find(BeaconListItem).at(0).simulate("click");
});
// centered on clicked beacon
@ -354,16 +346,20 @@ describe('<BeaconViewDialog />', () => {
// only called once
expect(mockMap.setCenter).toHaveBeenCalledTimes(1);
// bounds fitted just to clicked beacon
expect(mockMap.fitBounds).toHaveBeenCalledWith(new maplibregl.LngLatBounds(
[41, 51], [41, 51],
), fitBoundsOptions);
expect(mockMap.fitBounds).toHaveBeenCalledWith(
new maplibregl.LngLatBounds([41, 51], [41, 51]),
fitBoundsOptions,
);
expect(mockMap.fitBounds).toHaveBeenCalledTimes(1);
});
it('refocuses on same beacon when clicking list item again', () => {
it("refocuses on same beacon when clicking list item again", () => {
// test the map responds to refocusing the same beacon
const [beacon1, beacon2] = makeRoomWithBeacons(
roomId, mockClient, [defaultEvent, beacon2Event], [location1, location2],
roomId,
mockClient,
[defaultEvent, beacon2Event],
[location1, location2],
);
const component = getComponent({ beacons: [beacon1, beacon2] });
@ -375,19 +371,17 @@ describe('<BeaconViewDialog />', () => {
act(() => {
// click on the second beacon in the list
component.find(BeaconListItem).at(1).simulate('click');
component.find(BeaconListItem).at(1).simulate("click");
});
const expectedBounds = new maplibregl.LngLatBounds(
[22, 33], [22, 33],
);
const expectedBounds = new maplibregl.LngLatBounds([22, 33], [22, 33]);
// date is mocked but this relies on timestamp, manually mock a tick
jest.spyOn(global.Date, 'now').mockReturnValue(now + 1);
jest.spyOn(global.Date, "now").mockReturnValue(now + 1);
act(() => {
// click on the second beacon in the list
component.find(BeaconListItem).at(1).simulate('click');
component.find(BeaconListItem).at(1).simulate("click");
});
// centered on clicked beacon

View file

@ -14,21 +14,21 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { fireEvent, render } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import React from "react";
import { fireEvent, render } from "@testing-library/react";
import { act } from "react-dom/test-utils";
import DialogSidebar from '../../../../src/components/views/beacon/DialogSidebar';
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
import DialogSidebar from "../../../../src/components/views/beacon/DialogSidebar";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import {
getMockClientWithEventEmitter,
makeBeaconEvent,
makeBeaconInfoEvent,
makeRoomWithBeacons,
mockClientMethodsUser,
} from '../../../test-utils';
} from "../../../test-utils";
describe('<DialogSidebar />', () => {
describe("<DialogSidebar />", () => {
const defaultProps = {
beacons: [],
requestClose: jest.fn(),
@ -37,66 +37,66 @@ describe('<DialogSidebar />', () => {
const now = 1647270879403;
const roomId = '!room:server.org';
const aliceId = '@alice:server.org';
const roomId = "!room:server.org";
const aliceId = "@alice:server.org";
const client = getMockClientWithEventEmitter({
...mockClientMethodsUser(aliceId),
getRoom: jest.fn(),
});
const beaconEvent = makeBeaconInfoEvent(aliceId,
roomId,
{ isLive: true, timestamp: now },
'$alice-room1-1',
);
const location1 = makeBeaconEvent(
aliceId, { beaconInfoId: beaconEvent.getId(), geoUri: 'geo:51,41', timestamp: now },
);
const beaconEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: true, timestamp: now }, "$alice-room1-1");
const location1 = makeBeaconEvent(aliceId, {
beaconInfoId: beaconEvent.getId(),
geoUri: "geo:51,41",
timestamp: now,
});
const getComponent = (props = {}) => (
<MatrixClientContext.Provider value={client}>
<DialogSidebar {...defaultProps} {...props} />);
</MatrixClientContext.Provider>);
<DialogSidebar {...defaultProps} {...props} />
);
</MatrixClientContext.Provider>
);
beforeEach(() => {
// mock now so time based text in snapshots is stable
jest.spyOn(Date, 'now').mockReturnValue(now);
jest.spyOn(Date, "now").mockReturnValue(now);
});
afterAll(() => {
jest.spyOn(Date, 'now').mockRestore();
jest.spyOn(Date, "now").mockRestore();
});
it('renders sidebar correctly without beacons', () => {
it("renders sidebar correctly without beacons", () => {
const { container } = render(getComponent());
expect(container).toMatchSnapshot();
});
it('renders sidebar correctly with beacons', () => {
it("renders sidebar correctly with beacons", () => {
const [beacon] = makeRoomWithBeacons(roomId, client, [beaconEvent], [location1]);
const { container } = render(getComponent({ beacons: [beacon] }));
expect(container).toMatchSnapshot();
});
it('calls on beacon click', () => {
it("calls on beacon click", () => {
const onBeaconClick = jest.fn();
const [beacon] = makeRoomWithBeacons(roomId, client, [beaconEvent], [location1]);
const { container } = render(getComponent({ beacons: [beacon], onBeaconClick }));
act(() => {
const [listItem] = container.getElementsByClassName('mx_BeaconListItem');
const [listItem] = container.getElementsByClassName("mx_BeaconListItem");
fireEvent.click(listItem);
});
expect(onBeaconClick).toHaveBeenCalled();
});
it('closes on close button click', () => {
it("closes on close button click", () => {
const requestClose = jest.fn();
const { getByTestId } = render(getComponent({ requestClose }));
act(() => {
fireEvent.click(getByTestId('dialog-sidebar-close'));
fireEvent.click(getByTestId("dialog-sidebar-close"));
});
expect(requestClose).toHaveBeenCalled();
});

View file

@ -14,19 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { mocked } from 'jest-mock';
import React from "react";
import { mocked } from "jest-mock";
import { fireEvent, render } from "@testing-library/react";
import { act } from 'react-dom/test-utils';
import { Beacon, BeaconIdentifier } from 'matrix-js-sdk/src/matrix';
import { act } from "react-dom/test-utils";
import { Beacon, BeaconIdentifier } from "matrix-js-sdk/src/matrix";
import LeftPanelLiveShareWarning from '../../../../src/components/views/beacon/LeftPanelLiveShareWarning';
import { OwnBeaconStore, OwnBeaconStoreEvent } from '../../../../src/stores/OwnBeaconStore';
import { flushPromises, makeBeaconInfoEvent } from '../../../test-utils';
import dispatcher from '../../../../src/dispatcher/dispatcher';
import { Action } from '../../../../src/dispatcher/actions';
import LeftPanelLiveShareWarning from "../../../../src/components/views/beacon/LeftPanelLiveShareWarning";
import { OwnBeaconStore, OwnBeaconStoreEvent } from "../../../../src/stores/OwnBeaconStore";
import { flushPromises, makeBeaconInfoEvent } from "../../../test-utils";
import dispatcher from "../../../../src/dispatcher/dispatcher";
import { Action } from "../../../../src/dispatcher/actions";
jest.mock('../../../../src/stores/OwnBeaconStore', () => {
jest.mock("../../../../src/stores/OwnBeaconStore", () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const EventEmitter = require("events");
class MockOwnBeaconStore extends EventEmitter {
@ -38,57 +38,52 @@ jest.mock('../../../../src/stores/OwnBeaconStore', () => {
}
return {
// @ts-ignore
...jest.requireActual('../../../../src/stores/OwnBeaconStore'),
...jest.requireActual("../../../../src/stores/OwnBeaconStore"),
OwnBeaconStore: {
instance: new MockOwnBeaconStore() as unknown as OwnBeaconStore,
},
};
},
);
});
describe('<LeftPanelLiveShareWarning />', () => {
describe("<LeftPanelLiveShareWarning />", () => {
const getComponent = (props = {}) => render(<LeftPanelLiveShareWarning {...props} />);
const roomId1 = '!room1:server';
const roomId2 = '!room2:server';
const aliceId = '@alive:server';
const roomId1 = "!room1:server";
const roomId2 = "!room2:server";
const aliceId = "@alive:server";
const now = 1647270879403;
const HOUR_MS = 3600000;
beforeEach(() => {
jest.spyOn(global.Date, 'now').mockReturnValue(now);
jest.spyOn(dispatcher, 'dispatch').mockClear().mockImplementation(() => { });
jest.spyOn(global.Date, "now").mockReturnValue(now);
jest.spyOn(dispatcher, "dispatch")
.mockClear()
.mockImplementation(() => {});
OwnBeaconStore.instance.beaconUpdateErrors.clear();
});
afterAll(() => {
jest.spyOn(global.Date, 'now').mockRestore();
jest.spyOn(global.Date, "now").mockRestore();
jest.restoreAllMocks();
});
// 12h old, 12h left
const beacon1 = new Beacon(makeBeaconInfoEvent(aliceId,
roomId1,
{ timeout: HOUR_MS * 24, timestamp: now - 12 * HOUR_MS },
'$1',
));
const beacon1 = new Beacon(
makeBeaconInfoEvent(aliceId, roomId1, { timeout: HOUR_MS * 24, timestamp: now - 12 * HOUR_MS }, "$1"),
);
// 10h left
const beacon2 = new Beacon(makeBeaconInfoEvent(aliceId,
roomId2,
{ timeout: HOUR_MS * 10, timestamp: now },
'$2',
));
const beacon2 = new Beacon(makeBeaconInfoEvent(aliceId, roomId2, { timeout: HOUR_MS * 10, timestamp: now }, "$2"));
it('renders nothing when user has no live beacons', () => {
it("renders nothing when user has no live beacons", () => {
const { container } = getComponent();
expect(container.innerHTML).toBeFalsy();
});
describe('when user has live location monitor', () => {
describe("when user has live location monitor", () => {
beforeAll(() => {
mocked(OwnBeaconStore.instance).getBeaconById.mockImplementation(beaconId => {
mocked(OwnBeaconStore.instance).getBeaconById.mockImplementation((beaconId) => {
if (beaconId === beacon1.identifier) {
return beacon1;
}
@ -104,17 +99,17 @@ describe('<LeftPanelLiveShareWarning />', () => {
});
afterAll(() => {
jest.spyOn(document, 'addEventListener').mockRestore();
jest.spyOn(document, "addEventListener").mockRestore();
});
it('renders correctly when not minimized', () => {
it("renders correctly when not minimized", () => {
const { asFragment } = getComponent();
expect(asFragment()).toMatchSnapshot();
});
it('goes to room of latest beacon when clicked', () => {
it("goes to room of latest beacon when clicked", () => {
const { container } = getComponent();
const dispatchSpy = jest.spyOn(dispatcher, 'dispatch');
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
fireEvent.click(container.querySelector("[role=button]"));
@ -129,25 +124,25 @@ describe('<LeftPanelLiveShareWarning />', () => {
});
});
it('renders correctly when minimized', () => {
it("renders correctly when minimized", () => {
const { asFragment } = getComponent({ isMinimized: true });
expect(asFragment()).toMatchSnapshot();
});
it('renders location publish error', () => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue(
[beacon1.identifier],
);
it("renders location publish error", () => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue([
beacon1.identifier,
]);
const { asFragment } = getComponent();
expect(asFragment()).toMatchSnapshot();
});
it('goes to room of latest beacon with location publish error when clicked', () => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue(
[beacon1.identifier],
);
it("goes to room of latest beacon with location publish error when clicked", () => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue([
beacon1.identifier,
]);
const { container } = getComponent();
const dispatchSpy = jest.spyOn(dispatcher, 'dispatch');
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
fireEvent.click(container.querySelector("[role=button]"));
@ -162,30 +157,30 @@ describe('<LeftPanelLiveShareWarning />', () => {
});
});
it('goes back to default style when wire errors are cleared', () => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue(
[beacon1.identifier],
);
it("goes back to default style when wire errors are cleared", () => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue([
beacon1.identifier,
]);
const { container, rerender } = getComponent();
// error mode
expect(container.querySelector('.mx_LeftPanelLiveShareWarning').textContent).toEqual(
'An error occurred whilst sharing your live location',
expect(container.querySelector(".mx_LeftPanelLiveShareWarning").textContent).toEqual(
"An error occurred whilst sharing your live location",
);
act(() => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue([]);
OwnBeaconStore.instance.emit(OwnBeaconStoreEvent.LocationPublishError, 'abc');
OwnBeaconStore.instance.emit(OwnBeaconStoreEvent.LocationPublishError, "abc");
});
rerender(<LeftPanelLiveShareWarning />);
// default mode
expect(container.querySelector('.mx_LeftPanelLiveShareWarning').textContent).toEqual(
'You are sharing your live location',
expect(container.querySelector(".mx_LeftPanelLiveShareWarning").textContent).toEqual(
"You are sharing your live location",
);
});
it('removes itself when user stops having live beacons', async () => {
it("removes itself when user stops having live beacons", async () => {
const { container, rerender } = getComponent({ isMinimized: true });
// started out rendered
expect(container.innerHTML).toBeTruthy();
@ -202,14 +197,14 @@ describe('<LeftPanelLiveShareWarning />', () => {
expect(container.innerHTML).toBeFalsy();
});
it('refreshes beacon liveness monitors when pagevisibilty changes to visible', () => {
it("refreshes beacon liveness monitors when pagevisibilty changes to visible", () => {
OwnBeaconStore.instance.beacons.set(beacon1.identifier, beacon1);
OwnBeaconStore.instance.beacons.set(beacon2.identifier, beacon2);
const beacon1MonitorSpy = jest.spyOn(beacon1, 'monitorLiveness');
const beacon2MonitorSpy = jest.spyOn(beacon1, 'monitorLiveness');
const beacon1MonitorSpy = jest.spyOn(beacon1, "monitorLiveness");
const beacon2MonitorSpy = jest.spyOn(beacon1, "monitorLiveness");
jest.spyOn(document, 'addEventListener').mockImplementation(
(_e, listener) => (listener as EventListener)(new Event('')),
jest.spyOn(document, "addEventListener").mockImplementation((_e, listener) =>
(listener as EventListener)(new Event("")),
);
expect(beacon1MonitorSpy).not.toHaveBeenCalled();
@ -220,42 +215,42 @@ describe('<LeftPanelLiveShareWarning />', () => {
expect(beacon2MonitorSpy).toHaveBeenCalled();
});
describe('stopping errors', () => {
it('renders stopping error', () => {
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error('error'));
describe("stopping errors", () => {
it("renders stopping error", () => {
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error("error"));
const { container } = getComponent();
expect(container.textContent).toEqual('An error occurred while stopping your live location');
expect(container.textContent).toEqual("An error occurred while stopping your live location");
});
it('starts rendering stopping error on beaconUpdateError emit', () => {
it("starts rendering stopping error on beaconUpdateError emit", () => {
const { container } = getComponent();
// no error
expect(container.textContent).toEqual('You are sharing your live location');
expect(container.textContent).toEqual("You are sharing your live location");
act(() => {
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error('error'));
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error("error"));
OwnBeaconStore.instance.emit(OwnBeaconStoreEvent.BeaconUpdateError, beacon2.identifier, true);
});
expect(container.textContent).toEqual('An error occurred while stopping your live location');
expect(container.textContent).toEqual("An error occurred while stopping your live location");
});
it('renders stopping error when beacons have stopping and location errors', () => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue(
[beacon1.identifier],
);
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error('error'));
it("renders stopping error when beacons have stopping and location errors", () => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue([
beacon1.identifier,
]);
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error("error"));
const { container } = getComponent();
expect(container.textContent).toEqual('An error occurred while stopping your live location');
expect(container.textContent).toEqual("An error occurred while stopping your live location");
});
it('goes to room of latest beacon with stopping error when clicked', () => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue(
[beacon1.identifier],
);
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error('error'));
it("goes to room of latest beacon with stopping error when clicked", () => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue([
beacon1.identifier,
]);
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error("error"));
const { container } = getComponent();
const dispatchSpy = jest.spyOn(dispatcher, 'dispatch');
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
fireEvent.click(container.querySelector("[role=button]"));

View file

@ -14,62 +14,61 @@ 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 { mocked } from 'jest-mock';
import { Beacon } from 'matrix-js-sdk/src/matrix';
import { mount } from "enzyme";
import { act } from "react-dom/test-utils";
import { mocked } from "jest-mock";
import { Beacon } from "matrix-js-sdk/src/matrix";
import OwnBeaconStatus from '../../../../src/components/views/beacon/OwnBeaconStatus';
import { BeaconDisplayStatus } from '../../../../src/components/views/beacon/displayStatus';
import { useOwnLiveBeacons } from '../../../../src/utils/beacon';
import { findByTestId, makeBeaconInfoEvent } from '../../../test-utils';
import OwnBeaconStatus from "../../../../src/components/views/beacon/OwnBeaconStatus";
import { BeaconDisplayStatus } from "../../../../src/components/views/beacon/displayStatus";
import { useOwnLiveBeacons } from "../../../../src/utils/beacon";
import { findByTestId, makeBeaconInfoEvent } from "../../../test-utils";
jest.mock('../../../../src/utils/beacon/useOwnLiveBeacons', () => ({
jest.mock("../../../../src/utils/beacon/useOwnLiveBeacons", () => ({
useOwnLiveBeacons: jest.fn(),
}));
describe('<OwnBeaconStatus />', () => {
describe("<OwnBeaconStatus />", () => {
const defaultProps = {
displayStatus: BeaconDisplayStatus.Loading,
};
const userId = '@user:server';
const roomId = '!room:server';
const userId = "@user:server";
const roomId = "!room:server";
let defaultBeacon;
const getComponent = (props = {}) =>
mount(<OwnBeaconStatus {...defaultProps} {...props} />);
const getComponent = (props = {}) => mount(<OwnBeaconStatus {...defaultProps} {...props} />);
beforeEach(() => {
jest.spyOn(global.Date, 'now').mockReturnValue(123456789);
jest.spyOn(global.Date, "now").mockReturnValue(123456789);
mocked(useOwnLiveBeacons).mockClear().mockReturnValue({});
defaultBeacon = new Beacon(makeBeaconInfoEvent(userId, roomId));
});
it('renders without a beacon instance', () => {
it("renders without a beacon instance", () => {
const component = getComponent();
expect(component).toMatchSnapshot();
});
it('renders loading state correctly', () => {
it("renders loading state correctly", () => {
const component = getComponent();
expect(component.find('BeaconStatus').props()).toBeTruthy();
expect(component.find("BeaconStatus").props()).toBeTruthy();
});
describe('Active state', () => {
it('renders stop button', () => {
describe("Active state", () => {
it("renders stop button", () => {
const displayStatus = BeaconDisplayStatus.Active;
mocked(useOwnLiveBeacons).mockReturnValue({
onStopSharing: jest.fn(),
});
const component = getComponent({ displayStatus, beacon: defaultBeacon });
expect(component.text()).toContain('Live location enabled');
expect(component.text()).toContain("Live location enabled");
expect(findByTestId(component, 'beacon-status-stop-beacon').length).toBeTruthy();
expect(findByTestId(component, "beacon-status-stop-beacon").length).toBeTruthy();
});
it('stops sharing on stop button click', () => {
it("stops sharing on stop button click", () => {
const displayStatus = BeaconDisplayStatus.Active;
const onStopSharing = jest.fn();
mocked(useOwnLiveBeacons).mockReturnValue({
@ -78,37 +77,37 @@ describe('<OwnBeaconStatus />', () => {
const component = getComponent({ displayStatus, beacon: defaultBeacon });
act(() => {
findByTestId(component, 'beacon-status-stop-beacon').at(0).simulate('click');
findByTestId(component, "beacon-status-stop-beacon").at(0).simulate("click");
});
expect(onStopSharing).toHaveBeenCalled();
});
});
describe('errors', () => {
it('renders in error mode when displayStatus is error', () => {
describe("errors", () => {
it("renders in error mode when displayStatus is error", () => {
const displayStatus = BeaconDisplayStatus.Error;
const component = getComponent({ displayStatus });
expect(component.text()).toEqual('Live location error');
expect(component.text()).toEqual("Live location error");
// no actions for plain error
expect(component.find('AccessibleButton').length).toBeFalsy();
expect(component.find("AccessibleButton").length).toBeFalsy();
});
describe('with location publish error', () => {
it('renders in error mode', () => {
describe("with location publish error", () => {
it("renders in error mode", () => {
const displayStatus = BeaconDisplayStatus.Active;
mocked(useOwnLiveBeacons).mockReturnValue({
hasLocationPublishError: true,
onResetLocationPublishError: jest.fn(),
});
const component = getComponent({ displayStatus, beacon: defaultBeacon });
expect(component.text()).toContain('Live location error');
expect(component.text()).toContain("Live location error");
// retry button
expect(findByTestId(component, 'beacon-status-reset-wire-error').length).toBeTruthy();
expect(findByTestId(component, "beacon-status-reset-wire-error").length).toBeTruthy();
});
it('retry button resets location publish error', () => {
it("retry button resets location publish error", () => {
const displayStatus = BeaconDisplayStatus.Active;
const onResetLocationPublishError = jest.fn();
mocked(useOwnLiveBeacons).mockReturnValue({
@ -117,15 +116,15 @@ describe('<OwnBeaconStatus />', () => {
});
const component = getComponent({ displayStatus, beacon: defaultBeacon });
act(() => {
findByTestId(component, 'beacon-status-reset-wire-error').at(0).simulate('click');
findByTestId(component, "beacon-status-reset-wire-error").at(0).simulate("click");
});
expect(onResetLocationPublishError).toHaveBeenCalled();
});
});
describe('with stopping error', () => {
it('renders in error mode', () => {
describe("with stopping error", () => {
it("renders in error mode", () => {
const displayStatus = BeaconDisplayStatus.Active;
mocked(useOwnLiveBeacons).mockReturnValue({
hasLocationPublishError: false,
@ -133,12 +132,12 @@ describe('<OwnBeaconStatus />', () => {
onStopSharing: jest.fn(),
});
const component = getComponent({ displayStatus, beacon: defaultBeacon });
expect(component.text()).toContain('Live location error');
expect(component.text()).toContain("Live location error");
// retry button
expect(findByTestId(component, 'beacon-status-stop-beacon-retry').length).toBeTruthy();
expect(findByTestId(component, "beacon-status-stop-beacon-retry").length).toBeTruthy();
});
it('retry button retries stop sharing', () => {
it("retry button retries stop sharing", () => {
const displayStatus = BeaconDisplayStatus.Active;
const onStopSharing = jest.fn();
mocked(useOwnLiveBeacons).mockReturnValue({
@ -147,7 +146,7 @@ describe('<OwnBeaconStatus />', () => {
});
const component = getComponent({ displayStatus, beacon: defaultBeacon });
act(() => {
findByTestId(component, 'beacon-status-stop-beacon-retry').at(0).simulate('click');
findByTestId(component, "beacon-status-stop-beacon-retry").at(0).simulate("click");
});
expect(onStopSharing).toHaveBeenCalled();
@ -155,7 +154,7 @@ describe('<OwnBeaconStatus />', () => {
});
});
it('renders loading state correctly', () => {
it("renders loading state correctly", () => {
const component = getComponent();
expect(component).toBeTruthy();
});

View file

@ -16,28 +16,12 @@ limitations under the License.
import React from "react";
import { act } from "react-dom/test-utils";
import {
Room,
PendingEventOrdering,
MatrixClient,
RoomMember,
RoomStateEvent,
} from "matrix-js-sdk/src/matrix";
import { Room, PendingEventOrdering, MatrixClient, RoomMember, RoomStateEvent } from "matrix-js-sdk/src/matrix";
import { ClientWidgetApi, Widget } from "matrix-widget-api";
import {
cleanup,
render,
screen,
} from "@testing-library/react";
import { cleanup, render, screen } from "@testing-library/react";
import { mocked, Mocked } from "jest-mock";
import {
mkRoomMember,
MockedCall,
setupAsyncStoreWithClient,
stubClient,
useMockedCalls,
} from "../../../test-utils";
import { mkRoomMember, MockedCall, setupAsyncStoreWithClient, stubClient, useMockedCalls } from "../../../test-utils";
import RoomCallBanner from "../../../../src/components/views/beacon/RoomCallBanner";
import { CallStore } from "../../../../src/stores/CallStore";
import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore";
@ -64,13 +48,9 @@ describe("<RoomCallBanner />", () => {
pendingEventOrdering: PendingEventOrdering.Detached,
});
alice = mkRoomMember(room.roomId, "@alice:example.org");
jest.spyOn(room, "getMember").mockImplementation((userId) =>
userId === alice.userId ? alice : null,
);
jest.spyOn(room, "getMember").mockImplementation((userId) => (userId === alice.userId ? alice : 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]);
@ -100,7 +80,9 @@ describe("<RoomCallBanner />", () => {
beforeEach(() => {
MockedCall.create(room, "1");
const maybeCall = CallStore.instance.getCall(room.roomId);
if (!(maybeCall instanceof MockedCall)) {throw new Error("Failed to create call");}
if (!(maybeCall instanceof MockedCall)) {
throw new Error("Failed to create call");
}
call = maybeCall;
widget = new Widget(call.widget);

View file

@ -14,15 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { act } from 'react-dom/test-utils';
import React from "react";
import { act } from "react-dom/test-utils";
// eslint-disable-next-line deprecate/import
import { mount } from 'enzyme';
import { Room, Beacon, BeaconEvent, getBeaconInfoIdentifier } from 'matrix-js-sdk/src/matrix';
import { logger } from 'matrix-js-sdk/src/logger';
import { mount } from "enzyme";
import { Room, Beacon, BeaconEvent, getBeaconInfoIdentifier } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import RoomLiveShareWarning from '../../../../src/components/views/beacon/RoomLiveShareWarning';
import { OwnBeaconStore, OwnBeaconStoreEvent } from '../../../../src/stores/OwnBeaconStore';
import RoomLiveShareWarning from "../../../../src/components/views/beacon/RoomLiveShareWarning";
import { OwnBeaconStore, OwnBeaconStoreEvent } from "../../../../src/stores/OwnBeaconStore";
import {
advanceDateAndTime,
findByTestId,
@ -32,20 +32,20 @@ import {
mockGeolocation,
resetAsyncStoreWithClient,
setupAsyncStoreWithClient,
} from '../../../test-utils';
import defaultDispatcher from '../../../../src/dispatcher/dispatcher';
import { Action } from '../../../../src/dispatcher/actions';
} from "../../../test-utils";
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
import { Action } from "../../../../src/dispatcher/actions";
jest.useFakeTimers();
describe('<RoomLiveShareWarning />', () => {
const aliceId = '@alice:server.org';
const room1Id = '$room1:server.org';
const room2Id = '$room2:server.org';
const room3Id = '$room3:server.org';
describe("<RoomLiveShareWarning />", () => {
const aliceId = "@alice:server.org";
const room1Id = "$room1:server.org";
const room2Id = "$room2:server.org";
const room3Id = "$room3:server.org";
const mockClient = getMockClientWithEventEmitter({
getVisibleRooms: jest.fn().mockReturnValue([]),
getUserId: jest.fn().mockReturnValue(aliceId),
unstable_setLiveBeacon: jest.fn().mockResolvedValue({ event_id: '1' }),
unstable_setLiveBeacon: jest.fn().mockResolvedValue({ event_id: "1" }),
sendEvent: jest.fn(),
});
@ -54,14 +54,19 @@ describe('<RoomLiveShareWarning />', () => {
const MINUTE_MS = 60000;
const HOUR_MS = 3600000;
// mock the date so events are stable for snapshots etc
jest.spyOn(global.Date, 'now').mockReturnValue(now);
const room1Beacon1 = makeBeaconInfoEvent(aliceId, room1Id, {
isLive: true,
timeout: HOUR_MS,
}, '$0');
const room2Beacon1 = makeBeaconInfoEvent(aliceId, room2Id, { isLive: true, timeout: HOUR_MS }, '$1');
const room2Beacon2 = makeBeaconInfoEvent(aliceId, room2Id, { isLive: true, timeout: HOUR_MS * 12 }, '$2');
const room3Beacon1 = makeBeaconInfoEvent(aliceId, room3Id, { isLive: true, timeout: HOUR_MS }, '$3');
jest.spyOn(global.Date, "now").mockReturnValue(now);
const room1Beacon1 = makeBeaconInfoEvent(
aliceId,
room1Id,
{
isLive: true,
timeout: HOUR_MS,
},
"$0",
);
const room2Beacon1 = makeBeaconInfoEvent(aliceId, room2Id, { isLive: true, timeout: HOUR_MS }, "$1");
const room2Beacon2 = makeBeaconInfoEvent(aliceId, room2Id, { isLive: true, timeout: HOUR_MS * 12 }, "$2");
const room3Beacon1 = makeBeaconInfoEvent(aliceId, room3Id, { isLive: true, timeout: HOUR_MS }, "$3");
// make fresh rooms every time
// as we update room state
@ -96,42 +101,39 @@ describe('<RoomLiveShareWarning />', () => {
return component;
};
const localStorageSpy = jest.spyOn(localStorage.__proto__, 'getItem').mockReturnValue(undefined);
const localStorageSpy = jest.spyOn(localStorage.__proto__, "getItem").mockReturnValue(undefined);
beforeEach(() => {
mockGeolocation();
jest.spyOn(global.Date, 'now').mockReturnValue(now);
mockClient.unstable_setLiveBeacon.mockReset().mockResolvedValue({ event_id: '1' });
jest.spyOn(global.Date, "now").mockReturnValue(now);
mockClient.unstable_setLiveBeacon.mockReset().mockResolvedValue({ event_id: "1" });
// assume all beacons were created on this device
localStorageSpy.mockReturnValue(JSON.stringify([
room1Beacon1.getId(),
room2Beacon1.getId(),
room2Beacon2.getId(),
room3Beacon1.getId(),
]));
localStorageSpy.mockReturnValue(
JSON.stringify([room1Beacon1.getId(), room2Beacon1.getId(), room2Beacon2.getId(), room3Beacon1.getId()]),
);
});
afterEach(async () => {
jest.spyOn(OwnBeaconStore.instance, 'beaconHasLocationPublishError').mockRestore();
jest.spyOn(OwnBeaconStore.instance, "beaconHasLocationPublishError").mockRestore();
await resetAsyncStoreWithClient(OwnBeaconStore.instance);
});
afterAll(() => {
jest.spyOn(global.Date, 'now').mockRestore();
jest.spyOn(global.Date, "now").mockRestore();
localStorageSpy.mockRestore();
jest.spyOn(defaultDispatcher, 'dispatch').mockRestore();
jest.spyOn(defaultDispatcher, "dispatch").mockRestore();
});
const getExpiryText = wrapper => findByTestId(wrapper, 'room-live-share-expiry').text();
const getExpiryText = (wrapper) => findByTestId(wrapper, "room-live-share-expiry").text();
it('renders nothing when user has no live beacons at all', async () => {
it("renders nothing when user has no live beacons at all", async () => {
await makeOwnBeaconStore();
const component = getComponent();
expect(component.html()).toBe(null);
});
it('renders nothing when user has no live beacons in room', async () => {
it("renders nothing when user has no live beacons in room", async () => {
await act(async () => {
await makeRoomsWithStateEvents([room2Beacon1]);
await makeOwnBeaconStore();
@ -140,8 +142,8 @@ describe('<RoomLiveShareWarning />', () => {
expect(component.html()).toBe(null);
});
it('does not render when geolocation is not working', async () => {
jest.spyOn(logger, 'error').mockImplementation(() => { });
it("does not render when geolocation is not working", async () => {
jest.spyOn(logger, "error").mockImplementation(() => {});
// @ts-ignore
navigator.geolocation = undefined;
await act(async () => {
@ -155,7 +157,7 @@ describe('<RoomLiveShareWarning />', () => {
expect(component.html()).toBeNull();
});
describe('when user has live beacons and geolocation is available', () => {
describe("when user has live beacons and geolocation is available", () => {
beforeEach(async () => {
await act(async () => {
await makeRoomsWithStateEvents([room1Beacon1, room2Beacon1, room2Beacon2]);
@ -163,23 +165,23 @@ describe('<RoomLiveShareWarning />', () => {
});
});
it('renders correctly with one live beacon in room', () => {
it("renders correctly with one live beacon in room", () => {
const component = getComponent({ roomId: room1Id });
// beacons have generated ids that break snapshots
// assert on html
expect(component.html()).toMatchSnapshot();
});
it('renders correctly with two live beacons in room', () => {
it("renders correctly with two live beacons in room", () => {
const component = getComponent({ roomId: room2Id });
// beacons have generated ids that break snapshots
// assert on html
expect(component.html()).toMatchSnapshot();
// later expiry displayed
expect(getExpiryText(component)).toEqual('12h left');
expect(getExpiryText(component)).toEqual("12h left");
});
it('removes itself when user stops having live beacons', async () => {
it("removes itself when user stops having live beacons", async () => {
const component = getComponent({ roomId: room1Id });
// started out rendered
expect(component.html()).toBeTruthy();
@ -196,7 +198,7 @@ describe('<RoomLiveShareWarning />', () => {
expect(component.html()).toBe(null);
});
it('removes itself when user stops monitoring live position', async () => {
it("removes itself when user stops monitoring live position", async () => {
const component = getComponent({ roomId: room1Id });
// started out rendered
expect(component.html()).toBeTruthy();
@ -212,7 +214,7 @@ describe('<RoomLiveShareWarning />', () => {
expect(component.html()).toBe(null);
});
it('renders when user adds a live beacon', async () => {
it("renders when user adds a live beacon", async () => {
const component = getComponent({ roomId: room3Id });
// started out not rendered
expect(component.html()).toBeFalsy();
@ -225,40 +227,45 @@ describe('<RoomLiveShareWarning />', () => {
expect(component.html()).toBeTruthy();
});
it('updates beacon time left periodically', () => {
it("updates beacon time left periodically", () => {
const component = getComponent({ roomId: room1Id });
expect(getExpiryText(component)).toEqual('1h left');
expect(getExpiryText(component)).toEqual("1h left");
act(() => {
advanceDateAndTime(MINUTE_MS * 25);
});
expect(getExpiryText(component)).toEqual('35m left');
expect(getExpiryText(component)).toEqual("35m left");
});
it('updates beacon time left when beacon updates', () => {
it("updates beacon time left when beacon updates", () => {
const component = getComponent({ roomId: room1Id });
expect(getExpiryText(component)).toEqual('1h left');
expect(getExpiryText(component)).toEqual("1h left");
expect(getExpiryText(component)).toEqual('1h left');
expect(getExpiryText(component)).toEqual("1h left");
act(() => {
const beacon = OwnBeaconStore.instance.getBeaconById(getBeaconInfoIdentifier(room1Beacon1));
const room1Beacon1Update = makeBeaconInfoEvent(aliceId, room1Id, {
isLive: true,
timeout: 3 * HOUR_MS,
}, '$0');
const room1Beacon1Update = makeBeaconInfoEvent(
aliceId,
room1Id,
{
isLive: true,
timeout: 3 * HOUR_MS,
},
"$0",
);
beacon.update(room1Beacon1Update);
});
// update to expiry of new beacon
expect(getExpiryText(component)).toEqual('3h left');
expect(getExpiryText(component)).toEqual("3h left");
});
it('clears expiry time interval on unmount', () => {
const clearIntervalSpy = jest.spyOn(global, 'clearInterval');
it("clears expiry time interval on unmount", () => {
const clearIntervalSpy = jest.spyOn(global, "clearInterval");
const component = getComponent({ roomId: room1Id });
expect(getExpiryText(component)).toEqual('1h left');
expect(getExpiryText(component)).toEqual("1h left");
act(() => {
component.unmount();
@ -267,12 +274,12 @@ describe('<RoomLiveShareWarning />', () => {
expect(clearIntervalSpy).toHaveBeenCalled();
});
it('navigates to beacon tile on click', () => {
const dispatcherSpy = jest.spyOn(defaultDispatcher, 'dispatch');
it("navigates to beacon tile on click", () => {
const dispatcherSpy = jest.spyOn(defaultDispatcher, "dispatch");
const component = getComponent({ roomId: room1Id });
act(() => {
component.simulate('click');
component.simulate("click");
});
expect(dispatcherSpy).toHaveBeenCalledWith({
@ -285,30 +292,30 @@ describe('<RoomLiveShareWarning />', () => {
});
});
describe('stopping beacons', () => {
it('stops beacon on stop sharing click', () => {
describe("stopping beacons", () => {
it("stops beacon on stop sharing click", () => {
const component = getComponent({ roomId: room2Id });
act(() => {
findByTestId(component, 'room-live-share-primary-button').at(0).simulate('click');
findByTestId(component, "room-live-share-primary-button").at(0).simulate("click");
component.setProps({});
});
expect(mockClient.unstable_setLiveBeacon).toHaveBeenCalled();
expect(component.find('Spinner').length).toBeTruthy();
expect(findByTestId(component, 'room-live-share-primary-button').at(0).props().disabled).toBeTruthy();
expect(component.find("Spinner").length).toBeTruthy();
expect(findByTestId(component, "room-live-share-primary-button").at(0).props().disabled).toBeTruthy();
});
it('displays error when stop sharing fails', async () => {
it("displays error when stop sharing fails", async () => {
const component = getComponent({ roomId: room1Id });
// fail first time
mockClient.unstable_setLiveBeacon
.mockRejectedValueOnce(new Error('oups'))
.mockResolvedValue(({ event_id: '1' }));
.mockRejectedValueOnce(new Error("oups"))
.mockResolvedValue({ event_id: "1" });
await act(async () => {
findByTestId(component, 'room-live-share-primary-button').at(0).simulate('click');
findByTestId(component, "room-live-share-primary-button").at(0).simulate("click");
await flushPromisesWithFakeTimers();
});
component.setProps({});
@ -316,20 +323,20 @@ describe('<RoomLiveShareWarning />', () => {
expect(component.html()).toMatchSnapshot();
act(() => {
findByTestId(component, 'room-live-share-primary-button').at(0).simulate('click');
findByTestId(component, "room-live-share-primary-button").at(0).simulate("click");
component.setProps({});
});
expect(mockClient.unstable_setLiveBeacon).toHaveBeenCalledTimes(2);
});
it('displays again with correct state after stopping a beacon', () => {
it("displays again with correct state after stopping a beacon", () => {
// make sure the loading state is reset correctly after removing a beacon
const component = getComponent({ roomId: room1Id });
// stop the beacon
act(() => {
findByTestId(component, 'room-live-share-primary-button').at(0).simulate('click');
findByTestId(component, "room-live-share-primary-button").at(0).simulate("click");
});
// time travel until room1Beacon1 is expired
act(() => {
@ -345,28 +352,30 @@ describe('<RoomLiveShareWarning />', () => {
});
// button not disabled and expiry time shown
expect(findByTestId(component, 'room-live-share-primary-button').at(0).props().disabled).toBeFalsy();
expect(findByTestId(component, 'room-live-share-expiry').text()).toEqual('1h left');
expect(findByTestId(component, "room-live-share-primary-button").at(0).props().disabled).toBeFalsy();
expect(findByTestId(component, "room-live-share-expiry").text()).toEqual("1h left");
});
});
describe('with location publish errors', () => {
it('displays location publish error when mounted with location publish errors', async () => {
const locationPublishErrorSpy = jest.spyOn(OwnBeaconStore.instance, 'beaconHasLocationPublishError')
describe("with location publish errors", () => {
it("displays location publish error when mounted with location publish errors", async () => {
const locationPublishErrorSpy = jest
.spyOn(OwnBeaconStore.instance, "beaconHasLocationPublishError")
.mockReturnValue(true);
const component = getComponent({ roomId: room2Id });
expect(component).toMatchSnapshot();
expect(locationPublishErrorSpy).toHaveBeenCalledWith(
getBeaconInfoIdentifier(room2Beacon1), 0, [getBeaconInfoIdentifier(room2Beacon1)],
);
expect(locationPublishErrorSpy).toHaveBeenCalledWith(getBeaconInfoIdentifier(room2Beacon1), 0, [
getBeaconInfoIdentifier(room2Beacon1),
]);
});
it(
'displays location publish error when locationPublishError event is emitted' +
' and beacons have errors',
"displays location publish error when locationPublishError event is emitted" +
" and beacons have errors",
async () => {
const locationPublishErrorSpy = jest.spyOn(OwnBeaconStore.instance, 'beaconHasLocationPublishError')
const locationPublishErrorSpy = jest
.spyOn(OwnBeaconStore.instance, "beaconHasLocationPublishError")
.mockReturnValue(false);
const component = getComponent({ roomId: room2Id });
@ -374,20 +383,23 @@ describe('<RoomLiveShareWarning />', () => {
act(() => {
locationPublishErrorSpy.mockReturnValue(true);
OwnBeaconStore.instance.emit(
OwnBeaconStoreEvent.LocationPublishError, getBeaconInfoIdentifier(room2Beacon1),
OwnBeaconStoreEvent.LocationPublishError,
getBeaconInfoIdentifier(room2Beacon1),
);
});
component.setProps({});
// renders wire error ui
expect(component.find('.mx_RoomLiveShareWarning_label').text()).toEqual(
'An error occurred whilst sharing your live location, please try again',
expect(component.find(".mx_RoomLiveShareWarning_label").text()).toEqual(
"An error occurred whilst sharing your live location, please try again",
);
expect(findByTestId(component, 'room-live-share-wire-error-close-button').length).toBeTruthy();
});
expect(findByTestId(component, "room-live-share-wire-error-close-button").length).toBeTruthy();
},
);
it('stops displaying wire error when errors are cleared', async () => {
const locationPublishErrorSpy = jest.spyOn(OwnBeaconStore.instance, 'beaconHasLocationPublishError')
it("stops displaying wire error when errors are cleared", async () => {
const locationPublishErrorSpy = jest
.spyOn(OwnBeaconStore.instance, "beaconHasLocationPublishError")
.mockReturnValue(true);
const component = getComponent({ roomId: room2Id });
@ -395,39 +407,40 @@ describe('<RoomLiveShareWarning />', () => {
act(() => {
locationPublishErrorSpy.mockReturnValue(false);
OwnBeaconStore.instance.emit(
OwnBeaconStoreEvent.LocationPublishError, getBeaconInfoIdentifier(room2Beacon1),
OwnBeaconStoreEvent.LocationPublishError,
getBeaconInfoIdentifier(room2Beacon1),
);
});
component.setProps({});
// renders error-free ui
expect(component.find('.mx_RoomLiveShareWarning_label').text()).toEqual(
'You are sharing your live location',
expect(component.find(".mx_RoomLiveShareWarning_label").text()).toEqual(
"You are sharing your live location",
);
expect(findByTestId(component, 'room-live-share-wire-error-close-button').length).toBeFalsy();
expect(findByTestId(component, "room-live-share-wire-error-close-button").length).toBeFalsy();
});
it('clicking retry button resets location publish errors', async () => {
jest.spyOn(OwnBeaconStore.instance, 'beaconHasLocationPublishError').mockReturnValue(true);
const resetErrorSpy = jest.spyOn(OwnBeaconStore.instance, 'resetLocationPublishError');
it("clicking retry button resets location publish errors", async () => {
jest.spyOn(OwnBeaconStore.instance, "beaconHasLocationPublishError").mockReturnValue(true);
const resetErrorSpy = jest.spyOn(OwnBeaconStore.instance, "resetLocationPublishError");
const component = getComponent({ roomId: room2Id });
act(() => {
findByTestId(component, 'room-live-share-primary-button').at(0).simulate('click');
findByTestId(component, "room-live-share-primary-button").at(0).simulate("click");
});
expect(resetErrorSpy).toHaveBeenCalledWith(getBeaconInfoIdentifier(room2Beacon1));
});
it('clicking close button stops beacons', async () => {
jest.spyOn(OwnBeaconStore.instance, 'beaconHasLocationPublishError').mockReturnValue(true);
const stopBeaconSpy = jest.spyOn(OwnBeaconStore.instance, 'stopBeacon');
it("clicking close button stops beacons", async () => {
jest.spyOn(OwnBeaconStore.instance, "beaconHasLocationPublishError").mockReturnValue(true);
const stopBeaconSpy = jest.spyOn(OwnBeaconStore.instance, "stopBeacon");
const component = getComponent({ roomId: room2Id });
act(() => {
findByTestId(component, 'room-live-share-wire-error-close-button').at(0).simulate('click');
findByTestId(component, "room-live-share-wire-error-close-button").at(0).simulate("click");
});
expect(stopBeaconSpy).toHaveBeenCalledWith(getBeaconInfoIdentifier(room2Beacon1));

View file

@ -14,21 +14,21 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { fireEvent, render } from "@testing-library/react";
import ShareLatestLocation from '../../../../src/components/views/beacon/ShareLatestLocation';
import { copyPlaintext } from '../../../../src/utils/strings';
import { flushPromises } from '../../../test-utils';
import ShareLatestLocation from "../../../../src/components/views/beacon/ShareLatestLocation";
import { copyPlaintext } from "../../../../src/utils/strings";
import { flushPromises } from "../../../test-utils";
jest.mock('../../../../src/utils/strings', () => ({
jest.mock("../../../../src/utils/strings", () => ({
copyPlaintext: jest.fn().mockResolvedValue(undefined),
}));
describe('<ShareLatestLocation />', () => {
describe("<ShareLatestLocation />", () => {
const defaultProps = {
latestLocationState: {
uri: 'geo:51,42;u=35',
uri: "geo:51,42;u=35",
timestamp: 123,
},
};
@ -38,18 +38,18 @@ describe('<ShareLatestLocation />', () => {
jest.clearAllMocks();
});
it('renders null when no location', () => {
it("renders null when no location", () => {
const { container } = getComponent({ latestLocationState: undefined });
expect(container.innerHTML).toBeFalsy();
});
it('renders share buttons when there is a location', async () => {
it("renders share buttons when there is a location", async () => {
const { container, asFragment } = getComponent();
expect(asFragment()).toMatchSnapshot();
fireEvent.click(container.querySelector('.mx_CopyableText_copyButton'));
fireEvent.click(container.querySelector(".mx_CopyableText_copyButton"));
await flushPromises();
expect(copyPlaintext).toHaveBeenCalledWith('51,42');
expect(copyPlaintext).toHaveBeenCalledWith("51,42");
});
});

View file

@ -14,16 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { render } from "@testing-library/react";
import StyledLiveBeaconIcon from '../../../../src/components/views/beacon/StyledLiveBeaconIcon';
import StyledLiveBeaconIcon from "../../../../src/components/views/beacon/StyledLiveBeaconIcon";
describe('<StyledLiveBeaconIcon />', () => {
describe("<StyledLiveBeaconIcon />", () => {
const defaultProps = {};
const getComponent = (props = {}) => render(<StyledLiveBeaconIcon {...defaultProps} {...props} />);
it('renders', () => {
it("renders", () => {
const { asFragment } = getComponent();
expect(asFragment()).toMatchSnapshot();
});

View file

@ -25,7 +25,7 @@ import SettingsStore from "../../../../src/settings/SettingsStore";
jest.mock("../../../../src/utils/Feedback");
jest.mock("../../../../src/settings/SettingsStore");
describe('<BetaCard />', () => {
describe("<BetaCard />", () => {
describe("Feedback prompt", () => {
const featureId = "featureId";

View file

@ -27,10 +27,13 @@ describe("<ContextMenu />", () => {
// Hardcode window and menu dimensions
const windowSize = 300;
const menuSize = 200;
jest.spyOn(UIStore, "instance", "get").mockImplementation(() => ({
windowWidth: windowSize,
windowHeight: windowSize,
}) as unknown as UIStore);
jest.spyOn(UIStore, "instance", "get").mockImplementation(
() =>
({
windowWidth: windowSize,
windowHeight: windowSize,
} as unknown as UIStore),
);
window.Element.prototype.getBoundingClientRect = jest.fn().mockReturnValue({
width: menuSize,
height: menuSize,

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, ReactWrapper } from 'enzyme';
import { EventStatus, MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { Room } from 'matrix-js-sdk/src/models/room';
import { mount, ReactWrapper } from "enzyme";
import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Room } from "matrix-js-sdk/src/models/room";
import {
PendingEventOrdering,
BeaconIdentifier,
Beacon,
getBeaconInfoIdentifier,
EventType,
} from 'matrix-js-sdk/src/matrix';
import { ExtensibleEvent, MessageEvent, M_POLL_KIND_DISCLOSED, PollStartEvent } from 'matrix-events-sdk';
} from "matrix-js-sdk/src/matrix";
import { ExtensibleEvent, MessageEvent, M_POLL_KIND_DISCLOSED, PollStartEvent } from "matrix-events-sdk";
import { FeatureSupport, Thread } from "matrix-js-sdk/src/models/thread";
import { mocked } from "jest-mock";
import { act } from '@testing-library/react';
import { act } from "@testing-library/react";
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import RoomContext, { TimelineRenderingType } from "../../../../src/contexts/RoomContext";
import { IRoomState } from "../../../../src/components/structures/RoomView";
import { canEditContent } from "../../../../src/utils/EventUtils";
import { copyPlaintext, getSelectedText } from "../../../../src/utils/strings";
import MessageContextMenu from "../../../../src/components/views/context_menus/MessageContextMenu";
import { makeBeaconEvent, makeBeaconInfoEvent, makeLocationEvent, stubClient } from '../../../test-utils';
import dispatcher from '../../../../src/dispatcher/dispatcher';
import SettingsStore from '../../../../src/settings/SettingsStore';
import { ReadPinsEventId } from '../../../../src/components/views/right_panel/types';
import { makeBeaconEvent, makeBeaconInfoEvent, makeLocationEvent, stubClient } from "../../../test-utils";
import dispatcher from "../../../../src/dispatcher/dispatcher";
import SettingsStore from "../../../../src/settings/SettingsStore";
import { ReadPinsEventId } from "../../../../src/components/views/right_panel/types";
import { Action } from "../../../../src/dispatcher/actions";
jest.mock("../../../../src/utils/strings", () => ({
@ -52,17 +52,17 @@ jest.mock("../../../../src/utils/EventUtils", () => ({
...jest.requireActual("../../../../src/utils/EventUtils"),
canEditContent: jest.fn(),
}));
jest.mock('../../../../src/dispatcher/dispatcher');
jest.mock("../../../../src/dispatcher/dispatcher");
const roomId = 'roomid';
const roomId = "roomid";
describe('MessageContextMenu', () => {
describe("MessageContextMenu", () => {
beforeEach(() => {
jest.resetAllMocks();
stubClient();
});
it('does show copy link button when supplied a link', () => {
it("does show copy link button when supplied a link", () => {
const eventContent = MessageEvent.from("hello");
const props = {
link: "https://google.com/",
@ -73,119 +73,123 @@ describe('MessageContextMenu', () => {
expect(copyLinkButton.props().href).toBe(props.link);
});
it('does not show copy link button when not supplied a link', () => {
it("does not show copy link button when not supplied a link", () => {
const eventContent = MessageEvent.from("hello");
const menu = createMenuWithContent(eventContent);
const copyLinkButton = menu.find('a[aria-label="Copy link"]');
expect(copyLinkButton).toHaveLength(0);
});
describe('message pinning', () => {
describe("message pinning", () => {
beforeEach(() => {
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(true);
jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
});
afterAll(() => {
jest.spyOn(SettingsStore, 'getValue').mockRestore();
jest.spyOn(SettingsStore, "getValue").mockRestore();
});
it('does not show pin option when user does not have rights to pin', () => {
it("does not show pin option when user does not have rights to pin", () => {
const eventContent = MessageEvent.from("hello");
const event = new MatrixEvent(eventContent.serialize());
const room = makeDefaultRoom();
// mock permission to disallow adding pinned messages to room
jest.spyOn(room.currentState, 'mayClientSendStateEvent').mockReturnValue(false);
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(false);
const menu = createMenu(event, {}, {}, undefined, room);
expect(menu.find('div[aria-label="Pin"]')).toHaveLength(0);
});
it('does not show pin option for beacon_info event', () => {
const deadBeaconEvent = makeBeaconInfoEvent('@alice:server.org', roomId, { isLive: false });
it("does not show pin option for beacon_info event", () => {
const deadBeaconEvent = makeBeaconInfoEvent("@alice:server.org", roomId, { isLive: false });
const room = makeDefaultRoom();
// mock permission to allow adding pinned messages to room
jest.spyOn(room.currentState, 'mayClientSendStateEvent').mockReturnValue(true);
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
const menu = createMenu(deadBeaconEvent, {}, {}, undefined, room);
expect(menu.find('div[aria-label="Pin"]')).toHaveLength(0);
});
it('does not show pin option when pinning feature is disabled', () => {
it("does not show pin option when pinning feature is disabled", () => {
const eventContent = MessageEvent.from("hello");
const pinnableEvent = new MatrixEvent({ ...eventContent.serialize(), room_id: roomId });
const room = makeDefaultRoom();
// mock permission to allow adding pinned messages to room
jest.spyOn(room.currentState, 'mayClientSendStateEvent').mockReturnValue(true);
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
// disable pinning feature
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false);
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
const menu = createMenu(pinnableEvent, {}, {}, undefined, room);
expect(menu.find('div[aria-label="Pin"]')).toHaveLength(0);
});
it('shows pin option when pinning feature is enabled', () => {
it("shows pin option when pinning feature is enabled", () => {
const eventContent = MessageEvent.from("hello");
const pinnableEvent = new MatrixEvent({ ...eventContent.serialize(), room_id: roomId });
const room = makeDefaultRoom();
// mock permission to allow adding pinned messages to room
jest.spyOn(room.currentState, 'mayClientSendStateEvent').mockReturnValue(true);
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
const menu = createMenu(pinnableEvent, {}, {}, undefined, room);
expect(menu.find('div[aria-label="Pin"]')).toHaveLength(1);
});
it('pins event on pin option click', () => {
it("pins event on pin option click", () => {
const onFinished = jest.fn();
const eventContent = MessageEvent.from("hello");
const pinnableEvent = new MatrixEvent({ ...eventContent.serialize(), room_id: roomId });
pinnableEvent.event.event_id = '!3';
pinnableEvent.event.event_id = "!3";
const client = MatrixClientPeg.get();
const room = makeDefaultRoom();
// mock permission to allow adding pinned messages to room
jest.spyOn(room.currentState, 'mayClientSendStateEvent').mockReturnValue(true);
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
// mock read pins account data
const pinsAccountData = new MatrixEvent({ content: { event_ids: ['!1', '!2'] } });
jest.spyOn(room, 'getAccountData').mockReturnValue(pinsAccountData);
const pinsAccountData = new MatrixEvent({ content: { event_ids: ["!1", "!2"] } });
jest.spyOn(room, "getAccountData").mockReturnValue(pinsAccountData);
const menu = createMenu(pinnableEvent, { onFinished }, {}, undefined, room);
act(() => {
menu.find('div[aria-label="Pin"]').simulate('click');
menu.find('div[aria-label="Pin"]').simulate("click");
});
// added to account data
expect(client.setRoomAccountData).toHaveBeenCalledWith(
roomId,
ReadPinsEventId,
{ event_ids: [
expect(client.setRoomAccountData).toHaveBeenCalledWith(roomId, ReadPinsEventId, {
event_ids: [
// from account data
'!1', '!2',
"!1",
"!2",
pinnableEvent.getId(),
],
},
);
});
// add to room's pins
expect(client.sendStateEvent).toHaveBeenCalledWith(roomId, EventType.RoomPinnedEvents, {
pinned: [pinnableEvent.getId()] }, "");
expect(client.sendStateEvent).toHaveBeenCalledWith(
roomId,
EventType.RoomPinnedEvents,
{
pinned: [pinnableEvent.getId()],
},
"",
);
expect(onFinished).toHaveBeenCalled();
});
it('unpins event on pin option click when event is pinned', () => {
it("unpins event on pin option click when event is pinned", () => {
const eventContent = MessageEvent.from("hello");
const pinnableEvent = new MatrixEvent({ ...eventContent.serialize(), room_id: roomId });
pinnableEvent.event.event_id = '!3';
pinnableEvent.event.event_id = "!3";
const client = MatrixClientPeg.get();
const room = makeDefaultRoom();
@ -194,52 +198,53 @@ describe('MessageContextMenu', () => {
type: EventType.RoomPinnedEvents,
room_id: roomId,
state_key: "",
content: { pinned: [pinnableEvent.getId(), '!another-event'] },
content: { pinned: [pinnableEvent.getId(), "!another-event"] },
});
room.currentState.setStateEvents([pinEvent]);
// mock permission to allow adding pinned messages to room
jest.spyOn(room.currentState, 'mayClientSendStateEvent').mockReturnValue(true);
jest.spyOn(room.currentState, "mayClientSendStateEvent").mockReturnValue(true);
// mock read pins account data
const pinsAccountData = new MatrixEvent({ content: { event_ids: ['!1', '!2'] } });
jest.spyOn(room, 'getAccountData').mockReturnValue(pinsAccountData);
const pinsAccountData = new MatrixEvent({ content: { event_ids: ["!1", "!2"] } });
jest.spyOn(room, "getAccountData").mockReturnValue(pinsAccountData);
const menu = createMenu(pinnableEvent, {}, {}, undefined, room);
act(() => {
menu.find('div[aria-label="Unpin"]').simulate('click');
menu.find('div[aria-label="Unpin"]').simulate("click");
});
expect(client.setRoomAccountData).not.toHaveBeenCalled();
// add to room's pins
expect(client.sendStateEvent).toHaveBeenCalledWith(
roomId, EventType.RoomPinnedEvents,
roomId,
EventType.RoomPinnedEvents,
// pinnableEvent's id removed, other pins intact
{ pinned: ['!another-event'] },
{ pinned: ["!another-event"] },
"",
);
});
});
describe('message forwarding', () => {
it('allows forwarding a room message', () => {
describe("message forwarding", () => {
it("allows forwarding a room message", () => {
const eventContent = MessageEvent.from("hello");
const menu = createMenuWithContent(eventContent);
expect(menu.find('div[aria-label="Forward"]')).toHaveLength(1);
});
it('does not allow forwarding a poll', () => {
it("does not allow forwarding a poll", () => {
const eventContent = PollStartEvent.from("why?", ["42"], M_POLL_KIND_DISCLOSED);
const menu = createMenuWithContent(eventContent);
expect(menu.find('div[aria-label="Forward"]')).toHaveLength(0);
});
describe('forwarding beacons', () => {
describe("forwarding beacons", () => {
const aliceId = "@alice:server.org";
it('does not allow forwarding a beacon that is not live', () => {
it("does not allow forwarding a beacon that is not live", () => {
const deadBeaconEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: false });
const beacon = new Beacon(deadBeaconEvent);
const beacons = new Map<BeaconIdentifier, Beacon>();
@ -248,11 +253,12 @@ describe('MessageContextMenu', () => {
expect(menu.find('div[aria-label="Forward"]')).toHaveLength(0);
});
it('does not allow forwarding a beacon that is not live but has a latestLocation', () => {
it("does not allow forwarding a beacon that is not live but has a latestLocation", () => {
const deadBeaconEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: false });
const beaconLocation = makeBeaconEvent(
aliceId, { beaconInfoId: deadBeaconEvent.getId(), geoUri: 'geo:51,41' },
);
const beaconLocation = makeBeaconEvent(aliceId, {
beaconInfoId: deadBeaconEvent.getId(),
geoUri: "geo:51,41",
});
const beacon = new Beacon(deadBeaconEvent);
// @ts-ignore illegally set private prop
beacon._latestLocationEvent = beaconLocation;
@ -262,7 +268,7 @@ describe('MessageContextMenu', () => {
expect(menu.find('div[aria-label="Forward"]')).toHaveLength(0);
});
it('does not allow forwarding a live beacon that does not have a latestLocation', () => {
it("does not allow forwarding a live beacon that does not have a latestLocation", () => {
const beaconEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: true });
const beacon = new Beacon(beaconEvent);
@ -272,11 +278,12 @@ describe('MessageContextMenu', () => {
expect(menu.find('div[aria-label="Forward"]')).toHaveLength(0);
});
it('allows forwarding a live beacon that has a location', () => {
it("allows forwarding a live beacon that has a location", () => {
const liveBeaconEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: true });
const beaconLocation = makeBeaconEvent(
aliceId, { beaconInfoId: liveBeaconEvent.getId(), geoUri: 'geo:51,41' },
);
const beaconLocation = makeBeaconEvent(aliceId, {
beaconInfoId: liveBeaconEvent.getId(),
geoUri: "geo:51,41",
});
const beacon = new Beacon(liveBeaconEvent);
// @ts-ignore illegally set private prop
beacon._latestLocationEvent = beaconLocation;
@ -286,12 +293,13 @@ describe('MessageContextMenu', () => {
expect(menu.find('div[aria-label="Forward"]')).toHaveLength(1);
});
it('opens forward dialog with correct event', () => {
const dispatchSpy = jest.spyOn(dispatcher, 'dispatch');
it("opens forward dialog with correct event", () => {
const dispatchSpy = jest.spyOn(dispatcher, "dispatch");
const liveBeaconEvent = makeBeaconInfoEvent(aliceId, roomId, { isLive: true });
const beaconLocation = makeBeaconEvent(
aliceId, { beaconInfoId: liveBeaconEvent.getId(), geoUri: 'geo:51,41' },
);
const beaconLocation = makeBeaconEvent(aliceId, {
beaconInfoId: liveBeaconEvent.getId(),
geoUri: "geo:51,41",
});
const beacon = new Beacon(liveBeaconEvent);
// @ts-ignore illegally set private prop
beacon._latestLocationEvent = beaconLocation;
@ -300,26 +308,28 @@ describe('MessageContextMenu', () => {
const menu = createMenu(liveBeaconEvent, {}, {}, beacons);
act(() => {
menu.find('div[aria-label="Forward"]').simulate('click');
menu.find('div[aria-label="Forward"]').simulate("click");
});
// called with forwardableEvent, not beaconInfo event
expect(dispatchSpy).toHaveBeenCalledWith(expect.objectContaining({
event: beaconLocation,
}));
expect(dispatchSpy).toHaveBeenCalledWith(
expect.objectContaining({
event: beaconLocation,
}),
);
});
});
});
describe('open as map link', () => {
it('does not allow opening a plain message in open street maps', () => {
describe("open as map link", () => {
it("does not allow opening a plain message in open street maps", () => {
const eventContent = MessageEvent.from("hello");
const menu = createMenuWithContent(eventContent);
expect(menu.find('a[aria-label="Open in OpenStreetMap"]')).toHaveLength(0);
});
it('does not allow opening a beacon that does not have a shareable location event', () => {
const deadBeaconEvent = makeBeaconInfoEvent('@alice', roomId, { isLive: false });
it("does not allow opening a beacon that does not have a shareable location event", () => {
const deadBeaconEvent = makeBeaconInfoEvent("@alice", roomId, { isLive: false });
const beacon = new Beacon(deadBeaconEvent);
const beacons = new Map<BeaconIdentifier, Beacon>();
beacons.set(getBeaconInfoIdentifier(deadBeaconEvent), beacon);
@ -327,20 +337,21 @@ describe('MessageContextMenu', () => {
expect(menu.find('a[aria-label="Open in OpenStreetMap"]')).toHaveLength(0);
});
it('allows opening a location event in open street map', () => {
const locationEvent = makeLocationEvent('geo:50,50');
it("allows opening a location event in open street map", () => {
const locationEvent = makeLocationEvent("geo:50,50");
const menu = createMenu(locationEvent);
// exists with a href with the lat/lon from the location event
expect(
menu.find('a[aria-label="Open in OpenStreetMap"]').at(0).props().href,
).toEqual('https://www.openstreetmap.org/?mlat=50&mlon=50#map=16/50/50');
expect(menu.find('a[aria-label="Open in OpenStreetMap"]').at(0).props().href).toEqual(
"https://www.openstreetmap.org/?mlat=50&mlon=50#map=16/50/50",
);
});
it('allows opening a beacon that has a shareable location event', () => {
const liveBeaconEvent = makeBeaconInfoEvent('@alice', roomId, { isLive: true });
const beaconLocation = makeBeaconEvent(
'@alice', { beaconInfoId: liveBeaconEvent.getId(), geoUri: 'geo:51,41' },
);
it("allows opening a beacon that has a shareable location event", () => {
const liveBeaconEvent = makeBeaconInfoEvent("@alice", roomId, { isLive: true });
const beaconLocation = makeBeaconEvent("@alice", {
beaconInfoId: liveBeaconEvent.getId(),
geoUri: "geo:51,41",
});
const beacon = new Beacon(liveBeaconEvent);
// @ts-ignore illegally set private prop
beacon._latestLocationEvent = beaconLocation;
@ -348,14 +359,14 @@ describe('MessageContextMenu', () => {
beacons.set(getBeaconInfoIdentifier(liveBeaconEvent), beacon);
const menu = createMenu(liveBeaconEvent, {}, {}, beacons);
// exists with a href with the lat/lon from the location event
expect(
menu.find('a[aria-label="Open in OpenStreetMap"]').at(0).props().href,
).toEqual('https://www.openstreetmap.org/?mlat=51&mlon=41#map=16/51/41');
expect(menu.find('a[aria-label="Open in OpenStreetMap"]').at(0).props().href).toEqual(
"https://www.openstreetmap.org/?mlat=51&mlon=41#map=16/51/41",
);
});
});
describe("right click", () => {
it('copy button does work as expected', () => {
it("copy button does work as expected", () => {
const text = "hello";
const eventContent = MessageEvent.from(text);
mocked(getSelectedText).mockReturnValue(text);
@ -366,7 +377,7 @@ describe('MessageContextMenu', () => {
expect(copyPlaintext).toHaveBeenCalledWith(text);
});
it('copy button is not shown when there is nothing to copy', () => {
it("copy button is not shown when there is nothing to copy", () => {
const text = "hello";
const eventContent = MessageEvent.from(text);
mocked(getSelectedText).mockReturnValue("");
@ -376,7 +387,7 @@ describe('MessageContextMenu', () => {
expect(copyButton).toHaveLength(0);
});
it('shows edit button when we can edit', () => {
it("shows edit button when we can edit", () => {
const eventContent = MessageEvent.from("hello");
mocked(canEditContent).mockReturnValue(true);
@ -385,7 +396,7 @@ describe('MessageContextMenu', () => {
expect(editButton).toHaveLength(1);
});
it('does not show edit button when we cannot edit', () => {
it("does not show edit button when we cannot edit", () => {
const eventContent = MessageEvent.from("hello");
mocked(canEditContent).mockReturnValue(false);
@ -394,7 +405,7 @@ describe('MessageContextMenu', () => {
expect(editButton).toHaveLength(0);
});
it('shows reply button when we can reply', () => {
it("shows reply button when we can reply", () => {
const eventContent = MessageEvent.from("hello");
const context = {
canSendMessages: true,
@ -405,7 +416,7 @@ describe('MessageContextMenu', () => {
expect(replyButton).toHaveLength(1);
});
it('does not show reply button when we cannot reply', () => {
it("does not show reply button when we cannot reply", () => {
const eventContent = MessageEvent.from("hello");
const context = {
canSendMessages: true,
@ -419,7 +430,7 @@ describe('MessageContextMenu', () => {
expect(replyButton).toHaveLength(0);
});
it('shows react button when we can react', () => {
it("shows react button when we can react", () => {
const eventContent = MessageEvent.from("hello");
const context = {
canReact: true,
@ -430,7 +441,7 @@ describe('MessageContextMenu', () => {
expect(reactButton).toHaveLength(1);
});
it('does not show react button when we cannot react', () => {
it("does not show react button when we cannot react", () => {
const eventContent = MessageEvent.from("hello");
const context = {
canReact: false,
@ -441,10 +452,10 @@ describe('MessageContextMenu', () => {
expect(reactButton).toHaveLength(0);
});
it('shows view in room button when the event is a thread root', () => {
it("shows view in room button when the event is a thread root", () => {
const eventContent = MessageEvent.from("hello");
const mxEvent = new MatrixEvent(eventContent.serialize());
mxEvent.getThread = () => ({ rootEvent: mxEvent }) as Thread;
mxEvent.getThread = () => ({ rootEvent: mxEvent } as Thread);
const props = {
rightClick: true,
};
@ -457,7 +468,7 @@ describe('MessageContextMenu', () => {
expect(reactButton).toHaveLength(1);
});
it('does not show view in room button when the event is not a thread root', () => {
it("does not show view in room button when the event is not a thread root", () => {
const eventContent = MessageEvent.from("hello");
const menu = createRightClickMenuWithContent(eventContent);
@ -465,7 +476,7 @@ describe('MessageContextMenu', () => {
expect(reactButton).toHaveLength(0);
});
it('creates a new thread on reply in thread click', () => {
it("creates a new thread on reply in thread click", () => {
const eventContent = MessageEvent.from("hello");
const mxEvent = new MatrixEvent(eventContent.serialize());
@ -473,7 +484,7 @@ describe('MessageContextMenu', () => {
const context = {
canSendMessages: true,
};
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(true);
jest.spyOn(SettingsStore, "getValue").mockReturnValue(true);
const menu = createRightClickMenu(mxEvent, context);
@ -490,10 +501,7 @@ describe('MessageContextMenu', () => {
});
});
function createRightClickMenuWithContent(
eventContent: ExtensibleEvent,
context?: Partial<IRoomState>,
): ReactWrapper {
function createRightClickMenuWithContent(eventContent: ExtensibleEvent, context?: Partial<IRoomState>): ReactWrapper {
return createMenuWithContent(eventContent, { rightClick: true }, context);
}
@ -511,14 +519,9 @@ function createMenuWithContent(
}
function makeDefaultRoom(): Room {
return new Room(
roomId,
MatrixClientPeg.get(),
"@user:example.com",
{
pendingEventOrdering: PendingEventOrdering.Detached,
},
);
return new Room(roomId, MatrixClientPeg.get(), "@user:example.com", {
pendingEventOrdering: PendingEventOrdering.Detached,
});
}
function createMenu(
@ -540,12 +543,7 @@ function createMenu(
return mount(
<RoomContext.Provider value={context as IRoomState}>
<MessageContextMenu
chevronFace={null}
mxEvent={mxEvent}
onFinished={jest.fn()}
{...props}
/>
<MessageContextMenu chevronFace={null} mxEvent={mxEvent} onFinished={jest.fn()} {...props} />
</RoomContext.Provider>,
);
}

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 { Room } from 'matrix-js-sdk/src/matrix';
import { mocked } from 'jest-mock';
import { act } from 'react-dom/test-utils';
import 'focus-visible'; // to fix context menus
import { mount } from "enzyme";
import { Room } from "matrix-js-sdk/src/matrix";
import { mocked } from "jest-mock";
import { act } from "react-dom/test-utils";
import "focus-visible"; // to fix context menus
import SpaceContextMenu from '../../../../src/components/views/context_menus/SpaceContextMenu';
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
import { findByTestId } from '../../../test-utils';
import SpaceContextMenu from "../../../../src/components/views/context_menus/SpaceContextMenu";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import { findByTestId } from "../../../test-utils";
import {
shouldShowSpaceSettings,
showCreateNewRoom,
showCreateNewSubspace,
showSpaceInvite,
showSpaceSettings,
} from '../../../../src/utils/space';
} from "../../../../src/utils/space";
import { leaveSpace } from "../../../../src/utils/leave-behaviour";
import { shouldShowComponent } from '../../../../src/customisations/helpers/UIComponents';
import { UIComponent } from '../../../../src/settings/UIFeature';
import { shouldShowComponent } from "../../../../src/customisations/helpers/UIComponents";
import { UIComponent } from "../../../../src/settings/UIFeature";
jest.mock('../../../../src/customisations/helpers/UIComponents', () => ({
jest.mock("../../../../src/customisations/helpers/UIComponents", () => ({
shouldShowComponent: jest.fn(),
}));
jest.mock('../../../../src/utils/space', () => ({
jest.mock("../../../../src/utils/space", () => ({
shouldShowSpaceSettings: jest.fn(),
showCreateNewRoom: jest.fn(),
showCreateNewSubspace: jest.fn(),
@ -49,172 +49,172 @@ jest.mock('../../../../src/utils/space', () => ({
showSpaceSettings: jest.fn(),
}));
jest.mock('../../../../src/utils/leave-behaviour', () => ({
jest.mock("../../../../src/utils/leave-behaviour", () => ({
leaveSpace: jest.fn(),
}));
describe('<SpaceContextMenu />', () => {
const userId = '@test:server';
describe("<SpaceContextMenu />", () => {
const userId = "@test:server";
const mockClient = {
getUserId: jest.fn().mockReturnValue(userId),
};
const makeMockSpace = (props = {}) => ({
name: 'test space',
getJoinRule: jest.fn(),
canInvite: jest.fn(),
currentState: {
maySendStateEvent: jest.fn(),
},
client: mockClient,
getMyMembership: jest.fn(),
...props,
}) as unknown as Room;
const makeMockSpace = (props = {}) =>
({
name: "test space",
getJoinRule: jest.fn(),
canInvite: jest.fn(),
currentState: {
maySendStateEvent: jest.fn(),
},
client: mockClient,
getMyMembership: jest.fn(),
...props,
} as unknown as Room);
const defaultProps = {
space: makeMockSpace(),
onFinished: jest.fn(),
};
const getComponent = (props = {}) =>
mount(<SpaceContextMenu {...defaultProps} {...props} />,
{
wrappingComponent: MatrixClientContext.Provider,
wrappingComponentProps: {
value: mockClient,
},
});
mount(<SpaceContextMenu {...defaultProps} {...props} />, {
wrappingComponent: MatrixClientContext.Provider,
wrappingComponentProps: {
value: mockClient,
},
});
beforeEach(() => {
jest.resetAllMocks();
mockClient.getUserId.mockReturnValue(userId);
});
it('renders menu correctly', () => {
it("renders menu correctly", () => {
const component = getComponent();
expect(component).toMatchSnapshot();
});
it('renders invite option when space is public', () => {
it("renders invite option when space is public", () => {
const space = makeMockSpace({
getJoinRule: jest.fn().mockReturnValue('public'),
getJoinRule: jest.fn().mockReturnValue("public"),
});
const component = getComponent({ space });
expect(findByTestId(component, 'invite-option').length).toBeTruthy();
expect(findByTestId(component, "invite-option").length).toBeTruthy();
});
it('renders invite option when user is has invite rights for space', () => {
it("renders invite option when user is has invite rights for space", () => {
const space = makeMockSpace({
canInvite: jest.fn().mockReturnValue(true),
});
const component = getComponent({ space });
expect(space.canInvite).toHaveBeenCalledWith(userId);
expect(findByTestId(component, 'invite-option').length).toBeTruthy();
expect(findByTestId(component, "invite-option").length).toBeTruthy();
});
it('opens invite dialog when invite option is clicked', () => {
it("opens invite dialog when invite option is clicked", () => {
const space = makeMockSpace({
getJoinRule: jest.fn().mockReturnValue('public'),
getJoinRule: jest.fn().mockReturnValue("public"),
});
const onFinished = jest.fn();
const component = getComponent({ space, onFinished });
act(() => {
findByTestId(component, 'invite-option').at(0).simulate('click');
findByTestId(component, "invite-option").at(0).simulate("click");
});
expect(showSpaceInvite).toHaveBeenCalledWith(space);
expect(onFinished).toHaveBeenCalled();
});
it('renders space settings option when user has rights', () => {
it("renders space settings option when user has rights", () => {
mocked(shouldShowSpaceSettings).mockReturnValue(true);
const component = getComponent();
expect(shouldShowSpaceSettings).toHaveBeenCalledWith(defaultProps.space);
expect(findByTestId(component, 'settings-option').length).toBeTruthy();
expect(findByTestId(component, "settings-option").length).toBeTruthy();
});
it('opens space settings when space settings option is clicked', () => {
it("opens space settings when space settings option is clicked", () => {
mocked(shouldShowSpaceSettings).mockReturnValue(true);
const onFinished = jest.fn();
const component = getComponent({ onFinished });
act(() => {
findByTestId(component, 'settings-option').at(0).simulate('click');
findByTestId(component, "settings-option").at(0).simulate("click");
});
expect(showSpaceSettings).toHaveBeenCalledWith(defaultProps.space);
expect(onFinished).toHaveBeenCalled();
});
it('renders leave option when user does not have rights to see space settings', () => {
it("renders leave option when user does not have rights to see space settings", () => {
const component = getComponent();
expect(findByTestId(component, 'leave-option').length).toBeTruthy();
expect(findByTestId(component, "leave-option").length).toBeTruthy();
});
it('leaves space when leave option is clicked', () => {
it("leaves space when leave option is clicked", () => {
const onFinished = jest.fn();
const component = getComponent({ onFinished });
act(() => {
findByTestId(component, 'leave-option').at(0).simulate('click');
findByTestId(component, "leave-option").at(0).simulate("click");
});
expect(leaveSpace).toHaveBeenCalledWith(defaultProps.space);
expect(onFinished).toHaveBeenCalled();
});
describe('add children section', () => {
describe("add children section", () => {
const space = makeMockSpace();
beforeEach(() => {
// set space to allow adding children to space
mocked(space.currentState.maySendStateEvent).mockReturnValue(true);
mocked(shouldShowComponent).mockReturnValue(true);
});
it('does not render section when user does not have permission to add children', () => {
it("does not render section when user does not have permission to add children", () => {
mocked(space.currentState.maySendStateEvent).mockReturnValue(false);
const component = getComponent({ space });
expect(findByTestId(component, 'add-to-space-header').length).toBeFalsy();
expect(findByTestId(component, 'new-room-option').length).toBeFalsy();
expect(findByTestId(component, 'new-subspace-option').length).toBeFalsy();
expect(findByTestId(component, "add-to-space-header").length).toBeFalsy();
expect(findByTestId(component, "new-room-option").length).toBeFalsy();
expect(findByTestId(component, "new-subspace-option").length).toBeFalsy();
});
it('does not render section when UIComponent customisations disable room and space creation', () => {
it("does not render section when UIComponent customisations disable room and space creation", () => {
mocked(shouldShowComponent).mockReturnValue(false);
const component = getComponent({ space });
expect(shouldShowComponent).toHaveBeenCalledWith(UIComponent.CreateRooms);
expect(shouldShowComponent).toHaveBeenCalledWith(UIComponent.CreateSpaces);
expect(findByTestId(component, 'add-to-space-header').length).toBeFalsy();
expect(findByTestId(component, 'new-room-option').length).toBeFalsy();
expect(findByTestId(component, 'new-subspace-option').length).toBeFalsy();
expect(findByTestId(component, "add-to-space-header").length).toBeFalsy();
expect(findByTestId(component, "new-room-option").length).toBeFalsy();
expect(findByTestId(component, "new-subspace-option").length).toBeFalsy();
});
it('renders section with add room button when UIComponent customisation allows CreateRoom', () => {
it("renders section with add room button when UIComponent customisation allows CreateRoom", () => {
// only allow CreateRoom
mocked(shouldShowComponent).mockImplementation(feature => feature === UIComponent.CreateRooms);
mocked(shouldShowComponent).mockImplementation((feature) => feature === UIComponent.CreateRooms);
const component = getComponent({ space });
expect(findByTestId(component, 'add-to-space-header').length).toBeTruthy();
expect(findByTestId(component, 'new-room-option').length).toBeTruthy();
expect(findByTestId(component, 'new-subspace-option').length).toBeFalsy();
expect(findByTestId(component, "add-to-space-header").length).toBeTruthy();
expect(findByTestId(component, "new-room-option").length).toBeTruthy();
expect(findByTestId(component, "new-subspace-option").length).toBeFalsy();
});
it('renders section with add space button when UIComponent customisation allows CreateSpace', () => {
it("renders section with add space button when UIComponent customisation allows CreateSpace", () => {
// only allow CreateSpaces
mocked(shouldShowComponent).mockImplementation(feature => feature === UIComponent.CreateSpaces);
mocked(shouldShowComponent).mockImplementation((feature) => feature === UIComponent.CreateSpaces);
const component = getComponent({ space });
expect(findByTestId(component, 'add-to-space-header').length).toBeTruthy();
expect(findByTestId(component, 'new-room-option').length).toBeFalsy();
expect(findByTestId(component, 'new-subspace-option').length).toBeTruthy();
expect(findByTestId(component, "add-to-space-header").length).toBeTruthy();
expect(findByTestId(component, "new-room-option").length).toBeFalsy();
expect(findByTestId(component, "new-subspace-option").length).toBeTruthy();
});
it('opens create room dialog on add room button click', () => {
it("opens create room dialog on add room button click", () => {
const onFinished = jest.fn();
const component = getComponent({ space, onFinished });
act(() => {
findByTestId(component, 'new-room-option').at(0).simulate('click');
findByTestId(component, "new-room-option").at(0).simulate("click");
});
expect(showCreateNewRoom).toHaveBeenCalledWith(space);
expect(onFinished).toHaveBeenCalled();
});
it('opens create space dialog on add space button click', () => {
it("opens create space dialog on add space button click", () => {
const onFinished = jest.fn();
const component = getComponent({ space, onFinished });
act(() => {
findByTestId(component, 'new-subspace-option').at(0).simulate('click');
findByTestId(component, "new-subspace-option").at(0).simulate("click");
});
expect(showCreateNewSubspace).toHaveBeenCalledWith(space);
expect(onFinished).toHaveBeenCalled();

View file

@ -38,10 +38,7 @@ describe("ThreadListContextMenu", () => {
let event: MatrixEvent;
function getComponent(props: Partial<ThreadListContextMenuProps>) {
return render(<ThreadListContextMenu
mxEvent={event}
{...props}
/>);
return render(<ThreadListContextMenu mxEvent={event} {...props} />);
}
beforeEach(() => {

View file

@ -14,14 +14,14 @@ 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, ReactWrapper } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { IPassphraseInfo } from 'matrix-js-sdk/src/crypto/api';
import { mount, ReactWrapper } from "enzyme";
import { act } from "react-dom/test-utils";
import { IPassphraseInfo } from "matrix-js-sdk/src/crypto/api";
import { findByAttr, getMockClientWithEventEmitter, unmockClientPeg } from '../../../test-utils';
import { findById, flushPromises } from '../../../test-utils';
import { findByAttr, getMockClientWithEventEmitter, unmockClientPeg } from "../../../test-utils";
import { findById, flushPromises } from "../../../test-utils";
import AccessSecretStorageDialog from "../../../../src/components/views/dialogs/security/AccessSecretStorageDialog";
describe("AccessSecretStorageDialog", () => {
@ -35,12 +35,12 @@ describe("AccessSecretStorageDialog", () => {
checkPrivateKey: jest.fn(),
keyInfo: undefined,
};
const getComponent = (props ={}): ReactWrapper =>
const getComponent = (props = {}): ReactWrapper =>
mount(<AccessSecretStorageDialog {...defaultProps} {...props} />);
beforeEach(() => {
jest.clearAllMocks();
mockClient.keyBackupKeyFromRecoveryKey.mockReturnValue('a raw key' as unknown as Uint8Array);
mockClient.keyBackupKeyFromRecoveryKey.mockReturnValue("a raw key" as unknown as Uint8Array);
mockClient.isValidRecoveryKey.mockReturnValue(false);
});
@ -63,7 +63,7 @@ describe("AccessSecretStorageDialog", () => {
const e = { preventDefault: () => {} };
act(() => {
wrapper.find('form').simulate('submit', e);
wrapper.find("form").simulate("submit", e);
});
await flushPromises();
@ -75,13 +75,13 @@ describe("AccessSecretStorageDialog", () => {
it("Considers a valid key to be valid", async () => {
const checkPrivateKey = jest.fn().mockResolvedValue(true);
const wrapper = getComponent({ checkPrivateKey });
mockClient.keyBackupKeyFromRecoveryKey.mockReturnValue('a raw key' as unknown as Uint8Array);
mockClient.keyBackupKeyFromRecoveryKey.mockReturnValue("a raw key" as unknown as Uint8Array);
mockClient.checkSecretStorageKey.mockResolvedValue(true);
const v = "asdf";
const e = { target: { value: v } };
act(() => {
findById(wrapper, 'mx_securityKey').find('input').simulate('change', e);
findById(wrapper, "mx_securityKey").find("input").simulate("change", e);
wrapper.setProps({});
});
await act(async () => {
@ -91,10 +91,10 @@ describe("AccessSecretStorageDialog", () => {
wrapper.setProps({});
});
const submitButton = findByAttr('data-testid')(wrapper, 'dialog-primary-button').at(0);
const submitButton = findByAttr("data-testid")(wrapper, "dialog-primary-button").at(0);
// submit button is enabled when key is valid
expect(submitButton.props().disabled).toBeFalsy();
expect(wrapper.find('.mx_AccessSecretStorageDialog_recoveryKeyFeedback').text()).toEqual('Looks good!');
expect(wrapper.find(".mx_AccessSecretStorageDialog_recoveryKeyFeedback").text()).toEqual("Looks good!");
});
it("Notifies the user if they input an invalid Security Key", async () => {
@ -106,36 +106,36 @@ describe("AccessSecretStorageDialog", () => {
});
act(() => {
findById(wrapper, 'mx_securityKey').find('input').simulate('change', e);
findById(wrapper, "mx_securityKey").find("input").simulate("change", e);
});
// force a validation now because it debounces
// @ts-ignore private
await wrapper.instance().validateRecoveryKey();
const submitButton = findByAttr('data-testid')(wrapper, 'dialog-primary-button').at(0);
const submitButton = findByAttr("data-testid")(wrapper, "dialog-primary-button").at(0);
// submit button is disabled when recovery key is invalid
expect(submitButton.props().disabled).toBeTruthy();
expect(
wrapper.find('.mx_AccessSecretStorageDialog_recoveryKeyFeedback').text(),
).toEqual('Invalid Security Key');
expect(wrapper.find(".mx_AccessSecretStorageDialog_recoveryKeyFeedback").text()).toEqual(
"Invalid Security Key",
);
wrapper.setProps({});
const notification = wrapper.find(".mx_AccessSecretStorageDialog_recoveryKeyFeedback");
expect(notification.props().children).toEqual("Invalid Security Key");
});
it("Notifies the user if they input an invalid passphrase", async function() {
it("Notifies the user if they input an invalid passphrase", async function () {
const keyInfo = {
name: 'test',
algorithm: 'test',
iv: 'test',
mac: '1:2:3:4',
name: "test",
algorithm: "test",
iv: "test",
mac: "1:2:3:4",
passphrase: {
// this type is weird in js-sdk
// cast 'm.pbkdf2' to itself
algorithm: 'm.pbkdf2' as IPassphraseInfo['algorithm'],
algorithm: "m.pbkdf2" as IPassphraseInfo["algorithm"],
iterations: 2,
salt: 'nonempty',
salt: "nonempty",
},
};
const checkPrivateKey = jest.fn().mockResolvedValue(false);
@ -145,23 +145,24 @@ describe("AccessSecretStorageDialog", () => {
// update passphrase
act(() => {
const e = { target: { value: "a" } };
findById(wrapper, 'mx_passPhraseInput').at(1).simulate('change', e);
findById(wrapper, "mx_passPhraseInput").at(1).simulate("change", e);
});
wrapper.setProps({});
// input updated
expect(findById(wrapper, 'mx_passPhraseInput').at(0).props().value).toEqual('a');
expect(findById(wrapper, "mx_passPhraseInput").at(0).props().value).toEqual("a");
// submit the form
act(() => {
wrapper.find('form').at(0).simulate('submit');
wrapper.find("form").at(0).simulate("submit");
});
await flushPromises();
wrapper.setProps({});
const notification = wrapper.find(".mx_AccessSecretStorageDialog_keyStatus");
expect(notification.props().children).toEqual(
["\uD83D\uDC4E ", "Unable to access secret storage. Please verify that you " +
"entered the correct Security Phrase."]);
expect(notification.props().children).toEqual([
"\uD83D\uDC4E ",
"Unable to access secret storage. Please verify that you " + "entered the correct Security Phrase.",
]);
});
});

View file

@ -35,11 +35,13 @@ describe("<ChangelogDialog />", () => {
ahead_by: 24,
behind_by: 0,
total_commits: 24,
commits: [{
sha: "commit-sha",
html_url: "https://api.github.com/repos/vector-im/element-web/commit/commit-sha",
commit: { message: "This is the first commit message" },
}],
commits: [
{
sha: "commit-sha",
html_url: "https://api.github.com/repos/vector-im/element-web/commit/commit-sha",
commit: { message: "This is the first commit message" },
},
],
files: [],
});
const reactUrl = "https://riot.im/github/repos/matrix-org/matrix-react-sdk/compare/oldsha2...newsha2";
@ -55,11 +57,13 @@ describe("<ChangelogDialog />", () => {
ahead_by: 83,
behind_by: 0,
total_commits: 83,
commits: [{
sha: "commit-sha0",
html_url: "https://api.github.com/repos/matrix-org/matrix-react-sdk/commit/commit-sha",
commit: { message: "This is a commit message" },
}],
commits: [
{
sha: "commit-sha0",
html_url: "https://api.github.com/repos/matrix-org/matrix-react-sdk/commit/commit-sha",
commit: { message: "This is a commit message" },
},
],
files: [],
});
const jsUrl = "https://riot.im/github/repos/matrix-org/matrix-js-sdk/compare/oldsha3...newsha3";
@ -75,23 +79,26 @@ describe("<ChangelogDialog />", () => {
ahead_by: 48,
behind_by: 0,
total_commits: 48,
commits: [{
sha: "commit-sha1",
html_url: "https://api.github.com/repos/matrix-org/matrix-js-sdk/commit/commit-sha1",
commit: { message: "This is a commit message" },
}, {
sha: "commit-sha2",
html_url: "https://api.github.com/repos/matrix-org/matrix-js-sdk/commit/commit-sha2",
commit: { message: "This is another commit message" },
}],
commits: [
{
sha: "commit-sha1",
html_url: "https://api.github.com/repos/matrix-org/matrix-js-sdk/commit/commit-sha1",
commit: { message: "This is a commit message" },
},
{
sha: "commit-sha2",
html_url: "https://api.github.com/repos/matrix-org/matrix-js-sdk/commit/commit-sha2",
commit: { message: "This is another commit message" },
},
],
files: [],
});
const newVersion = "newsha1-react-newsha2-js-newsha3";
const oldVersion = "oldsha1-react-oldsha2-js-oldsha3";
const { asFragment } = render((
<ChangelogDialog newVersion={newVersion} version={oldVersion} onFinished={jest.fn()} />
));
const { asFragment } = render(
<ChangelogDialog newVersion={newVersion} version={oldVersion} onFinished={jest.fn()} />,
);
// Wait for spinners to go away
await waitForElementToBeRemoved(screen.getAllByRole("progressbar"));

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 { mocked } from 'jest-mock';
import { mount } from "enzyme";
import { mocked } from "jest-mock";
import { act } from "react-dom/test-utils";
import { Room } from 'matrix-js-sdk/src/matrix';
import { Room } from "matrix-js-sdk/src/matrix";
import ExportDialog from '../../../../src/components/views/dialogs/ExportDialog';
import { ExportType, ExportFormat } from '../../../../src/utils/exportUtils/exportUtils';
import { createTestClient, mkStubRoom } from '../../../test-utils';
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
import ExportDialog from "../../../../src/components/views/dialogs/ExportDialog";
import { ExportType, ExportFormat } from "../../../../src/utils/exportUtils/exportUtils";
import { createTestClient, mkStubRoom } from "../../../test-utils";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import HTMLExporter from "../../../../src/utils/exportUtils/HtmlExport";
import ChatExport from '../../../../src/customisations/ChatExport';
import PlainTextExporter from '../../../../src/utils/exportUtils/PlainTextExport';
import ChatExport from "../../../../src/customisations/ChatExport";
import PlainTextExporter from "../../../../src/utils/exportUtils/PlainTextExport";
jest.useFakeTimers();
const htmlExporterInstance = ({
const htmlExporterInstance = {
export: jest.fn().mockResolvedValue({}),
});
const plainTextExporterInstance = ({
};
const plainTextExporterInstance = {
export: jest.fn().mockResolvedValue({}),
});
};
jest.mock("../../../../src/utils/exportUtils/HtmlExport", () => jest.fn());
jest.mock("../../../../src/utils/exportUtils/PlainTextExport", () => jest.fn());
jest.mock('../../../../src/customisations/ChatExport', () => ({
jest.mock("../../../../src/customisations/ChatExport", () => ({
getForceChatExportParameters: jest.fn().mockReturnValue({}),
}));
@ -48,13 +48,13 @@ const ChatExportMock = mocked(ChatExport);
const HTMLExporterMock = mocked(HTMLExporter);
const PlainTextExporterMock = mocked(PlainTextExporter);
describe('<ExportDialog />', () => {
describe("<ExportDialog />", () => {
const mockClient = createTestClient();
jest.spyOn(MatrixClientPeg, 'get').mockReturnValue(mockClient);
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
const roomId = 'test:test.org';
const roomId = "test:test.org";
const defaultProps = {
room: mkStubRoom(roomId, 'test', mockClient) as unknown as Room,
room: mkStubRoom(roomId, "test", mockClient) as unknown as Room,
onFinished: jest.fn(),
};
@ -68,32 +68,38 @@ describe('<ExportDialog />', () => {
const getPrimaryButton = (component) => component.find('[data-testid="dialog-primary-button"]');
const getSecondaryButton = (component) => component.find('[data-testid="dialog-cancel-button"]');
const submitForm = async (component) => act(async () => {
getPrimaryButton(component).simulate('click');
component.setProps({});
});
const selectExportFormat = async (component, format: ExportFormat) => act(async () => {
getExportFormatInput(component, format).simulate('change');
component.setProps({});
});
const selectExportType = async (component, type: ExportType) => act(async () => {
getExportTypeInput(component).simulate('change', { target: { value: type } });
component.setProps({});
});
const setMessageCount = async (component, count: number) => act(async () => {
getMessageCountInput(component).simulate('change', { target: { value: count } });
component.setProps({});
});
const submitForm = async (component) =>
act(async () => {
getPrimaryButton(component).simulate("click");
component.setProps({});
});
const selectExportFormat = async (component, format: ExportFormat) =>
act(async () => {
getExportFormatInput(component, format).simulate("change");
component.setProps({});
});
const selectExportType = async (component, type: ExportType) =>
act(async () => {
getExportTypeInput(component).simulate("change", { target: { value: type } });
component.setProps({});
});
const setMessageCount = async (component, count: number) =>
act(async () => {
getMessageCountInput(component).simulate("change", { target: { value: count } });
component.setProps({});
});
const setSizeLimit = async (component, limit: number) => act(async () => {
getSizeInput(component).simulate('change', { target: { value: limit } });
component.setProps({});
});
const setSizeLimit = async (component, limit: number) =>
act(async () => {
getSizeInput(component).simulate("change", { target: { value: limit } });
component.setProps({});
});
const setIncludeAttachments = async (component, checked) => act(async () => {
getAttachmentsCheckbox(component).simulate('change', { target: { checked } });
component.setProps({});
});
const setIncludeAttachments = async (component, checked) =>
act(async () => {
getAttachmentsCheckbox(component).simulate("change", { target: { checked } });
component.setProps({});
});
beforeEach(() => {
HTMLExporterMock.mockClear().mockImplementation(jest.fn().mockReturnValue(htmlExporterInstance));
@ -105,21 +111,21 @@ describe('<ExportDialog />', () => {
ChatExportMock.getForceChatExportParameters.mockClear().mockReturnValue({});
});
it('renders export dialog', () => {
it("renders export dialog", () => {
const component = getComponent();
expect(component.find('.mx_ExportDialog')).toMatchSnapshot();
expect(component.find(".mx_ExportDialog")).toMatchSnapshot();
});
it('calls onFinished when cancel button is clicked', () => {
it("calls onFinished when cancel button is clicked", () => {
const onFinished = jest.fn();
const component = getComponent({ onFinished });
act(() => {
getSecondaryButton(component).simulate('click');
getSecondaryButton(component).simulate("click");
});
expect(onFinished).toHaveBeenCalledWith(false);
});
it('exports room on submit', async () => {
it("exports room on submit", async () => {
const component = getComponent();
await submitForm(component);
@ -137,7 +143,7 @@ describe('<ExportDialog />', () => {
expect(htmlExporterInstance.export).toHaveBeenCalled();
});
it('exports room using values set from ForceRoomExportParameters', async () => {
it("exports room using values set from ForceRoomExportParameters", async () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({
format: ExportFormat.PlainText,
range: ExportType.Beginning,
@ -162,35 +168,35 @@ describe('<ExportDialog />', () => {
expect(plainTextExporterInstance.export).toHaveBeenCalled();
});
it('renders success screen when export is finished', async () => {
it("renders success screen when export is finished", async () => {
const component = getComponent();
await submitForm(component);
component.setProps({});
jest.runAllTimers();
expect(component.find('.mx_InfoDialog .mx_Dialog_content')).toMatchSnapshot();
expect(component.find(".mx_InfoDialog .mx_Dialog_content")).toMatchSnapshot();
});
describe('export format', () => {
it('renders export format with html selected by default', () => {
describe("export format", () => {
it("renders export format with html selected by default", () => {
const component = getComponent();
expect(getExportFormatInput(component, ExportFormat.Html).props().checked).toBeTruthy();
});
it('sets export format on radio button click', async () => {
it("sets export format on radio button click", async () => {
const component = getComponent();
await selectExportFormat(component, ExportFormat.PlainText);
expect(getExportFormatInput(component, ExportFormat.PlainText).props().checked).toBeTruthy();
expect(getExportFormatInput(component, ExportFormat.Html).props().checked).toBeFalsy();
});
it('hides export format input when format is valid in ForceRoomExportParameters', () => {
it("hides export format input when format is valid in ForceRoomExportParameters", () => {
const component = getComponent();
expect(getExportFormatInput(component, ExportFormat.Html).props().checked).toBeTruthy();
});
it('does not render export format when set in ForceRoomExportParameters', () => {
it("does not render export format when set in ForceRoomExportParameters", () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({
format: ExportFormat.PlainText,
});
@ -199,19 +205,19 @@ describe('<ExportDialog />', () => {
});
});
describe('export type', () => {
it('renders export type with timeline selected by default', () => {
describe("export type", () => {
it("renders export type with timeline selected by default", () => {
const component = getComponent();
expect(getExportTypeInput(component).props().value).toEqual(ExportType.Timeline);
});
it('sets export type on change', async () => {
it("sets export type on change", async () => {
const component = getComponent();
await selectExportType(component, ExportType.Beginning);
expect(getExportTypeInput(component).props().value).toEqual(ExportType.Beginning);
});
it('does not render export type when set in ForceRoomExportParameters', () => {
it("does not render export type when set in ForceRoomExportParameters", () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({
range: ExportType.Beginning,
});
@ -219,25 +225,25 @@ describe('<ExportDialog />', () => {
expect(getExportTypeInput(component).length).toBeFalsy();
});
it('does not render message count input', async () => {
it("does not render message count input", async () => {
const component = getComponent();
expect(getMessageCountInput(component).length).toBeFalsy();
});
it('renders message count input with default value 100 when export type is lastNMessages', async () => {
it("renders message count input with default value 100 when export type is lastNMessages", async () => {
const component = getComponent();
await selectExportType(component, ExportType.LastNMessages);
expect(getMessageCountInput(component).props().value).toEqual("100");
});
it('sets message count on change', async () => {
it("sets message count on change", async () => {
const component = getComponent();
await selectExportType(component, ExportType.LastNMessages);
await setMessageCount(component, 10);
expect(getMessageCountInput(component).props().value).toEqual("10");
});
it('does not export when export type is lastNMessages and message count is falsy', async () => {
it("does not export when export type is lastNMessages and message count is falsy", async () => {
const component = getComponent();
await selectExportType(component, ExportType.LastNMessages);
await setMessageCount(component, 0);
@ -246,7 +252,7 @@ describe('<ExportDialog />', () => {
expect(htmlExporterInstance.export).not.toHaveBeenCalled();
});
it('does not export when export type is lastNMessages and message count is more than max', async () => {
it("does not export when export type is lastNMessages and message count is more than max", async () => {
const component = getComponent();
await selectExportType(component, ExportType.LastNMessages);
await setMessageCount(component, 99999999999);
@ -255,7 +261,7 @@ describe('<ExportDialog />', () => {
expect(htmlExporterInstance.export).not.toHaveBeenCalled();
});
it('exports when export type is NOT lastNMessages and message count is falsy', async () => {
it("exports when export type is NOT lastNMessages and message count is falsy", async () => {
const component = getComponent();
await selectExportType(component, ExportType.LastNMessages);
await setMessageCount(component, 0);
@ -266,19 +272,19 @@ describe('<ExportDialog />', () => {
});
});
describe('size limit', () => {
it('renders size limit input with default value', () => {
describe("size limit", () => {
it("renders size limit input with default value", () => {
const component = getComponent();
expect(getSizeInput(component).props().value).toEqual("8");
});
it('updates size limit on change', async () => {
it("updates size limit on change", async () => {
const component = getComponent();
await setSizeLimit(component, 20);
expect(getSizeInput(component).props().value).toEqual("20");
});
it('does not export when size limit is falsy', async () => {
it("does not export when size limit is falsy", async () => {
const component = getComponent();
await setSizeLimit(component, 0);
await submitForm(component);
@ -286,7 +292,7 @@ describe('<ExportDialog />', () => {
expect(htmlExporterInstance.export).not.toHaveBeenCalled();
});
it('does not export when size limit is larger than max', async () => {
it("does not export when size limit is larger than max", async () => {
const component = getComponent();
await setSizeLimit(component, 2001);
await submitForm(component);
@ -294,7 +300,7 @@ describe('<ExportDialog />', () => {
expect(htmlExporterInstance.export).not.toHaveBeenCalled();
});
it('exports when size limit is max', async () => {
it("exports when size limit is max", async () => {
const component = getComponent();
await setSizeLimit(component, 2000);
await submitForm(component);
@ -302,7 +308,7 @@ describe('<ExportDialog />', () => {
expect(htmlExporterInstance.export).toHaveBeenCalled();
});
it('does not render size limit input when set in ForceRoomExportParameters', () => {
it("does not render size limit input when set in ForceRoomExportParameters", () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({
sizeMb: 10000,
});
@ -313,7 +319,7 @@ describe('<ExportDialog />', () => {
/**
* 2000mb size limit does not apply when higher limit is configured in config
*/
it('exports when size limit set in ForceRoomExportParameters is larger than 2000', async () => {
it("exports when size limit set in ForceRoomExportParameters is larger than 2000", async () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({
sizeMb: 10000,
});
@ -324,19 +330,19 @@ describe('<ExportDialog />', () => {
});
});
describe('include attachments', () => {
it('renders input with default value of false', () => {
describe("include attachments", () => {
it("renders input with default value of false", () => {
const component = getComponent();
expect(getAttachmentsCheckbox(component).props().checked).toEqual(false);
});
it('updates include attachments on change', async () => {
it("updates include attachments on change", async () => {
const component = getComponent();
await setIncludeAttachments(component, true);
expect(getAttachmentsCheckbox(component).props().checked).toEqual(true);
});
it('does not render input when set in ForceRoomExportParameters', () => {
it("does not render input when set in ForceRoomExportParameters", () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({
includeAttachments: false,
});
@ -345,4 +351,3 @@ describe('<ExportDialog />', () => {
});
});
});

View file

@ -59,22 +59,22 @@ describe("ForwardDialog", () => {
getRoom: jest.fn(),
getAccountData: jest.fn().mockReturnValue(accountDataEvent),
getPushActionsForEvent: jest.fn(),
mxcUrlToHttp: jest.fn().mockReturnValue(''),
mxcUrlToHttp: jest.fn().mockReturnValue(""),
isRoomEncrypted: jest.fn().mockReturnValue(false),
getProfileInfo: jest.fn().mockResolvedValue({
displayname: 'Alice',
displayname: "Alice",
}),
decryptEventIfNeeded: jest.fn(),
sendEvent: jest.fn(),
getClientWellKnown: jest.fn().mockReturnValue({
[TILE_SERVER_WK_KEY.name]: { map_style_url: 'maps.com' },
[TILE_SERVER_WK_KEY.name]: { map_style_url: "maps.com" },
}),
});
const defaultRooms = ["a", "A", "b"].map(name => mkStubRoom(name, name, mockClient));
const defaultRooms = ["a", "A", "b"].map((name) => mkStubRoom(name, name, mockClient));
const mountForwardDialog = (message = defaultMessage, rooms = defaultRooms) => {
mockClient.getVisibleRooms.mockReturnValue(rooms);
mockClient.getRoom.mockImplementation(roomId => rooms.find(room => room.roomId === roomId));
mockClient.getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId));
const wrapper: RenderResult = render(
<ForwardDialog
@ -96,7 +96,7 @@ describe("ForwardDialog", () => {
});
afterAll(() => {
jest.spyOn(MatrixClientPeg, 'get').mockRestore();
jest.spyOn(MatrixClientPeg, "get").mockRestore();
});
it("shows a preview with us as the sender", async () => {
@ -127,10 +127,13 @@ describe("ForwardDialog", () => {
// Make sendEvent require manual resolution so we can see the sending state
let finishSend;
let cancelSend;
mockClient.sendEvent.mockImplementation(<T extends {}>() => new Promise<T>((resolve, reject) => {
finishSend = resolve;
cancelSend = reject;
}));
mockClient.sendEvent.mockImplementation(
<T extends {}>() =>
new Promise<T>((resolve, reject) => {
finishSend = resolve;
cancelSend = reject;
}),
);
let firstButton;
let secondButton;
@ -141,28 +144,32 @@ describe("ForwardDialog", () => {
expect(firstButton.className).toContain("mx_ForwardList_canSend");
act(() => { fireEvent.click(firstButton); });
act(() => {
fireEvent.click(firstButton);
});
update();
expect(firstButton.className).toContain("mx_ForwardList_sending");
await act(async () => {
cancelSend();
// Wait one tick for the button to realize the send failed
await new Promise(resolve => setImmediate(resolve));
await new Promise((resolve) => setImmediate(resolve));
});
update();
expect(firstButton.className).toContain("mx_ForwardList_sendFailed");
expect(secondButton.className).toContain("mx_ForwardList_canSend");
act(() => { fireEvent.click(secondButton); });
act(() => {
fireEvent.click(secondButton);
});
update();
expect(secondButton.className).toContain("mx_ForwardList_sending");
await act(async () => {
finishSend();
// Wait one tick for the button to realize the send succeeded
await new Promise(resolve => setImmediate(resolve));
await new Promise((resolve) => setImmediate(resolve));
});
update();
expect(secondButton.className).toContain("mx_ForwardList_sent");
@ -203,7 +210,7 @@ describe("ForwardDialog", () => {
expect(secondButton.getAttribute("aria-disabled")).toBeFalsy();
});
describe('Location events', () => {
describe("Location events", () => {
// 14.03.2022 16:15
const now = 1647270879403;
const roomId = "a";
@ -215,11 +222,11 @@ describe("ForwardDialog", () => {
beforeEach(() => {
// legacy events will default timestamp to Date.now()
// mock a stable now for easy assertion
jest.spyOn(Date, 'now').mockReturnValue(now);
jest.spyOn(Date, "now").mockReturnValue(now);
});
afterAll(() => {
jest.spyOn(Date, 'now').mockRestore();
jest.spyOn(Date, "now").mockRestore();
});
const sendToFirstRoom = (container: HTMLElement): void =>
@ -228,7 +235,7 @@ describe("ForwardDialog", () => {
fireEvent.click(sendToFirstRoomButton!);
});
it('converts legacy location events to pin drop shares', async () => {
it("converts legacy location events to pin drop shares", async () => {
const { container } = mountForwardDialog(legacyLocationEvent);
expect(container.querySelector(".mx_MLocationBody")).toBeTruthy();
@ -250,11 +257,13 @@ describe("ForwardDialog", () => {
},
};
expect(mockClient.sendEvent).toHaveBeenCalledWith(
roomId, legacyLocationEvent.getType(), expectedStrippedContent,
roomId,
legacyLocationEvent.getType(),
expectedStrippedContent,
);
});
it('removes personal information from static self location shares', async () => {
it("removes personal information from static self location shares", async () => {
const { container } = mountForwardDialog(modernLocationEvent);
expect(container.querySelector(".mx_MLocationBody")).toBeTruthy();
@ -275,13 +284,15 @@ describe("ForwardDialog", () => {
},
};
expect(mockClient.sendEvent).toHaveBeenCalledWith(
roomId, modernLocationEvent.getType(), expectedStrippedContent,
roomId,
modernLocationEvent.getType(),
expectedStrippedContent,
);
});
it('forwards beacon location as a pin drop event', async () => {
it("forwards beacon location as a pin drop event", async () => {
const timestamp = 123456;
const beaconEvent = makeBeaconEvent('@alice:server.org', { geoUri, timestamp });
const beaconEvent = makeBeaconEvent("@alice:server.org", { geoUri, timestamp });
const text = `Location ${geoUri} at ${new Date(timestamp).toISOString()}`;
const expectedContent = {
msgtype: "m.location",
@ -301,12 +312,10 @@ describe("ForwardDialog", () => {
sendToFirstRoom(container);
expect(mockClient.sendEvent).toHaveBeenCalledWith(
roomId, EventType.RoomMessage, expectedContent,
);
expect(mockClient.sendEvent).toHaveBeenCalledWith(roomId, EventType.RoomMessage, expectedContent);
});
it('forwards pin drop event', async () => {
it("forwards pin drop event", async () => {
const { container } = mountForwardDialog(pinDropLocationEvent);
expect(container.querySelector(".mx_MLocationBody")).toBeTruthy();
@ -314,7 +323,9 @@ describe("ForwardDialog", () => {
sendToFirstRoom(container);
expect(mockClient.sendEvent).toHaveBeenCalledWith(
roomId, pinDropLocationEvent.getType(), pinDropLocationEvent.getContent(),
roomId,
pinDropLocationEvent.getType(),
pinDropLocationEvent.getContent(),
);
});
});

View file

@ -15,17 +15,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { act } from 'react-dom/test-utils';
import React from "react";
import { act } from "react-dom/test-utils";
// eslint-disable-next-line deprecate/import
import { mount, ReactWrapper } from 'enzyme';
import { mount, ReactWrapper } from "enzyme";
import InteractiveAuthDialog from "../../../../src/components/views/dialogs/InteractiveAuthDialog";
import { flushPromises, getMockClientWithEventEmitter, unmockClientPeg } from '../../../test-utils';
import { flushPromises, getMockClientWithEventEmitter, unmockClientPeg } from "../../../test-utils";
describe('InteractiveAuthDialog', function() {
describe("InteractiveAuthDialog", function () {
const mockClient = getMockClientWithEventEmitter({
generateClientSecret: jest.fn().mockReturnValue('t35tcl1Ent5ECr3T'),
generateClientSecret: jest.fn().mockReturnValue("t35tcl1Ent5ECr3T"),
});
const defaultProps = {
@ -33,12 +33,9 @@ describe('InteractiveAuthDialog', function() {
makeRequest: jest.fn().mockResolvedValue(undefined),
onFinished: jest.fn(),
};
const getComponent = (props = {}) => mount(<InteractiveAuthDialog
{...defaultProps}
{...props}
/>);
const getComponent = (props = {}) => mount(<InteractiveAuthDialog {...defaultProps} {...props} />);
beforeEach(function() {
beforeEach(function () {
jest.clearAllMocks();
mockClient.credentials = null;
});
@ -49,16 +46,14 @@ describe('InteractiveAuthDialog', function() {
const getSubmitButton = (wrapper: ReactWrapper) => wrapper.find('[type="submit"]').at(0);
it('Should successfully complete a password flow', async () => {
it("Should successfully complete a password flow", async () => {
const onFinished = jest.fn();
const makeRequest = jest.fn().mockResolvedValue({ a: 1 });
mockClient.credentials = { userId: "@user:id" };
const authData = {
session: "sess",
flows: [
{ "stages": ["m.login.password"] },
],
flows: [{ stages: ["m.login.password"] }],
};
const wrapper = getComponent({ makeRequest, onFinished, authData });
@ -66,7 +61,7 @@ describe('InteractiveAuthDialog', function() {
const passwordNode = wrapper.find('input[type="password"]').at(0);
const submitNode = getSubmitButton(wrapper);
const formNode = wrapper.find('form').at(0);
const formNode = wrapper.find("form").at(0);
expect(passwordNode).toBeTruthy();
expect(submitNode).toBeTruthy();
@ -75,7 +70,7 @@ describe('InteractiveAuthDialog', function() {
// put something in the password box
act(() => {
passwordNode.simulate('change', { target: { value: "s3kr3t" } });
passwordNode.simulate("change", { target: { value: "s3kr3t" } });
wrapper.setProps({});
});
@ -84,22 +79,24 @@ describe('InteractiveAuthDialog', function() {
// hit enter; that should trigger a request
act(() => {
formNode.simulate('submit');
formNode.simulate("submit");
});
// wait for auth request to resolve
await flushPromises();
expect(makeRequest).toHaveBeenCalledTimes(1);
expect(makeRequest).toBeCalledWith(expect.objectContaining({
session: "sess",
type: "m.login.password",
password: "s3kr3t",
identifier: {
type: "m.id.user",
user: "@user:id",
},
}));
expect(makeRequest).toBeCalledWith(
expect.objectContaining({
session: "sess",
type: "m.login.password",
password: "s3kr3t",
identifier: {
type: "m.id.user",
user: "@user:id",
},
}),
);
expect(onFinished).toBeCalledTimes(1);
expect(onFinished).toBeCalledWith(true, { a: 1 });

View file

@ -28,9 +28,11 @@ import { ValidatedServerConfig } from "../../../../src/utils/ValidatedServerConf
import { IConfigOptions } from "../../../../src/IConfigOptions";
const mockGetAccessToken = jest.fn().mockResolvedValue("getAccessToken");
jest.mock("../../../../src/IdentityAuthClient", () => jest.fn().mockImplementation(() => ({
getAccessToken: mockGetAccessToken,
})));
jest.mock("../../../../src/IdentityAuthClient", () =>
jest.fn().mockImplementation(() => ({
getAccessToken: mockGetAccessToken,
})),
);
describe("InviteDialog", () => {
const roomId = "!111111111111111111:example.org";
@ -43,7 +45,7 @@ describe("InviteDialog", () => {
getRooms: jest.fn(),
getAccountData: jest.fn(),
getPushActionsForEvent: jest.fn(),
mxcUrlToHttp: jest.fn().mockReturnValue(''),
mxcUrlToHttp: jest.fn().mockReturnValue(""),
isRoomEncrypted: jest.fn().mockReturnValue(false),
getProfileInfo: jest.fn().mockRejectedValue({ errcode: "" }),
getIdentityServerUrl: jest.fn(),
@ -70,58 +72,46 @@ describe("InviteDialog", () => {
});
afterAll(() => {
jest.spyOn(MatrixClientPeg, 'get').mockRestore();
jest.spyOn(MatrixClientPeg, "get").mockRestore();
});
it("should label with space name", () => {
mockClient.getRoom(roomId).isSpaceRoom = jest.fn().mockReturnValue(true);
mockClient.getRoom(roomId).getType = jest.fn().mockReturnValue(RoomType.Space);
mockClient.getRoom(roomId).name = "Space";
render((
<InviteDialog
kind={KIND_INVITE}
roomId={roomId}
onFinished={jest.fn()}
/>
));
render(<InviteDialog kind={KIND_INVITE} roomId={roomId} onFinished={jest.fn()} />);
expect(screen.queryByText("Invite to Space")).toBeTruthy();
});
it("should label with room name", () => {
render((
<InviteDialog
kind={KIND_INVITE}
roomId={roomId}
onFinished={jest.fn()}
/>
));
render(<InviteDialog kind={KIND_INVITE} roomId={roomId} onFinished={jest.fn()} />);
expect(screen.queryByText("Invite to Room")).toBeTruthy();
});
it("should suggest valid MXIDs even if unknown", async () => {
render((
render(
<InviteDialog
kind={KIND_INVITE}
roomId={roomId}
onFinished={jest.fn()}
initialText="@localpart:server.tld"
/>
));
/>,
);
await screen.findAllByText("@localpart:server.tld"); // Using findAllByText as the MXID is used for name too
});
it("should not suggest invalid MXIDs", () => {
render((
render(
<InviteDialog
kind={KIND_INVITE}
roomId={roomId}
onFinished={jest.fn()}
initialText="@localpart:server:tld"
/>
));
/>,
);
expect(screen.queryByText("@localpart:server:tld")).toBeFalsy();
});
@ -138,14 +128,9 @@ describe("InviteDialog", () => {
avatar_url: "mxc://foo/bar",
});
render((
<InviteDialog
kind={KIND_INVITE}
roomId={roomId}
onFinished={jest.fn()}
initialText="foobar@email.com"
/>
));
render(
<InviteDialog kind={KIND_INVITE} roomId={roomId} onFinished={jest.fn()} initialText="foobar@email.com" />,
);
await screen.findByText("Mr. Foo");
await screen.findByText("@foobar:server");
@ -157,14 +142,9 @@ describe("InviteDialog", () => {
mockClient.getIdentityServerUrl.mockReturnValue("https://identity-server");
mockClient.lookupThreePid.mockResolvedValue({});
render((
<InviteDialog
kind={KIND_INVITE}
roomId={roomId}
onFinished={jest.fn()}
initialText="foobar@email.com"
/>
));
render(
<InviteDialog kind={KIND_INVITE} roomId={roomId} onFinished={jest.fn()} initialText="foobar@email.com" />,
);
await screen.findByText("foobar@email.com");
await screen.findByText("Invite by email");

View file

@ -54,16 +54,14 @@ interface MockClientOptions {
users?: IUserChunkMember[];
}
function mockClient(
{
userId = "testuser",
homeserver = "example.tld",
thirdPartyProtocols = {},
rooms = [],
members = [],
users = [],
}: MockClientOptions = {},
): MatrixClient {
function mockClient({
userId = "testuser",
homeserver = "example.tld",
thirdPartyProtocols = {},
rooms = [],
members = [],
users = [],
}: MockClientOptions = {}): MatrixClient {
stubClient();
const cli = MatrixClientPeg.get();
MatrixClientPeg.getHomeserverName = jest.fn(() => homeserver);
@ -72,13 +70,15 @@ function mockClient(
cli.getThirdpartyProtocols = jest.fn(() => Promise.resolve(thirdPartyProtocols));
cli.publicRooms = jest.fn((options) => {
const searchTerm = options?.filter?.generic_search_term?.toLowerCase();
const chunk = rooms.filter(it =>
!searchTerm ||
it.room_id.toLowerCase().includes(searchTerm) ||
it.name?.toLowerCase().includes(searchTerm) ||
sanitizeHtml(it?.topic, { allowedTags: [] }).toLowerCase().includes(searchTerm) ||
it.canonical_alias?.toLowerCase().includes(searchTerm) ||
it.aliases?.find(alias => alias.toLowerCase().includes(searchTerm)));
const chunk = rooms.filter(
(it) =>
!searchTerm ||
it.room_id.toLowerCase().includes(searchTerm) ||
it.name?.toLowerCase().includes(searchTerm) ||
sanitizeHtml(it?.topic, { allowedTags: [] }).toLowerCase().includes(searchTerm) ||
it.canonical_alias?.toLowerCase().includes(searchTerm) ||
it.aliases?.find((alias) => alias.toLowerCase().includes(searchTerm)),
);
return Promise.resolve({
chunk,
total_room_count_estimate: chunk.length,
@ -86,16 +86,19 @@ function mockClient(
});
cli.searchUserDirectory = jest.fn(({ term, limit }) => {
const searchTerm = term?.toLowerCase();
const results = users.filter(it => !searchTerm ||
it.user_id.toLowerCase().includes(searchTerm) ||
it.display_name.toLowerCase().includes(searchTerm));
const results = users.filter(
(it) =>
!searchTerm ||
it.user_id.toLowerCase().includes(searchTerm) ||
it.display_name.toLowerCase().includes(searchTerm),
);
return Promise.resolve({
results: results.slice(0, limit ?? +Infinity),
limited: limit && limit < results.length,
});
});
cli.getProfileInfo = jest.fn(async (userId) => {
const member = members.find(it => it.userId === userId);
const member = members.find((it) => it.userId === userId);
if (member) {
return Promise.resolve({
displayname: member.rawDisplayName,
@ -144,11 +147,7 @@ describe("Spotlight Dialog", () => {
describe("should apply filters supplied via props", () => {
it("without filter", async () => {
const wrapper = mount(
<SpotlightDialog
initialFilter={null}
onFinished={() => null} />,
);
const wrapper = mount(<SpotlightDialog initialFilter={null} onFinished={() => null} />);
await act(async () => {
await sleep(200);
});
@ -160,11 +159,7 @@ describe("Spotlight Dialog", () => {
wrapper.unmount();
});
it("with public room filter", async () => {
const wrapper = mount(
<SpotlightDialog
initialFilter={Filter.PublicRooms}
onFinished={() => null} />,
);
const wrapper = mount(<SpotlightDialog initialFilter={Filter.PublicRooms} onFinished={() => null} />);
await act(async () => {
await sleep(200);
});
@ -186,7 +181,8 @@ describe("Spotlight Dialog", () => {
<SpotlightDialog
initialFilter={Filter.People}
initialText={testPerson.display_name}
onFinished={() => null} />,
onFinished={() => null}
/>,
);
await act(async () => {
await sleep(200);
@ -208,10 +204,7 @@ describe("Spotlight Dialog", () => {
describe("should apply manually selected filter", () => {
it("with public rooms", async () => {
const wrapper = mount(
<SpotlightDialog
onFinished={() => null} />,
);
const wrapper = mount(<SpotlightDialog onFinished={() => null} />);
await act(async () => {
await sleep(1);
});
@ -234,11 +227,7 @@ describe("Spotlight Dialog", () => {
wrapper.unmount();
});
it("with people", async () => {
const wrapper = mount(
<SpotlightDialog
initialText={testPerson.display_name}
onFinished={() => null} />,
);
const wrapper = mount(<SpotlightDialog initialText={testPerson.display_name} onFinished={() => null} />);
await act(async () => {
await sleep(1);
});
@ -264,11 +253,7 @@ describe("Spotlight Dialog", () => {
describe("should allow clearing filter manually", () => {
it("with public room filter", async () => {
const wrapper = mount(
<SpotlightDialog
initialFilter={Filter.PublicRooms}
onFinished={() => null} />,
);
const wrapper = mount(<SpotlightDialog initialFilter={Filter.PublicRooms} onFinished={() => null} />);
await act(async () => {
await sleep(200);
});
@ -294,7 +279,8 @@ describe("Spotlight Dialog", () => {
<SpotlightDialog
initialFilter={Filter.People}
initialText={testPerson.display_name}
onFinished={() => null} />,
onFinished={() => null}
/>,
);
await act(async () => {
await sleep(200);
@ -323,11 +309,7 @@ describe("Spotlight Dialog", () => {
let options: ReactWrapper;
beforeAll(async () => {
wrapper = mount(
<SpotlightDialog
initialText="test23"
onFinished={() => null} />,
);
wrapper = mount(<SpotlightDialog initialText="test23" onFinished={() => null} />);
await act(async () => {
await sleep(200);
});
@ -357,7 +339,8 @@ describe("Spotlight Dialog", () => {
<SpotlightDialog
initialFilter={Filter.People}
initialText={testPerson.display_name}
onFinished={() => null} />,
onFinished={() => null}
/>,
);
await act(async () => {

View file

@ -14,30 +14,30 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { ReactElement } from 'react';
import { render } from '@testing-library/react';
import { mocked } from 'jest-mock';
import React, { ReactElement } from "react";
import { render } from "@testing-library/react";
import { mocked } from "jest-mock";
import SettingsStore, { CallbackFn } from '../../../../src/settings/SettingsStore';
import SdkConfig from '../../../../src/SdkConfig';
import { UserTab } from '../../../../src/components/views/dialogs/UserTab';
import UserSettingsDialog from '../../../../src/components/views/dialogs/UserSettingsDialog';
import { IDialogProps } from '../../../../src/components/views/dialogs/IDialogProps';
import SettingsStore, { CallbackFn } from "../../../../src/settings/SettingsStore";
import SdkConfig from "../../../../src/SdkConfig";
import { UserTab } from "../../../../src/components/views/dialogs/UserTab";
import UserSettingsDialog from "../../../../src/components/views/dialogs/UserSettingsDialog";
import { IDialogProps } from "../../../../src/components/views/dialogs/IDialogProps";
import {
getMockClientWithEventEmitter,
mockClientMethodsUser,
mockClientMethodsServer,
mockPlatformPeg,
} from '../../../test-utils';
import { UIFeature } from '../../../../src/settings/UIFeature';
import { SettingLevel } from '../../../../src/settings/SettingLevel';
} from "../../../test-utils";
import { UIFeature } from "../../../../src/settings/UIFeature";
import { SettingLevel } from "../../../../src/settings/SettingLevel";
mockPlatformPeg({
supportsSpellCheckSettings: jest.fn().mockReturnValue(false),
getAppVersion: jest.fn().mockResolvedValue('1'),
getAppVersion: jest.fn().mockResolvedValue("1"),
});
jest.mock('../../../../src/settings/SettingsStore', () => ({
jest.mock("../../../../src/settings/SettingsStore", () => ({
getValue: jest.fn(),
getValueAt: jest.fn(),
canSetValue: jest.fn(),
@ -48,12 +48,12 @@ jest.mock('../../../../src/settings/SettingsStore', () => ({
getBetaInfo: jest.fn(),
}));
jest.mock('../../../../src/SdkConfig', () => ({
jest.mock("../../../../src/SdkConfig", () => ({
get: jest.fn(),
}));
describe('<UserSettingsDialog />', () => {
const userId = '@alice:server.org';
describe("<UserSettingsDialog />", () => {
const userId = "@alice:server.org";
const mockSettingsStore = mocked(SettingsStore);
const mockSdkConfig = mocked(SdkConfig);
getMockClientWithEventEmitter({
@ -62,7 +62,7 @@ describe('<UserSettingsDialog />', () => {
});
const defaultProps = { onFinished: jest.fn() };
const getComponent = (props: Partial<IDialogProps & {initialTabId?: UserTab}> = {}): ReactElement => (
const getComponent = (props: Partial<IDialogProps & { initialTabId?: UserTab }> = {}): ReactElement => (
<UserSettingsDialog {...defaultProps} {...props} />
);
@ -70,52 +70,52 @@ describe('<UserSettingsDialog />', () => {
jest.clearAllMocks();
mockSettingsStore.getValue.mockReturnValue(false);
mockSettingsStore.getFeatureSettingNames.mockReturnValue([]);
mockSdkConfig.get.mockReturnValue({ brand: 'Test' });
mockSdkConfig.get.mockReturnValue({ brand: "Test" });
});
const getActiveTabLabel = (container) => container.querySelector('.mx_TabbedView_tabLabel_active').textContent;
const getActiveTabHeading = (container) => container.querySelector('.mx_SettingsTab_heading').textContent;
const getActiveTabLabel = (container) => container.querySelector(".mx_TabbedView_tabLabel_active").textContent;
const getActiveTabHeading = (container) => container.querySelector(".mx_SettingsTab_heading").textContent;
it('should render general settings tab when no initialTabId', () => {
it("should render general settings tab when no initialTabId", () => {
const { container } = render(getComponent());
expect(getActiveTabLabel(container)).toEqual('General');
expect(getActiveTabHeading(container)).toEqual('General');
expect(getActiveTabLabel(container)).toEqual("General");
expect(getActiveTabHeading(container)).toEqual("General");
});
it('should render initial tab when initialTabId is set', () => {
it("should render initial tab when initialTabId is set", () => {
const { container } = render(getComponent({ initialTabId: UserTab.Help }));
expect(getActiveTabLabel(container)).toEqual('Help & About');
expect(getActiveTabHeading(container)).toEqual('Help & About');
expect(getActiveTabLabel(container)).toEqual("Help & About");
expect(getActiveTabHeading(container)).toEqual("Help & About");
});
it('should render general tab if initialTabId tab cannot be rendered', () => {
it("should render general tab if initialTabId tab cannot be rendered", () => {
// mjolnir tab is only rendered in some configs
const { container } = render(getComponent({ initialTabId: UserTab.Mjolnir }));
expect(getActiveTabLabel(container)).toEqual('General');
expect(getActiveTabHeading(container)).toEqual('General');
expect(getActiveTabLabel(container)).toEqual("General");
expect(getActiveTabHeading(container)).toEqual("General");
});
it('renders tabs correctly', () => {
it("renders tabs correctly", () => {
const { container } = render(getComponent());
expect(container.querySelectorAll('.mx_TabbedView_tabLabel')).toMatchSnapshot();
expect(container.querySelectorAll(".mx_TabbedView_tabLabel")).toMatchSnapshot();
});
it('renders ignored users tab when feature_mjolnir is enabled', () => {
it("renders ignored users tab when feature_mjolnir is enabled", () => {
mockSettingsStore.getValue.mockImplementation((settingName): any => settingName === "feature_mjolnir");
const { getByTestId } = render(getComponent());
expect(getByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeTruthy();
});
it('renders voip tab when voip is enabled', () => {
it("renders voip tab when voip is enabled", () => {
mockSettingsStore.getValue.mockImplementation((settingName): any => settingName === UIFeature.Voip);
const { getByTestId } = render(getComponent());
expect(getByTestId(`settings-tab-${UserTab.Voice}`)).toBeTruthy();
});
it('renders session manager tab when enabled', () => {
it("renders session manager tab when enabled", () => {
mockSettingsStore.getValue.mockImplementation((settingName): any => {
return settingName === "feature_new_device_manager";
});
@ -123,23 +123,23 @@ describe('<UserSettingsDialog />', () => {
expect(getByTestId(`settings-tab-${UserTab.SessionManager}`)).toBeTruthy();
});
it('renders labs tab when show_labs_settings is enabled in config', () => {
it("renders labs tab when show_labs_settings is enabled in config", () => {
// @ts-ignore simplified test stub
mockSdkConfig.get.mockImplementation((configName) => configName === "show_labs_settings");
const { getByTestId } = render(getComponent());
expect(getByTestId(`settings-tab-${UserTab.Labs}`)).toBeTruthy();
});
it('renders labs tab when some feature is in beta', () => {
mockSettingsStore.getFeatureSettingNames.mockReturnValue(['feature_beta_setting', 'feature_just_normal_labs']);
mockSettingsStore.getBetaInfo.mockImplementation(
(settingName) => settingName === 'feature_beta_setting' ? {} as any : undefined,
it("renders labs tab when some feature is in beta", () => {
mockSettingsStore.getFeatureSettingNames.mockReturnValue(["feature_beta_setting", "feature_just_normal_labs"]);
mockSettingsStore.getBetaInfo.mockImplementation((settingName) =>
settingName === "feature_beta_setting" ? ({} as any) : undefined,
);
const { getByTestId } = render(getComponent());
expect(getByTestId(`settings-tab-${UserTab.Labs}`)).toBeTruthy();
});
it('watches settings', () => {
it("watches settings", () => {
const watchSettingCallbacks: Record<string, CallbackFn> = {};
mockSettingsStore.watchSetting.mockImplementation((settingName, roomId, callback) => {
@ -150,17 +150,21 @@ describe('<UserSettingsDialog />', () => {
const { queryByTestId, unmount } = render(getComponent());
expect(queryByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeFalsy();
expect(mockSettingsStore.watchSetting.mock.calls[0][0]).toEqual('feature_mjolnir');
expect(mockSettingsStore.watchSetting.mock.calls[1][0]).toEqual('feature_new_device_manager');
expect(mockSettingsStore.watchSetting.mock.calls[0][0]).toEqual("feature_mjolnir");
expect(mockSettingsStore.watchSetting.mock.calls[1][0]).toEqual("feature_new_device_manager");
// call the watch setting callback
watchSettingCallbacks["feature_mjolnir"]("feature_mjolnir", '', SettingLevel.ACCOUNT, true, true);
watchSettingCallbacks["feature_mjolnir"]("feature_mjolnir", "", SettingLevel.ACCOUNT, true, true);
// tab is rendered now
expect(queryByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeTruthy();
// call the watch setting callback
watchSettingCallbacks["feature_new_device_manager"](
"feature_new_device_manager", '', SettingLevel.ACCOUNT, true, true,
"feature_new_device_manager",
"",
SettingLevel.ACCOUNT,
true,
true,
);
// tab is rendered now
expect(queryByTestId(`settings-tab-${UserTab.SessionManager}`)).toBeTruthy();
@ -168,7 +172,7 @@ describe('<UserSettingsDialog />', () => {
unmount();
// unwatches settings on unmount
expect(mockSettingsStore.unwatchSetting).toHaveBeenCalledWith('mock-watcher-id-feature_mjolnir');
expect(mockSettingsStore.unwatchSetting).toHaveBeenCalledWith('mock-watcher-id-feature_new_device_manager');
expect(mockSettingsStore.unwatchSetting).toHaveBeenCalledWith("mock-watcher-id-feature_mjolnir");
expect(mockSettingsStore.unwatchSetting).toHaveBeenCalledWith("mock-watcher-id-feature_new_device_manager");
});
});

View file

@ -22,19 +22,21 @@ import { PublicRoomResultDetails } from "../../../../../src/components/views/dia
describe("PublicRoomResultDetails", () => {
it("renders", () => {
const { asFragment } = render(<PublicRoomResultDetails
room={{
room_id: "room-id",
name: "hello?",
canonical_alias: "canonical-alias",
world_readable: true,
guest_can_join: false,
num_joined_members: 666,
}}
labelId="label-id"
descriptionId="description-id"
detailsId="details-id"
/>);
const { asFragment } = render(
<PublicRoomResultDetails
room={{
room_id: "room-id",
name: "hello?",
canonical_alias: "canonical-alias",
world_readable: true,
guest_can_join: false,
num_joined_members: 666,
}}
labelId="label-id"
descriptionId="description-id"
detailsId="details-id"
/>,
);
expect(asFragment()).toMatchSnapshot();
});
@ -57,12 +59,14 @@ describe("PublicRoomResultDetails", () => {
...partialPublicRoomChunk,
};
const { asFragment } = render(<PublicRoomResultDetails
room={roomChunk}
labelId="label-id"
descriptionId="description-id"
detailsId="details-id"
/>);
const { asFragment } = render(
<PublicRoomResultDetails
room={roomChunk}
labelId="label-id"
descriptionId="description-id"
detailsId="details-id"
/>,
);
expect(asFragment()).toMatchSnapshot();
});

View file

@ -14,22 +14,21 @@ 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 { mount } from "enzyme";
import { act } from "react-dom/test-utils";
import AccessibleButton from '../../../../src/components/views/elements/AccessibleButton';
import { Key } from '../../../../src/Keyboard';
import { mockPlatformPeg, unmockPlatformPeg } from '../../../test-utils';
import AccessibleButton from "../../../../src/components/views/elements/AccessibleButton";
import { Key } from "../../../../src/Keyboard";
import { mockPlatformPeg, unmockPlatformPeg } from "../../../test-utils";
describe('<AccessibleButton />', () => {
describe("<AccessibleButton />", () => {
const defaultProps = {
onClick: jest.fn(),
children: 'i am a button',
children: "i am a button",
};
const getComponent = (props = {}) =>
mount(<AccessibleButton {...defaultProps} {...props} />);
const getComponent = (props = {}) => mount(<AccessibleButton {...defaultProps} {...props} />);
beforeEach(() => {
mockPlatformPeg();
@ -39,66 +38,67 @@ describe('<AccessibleButton />', () => {
unmockPlatformPeg();
});
const makeKeyboardEvent = (key: string) => ({
key,
stopPropagation: jest.fn(),
preventDefault: jest.fn(),
}) as unknown as KeyboardEvent;
const makeKeyboardEvent = (key: string) =>
({
key,
stopPropagation: jest.fn(),
preventDefault: jest.fn(),
} as unknown as KeyboardEvent);
it('renders div with role button by default', () => {
it("renders div with role button by default", () => {
const component = getComponent();
expect(component).toMatchSnapshot();
});
it('renders a button element', () => {
const component = getComponent({ element: 'button' });
it("renders a button element", () => {
const component = getComponent({ element: "button" });
expect(component).toMatchSnapshot();
});
it('renders with correct classes when button has kind', () => {
it("renders with correct classes when button has kind", () => {
const component = getComponent({
kind: 'primary',
kind: "primary",
});
expect(component).toMatchSnapshot();
});
it('disables button correctly', () => {
it("disables button correctly", () => {
const onClick = jest.fn();
const component = getComponent({
onClick,
disabled: true,
});
expect(component.find('.mx_AccessibleButton').props().disabled).toBeTruthy();
expect(component.find('.mx_AccessibleButton').props()['aria-disabled']).toBeTruthy();
expect(component.find(".mx_AccessibleButton").props().disabled).toBeTruthy();
expect(component.find(".mx_AccessibleButton").props()["aria-disabled"]).toBeTruthy();
act(() => {
component.simulate('click');
component.simulate("click");
});
expect(onClick).not.toHaveBeenCalled();
act(() => {
const keydownEvent = makeKeyboardEvent(Key.ENTER);
component.simulate('keydown', keydownEvent);
component.simulate("keydown", keydownEvent);
});
expect(onClick).not.toHaveBeenCalled();
});
it('calls onClick handler on button click', () => {
it("calls onClick handler on button click", () => {
const onClick = jest.fn();
const component = getComponent({
onClick,
});
act(() => {
component.simulate('click');
component.simulate("click");
});
expect(onClick).toHaveBeenCalled();
});
it('calls onClick handler on button mousedown when triggerOnMousedown is passed', () => {
it("calls onClick handler on button mousedown when triggerOnMousedown is passed", () => {
const onClick = jest.fn();
const component = getComponent({
onClick,
@ -106,14 +106,14 @@ describe('<AccessibleButton />', () => {
});
act(() => {
component.simulate('mousedown');
component.simulate("mousedown");
});
expect(onClick).toHaveBeenCalled();
});
describe('handling keyboard events', () => {
it('calls onClick handler on enter keydown', () => {
describe("handling keyboard events", () => {
it("calls onClick handler on enter keydown", () => {
const onClick = jest.fn();
const component = getComponent({
onClick,
@ -121,13 +121,13 @@ describe('<AccessibleButton />', () => {
const keyboardEvent = makeKeyboardEvent(Key.ENTER);
act(() => {
component.simulate('keydown', keyboardEvent);
component.simulate("keydown", keyboardEvent);
});
expect(onClick).toHaveBeenCalled();
act(() => {
component.simulate('keyup', keyboardEvent);
component.simulate("keyup", keyboardEvent);
});
// handler only called once on keydown
@ -137,7 +137,7 @@ describe('<AccessibleButton />', () => {
expect(keyboardEvent.preventDefault).toHaveBeenCalledTimes(2);
});
it('calls onClick handler on space keyup', () => {
it("calls onClick handler on space keyup", () => {
const onClick = jest.fn();
const component = getComponent({
onClick,
@ -145,13 +145,13 @@ describe('<AccessibleButton />', () => {
const keyboardEvent = makeKeyboardEvent(Key.SPACE);
act(() => {
component.simulate('keydown', keyboardEvent);
component.simulate("keydown", keyboardEvent);
});
expect(onClick).not.toHaveBeenCalled();
act(() => {
component.simulate('keyup', keyboardEvent);
component.simulate("keyup", keyboardEvent);
});
// handler only called once on keyup
@ -161,7 +161,7 @@ describe('<AccessibleButton />', () => {
expect(keyboardEvent.preventDefault).toHaveBeenCalledTimes(2);
});
it('calls onKeydown/onKeyUp handlers for keys other than space and enter', () => {
it("calls onKeydown/onKeyUp handlers for keys other than space and enter", () => {
const onClick = jest.fn();
const onKeyDown = jest.fn();
const onKeyUp = jest.fn();
@ -173,8 +173,8 @@ describe('<AccessibleButton />', () => {
const keyboardEvent = makeKeyboardEvent(Key.K);
act(() => {
component.simulate('keydown', keyboardEvent);
component.simulate('keyup', keyboardEvent);
component.simulate("keydown", keyboardEvent);
component.simulate("keyup", keyboardEvent);
});
expect(onClick).not.toHaveBeenCalled();
@ -184,7 +184,7 @@ describe('<AccessibleButton />', () => {
expect(keyboardEvent.preventDefault).not.toHaveBeenCalled();
});
it('does nothing on non space/enter key presses when no onKeydown/onKeyUp handlers provided', () => {
it("does nothing on non space/enter key presses when no onKeydown/onKeyUp handlers provided", () => {
const onClick = jest.fn();
const component = getComponent({
onClick,
@ -192,8 +192,8 @@ describe('<AccessibleButton />', () => {
const keyboardEvent = makeKeyboardEvent(Key.K);
act(() => {
component.simulate('keydown', keyboardEvent);
component.simulate('keyup', keyboardEvent);
component.simulate("keydown", keyboardEvent);
component.simulate("keyup", keyboardEvent);
});
// no onClick call, no problems

View file

@ -52,17 +52,15 @@ describe("AppTile", () => {
let app1: IApp;
let app2: IApp;
const waitForRps = (roomId: string) => new Promise<void>(resolve => {
const update = () => {
if (
RightPanelStore.instance.currentCardForRoom(roomId).phase !==
RightPanelPhases.Widget
) return;
RightPanelStore.instance.off(UPDATE_EVENT, update);
resolve();
};
RightPanelStore.instance.on(UPDATE_EVENT, update);
});
const waitForRps = (roomId: string) =>
new Promise<void>((resolve) => {
const update = () => {
if (RightPanelStore.instance.currentCardForRoom(roomId).phase !== RightPanelPhases.Widget) return;
RightPanelStore.instance.off(UPDATE_EVENT, update);
resolve();
};
RightPanelStore.instance.on(UPDATE_EVENT, update);
});
beforeAll(async () => {
stubClient();
@ -75,7 +73,7 @@ describe("AppTile", () => {
r1 = new Room("r1", cli, "@name:example.com");
r2 = new Room("r2", cli, "@name:example.com");
jest.spyOn(cli, "getRoom").mockImplementation(roomId => {
jest.spyOn(cli, "getRoom").mockImplementation((roomId) => {
if (roomId === "r1") return r1;
if (roomId === "r2") return r2;
return null;
@ -105,7 +103,7 @@ describe("AppTile", () => {
creatorUserId: cli.getUserId(),
avatar_url: undefined,
};
jest.spyOn(WidgetStore.instance, "getApps").mockImplementation(roomId => {
jest.spyOn(WidgetStore.instance, "getApps").mockImplementation((roomId) => {
if (roomId === "r1") return [app1];
if (roomId === "r2") return [app2];
});
@ -130,12 +128,14 @@ describe("AppTile", () => {
if (name !== "RightPanel.phases") return realGetValue(name, roomId);
if (roomId === "r1") {
return {
history: [{
phase: RightPanelPhases.Widget,
state: {
widgetId: "1",
history: [
{
phase: RightPanelPhases.Widget,
state: {
widgetId: "1",
},
},
}],
],
isOpen: true,
};
}
@ -143,12 +143,11 @@ describe("AppTile", () => {
});
// Run initial render with room 1, and also running lifecycle methods
const renderer = TestRenderer.create(<MatrixClientContext.Provider value={cli}>
<RightPanel
room={r1}
resizeNotifier={resizeNotifier}
/>
</MatrixClientContext.Provider>);
const renderer = TestRenderer.create(
<MatrixClientContext.Provider value={cli}>
<RightPanel room={r1} resizeNotifier={resizeNotifier} />
</MatrixClientContext.Provider>,
);
// Wait for RPS room 1 updates to fire
const rpsUpdated = waitForRps("r1");
dis.dispatch({
@ -169,12 +168,11 @@ describe("AppTile", () => {
action: Action.ViewRoom,
room_id: "r2",
});
renderer.update(<MatrixClientContext.Provider value={cli}>
<RightPanel
room={r2}
resizeNotifier={resizeNotifier}
/>
</MatrixClientContext.Provider>);
renderer.update(
<MatrixClientContext.Provider value={cli}>
<RightPanel room={r2} resizeNotifier={resizeNotifier} />
</MatrixClientContext.Provider>,
);
expect(endWidgetActions.mock.calls.length).toBe(1);
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(false);
@ -185,16 +183,18 @@ describe("AppTile", () => {
it("distinguishes widgets with the same ID in different rooms", async () => {
// Set up right panel state
const realGetValue = SettingsStore.getValue;
jest.spyOn(SettingsStore, 'getValue').mockImplementation((name, roomId) => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((name, roomId) => {
if (name === "RightPanel.phases") {
if (roomId === "r1") {
return {
history: [{
phase: RightPanelPhases.Widget,
state: {
widgetId: "1",
history: [
{
phase: RightPanelPhases.Widget,
state: {
widgetId: "1",
},
},
}],
],
isOpen: true,
};
}
@ -204,12 +204,11 @@ describe("AppTile", () => {
});
// Run initial render with room 1, and also running lifecycle methods
const renderer = TestRenderer.create(<MatrixClientContext.Provider value={cli}>
<RightPanel
room={r1}
resizeNotifier={resizeNotifier}
/>
</MatrixClientContext.Provider>);
const renderer = TestRenderer.create(
<MatrixClientContext.Provider value={cli}>
<RightPanel room={r1} resizeNotifier={resizeNotifier} />
</MatrixClientContext.Provider>,
);
// Wait for RPS room 1 updates to fire
const rpsUpdated1 = waitForRps("r1");
dis.dispatch({
@ -225,12 +224,14 @@ describe("AppTile", () => {
if (name === "RightPanel.phases") {
if (roomId === "r2") {
return {
history: [{
phase: RightPanelPhases.Widget,
state: {
widgetId: "1",
history: [
{
phase: RightPanelPhases.Widget,
state: {
widgetId: "1",
},
},
}],
],
isOpen: true,
};
}
@ -245,12 +246,11 @@ describe("AppTile", () => {
action: Action.ViewRoom,
room_id: "r2",
});
renderer.update(<MatrixClientContext.Provider value={cli}>
<RightPanel
room={r2}
resizeNotifier={resizeNotifier}
/>
</MatrixClientContext.Provider>);
renderer.update(
<MatrixClientContext.Provider value={cli}>
<RightPanel room={r2} resizeNotifier={resizeNotifier} />
</MatrixClientContext.Provider>,
);
await rpsUpdated2;
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(false);
@ -279,13 +279,11 @@ describe("AppTile", () => {
});
// Run initial render with room 1, and also running lifecycle methods
const renderer = TestRenderer.create(<MatrixClientContext.Provider value={cli}>
<AppsDrawer
userId={cli.getUserId()}
room={r1}
resizeNotifier={resizeNotifier}
/>
</MatrixClientContext.Provider>);
const renderer = TestRenderer.create(
<MatrixClientContext.Provider value={cli}>
<AppsDrawer userId={cli.getUserId()} room={r1} resizeNotifier={resizeNotifier} />
</MatrixClientContext.Provider>,
);
expect(ActiveWidgetStore.instance.isLive("1", "r1")).toBe(true);
@ -319,38 +317,34 @@ describe("AppTile", () => {
let moveToContainerSpy;
beforeEach(() => {
wrapper = mount((
wrapper = mount(
<MatrixClientContext.Provider value={cli}>
<AppTile
key={app1.id}
app={app1}
room={r1}
/>
</MatrixClientContext.Provider>
));
<AppTile key={app1.id} app={app1} room={r1} />
</MatrixClientContext.Provider>,
);
moveToContainerSpy = jest.spyOn(WidgetLayoutStore.instance, 'moveToContainer');
moveToContainerSpy = jest.spyOn(WidgetLayoutStore.instance, "moveToContainer");
});
it("requiresClient should be true", () => {
expect(wrapper.state('requiresClient')).toBe(true);
expect(wrapper.state("requiresClient")).toBe(true);
});
it("clicking 'minimise' should send the widget to the right", () => {
const minimiseButton = wrapper.find('.mx_AppTileMenuBar_iconButton_minimise');
minimiseButton.first().simulate('click');
const minimiseButton = wrapper.find(".mx_AppTileMenuBar_iconButton_minimise");
minimiseButton.first().simulate("click");
expect(moveToContainerSpy).toHaveBeenCalledWith(r1, app1, Container.Right);
});
it("clicking 'maximise' should send the widget to the center", () => {
const minimiseButton = wrapper.find('.mx_AppTileMenuBar_iconButton_maximise');
minimiseButton.first().simulate('click');
const minimiseButton = wrapper.find(".mx_AppTileMenuBar_iconButton_maximise");
minimiseButton.first().simulate("click");
expect(moveToContainerSpy).toHaveBeenCalledWith(r1, app1, Container.Center);
});
describe("for a maximised (centered) widget", () => {
beforeEach(() => {
jest.spyOn(WidgetLayoutStore.instance, 'isInContainer').mockImplementation(
jest.spyOn(WidgetLayoutStore.instance, "isInContainer").mockImplementation(
(room: Optional<Room>, widget: IApp, container: Container) => {
return room === r1 && widget === app1 && container === Container.Center;
},
@ -358,8 +352,8 @@ describe("AppTile", () => {
});
it("clicking 'un-maximise' should send the widget to the top", () => {
const unMaximiseButton = wrapper.find('.mx_AppTileMenuBar_iconButton_collapse');
unMaximiseButton.first().simulate('click');
const unMaximiseButton = wrapper.find(".mx_AppTileMenuBar_iconButton_collapse");
unMaximiseButton.first().simulate("click");
expect(moveToContainerSpy).toHaveBeenCalledWith(r1, app1, Container.Top);
});
});
@ -378,19 +372,15 @@ describe("AppTile", () => {
const mockWidget = new ElementWidget(app1);
WidgetMessagingStore.instance.storeMessaging(mockWidget, r1.roomId, api);
wrapper = mount((
wrapper = mount(
<MatrixClientContext.Provider value={cli}>
<AppTile
key={app1.id}
app={app1}
room={r1}
/>
</MatrixClientContext.Provider>
));
<AppTile key={app1.id} app={app1} room={r1} />
</MatrixClientContext.Provider>,
);
});
it("requiresClient should be false", () => {
expect(wrapper.state('requiresClient')).toBe(false);
expect(wrapper.state("requiresClient")).toBe(false);
});
});
});

View file

@ -14,23 +14,23 @@ 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, ReactWrapper } from 'enzyme';
import { MatrixEvent, RoomMember } from 'matrix-js-sdk/src/matrix';
import { mount, ReactWrapper } from "enzyme";
import { MatrixEvent, RoomMember } from "matrix-js-sdk/src/matrix";
import {
getMockClientWithEventEmitter,
mkMembership,
mockClientMethodsUser,
unmockClientPeg,
} from '../../../test-utils';
} from "../../../test-utils";
import EventListSummary from "../../../../src/components/views/elements/EventListSummary";
import { Layout } from '../../../../src/settings/enums/Layout';
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
import { Layout } from "../../../../src/settings/enums/Layout";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
describe('EventListSummary', function() {
const roomId = '!room:server.org';
describe("EventListSummary", function () {
const roomId = "!room:server.org";
// Generate dummy event tiles for use in simulating an expanded MELS
const generateTiles = (events: MatrixEvent[]) => {
return events.map((e) => {
@ -64,13 +64,14 @@ describe('EventListSummary', function() {
prevMembership?: string;
}
const generateMembershipEvent = (
eventId: string, { senderId, userId, membership, prevMembership }: MembershipEventParams,
eventId: string,
{ senderId, userId, membership, prevMembership }: MembershipEventParams,
): MatrixEvent => {
const member = new RoomMember(roomId, userId);
// Use localpart as display name;
member.name = userId.match(/@([^:]*):/)[1];
jest.spyOn(member, 'getAvatarUrl').mockReturnValue('avatar.jpeg');
jest.spyOn(member, 'getMxcAvatarUrl').mockReturnValue('mxc://avatar.url/image.png');
jest.spyOn(member, "getAvatarUrl").mockReturnValue("avatar.jpeg");
jest.spyOn(member, "getMxcAvatarUrl").mockReturnValue("mxc://avatar.url/image.png");
const e = mkMembership({
event: true,
room: roomId,
@ -102,7 +103,7 @@ describe('EventListSummary', function() {
let eventsForUsers = [];
let userId = "";
for (let i = 0; i < n; i++) {
userId = userIdTemplate.replace('$', i);
userId = userIdTemplate.replace("$", i);
events.forEach((e) => {
e.userId = userId;
});
@ -121,13 +122,14 @@ describe('EventListSummary', function() {
children: [],
};
const renderComponent = (props = {}): ReactWrapper => {
return mount(<MatrixClientContext.Provider value={mockClient}>
<EventListSummary {...defaultProps} {...props} />
</MatrixClientContext.Provider>,
return mount(
<MatrixClientContext.Provider value={mockClient}>
<EventListSummary {...defaultProps} {...props} />
</MatrixClientContext.Provider>,
);
};
beforeEach(function() {
beforeEach(function () {
jest.clearAllMocks();
});
@ -135,10 +137,8 @@ describe('EventListSummary', function() {
unmockClientPeg();
});
it('renders expanded events if there are less than props.threshold', function() {
const events = generateEvents([
{ userId: "@user_1:some.domain", prevMembership: "leave", membership: "join" },
]);
it("renders expanded events if there are less than props.threshold", function () {
const events = generateEvents([{ userId: "@user_1:some.domain", prevMembership: "leave", membership: "join" }]);
const props = {
events: events,
children: generateTiles(events),
@ -149,12 +149,14 @@ describe('EventListSummary', function() {
const wrapper = renderComponent(props); // matrix cli context wrapper
expect(wrapper.find('GenericEventListSummary').props().children).toEqual([
<div className="event_tile" key="event0">Expanded membership</div>,
expect(wrapper.find("GenericEventListSummary").props().children).toEqual([
<div className="event_tile" key="event0">
Expanded membership
</div>,
]);
});
it('renders expanded events if there are less than props.threshold', function() {
it("renders expanded events if there are less than props.threshold", function () {
const events = generateEvents([
{ userId: "@user_1:some.domain", prevMembership: "leave", membership: "join" },
{ userId: "@user_1:some.domain", prevMembership: "join", membership: "leave" },
@ -169,13 +171,17 @@ describe('EventListSummary', function() {
const wrapper = renderComponent(props); // matrix cli context wrapper
expect(wrapper.find('GenericEventListSummary').props().children).toEqual([
<div className="event_tile" key="event0">Expanded membership</div>,
<div className="event_tile" key="event1">Expanded membership</div>,
expect(wrapper.find("GenericEventListSummary").props().children).toEqual([
<div className="event_tile" key="event0">
Expanded membership
</div>,
<div className="event_tile" key="event1">
Expanded membership
</div>,
]);
});
it('renders collapsed events if events.length = props.threshold', function() {
it("renders collapsed events if events.length = props.threshold", function () {
const events = generateEvents([
{ userId: "@user_1:some.domain", prevMembership: "leave", membership: "join" },
{ userId: "@user_1:some.domain", prevMembership: "join", membership: "leave" },
@ -196,7 +202,7 @@ describe('EventListSummary', function() {
expect(summaryText).toBe("user_1 joined and left and joined");
});
it('truncates long join,leave repetitions', function() {
it("truncates long join,leave repetitions", function () {
const events = generateEvents([
{ userId: "@user_1:some.domain", prevMembership: "leave", membership: "join" },
{ userId: "@user_1:some.domain", prevMembership: "join", membership: "leave" },
@ -228,7 +234,7 @@ describe('EventListSummary', function() {
expect(summaryText).toBe("user_1 joined and left 7 times");
});
it('truncates long join,leave repetitions between other events', function() {
it("truncates long join,leave repetitions between other events", function () {
const events = generateEvents([
{
userId: "@user_1:some.domain",
@ -269,12 +275,10 @@ describe('EventListSummary', function() {
const summary = wrapper.find(".mx_GenericEventListSummary_summary");
const summaryText = summary.text();
expect(summaryText).toBe(
"user_1 was unbanned, joined and left 7 times and was invited",
);
expect(summaryText).toBe("user_1 was unbanned, joined and left 7 times and was invited");
});
it('truncates multiple sequences of repetitions with other events between', function() {
it("truncates multiple sequences of repetitions with other events between", function () {
const events = generateEvents([
{
userId: "@user_1:some.domain",
@ -318,12 +322,11 @@ describe('EventListSummary', function() {
const summaryText = summary.text();
expect(summaryText).toBe(
"user_1 was unbanned, joined and left 2 times, was banned, " +
"joined and left 3 times and was invited",
"user_1 was unbanned, joined and left 2 times, was banned, " + "joined and left 3 times and was invited",
);
});
it('handles multiple users following the same sequence of memberships', function() {
it("handles multiple users following the same sequence of memberships", function () {
const events = generateEvents([
// user_1
{
@ -372,12 +375,10 @@ describe('EventListSummary', function() {
const summary = wrapper.find(".mx_GenericEventListSummary_summary");
const summaryText = summary.text();
expect(summaryText).toBe(
"user_1 and one other were unbanned, joined and left 2 times and were banned",
);
expect(summaryText).toBe("user_1 and one other were unbanned, joined and left 2 times and were banned");
});
it('handles many users following the same sequence of memberships', function() {
it("handles many users following the same sequence of memberships", function () {
const events = generateEventsForUsers("@user_$:some.domain", 20, [
{
prevMembership: "ban",
@ -406,12 +407,10 @@ describe('EventListSummary', function() {
const summary = wrapper.find(".mx_GenericEventListSummary_summary");
const summaryText = summary.text();
expect(summaryText).toBe(
"user_0 and 19 others were unbanned, joined and left 2 times and were banned",
);
expect(summaryText).toBe("user_0 and 19 others were unbanned, joined and left 2 times and were banned");
});
it('correctly orders sequences of transitions by the order of their first event', function() {
it("correctly orders sequences of transitions by the order of their first event", function () {
const events = generateEvents([
{
userId: "@user_2:some.domain",
@ -454,11 +453,11 @@ describe('EventListSummary', function() {
expect(summaryText).toBe(
"user_2 was unbanned and joined and left 2 times, user_1 was unbanned, " +
"joined and left 2 times and was banned",
"joined and left 2 times and was banned",
);
});
it('correctly identifies transitions', function() {
it("correctly identifies transitions", function () {
const events = generateEvents([
// invited
{ userId: "@user_1:some.domain", membership: "invite" },
@ -524,11 +523,11 @@ describe('EventListSummary', function() {
expect(summaryText).toBe(
"user_1 was invited, was banned, joined, rejected their invitation, left, " +
"had their invitation withdrawn, was unbanned, was removed, left and was removed",
"had their invitation withdrawn, was unbanned, was removed, left and was removed",
);
});
it('handles invitation plurals correctly when there are multiple users', function() {
it("handles invitation plurals correctly when there are multiple users", function () {
const events = generateEvents([
{
userId: "@user_1:some.domain",
@ -566,12 +565,11 @@ describe('EventListSummary', function() {
const summaryText = summary.text();
expect(summaryText).toBe(
"user_1 and one other rejected their invitations and " +
"had their invitations withdrawn",
"user_1 and one other rejected their invitations and " + "had their invitations withdrawn",
);
});
it('handles invitation plurals correctly when there are multiple invites', function() {
it("handles invitation plurals correctly when there are multiple invites", function () {
const events = generateEvents([
{
userId: "@user_1:some.domain",
@ -596,12 +594,10 @@ describe('EventListSummary', function() {
const summary = wrapper.find(".mx_GenericEventListSummary_summary");
const summaryText = summary.text();
expect(summaryText).toBe(
"user_1 rejected their invitation 2 times",
);
expect(summaryText).toBe("user_1 rejected their invitation 2 times");
});
it('handles a summary length = 2, with no "others"', function() {
it('handles a summary length = 2, with no "others"', function () {
const events = generateEvents([
{ userId: "@user_1:some.domain", membership: "join" },
{ userId: "@user_1:some.domain", membership: "join" },
@ -620,12 +616,10 @@ describe('EventListSummary', function() {
const summary = wrapper.find(".mx_GenericEventListSummary_summary");
const summaryText = summary.text();
expect(summaryText).toBe(
"user_1 and user_2 joined 2 times",
);
expect(summaryText).toBe("user_1 and user_2 joined 2 times");
});
it('handles a summary length = 2, with 1 "other"', function() {
it('handles a summary length = 2, with 1 "other"', function () {
const events = generateEvents([
{ userId: "@user_1:some.domain", membership: "join" },
{ userId: "@user_2:some.domain", membership: "join" },
@ -643,15 +637,11 @@ describe('EventListSummary', function() {
const summary = wrapper.find(".mx_GenericEventListSummary_summary");
const summaryText = summary.text();
expect(summaryText).toBe(
"user_1, user_2 and one other joined",
);
expect(summaryText).toBe("user_1, user_2 and one other joined");
});
it('handles a summary length = 2, with many "others"', function() {
const events = generateEventsForUsers("@user_$:some.domain", 20, [
{ membership: "join" },
]);
it('handles a summary length = 2, with many "others"', function () {
const events = generateEventsForUsers("@user_$:some.domain", 20, [{ membership: "join" }]);
const props = {
events: events,
children: generateTiles(events),
@ -664,8 +654,6 @@ describe('EventListSummary', function() {
const summary = wrapper.find(".mx_GenericEventListSummary_summary");
const summaryText = summary.text();
expect(summaryText).toBe(
"user_0, user_1 and 18 others joined",
);
expect(summaryText).toBe("user_0, user_1 and 18 others joined");
});
});

View file

@ -12,39 +12,45 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { renderIntoDocument } from 'react-dom/test-utils';
import React from "react";
import { renderIntoDocument } from "react-dom/test-utils";
import ExternalLink from '../../../../src/components/views/elements/ExternalLink';
import ExternalLink from "../../../../src/components/views/elements/ExternalLink";
describe('<ExternalLink />', () => {
describe("<ExternalLink />", () => {
const defaultProps = {
"href": 'test.com',
"href": "test.com",
"onClick": jest.fn(),
"className": 'myCustomClass',
'data-test-id': 'test',
"className": "myCustomClass",
"data-test-id": "test",
};
const getComponent = (props = {}) => {
const wrapper = renderIntoDocument<HTMLDivElement>(
<div><ExternalLink {...defaultProps} {...props} /></div>,
<div>
<ExternalLink {...defaultProps} {...props} />
</div>,
) as HTMLDivElement;
return wrapper.children[0];
};
it('renders link correctly', () => {
const children = <span>react element <b>children</b></span>;
expect(getComponent({ children, target: '_self', rel: 'noopener' })).toMatchSnapshot();
it("renders link correctly", () => {
const children = (
<span>
react element <b>children</b>
</span>
);
expect(getComponent({ children, target: "_self", rel: "noopener" })).toMatchSnapshot();
});
it('defaults target and rel', () => {
const children = 'test';
it("defaults target and rel", () => {
const children = "test";
const component = getComponent({ children });
expect(component.getAttribute('rel')).toEqual('noreferrer noopener');
expect(component.getAttribute('target')).toEqual('_blank');
expect(component.getAttribute("rel")).toEqual("noreferrer noopener");
expect(component.getAttribute("target")).toEqual("_blank");
});
it('renders plain text link correctly', () => {
const children = 'test';
it("renders plain text link correctly", () => {
const children = "test";
expect(getComponent({ children })).toMatchSnapshot();
});
});

View file

@ -14,55 +14,55 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { act, fireEvent, render } from '@testing-library/react';
import React from 'react';
import { act, fireEvent, render } from "@testing-library/react";
import React from "react";
import { FilterDropdown } from '../../../../src/components/views/elements/FilterDropdown';
import { flushPromises, mockPlatformPeg } from '../../../test-utils';
import { FilterDropdown } from "../../../../src/components/views/elements/FilterDropdown";
import { flushPromises, mockPlatformPeg } from "../../../test-utils";
mockPlatformPeg();
describe('<FilterDropdown />', () => {
describe("<FilterDropdown />", () => {
const options = [
{ id: 'one', label: 'Option one' },
{ id: 'two', label: 'Option two', description: 'with description' },
{ id: "one", label: "Option one" },
{ id: "two", label: "Option two", description: "with description" },
];
const defaultProps = {
className: 'test',
value: 'one',
className: "test",
value: "one",
options,
id: 'test',
label: 'test label',
id: "test",
label: "test label",
onOptionChange: jest.fn(),
};
const getComponent = (props = {}): JSX.Element =>
(<FilterDropdown {...defaultProps} {...props} />);
const getComponent = (props = {}): JSX.Element => <FilterDropdown {...defaultProps} {...props} />;
const openDropdown = async (container: HTMLElement): Promise<void> => await act(async () => {
const button = container.querySelector('[role="button"]');
expect(button).toBeTruthy();
fireEvent.click(button as Element);
await flushPromises();
});
const openDropdown = async (container: HTMLElement): Promise<void> =>
await act(async () => {
const button = container.querySelector('[role="button"]');
expect(button).toBeTruthy();
fireEvent.click(button as Element);
await flushPromises();
});
it('renders selected option', () => {
it("renders selected option", () => {
const { container } = render(getComponent());
expect(container).toMatchSnapshot();
});
it('renders when selected option is not in options', () => {
const { container } = render(getComponent({ value: 'oops' }));
it("renders when selected option is not in options", () => {
const { container } = render(getComponent({ value: "oops" }));
expect(container).toMatchSnapshot();
});
it('renders selected option with selectedLabel', () => {
const { container } = render(getComponent({ selectedLabel: 'Show' }));
it("renders selected option with selectedLabel", () => {
const { container } = render(getComponent({ selectedLabel: "Show" }));
expect(container).toMatchSnapshot();
});
it('renders dropdown options in menu', async () => {
it("renders dropdown options in menu", async () => {
const { container } = render(getComponent());
await openDropdown(container);
expect(container.querySelector('.mx_Dropdown_menu')).toMatchSnapshot();
expect(container.querySelector(".mx_Dropdown_menu")).toMatchSnapshot();
});
});

View file

@ -14,24 +14,21 @@ 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 { mount } from "enzyme";
import { act } from "react-dom/test-utils";
import LabelledCheckbox from "../../../../src/components/views/elements/LabelledCheckbox";
// Fake random strings to give a predictable snapshot for checkbox IDs
jest.mock(
'matrix-js-sdk/src/randomstring',
() => {
return {
randomString: () => "abdefghi",
};
},
);
jest.mock("matrix-js-sdk/src/randomstring", () => {
return {
randomString: () => "abdefghi",
};
});
describe('<LabelledCheckbox />', () => {
describe("<LabelledCheckbox />", () => {
type CompProps = React.ComponentProps<typeof LabelledCheckbox>;
const getComponent = (props: CompProps) => mount(<LabelledCheckbox {...props} />);
type CompClass = ReturnType<typeof getComponent>;
@ -42,29 +39,26 @@ describe('<LabelledCheckbox />', () => {
const isChecked = (checkbox: ReturnType<typeof getCheckbox>) => checkbox.is(`[checked=true]`);
const isDisabled = (checkbox: ReturnType<typeof getCheckbox>) => checkbox.is(`[disabled=true]`);
const getText = (span: ReturnType<typeof getLabel>) => span.length > 0 ? span.at(0).text() : null;
const getText = (span: ReturnType<typeof getLabel>) => (span.length > 0 ? span.at(0).text() : null);
test.each([null, "this is a byline"])(
"should render with byline of %p",
(byline) => {
const props: CompProps = {
label: "Hello world",
value: true,
byline: byline,
onChange: jest.fn(),
};
const component = getComponent(props);
const checkbox = getCheckbox(component);
test.each([null, "this is a byline"])("should render with byline of %p", (byline) => {
const props: CompProps = {
label: "Hello world",
value: true,
byline: byline,
onChange: jest.fn(),
};
const component = getComponent(props);
const checkbox = getCheckbox(component);
expect(component).toMatchSnapshot();
expect(isChecked(checkbox)).toBe(true);
expect(isDisabled(checkbox)).toBe(false);
expect(getText(getLabel(component))).toBe(props.label);
expect(getText(getByline(component))).toBe(byline);
},
);
expect(component).toMatchSnapshot();
expect(isChecked(checkbox)).toBe(true);
expect(isDisabled(checkbox)).toBe(false);
expect(getText(getLabel(component))).toBe(props.label);
expect(getText(getByline(component))).toBe(byline);
});
it('should support unchecked by default', () => {
it("should support unchecked by default", () => {
const props: CompProps = {
label: "Hello world",
value: false,
@ -75,7 +69,7 @@ describe('<LabelledCheckbox />', () => {
expect(isChecked(getCheckbox(component))).toBe(false);
});
it('should be possible to disable the checkbox', () => {
it("should be possible to disable the checkbox", () => {
const props: CompProps = {
label: "Hello world",
value: false,
@ -87,7 +81,7 @@ describe('<LabelledCheckbox />', () => {
expect(isDisabled(getCheckbox(component))).toBe(true);
});
it('should emit onChange calls', () => {
it("should emit onChange calls", () => {
const props: CompProps = {
label: "Hello world",
value: false,
@ -98,13 +92,13 @@ describe('<LabelledCheckbox />', () => {
expect(props.onChange).not.toHaveBeenCalled();
act(() => {
getCheckbox(component).simulate('change');
getCheckbox(component).simulate("change");
});
expect(props.onChange).toHaveBeenCalledTimes(1);
});
it('should react to value and disabled prop changes', () => {
it("should react to value and disabled prop changes", () => {
const props: CompProps = {
label: "Hello world",
value: false,

View file

@ -14,44 +14,41 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { fireEvent, render } from '@testing-library/react';
import React from "react";
import { fireEvent, render } from "@testing-library/react";
import LearnMore from '../../../../src/components/views/elements/LearnMore';
import Modal from '../../../../src/Modal';
import InfoDialog from '../../../../src/components/views/dialogs/InfoDialog';
import LearnMore from "../../../../src/components/views/elements/LearnMore";
import Modal from "../../../../src/Modal";
import InfoDialog from "../../../../src/components/views/dialogs/InfoDialog";
describe('<LearnMore />', () => {
describe("<LearnMore />", () => {
const defaultProps = {
title: 'Test',
description: 'test test test',
['data-testid']: 'testid',
title: "Test",
description: "test test test",
["data-testid"]: "testid",
};
const getComponent = (props = {}) =>
(<LearnMore {...defaultProps} {...props} />);
const getComponent = (props = {}) => <LearnMore {...defaultProps} {...props} />;
const modalSpy = jest.spyOn(Modal, 'createDialog').mockReturnValue(undefined);
const modalSpy = jest.spyOn(Modal, "createDialog").mockReturnValue(undefined);
beforeEach(() => {
jest.clearAllMocks();
});
it('renders button', () => {
it("renders button", () => {
const { container } = render(getComponent());
expect(container).toMatchSnapshot();
});
it('opens modal on click', async () => {
it("opens modal on click", async () => {
const { getByTestId } = render(getComponent());
fireEvent.click(getByTestId('testid'));
fireEvent.click(getByTestId("testid"));
expect(modalSpy).toHaveBeenCalledWith(
InfoDialog,
{
button: 'Got it',
description: defaultProps.description,
hasCloseButton: true,
title: defaultProps.title,
});
expect(modalSpy).toHaveBeenCalledWith(InfoDialog, {
button: "Got it",
description: defaultProps.description,
hasCloseButton: true,
title: defaultProps.title,
});
});
});

View file

@ -19,34 +19,28 @@ import { Linkify } from "../../../../src/components/views/elements/Linkify";
describe("Linkify", () => {
it("linkifies the context", () => {
const { container } = render(<Linkify>
https://perdu.com
</Linkify>);
const { container } = render(<Linkify>https://perdu.com</Linkify>);
expect(container.innerHTML).toBe(
"<div><a href=\"https://perdu.com\" class=\"linkified\" target=\"_blank\" rel=\"noreferrer noopener\">"+
"https://perdu.com" +
"</a></div>",
'<div><a href="https://perdu.com" class="linkified" target="_blank" rel="noreferrer noopener">' +
"https://perdu.com" +
"</a></div>",
);
});
it("correctly linkifies a room alias", () => {
const { container } = render(<Linkify>
#element-web:matrix.org
</Linkify>);
const { container } = render(<Linkify>#element-web:matrix.org</Linkify>);
expect(container.innerHTML).toBe(
"<div>" +
"<a href=\"https://matrix.to/#/#element-web:matrix.org\" class=\"linkified\" rel=\"noreferrer noopener\">" +
"#element-web:matrix.org" +
"</a></div>",
'<a href="https://matrix.to/#/#element-web:matrix.org" class="linkified" rel="noreferrer noopener">' +
"#element-web:matrix.org" +
"</a></div>",
);
});
it("changes the root tag name", () => {
const TAG_NAME = "p";
const { container } = render(<Linkify as={TAG_NAME}>
Hello world!
</Linkify>);
const { container } = render(<Linkify as={TAG_NAME}>Hello world!</Linkify>);
expect(container.querySelectorAll("p")).toHaveLength(1);
});
@ -60,31 +54,29 @@ describe("Linkify", () => {
// upon clicking the element, change the content, and expect
// linkify to update
return <div onClick={onClick}>
<Linkify>
{ n % 2 === 0
? "https://perdu.com"
: "https://matrix.org" }
</Linkify>
</div>;
return (
<div onClick={onClick}>
<Linkify>{n % 2 === 0 ? "https://perdu.com" : "https://matrix.org"}</Linkify>
</div>
);
}
const { container } = render(<DummyTest />);
expect(container.innerHTML).toBe(
"<div><div>" +
"<a href=\"https://perdu.com\" class=\"linkified\" target=\"_blank\" rel=\"noreferrer noopener\">" +
"https://perdu.com" +
"</a></div></div>",
'<a href="https://perdu.com" class="linkified" target="_blank" rel="noreferrer noopener">' +
"https://perdu.com" +
"</a></div></div>",
);
fireEvent.click(container.querySelector("div"));
expect(container.innerHTML).toBe(
"<div><div>" +
"<a href=\"https://matrix.org\" class=\"linkified\" target=\"_blank\" rel=\"noreferrer noopener\">" +
"https://matrix.org" +
"</a></div></div>",
'<a href="https://matrix.org" class="linkified" target="_blank" rel="noreferrer noopener">' +
"https://matrix.org" +
"</a></div></div>",
);
});
});

View file

@ -24,16 +24,13 @@ import {
M_POLL_START,
M_TEXT,
PollStartEvent,
} from 'matrix-events-sdk';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
} from "matrix-events-sdk";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import {
findById,
getMockClientWithEventEmitter,
} from '../../../test-utils';
import { findById, getMockClientWithEventEmitter } from "../../../test-utils";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import PollCreateDialog from "../../../../src/components/views/elements/PollCreateDialog";
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
// Fake date to give a predictable snapshot
const realDateNow = Date.now;
@ -50,7 +47,7 @@ afterAll(() => {
describe("PollCreateDialog", () => {
const mockClient = getMockClientWithEventEmitter({
sendEvent: jest.fn().mockResolvedValue({ event_id: '1' }),
sendEvent: jest.fn().mockResolvedValue({ event_id: "1" }),
});
beforeEach(() => {
@ -58,48 +55,35 @@ describe("PollCreateDialog", () => {
});
it("renders a blank poll", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
{
wrappingComponent: MatrixClientContext.Provider,
wrappingComponentProps: { value: mockClient },
},
);
const dialog = mount(<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />, {
wrappingComponent: MatrixClientContext.Provider,
wrappingComponentProps: { value: mockClient },
});
expect(dialog.html()).toMatchSnapshot();
});
it("autofocuses the poll topic on mount", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
expect(findById(dialog, 'poll-topic-input').at(0).props().autoFocus).toEqual(true);
const dialog = mount(<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />);
expect(findById(dialog, "poll-topic-input").at(0).props().autoFocus).toEqual(true);
});
it("autofocuses the new poll option field after clicking add option button", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
expect(findById(dialog, 'poll-topic-input').at(0).props().autoFocus).toEqual(true);
const dialog = mount(<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />);
expect(findById(dialog, "poll-topic-input").at(0).props().autoFocus).toEqual(true);
dialog.find("div.mx_PollCreateDialog_addOption").simulate("click");
expect(findById(dialog, 'poll-topic-input').at(0).props().autoFocus).toEqual(false);
expect(findById(dialog, 'pollcreate_option_1').at(0).props().autoFocus).toEqual(false);
expect(findById(dialog, 'pollcreate_option_2').at(0).props().autoFocus).toEqual(true);
expect(findById(dialog, "poll-topic-input").at(0).props().autoFocus).toEqual(false);
expect(findById(dialog, "pollcreate_option_1").at(0).props().autoFocus).toEqual(false);
expect(findById(dialog, "pollcreate_option_2").at(0).props().autoFocus).toEqual(true);
});
it("renders a question and some options", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
const dialog = mount(<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />);
expect(submitIsDisabled(dialog)).toBe(true);
// When I set some values in the boxes
changeValue(
dialog,
"Question or topic",
"How many turnips is the optimal number?",
);
changeValue(dialog, "Question or topic", "How many turnips is the optimal number?");
changeValue(dialog, "Option 1", "As many as my neighbour");
changeValue(dialog, "Option 2", "The question is meaningless");
dialog.find("div.mx_PollCreateDialog_addOption").simulate("click");
@ -109,19 +93,11 @@ describe("PollCreateDialog", () => {
it("renders info from a previous event", () => {
const previousEvent: MatrixEvent = new MatrixEvent(
PollStartEvent.from(
"Poll Q",
["Answer 1", "Answer 2"],
M_POLL_KIND_DISCLOSED,
).serialize(),
PollStartEvent.from("Poll Q", ["Answer 1", "Answer 2"], M_POLL_KIND_DISCLOSED).serialize(),
);
const dialog = mount(
<PollCreateDialog
room={createRoom()}
onFinished={jest.fn()}
editingMxEvent={previousEvent}
/>,
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} editingMxEvent={previousEvent} />,
);
expect(submitIsDisabled(dialog)).toBe(false);
@ -129,17 +105,13 @@ describe("PollCreateDialog", () => {
});
it("doesn't allow submitting until there are options", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
const dialog = mount(<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />);
expect(submitIsDisabled(dialog)).toBe(true);
});
it("does allow submitting when there are options and a question", () => {
// Given a dialog with no info in (which I am unable to submit)
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
const dialog = mount(<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />);
expect(submitIsDisabled(dialog)).toBe(true);
// When I set some values in the boxes
@ -152,74 +124,42 @@ describe("PollCreateDialog", () => {
});
it("shows the open poll description at first", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
expect(
dialog.find('select').prop("value"),
).toEqual(M_POLL_KIND_DISCLOSED.name);
expect(
dialog.find('p').text(),
).toEqual("Voters see results as soon as they have voted");
const dialog = mount(<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />);
expect(dialog.find("select").prop("value")).toEqual(M_POLL_KIND_DISCLOSED.name);
expect(dialog.find("p").text()).toEqual("Voters see results as soon as they have voted");
});
it("shows the closed poll description if we choose it", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
const dialog = mount(<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />);
changeKind(dialog, M_POLL_KIND_UNDISCLOSED.name);
expect(
dialog.find('select').prop("value"),
).toEqual(M_POLL_KIND_UNDISCLOSED.name);
expect(
dialog.find('p').text(),
).toEqual("Results are only revealed when you end the poll");
expect(dialog.find("select").prop("value")).toEqual(M_POLL_KIND_UNDISCLOSED.name);
expect(dialog.find("p").text()).toEqual("Results are only revealed when you end the poll");
});
it("shows the open poll description if we choose it", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
const dialog = mount(<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />);
changeKind(dialog, M_POLL_KIND_UNDISCLOSED.name);
changeKind(dialog, M_POLL_KIND_DISCLOSED.name);
expect(
dialog.find('select').prop("value"),
).toEqual(M_POLL_KIND_DISCLOSED.name);
expect(
dialog.find('p').text(),
).toEqual("Voters see results as soon as they have voted");
expect(dialog.find("select").prop("value")).toEqual(M_POLL_KIND_DISCLOSED.name);
expect(dialog.find("p").text()).toEqual("Voters see results as soon as they have voted");
});
it("shows the closed poll description when editing a closed poll", () => {
const previousEvent: MatrixEvent = new MatrixEvent(
PollStartEvent.from(
"Poll Q",
["Answer 1", "Answer 2"],
M_POLL_KIND_UNDISCLOSED,
).serialize(),
PollStartEvent.from("Poll Q", ["Answer 1", "Answer 2"], M_POLL_KIND_UNDISCLOSED).serialize(),
);
previousEvent.event.event_id = "$prevEventId";
const dialog = mount(
<PollCreateDialog
room={createRoom()}
onFinished={jest.fn()}
editingMxEvent={previousEvent}
/>,
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} editingMxEvent={previousEvent} />,
);
expect(
dialog.find('select').prop("value"),
).toEqual(M_POLL_KIND_UNDISCLOSED.name);
expect(
dialog.find('p').text(),
).toEqual("Results are only revealed when you end the poll");
expect(dialog.find("select").prop("value")).toEqual(M_POLL_KIND_UNDISCLOSED.name);
expect(dialog.find("p").text()).toEqual("Results are only revealed when you end the poll");
});
it("displays a spinner after submitting", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
const dialog = mount(<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />);
changeValue(dialog, "Question or topic", "Q");
changeValue(dialog, "Option 1", "A1");
changeValue(dialog, "Option 2", "A2");
@ -230,9 +170,7 @@ describe("PollCreateDialog", () => {
});
it("sends a poll create event when submitted", () => {
const dialog = mount(
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />,
);
const dialog = mount(<PollCreateDialog room={createRoom()} onFinished={jest.fn()} />);
changeValue(dialog, "Question or topic", "Q");
changeValue(dialog, "Option 1", "A1");
changeValue(dialog, "Option 2", "A2");
@ -240,50 +178,40 @@ describe("PollCreateDialog", () => {
dialog.find("button").simulate("click");
const [, , eventType, sentEventContent] = mockClient.sendEvent.mock.calls[0];
expect(M_POLL_START.matches(eventType)).toBeTruthy();
expect(sentEventContent).toEqual(
{
[M_TEXT.name]: "Q\n1. A1\n2. A2",
[M_POLL_START.name]: {
"answers": [
{
"id": expect.any(String),
[M_TEXT.name]: "A1",
},
{
"id": expect.any(String),
[M_TEXT.name]: "A2",
},
],
"kind": M_POLL_KIND_DISCLOSED.name,
"max_selections": 1,
"question": {
"body": "Q",
"format": undefined,
"formatted_body": undefined,
"msgtype": "m.text",
[M_TEXT.name]: "Q",
expect(sentEventContent).toEqual({
[M_TEXT.name]: "Q\n1. A1\n2. A2",
[M_POLL_START.name]: {
answers: [
{
id: expect.any(String),
[M_TEXT.name]: "A1",
},
{
id: expect.any(String),
[M_TEXT.name]: "A2",
},
],
kind: M_POLL_KIND_DISCLOSED.name,
max_selections: 1,
question: {
body: "Q",
format: undefined,
formatted_body: undefined,
msgtype: "m.text",
[M_TEXT.name]: "Q",
},
},
);
});
});
it("sends a poll edit event when editing", () => {
const previousEvent: MatrixEvent = new MatrixEvent(
PollStartEvent.from(
"Poll Q",
["Answer 1", "Answer 2"],
M_POLL_KIND_DISCLOSED,
).serialize(),
PollStartEvent.from("Poll Q", ["Answer 1", "Answer 2"], M_POLL_KIND_DISCLOSED).serialize(),
);
previousEvent.event.event_id = "$prevEventId";
const dialog = mount(
<PollCreateDialog
room={createRoom()}
onFinished={jest.fn()}
editingMxEvent={previousEvent}
/>,
<PollCreateDialog room={createRoom()} onFinished={jest.fn()} editingMxEvent={previousEvent} />,
);
changeValue(dialog, "Question or topic", "Poll Q updated");
@ -293,65 +221,51 @@ describe("PollCreateDialog", () => {
const [, , eventType, sentEventContent] = mockClient.sendEvent.mock.calls[0];
expect(M_POLL_START.matches(eventType)).toBeTruthy();
expect(sentEventContent).toEqual(
{
"m.new_content": {
[M_TEXT.name]: "Poll Q updated\n1. Answer 1\n2. Answer 2 updated",
[M_POLL_START.name]: {
"answers": [
{
"id": expect.any(String),
[M_TEXT.name]: "Answer 1",
},
{
"id": expect.any(String),
[M_TEXT.name]: "Answer 2 updated",
},
],
"kind": M_POLL_KIND_UNDISCLOSED.name,
"max_selections": 1,
"question": {
"body": "Poll Q updated",
"format": undefined,
"formatted_body": undefined,
"msgtype": "m.text",
[M_TEXT.name]: "Poll Q updated",
expect(sentEventContent).toEqual({
"m.new_content": {
[M_TEXT.name]: "Poll Q updated\n1. Answer 1\n2. Answer 2 updated",
[M_POLL_START.name]: {
answers: [
{
id: expect.any(String),
[M_TEXT.name]: "Answer 1",
},
{
id: expect.any(String),
[M_TEXT.name]: "Answer 2 updated",
},
],
kind: M_POLL_KIND_UNDISCLOSED.name,
max_selections: 1,
question: {
body: "Poll Q updated",
format: undefined,
formatted_body: undefined,
msgtype: "m.text",
[M_TEXT.name]: "Poll Q updated",
},
},
"m.relates_to": {
"event_id": previousEvent.getId(),
"rel_type": "m.replace",
},
},
);
"m.relates_to": {
event_id: previousEvent.getId(),
rel_type: "m.replace",
},
});
});
});
function createRoom(): Room {
return new Room(
"roomid",
MatrixClientPeg.get(),
"@name:example.com",
{},
);
return new Room("roomid", MatrixClientPeg.get(), "@name:example.com", {});
}
function changeValue(wrapper: ReactWrapper, labelText: string, value: string) {
wrapper.find(`input[label="${labelText}"]`).simulate(
"change",
{ target: { value: value } },
);
wrapper.find(`input[label="${labelText}"]`).simulate("change", { target: { value: value } });
}
function changeKind(wrapper: ReactWrapper, value: string) {
wrapper.find("select").simulate(
"change",
{ target: { value: value } },
);
wrapper.find("select").simulate("change", { target: { value: value } });
}
function submitIsDisabled(wrapper: ReactWrapper) {
return wrapper.find('button[type="submit"]').prop("aria-disabled") === true;
}

View file

@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { fireEvent, render, screen } from "@testing-library/react";
import PowerSelector from "../../../../src/components/views/elements/PowerSelector";
describe('<PowerSelector />', () => {
describe("<PowerSelector />", () => {
it("should reset back to custom value when custom input is blurred blank", async () => {
const fn = jest.fn();
render(<PowerSelector value={25} maxValue={100} usersDefault={0} onChange={fn} />);

View file

@ -28,7 +28,9 @@ describe("<ProgressBar/>", () => {
expect(progress.value).toBe(0);
// Await the animation to conclude to our initial value of 50
act(() => { jest.runAllTimers(); });
act(() => {
jest.runAllTimers();
});
expect(progress.position).toBe(0.5);
// Move the needle to 80%
@ -36,7 +38,9 @@ describe("<ProgressBar/>", () => {
expect(progress.position).toBe(0.5);
// Let the animaiton run a tiny bit, assert it has moved from where it was to where it needs to go
act(() => { jest.advanceTimersByTime(150); });
act(() => {
jest.advanceTimersByTime(150);
});
expect(progress.position).toBeGreaterThan(0.5);
expect(progress.position).toBeLessThan(0.8);
});

View file

@ -24,13 +24,13 @@ describe("<QRCode />", () => {
it("renders a QR with defaults", async () => {
const { container, getAllByAltText } = render(<QRCode data="asd" />);
await waitFor(() => getAllByAltText('QR Code').length === 1);
await waitFor(() => getAllByAltText("QR Code").length === 1);
expect(container).toMatchSnapshot();
});
it("renders a QR with high error correction level", async () => {
const { container, getAllByAltText } = render(<QRCode data="asd" errorCorrectionLevel="high" />);
await waitFor(() => getAllByAltText('QR Code').length === 1);
await waitFor(() => getAllByAltText("QR Code").length === 1);
expect(container).toMatchSnapshot();
});
});

View file

@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import * as testUtils from '../../../test-utils';
import * as testUtils from "../../../test-utils";
import { getParentEventId } from "../../../../src/utils/Reply";
describe("ReplyChain", () => {
describe('getParentEventId', () => {
it('retrieves relation reply from unedited event', () => {
describe("getParentEventId", () => {
it("retrieves relation reply from unedited event", () => {
const originalEventWithRelation = testUtils.mkEvent({
event: true,
type: "m.room.message",
@ -28,7 +28,7 @@ describe("ReplyChain", () => {
"body": "> Reply to this message\n\n foo",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og",
event_id: "$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og",
},
},
},
@ -36,11 +36,12 @@ describe("ReplyChain", () => {
room: "room_id",
});
expect(getParentEventId(originalEventWithRelation))
.toStrictEqual('$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og');
expect(getParentEventId(originalEventWithRelation)).toStrictEqual(
"$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og",
);
});
it('retrieves relation reply from original event when edited', () => {
it("retrieves relation reply from original event when edited", () => {
const originalEventWithRelation = testUtils.mkEvent({
event: true,
type: "m.room.message",
@ -49,7 +50,7 @@ describe("ReplyChain", () => {
"body": "> Reply to this message\n\n foo",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og",
event_id: "$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og",
},
},
},
@ -64,12 +65,12 @@ describe("ReplyChain", () => {
"msgtype": "m.text",
"body": "> Reply to this message\n\n * foo bar",
"m.new_content": {
"msgtype": "m.text",
"body": "foo bar",
msgtype: "m.text",
body: "foo bar",
},
"m.relates_to": {
"rel_type": "m.replace",
"event_id": originalEventWithRelation.getId(),
rel_type: "m.replace",
event_id: originalEventWithRelation.getId(),
},
},
user: "some_other_user",
@ -80,11 +81,12 @@ describe("ReplyChain", () => {
originalEventWithRelation.makeReplaced(editEvent);
// The relation should be pulled from the original event
expect(getParentEventId(originalEventWithRelation))
.toStrictEqual('$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og');
expect(getParentEventId(originalEventWithRelation)).toStrictEqual(
"$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og",
);
});
it('retrieves relation reply from edit event when provided', () => {
it("retrieves relation reply from edit event when provided", () => {
const originalEvent = testUtils.mkEvent({
event: true,
type: "m.room.message",
@ -107,13 +109,13 @@ describe("ReplyChain", () => {
"body": "foo bar",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og",
event_id: "$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og",
},
},
},
"m.relates_to": {
"rel_type": "m.replace",
"event_id": originalEvent.getId(),
rel_type: "m.replace",
event_id: originalEvent.getId(),
},
},
user: "some_other_user",
@ -124,11 +126,10 @@ describe("ReplyChain", () => {
originalEvent.makeReplaced(editEvent);
// The relation should be pulled from the edit event
expect(getParentEventId(originalEvent))
.toStrictEqual('$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og');
expect(getParentEventId(originalEvent)).toStrictEqual("$qkjmFBTEc0VvfVyzq1CJuh1QZi_xDIgNEFjZ4Pq34og");
});
it('prefers relation reply from edit event over original event', () => {
it("prefers relation reply from edit event over original event", () => {
const originalEventWithRelation = testUtils.mkEvent({
event: true,
type: "m.room.message",
@ -137,7 +138,7 @@ describe("ReplyChain", () => {
"body": "> Reply to this message\n\n foo",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$111",
event_id: "$111",
},
},
},
@ -156,13 +157,13 @@ describe("ReplyChain", () => {
"body": "foo bar",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$999",
event_id: "$999",
},
},
},
"m.relates_to": {
"rel_type": "m.replace",
"event_id": originalEventWithRelation.getId(),
rel_type: "m.replace",
event_id: originalEventWithRelation.getId(),
},
},
user: "some_other_user",
@ -173,10 +174,10 @@ describe("ReplyChain", () => {
originalEventWithRelation.makeReplaced(editEvent);
// The relation should be pulled from the edit event
expect(getParentEventId(originalEventWithRelation)).toStrictEqual('$999');
expect(getParentEventId(originalEventWithRelation)).toStrictEqual("$999");
});
it('able to clear relation reply from original event by providing empty relation field', () => {
it("able to clear relation reply from original event by providing empty relation field", () => {
const originalEventWithRelation = testUtils.mkEvent({
event: true,
type: "m.room.message",
@ -185,7 +186,7 @@ describe("ReplyChain", () => {
"body": "> Reply to this message\n\n foo",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$111",
event_id: "$111",
},
},
},
@ -206,8 +207,8 @@ describe("ReplyChain", () => {
"m.relates_to": {},
},
"m.relates_to": {
"rel_type": "m.replace",
"event_id": originalEventWithRelation.getId(),
rel_type: "m.replace",
event_id: originalEventWithRelation.getId(),
},
},
user: "some_other_user",

View file

@ -14,47 +14,47 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { fireEvent, render } from "@testing-library/react";
import StyledRadioGroup from "../../../../src/components/views/elements/StyledRadioGroup";
describe('<StyledRadioGroup />', () => {
describe("<StyledRadioGroup />", () => {
const optionA = {
value: 'Anteater',
value: "Anteater",
label: <span>Anteater label</span>,
description: 'anteater description',
className: 'a-class',
description: "anteater description",
className: "a-class",
};
const optionB = {
value: 'Badger',
value: "Badger",
label: <span>Badger label</span>,
};
const optionC = {
value: 'Canary',
value: "Canary",
label: <span>Canary label</span>,
description: <span>Canary description</span>,
};
const defaultDefinitions = [optionA, optionB, optionC];
const defaultProps = {
name: 'test',
className: 'test-class',
name: "test",
className: "test-class",
definitions: defaultDefinitions,
onChange: jest.fn(),
};
const getComponent = (props = {}) => render(<StyledRadioGroup {...defaultProps} {...props} />);
const getInputByValue = (component, value) => component.container.querySelector(`input[value="${value}"]`);
const getCheckedInput = component => component.container.querySelector('input[checked]');
const getCheckedInput = (component) => component.container.querySelector("input[checked]");
it('renders radios correctly when no value is provided', () => {
it("renders radios correctly when no value is provided", () => {
const component = getComponent();
expect(component.asFragment()).toMatchSnapshot();
expect(getCheckedInput(component)).toBeFalsy();
});
it('selects correct button when value is provided', () => {
it("selects correct button when value is provided", () => {
const component = getComponent({
value: optionC.value,
});
@ -62,14 +62,11 @@ describe('<StyledRadioGroup />', () => {
expect(getCheckedInput(component).value).toEqual(optionC.value);
});
it('selects correct buttons when definitions have checked prop', () => {
const definitions = [
{ ...optionA, checked: true },
optionB,
{ ...optionC, checked: false },
];
it("selects correct buttons when definitions have checked prop", () => {
const definitions = [{ ...optionA, checked: true }, optionB, { ...optionC, checked: false }];
const component = getComponent({
value: optionC.value, definitions,
value: optionC.value,
definitions,
});
expect(getInputByValue(component, optionA.value)).toBeChecked();
@ -78,26 +75,22 @@ describe('<StyledRadioGroup />', () => {
expect(getInputByValue(component, optionC.value)).not.toBeChecked();
});
it('disables individual buttons based on definition.disabled', () => {
const definitions = [
optionA,
{ ...optionB, disabled: true },
{ ...optionC, disabled: true },
];
it("disables individual buttons based on definition.disabled", () => {
const definitions = [optionA, { ...optionB, disabled: true }, { ...optionC, disabled: true }];
const component = getComponent({ definitions });
expect(getInputByValue(component, optionA.value)).not.toBeDisabled();
expect(getInputByValue(component, optionB.value)).toBeDisabled();
expect(getInputByValue(component, optionC.value)).toBeDisabled();
});
it('disables all buttons with disabled prop', () => {
it("disables all buttons with disabled prop", () => {
const component = getComponent({ disabled: true });
expect(getInputByValue(component, optionA.value)).toBeDisabled();
expect(getInputByValue(component, optionB.value)).toBeDisabled();
expect(getInputByValue(component, optionC.value)).toBeDisabled();
});
it('calls onChange on click', () => {
it("calls onChange on click", () => {
const onChange = jest.fn();
const component = getComponent({
value: optionC.value,

View file

@ -15,29 +15,26 @@ limitations under the License.
*/
import React from "react";
import {
renderIntoDocument,
Simulate,
} from 'react-dom/test-utils';
import { renderIntoDocument, Simulate } from "react-dom/test-utils";
import { act } from "react-dom/test-utils";
import { Alignment } from '../../../../src/components/views/elements/Tooltip';
import { Alignment } from "../../../../src/components/views/elements/Tooltip";
import TooltipTarget from "../../../../src/components/views/elements/TooltipTarget";
describe('<TooltipTarget />', () => {
describe("<TooltipTarget />", () => {
const defaultProps = {
"tooltipTargetClassName": 'test tooltipTargetClassName',
"className": 'test className',
"tooltipClassName": 'test tooltipClassName',
"label": 'test label',
"tooltipTargetClassName": "test tooltipTargetClassName",
"className": "test className",
"tooltipClassName": "test tooltipClassName",
"label": "test label",
"alignment": Alignment.Left,
"id": 'test id',
'data-test-id': 'test',
"id": "test id",
"data-test-id": "test",
};
afterEach(() => {
// clean up renderer tooltips
const wrapper = document.querySelector('.mx_Tooltip_wrapper');
const wrapper = document.querySelector(".mx_Tooltip_wrapper");
while (wrapper?.firstChild) {
wrapper.removeChild(wrapper.lastChild);
}
@ -45,19 +42,19 @@ describe('<TooltipTarget />', () => {
const getComponent = (props = {}) => {
const wrapper = renderIntoDocument<HTMLSpanElement>(
// wrap in element so renderIntoDocument can render functional component
// wrap in element so renderIntoDocument can render functional component
<span>
<TooltipTarget {...defaultProps} {...props}>
<span>child</span>
</TooltipTarget>
</span>,
) as HTMLSpanElement;
return wrapper.querySelector('[data-test-id=test]');
return wrapper.querySelector("[data-test-id=test]");
};
const getVisibleTooltip = () => document.querySelector('.mx_Tooltip.mx_Tooltip_visible');
const getVisibleTooltip = () => document.querySelector(".mx_Tooltip.mx_Tooltip_visible");
it('renders container', () => {
it("renders container", () => {
const component = getComponent();
expect(component).toMatchSnapshot();
expect(getVisibleTooltip()).toBeFalsy();
@ -72,7 +69,7 @@ describe('<TooltipTarget />', () => {
expect(getVisibleTooltip()).toMatchSnapshot();
});
it('hides tooltip on mouseleave', () => {
it("hides tooltip on mouseleave", () => {
const wrapper = getComponent();
act(() => {
Simulate.mouseOver(wrapper);
@ -84,7 +81,7 @@ describe('<TooltipTarget />', () => {
expect(getVisibleTooltip()).toBeFalsy();
});
it('displays tooltip on focus', () => {
it("displays tooltip on focus", () => {
const wrapper = getComponent();
act(() => {
Simulate.focus(wrapper);
@ -92,7 +89,7 @@ describe('<TooltipTarget />', () => {
expect(getVisibleTooltip()).toBeTruthy();
});
it('hides tooltip on blur', async () => {
it("hides tooltip on blur", async () => {
const wrapper = getComponent();
act(() => {
Simulate.focus(wrapper);

View file

@ -14,51 +14,52 @@ 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 { mount } from "enzyme";
import { act } from "react-dom/test-utils";
import LiveDurationDropdown, { DEFAULT_DURATION_MS }
from '../../../../src/components/views/location/LiveDurationDropdown';
import { findById, mockPlatformPeg } from '../../../test-utils';
import LiveDurationDropdown, {
DEFAULT_DURATION_MS,
} from "../../../../src/components/views/location/LiveDurationDropdown";
import { findById, mockPlatformPeg } from "../../../test-utils";
mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });
describe('<LiveDurationDropdown />', () => {
describe("<LiveDurationDropdown />", () => {
const defaultProps = {
timeout: DEFAULT_DURATION_MS,
onChange: jest.fn(),
};
const getComponent = (props = {}) =>
mount(<LiveDurationDropdown {...defaultProps} {...props} />);
const getComponent = (props = {}) => mount(<LiveDurationDropdown {...defaultProps} {...props} />);
const getOption = (wrapper, timeout) => findById(wrapper, `live-duration__${timeout}`).at(0);
const getSelectedOption = (wrapper) => findById(wrapper, 'live-duration_value');
const openDropdown = (wrapper) => act(() => {
wrapper.find('[role="button"]').at(0).simulate('click');
wrapper.setProps({});
});
const getSelectedOption = (wrapper) => findById(wrapper, "live-duration_value");
const openDropdown = (wrapper) =>
act(() => {
wrapper.find('[role="button"]').at(0).simulate("click");
wrapper.setProps({});
});
it('renders timeout as selected option', () => {
it("renders timeout as selected option", () => {
const wrapper = getComponent();
expect(getSelectedOption(wrapper).text()).toEqual('Share for 15m');
expect(getSelectedOption(wrapper).text()).toEqual("Share for 15m");
});
it('renders non-default timeout as selected option', () => {
it("renders non-default timeout as selected option", () => {
const timeout = 1234567;
const wrapper = getComponent({ timeout });
expect(getSelectedOption(wrapper).text()).toEqual(`Share for 21m`);
});
it('renders a dropdown option for a non-default timeout value', () => {
it("renders a dropdown option for a non-default timeout value", () => {
const timeout = 1234567;
const wrapper = getComponent({ timeout });
openDropdown(wrapper);
expect(getOption(wrapper, timeout).text()).toEqual(`Share for 21m`);
});
it('updates value on option selection', () => {
it("updates value on option selection", () => {
const onChange = jest.fn();
const wrapper = getComponent({ onChange });
@ -67,7 +68,7 @@ describe('<LiveDurationDropdown />', () => {
openDropdown(wrapper);
act(() => {
getOption(wrapper, ONE_HOUR).simulate('click');
getOption(wrapper, ONE_HOUR).simulate("click");
});
expect(onChange).toHaveBeenCalledWith(ONE_HOUR);

View file

@ -14,34 +14,34 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import maplibregl from "maplibre-gl";
// eslint-disable-next-line deprecate/import
import { mount } from "enzyme";
import { act } from 'react-dom/test-utils';
import { act } from "react-dom/test-utils";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { MatrixClient } from 'matrix-js-sdk/src/client';
import { mocked } from 'jest-mock';
import { logger } from 'matrix-js-sdk/src/logger';
import { MatrixClient } from "matrix-js-sdk/src/client";
import { mocked } from "jest-mock";
import { logger } from "matrix-js-sdk/src/logger";
import LocationPicker from "../../../../src/components/views/location/LocationPicker";
import { LocationShareType } from "../../../../src/components/views/location/shareLocation";
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
import { findById, findByTestId, mockPlatformPeg } from '../../../test-utils';
import { findMapStyleUrl, LocationShareError } from '../../../../src/utils/location';
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import { findById, findByTestId, mockPlatformPeg } from "../../../test-utils";
import { findMapStyleUrl, LocationShareError } from "../../../../src/utils/location";
jest.mock('../../../../src/utils/location/findMapStyleUrl', () => ({
findMapStyleUrl: jest.fn().mockReturnValue('tileserver.com'),
jest.mock("../../../../src/utils/location/findMapStyleUrl", () => ({
findMapStyleUrl: jest.fn().mockReturnValue("tileserver.com"),
}));
// dropdown uses this
mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });
describe("LocationPicker", () => {
describe('<LocationPicker />', () => {
const roomId = '!room:server.org';
const userId = '@user:server.org';
describe("<LocationPicker />", () => {
const roomId = "!room:server.org";
const userId = "@user:server.org";
const sender = new RoomMember(roomId, userId);
const defaultProps = {
sender,
@ -56,10 +56,11 @@ describe("LocationPicker", () => {
isGuest: jest.fn(),
getClientWellKnown: jest.fn(),
};
const getComponent = (props = {}) => mount(<LocationPicker {...defaultProps} {...props} />, {
wrappingComponent: MatrixClientContext.Provider,
wrappingComponentProps: { value: mockClient },
});
const getComponent = (props = {}) =>
mount(<LocationPicker {...defaultProps} {...props} />, {
wrappingComponent: MatrixClientContext.Provider,
wrappingComponentProps: { value: mockClient },
});
const mockMap = new maplibregl.Map();
const mockGeolocate = new maplibregl.GeolocateControl();
@ -82,33 +83,33 @@ describe("LocationPicker", () => {
};
beforeEach(() => {
jest.spyOn(logger, 'error').mockRestore();
jest.spyOn(MatrixClientPeg, 'get').mockReturnValue(mockClient as unknown as MatrixClient);
jest.spyOn(logger, "error").mockRestore();
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient as unknown as MatrixClient);
jest.clearAllMocks();
mocked(mockMap).addControl.mockReset();
mocked(findMapStyleUrl).mockReturnValue('tileserver.com');
mocked(findMapStyleUrl).mockReturnValue("tileserver.com");
});
it('displays error when map emits an error', () => {
it("displays error when map emits an error", () => {
// suppress expected error log
jest.spyOn(logger, 'error').mockImplementation(() => { });
jest.spyOn(logger, "error").mockImplementation(() => {});
const wrapper = getComponent();
act(() => {
// @ts-ignore
mocked(mockMap).emit('error', { error: 'Something went wrong' });
mocked(mockMap).emit("error", { error: "Something went wrong" });
wrapper.setProps({});
});
expect(findByTestId(wrapper, 'map-rendering-error').find('p').text()).toEqual(
"This homeserver is not configured correctly to display maps, "
+ "or the configured map server may be unreachable.",
expect(findByTestId(wrapper, "map-rendering-error").find("p").text()).toEqual(
"This homeserver is not configured correctly to display maps, " +
"or the configured map server may be unreachable.",
);
});
it('displays error when map display is not configured properly', () => {
it("displays error when map display is not configured properly", () => {
// suppress expected error log
jest.spyOn(logger, 'error').mockImplementation(() => { });
jest.spyOn(logger, "error").mockImplementation(() => {});
mocked(findMapStyleUrl).mockImplementation(() => {
throw new Error(LocationShareError.MapStyleUrlNotConfigured);
});
@ -116,111 +117,111 @@ describe("LocationPicker", () => {
const wrapper = getComponent();
wrapper.setProps({});
expect(findByTestId(wrapper, 'map-rendering-error').find('p').text()).toEqual(
expect(findByTestId(wrapper, "map-rendering-error").find("p").text()).toEqual(
"This homeserver is not configured to display maps.",
);
});
it('displays error when map setup throws', () => {
it("displays error when map setup throws", () => {
// suppress expected error log
jest.spyOn(logger, 'error').mockImplementation(() => { });
jest.spyOn(logger, "error").mockImplementation(() => {});
// throw an error
mocked(mockMap).addControl.mockImplementation(() => { throw new Error('oups'); });
mocked(mockMap).addControl.mockImplementation(() => {
throw new Error("oups");
});
const wrapper = getComponent();
wrapper.setProps({});
expect(findByTestId(wrapper, 'map-rendering-error').find('p').text()).toEqual(
"This homeserver is not configured correctly to display maps, "
+ "or the configured map server may be unreachable.",
expect(findByTestId(wrapper, "map-rendering-error").find("p").text()).toEqual(
"This homeserver is not configured correctly to display maps, " +
"or the configured map server may be unreachable.",
);
});
it('initiates map with geolocation', () => {
it("initiates map with geolocation", () => {
getComponent();
expect(mockMap.addControl).toHaveBeenCalledWith(mockGeolocate);
act(() => {
// @ts-ignore
mocked(mockMap).emit('load');
mocked(mockMap).emit("load");
});
expect(mockGeolocate.trigger).toHaveBeenCalled();
});
const testUserLocationShareTypes = (shareType: LocationShareType.Own | LocationShareType.Live) => {
describe('user location behaviours', () => {
it('closes and displays error when geolocation errors', () => {
describe("user location behaviours", () => {
it("closes and displays error when geolocation errors", () => {
// suppress expected error log
jest.spyOn(logger, 'error').mockImplementation(() => { });
jest.spyOn(logger, "error").mockImplementation(() => {});
const onFinished = jest.fn();
getComponent({ onFinished, shareType });
expect(mockMap.addControl).toHaveBeenCalledWith(mockGeolocate);
act(() => {
// @ts-ignore
mockMap.emit('load');
mockMap.emit("load");
// @ts-ignore
mockGeolocate.emit('error', {});
mockGeolocate.emit("error", {});
});
// dialog is closed on error
expect(onFinished).toHaveBeenCalled();
});
it('sets position on geolocate event', () => {
it("sets position on geolocate event", () => {
const wrapper = getComponent({ shareType });
act(() => {
// @ts-ignore
mocked(mockGeolocate).emit('geolocate', mockGeolocationPosition);
mocked(mockGeolocate).emit("geolocate", mockGeolocationPosition);
wrapper.setProps({});
});
// marker added
expect(maplibregl.Marker).toHaveBeenCalled();
expect(mockMarker.setLngLat).toHaveBeenCalledWith(new maplibregl.LngLat(
12.4, 43.2,
));
expect(mockMarker.setLngLat).toHaveBeenCalledWith(new maplibregl.LngLat(12.4, 43.2));
// submit button is enabled when position is truthy
expect(findByTestId(wrapper, 'location-picker-submit-button').at(0).props().disabled).toBeFalsy();
expect(wrapper.find('MemberAvatar').length).toBeTruthy();
expect(findByTestId(wrapper, "location-picker-submit-button").at(0).props().disabled).toBeFalsy();
expect(wrapper.find("MemberAvatar").length).toBeTruthy();
});
it('disables submit button until geolocation completes', () => {
it("disables submit button until geolocation completes", () => {
const onChoose = jest.fn();
const wrapper = getComponent({ shareType, onChoose });
// submit button is enabled when position is truthy
expect(findByTestId(wrapper, 'location-picker-submit-button').at(0).props().disabled).toBeTruthy();
expect(findByTestId(wrapper, "location-picker-submit-button").at(0).props().disabled).toBeTruthy();
act(() => {
findByTestId(wrapper, 'location-picker-submit-button').at(0).simulate('click');
findByTestId(wrapper, "location-picker-submit-button").at(0).simulate("click");
});
// nothing happens on button click
expect(onChoose).not.toHaveBeenCalled();
act(() => {
// @ts-ignore
mocked(mockGeolocate).emit('geolocate', mockGeolocationPosition);
mocked(mockGeolocate).emit("geolocate", mockGeolocationPosition);
wrapper.setProps({});
});
// submit button is enabled when position is truthy
expect(findByTestId(wrapper, 'location-picker-submit-button').at(0).props().disabled).toBeFalsy();
expect(findByTestId(wrapper, "location-picker-submit-button").at(0).props().disabled).toBeFalsy();
});
it('submits location', () => {
it("submits location", () => {
const onChoose = jest.fn();
const wrapper = getComponent({ onChoose, shareType });
act(() => {
// @ts-ignore
mocked(mockGeolocate).emit('geolocate', mockGeolocationPosition);
mocked(mockGeolocate).emit("geolocate", mockGeolocationPosition);
// make sure button is enabled
wrapper.setProps({});
});
act(() => {
findByTestId(wrapper, 'location-picker-submit-button').at(0).simulate('click');
findByTestId(wrapper, "location-picker-submit-button").at(0).simulate("click");
});
// content of this call is tested in LocationShareMenu-test
@ -229,67 +230,68 @@ describe("LocationPicker", () => {
});
};
describe('for Own location share type', () => {
describe("for Own location share type", () => {
testUserLocationShareTypes(LocationShareType.Own);
});
describe('for Live location share type', () => {
describe("for Live location share type", () => {
const shareType = LocationShareType.Live;
testUserLocationShareTypes(shareType);
const getOption = (wrapper, timeout) => findById(wrapper, `live-duration__${timeout}`).at(0);
const getDropdown = wrapper => findByTestId(wrapper, 'live-duration-dropdown');
const getSelectedOption = (wrapper) => findById(wrapper, 'live-duration_value');
const getDropdown = (wrapper) => findByTestId(wrapper, "live-duration-dropdown");
const getSelectedOption = (wrapper) => findById(wrapper, "live-duration_value");
const openDropdown = (wrapper) => act(() => {
const dropdown = getDropdown(wrapper);
dropdown.find('[role="button"]').at(0).simulate('click');
wrapper.setProps({});
});
const openDropdown = (wrapper) =>
act(() => {
const dropdown = getDropdown(wrapper);
dropdown.find('[role="button"]').at(0).simulate("click");
wrapper.setProps({});
});
it('renders live duration dropdown with default option', () => {
it("renders live duration dropdown with default option", () => {
const wrapper = getComponent({ shareType });
expect(getSelectedOption(getDropdown(wrapper)).text()).toEqual('Share for 15m');
expect(getSelectedOption(getDropdown(wrapper)).text()).toEqual("Share for 15m");
});
it('updates selected duration', () => {
it("updates selected duration", () => {
const wrapper = getComponent({ shareType });
openDropdown(wrapper);
const dropdown = getDropdown(wrapper);
act(() => {
getOption(dropdown, 3600000).simulate('click');
getOption(dropdown, 3600000).simulate("click");
});
// value updated
expect(getSelectedOption(getDropdown(wrapper)).text()).toEqual('Share for 1h');
expect(getSelectedOption(getDropdown(wrapper)).text()).toEqual("Share for 1h");
});
});
describe('for Pin drop location share type', () => {
describe("for Pin drop location share type", () => {
const shareType = LocationShareType.Pin;
it('initiates map with geolocation', () => {
it("initiates map with geolocation", () => {
getComponent({ shareType });
expect(mockMap.addControl).toHaveBeenCalledWith(mockGeolocate);
act(() => {
// @ts-ignore
mocked(mockMap).emit('load');
mocked(mockMap).emit("load");
});
expect(mockGeolocate.trigger).toHaveBeenCalled();
});
it('removes geolocation control on geolocation error', () => {
it("removes geolocation control on geolocation error", () => {
// suppress expected error log
jest.spyOn(logger, 'error').mockImplementation(() => { });
jest.spyOn(logger, "error").mockImplementation(() => {});
const onFinished = jest.fn();
getComponent({ onFinished, shareType });
act(() => {
// @ts-ignore
mockMap.emit('load');
mockMap.emit("load");
// @ts-ignore
mockGeolocate.emit('error', {});
mockGeolocate.emit("error", {});
});
expect(mockMap.removeControl).toHaveBeenCalledWith(mockGeolocate);
@ -297,47 +299,45 @@ describe("LocationPicker", () => {
expect(onFinished).not.toHaveBeenCalled();
});
it('does not set position on geolocate event', () => {
it("does not set position on geolocate event", () => {
mocked(maplibregl.Marker).mockClear();
const wrapper = getComponent({ shareType });
act(() => {
// @ts-ignore
mocked(mockGeolocate).emit('geolocate', mockGeolocationPosition);
mocked(mockGeolocate).emit("geolocate", mockGeolocationPosition);
});
// marker not added
expect(wrapper.find('Marker').length).toBeFalsy();
expect(wrapper.find("Marker").length).toBeFalsy();
});
it('sets position on click event', () => {
it("sets position on click event", () => {
const wrapper = getComponent({ shareType });
act(() => {
// @ts-ignore
mocked(mockMap).emit('click', mockClickEvent);
mocked(mockMap).emit("click", mockClickEvent);
wrapper.setProps({});
});
// marker added
expect(maplibregl.Marker).toHaveBeenCalled();
expect(mockMarker.setLngLat).toHaveBeenCalledWith(new maplibregl.LngLat(
12.4, 43.2,
));
expect(mockMarker.setLngLat).toHaveBeenCalledWith(new maplibregl.LngLat(12.4, 43.2));
// marker is set, icon not avatar
expect(wrapper.find('.mx_Marker_icon').length).toBeTruthy();
expect(wrapper.find(".mx_Marker_icon").length).toBeTruthy();
});
it('submits location', () => {
it("submits location", () => {
const onChoose = jest.fn();
const wrapper = getComponent({ onChoose, shareType });
act(() => {
// @ts-ignore
mocked(mockMap).emit('click', mockClickEvent);
mocked(mockMap).emit("click", mockClickEvent);
wrapper.setProps({});
});
act(() => {
findByTestId(wrapper, 'location-picker-submit-button').at(0).simulate('click');
findByTestId(wrapper, "location-picker-submit-button").at(0).simulate("click");
});
// content of this call is tested in LocationShareMenu-test

View file

@ -14,43 +14,43 @@ 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, ReactWrapper } from 'enzyme';
import { mocked } from 'jest-mock';
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
import { MatrixClient } from 'matrix-js-sdk/src/client';
import { RelationType } from 'matrix-js-sdk/src/matrix';
import { logger } from 'matrix-js-sdk/src/logger';
import { M_ASSET, LocationAssetType } from 'matrix-js-sdk/src/@types/location';
import { act } from 'react-dom/test-utils';
import { mount, ReactWrapper } from "enzyme";
import { mocked } from "jest-mock";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { RelationType } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { M_ASSET, LocationAssetType } from "matrix-js-sdk/src/@types/location";
import { act } from "react-dom/test-utils";
import LocationShareMenu from '../../../../src/components/views/location/LocationShareMenu';
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
import { ChevronFace } from '../../../../src/components/structures/ContextMenu';
import SettingsStore from '../../../../src/settings/SettingsStore';
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
import { LocationShareType } from '../../../../src/components/views/location/shareLocation';
import LocationShareMenu from "../../../../src/components/views/location/LocationShareMenu";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import { ChevronFace } from "../../../../src/components/structures/ContextMenu";
import SettingsStore from "../../../../src/settings/SettingsStore";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import { LocationShareType } from "../../../../src/components/views/location/shareLocation";
import {
findByTagAndTestId,
findByTestId,
flushPromisesWithFakeTimers,
getMockClientWithEventEmitter,
setupAsyncStoreWithClient,
} from '../../../test-utils';
import Modal from '../../../../src/Modal';
import { DEFAULT_DURATION_MS } from '../../../../src/components/views/location/LiveDurationDropdown';
import { OwnBeaconStore } from '../../../../src/stores/OwnBeaconStore';
import { SettingLevel } from '../../../../src/settings/SettingLevel';
import QuestionDialog from '../../../../src/components/views/dialogs/QuestionDialog';
} from "../../../test-utils";
import Modal from "../../../../src/Modal";
import { DEFAULT_DURATION_MS } from "../../../../src/components/views/location/LiveDurationDropdown";
import { OwnBeaconStore } from "../../../../src/stores/OwnBeaconStore";
import { SettingLevel } from "../../../../src/settings/SettingLevel";
import QuestionDialog from "../../../../src/components/views/dialogs/QuestionDialog";
jest.useFakeTimers();
jest.mock('../../../../src/utils/location/findMapStyleUrl', () => ({
findMapStyleUrl: jest.fn().mockReturnValue('test'),
jest.mock("../../../../src/utils/location/findMapStyleUrl", () => ({
findMapStyleUrl: jest.fn().mockReturnValue("test"),
}));
jest.mock('../../../../src/settings/SettingsStore', () => ({
jest.mock("../../../../src/settings/SettingsStore", () => ({
getValue: jest.fn(),
setValue: jest.fn(),
monitorSetting: jest.fn(),
@ -58,44 +58,45 @@ jest.mock('../../../../src/settings/SettingsStore', () => ({
unwatchSetting: jest.fn(),
}));
jest.mock('../../../../src/stores/OwnProfileStore', () => ({
jest.mock("../../../../src/stores/OwnProfileStore", () => ({
OwnProfileStore: {
instance: {
displayName: 'Ernie',
getHttpAvatarUrl: jest.fn().mockReturnValue('image.com/img'),
displayName: "Ernie",
getHttpAvatarUrl: jest.fn().mockReturnValue("image.com/img"),
},
},
}));
jest.mock('../../../../src/Modal', () => ({
jest.mock("../../../../src/Modal", () => ({
createDialog: jest.fn(),
on: jest.fn(),
off: jest.fn(),
ModalManagerEvent: { Opened: "opened" },
}));
describe('<LocationShareMenu />', () => {
const userId = '@ernie:server.org';
describe("<LocationShareMenu />", () => {
const userId = "@ernie:server.org";
const mockClient = getMockClientWithEventEmitter({
getUserId: jest.fn().mockReturnValue(userId),
getClientWellKnown: jest.fn().mockResolvedValue({
map_style_url: 'maps.com',
map_style_url: "maps.com",
}),
sendMessage: jest.fn(),
unstable_createLiveBeacon: jest.fn().mockResolvedValue({ event_id: '1' }),
unstable_setLiveBeacon: jest.fn().mockResolvedValue({ event_id: '1' }),
unstable_createLiveBeacon: jest.fn().mockResolvedValue({ event_id: "1" }),
unstable_setLiveBeacon: jest.fn().mockResolvedValue({ event_id: "1" }),
getVisibleRooms: jest.fn().mockReturnValue([]),
});
const defaultProps = {
menuPosition: {
top: 1, left: 1,
top: 1,
left: 1,
chevronFace: ChevronFace.Bottom,
},
onFinished: jest.fn(),
openMenu: jest.fn(),
roomId: '!room:server.org',
sender: new RoomMember('!room:server.org', userId),
roomId: "!room:server.org",
sender: new RoomMember("!room:server.org", userId),
};
const position = {
@ -105,7 +106,7 @@ describe('<LocationShareMenu />', () => {
accuracy: 10,
},
timestamp: 1646305006802,
type: 'geolocate',
type: "geolocate",
};
const makeOwnBeaconStore = async () => {
@ -122,11 +123,11 @@ describe('<LocationShareMenu />', () => {
});
beforeEach(async () => {
jest.spyOn(logger, 'error').mockRestore();
jest.spyOn(logger, "error").mockRestore();
mocked(SettingsStore).getValue.mockReturnValue(false);
mockClient.sendMessage.mockClear();
mockClient.unstable_createLiveBeacon.mockClear().mockResolvedValue({ event_id: '1' });
jest.spyOn(MatrixClientPeg, 'get').mockReturnValue(mockClient as unknown as MatrixClient);
mockClient.unstable_createLiveBeacon.mockClear().mockResolvedValue({ event_id: "1" });
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient as unknown as MatrixClient);
mocked(Modal).createDialog.mockClear();
jest.clearAllMocks();
@ -135,20 +136,20 @@ describe('<LocationShareMenu />', () => {
});
const getShareTypeOption = (component: ReactWrapper, shareType: LocationShareType) =>
findByTagAndTestId(component, `share-location-option-${shareType}`, 'button');
findByTagAndTestId(component, `share-location-option-${shareType}`, "button");
const getBackButton = (component: ReactWrapper) =>
findByTagAndTestId(component, 'share-dialog-buttons-back', 'button');
findByTagAndTestId(component, "share-dialog-buttons-back", "button");
const getCancelButton = (component: ReactWrapper) =>
findByTagAndTestId(component, 'share-dialog-buttons-cancel', 'button');
findByTagAndTestId(component, "share-dialog-buttons-cancel", "button");
const getSubmitButton = (component: ReactWrapper) =>
findByTagAndTestId(component, 'location-picker-submit-button', 'button');
findByTagAndTestId(component, "location-picker-submit-button", "button");
const setLocation = (component: ReactWrapper) => {
// set the location
const locationPickerInstance = component.find('LocationPicker').instance();
const locationPickerInstance = component.find("LocationPicker").instance();
act(() => {
// @ts-ignore
locationPickerInstance.onGeolocate(position);
@ -159,38 +160,38 @@ describe('<LocationShareMenu />', () => {
const setShareType = (component: ReactWrapper, shareType: LocationShareType) =>
act(() => {
getShareTypeOption(component, shareType).at(0).simulate('click');
getShareTypeOption(component, shareType).at(0).simulate("click");
component.setProps({});
});
describe('when only Own share type is enabled', () => {
describe("when only Own share type is enabled", () => {
beforeEach(() => enableSettings([]));
it('renders own and live location options', () => {
it("renders own and live location options", () => {
const component = getComponent();
expect(getShareTypeOption(component, LocationShareType.Own).length).toBe(1);
expect(getShareTypeOption(component, LocationShareType.Live).length).toBe(1);
});
it('renders back button from location picker screen', () => {
it("renders back button from location picker screen", () => {
const component = getComponent();
setShareType(component, LocationShareType.Own);
expect(getBackButton(component).length).toBe(1);
});
it('clicking cancel button from location picker closes dialog', () => {
it("clicking cancel button from location picker closes dialog", () => {
const onFinished = jest.fn();
const component = getComponent({ onFinished });
act(() => {
getCancelButton(component).at(0).simulate('click');
getCancelButton(component).at(0).simulate("click");
});
expect(onFinished).toHaveBeenCalled();
});
it('creates static own location share event on submission', () => {
it("creates static own location share event on submission", () => {
const onFinished = jest.fn();
const component = getComponent({ onFinished });
@ -199,7 +200,7 @@ describe('<LocationShareMenu />', () => {
setLocation(component);
act(() => {
getSubmitButton(component).at(0).simulate('click');
getSubmitButton(component).at(0).simulate("click");
component.setProps({});
});
@ -207,66 +208,68 @@ describe('<LocationShareMenu />', () => {
const [messageRoomId, relation, messageBody] = mockClient.sendMessage.mock.calls[0];
expect(messageRoomId).toEqual(defaultProps.roomId);
expect(relation).toEqual(null);
expect(messageBody).toEqual(expect.objectContaining({
[M_ASSET.name]: {
type: LocationAssetType.Self,
},
}));
expect(messageBody).toEqual(
expect.objectContaining({
[M_ASSET.name]: {
type: LocationAssetType.Self,
},
}),
);
});
});
describe('with pin drop share type enabled', () => {
it('renders share type switch with own and pin drop options', () => {
describe("with pin drop share type enabled", () => {
it("renders share type switch with own and pin drop options", () => {
const component = getComponent();
expect(component.find('LocationPicker').length).toBe(0);
expect(component.find("LocationPicker").length).toBe(0);
expect(getShareTypeOption(component, LocationShareType.Own).length).toBe(1);
expect(getShareTypeOption(component, LocationShareType.Pin).length).toBe(1);
});
it('does not render back button on share type screen', () => {
it("does not render back button on share type screen", () => {
const component = getComponent();
expect(getBackButton(component).length).toBe(0);
});
it('clicking cancel button from share type screen closes dialog', () => {
it("clicking cancel button from share type screen closes dialog", () => {
const onFinished = jest.fn();
const component = getComponent({ onFinished });
act(() => {
getCancelButton(component).at(0).simulate('click');
getCancelButton(component).at(0).simulate("click");
});
expect(onFinished).toHaveBeenCalled();
});
it('selecting own location share type advances to location picker', () => {
it("selecting own location share type advances to location picker", () => {
const component = getComponent();
setShareType(component, LocationShareType.Own);
expect(component.find('LocationPicker').length).toBe(1);
expect(component.find("LocationPicker").length).toBe(1);
});
it('clicking back button from location picker screen goes back to share screen', () => {
it("clicking back button from location picker screen goes back to share screen", () => {
const onFinished = jest.fn();
const component = getComponent({ onFinished });
// advance to location picker
setShareType(component, LocationShareType.Own);
expect(component.find('LocationPicker').length).toBe(1);
expect(component.find("LocationPicker").length).toBe(1);
act(() => {
getBackButton(component).at(0).simulate('click');
getBackButton(component).at(0).simulate("click");
component.setProps({});
});
// back to share type
expect(component.find('ShareType').length).toBe(1);
expect(component.find("ShareType").length).toBe(1);
});
it('creates pin drop location share event on submission', () => {
it("creates pin drop location share event on submission", () => {
const onFinished = jest.fn();
const component = getComponent({ onFinished });
@ -276,7 +279,7 @@ describe('<LocationShareMenu />', () => {
setLocation(component);
act(() => {
getSubmitButton(component).at(0).simulate('click');
getSubmitButton(component).at(0).simulate("click");
component.setProps({});
});
@ -284,76 +287,81 @@ describe('<LocationShareMenu />', () => {
const [messageRoomId, relation, messageBody] = mockClient.sendMessage.mock.calls[0];
expect(messageRoomId).toEqual(defaultProps.roomId);
expect(relation).toEqual(null);
expect(messageBody).toEqual(expect.objectContaining({
[M_ASSET.name]: {
type: LocationAssetType.Pin,
},
}));
expect(messageBody).toEqual(
expect.objectContaining({
[M_ASSET.name]: {
type: LocationAssetType.Pin,
},
}),
);
});
});
describe('with live location disabled', () => {
describe("with live location disabled", () => {
beforeEach(() => enableSettings([]));
const getToggle = (component: ReactWrapper) =>
findByTestId(component, 'enable-live-share-toggle').find('[role="switch"]').at(0);
findByTestId(component, "enable-live-share-toggle").find('[role="switch"]').at(0);
const getSubmitEnableButton = (component: ReactWrapper) =>
findByTestId(component, 'enable-live-share-submit').at(0);
findByTestId(component, "enable-live-share-submit").at(0);
it('goes to labs flag screen after live options is clicked', () => {
it("goes to labs flag screen after live options is clicked", () => {
const onFinished = jest.fn();
const component = getComponent({ onFinished });
setShareType(component, LocationShareType.Live);
expect(findByTestId(component, 'location-picker-enable-live-share')).toMatchSnapshot();
expect(findByTestId(component, "location-picker-enable-live-share")).toMatchSnapshot();
});
it('disables OK button when labs flag is not enabled', () => {
it("disables OK button when labs flag is not enabled", () => {
const component = getComponent();
setShareType(component, LocationShareType.Live);
expect(getSubmitEnableButton(component).props()['disabled']).toBeTruthy();
expect(getSubmitEnableButton(component).props()["disabled"]).toBeTruthy();
});
it('enables OK button when labs flag is toggled to enabled', () => {
it("enables OK button when labs flag is toggled to enabled", () => {
const component = getComponent();
setShareType(component, LocationShareType.Live);
act(() => {
getToggle(component).simulate('click');
getToggle(component).simulate("click");
component.setProps({});
});
expect(getSubmitEnableButton(component).props()['disabled']).toBeFalsy();
expect(getSubmitEnableButton(component).props()["disabled"]).toBeFalsy();
});
it('enables live share setting on ok button submit', () => {
it("enables live share setting on ok button submit", () => {
const component = getComponent();
setShareType(component, LocationShareType.Live);
act(() => {
getToggle(component).simulate('click');
getToggle(component).simulate("click");
component.setProps({});
});
act(() => {
getSubmitEnableButton(component).simulate('click');
getSubmitEnableButton(component).simulate("click");
});
expect(SettingsStore.setValue).toHaveBeenCalledWith(
'feature_location_share_live', undefined, SettingLevel.DEVICE, true,
"feature_location_share_live",
undefined,
SettingLevel.DEVICE,
true,
);
});
it('navigates to location picker when live share is enabled in settings store', () => {
it("navigates to location picker when live share is enabled in settings store", () => {
// @ts-ignore
mocked(SettingsStore.watchSetting).mockImplementation((featureName, roomId, callback) => {
callback(featureName, roomId, SettingLevel.DEVICE, '', '');
callback(featureName, roomId, SettingLevel.DEVICE, "", "");
window.setTimeout(() => {
callback(featureName, roomId, SettingLevel.DEVICE, '', '');
callback(featureName, roomId, SettingLevel.DEVICE, "", "");
}, 1000);
});
mocked(SettingsStore.getValue).mockReturnValue(false);
@ -362,7 +370,7 @@ describe('<LocationShareMenu />', () => {
setShareType(component, LocationShareType.Live);
// we're on enable live share screen
expect(findByTestId(component, 'location-picker-enable-live-share').length).toBeTruthy();
expect(findByTestId(component, "location-picker-enable-live-share").length).toBeTruthy();
act(() => {
mocked(SettingsStore.getValue).mockReturnValue(true);
@ -373,24 +381,24 @@ describe('<LocationShareMenu />', () => {
component.setProps({});
// advanced to location picker
expect(component.find('LocationPicker').length).toBeTruthy();
expect(component.find("LocationPicker").length).toBeTruthy();
});
});
describe('Live location share', () => {
describe("Live location share", () => {
beforeEach(() => enableSettings(["feature_location_share_live"]));
it('does not display live location share option when composer has a relation', () => {
it("does not display live location share option when composer has a relation", () => {
const relation = {
rel_type: RelationType.Thread,
event_id: '12345',
event_id: "12345",
};
const component = getComponent({ relation });
expect(getShareTypeOption(component, LocationShareType.Live).length).toBeFalsy();
});
it('creates beacon info event on submission', async () => {
it("creates beacon info event on submission", async () => {
const onFinished = jest.fn();
const component = getComponent({ onFinished });
@ -399,7 +407,7 @@ describe('<LocationShareMenu />', () => {
setLocation(component);
act(() => {
getSubmitButton(component).at(0).simulate('click');
getSubmitButton(component).at(0).simulate("click");
component.setProps({});
});
@ -409,21 +417,23 @@ describe('<LocationShareMenu />', () => {
expect(onFinished).toHaveBeenCalled();
const [eventRoomId, eventContent] = mockClient.unstable_createLiveBeacon.mock.calls[0];
expect(eventRoomId).toEqual(defaultProps.roomId);
expect(eventContent).toEqual(expect.objectContaining({
// default timeout
timeout: DEFAULT_DURATION_MS,
description: `Ernie's live location`,
live: true,
[M_ASSET.name]: {
type: LocationAssetType.Self,
},
}));
expect(eventContent).toEqual(
expect.objectContaining({
// default timeout
timeout: DEFAULT_DURATION_MS,
description: `Ernie's live location`,
live: true,
[M_ASSET.name]: {
type: LocationAssetType.Self,
},
}),
);
});
it('opens error dialog when beacon creation fails', async () => {
it("opens error dialog when beacon creation fails", async () => {
// stub logger to keep console clean from expected error
const logSpy = jest.spyOn(logger, 'error').mockReturnValue(undefined);
const error = new Error('oh no');
const logSpy = jest.spyOn(logger, "error").mockReturnValue(undefined);
const error = new Error("oh no");
mockClient.unstable_createLiveBeacon.mockRejectedValue(error);
const component = getComponent();
@ -432,7 +442,7 @@ describe('<LocationShareMenu />', () => {
setLocation(component);
act(() => {
getSubmitButton(component).at(0).simulate('click');
getSubmitButton(component).at(0).simulate("click");
component.setProps({});
});
@ -441,18 +451,21 @@ describe('<LocationShareMenu />', () => {
await flushPromisesWithFakeTimers();
expect(logSpy).toHaveBeenCalledWith("We couldn't start sharing your live location", error);
expect(mocked(Modal).createDialog).toHaveBeenCalledWith(QuestionDialog, expect.objectContaining({
button: 'Try again',
description: 'Element could not send your location. Please try again later.',
title: `We couldn't send your location`,
cancelButton: 'Cancel',
}));
expect(mocked(Modal).createDialog).toHaveBeenCalledWith(
QuestionDialog,
expect.objectContaining({
button: "Try again",
description: "Element could not send your location. Please try again later.",
title: `We couldn't send your location`,
cancelButton: "Cancel",
}),
);
});
it('opens error dialog when beacon creation fails with permission error', async () => {
it("opens error dialog when beacon creation fails with permission error", async () => {
// stub logger to keep console clean from expected error
const logSpy = jest.spyOn(logger, 'error').mockReturnValue(undefined);
const error = { errcode: 'M_FORBIDDEN' } as unknown as Error;
const logSpy = jest.spyOn(logger, "error").mockReturnValue(undefined);
const error = { errcode: "M_FORBIDDEN" } as unknown as Error;
mockClient.unstable_createLiveBeacon.mockRejectedValue(error);
const component = getComponent();
@ -461,7 +474,7 @@ describe('<LocationShareMenu />', () => {
setLocation(component);
act(() => {
getSubmitButton(component).at(0).simulate('click');
getSubmitButton(component).at(0).simulate("click");
component.setProps({});
});
@ -470,12 +483,15 @@ describe('<LocationShareMenu />', () => {
await flushPromisesWithFakeTimers();
expect(logSpy).toHaveBeenCalledWith("Insufficient permissions to start sharing your live location", error);
expect(mocked(Modal).createDialog).toHaveBeenCalledWith(QuestionDialog, expect.objectContaining({
button: 'OK',
description: 'You need to have the right permissions in order to share locations in this room.',
title: `You don't have permission to share locations`,
hasCancelButton: false,
}));
expect(mocked(Modal).createDialog).toHaveBeenCalledWith(
QuestionDialog,
expect.objectContaining({
button: "OK",
description: "You need to have the right permissions in order to share locations in this room.",
title: `You don't have permission to share locations`,
hasCancelButton: false,
}),
);
});
});
});

View file

@ -14,23 +14,23 @@ 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 { RoomMember } from 'matrix-js-sdk/src/matrix';
import { LocationAssetType } from 'matrix-js-sdk/src/@types/location';
import maplibregl from 'maplibre-gl';
import { mount } from "enzyme";
import { RoomMember } from "matrix-js-sdk/src/matrix";
import { LocationAssetType } from "matrix-js-sdk/src/@types/location";
import maplibregl from "maplibre-gl";
import LocationViewDialog from '../../../../src/components/views/location/LocationViewDialog';
import { TILE_SERVER_WK_KEY } from '../../../../src/utils/WellKnownUtils';
import { getMockClientWithEventEmitter, makeLocationEvent } from '../../../test-utils';
import LocationViewDialog from "../../../../src/components/views/location/LocationViewDialog";
import { TILE_SERVER_WK_KEY } from "../../../../src/utils/WellKnownUtils";
import { getMockClientWithEventEmitter, makeLocationEvent } from "../../../test-utils";
describe('<LocationViewDialog />', () => {
const roomId = '!room:server';
const userId = '@user:server';
describe("<LocationViewDialog />", () => {
const roomId = "!room:server";
const userId = "@user:server";
const mockClient = getMockClientWithEventEmitter({
getClientWellKnown: jest.fn().mockReturnValue({
[TILE_SERVER_WK_KEY.name]: { map_style_url: 'maps.com' },
[TILE_SERVER_WK_KEY.name]: { map_style_url: "maps.com" },
}),
isGuest: jest.fn().mockReturnValue(false),
});
@ -40,24 +40,23 @@ describe('<LocationViewDialog />', () => {
mxEvent: defaultEvent,
onFinished: jest.fn(),
};
const getComponent = (props = {}) =>
mount(<LocationViewDialog {...defaultProps} {...props} />);
const getComponent = (props = {}) => mount(<LocationViewDialog {...defaultProps} {...props} />);
beforeAll(() => {
maplibregl.AttributionControl = jest.fn();
});
it('renders map correctly', () => {
it("renders map correctly", () => {
const component = getComponent();
expect(component.find('Map')).toMatchSnapshot();
expect(component.find("Map")).toMatchSnapshot();
});
it('renders marker correctly for self share', () => {
it("renders marker correctly for self share", () => {
const selfShareEvent = makeLocationEvent("geo:51.5076,-0.1276", LocationAssetType.Self);
const member = new RoomMember(roomId, userId);
// @ts-ignore cheat assignment to property
selfShareEvent.sender = member;
const component = getComponent({ mxEvent: selfShareEvent });
expect(component.find('SmartMarker').props()['roomMember']).toEqual(member);
expect(component.find("SmartMarker").props()["roomMember"]).toEqual(member);
});
});

View file

@ -14,29 +14,29 @@ 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 { ClientEvent } from 'matrix-js-sdk/src/matrix';
import { logger } from 'matrix-js-sdk/src/logger';
import { mount } from "enzyme";
import { act } from "react-dom/test-utils";
import maplibregl from "maplibre-gl";
import { ClientEvent } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import Map from '../../../../src/components/views/location/Map';
import { findByTestId, getMockClientWithEventEmitter } from '../../../test-utils';
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
import { TILE_SERVER_WK_KEY } from '../../../../src/utils/WellKnownUtils';
import Map from "../../../../src/components/views/location/Map";
import { findByTestId, getMockClientWithEventEmitter } from "../../../test-utils";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import { TILE_SERVER_WK_KEY } from "../../../../src/utils/WellKnownUtils";
describe('<Map />', () => {
describe("<Map />", () => {
const defaultProps = {
centerGeoUri: 'geo:52,41',
id: 'test-123',
centerGeoUri: "geo:52,41",
id: "test-123",
onError: jest.fn(),
onClick: jest.fn(),
};
const matrixClient = getMockClientWithEventEmitter({
getClientWellKnown: jest.fn().mockReturnValue({
[TILE_SERVER_WK_KEY.name]: { map_style_url: 'maps.com' },
[TILE_SERVER_WK_KEY.name]: { map_style_url: "maps.com" },
}),
});
const getComponent = (props = {}) =>
@ -52,33 +52,33 @@ describe('<Map />', () => {
beforeEach(() => {
jest.clearAllMocks();
matrixClient.getClientWellKnown.mockReturnValue({
[TILE_SERVER_WK_KEY.name]: { map_style_url: 'maps.com' },
[TILE_SERVER_WK_KEY.name]: { map_style_url: "maps.com" },
});
jest.spyOn(logger, 'error').mockRestore();
jest.spyOn(logger, "error").mockRestore();
});
const mockMap = new maplibregl.Map();
it('renders', () => {
it("renders", () => {
const component = getComponent();
expect(component).toBeTruthy();
});
describe('onClientWellKnown emits', () => {
it('updates map style when style url is truthy', () => {
describe("onClientWellKnown emits", () => {
it("updates map style when style url is truthy", () => {
getComponent();
act(() => {
matrixClient.emit(ClientEvent.ClientWellKnown, {
[TILE_SERVER_WK_KEY.name]: { map_style_url: 'new.maps.com' },
[TILE_SERVER_WK_KEY.name]: { map_style_url: "new.maps.com" },
});
});
expect(mockMap.setStyle).toHaveBeenCalledWith('new.maps.com');
expect(mockMap.setStyle).toHaveBeenCalledWith("new.maps.com");
});
it('does not update map style when style url is truthy', () => {
it("does not update map style when style url is truthy", () => {
getComponent();
act(() => {
@ -91,58 +91,60 @@ describe('<Map />', () => {
});
});
describe('map centering', () => {
it('does not try to center when no center uri provided', () => {
describe("map centering", () => {
it("does not try to center when no center uri provided", () => {
getComponent({ centerGeoUri: null });
expect(mockMap.setCenter).not.toHaveBeenCalled();
});
it('sets map center to centerGeoUri', () => {
getComponent({ centerGeoUri: 'geo:51,42' });
it("sets map center to centerGeoUri", () => {
getComponent({ centerGeoUri: "geo:51,42" });
expect(mockMap.setCenter).toHaveBeenCalledWith({ lat: 51, lon: 42 });
});
it('handles invalid centerGeoUri', () => {
const logSpy = jest.spyOn(logger, 'error').mockImplementation();
getComponent({ centerGeoUri: '123 Sesame Street' });
it("handles invalid centerGeoUri", () => {
const logSpy = jest.spyOn(logger, "error").mockImplementation();
getComponent({ centerGeoUri: "123 Sesame Street" });
expect(mockMap.setCenter).not.toHaveBeenCalled();
expect(logSpy).toHaveBeenCalledWith('Could not set map center');
expect(logSpy).toHaveBeenCalledWith("Could not set map center");
});
it('updates map center when centerGeoUri prop changes', () => {
const component = getComponent({ centerGeoUri: 'geo:51,42' });
it("updates map center when centerGeoUri prop changes", () => {
const component = getComponent({ centerGeoUri: "geo:51,42" });
component.setProps({ centerGeoUri: 'geo:53,45' });
component.setProps({ centerGeoUri: 'geo:56,47' });
component.setProps({ centerGeoUri: "geo:53,45" });
component.setProps({ centerGeoUri: "geo:56,47" });
expect(mockMap.setCenter).toHaveBeenCalledWith({ lat: 51, lon: 42 });
expect(mockMap.setCenter).toHaveBeenCalledWith({ lat: 53, lon: 45 });
expect(mockMap.setCenter).toHaveBeenCalledWith({ lat: 56, lon: 47 });
});
});
describe('map bounds', () => {
it('does not try to fit map bounds when no bounds provided', () => {
describe("map bounds", () => {
it("does not try to fit map bounds when no bounds provided", () => {
getComponent({ bounds: null });
expect(mockMap.fitBounds).not.toHaveBeenCalled();
});
it('fits map to bounds', () => {
it("fits map to bounds", () => {
const bounds = { north: 51, south: 50, east: 42, west: 41 };
getComponent({ bounds });
expect(mockMap.fitBounds).toHaveBeenCalledWith(new maplibregl.LngLatBounds([bounds.west, bounds.south],
[bounds.east, bounds.north]), { padding: 100, maxZoom: 15 });
expect(mockMap.fitBounds).toHaveBeenCalledWith(
new maplibregl.LngLatBounds([bounds.west, bounds.south], [bounds.east, bounds.north]),
{ padding: 100, maxZoom: 15 },
);
});
it('handles invalid bounds', () => {
const logSpy = jest.spyOn(logger, 'error').mockImplementation();
const bounds = { north: 'a', south: 'b', east: 42, west: 41 };
it("handles invalid bounds", () => {
const logSpy = jest.spyOn(logger, "error").mockImplementation();
const bounds = { north: "a", south: "b", east: 42, west: 41 };
getComponent({ bounds });
expect(mockMap.fitBounds).not.toHaveBeenCalled();
expect(logSpy).toHaveBeenCalledWith('Invalid map bounds');
expect(logSpy).toHaveBeenCalledWith("Invalid map bounds");
});
it('updates map bounds when bounds prop changes', () => {
const component = getComponent({ centerGeoUri: 'geo:51,42' });
it("updates map bounds when bounds prop changes", () => {
const component = getComponent({ centerGeoUri: "geo:51,42" });
const bounds = { north: 51, south: 50, east: 42, west: 41 };
const bounds2 = { north: 53, south: 51, east: 45, west: 44 };
@ -152,8 +154,8 @@ describe('<Map />', () => {
});
});
describe('children', () => {
it('renders without children', () => {
describe("children", () => {
it("renders without children", () => {
const component = getComponent({ children: null });
component.setProps({});
@ -162,18 +164,22 @@ describe('<Map />', () => {
expect(component).toBeTruthy();
});
it('renders children with map renderProp', () => {
const children = ({ map }) => <div data-test-id='test-child' data-map={map}>Hello, world</div>;
it("renders children with map renderProp", () => {
const children = ({ map }) => (
<div data-test-id="test-child" data-map={map}>
Hello, world
</div>
);
const component = getComponent({ children });
// renders child with map instance
expect(findByTestId(component, 'test-child').props()['data-map']).toEqual(mockMap);
expect(findByTestId(component, "test-child").props()["data-map"]).toEqual(mockMap);
});
});
describe('onClick', () => {
it('eats clicks to maplibre attribution button', () => {
describe("onClick", () => {
it("eats clicks to maplibre attribution button", () => {
const onClick = jest.fn();
const component = getComponent({ onClick });
@ -181,20 +187,20 @@ describe('<Map />', () => {
// this is added to the dom by maplibregl
// which is mocked
// just fake the target
const fakeEl = document.createElement('div');
fakeEl.className = 'maplibregl-ctrl-attrib-button';
component.simulate('click', { target: fakeEl });
const fakeEl = document.createElement("div");
fakeEl.className = "maplibregl-ctrl-attrib-button";
component.simulate("click", { target: fakeEl });
});
expect(onClick).not.toHaveBeenCalled();
});
it('calls onClick', () => {
it("calls onClick", () => {
const onClick = jest.fn();
const component = getComponent({ onClick });
act(() => {
component.simulate('click');
component.simulate("click");
});
expect(onClick).toHaveBeenCalled();

View file

@ -14,43 +14,43 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { render, RenderResult } from '@testing-library/react';
import React from "react";
import { render, RenderResult } from "@testing-library/react";
import { MapError, MapErrorProps } from '../../../../src/components/views/location/MapError';
import { LocationShareError } from '../../../../src/utils/location';
import { MapError, MapErrorProps } from "../../../../src/components/views/location/MapError";
import { LocationShareError } from "../../../../src/utils/location";
describe('<MapError />', () => {
describe("<MapError />", () => {
const defaultProps = {
onFinished: jest.fn(),
error: LocationShareError.MapStyleUrlNotConfigured,
className: 'test',
className: "test",
};
const getComponent = (props: Partial<MapErrorProps> = {}): RenderResult =>
render(<MapError {...defaultProps} {...props} />);
it('renders correctly for MapStyleUrlNotConfigured', () => {
it("renders correctly for MapStyleUrlNotConfigured", () => {
const { container } = getComponent();
expect(container).toMatchSnapshot();
});
it('renders correctly for MapStyleUrlNotReachable', () => {
it("renders correctly for MapStyleUrlNotReachable", () => {
const { container } = getComponent({
error: LocationShareError.MapStyleUrlNotReachable,
});
expect(container).toMatchSnapshot();
});
it('does not render button when onFinished falsy', () => {
it("does not render button when onFinished falsy", () => {
const { queryByText } = getComponent({
error: LocationShareError.MapStyleUrlNotReachable,
onFinished: undefined,
});
// no button
expect(queryByText('OK')).toBeFalsy();
expect(queryByText("OK")).toBeFalsy();
});
it('applies class when isMinimised is truthy', () => {
it("applies class when isMinimised is truthy", () => {
const { container } = getComponent({
isMinimised: true,
});

View file

@ -14,39 +14,38 @@ 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 { RoomMember } from 'matrix-js-sdk/src/matrix';
import { mount } from "enzyme";
import { RoomMember } from "matrix-js-sdk/src/matrix";
import Marker from '../../../../src/components/views/location/Marker';
import Marker from "../../../../src/components/views/location/Marker";
describe('<Marker />', () => {
describe("<Marker />", () => {
const defaultProps = {
id: 'abc123',
id: "abc123",
};
const getComponent = (props = {}) =>
mount(<Marker {...defaultProps} {...props} />);
const getComponent = (props = {}) => mount(<Marker {...defaultProps} {...props} />);
it('renders with location icon when no room member', () => {
it("renders with location icon when no room member", () => {
const component = getComponent();
expect(component).toMatchSnapshot();
});
it('does not try to use member color without room member', () => {
it("does not try to use member color without room member", () => {
const component = getComponent({ useMemberColor: true });
expect(component.find('div').at(0).props().className).toEqual('mx_Marker mx_Marker_defaultColor');
expect(component.find("div").at(0).props().className).toEqual("mx_Marker mx_Marker_defaultColor");
});
it('uses member color class', () => {
const member = new RoomMember('!room:server', '@user:server');
it("uses member color class", () => {
const member = new RoomMember("!room:server", "@user:server");
const component = getComponent({ useMemberColor: true, roomMember: member });
expect(component.find('div').at(0).props().className).toEqual('mx_Marker mx_Username_color3');
expect(component.find("div").at(0).props().className).toEqual("mx_Marker mx_Username_color3");
});
it('renders member avatar when roomMember is truthy', () => {
const member = new RoomMember('!room:server', '@user:server');
it("renders member avatar when roomMember is truthy", () => {
const member = new RoomMember("!room:server", "@user:server");
const component = getComponent({ roomMember: member });
expect(component.find('MemberAvatar').length).toBeTruthy();
expect(component.find("MemberAvatar").length).toBeTruthy();
});
});

View file

@ -14,34 +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 { mocked } from 'jest-mock';
import maplibregl from 'maplibre-gl';
import { mount } from "enzyme";
import { mocked } from "jest-mock";
import maplibregl from "maplibre-gl";
import SmartMarker from '../../../../src/components/views/location/SmartMarker';
import SmartMarker from "../../../../src/components/views/location/SmartMarker";
jest.mock('../../../../src/utils/location/findMapStyleUrl', () => ({
findMapStyleUrl: jest.fn().mockReturnValue('tileserver.com'),
jest.mock("../../../../src/utils/location/findMapStyleUrl", () => ({
findMapStyleUrl: jest.fn().mockReturnValue("tileserver.com"),
}));
describe('<SmartMarker />', () => {
describe("<SmartMarker />", () => {
const mockMap = new maplibregl.Map();
const mockMarker = new maplibregl.Marker();
const defaultProps = {
map: mockMap,
geoUri: 'geo:43.2,54.6',
geoUri: "geo:43.2,54.6",
};
const getComponent = (props = {}) =>
mount(<SmartMarker {...defaultProps} {...props} />);
const getComponent = (props = {}) => mount(<SmartMarker {...defaultProps} {...props} />);
beforeEach(() => {
jest.clearAllMocks();
});
it('creates a marker on mount', () => {
it("creates a marker on mount", () => {
const component = getComponent();
expect(component).toMatchSnapshot();
@ -55,11 +54,11 @@ describe('<SmartMarker />', () => {
expect(mockMarker.addTo).toHaveBeenCalledWith(mockMap);
});
it('updates marker position on change', () => {
const component = getComponent({ geoUri: 'geo:40,50' });
it("updates marker position on change", () => {
const component = getComponent({ geoUri: "geo:40,50" });
component.setProps({ geoUri: 'geo:41,51' });
component.setProps({ geoUri: 'geo:42,52' });
component.setProps({ geoUri: "geo:41,51" });
component.setProps({ geoUri: "geo:42,52" });
// marker added only once
expect(maplibregl.Marker).toHaveBeenCalledTimes(1);
@ -69,7 +68,7 @@ describe('<SmartMarker />', () => {
expect(mocked(mockMarker.setLngLat)).toHaveBeenCalledWith({ lat: 42, lon: 52 });
});
it('removes marker on unmount', () => {
it("removes marker on unmount", () => {
const component = getComponent();
expect(component).toMatchSnapshot();

View file

@ -14,48 +14,47 @@ 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 maplibregl from 'maplibre-gl';
import { act } from 'react-dom/test-utils';
import { mount } from "enzyme";
import maplibregl from "maplibre-gl";
import { act } from "react-dom/test-utils";
import ZoomButtons from '../../../../src/components/views/location/ZoomButtons';
import { findByTestId } from '../../../test-utils';
import ZoomButtons from "../../../../src/components/views/location/ZoomButtons";
import { findByTestId } from "../../../test-utils";
describe('<ZoomButtons />', () => {
describe("<ZoomButtons />", () => {
const mockMap = new maplibregl.Map();
const defaultProps = {
map: mockMap,
};
const getComponent = (props = {}) =>
mount(<ZoomButtons {...defaultProps} {...props} />);
const getComponent = (props = {}) => mount(<ZoomButtons {...defaultProps} {...props} />);
beforeEach(() => {
jest.clearAllMocks();
});
it('renders buttons', () => {
it("renders buttons", () => {
const component = getComponent();
expect(component).toMatchSnapshot();
});
it('calls map zoom in on zoom in click', () => {
it("calls map zoom in on zoom in click", () => {
const component = getComponent();
act(() => {
findByTestId(component, 'map-zoom-in-button').at(0).simulate('click');
findByTestId(component, "map-zoom-in-button").at(0).simulate("click");
});
expect(mockMap.zoomIn).toHaveBeenCalled();
expect(component).toBeTruthy();
});
it('calls map zoom out on zoom out click', () => {
it("calls map zoom out on zoom out click", () => {
const component = getComponent();
act(() => {
findByTestId(component, 'map-zoom-out-button').at(0).simulate('click');
findByTestId(component, "map-zoom-out-button").at(0).simulate("click");
});
expect(mockMap.zoomOut).toHaveBeenCalled();

View file

@ -47,13 +47,11 @@ describe("shareLocation", () => {
} as unknown as MatrixClient;
mocked(makeLocationContent).mockReturnValue(content);
mocked(doMaybeLocalRoomAction).mockImplementation(<T>(
roomId: string,
fn: (actualRoomId: string) => Promise<T>,
client?: MatrixClient,
) => {
return fn(roomId);
});
mocked(doMaybeLocalRoomAction).mockImplementation(
<T>(roomId: string, fn: (actualRoomId: string) => Promise<T>, client?: MatrixClient) => {
return fn(roomId);
},
);
shareLocationFn = shareLocation(client, roomId, shareType, null, () => {});
});

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();
});

View file

@ -32,12 +32,7 @@ import {
PollEndEvent,
} from "matrix-events-sdk";
import {
stubClient,
mkStubRoom,
mkEvent,
mkMessage,
} from "../../../test-utils";
import { stubClient, mkStubRoom, mkEvent, mkMessage } from "../../../test-utils";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import PinnedMessagesCard from "../../../../src/components/views/right_panel/PinnedMessagesCard";
import PinnedEventTile from "../../../../src/components/views/rooms/PinnedEventTile";
@ -53,31 +48,34 @@ describe("<PinnedMessagesCard />", () => {
cli.relations.mockResolvedValue({ originalEvent: {} as unknown as MatrixEvent, events: [] });
const mkRoom = (localPins: MatrixEvent[], nonLocalPins: MatrixEvent[]): Room => {
const room = mkStubRoom("!room:example.org", 'room', cli);
const room = mkStubRoom("!room:example.org", "room", cli);
// Deferred since we may be adding or removing pins later
const pins = () => [...localPins, ...nonLocalPins];
// Insert pin IDs into room state
mocked(room.currentState).getStateEvents.mockImplementation((): any => mkEvent({
event: true,
type: EventType.RoomPinnedEvents,
content: {
pinned: pins().map(e => e.getId()),
},
user: '@user:example.org',
room: '!room:example.org',
}));
mocked(room.currentState).getStateEvents.mockImplementation((): any =>
mkEvent({
event: true,
type: EventType.RoomPinnedEvents,
content: {
pinned: pins().map((e) => e.getId()),
},
user: "@user:example.org",
room: "!room:example.org",
}),
);
// Insert local pins into local timeline set
room.getUnfilteredTimelineSet = () => ({
getTimelineForEvent: () => ({
getEvents: () => localPins,
}),
} as unknown as EventTimelineSet);
room.getUnfilteredTimelineSet = () =>
({
getTimelineForEvent: () => ({
getEvents: () => localPins,
}),
} as unknown as EventTimelineSet);
// Return all pins over fetchRoomEvent
cli.fetchRoomEvent.mockImplementation((roomId, eventId) => {
const event = pins().find(e => e.getId() === eventId)?.event;
const event = pins().find((e) => e.getId() === eventId)?.event;
return Promise.resolve(event as IMinimalEvent);
});
@ -87,16 +85,19 @@ describe("<PinnedMessagesCard />", () => {
const mountPins = async (room: Room): Promise<ReactWrapper<ComponentProps<typeof PinnedMessagesCard>>> => {
let pins;
await act(async () => {
pins = mount(<PinnedMessagesCard
room={room}
onClose={jest.fn()}
permalinkCreator={new RoomPermalinkCreator(room, room.roomId)}
/>, {
wrappingComponent: MatrixClientContext.Provider,
wrappingComponentProps: { value: cli },
});
pins = mount(
<PinnedMessagesCard
room={room}
onClose={jest.fn()}
permalinkCreator={new RoomPermalinkCreator(room, room.roomId)}
/>,
{
wrappingComponent: MatrixClientContext.Provider,
wrappingComponentProps: { value: cli },
},
);
// Wait a tick for state updates
await new Promise(resolve => setImmediate(resolve));
await new Promise((resolve) => setImmediate(resolve));
});
pins.update();
@ -105,15 +106,16 @@ describe("<PinnedMessagesCard />", () => {
const emitPinUpdates = async (pins: ReactWrapper<ComponentProps<typeof PinnedMessagesCard>>) => {
const room = pins.props().room;
const pinListener = mocked(room.currentState).on.mock.calls
.find(([eventName, listener]) => eventName === RoomStateEvent.Events)[1];
const pinListener = mocked(room.currentState).on.mock.calls.find(
([eventName, listener]) => eventName === RoomStateEvent.Events,
)[1];
await act(async () => {
// Emit the update
// @ts-ignore what is going on here?
pinListener(room.currentState.getStateEvents());
// Wait a tick for state updates
await new Promise(resolve => setImmediate(resolve));
await new Promise((resolve) => setImmediate(resolve));
});
pins.update();
};
@ -240,12 +242,14 @@ describe("<PinnedMessagesCard />", () => {
["@alice:example.org", 0],
["@bob:example.org", 0],
["@eve:example.org", 1],
].map(([user, option], i) => mkEvent({
...PollResponseEvent.from([answers[option].id], poll.getId()).serialize(),
event: true,
room: "!room:example.org",
user: user as string,
}));
].map(([user, option], i) =>
mkEvent({
...PollResponseEvent.from([answers[option].id], poll.getId()).serialize(),
event: true,
room: "!room:example.org",
user: user as string,
}),
);
const end = mkEvent({
...PollEndEvent.from(poll.getId(), "Closing the poll").serialize(),
event: true,
@ -259,9 +263,9 @@ describe("<PinnedMessagesCard />", () => {
switch (eventType) {
case M_POLL_RESPONSE.name:
// Paginate the results, for added challenge
return (from === "page2") ?
{ originalEvent: poll, events: responses.slice(2) } :
{ originalEvent: poll, events: responses.slice(0, 2), nextBatch: "page2" };
return from === "page2"
? { originalEvent: poll, events: responses.slice(2) }
: { originalEvent: poll, events: responses.slice(0, 2), nextBatch: "page2" };
case M_POLL_END.name:
return { originalEvent: null, events: [end] };
}

View file

@ -25,7 +25,7 @@ import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import SettingsStore from "../../../../src/settings/SettingsStore";
import { stubClient } from "../../../test-utils";
describe("RoomHeaderButtons-test.tsx", function() {
describe("RoomHeaderButtons-test.tsx", function () {
const ROOM_ID = "!roomId:example.org";
let room: Room;
let client: MatrixClient;
@ -45,10 +45,7 @@ describe("RoomHeaderButtons-test.tsx", function() {
});
function getComponent(room?: Room) {
return render(<RoomHeaderButtons
room={room}
excludedRightPanelPhaseButtons={[]}
/>);
return render(<RoomHeaderButtons room={room} excludedRightPanelPhaseButtons={[]} />);
}
function getThreadButton(container) {
@ -56,9 +53,7 @@ describe("RoomHeaderButtons-test.tsx", function() {
}
function isIndicatorOfType(container, type: "red" | "gray") {
return container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")
.className
.includes(type);
return container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator").className.includes(type);
}
it("shows the thread button", () => {

View file

@ -14,24 +14,24 @@ 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 { mocked } from 'jest-mock';
import { mount } from "enzyme";
import { mocked } from "jest-mock";
import { act } from "react-dom/test-utils";
import { Room, User, MatrixClient } from 'matrix-js-sdk/src/matrix';
import { Phase, VerificationRequest } from 'matrix-js-sdk/src/crypto/verification/request/VerificationRequest';
import { Room, User, MatrixClient } from "matrix-js-sdk/src/matrix";
import { Phase, VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import UserInfo from '../../../../src/components/views/right_panel/UserInfo';
import { RightPanelPhases } from '../../../../src/stores/right-panel/RightPanelStorePhases';
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
import VerificationPanel from '../../../../src/components/views/right_panel/VerificationPanel';
import EncryptionInfo from '../../../../src/components/views/right_panel/EncryptionInfo';
import UserInfo from "../../../../src/components/views/right_panel/UserInfo";
import { RightPanelPhases } from "../../../../src/stores/right-panel/RightPanelStorePhases";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import VerificationPanel from "../../../../src/components/views/right_panel/VerificationPanel";
import EncryptionInfo from "../../../../src/components/views/right_panel/EncryptionInfo";
const findByTestId = (component, id) => component.find(`[data-test-id="${id}"]`);
jest.mock('../../../../src/utils/DMRoomMap', () => {
jest.mock("../../../../src/utils/DMRoomMap", () => {
const mock = {
getUserIdForRoomId: jest.fn(),
getDMRoomsForUserId: jest.fn(),
@ -43,8 +43,8 @@ jest.mock('../../../../src/utils/DMRoomMap', () => {
};
});
describe('<UserInfo />', () => {
const defaultUserId = '@test:test';
describe("<UserInfo />", () => {
const defaultUserId = "@test:test";
const defaultUser = new User(defaultUserId);
const mockClient = mocked({
@ -57,7 +57,7 @@ describe('<UserInfo />', () => {
isSynapseAdministrator: jest.fn().mockResolvedValue(false),
isRoomEncrypted: jest.fn().mockReturnValue(false),
doesServerSupportUnstableFeature: jest.fn().mockReturnValue(false),
mxcUrlToHttp: jest.fn().mockReturnValue('mock-mxcUrlToHttp'),
mxcUrlToHttp: jest.fn().mockReturnValue("mock-mxcUrlToHttp"),
removeListener: jest.fn(),
currentState: {
on: jest.fn(),
@ -65,7 +65,9 @@ describe('<UserInfo />', () => {
} as unknown as MatrixClient);
const verificationRequest = {
pending: true, on: jest.fn(), phase: Phase.Ready,
pending: true,
on: jest.fn(),
phase: Phase.Ready,
channel: { transactionId: 1 },
otherPartySupportsMethod: jest.fn(),
} as unknown as VerificationRequest;
@ -77,51 +79,49 @@ describe('<UserInfo />', () => {
onClose: jest.fn(),
};
const getComponent = (props = {}) => mount(
<UserInfo {...defaultProps} {...props} />,
{
const getComponent = (props = {}) =>
mount(<UserInfo {...defaultProps} {...props} />, {
wrappingComponent: MatrixClientContext.Provider,
wrappingComponentProps: { value: mockClient },
},
);
});
beforeAll(() => {
jest.spyOn(MatrixClientPeg, 'get').mockReturnValue(mockClient);
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
});
beforeEach(() => {
mockClient.getUser.mockClear().mockReturnValue({} as unknown as User);
});
it('closes on close button click', () => {
it("closes on close button click", () => {
const onClose = jest.fn();
const component = getComponent({ onClose });
act(() => {
findByTestId(component, 'base-card-close-button').at(0).simulate('click');
findByTestId(component, "base-card-close-button").at(0).simulate("click");
});
expect(onClose).toHaveBeenCalled();
});
describe('without a room', () => {
it('does not render space header', () => {
describe("without a room", () => {
it("does not render space header", () => {
const component = getComponent();
expect(findByTestId(component, 'space-header').length).toBeFalsy();
expect(findByTestId(component, "space-header").length).toBeFalsy();
});
it('renders user info', () => {
it("renders user info", () => {
const component = getComponent();
expect(component.find("BasicUserInfo").length).toBeTruthy();
});
it('renders encryption info panel without pending verification', () => {
it("renders encryption info panel without pending verification", () => {
const phase = RightPanelPhases.EncryptionPanel;
const component = getComponent({ phase });
expect(component.find(EncryptionInfo).length).toBeTruthy();
});
it('renders encryption verification panel with pending verification', () => {
it("renders encryption verification panel with pending verification", () => {
const phase = RightPanelPhases.EncryptionPanel;
const component = getComponent({ phase, verificationRequest });
@ -129,22 +129,22 @@ describe('<UserInfo />', () => {
expect(component.find(VerificationPanel).length).toBeTruthy();
});
it('renders close button correctly when encryption panel with a pending verification request', () => {
it("renders close button correctly when encryption panel with a pending verification request", () => {
const phase = RightPanelPhases.EncryptionPanel;
const component = getComponent({ phase, verificationRequest });
expect(findByTestId(component, 'base-card-close-button').at(0).props().title).toEqual('Cancel');
expect(findByTestId(component, "base-card-close-button").at(0).props().title).toEqual("Cancel");
});
});
describe('with a room', () => {
describe("with a room", () => {
const room = {
roomId: '!fkfk',
roomId: "!fkfk",
getType: jest.fn().mockReturnValue(undefined),
isSpaceRoom: jest.fn().mockReturnValue(false),
getMember: jest.fn().mockReturnValue(undefined),
getMxcAvatarUrl: jest.fn().mockReturnValue('mock-avatar-url'),
name: 'test room',
getMxcAvatarUrl: jest.fn().mockReturnValue("mock-avatar-url"),
name: "test room",
on: jest.fn(),
currentState: {
getStateEvents: jest.fn(),
@ -152,32 +152,33 @@ describe('<UserInfo />', () => {
},
} as unknown as Room;
it('renders user info', () => {
it("renders user info", () => {
const component = getComponent();
expect(component.find("BasicUserInfo").length).toBeTruthy();
});
it('does not render space header when room is not a space room', () => {
it("does not render space header when room is not a space room", () => {
const component = getComponent({ room });
expect(findByTestId(component, 'space-header').length).toBeFalsy();
expect(findByTestId(component, "space-header").length).toBeFalsy();
});
it('renders space header when room is a space room', () => {
it("renders space header when room is a space room", () => {
const spaceRoom = {
...room, isSpaceRoom: jest.fn().mockReturnValue(true),
...room,
isSpaceRoom: jest.fn().mockReturnValue(true),
};
const component = getComponent({ room: spaceRoom });
expect(findByTestId(component, 'space-header').length).toBeTruthy();
expect(findByTestId(component, "space-header").length).toBeTruthy();
});
it('renders encryption info panel without pending verification', () => {
it("renders encryption info panel without pending verification", () => {
const phase = RightPanelPhases.EncryptionPanel;
const component = getComponent({ phase, room });
expect(component.find(EncryptionInfo).length).toBeTruthy();
});
it('renders encryption verification panel with pending verification', () => {
it("renders encryption verification panel with pending verification", () => {
const phase = RightPanelPhases.EncryptionPanel;
const component = getComponent({ phase, verificationRequest, room });

View file

@ -14,12 +14,12 @@ 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, ReactWrapper } from 'enzyme';
import { MatrixClient, Room } from 'matrix-js-sdk/src/matrix';
import { mount, ReactWrapper } from "enzyme";
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
import BasicMessageComposer from '../../../../src/components/views/rooms/BasicMessageComposer';
import BasicMessageComposer from "../../../../src/components/views/rooms/BasicMessageComposer";
import * as TestUtils from "../../../test-utils";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import EditorModel from "../../../../src/editor/model";
@ -40,7 +40,7 @@ describe("BasicMessageComposer", () => {
wrapper.find(".mx_BasicMessageComposer_input").simulate("paste", {
clipboardData: {
getData: type => {
getData: (type) => {
if (type === "text/plain") {
return "https://element.io";
}
@ -56,11 +56,9 @@ describe("BasicMessageComposer", () => {
function render(model: EditorModel): ReactWrapper {
const client: MatrixClient = MatrixClientPeg.get();
const roomId = '!1234567890:domain';
const roomId = "!1234567890:domain";
const userId = client.getUserId();
const room = new Room(roomId, client, userId);
return mount((
<BasicMessageComposer model={model} room={room} />
));
return mount(<BasicMessageComposer model={model} room={room} />);
}

View file

@ -41,10 +41,7 @@ describe("EventTile", () => {
// Give a way for a test to update the event prop.
// changeEvent = setEvent;
return <EventTile
mxEvent={mxEvent}
{...props}
/>;
return <EventTile mxEvent={mxEvent} {...props} />;
}
function getComponent(
@ -58,7 +55,8 @@ describe("EventTile", () => {
<MatrixClientContext.Provider value={client}>
<RoomContext.Provider value={context}>
<TestEventTile {...overrides} />
</RoomContext.Provider>,
</RoomContext.Provider>
,
</MatrixClientContext.Provider>,
);
}
@ -75,7 +73,7 @@ describe("EventTile", () => {
jest.spyOn(client, "getRoom").mockReturnValue(room);
jest.spyOn(client, "decryptEventIfNeeded").mockResolvedValue();
jest.spyOn(SettingsStore, "getValue").mockImplementation(name => name === "feature_thread");
jest.spyOn(SettingsStore, "getValue").mockImplementation((name) => name === "feature_thread");
mxEvent = mkMessage({
room: room.roomId,
@ -91,16 +89,22 @@ describe("EventTile", () => {
});
it("removes the thread summary when thread is deleted", async () => {
const { rootEvent, events: [, reply] } = mkThread({
const {
rootEvent,
events: [, reply],
} = mkThread({
room,
client,
authorId: "@alice:example.org",
participantUserIds: ["@alice:example.org"],
length: 2, // root + 1 answer
});
getComponent({
mxEvent: rootEvent,
}, TimelineRenderingType.Room);
getComponent(
{
mxEvent: rootEvent,
},
TimelineRenderingType.Room,
);
await waitFor(() => expect(screen.queryByTestId("thread-summary")).not.toBeNull());

View file

@ -15,26 +15,26 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import ReactTestUtils from 'react-dom/test-utils';
import ReactDOM from 'react-dom';
import { Room } from 'matrix-js-sdk/src/models/room';
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
import React from "react";
import ReactTestUtils from "react-dom/test-utils";
import ReactDOM from "react-dom";
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { User } from "matrix-js-sdk/src/models/user";
import { compare } from "matrix-js-sdk/src/utils";
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
import * as TestUtils from '../../../test-utils';
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import * as TestUtils from "../../../test-utils";
import MemberList from "../../../../src/components/views/rooms/MemberList";
import MemberTile from '../../../../src/components/views/rooms/MemberTile';
import { SDKContext } from '../../../../src/contexts/SDKContext';
import { TestSdkContext } from '../../../TestSdkContext';
import MemberTile from "../../../../src/components/views/rooms/MemberTile";
import { SDKContext } from "../../../../src/contexts/SDKContext";
import { TestSdkContext } from "../../../TestSdkContext";
function generateRoomId() {
return '!' + Math.random().toString().slice(2, 10) + ':domain';
return "!" + Math.random().toString().slice(2, 10) + ":domain";
}
describe('MemberList', () => {
describe("MemberList", () => {
function createRoom(opts = {}) {
const room = new Room(generateRoomId(), null, client.getUserId());
if (opts) {
@ -53,12 +53,12 @@ describe('MemberList', () => {
let moderatorUsers = [];
let defaultUsers = [];
beforeEach(function() {
beforeEach(function () {
TestUtils.stubClient();
client = MatrixClientPeg.get();
client.hasLazyLoadMembersEnabled = () => false;
parentDiv = document.createElement('div');
parentDiv = document.createElement("div");
document.body.appendChild(parentDiv);
// Make room
@ -76,7 +76,7 @@ describe('MemberList', () => {
adminUser.powerLevel = 100;
adminUser.user = new User(adminUser.userId);
adminUser.user.currentlyActive = true;
adminUser.user.presence = 'online';
adminUser.user.presence = "online";
adminUser.user.lastPresenceTs = 1000;
adminUser.user.lastActiveAgo = 10;
adminUsers.push(adminUser);
@ -86,7 +86,7 @@ describe('MemberList', () => {
moderatorUser.powerLevel = 50;
moderatorUser.user = new User(moderatorUser.userId);
moderatorUser.user.currentlyActive = true;
moderatorUser.user.presence = 'online';
moderatorUser.user.presence = "online";
moderatorUser.user.lastPresenceTs = 1000;
moderatorUser.user.lastActiveAgo = 10;
moderatorUsers.push(moderatorUser);
@ -96,7 +96,7 @@ describe('MemberList', () => {
defaultUser.powerLevel = 0;
defaultUser.user = new User(defaultUser.userId);
defaultUser.user.currentlyActive = true;
defaultUser.user.presence = 'online';
defaultUser.user.presence = "online";
defaultUser.user.lastPresenceTs = 1000;
defaultUser.user.lastActiveAgo = 10;
defaultUsers.push(defaultUser);
@ -109,7 +109,7 @@ describe('MemberList', () => {
memberListRoom.currentState = {
members: {},
getMember: jest.fn(),
getStateEvents: (eventType, stateKey) => stateKey === undefined ? [] : null, // ignore 3pid invites
getStateEvents: (eventType, stateKey) => (stateKey === undefined ? [] : null), // ignore 3pid invites
};
for (const member of [...adminUsers, ...moderatorUsers, ...defaultUsers]) {
memberListRoom.currentState.members[member.userId] = member;
@ -121,17 +121,15 @@ describe('MemberList', () => {
const context = new TestSdkContext();
context.client = client;
root = ReactDOM.render(
(
<SDKContext.Provider value={context}>
<MemberList
searchQuery=""
onClose={jest.fn()}
onSearchQueryChanged={jest.fn()}
roomId={memberListRoom.roomId}
ref={gatherWrappedRef}
/>
</SDKContext.Provider>
),
<SDKContext.Provider value={context}>
<MemberList
searchQuery=""
onClose={jest.fn()}
onSearchQueryChanged={jest.fn()}
roomId={memberListRoom.roomId}
ref={gatherWrappedRef}
/>
</SDKContext.Provider>,
parentDiv,
);
});
@ -166,15 +164,15 @@ describe('MemberList', () => {
let groupChange = false;
if (isPresenceEnabled) {
const convertPresence = (p) => p === 'unavailable' ? 'online' : p;
const presenceIndex = p => {
const order = ['active', 'online', 'offline'];
const convertPresence = (p) => (p === "unavailable" ? "online" : p);
const presenceIndex = (p) => {
const order = ["active", "online", "offline"];
const idx = order.indexOf(convertPresence(p));
return idx === -1 ? order.length : idx; // unknown states at the end
};
const idxA = presenceIndex(userA.currentlyActive ? 'active' : userA.presence);
const idxB = presenceIndex(userB.currentlyActive ? 'active' : userB.presence);
const idxA = presenceIndex(userA.currentlyActive ? "active" : userA.presence);
const idxB = presenceIndex(userB.currentlyActive ? "active" : userB.presence);
console.log("Comparing presence groups...");
expect(idxB).toBeGreaterThanOrEqual(idxA);
groupChange = idxA !== idxB;
@ -203,8 +201,8 @@ describe('MemberList', () => {
}
if (!groupChange) {
const nameA = memberA.name[0] === '@' ? memberA.name.slice(1) : memberA.name;
const nameB = memberB.name[0] === '@' ? memberB.name.slice(1) : memberB.name;
const nameA = memberA.name[0] === "@" ? memberA.name.slice(1) : memberA.name;
const nameB = memberB.name[0] === "@" ? memberB.name.slice(1) : memberB.name;
const nameCompare = compare(nameB, nameA);
console.log("Comparing name");
expect(nameCompare).toBeGreaterThanOrEqual(0);
@ -215,7 +213,7 @@ describe('MemberList', () => {
}
function itDoesOrderMembersCorrectly(enablePresence) {
describe('does order members correctly', () => {
describe("does order members correctly", () => {
// Note: even if presence is disabled, we still expect that the presence
// tests will pass. All expectOrderedByPresenceAndPowerLevel does is ensure
// the order is perceived correctly, regardless of what we did to the members.
@ -223,22 +221,22 @@ describe('MemberList', () => {
// Each of the 4 tests here is done to prove that the member list can meet
// all 4 criteria independently. Together, they should work.
it('by presence state', () => {
it("by presence state", () => {
// Intentionally pick users that will confuse the power level sorting
const activeUsers = [defaultUsers[0]];
const onlineUsers = [adminUsers[0]];
const offlineUsers = [...moderatorUsers, ...adminUsers.slice(1), ...defaultUsers.slice(1)];
activeUsers.forEach((u) => {
u.user.currentlyActive = true;
u.user.presence = 'online';
u.user.presence = "online";
});
onlineUsers.forEach((u) => {
u.user.currentlyActive = false;
u.user.presence = 'online';
u.user.presence = "online";
});
offlineUsers.forEach((u) => {
u.user.currentlyActive = false;
u.user.presence = 'offline';
u.user.presence = "offline";
});
// Bypass all the event listeners and skip to the good part
@ -249,7 +247,7 @@ describe('MemberList', () => {
expectOrderedByPresenceAndPowerLevel(tiles, enablePresence);
});
it('by power level', () => {
it("by power level", () => {
// We already have admin, moderator, and default users so leave them alone
// Bypass all the event listeners and skip to the good part
@ -260,7 +258,7 @@ describe('MemberList', () => {
expectOrderedByPresenceAndPowerLevel(tiles, enablePresence);
});
it('by last active timestamp', () => {
it("by last active timestamp", () => {
// Intentionally pick users that will confuse the power level sorting
// lastActiveAgoTs == lastPresenceTs - lastActiveAgo
const activeUsers = [defaultUsers[0]];
@ -290,7 +288,7 @@ describe('MemberList', () => {
expectOrderedByPresenceAndPowerLevel(tiles, enablePresence);
});
it('by name', () => {
it("by name", () => {
// Intentionally put everyone on the same level to force a name comparison
const allUsers = [...adminUsers, ...moderatorUsers, ...defaultUsers];
allUsers.forEach((u) => {
@ -311,12 +309,11 @@ describe('MemberList', () => {
});
}
describe('when presence is enabled', () => {
describe("when presence is enabled", () => {
itDoesOrderMembersCorrectly(true);
});
describe('when presence is not enabled', () => {
describe("when presence is not enabled", () => {
itDoesOrderMembersCorrectly(false);
});
});

View file

@ -21,8 +21,9 @@ import { MatrixEvent, MsgType, RoomMember } from "matrix-js-sdk/src/matrix";
import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread";
import { createTestClient, mkEvent, mkStubRoom, stubClient } from "../../../test-utils";
import MessageComposer, { MessageComposer as MessageComposerClass }
from "../../../../src/components/views/rooms/MessageComposer";
import MessageComposer, {
MessageComposer as MessageComposerClass,
} from "../../../../src/components/views/rooms/MessageComposer";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import RoomContext from "../../../../src/contexts/RoomContext";
@ -65,15 +66,20 @@ describe("MessageComposer", () => {
});
it("Does not render a SendMessageComposer or MessageComposerButtons when room is tombstoned", () => {
const wrapper = wrapAndRender({ room }, true, false, mkEvent({
event: true,
type: "m.room.tombstone",
room: room.roomId,
user: "@user1:server",
skey: "",
content: {},
ts: Date.now(),
}));
const wrapper = wrapAndRender(
{ room },
true,
false,
mkEvent({
event: true,
type: "m.room.tombstone",
room: room.roomId,
user: "@user1:server",
skey: "",
content: {},
ts: Date.now(),
}),
);
expect(wrapper.find("SendMessageComposer")).toHaveLength(0);
expect(wrapper.find("MessageComposerButtons")).toHaveLength(0);
@ -150,11 +156,14 @@ describe("MessageComposer", () => {
beforeEach(async () => {
// simulate settings update
await SettingsStore.setValue(setting, null, SettingLevel.DEVICE, !value);
dis.dispatch({
action: Action.SettingUpdated,
settingName: setting,
newValue: !value,
}, true);
dis.dispatch(
{
action: Action.SettingUpdated,
settingName: setting,
newValue: !value,
},
true,
);
wrapper.update();
});
@ -339,7 +348,7 @@ describe("MessageComposer", () => {
});
});
it('should render SendWysiwygComposer', () => {
it("should render SendWysiwygComposer", () => {
const room = mkStubRoom("!roomId:server", "Room 1", cli);
SettingsStore.setValue("feature_wysiwyg_composer", null, SettingLevel.DEVICE, true);
@ -362,7 +371,7 @@ function wrapAndRender(
currentState: undefined,
roomId,
client: mockClient,
getMember: function(userId: string): RoomMember {
getMember: function (userId: string): RoomMember {
return new RoomMember(roomId, userId);
},
};

View file

@ -53,11 +53,7 @@ describe("MessageComposerButtons", () => {
false,
);
expect(buttonLabels(buttons)).toEqual([
"Emoji",
"Attachment",
"More options",
]);
expect(buttonLabels(buttons)).toEqual(["Emoji", "Attachment", "More options"]);
});
it("Renders other buttons in menu in wide mode", () => {
@ -76,12 +72,7 @@ describe("MessageComposerButtons", () => {
"Emoji",
"Attachment",
"More options",
[
"Sticker",
"Voice Message",
"Poll",
"Location",
],
["Sticker", "Voice Message", "Poll", "Location"],
]);
});
@ -97,10 +88,7 @@ describe("MessageComposerButtons", () => {
true,
);
expect(buttonLabels(buttons)).toEqual([
"Emoji",
"More options",
]);
expect(buttonLabels(buttons)).toEqual(["Emoji", "More options"]);
});
it("Renders other buttons in menu (except voice messages) in narrow mode", () => {
@ -115,20 +103,11 @@ describe("MessageComposerButtons", () => {
true,
);
expect(buttonLabels(buttons)).toEqual([
"Emoji",
"More options",
[
"Attachment",
"Sticker",
"Poll",
"Location",
],
]);
expect(buttonLabels(buttons)).toEqual(["Emoji", "More options", ["Attachment", "Sticker", "Poll", "Location"]]);
});
describe('polls button', () => {
it('should render when asked to', () => {
describe("polls button", () => {
it("should render when asked to", () => {
const buttons = wrapAndRender(
<MessageComposerButtons
{...mockProps}
@ -143,16 +122,11 @@ describe("MessageComposerButtons", () => {
expect(buttonLabels(buttons)).toEqual([
"Emoji",
"More options",
[
"Attachment",
"Sticker",
"Poll",
"Location",
],
["Attachment", "Sticker", "Poll", "Location"],
]);
});
it('should not render when asked not to', () => {
it("should not render when asked not to", () => {
const buttons = wrapAndRender(
<MessageComposerButtons
{...mockProps}
@ -195,13 +169,7 @@ describe("MessageComposerButtons", () => {
"Emoji",
"Attachment",
"More options",
[
"Sticker",
"Voice Message",
"Voice broadcast",
"Poll",
"Location",
],
["Sticker", "Voice Message", "Voice broadcast", "Poll", "Location"],
]);
});
});
@ -209,13 +177,13 @@ describe("MessageComposerButtons", () => {
function wrapAndRender(component: React.ReactElement, narrow: boolean): ReactWrapper {
const mockClient = createTestClient();
jest.spyOn(MatrixClientPeg, 'get').mockReturnValue(mockClient);
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
const roomId = "myroomid";
const mockRoom: any = {
currentState: undefined,
roomId,
client: mockClient,
getMember: function(userId: string): RoomMember {
getMember: function (userId: string): RoomMember {
return new RoomMember(roomId, userId);
},
};
@ -223,9 +191,7 @@ function wrapAndRender(component: React.ReactElement, narrow: boolean): ReactWra
return mount(
<MatrixClientContext.Provider value={mockClient}>
<RoomContext.Provider value={roomState}>
{ component }
</RoomContext.Provider>
<RoomContext.Provider value={roomState}>{component}</RoomContext.Provider>
</MatrixClientContext.Provider>,
);
}
@ -274,23 +240,17 @@ function createRoomState(room: Room, narrow: boolean): IRoomState {
function buttonLabels(buttons: ReactWrapper): any[] {
// Note: Depends on the fact that the mini buttons use aria-label
// and the labels under More options use textContent
const mainButtons = (
buttons
.find('div.mx_MessageComposer_button[aria-label]')
.map((button: ReactWrapper) => button.prop("aria-label") as string)
.filter(x => x)
);
const mainButtons = buttons
.find("div.mx_MessageComposer_button[aria-label]")
.map((button: ReactWrapper) => button.prop("aria-label") as string)
.filter((x) => x);
const extraButtons = (
buttons
.find('.mx_MessageComposer_Menu div.mx_AccessibleButton[role="menuitem"]')
.map((button: ReactWrapper) => button.text())
.filter(x => x)
);
const extraButtons = buttons
.find('.mx_MessageComposer_Menu div.mx_AccessibleButton[role="menuitem"]')
.map((button: ReactWrapper) => button.text())
.filter((x) => x);
const list: any[] = [
...mainButtons,
];
const list: any[] = [...mainButtons];
if (extraButtons.length > 0) {
list.push(extraButtons);

View file

@ -29,7 +29,7 @@ import DMRoomMap from "../../../../src/utils/DMRoomMap";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import { DirectoryMember } from "../../../../src/utils/direct-messages";
const renderNewRoomIntro = (client: MatrixClient, room: Room|LocalRoom) => {
const renderNewRoomIntro = (client: MatrixClient, room: Room | LocalRoom) => {
render(
<MatrixClientContext.Provider value={client}>
<RoomContext.Provider value={{ room, roomId: room.roomId } as unknown as IRoomState}>

View file

@ -17,9 +17,7 @@ limitations under the License.
import { fireEvent, render } from "@testing-library/react";
import React from "react";
import {
StatelessNotificationBadge,
} from "../../../../../src/components/views/rooms/NotificationBadge/StatelessNotificationBadge";
import { StatelessNotificationBadge } from "../../../../../src/components/views/rooms/NotificationBadge/StatelessNotificationBadge";
import SettingsStore from "../../../../../src/settings/SettingsStore";
import { NotificationColor } from "../../../../../src/stores/notifications/NotificationColor";
@ -28,14 +26,16 @@ describe("NotificationBadge", () => {
it("lets you click it", () => {
const cb = jest.fn();
const { container } = render(<StatelessNotificationBadge
symbol=""
color={NotificationColor.Red}
count={5}
onClick={cb}
onMouseOver={cb}
onMouseLeave={cb}
/>);
const { container } = render(
<StatelessNotificationBadge
symbol=""
color={NotificationColor.Red}
count={5}
onClick={cb}
onMouseOver={cb}
onMouseLeave={cb}
/>,
);
fireEvent.click(container.firstChild);
expect(cb).toHaveBeenCalledTimes(1);
@ -52,11 +52,9 @@ describe("NotificationBadge", () => {
return name === "feature_hidebold";
});
const { container } = render(<StatelessNotificationBadge
symbol=""
color={NotificationColor.Bold}
count={1}
/>);
const { container } = render(
<StatelessNotificationBadge symbol="" color={NotificationColor.Bold} count={1} />,
);
expect(container.firstChild).toBeNull();
});

View file

@ -17,22 +17,14 @@ limitations under the License.
import React from "react";
import { render } from "@testing-library/react";
import {
StatelessNotificationBadge,
} from "../../../../../src/components/views/rooms/NotificationBadge/StatelessNotificationBadge";
import { StatelessNotificationBadge } from "../../../../../src/components/views/rooms/NotificationBadge/StatelessNotificationBadge";
import { NotificationColor } from "../../../../../src/stores/notifications/NotificationColor";
describe("StatelessNotificationBadge", () => {
it("is highlighted when unsent", () => {
const { container } = render(
<StatelessNotificationBadge
symbol="!"
count={0}
color={NotificationColor.Unsent}
/>,
<StatelessNotificationBadge symbol="!" count={0} color={NotificationColor.Unsent} />,
);
expect(
container.querySelector(".mx_NotificationBadge_highlighted"),
).not.toBe(null);
expect(container.querySelector(".mx_NotificationBadge_highlighted")).not.toBe(null);
});
});

View file

@ -22,16 +22,14 @@ import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
import { mocked } from "jest-mock";
import { EventStatus } from "matrix-js-sdk/src/models/event-status";
import {
UnreadNotificationBadge,
} from "../../../../../src/components/views/rooms/NotificationBadge/UnreadNotificationBadge";
import { UnreadNotificationBadge } from "../../../../../src/components/views/rooms/NotificationBadge/UnreadNotificationBadge";
import { mkMessage, stubClient } from "../../../../test-utils/test-utils";
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
import * as RoomNotifs from "../../../../../src/RoomNotifs";
jest.mock("../../../../../src/RoomNotifs");
jest.mock('../../../../../src/RoomNotifs', () => ({
...(jest.requireActual('../../../../../src/RoomNotifs') as Object),
jest.mock("../../../../../src/RoomNotifs", () => ({
...(jest.requireActual("../../../../../src/RoomNotifs") as Object),
getRoomNotifsState: jest.fn(),
}));
@ -122,9 +120,7 @@ describe("UnreadNotificationBadge", () => {
});
it("hides counter for muted rooms", () => {
jest.spyOn(RoomNotifs, "getRoomNotifsState")
.mockReset()
.mockReturnValue(RoomNotifs.RoomNotifState.Mute);
jest.spyOn(RoomNotifs, "getRoomNotifsState").mockReset().mockReturnValue(RoomNotifs.RoomNotifState.Mute);
const { container } = render(getComponent());
expect(container.querySelector(".mx_NotificationBadge")).toBeNull();

View file

@ -19,32 +19,28 @@ import { determineAvatarPosition, readReceiptTooltip } from "../../../../src/com
describe("ReadReceiptGroup", () => {
describe("TooltipText", () => {
it("returns '...and more' with hasMore", () => {
expect(readReceiptTooltip(["Alice", "Bob", "Charlie", "Dan", "Eve"], true))
.toEqual("Alice, Bob, Charlie, Dan, Eve and more");
expect(readReceiptTooltip(["Alice", "Bob", "Charlie", "Dan"], true))
.toEqual("Alice, Bob, Charlie, Dan and more");
expect(readReceiptTooltip(["Alice", "Bob", "Charlie"], true))
.toEqual("Alice, Bob, Charlie and more");
expect(readReceiptTooltip(["Alice", "Bob"], true))
.toEqual("Alice, Bob and more");
expect(readReceiptTooltip(["Alice"], true))
.toEqual("Alice and more");
expect(readReceiptTooltip([], false))
.toEqual(null);
expect(readReceiptTooltip(["Alice", "Bob", "Charlie", "Dan", "Eve"], true)).toEqual(
"Alice, Bob, Charlie, Dan, Eve and more",
);
expect(readReceiptTooltip(["Alice", "Bob", "Charlie", "Dan"], true)).toEqual(
"Alice, Bob, Charlie, Dan and more",
);
expect(readReceiptTooltip(["Alice", "Bob", "Charlie"], true)).toEqual("Alice, Bob, Charlie and more");
expect(readReceiptTooltip(["Alice", "Bob"], true)).toEqual("Alice, Bob and more");
expect(readReceiptTooltip(["Alice"], true)).toEqual("Alice and more");
expect(readReceiptTooltip([], false)).toEqual(null);
});
it("returns a pretty list without hasMore", () => {
expect(readReceiptTooltip(["Alice", "Bob", "Charlie", "Dan", "Eve"], false))
.toEqual("Alice, Bob, Charlie, Dan and Eve");
expect(readReceiptTooltip(["Alice", "Bob", "Charlie", "Dan"], false))
.toEqual("Alice, Bob, Charlie and Dan");
expect(readReceiptTooltip(["Alice", "Bob", "Charlie"], false))
.toEqual("Alice, Bob and Charlie");
expect(readReceiptTooltip(["Alice", "Bob"], false))
.toEqual("Alice and Bob");
expect(readReceiptTooltip(["Alice"], false))
.toEqual("Alice");
expect(readReceiptTooltip([], false))
.toEqual(null);
expect(readReceiptTooltip(["Alice", "Bob", "Charlie", "Dan", "Eve"], false)).toEqual(
"Alice, Bob, Charlie, Dan and Eve",
);
expect(readReceiptTooltip(["Alice", "Bob", "Charlie", "Dan"], false)).toEqual(
"Alice, Bob, Charlie and Dan",
);
expect(readReceiptTooltip(["Alice", "Bob", "Charlie"], false)).toEqual("Alice, Bob and Charlie");
expect(readReceiptTooltip(["Alice", "Bob"], false)).toEqual("Alice and Bob");
expect(readReceiptTooltip(["Alice"], false)).toEqual("Alice");
expect(readReceiptTooltip([], false)).toEqual(null);
});
});
describe("AvatarPosition", () => {
@ -53,44 +49,28 @@ describe("ReadReceiptGroup", () => {
// We want to fill slots so the first avatar is in the right-most slot without leaving any slots at the left
// unoccupied.
it("to handle the non-overflowing case correctly", () => {
expect(determineAvatarPosition(0, 4))
.toEqual({ hidden: false, position: 0 });
expect(determineAvatarPosition(0, 4)).toEqual({ hidden: false, position: 0 });
expect(determineAvatarPosition(0, 4))
.toEqual({ hidden: false, position: 0 });
expect(determineAvatarPosition(1, 4))
.toEqual({ hidden: false, position: 1 });
expect(determineAvatarPosition(0, 4)).toEqual({ hidden: false, position: 0 });
expect(determineAvatarPosition(1, 4)).toEqual({ hidden: false, position: 1 });
expect(determineAvatarPosition(0, 4))
.toEqual({ hidden: false, position: 0 });
expect(determineAvatarPosition(1, 4))
.toEqual({ hidden: false, position: 1 });
expect(determineAvatarPosition(2, 4))
.toEqual({ hidden: false, position: 2 });
expect(determineAvatarPosition(0, 4)).toEqual({ hidden: false, position: 0 });
expect(determineAvatarPosition(1, 4)).toEqual({ hidden: false, position: 1 });
expect(determineAvatarPosition(2, 4)).toEqual({ hidden: false, position: 2 });
expect(determineAvatarPosition(0, 4))
.toEqual({ hidden: false, position: 0 });
expect(determineAvatarPosition(1, 4))
.toEqual({ hidden: false, position: 1 });
expect(determineAvatarPosition(2, 4))
.toEqual({ hidden: false, position: 2 });
expect(determineAvatarPosition(3, 4))
.toEqual({ hidden: false, position: 3 });
expect(determineAvatarPosition(0, 4)).toEqual({ hidden: false, position: 0 });
expect(determineAvatarPosition(1, 4)).toEqual({ hidden: false, position: 1 });
expect(determineAvatarPosition(2, 4)).toEqual({ hidden: false, position: 2 });
expect(determineAvatarPosition(3, 4)).toEqual({ hidden: false, position: 3 });
});
it("to handle the overflowing case correctly", () => {
expect(determineAvatarPosition(0, 4))
.toEqual({ hidden: false, position: 0 });
expect(determineAvatarPosition(1, 4))
.toEqual({ hidden: false, position: 1 });
expect(determineAvatarPosition(2, 4))
.toEqual({ hidden: false, position: 2 });
expect(determineAvatarPosition(3, 4))
.toEqual({ hidden: false, position: 3 });
expect(determineAvatarPosition(4, 4))
.toEqual({ hidden: true, position: 0 });
expect(determineAvatarPosition(5, 4))
.toEqual({ hidden: true, position: 0 });
expect(determineAvatarPosition(0, 4)).toEqual({ hidden: false, position: 0 });
expect(determineAvatarPosition(1, 4)).toEqual({ hidden: false, position: 1 });
expect(determineAvatarPosition(2, 4)).toEqual({ hidden: false, position: 2 });
expect(determineAvatarPosition(3, 4)).toEqual({ hidden: false, position: 3 });
expect(determineAvatarPosition(4, 4)).toEqual({ hidden: true, position: 0 });
expect(determineAvatarPosition(5, 4)).toEqual({ hidden: true, position: 0 });
});
});
});

View file

@ -14,9 +14,9 @@ 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, ReactWrapper } from 'enzyme';
import { mount, ReactWrapper } from "enzyme";
import { render, screen, act, fireEvent, waitFor, getByRole } from "@testing-library/react";
import { mocked, Mocked } from "jest-mock";
import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
@ -26,7 +26,7 @@ import { PendingEventOrdering } from "matrix-js-sdk/src/client";
import { CallType } from "matrix-js-sdk/src/webrtc/call";
import { ClientWidgetApi, Widget } from "matrix-widget-api";
import EventEmitter from "events";
import { ISearchResults } from 'matrix-js-sdk/src/@types/search';
import { ISearchResults } from "matrix-js-sdk/src/@types/search";
import type { MatrixClient } from "matrix-js-sdk/src/client";
import type { MatrixEvent } from "matrix-js-sdk/src/models/event";
@ -39,14 +39,14 @@ import {
resetAsyncStoreWithClient,
mockPlatformPeg,
} from "../../../test-utils";
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
import DMRoomMap from '../../../../src/utils/DMRoomMap';
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import DMRoomMap from "../../../../src/utils/DMRoomMap";
import RoomHeader, { IProps as RoomHeaderProps } from "../../../../src/components/views/rooms/RoomHeader";
import { SearchScope } from '../../../../src/components/views/rooms/SearchBar';
import { E2EStatus } from '../../../../src/utils/ShieldUtils';
import { mkEvent } from '../../../test-utils';
import { SearchScope } from "../../../../src/components/views/rooms/SearchBar";
import { E2EStatus } from "../../../../src/utils/ShieldUtils";
import { mkEvent } from "../../../test-utils";
import { IRoomState } from "../../../../src/components/structures/RoomView";
import RoomContext from '../../../../src/contexts/RoomContext';
import RoomContext from "../../../../src/contexts/RoomContext";
import SdkConfig from "../../../../src/SdkConfig";
import SettingsStore from "../../../../src/settings/SettingsStore";
import { ElementCall, JitsiCall } from "../../../../src/models/Call";
@ -60,8 +60,8 @@ import WidgetUtils from "../../../../src/utils/WidgetUtils";
import { ElementWidgetActions } from "../../../../src/stores/widgets/ElementWidgetActions";
import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../../src/MediaDeviceHandler";
describe('RoomHeader (Enzyme)', () => {
it('shows the room avatar in a room with only ourselves', () => {
describe("RoomHeader (Enzyme)", () => {
it("shows the room avatar in a room with only ourselves", () => {
// When we render a non-DM room with 1 person in it
const room = createRoom({ name: "X Room", isDm: false, userIds: [] });
const rendered = mountHeader(room);
@ -75,10 +75,9 @@ describe('RoomHeader (Enzyme)', () => {
expect(image.prop("src")).toEqual("data:image/png;base64,00");
});
it('shows the room avatar in a room with 2 people', () => {
it("shows the room avatar in a room with 2 people", () => {
// When we render a non-DM room with 2 people in it
const room = createRoom(
{ name: "Y Room", isDm: false, userIds: ["other"] });
const room = createRoom({ name: "Y Room", isDm: false, userIds: ["other"] });
const rendered = mountHeader(room);
// Then the room's avatar is the initial of its name
@ -90,7 +89,7 @@ describe('RoomHeader (Enzyme)', () => {
expect(image.prop("src")).toEqual("data:image/png;base64,00");
});
it('shows the room avatar in a room with >2 people', () => {
it("shows the room avatar in a room with >2 people", () => {
// When we render a non-DM room with 3 people in it
const room = createRoom({ name: "Z Room", isDm: false, userIds: ["other1", "other2"] });
const rendered = mountHeader(room);
@ -104,7 +103,7 @@ describe('RoomHeader (Enzyme)', () => {
expect(image.prop("src")).toEqual("data:image/png;base64,00");
});
it('shows the room avatar in a DM with only ourselves', () => {
it("shows the room avatar in a DM with only ourselves", () => {
// When we render a non-DM room with 1 person in it
const room = createRoom({ name: "Z Room", isDm: true, userIds: [] });
const rendered = mountHeader(room);
@ -118,7 +117,7 @@ describe('RoomHeader (Enzyme)', () => {
expect(image.prop("src")).toEqual("data:image/png;base64,00");
});
it('shows the user avatar in a DM with 2 people', () => {
it("shows the user avatar in a DM with 2 people", () => {
// Note: this is the interesting case - this is the ONLY
// time we should use the user's avatar.
@ -134,10 +133,12 @@ describe('RoomHeader (Enzyme)', () => {
expect(rendered.find(".mx_BaseAvatar_initial")).toHaveLength(0);
});
it('shows the room avatar in a DM with >2 people', () => {
it("shows the room avatar in a DM with >2 people", () => {
// When we render a DM room with 3 people in it
const room = createRoom({
name: "Z Room", isDm: true, userIds: ["other1", "other2"],
name: "Z Room",
isDm: true,
userIds: ["other1", "other2"],
});
const rendered = mountHeader(room);
@ -160,17 +161,21 @@ describe('RoomHeader (Enzyme)', () => {
it("hides call buttons when the room is tombstoned", () => {
const room = createRoom({ name: "Room", isDm: false, userIds: [] });
const wrapper = mountHeader(room, {}, {
tombstone: mkEvent({
event: true,
type: "m.room.tombstone",
room: room.roomId,
user: "@user1:server",
skey: "",
content: {},
ts: Date.now(),
}),
});
const wrapper = mountHeader(
room,
{},
{
tombstone: mkEvent({
event: true,
type: "m.room.tombstone",
room: room.roomId,
user: "@user1:server",
skey: "",
content: {},
ts: Date.now(),
}),
},
);
expect(wrapper.find('[aria-label="Voice call"]').hostNodes()).toHaveLength(0);
expect(wrapper.find('[aria-label="Video call"]').hostNodes()).toHaveLength(0);
@ -211,7 +216,7 @@ function createRoom(info: IRoomCreationInfo) {
stubClient();
const client: MatrixClient = MatrixClientPeg.get();
const roomId = '!1234567890:domain';
const roomId = "!1234567890:domain";
const userId = client.getUserId();
if (info.isDm) {
client.getAccountData = (eventType) => {
@ -246,11 +251,11 @@ function mountHeader(room: Room, propsOverride = {}, roomContext?: Partial<IRoom
const props = {
room,
inRoom: true,
onSearchClick: () => { },
onSearchClick: () => {},
onInviteClick: null,
onForgetClick: () => { },
onCallPlaced: (_type) => { },
onAppsClick: () => { },
onForgetClick: () => {},
onCallPlaced: (_type) => {},
onAppsClick: () => {},
e2eStatus: E2EStatus.Normal,
appsShown: true,
searchInfo: {
@ -265,11 +270,11 @@ function mountHeader(room: Room, propsOverride = {}, roomContext?: Partial<IRoom
...propsOverride,
};
return mount((
return mount(
<RoomContext.Provider value={{ ...roomContext, room } as IRoomState}>
<RoomHeader {...props} />
</RoomContext.Provider>
));
</RoomContext.Provider>,
);
}
function mkCreationEvent(roomId: string, userId: string): MatrixEvent {
@ -289,9 +294,7 @@ function mkCreationEvent(roomId: string, userId: string): MatrixEvent {
});
}
function mkNameEvent(
roomId: string, userId: string, name: string,
): MatrixEvent {
function mkNameEvent(roomId: string, userId: string, name: string): MatrixEvent {
return mkEvent({
event: true,
type: "m.room.name",
@ -308,17 +311,15 @@ function mkJoinEvent(roomId: string, userId: string) {
room: roomId,
user: userId,
content: {
"membership": "join",
"avatar_url": "mxc://example.org/" + userId,
membership: "join",
avatar_url: "mxc://example.org/" + userId,
},
});
ret.event.state_key = userId;
return ret;
}
function mkDirectEvent(
roomId: string, userId: string, otherUsers: string[],
): MatrixEvent {
function mkDirectEvent(roomId: string, userId: string, otherUsers: string[]): MatrixEvent {
const content = {};
for (const otherUserId of otherUsers) {
content[otherUserId] = [roomId];
@ -363,7 +364,7 @@ describe("RoomHeader (React Testing Library)", () => {
});
room.currentState.setStateEvents([mkCreationEvent(room.roomId, "@alice:example.org")]);
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]);
client.sendStateEvent.mockImplementation(async (roomId, eventType, content, stateKey = "") => {
@ -384,13 +385,13 @@ describe("RoomHeader (React Testing Library)", () => {
bob = mkRoomMember(room.roomId, "@bob:example.org");
carol = mkRoomMember(room.roomId, "@carol:example.org");
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, WidgetStore.instance].map(
store => setupAsyncStoreWithClient(store, client),
));
await Promise.all(
[CallStore.instance, WidgetStore.instance].map((store) => setupAsyncStoreWithClient(store, client)),
);
jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({
[MediaDeviceKindEnum.AudioInput]: [],
@ -412,13 +413,11 @@ describe("RoomHeader (React Testing Library)", () => {
const mockRoomMembers = (members: RoomMember[]) => {
jest.spyOn(room, "getJoinedMembers").mockReturnValue(members);
jest.spyOn(room, "getMember").mockImplementation(
userId => members.find(member => member.userId === userId) ?? null,
(userId) => members.find((member) => member.userId === userId) ?? null,
);
};
const mockEnabledSettings = (settings: string[]) => {
jest.spyOn(SettingsStore, "getValue").mockImplementation(
settingName => settings.includes(settingName),
);
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName) => settings.includes(settingName));
};
const mockEventPowerLevels = (events: { [eventType: string]: number }) => {
room.currentState.setStateEvents([
@ -435,7 +434,7 @@ describe("RoomHeader (React Testing Library)", () => {
const mockLegacyCall = () => {
jest.spyOn(LegacyCallHandler.instance, "getCallForRoom").mockReturnValue({} as unknown as MatrixCall);
};
const withCall = async (fn: (call: ElementCall) => (void | Promise<void>)): Promise<void> => {
const withCall = async (fn: (call: ElementCall) => void | Promise<void>): Promise<void> => {
await ElementCall.create(room);
const call = CallStore.instance.getCall(room.roomId);
if (!(call instanceof ElementCall)) throw new Error("Failed to create call");
@ -468,10 +467,10 @@ describe("RoomHeader (React Testing Library)", () => {
<RoomHeader
room={room}
inRoom={true}
onSearchClick={() => { }}
onSearchClick={() => {}}
onInviteClick={null}
onForgetClick={() => { }}
onAppsClick={() => { }}
onForgetClick={() => {}}
onAppsClick={() => {}}
e2eStatus={E2EStatus.Normal}
appsShown={true}
searchInfo={{
@ -505,13 +504,13 @@ describe("RoomHeader (React Testing Library)", () => {
});
it(
"hides the voice call button and disables the video call button if configured to use Element Call exclusively "
+ "and there's an ongoing call",
"hides the voice call button and disables the video call button if configured to use Element Call exclusively " +
"and there's an ongoing call",
async () => {
mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
SdkConfig.put(
{ element_call: { url: "https://call.element.io", use_exclusively: true, brand: "Element Call" } },
);
SdkConfig.put({
element_call: { url: "https://call.element.io", use_exclusively: true, brand: "Element Call" },
});
await ElementCall.create(room);
renderHeader();
@ -521,13 +520,13 @@ describe("RoomHeader (React Testing Library)", () => {
);
it(
"hides the voice call button and starts an Element call when the video call button is pressed if configured to "
+ "use Element Call exclusively",
"hides the voice call button and starts an Element call when the video call button is pressed if configured to " +
"use Element Call exclusively",
async () => {
mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
SdkConfig.put(
{ element_call: { url: "https://call.element.io", use_exclusively: true, brand: "Element Call" } },
);
SdkConfig.put({
element_call: { url: "https://call.element.io", use_exclusively: true, brand: "Element Call" },
});
renderHeader();
expect(screen.queryByRole("button", { name: "Voice call" })).toBeNull();
@ -535,23 +534,25 @@ describe("RoomHeader (React Testing Library)", () => {
const dispatcherSpy = jest.fn();
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
fireEvent.click(screen.getByRole("button", { name: "Video call" }));
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);
},
);
it(
"hides the voice call button and disables the video call button if configured to use Element Call exclusively "
+ "and the user lacks permission",
"hides the voice call button and disables the video call button if configured to use Element Call exclusively " +
"and the user lacks permission",
() => {
mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
SdkConfig.put(
{ element_call: { url: "https://call.element.io", use_exclusively: true, brand: "Element Call" } },
);
SdkConfig.put({
element_call: { url: "https://call.element.io", use_exclusively: true, brand: "Element Call" },
});
mockEventPowerLevels({ [ElementCall.CALL_EVENT_TYPE.name]: 100 });
renderHeader();
@ -596,8 +597,8 @@ describe("RoomHeader (React Testing Library)", () => {
});
it(
"starts a legacy 1:1 call when call buttons are pressed in the new group call experience if there's 1 other "
+ "member",
"starts a legacy 1:1 call when call buttons are pressed in the new group call experience if there's 1 other " +
"member",
async () => {
mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
mockRoomMembers([alice, bob]);
@ -617,8 +618,8 @@ describe("RoomHeader (React Testing Library)", () => {
);
it(
"creates a Jitsi widget when call buttons are pressed in the new group call experience if the user lacks "
+ "permission to start Element calls",
"creates a Jitsi widget when call buttons are pressed in the new group call experience if the user lacks " +
"permission to start Element calls",
async () => {
mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
mockRoomMembers([alice, bob, carol]);
@ -639,8 +640,8 @@ describe("RoomHeader (React Testing Library)", () => {
);
it(
"creates a Jitsi widget when the voice call button is pressed and shows a menu when the video call button is "
+ "pressed in the new group call experience",
"creates a Jitsi widget when the voice call button is pressed and shows a menu when the video call button is " +
"pressed in the new group call experience",
async () => {
mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
mockRoomMembers([alice, bob, carol]);
@ -664,18 +665,20 @@ describe("RoomHeader (React Testing Library)", () => {
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
fireEvent.click(screen.getByRole("button", { name: "Video call" }));
fireEvent.click(getByRole(screen.getByRole("menu"), "menuitem", { name: /element/i }));
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);
},
);
it(
"disables the voice call button and starts an Element call when the video call button is pressed in the new "
+ "group call experience if the user lacks permission to edit widgets",
"disables the voice call button and starts an Element call when the video call button is pressed in the new " +
"group call experience if the user lacks permission to edit widgets",
async () => {
mockEnabledSettings(["showCallButtonsInComposer", "feature_group_calls"]);
mockRoomMembers([alice, bob, carol]);
@ -687,11 +690,13 @@ describe("RoomHeader (React Testing Library)", () => {
const dispatcherSpy = jest.fn();
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
fireEvent.click(screen.getByRole("button", { name: "Video call" }));
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);
},
);
@ -785,28 +790,32 @@ describe("RoomHeader (React Testing Library)", () => {
const dispatcherSpy = jest.fn();
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
fireEvent.click(screen.getByRole("button", { name: /close/i }));
await waitFor(() => expect(dispatcherSpy).toHaveBeenCalledWith({
action: Action.ViewRoom,
room_id: room.roomId,
view_call: false,
}));
await waitFor(() =>
expect(dispatcherSpy).toHaveBeenCalledWith({
action: Action.ViewRoom,
room_id: room.roomId,
view_call: false,
}),
);
defaultDispatcher.unregister(dispatcherRef);
});
it("shows a reduce button when viewing a call that returns to the timeline when pressed", async () => {
mockEnabledSettings(["feature_group_calls"]);
await withCall(async call => {
await withCall(async (call) => {
renderHeader({ viewingCall: true, activeCall: call });
const dispatcherSpy = jest.fn();
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
fireEvent.click(screen.getByRole("button", { name: /timeline/i }));
await waitFor(() => expect(dispatcherSpy).toHaveBeenCalledWith({
action: Action.ViewRoom,
room_id: room.roomId,
view_call: false,
}));
await waitFor(() =>
expect(dispatcherSpy).toHaveBeenCalledWith({
action: Action.ViewRoom,
room_id: room.roomId,
view_call: false,
}),
);
defaultDispatcher.unregister(dispatcherRef);
});
});
@ -814,7 +823,7 @@ describe("RoomHeader (React Testing Library)", () => {
it("shows a layout button when viewing a call that shows a menu when pressed", async () => {
mockEnabledSettings(["feature_group_calls"]);
await withCall(async call => {
await withCall(async (call) => {
await call.connect();
const messaging = WidgetMessagingStore.instance.getMessagingForUid(WidgetUtils.getWidgetUid(call.widget));
renderHeader({ viewingCall: true, activeCall: call });

View file

@ -14,33 +14,29 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import ReactTestUtils from 'react-dom/test-utils';
import ReactDOM from 'react-dom';
import {
PendingEventOrdering,
Room,
RoomMember,
} from 'matrix-js-sdk/src/matrix';
import React from "react";
import ReactTestUtils from "react-dom/test-utils";
import ReactDOM from "react-dom";
import { PendingEventOrdering, Room, RoomMember } from "matrix-js-sdk/src/matrix";
import * as TestUtils from '../../../test-utils';
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
import dis from '../../../../src/dispatcher/dispatcher';
import DMRoomMap from '../../../../src/utils/DMRoomMap';
import * as TestUtils from "../../../test-utils";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import dis from "../../../../src/dispatcher/dispatcher";
import DMRoomMap from "../../../../src/utils/DMRoomMap";
import { DefaultTagID } from "../../../../src/stores/room-list/models";
import RoomListStore, { RoomListStoreClass } from "../../../../src/stores/room-list/RoomListStore";
import RoomListLayoutStore from "../../../../src/stores/room-list/RoomListLayoutStore";
import RoomList from "../../../../src/components/views/rooms/RoomList";
import RoomSublist from "../../../../src/components/views/rooms/RoomSublist";
import { RoomTile } from "../../../../src/components/views/rooms/RoomTile";
import { getMockClientWithEventEmitter, mockClientMethodsUser } from '../../../test-utils';
import ResizeNotifier from '../../../../src/utils/ResizeNotifier';
import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../test-utils";
import ResizeNotifier from "../../../../src/utils/ResizeNotifier";
function generateRoomId() {
return '!' + Math.random().toString().slice(2, 10) + ':domain';
return "!" + Math.random().toString().slice(2, 10) + ":domain";
}
describe('RoomList', () => {
describe("RoomList", () => {
function createRoom(opts) {
const room = new Room(generateRoomId(), MatrixClientPeg.get(), client.getUserId(), {
// The room list now uses getPendingEvents(), so we need a detached ordering.
@ -54,9 +50,9 @@ describe('RoomList', () => {
let parentDiv = null;
let root = null;
const myUserId = '@me:domain';
const myUserId = "@me:domain";
const movingRoomId = '!someroomid';
const movingRoomId = "!someroomid";
let movingRoom: Room | undefined;
let otherRoom: Room | undefined;
@ -77,10 +73,10 @@ describe('RoomList', () => {
onResize: jest.fn(),
resizeNotifier: {} as unknown as ResizeNotifier,
isMinimized: false,
activeSpace: '',
activeSpace: "",
};
beforeEach(async function(done) {
beforeEach(async function (done) {
RoomListStoreClass.TEST_MODE = true;
jest.clearAllMocks();
@ -88,43 +84,42 @@ describe('RoomList', () => {
DMRoomMap.makeShared();
parentDiv = document.createElement('div');
parentDiv = document.createElement("div");
document.body.appendChild(parentDiv);
const WrappedRoomList = TestUtils.wrapInMatrixClientContext(RoomList);
root = ReactDOM.render(
<WrappedRoomList {...defaultProps} />,
parentDiv,
);
root = ReactDOM.render(<WrappedRoomList {...defaultProps} />, parentDiv);
ReactTestUtils.findRenderedComponentWithType(root, RoomList);
movingRoom = createRoom({ name: 'Moving room' });
movingRoom = createRoom({ name: "Moving room" });
expect(movingRoom.roomId).not.toBe(null);
// Mock joined member
myMember = new RoomMember(movingRoomId, myUserId);
myMember.membership = 'join';
movingRoom.updateMyMembership('join');
movingRoom.getMember = (userId) => ({
[client.credentials.userId]: myMember,
}[userId]);
myMember.membership = "join";
movingRoom.updateMyMembership("join");
movingRoom.getMember = (userId) =>
({
[client.credentials.userId]: myMember,
}[userId]);
otherRoom = createRoom({ name: 'Other room' });
otherRoom = createRoom({ name: "Other room" });
myOtherMember = new RoomMember(otherRoom.roomId, myUserId);
myOtherMember.membership = 'join';
otherRoom.updateMyMembership('join');
otherRoom.getMember = (userId) => ({
[client.credentials.userId]: myOtherMember,
}[userId]);
myOtherMember.membership = "join";
otherRoom.updateMyMembership("join");
otherRoom.getMember = (userId) =>
({
[client.credentials.userId]: myOtherMember,
}[userId]);
// Mock the matrix client
const mockRooms = [
movingRoom,
otherRoom,
createRoom({ tags: { 'm.favourite': { order: 0.1 } }, name: 'Some other room' }),
createRoom({ tags: { 'm.favourite': { order: 0.2 } }, name: 'Some other room 2' }),
createRoom({ tags: { 'm.lowpriority': {} }, name: 'Some unimportant room' }),
createRoom({ tags: { 'custom.tag': {} }, name: 'Some room customly tagged' }),
createRoom({ tags: { "m.favourite": { order: 0.1 } }, name: "Some other room" }),
createRoom({ tags: { "m.favourite": { order: 0.2 } }, name: "Some other room 2" }),
createRoom({ tags: { "m.lowpriority": {} }, name: "Some unimportant room" }),
createRoom({ tags: { "custom.tag": {} }, name: "Some room customly tagged" }),
];
client.getRooms.mockReturnValue(mockRooms);
client.getVisibleRooms.mockReturnValue(mockRooms);
@ -166,9 +161,14 @@ describe('RoomList', () => {
expectedRoomTile = roomTiles.find((tile) => tile.props.room === room);
} catch (err) {
// truncate the error message because it's spammy
err.message = 'Error finding RoomTile for ' + room.roomId + ' in ' +
subListTest + ': ' +
err.message.split('componentType')[0] + '...';
err.message =
"Error finding RoomTile for " +
room.roomId +
" in " +
subListTest +
": " +
err.message.split("componentType")[0] +
"...";
throw err;
}
@ -193,77 +193,81 @@ describe('RoomList', () => {
// Mock inverse m.direct
// @ts-ignore forcing private property
DMRoomMap.shared().roomToUser = {
[movingRoom.roomId]: '@someotheruser:domain',
[movingRoom.roomId]: "@someotheruser:domain",
};
}
dis.dispatch({ action: 'MatrixActions.sync', prevState: null, state: 'PREPARED', matrixClient: client });
dis.dispatch({ action: "MatrixActions.sync", prevState: null, state: "PREPARED", matrixClient: client });
expectRoomInSubList(movingRoom, srcSubListTest);
dis.dispatch({ action: 'RoomListActions.tagRoom.pending', request: {
oldTagId, newTagId, room: movingRoom,
} });
dis.dispatch({
action: "RoomListActions.tagRoom.pending",
request: {
oldTagId,
newTagId,
room: movingRoom,
},
});
expectRoomInSubList(movingRoom, destSubListTest);
}
function itDoesCorrectOptimisticUpdatesForDraggedRoomTiles() {
// TODO: Re-enable dragging tests when we support dragging again.
describe.skip('does correct optimistic update when dragging from', () => {
it('rooms to people', () => {
describe.skip("does correct optimistic update when dragging from", () => {
it("rooms to people", () => {
expectCorrectMove(undefined, DefaultTagID.DM);
});
it('rooms to favourites', () => {
expectCorrectMove(undefined, 'm.favourite');
it("rooms to favourites", () => {
expectCorrectMove(undefined, "m.favourite");
});
it('rooms to low priority', () => {
expectCorrectMove(undefined, 'm.lowpriority');
it("rooms to low priority", () => {
expectCorrectMove(undefined, "m.lowpriority");
});
// XXX: Known to fail - the view does not update immediately to reflect the change.
// Whe running the app live, it updates when some other event occurs (likely the
// m.direct arriving) that these tests do not fire.
xit('people to rooms', () => {
xit("people to rooms", () => {
expectCorrectMove(DefaultTagID.DM, undefined);
});
it('people to favourites', () => {
expectCorrectMove(DefaultTagID.DM, 'm.favourite');
it("people to favourites", () => {
expectCorrectMove(DefaultTagID.DM, "m.favourite");
});
it('people to lowpriority', () => {
expectCorrectMove(DefaultTagID.DM, 'm.lowpriority');
it("people to lowpriority", () => {
expectCorrectMove(DefaultTagID.DM, "m.lowpriority");
});
it('low priority to rooms', () => {
expectCorrectMove('m.lowpriority', undefined);
it("low priority to rooms", () => {
expectCorrectMove("m.lowpriority", undefined);
});
it('low priority to people', () => {
expectCorrectMove('m.lowpriority', DefaultTagID.DM);
it("low priority to people", () => {
expectCorrectMove("m.lowpriority", DefaultTagID.DM);
});
it('low priority to low priority', () => {
expectCorrectMove('m.lowpriority', 'm.lowpriority');
it("low priority to low priority", () => {
expectCorrectMove("m.lowpriority", "m.lowpriority");
});
it('favourites to rooms', () => {
expectCorrectMove('m.favourite', undefined);
it("favourites to rooms", () => {
expectCorrectMove("m.favourite", undefined);
});
it('favourites to people', () => {
expectCorrectMove('m.favourite', DefaultTagID.DM);
it("favourites to people", () => {
expectCorrectMove("m.favourite", DefaultTagID.DM);
});
it('favourites to low priority', () => {
expectCorrectMove('m.favourite', 'm.lowpriority');
it("favourites to low priority", () => {
expectCorrectMove("m.favourite", "m.lowpriority");
});
});
}
itDoesCorrectOptimisticUpdatesForDraggedRoomTiles();
});

Some files were not shown because too many files have changed in this diff Show more