Merge remote-tracking branch 'origin/develop' into feat/add-message-edition-wysiwyg-composer

This commit is contained in:
Florian Duros 2022-10-24 14:41:27 +02:00
commit de86221c72
No known key found for this signature in database
GPG key ID: 9700AA5870258A0B
55 changed files with 1551 additions and 668 deletions

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import { mocked } from "jest-mock";
import { Room, RoomMember } from "matrix-js-sdk/src/matrix";
import { Room, RoomMember, RoomType } from "matrix-js-sdk/src/matrix";
import { avatarUrlForRoom } from "../src/Avatar";
import { Media, mediaFromMxc } from "../src/customisations/Media";
@ -46,6 +46,7 @@ describe("avatarUrlForRoom", () => {
roomId,
getMxcAvatarUrl: jest.fn(),
isSpaceRoom: jest.fn(),
getType: jest.fn(),
getAvatarFallbackMember: jest.fn(),
} as unknown as Room;
dmRoomMap = {
@ -70,6 +71,7 @@ describe("avatarUrlForRoom", () => {
it("should return null for a space room", () => {
mocked(room.isSpaceRoom).mockReturnValue(true);
mocked(room.getType).mockReturnValue(RoomType.Space);
expect(avatarUrlForRoom(room, 128, 128)).toBeNull();
});

View file

@ -16,10 +16,15 @@ limitations under the License.
import { mocked } from 'jest-mock';
import { ConditionKind, PushRuleActionName, TweakName } from "matrix-js-sdk/src/@types/PushRules";
import { NotificationCountType, Room } from 'matrix-js-sdk/src/models/room';
import { stubClient } from "./test-utils";
import { mkEvent, stubClient } from "./test-utils";
import { MatrixClientPeg } from "../src/MatrixClientPeg";
import { getRoomNotifsState, RoomNotifState } from "../src/RoomNotifs";
import {
getRoomNotifsState,
RoomNotifState,
getUnreadNotificationCount,
} from "../src/RoomNotifs";
describe("RoomNotifs test", () => {
beforeEach(() => {
@ -83,4 +88,74 @@ describe("RoomNotifs test", () => {
});
expect(getRoomNotifsState("!roomId:server")).toBe(RoomNotifState.AllMessagesLoud);
});
describe("getUnreadNotificationCount", () => {
const ROOM_ID = "!roomId:example.org";
const THREAD_ID = "$threadId";
let cli;
let room: Room;
beforeEach(() => {
cli = MatrixClientPeg.get();
room = new Room(ROOM_ID, cli, cli.getUserId());
});
it("counts room notification type", () => {
expect(getUnreadNotificationCount(room, NotificationCountType.Total)).toBe(0);
expect(getUnreadNotificationCount(room, NotificationCountType.Highlight)).toBe(0);
});
it("counts notifications type", () => {
room.setUnreadNotificationCount(NotificationCountType.Total, 2);
room.setUnreadNotificationCount(NotificationCountType.Highlight, 1);
expect(getUnreadNotificationCount(room, NotificationCountType.Total)).toBe(2);
expect(getUnreadNotificationCount(room, NotificationCountType.Highlight)).toBe(1);
});
it("counts predecessor highlight", () => {
room.setUnreadNotificationCount(NotificationCountType.Total, 2);
room.setUnreadNotificationCount(NotificationCountType.Highlight, 1);
const OLD_ROOM_ID = "!oldRoomId:example.org";
const oldRoom = new Room(OLD_ROOM_ID, cli, cli.getUserId());
oldRoom.setUnreadNotificationCount(NotificationCountType.Total, 10);
oldRoom.setUnreadNotificationCount(NotificationCountType.Highlight, 6);
cli.getRoom.mockReset().mockReturnValue(oldRoom);
const predecessorEvent = mkEvent({
event: true,
type: "m.room.create",
room: ROOM_ID,
user: cli.getUserId(),
content: {
creator: cli.getUserId(),
room_version: "5",
predecessor: {
room_id: OLD_ROOM_ID,
event_id: "$someevent",
},
},
ts: Date.now(),
});
room.addLiveEvents([predecessorEvent]);
expect(getUnreadNotificationCount(room, NotificationCountType.Total)).toBe(8);
expect(getUnreadNotificationCount(room, NotificationCountType.Highlight)).toBe(7);
});
it("counts thread notification type", () => {
expect(getUnreadNotificationCount(room, NotificationCountType.Total, THREAD_ID)).toBe(0);
expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, THREAD_ID)).toBe(0);
});
it("counts notifications type", () => {
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total, 2);
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight, 1);
expect(getUnreadNotificationCount(room, NotificationCountType.Total, THREAD_ID)).toBe(2);
expect(getUnreadNotificationCount(room, NotificationCountType.Highlight, THREAD_ID)).toBe(1);
});
});
});

View file

@ -0,0 +1,105 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { VoiceRecording } from "../../src/audio/VoiceRecording";
/**
* The tests here are heavily using access to private props.
* While this is not so great, we can at lest test some behaviour easily this way.
*/
describe("VoiceRecording", () => {
let recording: VoiceRecording;
let recorderSecondsSpy: jest.SpyInstance;
const itShouldNotCallStop = () => {
it("should not call stop", () => {
expect(recording.stop).not.toHaveBeenCalled();
});
};
const simulateUpdate = (recorderSeconds: number) => {
beforeEach(() => {
recorderSecondsSpy.mockReturnValue(recorderSeconds);
// @ts-ignore
recording.processAudioUpdate(recorderSeconds);
});
};
beforeEach(() => {
recording = new VoiceRecording();
// @ts-ignore
recording.observable = {
update: jest.fn(),
};
jest.spyOn(recording, "stop").mockImplementation();
recorderSecondsSpy = jest.spyOn(recording, "recorderSeconds", "get");
});
afterEach(() => {
jest.resetAllMocks();
});
describe("when recording", () => {
beforeEach(() => {
// @ts-ignore
recording.recording = true;
});
describe("and there is an audio update and time left", () => {
simulateUpdate(42);
itShouldNotCallStop();
});
describe("and there is an audio update and time is up", () => {
// one second above the limit
simulateUpdate(901);
it("should call stop", () => {
expect(recording.stop).toHaveBeenCalled();
});
});
describe("and the max length limit has been disabled", () => {
beforeEach(() => {
recording.disableMaxLength();
});
describe("and there is an audio update and time left", () => {
simulateUpdate(42);
itShouldNotCallStop();
});
describe("and there is an audio update and time is up", () => {
// one second above the limit
simulateUpdate(901);
itShouldNotCallStop();
});
});
});
describe("when not recording", () => {
describe("and there is an audio update and time left", () => {
simulateUpdate(42);
itShouldNotCallStop();
});
describe("and there is an audio update and time is up", () => {
// one second above the limit
simulateUpdate(901);
itShouldNotCallStop();
});
});
});

View file

@ -0,0 +1,91 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/client";
import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Room } from "matrix-js-sdk/src/models/room";
import { getUnsentMessages } from "../../../src/components/structures/RoomStatusBar";
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
import { mkEvent, stubClient } from "../../test-utils/test-utils";
import { mkThread } from "../../test-utils/threads";
describe("RoomStatusBar", () => {
const ROOM_ID = "!roomId:example.org";
let room: Room;
let client: MatrixClient;
let event: MatrixEvent;
beforeEach(() => {
jest.clearAllMocks();
stubClient();
client = MatrixClientPeg.get();
room = new Room(ROOM_ID, client, client.getUserId(), {
pendingEventOrdering: PendingEventOrdering.Detached,
});
event = mkEvent({
event: true,
type: "m.room.message",
user: "@user1:server",
room: "!room1:server",
content: {},
});
event.status = EventStatus.NOT_SENT;
});
describe("getUnsentMessages", () => {
it("returns no unsent messages", () => {
expect(getUnsentMessages(room)).toHaveLength(0);
});
it("checks the event status", () => {
room.addPendingEvent(event, "123");
expect(getUnsentMessages(room)).toHaveLength(1);
event.status = EventStatus.SENT;
expect(getUnsentMessages(room)).toHaveLength(0);
});
it("only returns events related to a thread", () => {
room.addPendingEvent(event, "123");
const { rootEvent, events } = mkThread({
room,
client,
authorId: "@alice:example.org",
participantUserIds: ["@alice:example.org"],
length: 2,
});
rootEvent.status = EventStatus.NOT_SENT;
room.addPendingEvent(rootEvent, rootEvent.getId());
for (const event of events) {
event.status = EventStatus.NOT_SENT;
room.addPendingEvent(event, Date.now() + Math.random() + "");
}
const pendingEvents = getUnsentMessages(room, rootEvent.getId());
expect(pendingEvents[0].threadRootId).toBe(rootEvent.getId());
expect(pendingEvents[1].threadRootId).toBe(rootEvent.getId());
expect(pendingEvents[2].threadRootId).toBe(rootEvent.getId());
// Filters out the non thread events
expect(pendingEvents.every(ev => ev.getId() !== event.getId())).toBe(true);
});
});
});

View file

@ -15,8 +15,7 @@ limitations under the License.
*/
import React from 'react';
// eslint-disable-next-line deprecate/import
import { mount } from 'enzyme';
import { fireEvent, render } from "@testing-library/react";
import {
Beacon,
RoomMember,
@ -28,7 +27,6 @@ import { act } from 'react-dom/test-utils';
import BeaconListItem from '../../../../src/components/views/beacon/BeaconListItem';
import MatrixClientContext from '../../../../src/contexts/MatrixClientContext';
import {
findByTestId,
getMockClientWithEventEmitter,
makeBeaconEvent,
makeBeaconInfoEvent,
@ -76,11 +74,9 @@ describe('<BeaconListItem />', () => {
beacon: new Beacon(aliceBeaconEvent),
};
const getComponent = (props = {}) =>
mount(<BeaconListItem {...defaultProps} {...props} />, {
wrappingComponent: MatrixClientContext.Provider,
wrappingComponentProps: { value: mockClient },
});
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);
@ -104,71 +100,72 @@ describe('<BeaconListItem />', () => {
{ isLive: false },
);
const [beacon] = setupRoomWithBeacons([notLiveBeacon]);
const component = getComponent({ beacon });
expect(component.html()).toBeNull();
const { container } = getComponent({ beacon });
expect(container.innerHTML).toBeFalsy();
});
it('renders null when beacon has no location', () => {
const [beacon] = setupRoomWithBeacons([aliceBeaconEvent]);
const component = getComponent({ beacon });
expect(component.html()).toBeNull();
const { container } = getComponent({ beacon });
expect(container.innerHTML).toBeFalsy();
});
describe('when a beacon is live and has locations', () => {
it('renders beacon info', () => {
const [beacon] = setupRoomWithBeacons([alicePinBeaconEvent], [aliceLocation1]);
const component = getComponent({ beacon });
expect(component.html()).toMatchSnapshot();
const { asFragment } = getComponent({ beacon });
expect(asFragment()).toMatchSnapshot();
});
describe('non-self beacons', () => {
it('uses beacon description as beacon name', () => {
const [beacon] = setupRoomWithBeacons([alicePinBeaconEvent], [aliceLocation1]);
const component = getComponent({ beacon });
expect(component.find('BeaconStatus').props().label).toEqual("Alice's car");
const { container } = getComponent({ beacon });
expect(container.querySelector('.mx_BeaconStatus_label')).toHaveTextContent("Alice's car");
});
it('uses beacon owner mxid as beacon name for a beacon without description', () => {
const [beacon] = setupRoomWithBeacons([pinBeaconWithoutDescription], [aliceLocation1]);
const component = getComponent({ beacon });
expect(component.find('BeaconStatus').props().label).toEqual(aliceId);
const { container } = getComponent({ beacon });
expect(container.querySelector('.mx_BeaconStatus_label')).toHaveTextContent(aliceId);
});
it('renders location icon', () => {
const [beacon] = setupRoomWithBeacons([alicePinBeaconEvent], [aliceLocation1]);
const component = getComponent({ beacon });
expect(component.find('StyledLiveBeaconIcon').length).toBeTruthy();
const { container } = getComponent({ beacon });
expect(container.querySelector('.mx_StyledLiveBeaconIcon')).toBeTruthy();
});
});
describe('self locations', () => {
it('renders beacon owner avatar', () => {
const [beacon] = setupRoomWithBeacons([aliceBeaconEvent], [aliceLocation1]);
const component = getComponent({ beacon });
expect(component.find('MemberAvatar').length).toBeTruthy();
const { container } = getComponent({ beacon });
expect(container.querySelector('.mx_BaseAvatar')).toBeTruthy();
});
it('uses beacon owner name as beacon name', () => {
const [beacon] = setupRoomWithBeacons([aliceBeaconEvent], [aliceLocation1]);
const component = getComponent({ beacon });
expect(component.find('BeaconStatus').props().label).toEqual('Alice');
const { container } = getComponent({ beacon });
expect(container.querySelector('.mx_BeaconStatus_label')).toHaveTextContent("Alice");
});
});
describe('on location updates', () => {
it('updates last updated time on location updated', () => {
const [beacon] = setupRoomWithBeacons([aliceBeaconEvent], [aliceLocation2]);
const component = getComponent({ beacon });
const { container } = getComponent({ beacon });
expect(component.find('.mx_BeaconListItem_lastUpdated').text()).toEqual('Updated 9 minutes ago');
expect(container.querySelector('.mx_BeaconListItem_lastUpdated'))
.toHaveTextContent('Updated 9 minutes ago');
// update to a newer location
act(() => {
beacon.addLocations([aliceLocation1]);
component.setProps({});
});
expect(component.find('.mx_BeaconListItem_lastUpdated').text()).toEqual('Updated a few seconds ago');
expect(container.querySelector('.mx_BeaconListItem_lastUpdated'))
.toHaveTextContent('Updated a few seconds ago');
});
});
@ -176,23 +173,19 @@ describe('<BeaconListItem />', () => {
it('does not call onClick handler when clicking share button', () => {
const [beacon] = setupRoomWithBeacons([alicePinBeaconEvent], [aliceLocation1]);
const onClick = jest.fn();
const component = getComponent({ beacon, onClick });
const { getByTestId } = getComponent({ beacon, onClick });
act(() => {
findByTestId(component, 'open-location-in-osm').at(0).simulate('click');
});
fireEvent.click(getByTestId('open-location-in-osm'));
expect(onClick).not.toHaveBeenCalled();
});
it('calls onClick handler when clicking outside of share buttons', () => {
const [beacon] = setupRoomWithBeacons([alicePinBeaconEvent], [aliceLocation1]);
const onClick = jest.fn();
const component = getComponent({ beacon, onClick });
const { container } = getComponent({ beacon, onClick });
act(() => {
// click the beacon name
component.find('.mx_BeaconStatus_description').simulate('click');
});
// click the beacon name
fireEvent.click(container.querySelector(".mx_BeaconStatus_description"));
expect(onClick).toHaveBeenCalled();
});
});

View file

@ -16,8 +16,7 @@ limitations under the License.
import React from 'react';
import { mocked } from 'jest-mock';
// eslint-disable-next-line deprecate/import
import { mount } from 'enzyme';
import { fireEvent, render } from "@testing-library/react";
import { act } from 'react-dom/test-utils';
import { Beacon, BeaconIdentifier } from 'matrix-js-sdk/src/matrix';
@ -48,9 +47,7 @@ jest.mock('../../../../src/stores/OwnBeaconStore', () => {
);
describe('<LeftPanelLiveShareWarning />', () => {
const defaultProps = {};
const getComponent = (props = {}) =>
mount(<LeftPanelLiveShareWarning {...defaultProps} {...props} />);
const getComponent = (props = {}) => render(<LeftPanelLiveShareWarning {...props} />);
const roomId1 = '!room1:server';
const roomId2 = '!room2:server';
@ -85,8 +82,8 @@ describe('<LeftPanelLiveShareWarning />', () => {
));
it('renders nothing when user has no live beacons', () => {
const component = getComponent();
expect(component.html()).toBe(null);
const { container } = getComponent();
expect(container.innerHTML).toBeFalsy();
});
describe('when user has live location monitor', () => {
@ -110,17 +107,15 @@ describe('<LeftPanelLiveShareWarning />', () => {
});
it('renders correctly when not minimized', () => {
const component = getComponent();
expect(component).toMatchSnapshot();
const { asFragment } = getComponent();
expect(asFragment()).toMatchSnapshot();
});
it('goes to room of latest beacon when clicked', () => {
const component = getComponent();
const { container } = getComponent();
const dispatchSpy = jest.spyOn(dispatcher, 'dispatch');
act(() => {
component.simulate('click');
});
fireEvent.click(container.querySelector("[role=button]"));
expect(dispatchSpy).toHaveBeenCalledWith({
action: Action.ViewRoom,
@ -134,28 +129,26 @@ describe('<LeftPanelLiveShareWarning />', () => {
});
it('renders correctly when minimized', () => {
const component = getComponent({ isMinimized: true });
expect(component).toMatchSnapshot();
const { asFragment } = getComponent({ isMinimized: true });
expect(asFragment()).toMatchSnapshot();
});
it('renders location publish error', () => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue(
[beacon1.identifier],
);
const component = getComponent();
expect(component).toMatchSnapshot();
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],
);
const component = getComponent();
const { container } = getComponent();
const dispatchSpy = jest.spyOn(dispatcher, 'dispatch');
act(() => {
component.simulate('click');
});
fireEvent.click(container.querySelector("[role=button]"));
expect(dispatchSpy).toHaveBeenCalledWith({
action: Action.ViewRoom,
@ -172,9 +165,9 @@ describe('<LeftPanelLiveShareWarning />', () => {
mocked(OwnBeaconStore.instance).getLiveBeaconIdsWithLocationPublishError.mockReturnValue(
[beacon1.identifier],
);
const component = getComponent();
const { container, rerender } = getComponent();
// error mode
expect(component.find('.mx_LeftPanelLiveShareWarning').at(0).text()).toEqual(
expect(container.querySelector('.mx_LeftPanelLiveShareWarning').textContent).toEqual(
'An error occurred whilst sharing your live location',
);
@ -183,18 +176,18 @@ describe('<LeftPanelLiveShareWarning />', () => {
OwnBeaconStore.instance.emit(OwnBeaconStoreEvent.LocationPublishError, 'abc');
});
component.setProps({});
rerender(<LeftPanelLiveShareWarning />);
// default mode
expect(component.find('.mx_LeftPanelLiveShareWarning').at(0).text()).toEqual(
expect(container.querySelector('.mx_LeftPanelLiveShareWarning').textContent).toEqual(
'You are sharing your live location',
);
});
it('removes itself when user stops having live beacons', async () => {
const component = getComponent({ isMinimized: true });
const { container, rerender } = getComponent({ isMinimized: true });
// started out rendered
expect(component.html()).toBeTruthy();
expect(container.innerHTML).toBeTruthy();
act(() => {
mocked(OwnBeaconStore.instance).isMonitoringLiveLocation = false;
@ -202,9 +195,9 @@ describe('<LeftPanelLiveShareWarning />', () => {
});
await flushPromises();
component.setProps({});
rerender(<LeftPanelLiveShareWarning />);
expect(component.html()).toBe(null);
expect(container.innerHTML).toBeFalsy();
});
it('refreshes beacon liveness monitors when pagevisibilty changes to visible', () => {
@ -228,21 +221,21 @@ describe('<LeftPanelLiveShareWarning />', () => {
describe('stopping errors', () => {
it('renders stopping error', () => {
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error('error'));
const component = getComponent();
expect(component.text()).toEqual('An error occurred while stopping your live location');
const { container } = getComponent();
expect(container.textContent).toEqual('An error occurred while stopping your live location');
});
it('starts rendering stopping error on beaconUpdateError emit', () => {
const component = getComponent();
const { container } = getComponent();
// no error
expect(component.text()).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.emit(OwnBeaconStoreEvent.BeaconUpdateError, beacon2.identifier, true);
});
expect(component.text()).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', () => {
@ -250,8 +243,8 @@ describe('<LeftPanelLiveShareWarning />', () => {
[beacon1.identifier],
);
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error('error'));
const component = getComponent();
expect(component.text()).toEqual('An error occurred while stopping your live location');
const { container } = getComponent();
expect(container.textContent).toEqual('An error occurred while stopping your live location');
});
it('goes to room of latest beacon with stopping error when clicked', () => {
@ -259,12 +252,10 @@ describe('<LeftPanelLiveShareWarning />', () => {
[beacon1.identifier],
);
OwnBeaconStore.instance.beaconUpdateErrors.set(beacon2.identifier, new Error('error'));
const component = getComponent();
const { container } = getComponent();
const dispatchSpy = jest.spyOn(dispatcher, 'dispatch');
act(() => {
component.simulate('click');
});
fireEvent.click(container.querySelector("[role=button]"));
expect(dispatchSpy).toHaveBeenCalledWith({
action: Action.ViewRoom,

View file

@ -15,9 +15,7 @@ limitations under the License.
*/
import React from 'react';
// eslint-disable-next-line deprecate/import
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { fireEvent, render } from "@testing-library/react";
import ShareLatestLocation from '../../../../src/components/views/beacon/ShareLatestLocation';
import { copyPlaintext } from '../../../../src/utils/strings';
@ -34,26 +32,23 @@ describe('<ShareLatestLocation />', () => {
timestamp: 123,
},
};
const getComponent = (props = {}) =>
mount(<ShareLatestLocation {...defaultProps} {...props} />);
const getComponent = (props = {}) => render(<ShareLatestLocation {...defaultProps} {...props} />);
beforeEach(() => {
jest.clearAllMocks();
});
it('renders null when no location', () => {
const component = getComponent({ latestLocationState: undefined });
expect(component.html()).toBeNull();
const { container } = getComponent({ latestLocationState: undefined });
expect(container.innerHTML).toBeFalsy();
});
it('renders share buttons when there is a location', async () => {
const component = getComponent();
expect(component).toMatchSnapshot();
const { container, asFragment } = getComponent();
expect(asFragment()).toMatchSnapshot();
await act(async () => {
component.find('.mx_CopyableText_copyButton').at(0).simulate('click');
await flushPromises();
});
fireEvent.click(container.querySelector('.mx_CopyableText_copyButton'));
await flushPromises();
expect(copyPlaintext).toHaveBeenCalledWith('51,42');
});

View file

@ -15,18 +15,16 @@ limitations under the License.
*/
import React from 'react';
// eslint-disable-next-line deprecate/import
import { mount } from 'enzyme';
import { render } from "@testing-library/react";
import StyledLiveBeaconIcon from '../../../../src/components/views/beacon/StyledLiveBeaconIcon';
describe('<StyledLiveBeaconIcon />', () => {
const defaultProps = {};
const getComponent = (props = {}) =>
mount(<StyledLiveBeaconIcon {...defaultProps} {...props} />);
const getComponent = (props = {}) => render(<StyledLiveBeaconIcon {...defaultProps} {...props} />);
it('renders', () => {
const component = getComponent();
expect(component).toBeTruthy();
const { asFragment } = getComponent();
expect(asFragment()).toMatchSnapshot();
});
});

View file

@ -1,3 +1,68 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<BeaconListItem /> when a beacon is live and has locations renders beacon info 1`] = `"<li class=\\"mx_BeaconListItem\\"><div class=\\"mx_StyledLiveBeaconIcon mx_BeaconListItem_avatarIcon\\"></div><div class=\\"mx_BeaconListItem_info\\"><div class=\\"mx_BeaconStatus mx_BeaconStatus_Active mx_BeaconListItem_status\\"><div class=\\"mx_BeaconStatus_description\\"><span class=\\"mx_BeaconStatus_label\\">Alice's car</span><span class=\\"mx_BeaconStatus_expiryTime\\">Live until 16:04</span></div><div class=\\"mx_BeaconListItem_interactions\\"><div tabindex=\\"0\\"><a data-test-id=\\"open-location-in-osm\\" href=\\"https://www.openstreetmap.org/?mlat=51&amp;mlon=41#map=16/51/41\\" target=\\"_blank\\" rel=\\"noreferrer noopener\\"><div class=\\"mx_ShareLatestLocation_icon\\"></div></a></div><div class=\\"mx_CopyableText mx_ShareLatestLocation_copy\\"><div aria-label=\\"Copy\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_CopyableText_copyButton\\"></div></div></div></div><span class=\\"mx_BeaconListItem_lastUpdated\\">Updated a few seconds ago</span></div></li>"`;
exports[`<BeaconListItem /> when a beacon is live and has locations renders beacon info 1`] = `
<DocumentFragment>
<li
class="mx_BeaconListItem"
>
<div
class="mx_StyledLiveBeaconIcon mx_BeaconListItem_avatarIcon"
/>
<div
class="mx_BeaconListItem_info"
>
<div
class="mx_BeaconStatus mx_BeaconStatus_Active mx_BeaconListItem_status"
>
<div
class="mx_BeaconStatus_description"
>
<span
class="mx_BeaconStatus_label"
>
Alice's car
</span>
<span
class="mx_BeaconStatus_expiryTime"
>
Live until 16:04
</span>
</div>
<div
class="mx_BeaconListItem_interactions"
>
<div
tabindex="0"
>
<a
data-testid="open-location-in-osm"
href="https://www.openstreetmap.org/?mlat=51&mlon=41#map=16/51/41"
rel="noreferrer noopener"
target="_blank"
>
<div
class="mx_ShareLatestLocation_icon"
/>
</a>
</div>
<div
class="mx_CopyableText mx_ShareLatestLocation_copy"
>
<div
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
role="button"
tabindex="0"
/>
</div>
</div>
</div>
<span
class="mx_BeaconListItem_lastUpdated"
>
Updated a few seconds ago
</span>
</div>
</li>
</DocumentFragment>
`;

View file

@ -75,7 +75,7 @@ exports[`<DialogSidebar /> renders sidebar correctly with beacons 1`] = `
tabindex="0"
>
<a
data-test-id="open-location-in-osm"
data-testid="open-location-in-osm"
href="https://www.openstreetmap.org/?mlat=51&mlon=41#map=16/51/41"
rel="noreferrer noopener"
target="_blank"

View file

@ -1,76 +1,40 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<LeftPanelLiveShareWarning /> when user has live location monitor renders correctly when minimized 1`] = `
<LeftPanelLiveShareWarning
isMinimized={true}
>
<AccessibleButton
className="mx_LeftPanelLiveShareWarning mx_LeftPanelLiveShareWarning__minimized"
element="div"
onClick={[Function]}
<DocumentFragment>
<div
class="mx_AccessibleButton mx_LeftPanelLiveShareWarning mx_LeftPanelLiveShareWarning__minimized"
role="button"
tabIndex={0}
tabindex="0"
title="You are sharing your live location"
>
<div
className="mx_AccessibleButton mx_LeftPanelLiveShareWarning mx_LeftPanelLiveShareWarning__minimized"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
role="button"
tabIndex={0}
title="You are sharing your live location"
>
<div
height={10}
/>
</div>
</AccessibleButton>
</LeftPanelLiveShareWarning>
height="10"
/>
</div>
</DocumentFragment>
`;
exports[`<LeftPanelLiveShareWarning /> when user has live location monitor renders correctly when not minimized 1`] = `
<LeftPanelLiveShareWarning>
<AccessibleButton
className="mx_LeftPanelLiveShareWarning"
element="div"
onClick={[Function]}
<DocumentFragment>
<div
class="mx_AccessibleButton mx_LeftPanelLiveShareWarning"
role="button"
tabIndex={0}
tabindex="0"
>
<div
className="mx_AccessibleButton mx_LeftPanelLiveShareWarning"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
role="button"
tabIndex={0}
>
You are sharing your live location
</div>
</AccessibleButton>
</LeftPanelLiveShareWarning>
You are sharing your live location
</div>
</DocumentFragment>
`;
exports[`<LeftPanelLiveShareWarning /> when user has live location monitor renders location publish error 1`] = `
<LeftPanelLiveShareWarning>
<AccessibleButton
className="mx_LeftPanelLiveShareWarning mx_LeftPanelLiveShareWarning__error"
element="div"
onClick={[Function]}
<DocumentFragment>
<div
class="mx_AccessibleButton mx_LeftPanelLiveShareWarning mx_LeftPanelLiveShareWarning__error"
role="button"
tabIndex={0}
tabindex="0"
>
<div
className="mx_AccessibleButton mx_LeftPanelLiveShareWarning mx_LeftPanelLiveShareWarning__error"
onClick={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
role="button"
tabIndex={0}
>
An error occurred whilst sharing your live location
</div>
</AccessibleButton>
</LeftPanelLiveShareWarning>
An error occurred whilst sharing your live location
</div>
</DocumentFragment>
`;

View file

@ -1,79 +1,30 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<ShareLatestLocation /> renders share buttons when there is a location 1`] = `
<ShareLatestLocation
latestLocationState={
Object {
"timestamp": 123,
"uri": "geo:51,42;u=35",
}
}
>
<TooltipTarget
label="Open in OpenStreetMap"
<DocumentFragment>
<div
tabindex="0"
>
<a
data-testid="open-location-in-osm"
href="https://www.openstreetmap.org/?mlat=51&mlon=42#map=16/51/42"
rel="noreferrer noopener"
target="_blank"
>
<div
class="mx_ShareLatestLocation_icon"
/>
</a>
</div>
<div
class="mx_CopyableText mx_ShareLatestLocation_copy"
>
<div
onBlur={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseMove={[Function]}
onMouseOver={[Function]}
tabIndex={0}
>
<a
data-test-id="open-location-in-osm"
href="https://www.openstreetmap.org/?mlat=51&mlon=42#map=16/51/42"
rel="noreferrer noopener"
target="_blank"
>
<div
className="mx_ShareLatestLocation_icon"
/>
</a>
</div>
</TooltipTarget>
<CopyableText
border={false}
className="mx_ShareLatestLocation_copy"
getTextToCopy={[Function]}
>
<div
className="mx_CopyableText mx_ShareLatestLocation_copy"
>
<AccessibleTooltipButton
className="mx_CopyableText_copyButton"
onClick={[Function]}
onHideTooltip={[Function]}
title="Copy"
>
<AccessibleButton
aria-label="Copy"
className="mx_CopyableText_copyButton"
element="div"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
role="button"
tabIndex={0}
>
<div
aria-label="Copy"
className="mx_AccessibleButton mx_CopyableText_copyButton"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onKeyDown={[Function]}
onKeyUp={[Function]}
onMouseLeave={[Function]}
onMouseOver={[Function]}
role="button"
tabIndex={0}
/>
</AccessibleButton>
</AccessibleTooltipButton>
</div>
</CopyableText>
</ShareLatestLocation>
aria-label="Copy"
class="mx_AccessibleButton mx_CopyableText_copyButton"
role="button"
tabindex="0"
/>
</div>
</DocumentFragment>
`;

View file

@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<StyledLiveBeaconIcon /> renders 1`] = `
<DocumentFragment>
<div
class="mx_StyledLiveBeaconIcon"
/>
</DocumentFragment>
`;

View file

@ -16,6 +16,7 @@ limitations under the License.
import React from "react";
import { render, screen } from "@testing-library/react";
import { RoomType } from "matrix-js-sdk/src/@types/event";
import InviteDialog from "../../../../src/components/views/dialogs/InviteDialog";
import { KIND_INVITE } from "../../../../src/components/views/dialogs/InviteDialogTypes";
@ -74,6 +75,7 @@ describe("InviteDialog", () => {
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

View file

@ -15,9 +15,7 @@ limitations under the License.
*/
import React from 'react';
// eslint-disable-next-line deprecate/import
import { mount } from 'enzyme';
import { act } from "react-dom/test-utils";
import { fireEvent, render } from "@testing-library/react";
import StyledRadioGroup from "../../../../src/components/views/elements/StyledRadioGroup";
@ -44,16 +42,16 @@ describe('<StyledRadioGroup />', () => {
definitions: defaultDefinitions,
onChange: jest.fn(),
};
const getComponent = (props = {}) => mount(<StyledRadioGroup {...defaultProps} {...props} />);
const getComponent = (props = {}) => render(<StyledRadioGroup {...defaultProps} {...props} />);
const getInputByValue = (component, value) => component.find(`input[value="${value}"]`);
const getCheckedInput = component => component.find('input[checked=true]');
const getInputByValue = (component, value) => component.container.querySelector(`input[value="${value}"]`);
const getCheckedInput = component => component.container.querySelector('input[checked]');
it('renders radios correctly when no value is provided', () => {
const component = getComponent();
expect(component).toMatchSnapshot();
expect(getCheckedInput(component).length).toBeFalsy();
expect(component.asFragment()).toMatchSnapshot();
expect(getCheckedInput(component)).toBeFalsy();
});
it('selects correct button when value is provided', () => {
@ -61,7 +59,7 @@ describe('<StyledRadioGroup />', () => {
value: optionC.value,
});
expect(getCheckedInput(component).at(0).props().value).toEqual(optionC.value);
expect(getCheckedInput(component).value).toEqual(optionC.value);
});
it('selects correct buttons when definitions have checked prop', () => {
@ -74,10 +72,10 @@ describe('<StyledRadioGroup />', () => {
value: optionC.value, definitions,
});
expect(getInputByValue(component, optionA.value).props().checked).toBeTruthy();
expect(getInputByValue(component, optionB.value).props().checked).toBeFalsy();
expect(getInputByValue(component, optionA.value)).toBeChecked();
expect(getInputByValue(component, optionB.value)).not.toBeChecked();
// optionC.checked = false overrides value matching
expect(getInputByValue(component, optionC.value).props().checked).toBeFalsy();
expect(getInputByValue(component, optionC.value)).not.toBeChecked();
});
it('disables individual buttons based on definition.disabled', () => {
@ -87,16 +85,16 @@ describe('<StyledRadioGroup />', () => {
{ ...optionC, disabled: true },
];
const component = getComponent({ definitions });
expect(getInputByValue(component, optionA.value).props().disabled).toBeFalsy();
expect(getInputByValue(component, optionB.value).props().disabled).toBeTruthy();
expect(getInputByValue(component, optionC.value).props().disabled).toBeTruthy();
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', () => {
const component = getComponent({ disabled: true });
expect(getInputByValue(component, optionA.value).props().disabled).toBeTruthy();
expect(getInputByValue(component, optionB.value).props().disabled).toBeTruthy();
expect(getInputByValue(component, optionC.value).props().disabled).toBeTruthy();
expect(getInputByValue(component, optionA.value)).toBeDisabled();
expect(getInputByValue(component, optionB.value)).toBeDisabled();
expect(getInputByValue(component, optionC.value)).toBeDisabled();
});
it('calls onChange on click', () => {
@ -106,9 +104,7 @@ describe('<StyledRadioGroup />', () => {
onChange,
});
act(() => {
getInputByValue(component, optionB.value).simulate('change');
});
fireEvent.click(getInputByValue(component, optionB.value));
expect(onChange).toHaveBeenCalledWith(optionB.value);
});

View file

@ -1,152 +1,83 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<StyledRadioGroup /> renders radios correctly when no value is provided 1`] = `
<StyledRadioGroup
className="test-class"
definitions={
Array [
Object {
"className": "a-class",
"description": "anteater description",
"label": <span>
Anteater label
</span>,
"value": "Anteater",
},
Object {
"label": <span>
Badger label
</span>,
"value": "Badger",
},
Object {
"description": <span>
Canary description
</span>,
"label": <span>
Canary label
</span>,
"value": "Canary",
},
]
}
name="test"
onChange={[MockFunction]}
>
<StyledRadioButton
aria-describedby="test-Anteater-description"
checked={false}
childrenInLabel={true}
className="test-class a-class"
id="test-Anteater"
name="test"
onChange={[Function]}
value="Anteater"
<DocumentFragment>
<label
class="mx_StyledRadioButton test-class a-class mx_StyledRadioButton_enabled"
>
<label
className="mx_StyledRadioButton test-class a-class mx_StyledRadioButton_enabled"
<input
aria-describedby="test-Anteater-description"
id="test-Anteater"
name="test"
type="radio"
value="Anteater"
/>
<div>
<div />
</div>
<div
class="mx_StyledRadioButton_content"
>
<input
aria-describedby="test-Anteater-description"
checked={false}
id="test-Anteater"
name="test"
onChange={[Function]}
type="radio"
value="Anteater"
/>
<div>
<div />
</div>
<div
className="mx_StyledRadioButton_content"
>
<span>
Anteater label
</span>
</div>
<div
className="mx_StyledRadioButton_spacer"
/>
</label>
</StyledRadioButton>
<span>
Anteater label
</span>
</div>
<div
class="mx_StyledRadioButton_spacer"
/>
</label>
<span
id="test-Anteater-description"
>
anteater description
</span>
<StyledRadioButton
checked={false}
childrenInLabel={true}
className="test-class"
id="test-Badger"
name="test"
onChange={[Function]}
value="Badger"
<label
class="mx_StyledRadioButton test-class mx_StyledRadioButton_enabled"
>
<label
className="mx_StyledRadioButton test-class mx_StyledRadioButton_enabled"
<input
id="test-Badger"
name="test"
type="radio"
value="Badger"
/>
<div>
<div />
</div>
<div
class="mx_StyledRadioButton_content"
>
<input
checked={false}
id="test-Badger"
name="test"
onChange={[Function]}
type="radio"
value="Badger"
/>
<div>
<div />
</div>
<div
className="mx_StyledRadioButton_content"
>
<span>
Badger label
</span>
</div>
<div
className="mx_StyledRadioButton_spacer"
/>
</label>
</StyledRadioButton>
<StyledRadioButton
aria-describedby="test-Canary-description"
checked={false}
childrenInLabel={true}
className="test-class"
id="test-Canary"
name="test"
onChange={[Function]}
value="Canary"
<span>
Badger label
</span>
</div>
<div
class="mx_StyledRadioButton_spacer"
/>
</label>
<label
class="mx_StyledRadioButton test-class mx_StyledRadioButton_enabled"
>
<label
className="mx_StyledRadioButton test-class mx_StyledRadioButton_enabled"
<input
aria-describedby="test-Canary-description"
id="test-Canary"
name="test"
type="radio"
value="Canary"
/>
<div>
<div />
</div>
<div
class="mx_StyledRadioButton_content"
>
<input
aria-describedby="test-Canary-description"
checked={false}
id="test-Canary"
name="test"
onChange={[Function]}
type="radio"
value="Canary"
/>
<div>
<div />
</div>
<div
className="mx_StyledRadioButton_content"
>
<span>
Canary label
</span>
</div>
<div
className="mx_StyledRadioButton_spacer"
/>
</label>
</StyledRadioButton>
<span>
Canary label
</span>
</div>
<div
class="mx_StyledRadioButton_spacer"
/>
</label>
<span
id="test-Canary-description"
>
@ -154,5 +85,5 @@ exports[`<StyledRadioGroup /> renders radios correctly when no value is provided
Canary description
</span>
</span>
</StyledRadioGroup>
</DocumentFragment>
`;

View file

@ -0,0 +1,97 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { render } from "@testing-library/react";
import { MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/client";
import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
import React from "react";
import RoomHeaderButtons from "../../../../src/components/views/right_panel/RoomHeaderButtons";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import SettingsStore from "../../../../src/settings/SettingsStore";
import { stubClient } from "../../../test-utils";
describe("RoomHeaderButtons-test.tsx", function() {
const ROOM_ID = "!roomId:example.org";
let room: Room;
let client: MatrixClient;
beforeEach(() => {
jest.clearAllMocks();
stubClient();
client = MatrixClientPeg.get();
room = new Room(ROOM_ID, client, client.getUserId(), {
pendingEventOrdering: PendingEventOrdering.Detached,
});
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
if (name === "feature_thread") return true;
});
});
function getComponent(room: Room) {
return render(<RoomHeaderButtons
room={room}
excludedRightPanelPhaseButtons={[]}
/>);
}
function getThreadButton(container) {
return container.querySelector(".mx_RightPanel_threadsButton");
}
function isIndicatorOfType(container, type: "red" | "gray") {
return container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")
.className
.includes(type);
}
it("shows the thread button", () => {
const { container } = getComponent(room);
expect(getThreadButton(container)).not.toBeNull();
});
it("hides the thread button", () => {
jest.spyOn(SettingsStore, "getValue").mockReset().mockReturnValue(false);
const { container } = getComponent(room);
expect(getThreadButton(container)).toBeNull();
});
it("room wide notification does not change the thread button", () => {
room.setUnreadNotificationCount(NotificationCountType.Highlight, 1);
room.setUnreadNotificationCount(NotificationCountType.Total, 1);
const { container } = getComponent(room);
expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull();
});
it("room wide notification does not change the thread button", () => {
const { container } = getComponent(room);
room.setThreadUnreadNotificationCount("$123", NotificationCountType.Total, 1);
expect(isIndicatorOfType(container, "gray")).toBe(true);
room.setThreadUnreadNotificationCount("$123", NotificationCountType.Highlight, 1);
expect(isIndicatorOfType(container, "red")).toBe(true);
room.setThreadUnreadNotificationCount("$123", NotificationCountType.Total, 0);
room.setThreadUnreadNotificationCount("$123", NotificationCountType.Highlight, 0);
expect(container.querySelector(".mx_RightPanel_threadsButton .mx_Indicator")).toBeNull();
});
});

View file

@ -140,6 +140,7 @@ describe('<UserInfo />', () => {
describe('with a room', () => {
const room = {
roomId: '!fkfk',
getType: jest.fn().mockReturnValue(undefined),
isSpaceRoom: jest.fn().mockReturnValue(false),
getMember: jest.fn().mockReturnValue(undefined),
getMxcAvatarUrl: jest.fn().mockReturnValue('mock-avatar-url'),

View file

@ -0,0 +1,112 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { act, render } from "@testing-library/react";
import { MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/client";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
import React from "react";
import EventTile, { EventTileProps } from "../../../../src/components/views/rooms/EventTile";
import RoomContext, { TimelineRenderingType } from "../../../../src/contexts/RoomContext";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import { getRoomContext, mkMessage, stubClient } from "../../../test-utils";
import { mkThread } from "../../../test-utils/threads";
describe("EventTile", () => {
const ROOM_ID = "!roomId:example.org";
let mxEvent: MatrixEvent;
let room: Room;
let client: MatrixClient;
// let changeEvent: (event: MatrixEvent) => void;
function TestEventTile(props: Partial<EventTileProps>) {
// const [event] = useState(mxEvent);
// Give a way for a test to update the event prop.
// changeEvent = setEvent;
return <EventTile
mxEvent={mxEvent}
{...props}
/>;
}
function getComponent(
overrides: Partial<EventTileProps> = {},
renderingType: TimelineRenderingType = TimelineRenderingType.Room,
) {
const context = getRoomContext(room, {
timelineRenderingType: renderingType,
});
return render(
<RoomContext.Provider value={context}>
<TestEventTile {...overrides} />
</RoomContext.Provider>,
);
}
beforeEach(() => {
jest.clearAllMocks();
stubClient();
client = MatrixClientPeg.get();
room = new Room(ROOM_ID, client, client.getUserId(), {
pendingEventOrdering: PendingEventOrdering.Detached,
});
jest.spyOn(client, "getRoom").mockReturnValue(room);
mxEvent = mkMessage({
room: room.roomId,
user: "@alice:example.org",
msg: "Hello world!",
event: true,
});
});
describe("EventTile renderingType: ThreadsList", () => {
beforeEach(() => {
const { rootEvent } = mkThread({
room,
client,
authorId: "@alice:example.org",
participantUserIds: ["@alice:example.org"],
});
mxEvent = rootEvent;
});
it("shows an unread notification bage", () => {
const { container } = getComponent({}, TimelineRenderingType.ThreadsList);
expect(container.getElementsByClassName("mx_NotificationBadge")).toHaveLength(0);
act(() => {
room.setThreadUnreadNotificationCount(mxEvent.getId(), NotificationCountType.Total, 3);
});
expect(container.getElementsByClassName("mx_NotificationBadge")).toHaveLength(1);
expect(container.getElementsByClassName("mx_NotificationBadge_highlighted")).toHaveLength(0);
act(() => {
room.setThreadUnreadNotificationCount(mxEvent.getId(), NotificationCountType.Highlight, 1);
});
expect(container.getElementsByClassName("mx_NotificationBadge")).toHaveLength(1);
expect(container.getElementsByClassName("mx_NotificationBadge_highlighted")).toHaveLength(1);
});
});
});

View file

@ -0,0 +1,49 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { fireEvent, render } from "@testing-library/react";
import React from "react";
import {
StatelessNotificationBadge,
} from "../../../../../src/components/views/rooms/NotificationBadge/StatelessNotificationBadge";
import { NotificationColor } from "../../../../../src/stores/notifications/NotificationColor";
describe("NotificationBadge", () => {
describe("StatelessNotificationBadge", () => {
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}
/>);
fireEvent.click(container.firstChild);
expect(cb).toHaveBeenCalledTimes(1);
fireEvent.mouseEnter(container.firstChild);
expect(cb).toHaveBeenCalledTimes(2);
fireEvent.mouseLeave(container.firstChild);
expect(cb).toHaveBeenCalledTimes(3);
});
});
});

View file

@ -0,0 +1,132 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import "jest-mock";
import { screen, act, render } from "@testing-library/react";
import { MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/client";
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 { 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),
getRoomNotifsState: jest.fn(),
}));
const ROOM_ID = "!roomId:example.org";
let THREAD_ID;
describe("UnreadNotificationBadge", () => {
let mockClient: MatrixClient;
let room: Room;
function getComponent(threadId?: string) {
return <UnreadNotificationBadge room={room} threadId={threadId} />;
}
beforeEach(() => {
jest.clearAllMocks();
stubClient();
mockClient = mocked(MatrixClientPeg.get());
room = new Room(ROOM_ID, mockClient, mockClient.getUserId() ?? "", {
pendingEventOrdering: PendingEventOrdering.Detached,
});
room.setUnreadNotificationCount(NotificationCountType.Total, 1);
room.setUnreadNotificationCount(NotificationCountType.Highlight, 0);
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total, 1);
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight, 0);
jest.spyOn(RoomNotifs, "getRoomNotifsState").mockReturnValue(RoomNotifs.RoomNotifState.AllMessages);
});
it("renders unread notification badge", () => {
const { container } = render(getComponent());
expect(container.querySelector(".mx_NotificationBadge_visible")).toBeTruthy();
expect(container.querySelector(".mx_NotificationBadge_highlighted")).toBeFalsy();
act(() => {
room.setUnreadNotificationCount(NotificationCountType.Highlight, 1);
});
expect(container.querySelector(".mx_NotificationBadge_highlighted")).toBeTruthy();
});
it("renders unread thread notification badge", () => {
const { container } = render(getComponent(THREAD_ID));
expect(container.querySelector(".mx_NotificationBadge_visible")).toBeTruthy();
expect(container.querySelector(".mx_NotificationBadge_highlighted")).toBeFalsy();
act(() => {
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight, 1);
});
expect(container.querySelector(".mx_NotificationBadge_highlighted")).toBeTruthy();
});
it("hides unread notification badge", () => {
act(() => {
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Total, 0);
room.setThreadUnreadNotificationCount(THREAD_ID, NotificationCountType.Highlight, 0);
const { container } = render(getComponent(THREAD_ID));
expect(container.querySelector(".mx_NotificationBadge_visible")).toBeFalsy();
});
});
it("adds a warning for unsent messages", () => {
const evt = mkMessage({
room: room.roomId,
user: "@alice:example.org",
msg: "Hello world!",
event: true,
});
evt.status = EventStatus.NOT_SENT;
room.addPendingEvent(evt, "123");
render(getComponent());
expect(screen.queryByText("!")).not.toBeNull();
});
it("adds a warning for invites", () => {
jest.spyOn(room, "getMyMembership").mockReturnValue("invite");
render(getComponent());
expect(screen.queryByText("!")).not.toBeNull();
});
it("hides counter for muted rooms", () => {
jest.spyOn(RoomNotifs, "getRoomNotifsState")
.mockReset()
.mockReturnValue(RoomNotifs.RoomNotifState.Mute);
const { container } = render(getComponent());
expect(container.querySelector(".mx_NotificationBadge")).toBeNull();
});
});

View file

@ -15,18 +15,14 @@ limitations under the License.
*/
import React from 'react';
import {
renderIntoDocument,
Simulate,
findRenderedDOMComponentWithClass,
act,
} from 'react-dom/test-utils';
import { render, fireEvent, RenderResult, waitFor } from "@testing-library/react";
import { Room, RoomMember, MatrixError, IContent } from 'matrix-js-sdk/src/matrix';
import { stubClient } from '../../../test-utils';
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
import DMRoomMap from '../../../../src/utils/DMRoomMap';
import RoomPreviewBar from '../../../../src/components/views/rooms/RoomPreviewBar';
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
jest.mock('../../../../src/IdentityAuthClient', () => {
return jest.fn().mockImplementation(() => {
@ -79,19 +75,18 @@ describe('<RoomPreviewBar />', () => {
const defaultProps = {
room: createRoom(roomId, userId),
};
const wrapper = renderIntoDocument<React.Component>(
<RoomPreviewBar {...defaultProps} {...props} />,
) as React.Component;
return findRenderedDOMComponentWithClass(wrapper, 'mx_RoomPreviewBar') as HTMLDivElement;
return render(<RoomPreviewBar {...defaultProps} {...props} />);
};
const isSpinnerRendered = (element: Element) => !!element.querySelector('.mx_Spinner');
const getMessage = (element: Element) => element.querySelector<HTMLDivElement>('.mx_RoomPreviewBar_message');
const getActions = (element: Element) => element.querySelector<HTMLDivElement>('.mx_RoomPreviewBar_actions');
const getPrimaryActionButton = (element: Element) =>
getActions(element).querySelector('.mx_AccessibleButton_kind_primary');
const getSecondaryActionButton = (element: Element) =>
getActions(element).querySelector('.mx_AccessibleButton_kind_secondary');
const isSpinnerRendered = (wrapper: RenderResult) => !!wrapper.container.querySelector('.mx_Spinner');
const getMessage = (wrapper: RenderResult) =>
wrapper.container.querySelector<HTMLDivElement>('.mx_RoomPreviewBar_message');
const getActions = (wrapper: RenderResult) =>
wrapper.container.querySelector<HTMLDivElement>('.mx_RoomPreviewBar_actions');
const getPrimaryActionButton = (wrapper: RenderResult) =>
getActions(wrapper).querySelector('.mx_AccessibleButton_kind_primary');
const getSecondaryActionButton = (wrapper: RenderResult) =>
getActions(wrapper).querySelector('.mx_AccessibleButton_kind_secondary');
beforeEach(() => {
stubClient();
@ -128,6 +123,36 @@ describe('<RoomPreviewBar />', () => {
expect(getMessage(component).textContent).toEqual('Join the conversation with an account');
});
it("should send room oob data to start login", async () => {
MatrixClientPeg.get().isGuest = jest.fn().mockReturnValue(true);
const component = getComponent({
oobData: {
name: "Room Name",
avatarUrl: "mxc://foo/bar",
inviterName: "Charlie",
},
});
const dispatcherSpy = jest.fn();
const dispatcherRef = defaultDispatcher.register(dispatcherSpy);
expect(getMessage(component).textContent).toEqual('Join the conversation with an account');
fireEvent.click(getPrimaryActionButton(component));
await waitFor(() => expect(dispatcherSpy).toHaveBeenCalledWith(expect.objectContaining({
screenAfterLogin: {
screen: 'room',
params: expect.objectContaining({
room_name: "Room Name",
room_avatar_url: "mxc://foo/bar",
inviter_name: "Charlie",
}),
},
})));
defaultDispatcher.unregister(dispatcherRef);
});
it('renders kicked message', () => {
const room = createRoom(roomId, otherUserId);
jest.spyOn(room, 'getMember').mockReturnValue(makeMockRoomMember({ isKicked: true }));
@ -233,18 +258,14 @@ describe('<RoomPreviewBar />', () => {
it('joins room on primary button click', () => {
const component = getComponent({ inviterName, room, onJoinClick, onRejectClick });
act(() => {
Simulate.click(getPrimaryActionButton(component));
});
fireEvent.click(getPrimaryActionButton(component));
expect(onJoinClick).toHaveBeenCalled();
});
it('rejects invite on secondary button click', () => {
const component = getComponent({ inviterName, room, onJoinClick, onRejectClick });
act(() => {
Simulate.click(getSecondaryActionButton(component));
});
fireEvent.click(getSecondaryActionButton(component));
expect(onRejectClick).toHaveBeenCalled();
});
@ -296,9 +317,7 @@ describe('<RoomPreviewBar />', () => {
await new Promise(setImmediate);
expect(getPrimaryActionButton(component)).toBeTruthy();
expect(getSecondaryActionButton(component)).toBeFalsy();
act(() => {
Simulate.click(getPrimaryActionButton(component));
});
fireEvent.click(getPrimaryActionButton(component));
expect(onJoinClick).toHaveBeenCalled();
};

View file

@ -15,8 +15,7 @@ limitations under the License.
*/
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount } from "enzyme";
import { render } from "@testing-library/react";
import { TextInputField } from "@matrix-org/react-sdk-module-api/lib/components/TextInputField";
import { Spinner as ModuleSpinner } from "@matrix-org/react-sdk-module-api/lib/components/Spinner";
@ -31,12 +30,12 @@ describe("Module Components", () => {
// ModuleRunner import to do its job (as per documentation in ModuleComponents).
it("should override the factory for a TextInputField", () => {
const component = mount(<TextInputField label="My Label" value="My Value" onChange={() => {}} />);
expect(component).toMatchSnapshot();
const { asFragment } = render(<TextInputField label="My Label" value="My Value" onChange={() => {}} />);
expect(asFragment()).toMatchSnapshot();
});
it("should override the factory for a ModuleSpinner", () => {
const component = mount(<ModuleSpinner />);
expect(component).toMatchSnapshot();
const { asFragment } = render(<ModuleSpinner />);
expect(asFragment()).toMatchSnapshot();
});
});

View file

@ -1,68 +1,39 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Module Components should override the factory for a ModuleSpinner 1`] = `
<Spinner>
<Spinner
h={32}
w={32}
<DocumentFragment>
<div
class="mx_Spinner"
>
<div
className="mx_Spinner"
>
<div
aria-label="Loading..."
className="mx_Spinner_icon"
data-testid="spinner"
role="progressbar"
style={
Object {
"height": 32,
"width": 32,
}
}
/>
</div>
</Spinner>
</Spinner>
aria-label="Loading..."
class="mx_Spinner_icon"
data-testid="spinner"
role="progressbar"
style="width: 32px; height: 32px;"
/>
</div>
</DocumentFragment>
`;
exports[`Module Components should override the factory for a TextInputField 1`] = `
<TextInputField
label="My Label"
onChange={[Function]}
value="My Value"
>
<Field
autoComplete="off"
element="input"
label="My Label"
onChange={[Function]}
type="text"
validateOnBlur={true}
validateOnChange={true}
validateOnFocus={true}
value="My Value"
<DocumentFragment>
<div
class="mx_Field mx_Field_input"
>
<div
className="mx_Field mx_Field_input"
<input
autocomplete="off"
id="mx_Field_1"
label="My Label"
placeholder="My Label"
type="text"
value="My Value"
/>
<label
for="mx_Field_1"
>
<input
autoComplete="off"
id="mx_Field_1"
label="My Label"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
placeholder="My Label"
type="text"
value="My Value"
/>
<label
htmlFor="mx_Field_1"
>
My Label
</label>
</div>
</Field>
</TextInputField>
My Label
</label>
</div>
</DocumentFragment>
`;

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixEventEvent, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { MatrixEventEvent, MatrixEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
import { stubClient } from "../../test-utils";
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
@ -24,12 +24,16 @@ import * as testUtils from "../../test-utils";
import { NotificationStateEvents } from "../../../src/stores/notifications/NotificationState";
describe("RoomNotificationState", () => {
stubClient();
const client = MatrixClientPeg.get();
let testRoom: Room;
let client: MatrixClient;
beforeEach(() => {
stubClient();
client = MatrixClientPeg.get();
testRoom = testUtils.mkStubRoom("$aroomid", "Test room", client);
});
it("Updates on event decryption", () => {
const testRoom = testUtils.mkStubRoom("$aroomid", "Test room", client);
const roomNotifState = new RoomNotificationState(testRoom as any as Room);
const listener = jest.fn();
roomNotifState.addListener(NotificationStateEvents.Update, listener);
@ -40,4 +44,9 @@ describe("RoomNotificationState", () => {
client.emit(MatrixEventEvent.Decrypted, testEvent);
expect(listener).toHaveBeenCalled();
});
it("removes listeners", () => {
const roomNotifState = new RoomNotificationState(testRoom as any as Room);
expect(() => roomNotifState.destroy()).not.toThrow();
});
});

View file

@ -0,0 +1,60 @@
/*
Copyright 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { PendingEventOrdering } from "matrix-js-sdk/src/client";
import { Feature, ServerSupport } from "matrix-js-sdk/src/feature";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
import { RoomNotificationStateStore } from "../../../src/stores/notifications/RoomNotificationStateStore";
import { stubClient } from "../../test-utils";
describe("RoomNotificationStateStore", () => {
const ROOM_ID = "!roomId:example.org";
let room;
let client;
beforeEach(() => {
stubClient();
client = MatrixClientPeg.get();
room = new Room(ROOM_ID, client, client.getUserId(), {
pendingEventOrdering: PendingEventOrdering.Detached,
});
});
it("does not use legacy thread notification store", () => {
client.canSupport.set(Feature.ThreadUnreadNotifications, ServerSupport.Stable);
expect(RoomNotificationStateStore.instance.getThreadsRoomState(room)).toBeNull();
});
it("use legacy thread notification store", () => {
client.canSupport.set(Feature.ThreadUnreadNotifications, ServerSupport.Unsupported);
expect(RoomNotificationStateStore.instance.getThreadsRoomState(room)).not.toBeNull();
});
it("does not use legacy thread notification store", () => {
client.canSupport.set(Feature.ThreadUnreadNotifications, ServerSupport.Stable);
RoomNotificationStateStore.instance.getRoomState(room);
expect(RoomNotificationStateStore.instance.getThreadsRoomState(room)).toBeNull();
});
it("use legacy thread notification store", () => {
client.canSupport.set(Feature.ThreadUnreadNotifications, ServerSupport.Unsupported);
RoomNotificationStateStore.instance.getRoomState(room);
expect(RoomNotificationStateStore.instance.getThreadsRoomState(room)).not.toBeNull();
});
});

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import { mocked } from "jest-mock";
import { Room } from "matrix-js-sdk/src/matrix";
import { Room, RoomType } from "matrix-js-sdk/src/matrix";
import { VisibilityProvider } from "../../../../src/stores/room-list/filters/VisibilityProvider";
import LegacyCallHandler from "../../../../src/LegacyCallHandler";
@ -43,6 +43,7 @@ jest.mock("../../../../src/customisations/RoomList", () => ({
const createRoom = (isSpaceRoom = false): Room => {
return {
isSpaceRoom: () => isSpaceRoom,
getType: () => isSpaceRoom ? RoomType.Space : undefined,
} as unknown as Room;
};

View file

@ -31,6 +31,7 @@ import {
IEventRelation,
IUnsigned,
IPusher,
RoomType,
} from 'matrix-js-sdk/src/matrix';
import { normalize } from "matrix-js-sdk/src/utils";
import { ReEmitter } from "matrix-js-sdk/src/ReEmitter";
@ -448,6 +449,7 @@ export function mkStubRoom(roomId: string = null, name: string, client: MatrixCl
getAvatarUrl: () => 'mxc://avatar.url/room.png',
getMxcAvatarUrl: () => 'mxc://avatar.url/room.png',
isSpaceRoom: jest.fn().mockReturnValue(false),
getType: jest.fn().mockReturnValue(undefined),
isElementVideoRoom: jest.fn().mockReturnValue(false),
getUnreadNotificationCount: jest.fn(() => 0),
getEventReadUpTo: jest.fn(() => null),
@ -545,6 +547,7 @@ export const mkSpace = (
): MockedObject<Room> => {
const space = mocked(mkRoom(client, spaceId, rooms));
space.isSpaceRoom.mockReturnValue(true);
space.getType.mockReturnValue(RoomType.Space);
mocked(space.currentState).getStateEvents.mockImplementation(mockStateEventImplementation(children.map(roomId =>
mkEvent({
event: true,

View file

@ -106,7 +106,7 @@ export const mkThread = ({
participantUserIds,
length = 2,
ts = 1,
}: MakeThreadProps): { thread: Thread, rootEvent: MatrixEvent } => {
}: MakeThreadProps): { thread: Thread, rootEvent: MatrixEvent, events: MatrixEvent[] } => {
const { rootEvent, events } = makeThreadEvents({
roomId: room.roomId,
authorId,
@ -120,5 +120,5 @@ export const mkThread = ({
// So that we do not have to mock the thread loading
thread.initialEventsFetched = true;
return { thread, rootEvent };
return { thread, rootEvent, events };
};

View file

@ -26,6 +26,8 @@ import {
VoiceBroadcastRecorderEvent,
} from "../../../src/voice-broadcast";
jest.mock("../../../src/audio/VoiceRecording");
describe("VoiceBroadcastRecorder", () => {
describe("createVoiceBroadcastRecorder", () => {
beforeEach(() => {
@ -44,6 +46,7 @@ describe("VoiceBroadcastRecorder", () => {
it("should return a VoiceBroadcastRecorder instance with targetChunkLength from config", () => {
const voiceBroadcastRecorder = createVoiceBroadcastRecorder();
expect(mocked(VoiceRecording).mock.instances[0].disableMaxLength).toHaveBeenCalled();
expect(voiceBroadcastRecorder).toBeInstanceOf(VoiceBroadcastRecorder);
expect(voiceBroadcastRecorder.targetChunkLength).toBe(1337);
});
@ -72,16 +75,12 @@ describe("VoiceBroadcastRecorder", () => {
};
beforeEach(() => {
voiceRecording = {
contentType,
start: jest.fn().mockResolvedValue(undefined),
stop: jest.fn().mockResolvedValue(undefined),
on: jest.fn(),
off: jest.fn(),
emit: jest.fn(),
destroy: jest.fn(),
recorderSeconds: 23,
} as unknown as VoiceRecording;
voiceRecording = new VoiceRecording();
// @ts-ignore
voiceRecording.recorderSeconds = 23;
// @ts-ignore
voiceRecording.contentType = contentType;
voiceBroadcastRecorder = new VoiceBroadcastRecorder(voiceRecording, chunkLength);
jest.spyOn(voiceBroadcastRecorder, "removeAllListeners");
onChunkRecorded = jest.fn();