Merge remote-tracking branch 'origin/develop' into feat/add-message-edition-wysiwyg-composer
This commit is contained in:
commit
4d089dcc05
109 changed files with 2374 additions and 1011 deletions
|
@ -22,16 +22,18 @@ import RightPanelStore from "../src/stores/right-panel/RightPanelStore";
|
|||
import { RoomViewStore } from "../src/stores/RoomViewStore";
|
||||
import { SpaceStoreClass } from "../src/stores/spaces/SpaceStore";
|
||||
import { WidgetLayoutStore } from "../src/stores/widgets/WidgetLayoutStore";
|
||||
import { WidgetPermissionStore } from "../src/stores/widgets/WidgetPermissionStore";
|
||||
import WidgetStore from "../src/stores/WidgetStore";
|
||||
|
||||
/**
|
||||
* A class which provides the same API as Stores but adds additional unsafe setters which can
|
||||
* A class which provides the same API as SdkContextClass but adds additional unsafe setters which can
|
||||
* replace individual stores. This is useful for tests which need to mock out stores.
|
||||
*/
|
||||
export class TestStores extends SdkContextClass {
|
||||
export class TestSdkContext extends SdkContextClass {
|
||||
public _RightPanelStore?: RightPanelStore;
|
||||
public _RoomNotificationStateStore?: RoomNotificationStateStore;
|
||||
public _RoomViewStore?: RoomViewStore;
|
||||
public _WidgetPermissionStore?: WidgetPermissionStore;
|
||||
public _WidgetLayoutStore?: WidgetLayoutStore;
|
||||
public _WidgetStore?: WidgetStore;
|
||||
public _PosthogAnalytics?: PosthogAnalytics;
|
|
@ -1,47 +0,0 @@
|
|||
/*
|
||||
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 { render } from "@testing-library/react";
|
||||
|
||||
import { Icon, IconColour, IconSize, IconType } from "../../../src/components/atoms/Icon";
|
||||
|
||||
describe("Icon", () => {
|
||||
it.each([
|
||||
IconColour.Accent,
|
||||
IconColour.LiveBadge,
|
||||
])("should render the colour %s", (colour: IconColour) => {
|
||||
const { container } = render(
|
||||
<Icon
|
||||
colour={colour}
|
||||
type={IconType.Live}
|
||||
/>,
|
||||
);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it.each([
|
||||
IconSize.S16,
|
||||
])("should render the size %s", (size: IconSize) => {
|
||||
const { container } = render(
|
||||
<Icon
|
||||
size={size}
|
||||
type={IconType.Live}
|
||||
/>,
|
||||
);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -1,34 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Icon should render the colour accent 1`] = `
|
||||
<div>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_accent"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Icon should render the colour live-badge 1`] = `
|
||||
<div>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_live-badge"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Icon should render the size 16 1`] = `
|
||||
<div>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_accent"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
/>
|
||||
</div>
|
||||
`;
|
158
test/components/structures/ThreadView-test.tsx
Normal file
158
test/components/structures/ThreadView-test.tsx
Normal file
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
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 { getByTestId, render, RenderResult, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { mocked } from "jest-mock";
|
||||
import { MsgType, RelationType } from "matrix-js-sdk/src/@types/event";
|
||||
import { MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/client";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread";
|
||||
import React, { useState } from "react";
|
||||
import { act } from "react-dom/test-utils";
|
||||
|
||||
import ThreadView from "../../../src/components/structures/ThreadView";
|
||||
import MatrixClientContext from "../../../src/contexts/MatrixClientContext";
|
||||
import RoomContext from "../../../src/contexts/RoomContext";
|
||||
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
||||
import DMRoomMap from "../../../src/utils/DMRoomMap";
|
||||
import ResizeNotifier from "../../../src/utils/ResizeNotifier";
|
||||
import { mockPlatformPeg } from "../../test-utils/platform";
|
||||
import { getRoomContext } from "../../test-utils/room";
|
||||
import { stubClient } from "../../test-utils/test-utils";
|
||||
import { mkThread } from "../../test-utils/threads";
|
||||
|
||||
describe("ThreadView", () => {
|
||||
const ROOM_ID = "!roomId:example.org";
|
||||
const SENDER = "@alice:example.org";
|
||||
|
||||
let mockClient: MatrixClient;
|
||||
let room: Room;
|
||||
let rootEvent: MatrixEvent;
|
||||
|
||||
let changeEvent: (event: MatrixEvent) => void;
|
||||
|
||||
function TestThreadView() {
|
||||
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>;
|
||||
}
|
||||
|
||||
async function getComponent(): Promise<RenderResult> {
|
||||
const renderResult = render(
|
||||
<TestThreadView />,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(() => getByTestId(renderResult.container, 'spinner')).toThrow();
|
||||
});
|
||||
|
||||
return renderResult;
|
||||
}
|
||||
|
||||
async function sendMessage(container, text): Promise<void> {
|
||||
const composer = getByTestId(container, "basicmessagecomposer");
|
||||
await userEvent.click(composer);
|
||||
await userEvent.keyboard(text);
|
||||
const sendMessageBtn = getByTestId(container, "sendmessagebtn");
|
||||
await userEvent.click(sendMessageBtn);
|
||||
}
|
||||
|
||||
function expectedMessageBody(rootEvent, message) {
|
||||
return {
|
||||
"body": message,
|
||||
"m.relates_to": {
|
||||
"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(),
|
||||
},
|
||||
"rel_type": RelationType.Thread,
|
||||
},
|
||||
"msgtype": MsgType.Text,
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
stubClient();
|
||||
mockPlatformPeg();
|
||||
mockClient = mocked(MatrixClientPeg.get());
|
||||
|
||||
room = new Room(ROOM_ID, mockClient, mockClient.getUserId() ?? "", {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
|
||||
const res = mkThread({
|
||||
room,
|
||||
client: mockClient,
|
||||
authorId: mockClient.getUserId(),
|
||||
participantUserIds: [mockClient.getUserId()],
|
||||
});
|
||||
|
||||
rootEvent = res.rootEvent;
|
||||
|
||||
DMRoomMap.makeShared();
|
||||
jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue(SENDER);
|
||||
});
|
||||
|
||||
it("sends a message with the correct fallback", async () => {
|
||||
const { container } = await getComponent();
|
||||
|
||||
await sendMessage(container, "Hello world!");
|
||||
|
||||
expect(mockClient.sendMessage).toHaveBeenCalledWith(
|
||||
ROOM_ID, rootEvent.getId(), expectedMessageBody(rootEvent, "Hello world!"),
|
||||
);
|
||||
});
|
||||
|
||||
it("sends a message with the correct fallback", async () => {
|
||||
const { container } = await getComponent();
|
||||
|
||||
const { rootEvent: rootEvent2 } = mkThread({
|
||||
room,
|
||||
client: mockClient,
|
||||
authorId: mockClient.getUserId(),
|
||||
participantUserIds: [mockClient.getUserId()],
|
||||
});
|
||||
|
||||
act(() => {
|
||||
changeEvent(rootEvent2);
|
||||
});
|
||||
|
||||
await sendMessage(container, "yolo");
|
||||
|
||||
expect(mockClient.sendMessage).toHaveBeenCalledWith(
|
||||
ROOM_ID, rootEvent2.getId(), expectedMessageBody(rootEvent2, "yolo"),
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,9 +1,9 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`RoomView for a local room in state CREATING should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_E2EIcon mx_E2EIcon_normal mx_RoomHeader_icon\\"></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><div class=\\"mx_RoomView_body\\"><div class=\\"mx_LargeLoader\\"><div class=\\"mx_Spinner\\"><div class=\\"mx_Spinner_icon\\" style=\\"width: 45px; height: 45px;\\" aria-label=\\"Loading...\\" role=\\"progressbar\\"></div></div><div class=\\"mx_LargeLoader_text\\">We're creating a room with @user:example.com</div></div></div></div>"`;
|
||||
exports[`RoomView for a local room in state CREATING should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_E2EIcon mx_E2EIcon_normal mx_RoomHeader_icon\\"></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><div class=\\"mx_RoomView_body\\"><div class=\\"mx_LargeLoader\\"><div class=\\"mx_Spinner\\"><div class=\\"mx_Spinner_icon\\" style=\\"width: 45px; height: 45px;\\" aria-label=\\"Loading...\\" role=\\"progressbar\\" data-testid=\\"spinner\\"></div></div><div class=\\"mx_LargeLoader_text\\">We're creating a room with @user:example.com</div></div></div></div>"`;
|
||||
|
||||
exports[`RoomView for a local room in state ERROR should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_E2EIcon mx_E2EIcon_normal mx_RoomHeader_icon\\"></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><main class=\\"mx_RoomView_body\\"><div class=\\"mx_RoomView_timeline\\"><div class=\\"mx_AutoHideScrollbar mx_ScrollPanel mx_RoomView_messagePanel\\" tabindex=\\"-1\\"><div class=\\"mx_RoomView_messageListWrapper\\"><ol class=\\"mx_RoomView_MessageList\\" aria-live=\\"polite\\" style=\\"height: 400px;\\"><li class=\\"mx_NewRoomIntro\\"><div class=\\"mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon_warning\\"><div class=\\"mx_EventTileBubble_title\\">End-to-end encryption isn't enabled</div><div class=\\"mx_EventTileBubble_subtitle\\"><span> Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. </span></div></div><span aria-label=\\"Avatar\\" aria-live=\\"off\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_BaseAvatar\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 33.800000000000004px; width: 52px; line-height: 52px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 52px; height: 52px;\\" aria-hidden=\\"true\\"></span><h2>@user:example.com</h2><p><span>Send your first message to invite <b>@user:example.com</b> to chat</span></p></li></ol></div></div></div><div class=\\"mx_RoomStatusBar mx_RoomStatusBar_unsentMessages\\"><div role=\\"alert\\"><div class=\\"mx_RoomStatusBar_unsentBadge\\"><div class=\\"mx_NotificationBadge mx_NotificationBadge_visible mx_NotificationBadge_highlighted mx_NotificationBadge_2char\\"><span class=\\"mx_NotificationBadge_count\\">!</span></div></div><div><div class=\\"mx_RoomStatusBar_unsentTitle\\">Some of your messages have not been sent</div></div><div class=\\"mx_RoomStatusBar_unsentButtonBar\\"><div role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_RoomStatusBar_unsentRetry\\">Retry</div></div></div></div></main></div>"`;
|
||||
|
||||
exports[`RoomView for a local room in state NEW should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_E2EIcon mx_E2EIcon_normal mx_RoomHeader_icon\\"></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><main class=\\"mx_RoomView_body\\"><div class=\\"mx_RoomView_timeline\\"><div class=\\"mx_AutoHideScrollbar mx_ScrollPanel mx_RoomView_messagePanel\\" tabindex=\\"-1\\"><div class=\\"mx_RoomView_messageListWrapper\\"><ol class=\\"mx_RoomView_MessageList\\" aria-live=\\"polite\\" style=\\"height: 400px;\\"><li class=\\"mx_NewRoomIntro\\"><div class=\\"mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon_warning\\"><div class=\\"mx_EventTileBubble_title\\">End-to-end encryption isn't enabled</div><div class=\\"mx_EventTileBubble_subtitle\\"><span> Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. </span></div></div><span aria-label=\\"Avatar\\" aria-live=\\"off\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_BaseAvatar\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 33.800000000000004px; width: 52px; line-height: 52px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 52px; height: 52px;\\" aria-hidden=\\"true\\"></span><h2>@user:example.com</h2><p><span>Send your first message to invite <b>@user:example.com</b> to chat</span></p></li></ol></div></div></div><div class=\\"mx_MessageComposer\\"><div class=\\"mx_MessageComposer_wrapper\\"><div class=\\"mx_MessageComposer_row\\"><div class=\\"mx_SendMessageComposer\\"><div class=\\"mx_BasicMessageComposer\\"><div class=\\"mx_MessageComposerFormatBar\\"><button type=\\"button\\" aria-label=\\"Bold\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconBold\\"></button><button type=\\"button\\" aria-label=\\"Italics\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconItalic\\"></button><button type=\\"button\\" aria-label=\\"Strikethrough\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconStrikethrough\\"></button><button type=\\"button\\" aria-label=\\"Code block\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconCode\\"></button><button type=\\"button\\" aria-label=\\"Quote\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconQuote\\"></button><button type=\\"button\\" aria-label=\\"Insert link\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconInsertLink\\"></button></div><div class=\\"mx_BasicMessageComposer_input mx_BasicMessageComposer_input_shouldShowPillAvatar mx_BasicMessageComposer_inputEmpty\\" contenteditable=\\"true\\" tabindex=\\"0\\" aria-label=\\"Send a message…\\" role=\\"textbox\\" aria-multiline=\\"true\\" aria-autocomplete=\\"list\\" aria-haspopup=\\"listbox\\" dir=\\"auto\\" aria-disabled=\\"false\\" style=\\"--placeholder: 'Send a message…';\\"><div><br></div></div></div></div><div aria-label=\\"Emoji\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_emoji\\"></div><div aria-label=\\"Attachment\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_upload\\"></div><div aria-label=\\"More options\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_buttonMenu\\"></div><input type=\\"file\\" style=\\"display: none;\\" multiple=\\"\\"></div></div></div></main></div>"`;
|
||||
exports[`RoomView for a local room in state NEW should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_E2EIcon mx_E2EIcon_normal mx_RoomHeader_icon\\"></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><main class=\\"mx_RoomView_body\\"><div class=\\"mx_RoomView_timeline\\"><div class=\\"mx_AutoHideScrollbar mx_ScrollPanel mx_RoomView_messagePanel\\" tabindex=\\"-1\\"><div class=\\"mx_RoomView_messageListWrapper\\"><ol class=\\"mx_RoomView_MessageList\\" aria-live=\\"polite\\" style=\\"height: 400px;\\"><li class=\\"mx_NewRoomIntro\\"><div class=\\"mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon_warning\\"><div class=\\"mx_EventTileBubble_title\\">End-to-end encryption isn't enabled</div><div class=\\"mx_EventTileBubble_subtitle\\"><span> Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. </span></div></div><span aria-label=\\"Avatar\\" aria-live=\\"off\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_BaseAvatar\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 33.800000000000004px; width: 52px; line-height: 52px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 52px; height: 52px;\\" aria-hidden=\\"true\\"></span><h2>@user:example.com</h2><p><span>Send your first message to invite <b>@user:example.com</b> to chat</span></p></li></ol></div></div></div><div class=\\"mx_MessageComposer\\"><div class=\\"mx_MessageComposer_wrapper\\"><div class=\\"mx_MessageComposer_row\\"><div class=\\"mx_SendMessageComposer\\"><div class=\\"mx_BasicMessageComposer\\"><div class=\\"mx_MessageComposerFormatBar\\"><button type=\\"button\\" aria-label=\\"Bold\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconBold\\"></button><button type=\\"button\\" aria-label=\\"Italics\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconItalic\\"></button><button type=\\"button\\" aria-label=\\"Strikethrough\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconStrikethrough\\"></button><button type=\\"button\\" aria-label=\\"Code block\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconCode\\"></button><button type=\\"button\\" aria-label=\\"Quote\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconQuote\\"></button><button type=\\"button\\" aria-label=\\"Insert link\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconInsertLink\\"></button></div><div class=\\"mx_BasicMessageComposer_input mx_BasicMessageComposer_input_shouldShowPillAvatar mx_BasicMessageComposer_inputEmpty\\" contenteditable=\\"true\\" tabindex=\\"0\\" aria-label=\\"Send a message…\\" role=\\"textbox\\" aria-multiline=\\"true\\" aria-autocomplete=\\"list\\" aria-haspopup=\\"listbox\\" dir=\\"auto\\" aria-disabled=\\"false\\" data-testid=\\"basicmessagecomposer\\" style=\\"--placeholder: 'Send a message…';\\"><div><br></div></div></div></div><div aria-label=\\"Emoji\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_emoji\\"></div><div aria-label=\\"Attachment\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_upload\\"></div><div aria-label=\\"More options\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_buttonMenu\\"></div><input type=\\"file\\" style=\\"display: none;\\" multiple=\\"\\"></div></div></div></main></div>"`;
|
||||
|
||||
exports[`RoomView for a local room in state NEW that is encrypted should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_E2EIcon mx_E2EIcon_normal mx_RoomHeader_icon\\"></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><main class=\\"mx_RoomView_body\\"><div class=\\"mx_RoomView_timeline\\"><div class=\\"mx_AutoHideScrollbar mx_ScrollPanel mx_RoomView_messagePanel\\" tabindex=\\"-1\\"><div class=\\"mx_RoomView_messageListWrapper\\"><ol class=\\"mx_RoomView_MessageList\\" aria-live=\\"polite\\" style=\\"height: 400px;\\"><div class=\\"mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon\\"><div class=\\"mx_EventTileBubble_title\\">Encryption enabled</div><div class=\\"mx_EventTileBubble_subtitle\\">Messages in this chat will be end-to-end encrypted.</div></div><li class=\\"mx_NewRoomIntro\\"><span aria-label=\\"Avatar\\" aria-live=\\"off\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_BaseAvatar\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 33.800000000000004px; width: 52px; line-height: 52px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 52px; height: 52px;\\" aria-hidden=\\"true\\"></span><h2>@user:example.com</h2><p><span>Send your first message to invite <b>@user:example.com</b> to chat</span></p></li></ol></div></div></div><div class=\\"mx_MessageComposer\\"><div class=\\"mx_MessageComposer_wrapper\\"><div class=\\"mx_MessageComposer_row\\"><div class=\\"mx_SendMessageComposer\\"><div class=\\"mx_BasicMessageComposer\\"><div class=\\"mx_MessageComposerFormatBar\\"><button type=\\"button\\" aria-label=\\"Bold\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconBold\\"></button><button type=\\"button\\" aria-label=\\"Italics\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconItalic\\"></button><button type=\\"button\\" aria-label=\\"Strikethrough\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconStrikethrough\\"></button><button type=\\"button\\" aria-label=\\"Code block\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconCode\\"></button><button type=\\"button\\" aria-label=\\"Quote\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconQuote\\"></button><button type=\\"button\\" aria-label=\\"Insert link\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconInsertLink\\"></button></div><div class=\\"mx_BasicMessageComposer_input mx_BasicMessageComposer_input_shouldShowPillAvatar mx_BasicMessageComposer_inputEmpty\\" contenteditable=\\"true\\" tabindex=\\"0\\" aria-label=\\"Send a message…\\" role=\\"textbox\\" aria-multiline=\\"true\\" aria-autocomplete=\\"list\\" aria-haspopup=\\"listbox\\" dir=\\"auto\\" aria-disabled=\\"false\\" style=\\"--placeholder: 'Send a message…';\\"><div><br></div></div></div></div><div aria-label=\\"Emoji\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_emoji\\"></div><div aria-label=\\"Attachment\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_upload\\"></div><div aria-label=\\"More options\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_buttonMenu\\"></div><input type=\\"file\\" style=\\"display: none;\\" multiple=\\"\\"></div></div></div></main></div>"`;
|
||||
exports[`RoomView for a local room in state NEW that is encrypted should match the snapshot 1`] = `"<div class=\\"mx_RoomView mx_RoomView--local\\"><header class=\\"mx_RoomHeader light-panel\\"><div class=\\"mx_RoomHeader_wrapper\\"><div class=\\"mx_RoomHeader_avatar\\"><div class=\\"mx_DecoratedRoomAvatar\\"><span class=\\"mx_BaseAvatar\\" role=\\"presentation\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 15.600000000000001px; width: 24px; line-height: 24px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 24px; height: 24px;\\" aria-hidden=\\"true\\"></span></div></div><div class=\\"mx_E2EIcon mx_E2EIcon_normal mx_RoomHeader_icon\\"></div><div class=\\"mx_RoomHeader_name mx_RoomHeader_name--textonly\\"><div dir=\\"auto\\" class=\\"mx_RoomHeader_nametext\\" title=\\"@user:example.com\\" role=\\"heading\\" aria-level=\\"1\\">@user:example.com</div></div><div class=\\"mx_RoomHeader_topic mx_RoomTopic\\" dir=\\"auto\\"><div tabindex=\\"0\\"><div><span dir=\\"auto\\"></span></div></div></div></div></header><main class=\\"mx_RoomView_body\\"><div class=\\"mx_RoomView_timeline\\"><div class=\\"mx_AutoHideScrollbar mx_ScrollPanel mx_RoomView_messagePanel\\" tabindex=\\"-1\\"><div class=\\"mx_RoomView_messageListWrapper\\"><ol class=\\"mx_RoomView_MessageList\\" aria-live=\\"polite\\" style=\\"height: 400px;\\"><div class=\\"mx_EventTileBubble mx_cryptoEvent mx_cryptoEvent_icon\\"><div class=\\"mx_EventTileBubble_title\\">Encryption enabled</div><div class=\\"mx_EventTileBubble_subtitle\\">Messages in this chat will be end-to-end encrypted.</div></div><li class=\\"mx_NewRoomIntro\\"><span aria-label=\\"Avatar\\" aria-live=\\"off\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_BaseAvatar\\"><span class=\\"mx_BaseAvatar_initial\\" aria-hidden=\\"true\\" style=\\"font-size: 33.800000000000004px; width: 52px; line-height: 52px;\\">U</span><img class=\\"mx_BaseAvatar_image\\" src=\\"data:image/png;base64,00\\" alt=\\"\\" style=\\"width: 52px; height: 52px;\\" aria-hidden=\\"true\\"></span><h2>@user:example.com</h2><p><span>Send your first message to invite <b>@user:example.com</b> to chat</span></p></li></ol></div></div></div><div class=\\"mx_MessageComposer\\"><div class=\\"mx_MessageComposer_wrapper\\"><div class=\\"mx_MessageComposer_row\\"><div class=\\"mx_SendMessageComposer\\"><div class=\\"mx_BasicMessageComposer\\"><div class=\\"mx_MessageComposerFormatBar\\"><button type=\\"button\\" aria-label=\\"Bold\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconBold\\"></button><button type=\\"button\\" aria-label=\\"Italics\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconItalic\\"></button><button type=\\"button\\" aria-label=\\"Strikethrough\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconStrikethrough\\"></button><button type=\\"button\\" aria-label=\\"Code block\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconCode\\"></button><button type=\\"button\\" aria-label=\\"Quote\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconQuote\\"></button><button type=\\"button\\" aria-label=\\"Insert link\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIconInsertLink\\"></button></div><div class=\\"mx_BasicMessageComposer_input mx_BasicMessageComposer_input_shouldShowPillAvatar mx_BasicMessageComposer_inputEmpty\\" contenteditable=\\"true\\" tabindex=\\"0\\" aria-label=\\"Send a message…\\" role=\\"textbox\\" aria-multiline=\\"true\\" aria-autocomplete=\\"list\\" aria-haspopup=\\"listbox\\" dir=\\"auto\\" aria-disabled=\\"false\\" data-testid=\\"basicmessagecomposer\\" style=\\"--placeholder: 'Send a message…';\\"><div><br></div></div></div></div><div aria-label=\\"Emoji\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_emoji\\"></div><div aria-label=\\"Attachment\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_upload\\"></div><div aria-label=\\"More options\\" role=\\"button\\" tabindex=\\"0\\" class=\\"mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_buttonMenu\\"></div><input type=\\"file\\" style=\\"display: none;\\" multiple=\\"\\"></div></div></div></main></div>"`;
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
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 { getByTestId, render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { mocked } from "jest-mock";
|
||||
import { MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/client";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import React from "react";
|
||||
|
||||
import ThreadListContextMenu, {
|
||||
ThreadListContextMenuProps,
|
||||
} from "../../../../src/components/views/context_menus/ThreadListContextMenu";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
|
||||
import { stubClient } from "../../../test-utils/test-utils";
|
||||
import { mkThread } from "../../../test-utils/threads";
|
||||
|
||||
describe("ThreadListContextMenu", () => {
|
||||
const ROOM_ID = "!123:matrix.org";
|
||||
|
||||
let room: Room;
|
||||
let mockClient: MatrixClient;
|
||||
let event: MatrixEvent;
|
||||
|
||||
function getComponent(props: Partial<ThreadListContextMenuProps>) {
|
||||
return render(<ThreadListContextMenu
|
||||
mxEvent={event}
|
||||
{...props}
|
||||
/>);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
stubClient();
|
||||
mockClient = mocked(MatrixClientPeg.get());
|
||||
|
||||
room = new Room(ROOM_ID, mockClient, mockClient.getUserId() ?? "", {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
|
||||
const res = mkThread({
|
||||
room,
|
||||
client: mockClient,
|
||||
authorId: mockClient.getUserId(),
|
||||
participantUserIds: [mockClient.getUserId()],
|
||||
});
|
||||
|
||||
event = res.rootEvent;
|
||||
});
|
||||
|
||||
it("does not render the permalink", async () => {
|
||||
const { container } = getComponent({});
|
||||
|
||||
const btn = getByTestId(container, "threadlist-dropdown-button");
|
||||
await userEvent.click(btn);
|
||||
expect(screen.queryByTestId("copy-thread-link")).toBeNull();
|
||||
});
|
||||
|
||||
it("does render the permalink", async () => {
|
||||
const { container } = getComponent({
|
||||
permalinkCreator: new RoomPermalinkCreator(room, room.roomId, false),
|
||||
});
|
||||
|
||||
const btn = getByTestId(container, "threadlist-dropdown-button");
|
||||
await userEvent.click(btn);
|
||||
expect(screen.queryByTestId("copy-thread-link")).not.toBeNull();
|
||||
});
|
||||
});
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
import React from "react";
|
||||
import { act } from "react-dom/test-utils";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
import { ISendEventResponse, MatrixClient, MsgType } from "matrix-js-sdk/src/matrix";
|
||||
import { ISendEventResponse, MatrixClient, MsgType, RelationType } from "matrix-js-sdk/src/matrix";
|
||||
// eslint-disable-next-line deprecate/import
|
||||
import { mount } from 'enzyme';
|
||||
import { mocked } from "jest-mock";
|
||||
|
@ -291,7 +291,7 @@ describe('<SendMessageComposer/>', () => {
|
|||
|
||||
it('correctly sets the editorStateKey for threads', () => {
|
||||
const relation = {
|
||||
rel_type: "m.thread",
|
||||
rel_type: RelationType.Thread,
|
||||
event_id: "myFakeThreadId",
|
||||
};
|
||||
const includeReplyLegacyFallback = false;
|
||||
|
|
|
@ -20,13 +20,12 @@ import { act, render, screen, waitFor } from "@testing-library/react";
|
|||
import { InputEventProcessor, Wysiwyg, WysiwygProps } from "@matrix-org/matrix-wysiwyg";
|
||||
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import RoomContext, { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import RoomContext from "../../../../../src/contexts/RoomContext";
|
||||
import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import { IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import { Layout } from "../../../../../src/settings/enums/Layout";
|
||||
import { WysiwygComposer } from "../../../../../src/components/views/rooms/wysiwyg_composer/components/WysiwygComposer";
|
||||
import { createTestClient, mkEvent, mkStubRoom } from "../../../../test-utils";
|
||||
import { createTestClient, getRoomContext, mkEvent, mkStubRoom } from "../../../../test-utils";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
|
||||
// Work around missing ClipboardEvent type
|
||||
|
@ -74,43 +73,7 @@ describe('WysiwygComposer', () => {
|
|||
return eventId === mockEvent.getId() ? mockEvent : null;
|
||||
});
|
||||
|
||||
const defaultRoomContext: IRoomState = {
|
||||
room: mockRoom,
|
||||
roomLoading: true,
|
||||
peekLoading: false,
|
||||
shouldPeek: true,
|
||||
membersLoaded: false,
|
||||
numUnreadMessages: 0,
|
||||
canPeek: false,
|
||||
showApps: false,
|
||||
isPeeking: false,
|
||||
showRightPanel: true,
|
||||
joining: false,
|
||||
atEndOfLiveTimeline: true,
|
||||
showTopUnreadMessagesBar: false,
|
||||
statusBarVisible: false,
|
||||
canReact: false,
|
||||
canSendMessages: false,
|
||||
layout: Layout.Group,
|
||||
lowBandwidth: false,
|
||||
alwaysShowTimestamps: false,
|
||||
showTwelveHourTimestamps: false,
|
||||
readMarkerInViewThresholdMs: 3000,
|
||||
readMarkerOutOfViewThresholdMs: 30000,
|
||||
showHiddenEvents: false,
|
||||
showReadReceipts: true,
|
||||
showRedactions: true,
|
||||
showJoinLeaves: true,
|
||||
showAvatarChanges: true,
|
||||
showDisplaynameChanges: true,
|
||||
matrixClientIsReady: false,
|
||||
timelineRenderingType: TimelineRenderingType.Room,
|
||||
liveTimeline: undefined,
|
||||
canSelfRedact: false,
|
||||
resizing: false,
|
||||
narrow: false,
|
||||
activeCall: null,
|
||||
};
|
||||
const defaultRoomContext: IRoomState = getRoomContext(mockRoom, {});
|
||||
|
||||
let sendMessage: () => void;
|
||||
const customRender = (onChange = (_content: string) => void 0, disabled = false) => {
|
||||
|
|
|
@ -31,6 +31,7 @@ exports[`FontScalingPanel renders the font scaling UI 1`] = `
|
|||
<div
|
||||
aria-label="Loading..."
|
||||
className="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style={
|
||||
Object {
|
||||
|
|
|
@ -4,6 +4,7 @@ exports[`<LoginWithQR /> approves login and waits for new device 1`] = `
|
|||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
|
@ -32,6 +33,7 @@ exports[`<LoginWithQR /> approves login and waits for new device 1`] = `
|
|||
<div
|
||||
aria-label="Loading..."
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
|
@ -61,6 +63,7 @@ exports[`<LoginWithQR /> displays confirmation digits after connected to rendezv
|
|||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
|
@ -122,6 +125,7 @@ exports[`<LoginWithQR /> displays error when approving login fails 1`] = `
|
|||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_centreTitle"
|
||||
|
@ -168,6 +172,7 @@ exports[`<LoginWithQR /> displays qr code after it is created 1`] = `
|
|||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
|
@ -214,6 +219,7 @@ exports[`<LoginWithQR /> displays qr code after it is created 1`] = `
|
|||
<div
|
||||
aria-label="Loading..."
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
|
@ -232,6 +238,7 @@ exports[`<LoginWithQR /> displays unknown error if connection to rendezvous fail
|
|||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_centreTitle"
|
||||
|
@ -278,6 +285,7 @@ exports[`<LoginWithQR /> no content in case of no support 1`] = `
|
|||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class="mx_LoginWithQR_centreTitle"
|
||||
|
@ -324,6 +332,7 @@ exports[`<LoginWithQR /> renders spinner while generating code 1`] = `
|
|||
<div>
|
||||
<div
|
||||
class="mx_LoginWithQR"
|
||||
data-testid="login-with-qr"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
|
@ -352,6 +361,7 @@ exports[`<LoginWithQR /> renders spinner while generating code 1`] = `
|
|||
<div
|
||||
aria-label="Loading..."
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
|
|
|
@ -13,7 +13,7 @@ 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 { fireEvent, render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import SecurityUserSettingsTab from "../../../../../../src/components/views/settings/tabs/user/SecurityUserSettingsTab";
|
||||
|
@ -26,6 +26,7 @@ import {
|
|||
mockClientMethodsCrypto,
|
||||
mockClientMethodsDevice,
|
||||
mockPlatformPeg,
|
||||
flushPromises,
|
||||
} from '../../../../../test-utils';
|
||||
|
||||
describe('<SecurityUserSettingsTab />', () => {
|
||||
|
@ -42,6 +43,12 @@ describe('<SecurityUserSettingsTab />', () => {
|
|||
...mockClientMethodsCrypto(),
|
||||
getRooms: jest.fn().mockReturnValue([]),
|
||||
getIgnoredUsers: jest.fn(),
|
||||
getVersions: jest.fn().mockResolvedValue({
|
||||
unstable_features: {
|
||||
'org.matrix.msc3882': true,
|
||||
'org.matrix.msc3886': true,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const getComponent = () =>
|
||||
|
@ -70,4 +77,34 @@ describe('<SecurityUserSettingsTab />', () => {
|
|||
|
||||
expect(queryByTestId('devices-section')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('does not render qr code login section when disabled', () => {
|
||||
settingsValueSpy.mockReturnValue(false);
|
||||
const { queryByText } = render(getComponent());
|
||||
|
||||
expect(settingsValueSpy).toHaveBeenCalledWith('feature_qr_signin_reciprocate_show');
|
||||
|
||||
expect(queryByText('Sign in with QR code')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('renders qr code login section when enabled', async () => {
|
||||
settingsValueSpy.mockImplementation(settingName => settingName === 'feature_qr_signin_reciprocate_show');
|
||||
const { getByText } = render(getComponent());
|
||||
|
||||
// wait for versions call to settle
|
||||
await flushPromises();
|
||||
|
||||
expect(getByText('Sign in with QR code')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('enters qr code login section when show QR code button clicked', async () => {
|
||||
settingsValueSpy.mockImplementation(settingName => settingName === 'feature_qr_signin_reciprocate_show');
|
||||
const { getByText, getByTestId } = render(getComponent());
|
||||
// wait for versions call to settle
|
||||
await flushPromises();
|
||||
|
||||
fireEvent.click(getByText('Show QR code'));
|
||||
|
||||
expect(getByTestId("login-with-qr")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -34,6 +34,7 @@ import {
|
|||
import SessionManagerTab from '../../../../../../src/components/views/settings/tabs/user/SessionManagerTab';
|
||||
import MatrixClientContext from '../../../../../../src/contexts/MatrixClientContext';
|
||||
import {
|
||||
flushPromises,
|
||||
flushPromisesWithFakeTimers,
|
||||
getMockClientWithEventEmitter,
|
||||
mkPusher,
|
||||
|
@ -47,6 +48,7 @@ import {
|
|||
ExtendedDevice,
|
||||
} from '../../../../../../src/components/views/settings/devices/types';
|
||||
import { INACTIVE_DEVICE_AGE_MS } from '../../../../../../src/components/views/settings/devices/filter';
|
||||
import SettingsStore from '../../../../../../src/settings/SettingsStore';
|
||||
|
||||
mockPlatformPeg();
|
||||
|
||||
|
@ -1142,4 +1144,50 @@ describe('<SessionManagerTab />', () => {
|
|||
|
||||
expect(checkbox.getAttribute('aria-checked')).toEqual("false");
|
||||
});
|
||||
|
||||
describe('QR code login', () => {
|
||||
const settingsValueSpy = jest.spyOn(SettingsStore, 'getValue');
|
||||
|
||||
beforeEach(() => {
|
||||
settingsValueSpy.mockClear().mockReturnValue(false);
|
||||
// enable server support for qr login
|
||||
mockClient.getVersions.mockResolvedValue({
|
||||
versions: [],
|
||||
unstable_features: {
|
||||
'org.matrix.msc3882': true,
|
||||
'org.matrix.msc3886': true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('does not render qr code login section when disabled', () => {
|
||||
settingsValueSpy.mockReturnValue(false);
|
||||
const { queryByText } = render(getComponent());
|
||||
|
||||
expect(settingsValueSpy).toHaveBeenCalledWith('feature_qr_signin_reciprocate_show');
|
||||
|
||||
expect(queryByText('Sign in with QR code')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('renders qr code login section when enabled', async () => {
|
||||
settingsValueSpy.mockImplementation(settingName => settingName === 'feature_qr_signin_reciprocate_show');
|
||||
const { getByText } = render(getComponent());
|
||||
|
||||
// wait for versions call to settle
|
||||
await flushPromises();
|
||||
|
||||
expect(getByText('Sign in with QR code')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('enters qr code login section when show QR code button clicked', async () => {
|
||||
settingsValueSpy.mockImplementation(settingName => settingName === 'feature_qr_signin_reciprocate_show');
|
||||
const { getByText, getByTestId } = render(getComponent());
|
||||
// wait for versions call to settle
|
||||
await flushPromises();
|
||||
|
||||
fireEvent.click(getByText('Show QR code'));
|
||||
|
||||
expect(getByTestId("login-with-qr")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -38,6 +38,8 @@ import { WidgetMessagingStore } from "../../src/stores/widgets/WidgetMessagingSt
|
|||
import ActiveWidgetStore, { ActiveWidgetStoreEvent } from "../../src/stores/ActiveWidgetStore";
|
||||
import { ElementWidgetActions } from "../../src/stores/widgets/ElementWidgetActions";
|
||||
import SettingsStore from "../../src/settings/SettingsStore";
|
||||
import Modal, { IHandle } from "../../src/Modal";
|
||||
import PlatformPeg from "../../src/PlatformPeg";
|
||||
|
||||
jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({
|
||||
[MediaDeviceKindEnum.AudioInput]: [
|
||||
|
@ -807,6 +809,69 @@ describe("ElementCall", () => {
|
|||
call.off(CallEvent.Layout, onLayout);
|
||||
});
|
||||
|
||||
describe("screensharing", () => {
|
||||
it("passes source id if we can get it", async () => {
|
||||
const sourceId = "source_id";
|
||||
jest.spyOn(Modal, "createDialog").mockReturnValue(
|
||||
{ finished: new Promise((r) => r([sourceId])) } as IHandle<any[]>,
|
||||
);
|
||||
jest.spyOn(PlatformPeg.get(), "supportsDesktopCapturer").mockReturnValue(true);
|
||||
|
||||
await call.connect();
|
||||
|
||||
messaging.emit(
|
||||
`action:${ElementWidgetActions.Screenshare}`,
|
||||
new CustomEvent("widgetapirequest", { detail: {} }),
|
||||
);
|
||||
|
||||
waitFor(() => {
|
||||
expect(messaging!.transport.reply).toHaveBeenCalledWith(
|
||||
expect.objectContaining({}),
|
||||
expect.objectContaining({ desktopCapturerSourceId: sourceId }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("passes failed if we couldn't get a source id", async () => {
|
||||
jest.spyOn(Modal, "createDialog").mockReturnValue(
|
||||
{ finished: new Promise((r) => r([null])) } as IHandle<any[]>,
|
||||
);
|
||||
jest.spyOn(PlatformPeg.get(), "supportsDesktopCapturer").mockReturnValue(true);
|
||||
|
||||
await call.connect();
|
||||
|
||||
messaging.emit(
|
||||
`action:${ElementWidgetActions.Screenshare}`,
|
||||
new CustomEvent("widgetapirequest", { detail: {} }),
|
||||
);
|
||||
|
||||
waitFor(() => {
|
||||
expect(messaging!.transport.reply).toHaveBeenCalledWith(
|
||||
expect.objectContaining({}),
|
||||
expect.objectContaining({ failed: true }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("passes an empty object if we don't support desktop capturer", async () => {
|
||||
jest.spyOn(PlatformPeg.get(), "supportsDesktopCapturer").mockReturnValue(false);
|
||||
|
||||
await call.connect();
|
||||
|
||||
messaging.emit(
|
||||
`action:${ElementWidgetActions.Screenshare}`,
|
||||
new CustomEvent("widgetapirequest", { detail: {} }),
|
||||
);
|
||||
|
||||
waitFor(() => {
|
||||
expect(messaging!.transport.reply).toHaveBeenCalledWith(
|
||||
expect.objectContaining({}),
|
||||
expect.objectContaining({}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("ends the call immediately if we're the last participant to leave", async () => {
|
||||
await call.connect();
|
||||
const onDestroy = jest.fn();
|
||||
|
|
|
@ -12,6 +12,7 @@ exports[`Module Components should override the factory for a ModuleSpinner 1`] =
|
|||
<div
|
||||
aria-label="Loading..."
|
||||
className="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style={
|
||||
Object {
|
||||
|
|
|
@ -27,7 +27,7 @@ import { MatrixDispatcher } from '../../src/dispatcher/dispatcher';
|
|||
import { UPDATE_EVENT } from '../../src/stores/AsyncStore';
|
||||
import { ActiveRoomChangedPayload } from '../../src/dispatcher/payloads/ActiveRoomChangedPayload';
|
||||
import { SpaceStoreClass } from '../../src/stores/spaces/SpaceStore';
|
||||
import { TestStores } from '../TestStores';
|
||||
import { TestSdkContext } from '../TestSdkContext';
|
||||
|
||||
// mock out the injected classes
|
||||
jest.mock('../../src/PosthogAnalytics');
|
||||
|
@ -77,7 +77,7 @@ describe('RoomViewStore', function() {
|
|||
// Make the RVS to test
|
||||
dis = new MatrixDispatcher();
|
||||
slidingSyncManager = new MockSlidingSyncManager();
|
||||
const stores = new TestStores();
|
||||
const stores = new TestSdkContext();
|
||||
stores._SlidingSyncManager = slidingSyncManager;
|
||||
stores._PosthogAnalytics = new MockPosthogAnalytics();
|
||||
stores._SpaceStore = new MockSpaceStore();
|
||||
|
|
|
@ -17,13 +17,14 @@ limitations under the License.
|
|||
import { mocked } from "jest-mock";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
|
||||
import TypingStore from "../../src/stores/TypingStore";
|
||||
import { LOCAL_ROOM_ID_PREFIX } from "../../src/models/LocalRoom";
|
||||
import SettingsStore from "../../src/settings/SettingsStore";
|
||||
import { TestSdkContext } from "../TestSdkContext";
|
||||
|
||||
jest.mock("../../src/settings/SettingsStore", () => ({
|
||||
getValue: jest.fn(),
|
||||
monitorSetting: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("TypingStore", () => {
|
||||
|
@ -37,11 +38,12 @@ describe("TypingStore", () => {
|
|||
const localRoomId = LOCAL_ROOM_ID_PREFIX + "test";
|
||||
|
||||
beforeEach(() => {
|
||||
typingStore = new TypingStore();
|
||||
mockClient = {
|
||||
sendTyping: jest.fn(),
|
||||
} as unknown as MatrixClient;
|
||||
MatrixClientPeg.get = () => mockClient;
|
||||
const context = new TestSdkContext();
|
||||
context.client = mockClient;
|
||||
typingStore = new TypingStore(context);
|
||||
mocked(SettingsStore.getValue).mockImplementation((setting: string) => {
|
||||
return settings[setting];
|
||||
});
|
||||
|
|
107
test/stores/widgets/WidgetPermissionStore-test.ts
Normal file
107
test/stores/widgets/WidgetPermissionStore-test.ts
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
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 { mocked } from "jest-mock";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { Widget, WidgetKind } from "matrix-widget-api";
|
||||
|
||||
import { OIDCState, WidgetPermissionStore } from "../../../src/stores/widgets/WidgetPermissionStore";
|
||||
import SettingsStore from "../../../src/settings/SettingsStore";
|
||||
import { TestSdkContext } from "../../TestSdkContext";
|
||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||
import { SdkContextClass } from "../../../src/contexts/SDKContext";
|
||||
import { stubClient } from "../../test-utils";
|
||||
|
||||
jest.mock("../../../src/settings/SettingsStore");
|
||||
|
||||
describe("WidgetPermissionStore", () => {
|
||||
let widgetPermissionStore: WidgetPermissionStore;
|
||||
let mockClient: MatrixClient;
|
||||
const userId = "@alice:localhost";
|
||||
const roomId = "!room:localhost";
|
||||
const w = new Widget({
|
||||
id: "wid",
|
||||
creatorUserId: userId,
|
||||
type: "m.custom",
|
||||
url: "https://invalid.address.here",
|
||||
});
|
||||
let settings = {}; // key value store
|
||||
|
||||
beforeEach(() => {
|
||||
settings = {}; // clear settings
|
||||
mocked(SettingsStore.getValue).mockImplementation((setting: string) => {
|
||||
return settings[setting];
|
||||
});
|
||||
mocked(SettingsStore.setValue).mockImplementation((settingName: string,
|
||||
roomId: string | null,
|
||||
level: SettingLevel,
|
||||
value: any,
|
||||
): Promise<void> => {
|
||||
// the store doesn't use any specific level or room ID (room IDs are packed into keys in `value`)
|
||||
settings[settingName] = value;
|
||||
return Promise.resolve();
|
||||
});
|
||||
mockClient = stubClient();
|
||||
const context = new TestSdkContext();
|
||||
context.client = mockClient;
|
||||
widgetPermissionStore = new WidgetPermissionStore(context);
|
||||
});
|
||||
|
||||
it("should persist OIDCState.Allowed for a widget", () => {
|
||||
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, null, OIDCState.Allowed);
|
||||
// check it remembered the value
|
||||
expect(
|
||||
widgetPermissionStore.getOIDCState(w, WidgetKind.Account, null),
|
||||
).toEqual(OIDCState.Allowed);
|
||||
});
|
||||
|
||||
it("should persist OIDCState.Denied for a widget", () => {
|
||||
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, null, OIDCState.Denied);
|
||||
// check it remembered the value
|
||||
expect(
|
||||
widgetPermissionStore.getOIDCState(w, WidgetKind.Account, null),
|
||||
).toEqual(OIDCState.Denied);
|
||||
});
|
||||
|
||||
it("should update OIDCState for a widget", () => {
|
||||
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, null, OIDCState.Allowed);
|
||||
widgetPermissionStore.setOIDCState(w, WidgetKind.Account, null, OIDCState.Denied);
|
||||
// check it remembered the latest value
|
||||
expect(
|
||||
widgetPermissionStore.getOIDCState(w, WidgetKind.Account, null),
|
||||
).toEqual(OIDCState.Denied);
|
||||
});
|
||||
|
||||
it("should scope the location for a widget when setting OIDC state", () => {
|
||||
// allow this widget for this room
|
||||
widgetPermissionStore.setOIDCState(w, WidgetKind.Room, roomId, OIDCState.Allowed);
|
||||
// check it remembered the value
|
||||
expect(
|
||||
widgetPermissionStore.getOIDCState(w, WidgetKind.Room, roomId),
|
||||
).toEqual(OIDCState.Allowed);
|
||||
// check this is not the case for the entire account
|
||||
expect(
|
||||
widgetPermissionStore.getOIDCState(w, WidgetKind.Account, roomId),
|
||||
).toEqual(OIDCState.Unknown);
|
||||
});
|
||||
it("is created once in SdkContextClass", () => {
|
||||
const context = new SdkContextClass();
|
||||
const store = context.widgetPermissionStore;
|
||||
expect(store).toBeDefined();
|
||||
const store2 = context.widgetPermissionStore;
|
||||
expect(store2).toStrictEqual(store);
|
||||
});
|
||||
});
|
|
@ -22,6 +22,9 @@ import {
|
|||
Room,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { IRoomState } from "../../src/components/structures/RoomView";
|
||||
import { TimelineRenderingType } from "../../src/contexts/RoomContext";
|
||||
import { Layout } from "../../src/settings/enums/Layout";
|
||||
import { mkEvent } from "./test-utils";
|
||||
|
||||
export const makeMembershipEvent = (
|
||||
|
@ -50,3 +53,45 @@ export const makeRoomWithStateEvents = (
|
|||
mockClient.getRoom.mockReturnValue(room1);
|
||||
return room1;
|
||||
};
|
||||
|
||||
export function getRoomContext(room: Room, override: Partial<IRoomState>): IRoomState {
|
||||
return {
|
||||
room,
|
||||
roomLoading: true,
|
||||
peekLoading: false,
|
||||
shouldPeek: true,
|
||||
membersLoaded: false,
|
||||
numUnreadMessages: 0,
|
||||
canPeek: false,
|
||||
showApps: false,
|
||||
isPeeking: false,
|
||||
showRightPanel: true,
|
||||
joining: false,
|
||||
atEndOfLiveTimeline: true,
|
||||
showTopUnreadMessagesBar: false,
|
||||
statusBarVisible: false,
|
||||
canReact: false,
|
||||
canSendMessages: false,
|
||||
layout: Layout.Group,
|
||||
lowBandwidth: false,
|
||||
alwaysShowTimestamps: false,
|
||||
showTwelveHourTimestamps: false,
|
||||
readMarkerInViewThresholdMs: 3000,
|
||||
readMarkerOutOfViewThresholdMs: 30000,
|
||||
showHiddenEvents: false,
|
||||
showReadReceipts: true,
|
||||
showRedactions: true,
|
||||
showJoinLeaves: true,
|
||||
showAvatarChanges: true,
|
||||
showDisplaynameChanges: true,
|
||||
matrixClientIsReady: false,
|
||||
timelineRenderingType: TimelineRenderingType.Room,
|
||||
liveTimeline: undefined,
|
||||
canSelfRedact: false,
|
||||
resizing: false,
|
||||
narrow: false,
|
||||
activeCall: null,
|
||||
|
||||
...override,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { MatrixClient, MatrixEvent, RelationType, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { Thread } from "matrix-js-sdk/src/models/thread";
|
||||
|
||||
import { mkMessage, MessageEventProps } from "./test-utils";
|
||||
|
||||
|
@ -78,7 +79,7 @@ export const makeThreadEvents = ({
|
|||
|
||||
rootEvent.setUnsigned({
|
||||
"m.relations": {
|
||||
"m.thread": {
|
||||
[RelationType.Thread]: {
|
||||
latest_event: events[events.length - 1],
|
||||
count: length,
|
||||
current_user_participated: [...participantUserIds, authorId].includes(currentUserId),
|
||||
|
@ -88,3 +89,36 @@ export const makeThreadEvents = ({
|
|||
|
||||
return { rootEvent, events };
|
||||
};
|
||||
|
||||
type MakeThreadProps = {
|
||||
room: Room;
|
||||
client: MatrixClient;
|
||||
authorId: string;
|
||||
participantUserIds: string[];
|
||||
length?: number;
|
||||
ts?: number;
|
||||
};
|
||||
|
||||
export const mkThread = ({
|
||||
room,
|
||||
client,
|
||||
authorId,
|
||||
participantUserIds,
|
||||
length = 2,
|
||||
ts = 1,
|
||||
}: MakeThreadProps): { thread: Thread, rootEvent: MatrixEvent } => {
|
||||
const { rootEvent, events } = makeThreadEvents({
|
||||
roomId: room.roomId,
|
||||
authorId,
|
||||
participantUserIds,
|
||||
length,
|
||||
ts,
|
||||
currentUserId: client.getUserId(),
|
||||
});
|
||||
|
||||
const thread = room.createThread(rootEvent.getId(), rootEvent, events, true);
|
||||
// So that we do not have to mock the thread loading
|
||||
thread.initialEventsFetched = true;
|
||||
|
||||
return { thread, rootEvent };
|
||||
};
|
||||
|
|
|
@ -17,11 +17,10 @@ limitations under the License.
|
|||
import React from "react";
|
||||
import { act, render, screen } from "@testing-library/react";
|
||||
import { mocked } from "jest-mock";
|
||||
import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import {
|
||||
VoiceBroadcastBody,
|
||||
VoiceBroadcastInfoEventType,
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastRecordingBody,
|
||||
VoiceBroadcastRecordingsStore,
|
||||
|
@ -30,8 +29,8 @@ import {
|
|||
VoiceBroadcastPlayback,
|
||||
VoiceBroadcastPlaybacksStore,
|
||||
} from "../../../src/voice-broadcast";
|
||||
import { mkEvent, stubClient } from "../../test-utils";
|
||||
import { RelationsHelper } from "../../../src/events/RelationsHelper";
|
||||
import { stubClient } from "../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../utils/test-utils";
|
||||
|
||||
jest.mock("../../../src/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody", () => ({
|
||||
VoiceBroadcastRecordingBody: jest.fn(),
|
||||
|
@ -41,27 +40,15 @@ jest.mock("../../../src/voice-broadcast/components/molecules/VoiceBroadcastPlayb
|
|||
VoiceBroadcastPlaybackBody: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../../../src/events/RelationsHelper");
|
||||
|
||||
describe("VoiceBroadcastBody", () => {
|
||||
const roomId = "!room:example.com";
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
let infoEvent: MatrixEvent;
|
||||
let stoppedEvent: MatrixEvent;
|
||||
let testRecording: VoiceBroadcastRecording;
|
||||
let testPlayback: VoiceBroadcastPlayback;
|
||||
|
||||
const mkVoiceBroadcastInfoEvent = (state: VoiceBroadcastInfoState) => {
|
||||
return mkEvent({
|
||||
event: true,
|
||||
type: VoiceBroadcastInfoEventType,
|
||||
user: client.getUserId(),
|
||||
room: roomId,
|
||||
content: {
|
||||
state,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const renderVoiceBroadcast = () => {
|
||||
render(<VoiceBroadcastBody
|
||||
mxEvent={infoEvent}
|
||||
|
@ -75,7 +62,25 @@ describe("VoiceBroadcastBody", () => {
|
|||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
infoEvent = mkVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Started);
|
||||
room = new Room(roomId, client, client.getUserId());
|
||||
mocked(client.getRoom).mockImplementation((getRoomId: string) => {
|
||||
if (getRoomId === roomId) return room;
|
||||
});
|
||||
|
||||
infoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getUserId(),
|
||||
client.getDeviceId(),
|
||||
);
|
||||
stoppedEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
client.getUserId(),
|
||||
client.getDeviceId(),
|
||||
infoEvent,
|
||||
);
|
||||
room.addEventsToTimeline([infoEvent], true, room.getLiveTimeline());
|
||||
testRecording = new VoiceBroadcastRecording(infoEvent, client);
|
||||
testPlayback = new VoiceBroadcastPlayback(infoEvent, client);
|
||||
mocked(VoiceBroadcastRecordingBody).mockImplementation(({ recording }) => {
|
||||
|
@ -107,7 +112,18 @@ describe("VoiceBroadcastBody", () => {
|
|||
);
|
||||
});
|
||||
|
||||
describe("when displaying a voice broadcast recording", () => {
|
||||
describe("when there is a stopped voice broadcast", () => {
|
||||
beforeEach(() => {
|
||||
room.addEventsToTimeline([stoppedEvent], true, room.getLiveTimeline());
|
||||
renderVoiceBroadcast();
|
||||
});
|
||||
|
||||
it("should render a voice broadcast playback body", () => {
|
||||
screen.getByTestId("voice-broadcast-playback-body");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there is a started voice broadcast from the current user", () => {
|
||||
beforeEach(() => {
|
||||
renderVoiceBroadcast();
|
||||
});
|
||||
|
@ -118,13 +134,8 @@ describe("VoiceBroadcastBody", () => {
|
|||
|
||||
describe("and the recordings ends", () => {
|
||||
beforeEach(() => {
|
||||
const stoppedEvent = mkVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Stopped);
|
||||
// get the RelationsHelper instanced used in VoiceBroadcastBody
|
||||
const relationsHelper = mocked(RelationsHelper).mock.instances[5];
|
||||
act(() => {
|
||||
// invoke the callback of the VoiceBroadcastBody hook to simulate an ended broadcast
|
||||
// @ts-ignore
|
||||
mocked(relationsHelper.on).mock.calls[0][1](stoppedEvent);
|
||||
room.addEventsToTimeline([stoppedEvent], true, room.getLiveTimeline());
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
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 { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import { PlaybackControlButton, VoiceBroadcastPlaybackState } from "../../../../src/voice-broadcast";
|
||||
|
||||
describe("PlaybackControlButton", () => {
|
||||
let onClick: () => void;
|
||||
|
||||
beforeEach(() => {
|
||||
onClick = jest.fn();
|
||||
});
|
||||
|
||||
it.each([
|
||||
[VoiceBroadcastPlaybackState.Playing],
|
||||
[VoiceBroadcastPlaybackState.Paused],
|
||||
[VoiceBroadcastPlaybackState.Stopped],
|
||||
])("should render state »%s« as expected", (state: VoiceBroadcastPlaybackState) => {
|
||||
const result = render(<PlaybackControlButton state={state} onClick={onClick} />);
|
||||
expect(result.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should call onClick on click", async () => {
|
||||
render(<PlaybackControlButton state={VoiceBroadcastPlaybackState.Playing} onClick={onClick} />);
|
||||
const button = screen.getByLabelText("pause voice broadcast");
|
||||
await userEvent.click(button);
|
||||
expect(onClick).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
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 { render, RenderResult } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import { StopButton } from "../../../../src/voice-broadcast";
|
||||
|
||||
describe("StopButton", () => {
|
||||
let result: RenderResult;
|
||||
let onClick: () => {};
|
||||
|
||||
beforeEach(() => {
|
||||
onClick = jest.fn();
|
||||
result = render(<StopButton onClick={onClick} />);
|
||||
});
|
||||
|
||||
it("should render as expected", () => {
|
||||
expect(result.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when clicking it", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(result.getByLabelText("stop voice broadcast"));
|
||||
});
|
||||
|
||||
it("should invoke the callback", () => {
|
||||
expect(onClick).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
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 { render, RenderResult, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import { VoiceBroadcastControl } from "../../../../src/voice-broadcast";
|
||||
import { Icon as StopIcon } from "../../../../res/img/element-icons/Stop.svg";
|
||||
|
||||
describe("VoiceBroadcastControl", () => {
|
||||
let result: RenderResult;
|
||||
let onClick: () => void;
|
||||
|
||||
beforeEach(() => {
|
||||
onClick = jest.fn();
|
||||
});
|
||||
|
||||
describe("when rendering it", () => {
|
||||
beforeEach(() => {
|
||||
result = render(<VoiceBroadcastControl
|
||||
onClick={onClick}
|
||||
label="test label"
|
||||
icon={StopIcon}
|
||||
/>);
|
||||
});
|
||||
|
||||
it("should render as expected", () => {
|
||||
expect(result.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when clicking it", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(screen.getByLabelText("test label"));
|
||||
});
|
||||
|
||||
it("should call onClick", () => {
|
||||
expect(onClick).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,11 +5,8 @@ exports[`LiveBadge should render the expected HTML 1`] = `
|
|||
<div
|
||||
class="mx_LiveBadge"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_live-badge"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PlaybackControlButton should render state »0« as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="resume voice broadcast"
|
||||
class="mx_AccessibleButton mx_BroadcastPlaybackControlButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_compound-secondary-content"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`PlaybackControlButton should render state »1« as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="pause voice broadcast"
|
||||
class="mx_AccessibleButton mx_BroadcastPlaybackControlButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_compound-secondary-content"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`PlaybackControlButton should render state »2« as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="resume voice broadcast"
|
||||
class="mx_AccessibleButton mx_BroadcastPlaybackControlButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_compound-secondary-content"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -1,19 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`StopButton should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="stop voice broadcast"
|
||||
class="mx_AccessibleButton mx_BroadcastPlaybackControlButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_compound-secondary-content"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,16 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`VoiceBroadcastControl when rendering it should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="test label"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -22,11 +22,8 @@ exports[`VoiceBroadcastHeader when rendering a live broadcast header with broadc
|
|||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_compound-secondary-content"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
<span>
|
||||
test user
|
||||
|
@ -35,11 +32,8 @@ exports[`VoiceBroadcastHeader when rendering a live broadcast header with broadc
|
|||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_compound-secondary-content"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Voice broadcast
|
||||
</div>
|
||||
|
@ -47,11 +41,8 @@ exports[`VoiceBroadcastHeader when rendering a live broadcast header with broadc
|
|||
<div
|
||||
class="mx_LiveBadge"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_live-badge"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
|
@ -81,11 +72,8 @@ exports[`VoiceBroadcastHeader when rendering a non-live broadcast header should
|
|||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_compound-secondary-content"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
<span>
|
||||
test user
|
||||
|
|
|
@ -64,9 +64,6 @@ describe("VoiceBroadcastPlaybackBody", () => {
|
|||
describe("when rendering a buffering voice broadcast", () => {
|
||||
beforeEach(() => {
|
||||
mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Buffering);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
renderResult = render(<VoiceBroadcastPlaybackBody playback={playback} />);
|
||||
});
|
||||
|
||||
|
@ -75,18 +72,15 @@ describe("VoiceBroadcastPlaybackBody", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("when rendering a broadcast", () => {
|
||||
describe(`when rendering a ${VoiceBroadcastPlaybackState.Stopped} broadcast`, () => {
|
||||
beforeEach(() => {
|
||||
mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Stopped);
|
||||
renderResult = render(<VoiceBroadcastPlaybackBody playback={playback} />);
|
||||
});
|
||||
|
||||
it("should render as expected", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("and clicking the play button", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(renderResult.getByLabelText("resume voice broadcast"));
|
||||
await userEvent.click(renderResult.getByLabelText("play voice broadcast"));
|
||||
});
|
||||
|
||||
it("should toggle the recording", () => {
|
||||
|
@ -94,4 +88,18 @@ describe("VoiceBroadcastPlaybackBody", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe.each([
|
||||
VoiceBroadcastPlaybackState.Paused,
|
||||
VoiceBroadcastPlaybackState.Playing,
|
||||
])("when rendering a %s broadcast", (playbackState: VoiceBroadcastPlaybackState) => {
|
||||
beforeEach(() => {
|
||||
mocked(playback.getState).mockReturnValue(playbackState);
|
||||
renderResult = render(<VoiceBroadcastPlaybackBody playback={playback} />);
|
||||
});
|
||||
|
||||
it("should render as expected", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,6 +20,7 @@ import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
|||
|
||||
import {
|
||||
VoiceBroadcastInfoEventType,
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastRecording,
|
||||
VoiceBroadcastRecordingBody,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
|
@ -49,7 +50,7 @@ describe("VoiceBroadcastRecordingBody", () => {
|
|||
room: roomId,
|
||||
user: userId,
|
||||
});
|
||||
recording = new VoiceBroadcastRecording(infoEvent, client);
|
||||
recording = new VoiceBroadcastRecording(infoEvent, client, VoiceBroadcastInfoState.Running);
|
||||
});
|
||||
|
||||
describe("when rendering a live broadcast", () => {
|
||||
|
|
|
@ -22,12 +22,12 @@ import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
|||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import {
|
||||
VoiceBroadcastInfoEventType,
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastRecording,
|
||||
VoiceBroadcastRecordingPip,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import { mkEvent, stubClient } from "../../../test-utils";
|
||||
import { stubClient } from "../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../utils/test-utils";
|
||||
|
||||
// mock RoomAvatar, because it is doing too much fancy stuff
|
||||
jest.mock("../../../../src/components/views/avatars/RoomAvatar", () => ({
|
||||
|
@ -37,39 +37,52 @@ jest.mock("../../../../src/components/views/avatars/RoomAvatar", () => ({
|
|||
}),
|
||||
}));
|
||||
|
||||
jest.mock("../../../../src/audio/VoiceRecording");
|
||||
|
||||
describe("VoiceBroadcastRecordingPip", () => {
|
||||
const userId = "@user:example.com";
|
||||
const roomId = "!room:example.com";
|
||||
let client: MatrixClient;
|
||||
let infoEvent: MatrixEvent;
|
||||
let recording: VoiceBroadcastRecording;
|
||||
let renderResult: RenderResult;
|
||||
|
||||
const renderPip = (state: VoiceBroadcastInfoState) => {
|
||||
infoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
state,
|
||||
client.getUserId(),
|
||||
client.getDeviceId(),
|
||||
);
|
||||
recording = new VoiceBroadcastRecording(infoEvent, client, state);
|
||||
renderResult = render(<VoiceBroadcastRecordingPip recording={recording} />);
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
client = stubClient();
|
||||
infoEvent = mkEvent({
|
||||
event: true,
|
||||
type: VoiceBroadcastInfoEventType,
|
||||
content: {},
|
||||
room: roomId,
|
||||
user: userId,
|
||||
});
|
||||
recording = new VoiceBroadcastRecording(infoEvent, client);
|
||||
});
|
||||
|
||||
describe("when rendering", () => {
|
||||
let renderResult: RenderResult;
|
||||
|
||||
describe("when rendering a started recording", () => {
|
||||
beforeEach(() => {
|
||||
renderResult = render(<VoiceBroadcastRecordingPip recording={recording} />);
|
||||
renderPip(VoiceBroadcastInfoState.Started);
|
||||
});
|
||||
|
||||
it("should create the expected result", () => {
|
||||
it("should render as expected", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("and clicking the pause button", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(screen.getByLabelText("pause voice broadcast"));
|
||||
});
|
||||
|
||||
it("should pause the recording", () => {
|
||||
expect(recording.getState()).toBe(VoiceBroadcastInfoState.Paused);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and clicking the stop button", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(screen.getByLabelText("stop voice broadcast"));
|
||||
await userEvent.click(screen.getByLabelText("Stop Recording"));
|
||||
// modal rendering has some weird sleeps
|
||||
await sleep(100);
|
||||
});
|
||||
|
@ -89,4 +102,24 @@ describe("VoiceBroadcastRecordingPip", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when rendering a paused recording", () => {
|
||||
beforeEach(() => {
|
||||
renderPip(VoiceBroadcastInfoState.Paused);
|
||||
});
|
||||
|
||||
it("should render as expected", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("and clicking the resume button", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(screen.getByLabelText("resume voice broadcast"));
|
||||
});
|
||||
|
||||
it("should resume the recording", () => {
|
||||
expect(recording.getState()).toBe(VoiceBroadcastInfoState.Running);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`VoiceBroadcastPlaybackBody when rendering a broadcast should render as expected 1`] = `
|
||||
exports[`VoiceBroadcastPlaybackBody when rendering a 0 broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastPlaybackBody"
|
||||
|
@ -25,11 +25,8 @@ exports[`VoiceBroadcastPlaybackBody when rendering a broadcast should render as
|
|||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_compound-secondary-content"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
<span>
|
||||
@user:example.com
|
||||
|
@ -38,11 +35,8 @@ exports[`VoiceBroadcastPlaybackBody when rendering a broadcast should render as
|
|||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_compound-secondary-content"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Voice broadcast
|
||||
</div>
|
||||
|
@ -50,11 +44,8 @@ exports[`VoiceBroadcastPlaybackBody when rendering a broadcast should render as
|
|||
<div
|
||||
class="mx_LiveBadge"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_live-badge"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
|
@ -64,15 +55,80 @@ exports[`VoiceBroadcastPlaybackBody when rendering a broadcast should render as
|
|||
>
|
||||
<div
|
||||
aria-label="resume voice broadcast"
|
||||
class="mx_AccessibleButton mx_BroadcastPlaybackControlButton"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_compound-secondary-content"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`VoiceBroadcastPlaybackBody when rendering a 1 broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastPlaybackBody"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
<span>
|
||||
@user:example.com
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Voice broadcast
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LiveBadge"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastPlaybackBody_controls"
|
||||
>
|
||||
<div
|
||||
aria-label="pause voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -105,11 +161,8 @@ exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast s
|
|||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_compound-secondary-content"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
<span>
|
||||
@user:example.com
|
||||
|
@ -118,11 +171,8 @@ exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast s
|
|||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_compound-secondary-content"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Voice broadcast
|
||||
</div>
|
||||
|
@ -130,11 +180,8 @@ exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast s
|
|||
<div
|
||||
class="mx_LiveBadge"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_live-badge"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
|
@ -148,6 +195,7 @@ exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast s
|
|||
<div
|
||||
aria-label="Loading..."
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
|
|
|
@ -25,11 +25,8 @@ exports[`VoiceBroadcastRecordingBody when rendering a live broadcast should rend
|
|||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_compound-secondary-content"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
<span>
|
||||
@user:example.com
|
||||
|
@ -39,11 +36,8 @@ exports[`VoiceBroadcastRecordingBody when rendering a live broadcast should rend
|
|||
<div
|
||||
class="mx_LiveBadge"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_live-badge"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`VoiceBroadcastRecordingPip when rendering should create the expected result 1`] = `
|
||||
exports[`VoiceBroadcastRecordingPip when rendering a paused recording should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastRecordingPip"
|
||||
|
@ -25,25 +25,19 @@ exports[`VoiceBroadcastRecordingPip when rendering should create the expected re
|
|||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_compound-secondary-content"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
<span>
|
||||
@user:example.com
|
||||
@userId:matrix.org
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LiveBadge"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_live-badge"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
|
@ -55,16 +49,96 @@ exports[`VoiceBroadcastRecordingPip when rendering should create the expected re
|
|||
class="mx_VoiceBroadcastRecordingPip_controls"
|
||||
>
|
||||
<div
|
||||
aria-label="stop voice broadcast"
|
||||
class="mx_AccessibleButton mx_BroadcastPlaybackControlButton"
|
||||
aria-label="resume voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl mx_VoiceBroadcastControl-recording"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_compound-secondary-content"
|
||||
role="presentation"
|
||||
style="mask-image: url(\\"image-file-stub\\");"
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Stop Recording"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`VoiceBroadcastRecordingPip when rendering a started recording should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastRecordingPip"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
<span>
|
||||
@userId:matrix.org
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LiveBadge"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
</div>
|
||||
<hr
|
||||
class="mx_VoiceBroadcastRecordingPip_divider"
|
||||
/>
|
||||
<div
|
||||
class="mx_VoiceBroadcastRecordingPip_controls"
|
||||
>
|
||||
<div
|
||||
aria-label="pause voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Stop Recording"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
EventType,
|
||||
MatrixClient,
|
||||
MatrixEvent,
|
||||
MatrixEventEvent,
|
||||
MsgType,
|
||||
RelationType,
|
||||
Room,
|
||||
|
@ -81,6 +82,7 @@ describe("VoiceBroadcastRecording", () => {
|
|||
const setUpVoiceBroadcastRecording = () => {
|
||||
voiceBroadcastRecording = new VoiceBroadcastRecording(infoEvent, client);
|
||||
voiceBroadcastRecording.on(VoiceBroadcastRecordingEvent.StateChanged, onStateChanged);
|
||||
jest.spyOn(voiceBroadcastRecording, "destroy");
|
||||
jest.spyOn(voiceBroadcastRecording, "removeAllListeners");
|
||||
};
|
||||
|
||||
|
@ -90,6 +92,25 @@ describe("VoiceBroadcastRecording", () => {
|
|||
});
|
||||
};
|
||||
|
||||
const itShouldSendAnInfoEvent = (state: VoiceBroadcastInfoState) => {
|
||||
it(`should send a ${state} info event`, () => {
|
||||
expect(client.sendStateEvent).toHaveBeenCalledWith(
|
||||
roomId,
|
||||
VoiceBroadcastInfoEventType,
|
||||
{
|
||||
|
||||
device_id: client.getDeviceId(),
|
||||
state,
|
||||
["m.relates_to"]: {
|
||||
rel_type: RelationType.Reference,
|
||||
event_id: infoEvent.getId(),
|
||||
},
|
||||
},
|
||||
client.getUserId(),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
room = mkStubRoom(roomId, "Test Room", client);
|
||||
|
@ -214,6 +235,18 @@ describe("VoiceBroadcastRecording", () => {
|
|||
expect(voiceBroadcastRecorder.start).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("and the info event is redacted", () => {
|
||||
beforeEach(() => {
|
||||
infoEvent.emit(MatrixEventEvent.BeforeRedaction, null, null);
|
||||
});
|
||||
|
||||
itShouldBeInState(VoiceBroadcastInfoState.Stopped);
|
||||
|
||||
it("should destroy the recording", () => {
|
||||
expect(voiceBroadcastRecording.destroy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and receiving a call action", () => {
|
||||
beforeEach(() => {
|
||||
dis.dispatch({
|
||||
|
@ -341,6 +374,26 @@ describe("VoiceBroadcastRecording", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe.each([
|
||||
["pause", async () => voiceBroadcastRecording.pause()],
|
||||
["toggle", async () => voiceBroadcastRecording.toggle()],
|
||||
])("and calling %s", (_case: string, action: Function) => {
|
||||
beforeEach(async () => {
|
||||
await action();
|
||||
});
|
||||
|
||||
itShouldBeInState(VoiceBroadcastInfoState.Paused);
|
||||
itShouldSendAnInfoEvent(VoiceBroadcastInfoState.Paused);
|
||||
|
||||
it("should stop the recorder", () => {
|
||||
expect(mocked(voiceBroadcastRecorder.stop)).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should emit a paused state changed event", () => {
|
||||
expect(onStateChanged).toHaveBeenCalledWith(VoiceBroadcastInfoState.Paused);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and calling destroy", () => {
|
||||
beforeEach(() => {
|
||||
voiceBroadcastRecording.destroy();
|
||||
|
@ -356,6 +409,32 @@ describe("VoiceBroadcastRecording", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("and it is in paused state", () => {
|
||||
beforeEach(async () => {
|
||||
await voiceBroadcastRecording.pause();
|
||||
});
|
||||
|
||||
describe.each([
|
||||
["resume", async () => voiceBroadcastRecording.resume()],
|
||||
["toggle", async () => voiceBroadcastRecording.toggle()],
|
||||
])("and calling %s", (_case: string, action: Function) => {
|
||||
beforeEach(async () => {
|
||||
await action();
|
||||
});
|
||||
|
||||
itShouldBeInState(VoiceBroadcastInfoState.Running);
|
||||
itShouldSendAnInfoEvent(VoiceBroadcastInfoState.Running);
|
||||
|
||||
it("should start the recorder", () => {
|
||||
expect(mocked(voiceBroadcastRecorder.start)).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should emit a running state changed event", () => {
|
||||
expect(onStateChanged).toHaveBeenCalledWith(VoiceBroadcastInfoState.Running);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when created for a Voice Broadcast Info with a Stopped relation", () => {
|
||||
|
@ -363,7 +442,7 @@ describe("VoiceBroadcastRecording", () => {
|
|||
infoEvent = mkVoiceBroadcastInfoEvent({
|
||||
device_id: client.getDeviceId(),
|
||||
state: VoiceBroadcastInfoState.Started,
|
||||
chunk_length: 300,
|
||||
chunk_length: 120,
|
||||
});
|
||||
|
||||
const relationsContainer = {
|
||||
|
|
|
@ -18,28 +18,22 @@ import { mocked } from "jest-mock";
|
|||
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import {
|
||||
VoiceBroadcastInfoEventType,
|
||||
VoiceBroadcastRecordingsStore,
|
||||
VoiceBroadcastRecordingsStoreEvent,
|
||||
VoiceBroadcastRecording,
|
||||
VoiceBroadcastInfoState,
|
||||
} from "../../../src/voice-broadcast";
|
||||
import { mkEvent, mkStubRoom, stubClient } from "../../test-utils";
|
||||
|
||||
jest.mock("../../../src/voice-broadcast/models/VoiceBroadcastRecording.ts", () => ({
|
||||
VoiceBroadcastRecording: jest.fn().mockImplementation(
|
||||
(
|
||||
infoEvent: MatrixEvent,
|
||||
client: MatrixClient,
|
||||
) => ({ infoEvent, client }),
|
||||
),
|
||||
}));
|
||||
import { mkStubRoom, stubClient } from "../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../utils/test-utils";
|
||||
|
||||
describe("VoiceBroadcastRecordingsStore", () => {
|
||||
const roomId = "!room:example.com";
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
let infoEvent: MatrixEvent;
|
||||
let otherInfoEvent: MatrixEvent;
|
||||
let recording: VoiceBroadcastRecording;
|
||||
let otherRecording: VoiceBroadcastRecording;
|
||||
let recordings: VoiceBroadcastRecordingsStore;
|
||||
let onCurrentChanged: (recording: VoiceBroadcastRecording) => void;
|
||||
|
||||
|
@ -51,22 +45,27 @@ describe("VoiceBroadcastRecordingsStore", () => {
|
|||
return room;
|
||||
}
|
||||
});
|
||||
infoEvent = mkEvent({
|
||||
event: true,
|
||||
type: VoiceBroadcastInfoEventType,
|
||||
user: client.getUserId(),
|
||||
room: roomId,
|
||||
content: {},
|
||||
});
|
||||
recording = {
|
||||
infoEvent,
|
||||
} as unknown as VoiceBroadcastRecording;
|
||||
infoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getUserId(),
|
||||
client.getDeviceId(),
|
||||
);
|
||||
otherInfoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getUserId(),
|
||||
client.getDeviceId(),
|
||||
);
|
||||
recording = new VoiceBroadcastRecording(infoEvent, client);
|
||||
otherRecording = new VoiceBroadcastRecording(otherInfoEvent, client);
|
||||
recordings = new VoiceBroadcastRecordingsStore();
|
||||
onCurrentChanged = jest.fn();
|
||||
recordings.on(VoiceBroadcastRecordingsStoreEvent.CurrentChanged, onCurrentChanged);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
recording.destroy();
|
||||
recordings.off(VoiceBroadcastRecordingsStoreEvent.CurrentChanged, onCurrentChanged);
|
||||
});
|
||||
|
||||
|
@ -110,6 +109,32 @@ describe("VoiceBroadcastRecordingsStore", () => {
|
|||
it("should emit a current changed event", () => {
|
||||
expect(onCurrentChanged).toHaveBeenCalledWith(null);
|
||||
});
|
||||
|
||||
it("and calling it again should work", () => {
|
||||
recordings.clearCurrent();
|
||||
expect(recordings.getCurrent()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and setting another recording and stopping the previous recording", () => {
|
||||
beforeEach(() => {
|
||||
recordings.setCurrent(otherRecording);
|
||||
recording.stop();
|
||||
});
|
||||
|
||||
it("should keep the current recording", () => {
|
||||
expect(recordings.getCurrent()).toBe(otherRecording);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and the recording stops", () => {
|
||||
beforeEach(() => {
|
||||
recording.stop();
|
||||
});
|
||||
|
||||
it("should clear the current recording", () => {
|
||||
expect(recordings.getCurrent()).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -133,10 +158,7 @@ describe("VoiceBroadcastRecordingsStore", () => {
|
|||
});
|
||||
|
||||
it("should return the recording", () => {
|
||||
expect(returnedRecording).toEqual({
|
||||
infoEvent,
|
||||
client,
|
||||
});
|
||||
expect(returnedRecording.infoEvent).toBe(infoEvent);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
111
test/voice-broadcast/utils/VoiceBroadcastResumer-test.ts
Normal file
111
test/voice-broadcast/utils/VoiceBroadcastResumer-test.ts
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
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 { mocked } from "jest-mock";
|
||||
import { ClientEvent, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import {
|
||||
findRoomLiveVoiceBroadcastFromUserAndDevice,
|
||||
resumeVoiceBroadcastInRoom,
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastResumer,
|
||||
} from "../../../src/voice-broadcast";
|
||||
import { stubClient } from "../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "./test-utils";
|
||||
|
||||
jest.mock("../../../src/voice-broadcast/utils/findRoomLiveVoiceBroadcastFromUserAndDevice");
|
||||
jest.mock("../../../src/voice-broadcast/utils/resumeVoiceBroadcastInRoom");
|
||||
|
||||
describe("VoiceBroadcastResumer", () => {
|
||||
const roomId = "!room:example.com";
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
let resumer: VoiceBroadcastResumer;
|
||||
let infoEvent: MatrixEvent;
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
jest.spyOn(client, "off");
|
||||
room = new Room(roomId, client, client.getUserId());
|
||||
mocked(client.getRoom).mockImplementation((getRoomId: string) => {
|
||||
if (getRoomId === roomId) return room;
|
||||
});
|
||||
resumer = new VoiceBroadcastResumer(client);
|
||||
infoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getUserId(),
|
||||
client.getDeviceId(),
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("when there is no info event", () => {
|
||||
beforeEach(() => {
|
||||
client.emit(ClientEvent.Room, room);
|
||||
});
|
||||
|
||||
it("should not resume a broadcast", () => {
|
||||
expect(resumeVoiceBroadcastInRoom).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there is an info event", () => {
|
||||
beforeEach(() => {
|
||||
mocked(findRoomLiveVoiceBroadcastFromUserAndDevice).mockImplementation((
|
||||
findRoom: Room,
|
||||
userId: string,
|
||||
deviceId: string,
|
||||
) => {
|
||||
if (findRoom === room && userId === client.getUserId() && deviceId === client.getDeviceId()) {
|
||||
return infoEvent;
|
||||
}
|
||||
});
|
||||
client.emit(ClientEvent.Room, room);
|
||||
});
|
||||
|
||||
it("should resume a broadcast", () => {
|
||||
expect(resumeVoiceBroadcastInRoom).toHaveBeenCalledWith(
|
||||
infoEvent,
|
||||
room,
|
||||
client,
|
||||
);
|
||||
});
|
||||
|
||||
describe("and emitting a room event again", () => {
|
||||
beforeEach(() => {
|
||||
client.emit(ClientEvent.Room, room);
|
||||
});
|
||||
|
||||
it("should not resume the broadcast again", () => {
|
||||
expect(resumeVoiceBroadcastInRoom).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when calling destroy", () => {
|
||||
beforeEach(() => {
|
||||
resumer.destroy();
|
||||
});
|
||||
|
||||
it("should deregister from the client", () => {
|
||||
expect(client.off).toHaveBeenCalledWith(ClientEvent.Room, expect.any(Function));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -23,7 +23,30 @@ exports[`startNewVoiceBroadcastRecording when the current user is allowed to sen
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`startNewVoiceBroadcastRecording when the current user is allowed to send voice broadcast info state events when there already is a live broadcast of the current user should show an info dialog 1`] = `
|
||||
exports[`startNewVoiceBroadcastRecording when the current user is allowed to send voice broadcast info state events when there already is a live broadcast of the current user in the room should show an info dialog 1`] = `
|
||||
[MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
[Function],
|
||||
Object {
|
||||
"description": <p>
|
||||
You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.
|
||||
</p>,
|
||||
"hasCloseButton": true,
|
||||
"title": "Can't start a new voice broadcast",
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`startNewVoiceBroadcastRecording when the current user is allowed to send voice broadcast info state events when there is already a current voice broadcast should show an info dialog 1`] = `
|
||||
[MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
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 { mocked } from "jest-mock";
|
||||
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import {
|
||||
findRoomLiveVoiceBroadcastFromUserAndDevice,
|
||||
VoiceBroadcastInfoEventType,
|
||||
VoiceBroadcastInfoState,
|
||||
} from "../../../src/voice-broadcast";
|
||||
import { mkEvent, stubClient } from "../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "./test-utils";
|
||||
|
||||
describe("findRoomLiveVoiceBroadcastFromUserAndDevice", () => {
|
||||
const roomId = "!room:example.com";
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
|
||||
const itShouldReturnNull = () => {
|
||||
it("should return null", () => {
|
||||
expect(findRoomLiveVoiceBroadcastFromUserAndDevice(
|
||||
room,
|
||||
client.getUserId(),
|
||||
client.getDeviceId(),
|
||||
)).toBeNull();
|
||||
});
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
client = stubClient();
|
||||
room = new Room(roomId, client, client.getUserId());
|
||||
jest.spyOn(room.currentState, "getStateEvents");
|
||||
mocked(client.getRoom).mockImplementation((getRoomId: string) => {
|
||||
if (getRoomId === roomId) return room;
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there is no info event", () => {
|
||||
itShouldReturnNull();
|
||||
});
|
||||
|
||||
describe("when there is an info event without content", () => {
|
||||
beforeEach(() => {
|
||||
room.currentState.setStateEvents([
|
||||
mkEvent({
|
||||
event: true,
|
||||
type: VoiceBroadcastInfoEventType,
|
||||
room: roomId,
|
||||
user: client.getUserId(),
|
||||
content: {},
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
itShouldReturnNull();
|
||||
});
|
||||
|
||||
describe("when there is a stopped info event", () => {
|
||||
beforeEach(() => {
|
||||
room.currentState.setStateEvents([
|
||||
mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
client.getUserId(),
|
||||
client.getDeviceId(),
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
itShouldReturnNull();
|
||||
});
|
||||
|
||||
describe("when there is a started info event from another device", () => {
|
||||
beforeEach(() => {
|
||||
const event = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
client.getUserId(),
|
||||
"JKL123",
|
||||
);
|
||||
room.currentState.setStateEvents([event]);
|
||||
});
|
||||
|
||||
itShouldReturnNull();
|
||||
});
|
||||
|
||||
describe("when there is a started info event", () => {
|
||||
let event: MatrixEvent;
|
||||
|
||||
beforeEach(() => {
|
||||
event = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getUserId(),
|
||||
client.getDeviceId(),
|
||||
);
|
||||
room.currentState.setStateEvents([event]);
|
||||
});
|
||||
|
||||
it("should return this event", () => {
|
||||
expect(room.currentState.getStateEvents).toHaveBeenCalledWith(
|
||||
VoiceBroadcastInfoEventType,
|
||||
client.getUserId(),
|
||||
);
|
||||
|
||||
expect(findRoomLiveVoiceBroadcastFromUserAndDevice(
|
||||
room,
|
||||
client.getUserId(),
|
||||
client.getDeviceId(),
|
||||
)).toBe(event);
|
||||
});
|
||||
});
|
||||
});
|
60
test/voice-broadcast/utils/getChunkLength-test.ts
Normal file
60
test/voice-broadcast/utils/getChunkLength-test.ts
Normal 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 { mocked } from "jest-mock";
|
||||
|
||||
import SdkConfig, { DEFAULTS } from "../../../src/SdkConfig";
|
||||
import { getChunkLength } from "../../../src/voice-broadcast/utils/getChunkLength";
|
||||
|
||||
jest.mock("../../../src/SdkConfig");
|
||||
|
||||
describe("getChunkLength", () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe("when there is a value provided by Sdk config", () => {
|
||||
beforeEach(() => {
|
||||
mocked(SdkConfig.get).mockReturnValue({ chunk_length: 42 });
|
||||
});
|
||||
|
||||
it("should return this value", () => {
|
||||
expect(getChunkLength()).toBe(42);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when Sdk config does not provide a value", () => {
|
||||
beforeEach(() => {
|
||||
DEFAULTS.voice_broadcast = {
|
||||
chunk_length: 23,
|
||||
};
|
||||
});
|
||||
|
||||
it("should return this value", () => {
|
||||
expect(getChunkLength()).toBe(23);
|
||||
});
|
||||
});
|
||||
|
||||
describe("if there are no defaults", () => {
|
||||
beforeEach(() => {
|
||||
DEFAULTS.voice_broadcast = undefined;
|
||||
});
|
||||
|
||||
it("should return the fallback value", () => {
|
||||
expect(getChunkLength()).toBe(120);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -35,7 +35,12 @@ describe("hasRoomLiveVoiceBroadcast", () => {
|
|||
sender: string,
|
||||
) => {
|
||||
room.currentState.setStateEvents([
|
||||
mkVoiceBroadcastInfoStateEvent(room.roomId, state, sender),
|
||||
mkVoiceBroadcastInfoStateEvent(
|
||||
room.roomId,
|
||||
state,
|
||||
sender,
|
||||
"ASD123",
|
||||
),
|
||||
]);
|
||||
};
|
||||
|
||||
|
|
110
test/voice-broadcast/utils/resumeVoiceBroadcastInRoom-test.ts
Normal file
110
test/voice-broadcast/utils/resumeVoiceBroadcastInRoom-test.ts
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
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 { mocked } from "jest-mock";
|
||||
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import {
|
||||
resumeVoiceBroadcastInRoom,
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastRecording,
|
||||
VoiceBroadcastRecordingsStore,
|
||||
} from "../../../src/voice-broadcast";
|
||||
import { stubClient } from "../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../utils/test-utils";
|
||||
|
||||
const mockRecording = jest.fn();
|
||||
|
||||
jest.mock("../../../src/voice-broadcast/models/VoiceBroadcastRecording", () => ({
|
||||
...jest.requireActual("../../../src/voice-broadcast/models/VoiceBroadcastRecording") as object,
|
||||
VoiceBroadcastRecording: jest.fn().mockImplementation(() => mockRecording),
|
||||
}));
|
||||
|
||||
describe("resumeVoiceBroadcastInRoom", () => {
|
||||
let client: MatrixClient;
|
||||
const roomId = "!room:example.com";
|
||||
let room: Room;
|
||||
let startedInfoEvent: MatrixEvent;
|
||||
let stoppedInfoEvent: MatrixEvent;
|
||||
|
||||
const itShouldStartAPausedRecording = () => {
|
||||
it("should start a paused recording", () => {
|
||||
expect(VoiceBroadcastRecording).toHaveBeenCalledWith(
|
||||
startedInfoEvent,
|
||||
client,
|
||||
VoiceBroadcastInfoState.Paused,
|
||||
);
|
||||
expect(VoiceBroadcastRecordingsStore.instance().setCurrent).toHaveBeenCalledWith(mockRecording);
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
room = new Room(roomId, client, client.getUserId());
|
||||
jest.spyOn(room, "findEventById");
|
||||
jest.spyOn(VoiceBroadcastRecordingsStore.instance(), "setCurrent").mockImplementation();
|
||||
|
||||
startedInfoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getUserId(),
|
||||
client.getDeviceId(),
|
||||
);
|
||||
|
||||
stoppedInfoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
client.getUserId(),
|
||||
client.getDeviceId(),
|
||||
startedInfoEvent,
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("when called with a stopped info event", () => {
|
||||
describe("and there is a related event", () => {
|
||||
beforeEach(() => {
|
||||
mocked(room.findEventById).mockReturnValue(startedInfoEvent);
|
||||
resumeVoiceBroadcastInRoom(stoppedInfoEvent, room, client);
|
||||
});
|
||||
|
||||
itShouldStartAPausedRecording();
|
||||
});
|
||||
|
||||
describe("and there is no related event", () => {
|
||||
beforeEach(() => {
|
||||
mocked(room.findEventById).mockReturnValue(null);
|
||||
resumeVoiceBroadcastInRoom(stoppedInfoEvent, room, client);
|
||||
});
|
||||
|
||||
it("should not start a broadcast", () => {
|
||||
expect(VoiceBroadcastRecording).not.toHaveBeenCalled();
|
||||
expect(VoiceBroadcastRecordingsStore.instance().setCurrent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when called with a started info event", () => {
|
||||
beforeEach(() => {
|
||||
resumeVoiceBroadcastInRoom(startedInfoEvent, room, client);
|
||||
});
|
||||
|
||||
itShouldStartAPausedRecording();
|
||||
});
|
||||
});
|
|
@ -67,9 +67,15 @@ describe("startNewVoiceBroadcastRecording", () => {
|
|||
|
||||
recordingsStore = {
|
||||
setCurrent: jest.fn(),
|
||||
getCurrent: jest.fn(),
|
||||
} as unknown as VoiceBroadcastRecordingsStore;
|
||||
|
||||
infoEvent = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Started, client.getUserId());
|
||||
infoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getUserId(),
|
||||
client.getDeviceId(),
|
||||
);
|
||||
otherEvent = mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomMember,
|
||||
|
@ -121,7 +127,7 @@ describe("startNewVoiceBroadcastRecording", () => {
|
|||
roomId,
|
||||
VoiceBroadcastInfoEventType,
|
||||
{
|
||||
chunk_length: 300,
|
||||
chunk_length: 120,
|
||||
device_id: client.getDeviceId(),
|
||||
state: VoiceBroadcastInfoState.Started,
|
||||
},
|
||||
|
@ -132,10 +138,33 @@ describe("startNewVoiceBroadcastRecording", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("when there already is a live broadcast of the current user", () => {
|
||||
describe("when there is already a current voice broadcast", () => {
|
||||
beforeEach(async () => {
|
||||
mocked(recordingsStore.getCurrent).mockReturnValue(
|
||||
new VoiceBroadcastRecording(infoEvent, client),
|
||||
);
|
||||
|
||||
result = await startNewVoiceBroadcastRecording(room, client, recordingsStore);
|
||||
});
|
||||
|
||||
it("should not start a voice broadcast", () => {
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should show an info dialog", () => {
|
||||
expect(Modal.createDialog).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there already is a live broadcast of the current user in the room", () => {
|
||||
beforeEach(async () => {
|
||||
room.currentState.setStateEvents([
|
||||
mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Running, client.getUserId()),
|
||||
mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Running,
|
||||
client.getUserId(),
|
||||
client.getDeviceId(),
|
||||
),
|
||||
]);
|
||||
|
||||
result = await startNewVoiceBroadcastRecording(room, client, recordingsStore);
|
||||
|
@ -153,7 +182,12 @@ describe("startNewVoiceBroadcastRecording", () => {
|
|||
describe("when there already is a live broadcast of another user", () => {
|
||||
beforeEach(async () => {
|
||||
room.currentState.setStateEvents([
|
||||
mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Running, otherUserId),
|
||||
mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Running,
|
||||
otherUserId,
|
||||
"ASD123",
|
||||
),
|
||||
]);
|
||||
|
||||
result = await startNewVoiceBroadcastRecording(room, client, recordingsStore);
|
||||
|
|
|
@ -22,16 +22,29 @@ import { mkEvent } from "../../test-utils";
|
|||
export const mkVoiceBroadcastInfoStateEvent = (
|
||||
roomId: string,
|
||||
state: VoiceBroadcastInfoState,
|
||||
sender: string,
|
||||
senderId: string,
|
||||
senderDeviceId: string,
|
||||
startedInfoEvent?: MatrixEvent,
|
||||
): MatrixEvent => {
|
||||
const relationContent = {};
|
||||
|
||||
if (startedInfoEvent) {
|
||||
relationContent["m.relates_to"] = {
|
||||
event_id: startedInfoEvent.getId(),
|
||||
rel_type: "m.reference",
|
||||
};
|
||||
}
|
||||
|
||||
return mkEvent({
|
||||
event: true,
|
||||
room: roomId,
|
||||
user: sender,
|
||||
user: senderId,
|
||||
type: VoiceBroadcastInfoEventType,
|
||||
skey: sender,
|
||||
skey: senderId,
|
||||
content: {
|
||||
state,
|
||||
device_id: senderDeviceId,
|
||||
...relationContent,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue