Merge branch 'develop' into staging

This commit is contained in:
RiotRobot 2023-02-22 10:59:22 +00:00
commit 9d6ad99bfc
60 changed files with 870 additions and 717 deletions

View File

@ -6,7 +6,5 @@ concurrency: ${{ github.workflow }}-${{ github.event.pull_request.head.ref }}
jobs:
action:
uses: matrix-org/matrix-js-sdk/.github/workflows/pull_request.yaml@develop
with:
labels: "T-Defect,T-Enhancement,T-Task"
secrets:
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}

View File

@ -111,7 +111,7 @@
"react-transition-group": "^4.4.1",
"rfc4648": "^1.4.0",
"sanitize-filename": "^1.6.3",
"sanitize-html": "^2.3.2",
"sanitize-html": "2.8.0",
"tar-js": "^0.3.0",
"ua-parser-js": "^1.0.2",
"url": "^0.11.0",
@ -167,9 +167,8 @@
"@types/react": "17.0.49",
"@types/react-beautiful-dnd": "^13.0.0",
"@types/react-dom": "17.0.17",
"@types/react-test-renderer": "^17.0.1",
"@types/react-transition-group": "^4.4.0",
"@types/sanitize-html": "^2.3.1",
"@types/sanitize-html": "2.8.0",
"@types/tar-js": "^0.3.2",
"@types/ua-parser-js": "^0.7.36",
"@types/zxcvbn": "^4.4.0",
@ -212,7 +211,6 @@
"postcss-scss": "^4.0.4",
"prettier": "2.8.0",
"raw-loader": "^4.0.2",
"react-test-renderer": "^17.0.2",
"rimraf": "^3.0.2",
"stylelint": "^14.9.1",
"stylelint-config-prettier": "^9.0.4",

View File

@ -38,9 +38,12 @@ limitations under the License.
cursor: pointer;
margin-left: 20px;
display: block;
/* If the copy button is used within a scrollable div, make it stick to the right while scrolling */
position: sticky;
right: 0;
/* center to first line */
position: relative;
top: 0.15em;
background-color: $background;
&::before {
content: "";

View File

@ -28,4 +28,9 @@ limitations under the License.
margin-bottom: $spacing-16;
}
}
/* prevent the access token from overflowing the text box */
div .mx_CopyableText {
overflow: scroll;
}
}

View File

@ -158,12 +158,12 @@ const BeaconViewDialog: React.FC<IProps> = ({ initialFocusedBeacon, roomId, matr
)}
{mapDisplayError && <MapError error={mapDisplayError.message as LocationShareError} isMinimised />}
{!centerGeoUri && !mapDisplayError && (
<MapFallback data-test-id="beacon-view-dialog-map-fallback" className="mx_BeaconViewDialog_map">
<MapFallback data-testid="beacon-view-dialog-map-fallback" className="mx_BeaconViewDialog_map">
<span className="mx_BeaconViewDialog_mapFallbackMessage">{_t("No live locations")}</span>
<AccessibleButton
kind="primary"
onClick={onFinished}
data-test-id="beacon-view-dialog-fallback-close"
data-testid="beacon-view-dialog-fallback-close"
>
{_t("Close")}
</AccessibleButton>
@ -179,7 +179,7 @@ const BeaconViewDialog: React.FC<IProps> = ({ initialFocusedBeacon, roomId, matr
<AccessibleButton
kind="primary"
onClick={() => setSidebarOpen(true)}
data-test-id="beacon-view-dialog-open-sidebar"
data-testid="beacon-view-dialog-open-sidebar"
className="mx_BeaconViewDialog_viewListButton"
>
<LiveLocationIcon height={12} />

View File

@ -14,14 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { ReactNode } from "react";
import React from "react";
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import { _t } from "../../../languageHandler";
import { IBodyProps } from "./IBodyProps";
// A placeholder element for messages that could not be decrypted
export default class DecryptionFailureBody extends React.Component<Partial<IBodyProps>> {
public render(): ReactNode {
return <div className="mx_DecryptionFailureBody mx_EventTile_content">{_t("Unable to decrypt message")}</div>;
}
function getErrorMessage(mxEvent?: MatrixEvent): string {
return mxEvent?.isEncryptedDisabledForUnverifiedDevices
? _t("The sender has blocked you from receiving this message")
: _t("Unable to decrypt message");
}
// A placeholder element for messages that could not be decrypted
export function DecryptionFailureBody({ mxEvent }: Partial<IBodyProps>): JSX.Element {
return <div className="mx_DecryptionFailureBody mx_EventTile_content">{getErrorMessage(mxEvent)}</div>;
}

View File

@ -182,12 +182,14 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
private addListeners(): void {
this.state.poll?.on(PollEvent.Responses, this.onResponsesChange);
this.state.poll?.on(PollEvent.End, this.onRelationsChange);
this.state.poll?.on(PollEvent.UndecryptableRelations, this.render.bind(this));
}
private removeListeners(): void {
if (this.state.poll) {
this.state.poll.off(PollEvent.Responses, this.onResponsesChange);
this.state.poll.off(PollEvent.End, this.onRelationsChange);
this.state.poll.off(PollEvent.UndecryptableRelations, this.render.bind(this));
}
}
@ -297,7 +299,9 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
const showResults = poll.isEnded || (disclosed && myVote !== undefined);
let totalText: string;
if (poll.isEnded) {
if (showResults && poll.undecryptableRelationsCount) {
totalText = _t("Due to decryption errors, some votes may not be counted");
} else if (poll.isEnded) {
totalText = _t("Final result based on %(count)s votes", { count: totalVotes });
} else if (!disclosed) {
totalText = _t("Results will be visible when the poll is ended");

View File

@ -21,7 +21,9 @@ import { logger } from "matrix-js-sdk/src/logger";
import { Icon as PollIcon } from "../../../../res/img/element-icons/room/composer/poll.svg";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { _t } from "../../../languageHandler";
import { textForEvent } from "../../../TextForEvent";
import { Caption } from "../typography/Caption";
import { IBodyProps } from "./IBodyProps";
import MPollBody from "./MPollBody";
@ -105,5 +107,10 @@ export const MPollEndBody = React.forwardRef<any, IBodyProps>(({ mxEvent, ...pro
);
}
return <MPollBody mxEvent={pollStartEvent} {...props} />;
return (
<div>
<Caption>{_t("Ended a poll")}</Caption>
<MPollBody mxEvent={pollStartEvent} {...props} />
</div>
);
});

View File

@ -41,7 +41,7 @@ import { MPollEndBody } from "./MPollEndBody";
import MLocationBody from "./MLocationBody";
import MjolnirBody from "./MjolnirBody";
import MBeaconBody from "./MBeaconBody";
import DecryptionFailureBody from "./DecryptionFailureBody";
import { DecryptionFailureBody } from "./DecryptionFailureBody";
import { GetRelationsForEvent, IEventTileOps } from "../rooms/EventTile";
import { VoiceBroadcastBody, VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "../../../voice-broadcast";

View File

@ -399,7 +399,7 @@ export default class AliasSettings extends React.Component<IProps, IState> {
return (
<div className="mx_AliasSettings">
<SettingsFieldset
data-test-id="published-address-fieldset"
data-testid="published-address-fieldset"
legend={_t("Published Addresses")}
description={
<>
@ -450,7 +450,7 @@ export default class AliasSettings extends React.Component<IProps, IState> {
/>
</SettingsFieldset>
<SettingsFieldset
data-test-id="local-address-fieldset"
data-testid="local-address-fieldset"
legend={_t("Local Addresses")}
description={
isSpaceRoom

View File

@ -35,7 +35,7 @@ import { Layout } from "../../../settings/enums/Layout";
import { formatTime } from "../../../DateUtils";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import DecryptionFailureBody from "../messages/DecryptionFailureBody";
import { DecryptionFailureBody } from "../messages/DecryptionFailureBody";
import { E2EState } from "./E2EIcon";
import RoomAvatar from "../avatars/RoomAvatar";
import MessageContextMenu from "../context_menus/MessageContextMenu";
@ -1270,7 +1270,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
{this.props.mxEvent.isRedacted() ? (
<RedactedBody mxEvent={this.props.mxEvent} />
) : this.props.mxEvent.isDecryptionFailure() ? (
<DecryptionFailureBody />
<DecryptionFailureBody mxEvent={this.props.mxEvent} />
) : (
MessagePreviewStore.instance.generatePreviewForEvent(this.props.mxEvent)
)}

View File

@ -89,6 +89,8 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
"useOnlyCurrentProfiles",
];
private static ROOM_DIRECTORY_SETTINGS = ["SpotlightSearch.showNsfwPublicRooms"];
private static GENERAL_SETTINGS = [
"promptBeforeInviteUnknownUsers",
// Start automatically after startup (electron-only)
@ -234,6 +236,11 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
{this.renderGroup(PreferencesUserSettingsTab.TIMELINE_SETTINGS)}
</div>
<div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Room directory")}</span>
{this.renderGroup(PreferencesUserSettingsTab.ROOM_DIRECTORY_SETTINGS)}
</div>
<div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("General")}</span>
{this.renderGroup(PreferencesUserSettingsTab.GENERAL_SETTINGS)}

View File

@ -93,7 +93,7 @@ const SpaceSettingsVisibilityTab: React.FC<IProps> = ({ matrixClient: cli, space
advancedSection = (
<div>
<AccessibleButton
data-test-id="toggle-guest-access-btn"
data-testid="toggle-guest-access-btn"
onClick={toggleAdvancedSection}
kind="link"
className="mx_SettingsTab_showAdvanced"
@ -141,13 +141,13 @@ const SpaceSettingsVisibilityTab: React.FC<IProps> = ({ matrixClient: cli, space
<div className="mx_SettingsTab_heading">{_t("Visibility")}</div>
{error && (
<div data-test-id="space-settings-error" className="mx_SpaceRoomView_errorText">
<div data-testid="space-settings-error" className="mx_SpaceRoomView_errorText">
{error}
</div>
)}
<SettingsFieldset
data-test-id="access-fieldset"
data-testid="access-fieldset"
legend={_t("Access")}
description={_t("Decide who can view and join %(spaceName)s.", { spaceName: space.name })}
>

View File

@ -25,6 +25,7 @@ import SdkConfig from "../SdkConfig";
import SettingsStore from "../settings/SettingsStore";
import { Protocols } from "../utils/DirectoryUtils";
import { useLatestResult } from "./useLatestResult";
import { useSettingValue } from "./useSettings";
export const ALL_ROOMS = "ALL_ROOMS";
const LAST_SERVER_KEY = "mx_last_room_directory_server";
@ -38,6 +39,10 @@ export interface IPublicRoomsOpts {
let thirdParty: Protocols;
const NSFW_KEYWORD = "nsfw";
const cheapNsfwFilter = (room: IPublicRoomsChunkRoom): boolean =>
!room.name?.toLocaleLowerCase().includes(NSFW_KEYWORD) && !room.topic?.toLocaleLowerCase().includes(NSFW_KEYWORD);
export const usePublicRoomDirectory = (): {
ready: boolean;
loading: boolean;
@ -58,6 +63,8 @@ export const usePublicRoomDirectory = (): {
const [updateQuery, updateResult] = useLatestResult<IRoomDirectoryOptions, IPublicRoomsChunkRoom[]>(setPublicRooms);
const showNsfwPublicRooms = useSettingValue<boolean>("SpotlightSearch.showNsfwPublicRooms");
async function initProtocols(): Promise<void> {
if (!MatrixClientPeg.get()) {
// We may not have a client yet when invoked from welcome page
@ -108,7 +115,7 @@ export const usePublicRoomDirectory = (): {
try {
setLoading(true);
const { chunk } = await MatrixClientPeg.get().publicRooms(opts);
updateResult(opts, chunk);
updateResult(opts, showNsfwPublicRooms ? chunk : chunk.filter(cheapNsfwFilter));
return true;
} catch (e) {
console.error("Could not fetch public rooms for params", opts, e);
@ -118,7 +125,7 @@ export const usePublicRoomDirectory = (): {
setLoading(false);
}
},
[config, updateQuery, updateResult],
[config, updateQuery, updateResult, showNsfwPublicRooms],
);
useEffect(() => {

View File

@ -1018,6 +1018,7 @@
"Automatic gain control": "Automatic gain control",
"Echo cancellation": "Echo cancellation",
"Noise suppression": "Noise suppression",
"Show NSFW content": "Show NSFW content",
"Send analytics data": "Send analytics data",
"Record the client name, version, and url to recognise sessions more easily in session manager": "Record the client name, version, and url to recognise sessions more easily in session manager",
"Never send encrypted messages to unverified sessions from this session": "Never send encrypted messages to unverified sessions from this session",
@ -1619,6 +1620,7 @@
"Code blocks": "Code blocks",
"Images, GIFs and videos": "Images, GIFs and videos",
"Timeline": "Timeline",
"Room directory": "Room directory",
"Enable hardware acceleration (restart %(appName)s to take effect)": "Enable hardware acceleration (restart %(appName)s to take effect)",
"Autocomplete delay (ms)": "Autocomplete delay (ms)",
"Read Marker lifetime (ms)": "Read Marker lifetime (ms)",
@ -2328,6 +2330,7 @@
"Last month": "Last month",
"The beginning of the room": "The beginning of the room",
"Jump to date": "Jump to date",
"The sender has blocked you from receiving this message": "The sender has blocked you from receiving this message",
"%(displayName)s (%(matrixId)s)": "%(displayName)s (%(matrixId)s)",
"Downloading": "Downloading",
"Decrypting": "Decrypting",
@ -2402,6 +2405,7 @@
"Sorry, you can't edit a poll after votes have been cast.": "Sorry, you can't edit a poll after votes have been cast.",
"Vote not registered": "Vote not registered",
"Sorry, your vote was not registered. Please try again.": "Sorry, your vote was not registered. Please try again.",
"Due to decryption errors, some votes may not be counted": "Due to decryption errors, some votes may not be counted",
"Final result based on %(count)s votes|other": "Final result based on %(count)s votes",
"Final result based on %(count)s votes|one": "Final result based on %(count)s vote",
"Results will be visible when the poll is ended": "Results will be visible when the poll is ended",
@ -2411,6 +2415,7 @@
"Based on %(count)s votes|other": "Based on %(count)s votes",
"Based on %(count)s votes|one": "Based on %(count)s vote",
"edited": "edited",
"Ended a poll": "Ended a poll",
"Error decrypting video": "Error decrypting video",
"Error processing voice message": "Error processing voice message",
"Add reaction": "Add reaction",

View File

@ -743,6 +743,11 @@ export const SETTINGS: { [setting: string]: ISetting } = {
supportedLevels: [SettingLevel.ACCOUNT],
default: [], // list of room IDs, most recent first
},
"SpotlightSearch.showNsfwPublicRooms": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
displayName: _td("Show NSFW content"),
default: false,
},
"room_directory_servers": {
supportedLevels: [SettingLevel.ACCOUNT],
default: [],

View File

@ -89,7 +89,7 @@ export default class HTMLExporter extends Exporter {
return renderToStaticMarkup(avatar);
}
protected async wrapHTML(content: string): Promise<string> {
protected async wrapHTML(content: string, currentPage: number, nbPages: number): Promise<string> {
const roomAvatar = await this.getRoomAvatar();
const exportDate = formatFullDateNoDayNoTime(new Date());
const creator = this.room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender();
@ -128,6 +128,29 @@ export default class HTMLExporter extends Exporter {
);
const topicText = topic ? _t("Topic: %(topic)s", { topic }) : "";
const previousMessagesLink = renderToStaticMarkup(
currentPage !== 0 ? (
<div style={{ textAlign: "center" }}>
<a href={`./messages${currentPage === 1 ? "" : currentPage}.html`} style={{ fontWeight: "bold" }}>
Previous group of messages
</a>
</div>
) : (
<></>
),
);
const nextMessagesLink = renderToStaticMarkup(
currentPage < nbPages - 1 ? (
<div style={{ textAlign: "center", margin: "10px" }}>
<a href={"./messages" + (currentPage + 2) + ".html"} style={{ fontWeight: "bold" }}>
Next group of messages
</a>
</div>
) : (
<></>
),
);
return `
<!DOCTYPE html>
@ -168,6 +191,7 @@ export default class HTMLExporter extends Exporter {
<div class="mx_RoomHeader_topic" dir="auto"> ${topic} </div>
</div>
</div>
${previousMessagesLink}
<div class="mx_MainSplit">
<div class="mx_RoomView_body">
<div
@ -186,13 +210,17 @@ export default class HTMLExporter extends Exporter {
aria-live="polite"
role="list"
>
<div class="mx_NewRoomIntro">
${roomAvatar}
<h2> ${this.room.name} </h2>
<p> ${createdText} <br/><br/> ${exportedText} </p>
<br/>
<p> ${topicText} </p>
</div>
${
currentPage == 0
? `<div class="mx_NewRoomIntro">
${roomAvatar}
<h2> ${this.room.name} </h2>
<p> ${createdText} <br/><br/> ${exportedText} </p>
<br/>
<p> ${topicText} </p>
</div>`
: ""
}
${content}
</ol>
</div>
@ -205,6 +233,7 @@ export default class HTMLExporter extends Exporter {
</div>
</div>
</div>
${nextMessagesLink}
</main>
</div>
</div>
@ -381,7 +410,12 @@ export default class HTMLExporter extends Exporter {
return eventTile;
}
protected async createHTML(events: MatrixEvent[], start: number): Promise<string> {
protected async createHTML(
events: MatrixEvent[],
start: number,
currentPage: number,
nbPages: number,
): Promise<string> {
let content = "";
let prevEvent: MatrixEvent | null = null;
for (let i = start; i < Math.min(start + 1000, events.length); i++) {
@ -405,7 +439,7 @@ export default class HTMLExporter extends Exporter {
content += body;
prevEvent = event;
}
return this.wrapHTML(content);
return this.wrapHTML(content, currentPage, nbPages);
}
public async export(): Promise<void> {
@ -428,7 +462,7 @@ export default class HTMLExporter extends Exporter {
const usedClasses = new Set<string>();
for (let page = 0; page < res.length / 1000; page++) {
const html = await this.createHTML(res, page * 1000);
const html = await this.createHTML(res, page * 1000, page, res.length / 1000);
const document = new DOMParser().parseFromString(html, "text/html");
document.querySelectorAll("*").forEach((element) => {
element.classList.forEach((c) => usedClasses.add(c));

View File

@ -15,7 +15,7 @@ limitations under the License.
*/
import { EventType, MatrixClient, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
import TestRenderer from "react-test-renderer";
import { render } from "@testing-library/react";
import { ReactElement } from "react";
import { Mocked, mocked } from "jest-mock";
@ -46,43 +46,6 @@ function mockPinnedEvent(pinnedMessageIds?: string[], prevPinnedMessageIds?: str
});
}
// Helper function that renders a component to a plain text string.
// Once snapshots are introduced in tests, this function will no longer be necessary,
// and should be replaced with snapshots.
function renderComponent(component: TestRenderer.ReactTestRenderer): string {
const serializeObject = (
object:
| TestRenderer.ReactTestRendererJSON
| TestRenderer.ReactTestRendererJSON[]
| TestRenderer.ReactTestRendererNode
| TestRenderer.ReactTestRendererNode[],
): string => {
if (typeof object === "string") {
return object === " " ? "" : object;
}
if (Array.isArray(object) && object.length === 1 && typeof object[0] === "string") {
return object[0];
}
if (!Array.isArray(object) && object["type"] !== undefined && typeof object["children"] !== undefined) {
return serializeObject(object.children!);
}
if (!Array.isArray(object)) {
return "";
}
return object
.map((child) => {
return serializeObject(child);
})
.join("");
};
return serializeObject(component.toJSON()!);
}
describe("TextForEvent", () => {
describe("getSenderName()", () => {
it("Prefers sender.name", () => {
@ -105,71 +68,71 @@ describe("TextForEvent", () => {
it("mentions message when a single message was pinned, with no previously pinned messages", () => {
const event = mockPinnedEvent(["message-1"]);
const plainText = textForEvent(event);
const component = TestRenderer.create(textForEvent(event, true) as ReactElement);
const component = render(textForEvent(event, true) as ReactElement);
const expectedText = "@foo:example.com pinned a message to this room. See all pinned messages.";
expect(plainText).toBe(expectedText);
expect(renderComponent(component)).toBe(expectedText);
expect(component.container).toHaveTextContent(expectedText);
});
it("mentions message when a single message was pinned, with multiple previously pinned messages", () => {
const event = mockPinnedEvent(["message-1", "message-2", "message-3"], ["message-1", "message-2"]);
const plainText = textForEvent(event);
const component = TestRenderer.create(textForEvent(event, true) as ReactElement);
const component = render(textForEvent(event, true) as ReactElement);
const expectedText = "@foo:example.com pinned a message to this room. See all pinned messages.";
expect(plainText).toBe(expectedText);
expect(renderComponent(component)).toBe(expectedText);
expect(component.container).toHaveTextContent(expectedText);
});
it("mentions message when a single message was unpinned, with a single message previously pinned", () => {
const event = mockPinnedEvent([], ["message-1"]);
const plainText = textForEvent(event);
const component = TestRenderer.create(textForEvent(event, true) as ReactElement);
const component = render(textForEvent(event, true) as ReactElement);
const expectedText = "@foo:example.com unpinned a message from this room. See all pinned messages.";
expect(plainText).toBe(expectedText);
expect(renderComponent(component)).toBe(expectedText);
expect(component.container).toHaveTextContent(expectedText);
});
it("mentions message when a single message was unpinned, with multiple previously pinned messages", () => {
const event = mockPinnedEvent(["message-2"], ["message-1", "message-2"]);
const plainText = textForEvent(event);
const component = TestRenderer.create(textForEvent(event, true) as ReactElement);
const component = render(textForEvent(event, true) as ReactElement);
const expectedText = "@foo:example.com unpinned a message from this room. See all pinned messages.";
expect(plainText).toBe(expectedText);
expect(renderComponent(component)).toBe(expectedText);
expect(component.container).toHaveTextContent(expectedText);
});
it("shows generic text when multiple messages were pinned", () => {
const event = mockPinnedEvent(["message-1", "message-2", "message-3"], ["message-1"]);
const plainText = textForEvent(event);
const component = TestRenderer.create(textForEvent(event, true) as ReactElement);
const component = render(textForEvent(event, true) as ReactElement);
const expectedText = "@foo:example.com changed the pinned messages for the room.";
expect(plainText).toBe(expectedText);
expect(renderComponent(component)).toBe(expectedText);
expect(component.container).toHaveTextContent(expectedText);
});
it("shows generic text when multiple messages were unpinned", () => {
const event = mockPinnedEvent(["message-3"], ["message-1", "message-2", "message-3"]);
const plainText = textForEvent(event);
const component = TestRenderer.create(textForEvent(event, true) as ReactElement);
const component = render(textForEvent(event, true) as ReactElement);
const expectedText = "@foo:example.com changed the pinned messages for the room.";
expect(plainText).toBe(expectedText);
expect(renderComponent(component)).toBe(expectedText);
expect(component.container).toHaveTextContent(expectedText);
});
it("shows generic text when one message was pinned, and another unpinned", () => {
const event = mockPinnedEvent(["message-2"], ["message-1"]);
const plainText = textForEvent(event);
const component = TestRenderer.create(textForEvent(event, true) as ReactElement);
const component = render(textForEvent(event, true) as ReactElement);
const expectedText = "@foo:example.com changed the pinned messages for the room.";
expect(plainText).toBe(expectedText);
expect(renderComponent(component)).toBe(expectedText);
expect(component.container).toHaveTextContent(expectedText);
});
});

View File

@ -16,7 +16,6 @@ limitations under the License.
*/
import React from "react";
import ReactDOM from "react-dom";
import { EventEmitter } from "events";
import { MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
import FakeTimers from "@sinonjs/fake-timers";
@ -358,7 +357,7 @@ describe("MessagePanel", function () {
const [rm] = container.getElementsByClassName("mx_RoomView_myReadMarker_container");
// it should follow the <li> which wraps the event tile for event 4
const eventContainer = ReactDOM.findDOMNode(tiles[4]);
const eventContainer = tiles[4];
expect(rm.previousSibling).toEqual(eventContainer);
});

View File

@ -15,8 +15,7 @@ limitations under the License.
*/
import React from "react";
import { fireEvent, render } from "@testing-library/react";
import { act } from "react-dom/test-utils";
import { act, fireEvent, render } from "@testing-library/react";
import TabbedView, { Tab, TabLocation } from "../../../src/components/structures/TabbedView";

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { getByTestId, render, RenderResult, waitFor } from "@testing-library/react";
import { act, 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";
@ -23,7 +23,6 @@ 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";

View File

@ -15,10 +15,9 @@ limitations under the License.
*/
import React from "react";
import { fireEvent, render } from "@testing-library/react";
import { act, fireEvent, render } from "@testing-library/react";
import { Beacon, RoomMember, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { LocationAssetType } from "matrix-js-sdk/src/@types/location";
import { act } from "react-dom/test-utils";
import BeaconListItem from "../../../../src/components/views/beacon/BeaconListItem";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";

View File

@ -15,9 +15,8 @@ limitations under the License.
*/
import React from "react";
import { render, screen } from "@testing-library/react";
import { act, render, screen } from "@testing-library/react";
import * as maplibregl from "maplibre-gl";
import { act } from "react-dom/test-utils";
import { Beacon, Room, RoomMember, MatrixEvent, getBeaconInfoIdentifier } from "matrix-js-sdk/src/matrix";
import BeaconMarker from "../../../../src/components/views/beacon/BeaconMarker";

View File

@ -15,17 +15,14 @@ limitations under the License.
*/
import React from "react";
// eslint-disable-next-line deprecate/import
import { mount, ReactWrapper } from "enzyme";
import { act } from "react-dom/test-utils";
import { fireEvent, render, RenderResult } from "@testing-library/react";
import { MatrixClient, MatrixEvent, Room, RoomMember, getBeaconInfoIdentifier } from "matrix-js-sdk/src/matrix";
import * as maplibregl from "maplibre-gl";
import { mocked } from "jest-mock";
import BeaconViewDialog from "../../../../src/components/views/beacon/BeaconViewDialog";
import {
findByAttr,
findByTestId,
getMockClientWithEventEmitter,
makeBeaconEvent,
makeBeaconInfoEvent,
@ -34,8 +31,6 @@ import {
} from "../../../test-utils";
import { TILE_SERVER_WK_KEY } from "../../../../src/utils/WellKnownUtils";
import { OwnBeaconStore } from "../../../../src/stores/OwnBeaconStore";
import { BeaconDisplayStatus } from "../../../../src/components/views/beacon/displayStatus";
import BeaconListItem from "../../../../src/components/views/beacon/BeaconListItem";
describe("<BeaconViewDialog />", () => {
// 14.03.2022 16:15
@ -60,6 +55,7 @@ describe("<BeaconViewDialog />", () => {
const mapOptions = { container: {} as unknown as HTMLElement, style: "" };
const mockMap = new maplibregl.Map(mapOptions);
const mockMarker = new maplibregl.Marker();
// make fresh rooms every time
// as we update room state
@ -84,13 +80,11 @@ describe("<BeaconViewDialog />", () => {
matrixClient: mockClient as MatrixClient,
};
const getComponent = (props = {}) => mount(<BeaconViewDialog {...defaultProps} {...props} />);
const getComponent = (props = {}): RenderResult => render(<BeaconViewDialog {...defaultProps} {...props} />);
const openSidebar = (component: ReactWrapper) =>
act(() => {
findByTestId(component, "beacon-view-dialog-open-sidebar").at(0).simulate("click");
component.setProps({});
});
const openSidebar = (getByTestId: RenderResult["getByTestId"]) => {
fireEvent.click(getByTestId("beacon-view-dialog-open-sidebar"));
};
beforeEach(() => {
jest.spyOn(OwnBeaconStore.instance, "getLiveBeaconIds").mockRestore();
@ -103,14 +97,14 @@ describe("<BeaconViewDialog />", () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent))!;
beacon.addLocations([location1]);
const component = getComponent();
expect(component.find("Map").props()).toEqual(
expect.objectContaining({
centerGeoUri: "geo:51,41",
interactive: true,
}),
);
expect(component.find("SmartMarker").length).toEqual(1);
getComponent();
// centered on default event
expect(mockMap.setCenter).toHaveBeenCalledWith({
lon: 41,
lat: 51,
});
// marker added
expect(mockMarker.addTo).toHaveBeenCalledWith(mockMap);
});
it("does not render any own beacon status when user is not live sharing", () => {
@ -118,8 +112,8 @@ describe("<BeaconViewDialog />", () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent))!;
beacon.addLocations([location1]);
const component = getComponent();
expect(component.find("DialogOwnBeaconStatus").html()).toBeNull();
const { queryByText } = getComponent();
expect(queryByText("Live location enabled")).not.toBeInTheDocument();
});
it("renders own beacon status when user is live sharing", () => {
@ -130,52 +124,47 @@ describe("<BeaconViewDialog />", () => {
// mock own beacon store to show default event as alice's live beacon
jest.spyOn(OwnBeaconStore.instance, "getLiveBeaconIds").mockReturnValue([beacon.identifier]);
jest.spyOn(OwnBeaconStore.instance, "getBeaconById").mockReturnValue(beacon);
const component = getComponent();
expect(component.find("MemberAvatar").length).toBeTruthy();
expect(component.find("OwnBeaconStatus").props()).toEqual({
beacon,
displayStatus: BeaconDisplayStatus.Active,
className: "mx_DialogOwnBeaconStatus_status",
});
const { container } = getComponent();
expect(container.querySelector(".mx_DialogOwnBeaconStatus")).toMatchSnapshot();
});
it("updates markers on changes to beacons", () => {
it("updates markers on changes to beacons", async () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent))!;
beacon.addLocations([location1]);
const component = getComponent();
expect(component.find("BeaconMarker").length).toEqual(1);
const { container } = getComponent();
// one marker
expect(mockMarker.addTo).toHaveBeenCalledTimes(1);
expect(container.getElementsByClassName("mx_Marker").length).toEqual(1);
const anotherBeaconEvent = makeBeaconInfoEvent(bobId, roomId, { isLive: true }, "$bob-room1-1");
act(() => {
// emits RoomStateEvent.BeaconLiveness
room.currentState.setStateEvents([anotherBeaconEvent]);
const beacon2 = room.currentState.beacons.get(getBeaconInfoIdentifier(anotherBeaconEvent))!;
beacon2.addLocations([location1]);
});
component.setProps({});
// two markers now!
expect(component.find("BeaconMarker").length).toEqual(2);
expect(container.getElementsByClassName("mx_Marker").length).toEqual(2);
});
it("does not update bounds or center on changing beacons", () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent))!;
beacon.addLocations([location1]);
const component = getComponent();
expect(component.find("BeaconMarker").length).toEqual(1);
const { container } = getComponent();
expect(container.getElementsByClassName("mx_Marker").length).toEqual(1);
const anotherBeaconEvent = makeBeaconInfoEvent(bobId, roomId, { isLive: true }, "$bob-room1-1");
act(() => {
// emits RoomStateEvent.BeaconLiveness
room.currentState.setStateEvents([anotherBeaconEvent]);
const beacon2 = room.currentState.beacons.get(getBeaconInfoIdentifier(anotherBeaconEvent))!;
beacon2.addLocations([location1]);
});
component.setProps({});
// two markers now!
// called once on init
expect(mockMap.setCenter).toHaveBeenCalledTimes(1);
expect(mockMap.fitBounds).toHaveBeenCalledTimes(1);
});
@ -185,14 +174,12 @@ describe("<BeaconViewDialog />", () => {
const onFinished = jest.fn();
const room = setupRoom([defaultEvent]);
room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent));
const component = getComponent({ onFinished });
const { getByTestId } = getComponent({ onFinished });
// map placeholder
expect(findByTestId(component, "beacon-view-dialog-map-fallback")).toMatchSnapshot();
expect(getByTestId("beacon-view-dialog-map-fallback")).toMatchSnapshot();
act(() => {
findByTestId(component, "beacon-view-dialog-fallback-close").at(0).simulate("click");
});
fireEvent.click(getByTestId("beacon-view-dialog-fallback-close"));
expect(onFinished).toHaveBeenCalled();
});
@ -202,8 +189,8 @@ describe("<BeaconViewDialog />", () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent))!;
beacon.addLocations([location1]);
const component = getComponent({ onFinished });
expect(component.find("BeaconMarker").length).toEqual(1);
const { container } = getComponent({ onFinished });
expect(container.getElementsByClassName("mx_Marker").length).toEqual(1);
// this will replace the defaultEvent
// leading to no more live beacons
@ -219,12 +206,10 @@ describe("<BeaconViewDialog />", () => {
room.currentState.setStateEvents([anotherBeaconEvent]);
});
component.setProps({});
// no more avatars
expect(component.find("MemberAvatar").length).toBeFalsy();
expect(container.getElementsByClassName("mx_Marker").length).toEqual(0);
// map still rendered
expect(component.find("Map").length).toBeTruthy();
expect(container.querySelector("#mx_Map_mx_BeaconViewDialog")).toBeInTheDocument();
// map location unchanged
expect(mockMap.setCenter).not.toHaveBeenCalled();
expect(mockMap.fitBounds).not.toHaveBeenCalled();
@ -235,31 +220,28 @@ describe("<BeaconViewDialog />", () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent))!;
beacon.addLocations([location1]);
const component = getComponent();
const { container, getByTestId } = getComponent();
openSidebar(component);
openSidebar(getByTestId);
expect(component.find("DialogSidebar").length).toBeTruthy();
expect(container.querySelector(".mx_DialogSidebar")).toBeInTheDocument();
});
it("closes sidebar on close button click", () => {
const room = setupRoom([defaultEvent]);
const beacon = room.currentState.beacons.get(getBeaconInfoIdentifier(defaultEvent))!;
beacon.addLocations([location1]);
const component = getComponent();
const { container, getByTestId } = getComponent();
// open the sidebar
openSidebar(component);
openSidebar(getByTestId);
expect(component.find("DialogSidebar").length).toBeTruthy();
expect(container.querySelector(".mx_DialogSidebar")).toBeInTheDocument();
// now close it
act(() => {
findByAttr("data-testid")(component, "dialog-sidebar-close").at(0).simulate("click");
component.setProps({});
});
fireEvent.click(getByTestId("dialog-sidebar-close"));
expect(component.find("DialogSidebar").length).toBeFalsy();
expect(container.querySelector(".mx_DialogSidebar")).not.toBeInTheDocument();
});
});
@ -326,16 +308,17 @@ describe("<BeaconViewDialog />", () => {
[location1, location2],
);
const component = getComponent({ beacons: [beacon1, beacon2] });
const { container, getByTestId } = getComponent({ beacons: [beacon1, beacon2] });
// reset call counts on map mocks after initial render
jest.clearAllMocks();
openSidebar(component);
openSidebar(getByTestId);
act(() => {
const listItems = container.querySelectorAll(".mx_BeaconListItem");
// click on the first beacon in the list
component.find(BeaconListItem).at(0).simulate("click");
fireEvent.click(listItems[0]!);
});
// centered on clicked beacon
@ -359,16 +342,17 @@ describe("<BeaconViewDialog />", () => {
[location1, location2],
);
const component = getComponent({ beacons: [beacon1, beacon2] });
const { container, getByTestId } = getComponent({ beacons: [beacon1, beacon2] });
// reset call counts on map mocks after initial render
jest.clearAllMocks();
openSidebar(component);
openSidebar(getByTestId);
act(() => {
// click on the second beacon in the list
component.find(BeaconListItem).at(1).simulate("click");
const listItems = container.querySelectorAll(".mx_BeaconListItem");
fireEvent.click(listItems[1]!);
});
const expectedBounds = new maplibregl.LngLatBounds([22, 33], [22, 33]);
@ -378,7 +362,8 @@ describe("<BeaconViewDialog />", () => {
act(() => {
// click on the second beacon in the list
component.find(BeaconListItem).at(1).simulate("click");
const listItems = container.querySelectorAll(".mx_BeaconListItem");
fireEvent.click(listItems[1]!);
});
// centered on clicked beacon

View File

@ -15,8 +15,7 @@ limitations under the License.
*/
import React, { ComponentProps } from "react";
import { fireEvent, render } from "@testing-library/react";
import { act } from "react-dom/test-utils";
import { act, fireEvent, render } from "@testing-library/react";
import DialogSidebar from "../../../../src/components/views/beacon/DialogSidebar";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";

View File

@ -16,8 +16,7 @@ limitations under the License.
import React from "react";
import { mocked } from "jest-mock";
import { fireEvent, render } from "@testing-library/react";
import { act } from "react-dom/test-utils";
import { act, fireEvent, render } from "@testing-library/react";
import { Beacon, BeaconIdentifier } from "matrix-js-sdk/src/matrix";
import LeftPanelLiveShareWarning from "../../../../src/components/views/beacon/LeftPanelLiveShareWarning";

View File

@ -15,10 +15,9 @@ limitations under the License.
*/
import React from "react";
import { act } from "react-dom/test-utils";
import { Room, PendingEventOrdering, MatrixClient, RoomMember, RoomStateEvent } from "matrix-js-sdk/src/matrix";
import { ClientWidgetApi, Widget } from "matrix-widget-api";
import { cleanup, render, screen } from "@testing-library/react";
import { act, cleanup, render, screen } from "@testing-library/react";
import { mocked, Mocked } from "jest-mock";
import { mkRoomMember, MockedCall, setupAsyncStoreWithClient, stubClient, useMockedCalls } from "../../../test-utils";

View File

@ -15,10 +15,9 @@ limitations under the License.
*/
import React from "react";
import { act } from "react-dom/test-utils";
import { Room, Beacon, BeaconEvent, getBeaconInfoIdentifier, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { fireEvent, getByTestId, render, screen, waitFor } from "@testing-library/react";
import { act, fireEvent, getByTestId, render, screen, waitFor } from "@testing-library/react";
import RoomLiveShareWarning from "../../../../src/components/views/beacon/RoomLiveShareWarning";
import { OwnBeaconStore, OwnBeaconStoreEvent } from "../../../../src/stores/OwnBeaconStore";

View File

@ -1,83 +1,83 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<BeaconViewDialog /> renders a fallback when there are no locations 1`] = `
[
<MapFallback
className="mx_BeaconViewDialog_map"
data-test-id="beacon-view-dialog-map-fallback"
>
<div
className="mx_MapFallback mx_BeaconViewDialog_map"
data-test-id="beacon-view-dialog-map-fallback"
>
<div
className="mx_MapFallback_bg"
/>
<div
className="mx_MapFallback_icon"
/>
<span
className="mx_BeaconViewDialog_mapFallbackMessage"
>
No live locations
</span>
<AccessibleButton
data-test-id="beacon-view-dialog-fallback-close"
element="div"
kind="primary"
onClick={[MockFunction]}
role="button"
tabIndex={0}
>
<div
className="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
data-test-id="beacon-view-dialog-fallback-close"
onClick={[MockFunction]}
onKeyDown={[Function]}
onKeyUp={[Function]}
role="button"
tabIndex={0}
>
Close
</div>
</AccessibleButton>
</div>
</MapFallback>,
<div
class="mx_MapFallback mx_BeaconViewDialog_map"
data-testid="beacon-view-dialog-map-fallback"
>
<div
className="mx_MapFallback mx_BeaconViewDialog_map"
data-test-id="beacon-view-dialog-map-fallback"
class="mx_MapFallback_bg"
/>
<div
class="mx_MapFallback_icon"
/>
<span
class="mx_BeaconViewDialog_mapFallbackMessage"
>
No live locations
</span>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
data-testid="beacon-view-dialog-fallback-close"
role="button"
tabindex="0"
>
Close
</div>
</div>
`;
exports[`<BeaconViewDialog /> renders own beacon status when user is live sharing 1`] = `
<div
class="mx_DialogOwnBeaconStatus"
>
<span
class="mx_BaseAvatar mx_DialogOwnBeaconStatus_avatar"
role="presentation"
>
<span
aria-hidden="true"
class="mx_BaseAvatar_initial"
style="font-size: 20.8px; width: 32px; line-height: 32px;"
>
A
</span>
<img
alt=""
aria-hidden="true"
class="mx_BaseAvatar_image"
data-testid="avatar-img"
src="data:image/png;base64,00"
style="width: 32px; height: 32px;"
title="@alice:server"
/>
</span>
<div
class="mx_BeaconStatus mx_BeaconStatus_Active mx_DialogOwnBeaconStatus_status"
>
<div
className="mx_MapFallback_bg"
/>
<div
className="mx_MapFallback_icon"
/>
<span
className="mx_BeaconViewDialog_mapFallbackMessage"
class="mx_BeaconStatus_description"
>
No live locations
</span>
<AccessibleButton
data-test-id="beacon-view-dialog-fallback-close"
element="div"
kind="primary"
onClick={[MockFunction]}
role="button"
tabIndex={0}
>
<div
className="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
data-test-id="beacon-view-dialog-fallback-close"
onClick={[MockFunction]}
onKeyDown={[Function]}
onKeyUp={[Function]}
role="button"
tabIndex={0}
<span
class="mx_BeaconStatus_label"
>
Close
</div>
</AccessibleButton>
</div>,
]
Live location enabled
</span>
<span
class="mx_LiveTimeRemaining"
data-test-id="room-live-share-expiry"
>
1h left
</span>
</div>
<div
class="mx_AccessibleButton mx_OwnBeaconStatus_button mx_OwnBeaconStatus_destructiveButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link"
data-test-id="beacon-status-stop-beacon"
role="button"
tabindex="0"
>
Stop
</div>
</div>
</div>
`;

View File

@ -15,11 +15,10 @@ limitations under the License.
*/
import React from "react";
import { act } from "react-dom/test-utils";
import { MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
import { LocationAssetType, M_ASSET, M_LOCATION, M_TIMESTAMP } from "matrix-js-sdk/src/@types/location";
import { M_TEXT } from "matrix-js-sdk/src/@types/extensible_events";
import { fireEvent, getByTestId, render, RenderResult, screen } from "@testing-library/react";
import { act, fireEvent, getByTestId, render, RenderResult, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";

View File

@ -367,4 +367,67 @@ describe("Spotlight Dialog", () => {
expect(screen.queryByText("give feedback")).not.toBeInTheDocument();
});
});
describe("nsfw public rooms filter", () => {
const nsfwNameRoom: IPublicRoomsChunkRoom = {
room_id: "@room1:matrix.org",
name: "Room 1 [NSFW]",
topic: undefined,
world_readable: false,
num_joined_members: 1,
guest_can_join: false,
};
const nsfwTopicRoom: IPublicRoomsChunkRoom = {
room_id: "@room2:matrix.org",
name: "Room 2",
topic: "A room with a topic that includes nsfw",
world_readable: false,
num_joined_members: 1,
guest_can_join: false,
};
const potatoRoom: IPublicRoomsChunkRoom = {
room_id: "@room3:matrix.org",
name: "Potato Room 3",
topic: "Room where we discuss potatoes",
world_readable: false,
num_joined_members: 1,
guest_can_join: false,
};
beforeEach(() => {
mockedClient = mockClient({ rooms: [nsfwNameRoom, nsfwTopicRoom, potatoRoom], users: [testPerson] });
SettingsStore.setValue("SpotlightSearch.showNsfwPublicRooms", null, SettingLevel.DEVICE, false);
});
afterAll(() => {
SettingsStore.setValue("SpotlightSearch.showNsfwPublicRooms", null, SettingLevel.DEVICE, false);
});
it("does not display rooms with nsfw keywords in results when showNsfwPublicRooms is falsy", async () => {
render(<SpotlightDialog initialFilter={Filter.PublicRooms} onFinished={() => null} />);
// search is debounced
jest.advanceTimersByTime(200);
await flushPromisesWithFakeTimers();
expect(screen.getByText(potatoRoom.name)).toBeInTheDocument();
expect(screen.queryByText(nsfwTopicRoom.name)).not.toBeInTheDocument();
expect(screen.queryByText(nsfwTopicRoom.name)).not.toBeInTheDocument();
});
it("displays rooms with nsfw keywords in results when showNsfwPublicRooms is truthy", async () => {
SettingsStore.setValue("SpotlightSearch.showNsfwPublicRooms", null, SettingLevel.DEVICE, true);
render(<SpotlightDialog initialFilter={Filter.PublicRooms} onFinished={() => null} />);
// search is debounced
jest.advanceTimersByTime(200);
await flushPromisesWithFakeTimers();
expect(screen.getByText(nsfwTopicRoom.name)).toBeInTheDocument();
expect(screen.getByText(nsfwNameRoom.name)).toBeInTheDocument();
expect(screen.getByText(potatoRoom.name)).toBeInTheDocument();
});
});
});

View File

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

View File

@ -15,8 +15,7 @@ limitations under the License.
*/
import React from "react";
import { renderIntoDocument, Simulate } from "react-dom/test-utils";
import { act } from "react-dom/test-utils";
import { act, renderIntoDocument, Simulate } from "react-dom/test-utils";
import { Alignment } from "../../../../src/components/views/elements/Tooltip";
import TooltipTarget from "../../../../src/components/views/elements/TooltipTarget";

View File

@ -1,36 +1,40 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<ExternalLink /> renders link correctly 1`] = `
<a
class="mx_ExternalLink myCustomClass"
data-test-id="test"
href="test.com"
rel="noopener"
target="_self"
>
<span>
react element
<b>
children
</b>
</span>
<i
class="mx_ExternalLink_icon"
/>
</a>
<DocumentFragment>
<a
class="mx_ExternalLink myCustomClass"
data-testid="test"
href="test.com"
rel="noopener"
target="_self"
>
<span>
react element
<b>
children
</b>
</span>
<i
class="mx_ExternalLink_icon"
/>
</a>
</DocumentFragment>
`;
exports[`<ExternalLink /> renders plain text link correctly 1`] = `
<a
class="mx_ExternalLink myCustomClass"
data-test-id="test"
href="test.com"
rel="noreferrer noopener"
target="_blank"
>
test
<i
class="mx_ExternalLink_icon"
/>
</a>
<DocumentFragment>
<a
class="mx_ExternalLink myCustomClass"
data-testid="test"
href="test.com"
rel="noreferrer noopener"
target="_blank"
>
test
<i
class="mx_ExternalLink_icon"
/>
</a>
</DocumentFragment>
`;

View File

@ -15,9 +15,8 @@ limitations under the License.
*/
import React from "react";
import { fireEvent, render, RenderResult } from "@testing-library/react";
import { act, fireEvent, render, RenderResult } from "@testing-library/react";
import * as maplibregl from "maplibre-gl";
import { act } from "react-dom/test-utils";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { mocked } from "jest-mock";

View File

@ -21,8 +21,7 @@ import { MatrixClient } from "matrix-js-sdk/src/client";
import { RelationType } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { M_ASSET, LocationAssetType } from "matrix-js-sdk/src/@types/location";
import { act } from "react-dom/test-utils";
import { fireEvent, render, RenderResult } from "@testing-library/react";
import { act, fireEvent, render, RenderResult } from "@testing-library/react";
import * as maplibregl from "maplibre-gl";
import LocationShareMenu from "../../../../src/components/views/location/LocationShareMenu";

View File

@ -15,8 +15,7 @@ limitations under the License.
*/
import React from "react";
import { act } from "react-dom/test-utils";
import { fireEvent, getByTestId, render } from "@testing-library/react";
import { act, fireEvent, getByTestId, render } from "@testing-library/react";
import * as maplibregl from "maplibre-gl";
import { ClientEvent } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";

View File

@ -0,0 +1,63 @@
/*
* Copyright 2023 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 { MatrixEvent } from "matrix-js-sdk/src/matrix";
import { mkEvent } from "../../../test-utils";
import { DecryptionFailureBody } from "../../../../src/components/views/messages/DecryptionFailureBody";
describe("DecryptionFailureBody", () => {
function customRender(event: MatrixEvent) {
return render(<DecryptionFailureBody mxEvent={event} />);
}
it(`Should display "Unable to decrypt message"`, () => {
// When
const event = mkEvent({
type: "m.room.message",
room: "myfakeroom",
user: "myfakeuser",
content: {
msgtype: "m.bad.encrypted",
},
event: true,
});
const { container } = customRender(event);
// Then
expect(container).toMatchSnapshot();
});
it(`Should display "The sender has blocked you from receiving this message"`, () => {
// When
const event = mkEvent({
type: "m.room.message",
room: "myfakeroom",
user: "myfakeuser",
content: {
msgtype: "m.bad.encrypted",
},
event: true,
});
jest.spyOn(event, "isEncryptedDisabledForUnverifiedDevices", "get").mockReturnValue(true);
const { container } = customRender(event);
// Then
expect(container).toMatchSnapshot();
});
});

View File

@ -15,7 +15,7 @@ limitations under the License.
*/
import React from "react";
import TestRenderer from "react-test-renderer";
import { render } from "@testing-library/react";
import { EventEmitter } from "events";
import { MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
@ -72,29 +72,29 @@ describe("MKeyVerificationConclusion", () => {
it("shouldn't render if there's no verificationRequest", () => {
const event = new MatrixEvent({});
const renderer = TestRenderer.create(<MKeyVerificationConclusion mxEvent={event} />);
expect(renderer.toJSON()).toBeNull();
const { container } = render(<MKeyVerificationConclusion mxEvent={event} />);
expect(container).toBeEmpty();
});
it("shouldn't render if the verificationRequest is pending", () => {
const event = new MatrixEvent({});
event.verificationRequest = getMockVerificationRequest({ pending: true });
const renderer = TestRenderer.create(<MKeyVerificationConclusion mxEvent={event} />);
expect(renderer.toJSON()).toBeNull();
const { container } = render(<MKeyVerificationConclusion mxEvent={event} />);
expect(container).toBeEmpty();
});
it("shouldn't render if the event type is cancel but the request type isn't", () => {
const event = new MatrixEvent({ type: EventType.KeyVerificationCancel });
event.verificationRequest = getMockVerificationRequest({ cancelled: false });
const renderer = TestRenderer.create(<MKeyVerificationConclusion mxEvent={event} />);
expect(renderer.toJSON()).toBeNull();
const { container } = render(<MKeyVerificationConclusion mxEvent={event} />);
expect(container).toBeEmpty();
});
it("shouldn't render if the event type is done but the request type isn't", () => {
const event = new MatrixEvent({ type: "m.key.verification.done" });
event.verificationRequest = getMockVerificationRequest({ done: false });
const renderer = TestRenderer.create(<MKeyVerificationConclusion mxEvent={event} />);
expect(renderer.toJSON()).toBeNull();
const { container } = render(<MKeyVerificationConclusion mxEvent={event} />);
expect(container).toBeEmpty();
});
it("shouldn't render if the user isn't actually trusted", () => {
@ -102,8 +102,8 @@ describe("MKeyVerificationConclusion", () => {
const event = new MatrixEvent({ type: "m.key.verification.done" });
event.verificationRequest = getMockVerificationRequest({ done: true });
const renderer = TestRenderer.create(<MKeyVerificationConclusion mxEvent={event} />);
expect(renderer.toJSON()).toBeNull();
const { container } = render(<MKeyVerificationConclusion mxEvent={event} />);
expect(container).toBeEmpty();
});
it("should rerender appropriately if user trust status changes", () => {
@ -111,8 +111,8 @@ describe("MKeyVerificationConclusion", () => {
const event = new MatrixEvent({ type: "m.key.verification.done" });
event.verificationRequest = getMockVerificationRequest({ done: true, otherUserId: "@someuser:domain" });
const renderer = TestRenderer.create(<MKeyVerificationConclusion mxEvent={event} />);
expect(renderer.toJSON()).toBeNull();
const { container } = render(<MKeyVerificationConclusion mxEvent={event} />);
expect(container).toBeEmpty();
mockClient.checkUserTrust.mockReturnValue(trustworthy);
@ -122,7 +122,7 @@ describe("MKeyVerificationConclusion", () => {
"@anotheruser:domain",
new UserTrustLevel(true, true, true),
);
expect(renderer.toJSON()).toBeNull();
expect(container).toBeEmpty();
/* But when our user changes, we do rerender */
mockClient.emit(
@ -130,6 +130,6 @@ describe("MKeyVerificationConclusion", () => {
event.verificationRequest.otherUserId,
new UserTrustLevel(true, true, true),
);
expect(renderer.toJSON()).not.toBeNull();
expect(container).not.toBeEmpty();
});
});

View File

@ -765,6 +765,20 @@ describe("MPollBody", () => {
expect(container).toMatchSnapshot();
});
it("renders a warning message when poll has undecryptable relations", async () => {
const votes = [
responseEvent("@op:example.com", "pizza", 12),
responseEvent("@op:example.com", [], 13),
responseEvent("@op:example.com", "italian", 14),
responseEvent("@me:example.com", "wings", 15),
responseEvent("@qr:example.com", "italian", 16),
];
jest.spyOn(votes[1], "isDecryptionFailure").mockReturnValue(true);
const { getByText } = await newMPollBody(votes);
expect(getByText("Due to decryption errors, some votes may not be counted")).toBeInTheDocument();
});
it("renders a poll with local, non-local and invalid votes", async () => {
const votes = [
responseEvent("@a:example.com", "pizza", 12),

View File

@ -0,0 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`DecryptionFailureBody Should display "The sender has blocked you from receiving this message" 1`] = `
<div>
<div
class="mx_DecryptionFailureBody mx_EventTile_content"
>
The sender has blocked you from receiving this message
</div>
</div>
`;
exports[`DecryptionFailureBody Should display "Unable to decrypt message" 1`] = `
<div>
<div
class="mx_DecryptionFailureBody mx_EventTile_content"
>
Unable to decrypt message
</div>
</div>
`;

View File

@ -10,97 +10,104 @@ exports[`<MPollEndBody /> when poll start event does not exist in current timeli
exports[`<MPollEndBody /> when poll start event exists in current timeline renders an ended poll 1`] = `
<div>
<div
class="mx_MPollBody"
>
<h2
data-testid="pollQuestion"
<div>
<span
class="mx_Caption"
>
Question?
</h2>
Ended a poll
</span>
<div
class="mx_MPollBody_allOptions"
class="mx_MPollBody"
>
<h2
data-testid="pollQuestion"
>
Question?
</h2>
<div
class="mx_PollOption mx_PollOption_ended"
data-testid="pollOption-socks"
class="mx_MPollBody_allOptions"
>
<div
class="mx_PollOption_endedOption"
data-value="socks"
class="mx_PollOption mx_PollOption_ended"
data-testid="pollOption-socks"
>
<div
class="mx_PollOption_content"
class="mx_PollOption_endedOption"
data-value="socks"
>
<div
class="mx_PollOption_optionText"
class="mx_PollOption_content"
>
Socks
<div
class="mx_PollOption_optionText"
>
Socks
</div>
<div
class="mx_PollOption_optionVoteCount"
>
0 votes
</div>
</div>
</div>
<div
class="mx_PollOption_popularityBackground"
>
<div
class="mx_PollOption_optionVoteCount"
>
0 votes
</div>
class="mx_PollOption_popularityAmount"
style="width: 0%;"
/>
</div>
</div>
<div
class="mx_PollOption_popularityBackground"
class="mx_PollOption mx_PollOption_ended"
data-testid="pollOption-shoes"
>
<div
class="mx_PollOption_popularityAmount"
style="width: 0%;"
/>
</div>
</div>
<div
class="mx_PollOption mx_PollOption_ended"
data-testid="pollOption-shoes"
>
<div
class="mx_PollOption_endedOption"
data-value="shoes"
>
<div
class="mx_PollOption_content"
class="mx_PollOption_endedOption"
data-value="shoes"
>
<div
class="mx_PollOption_optionText"
class="mx_PollOption_content"
>
Shoes
</div>
<div
class="mx_PollOption_optionVoteCount"
>
0 votes
<div
class="mx_PollOption_optionText"
>
Shoes
</div>
<div
class="mx_PollOption_optionVoteCount"
>
0 votes
</div>
</div>
</div>
</div>
<div
class="mx_PollOption_popularityBackground"
>
<div
class="mx_PollOption_popularityAmount"
style="width: 0%;"
/>
class="mx_PollOption_popularityBackground"
>
<div
class="mx_PollOption_popularityAmount"
style="width: 0%;"
/>
</div>
</div>
</div>
</div>
<div
class="mx_MPollBody_totalVotes"
data-testid="totalVotes"
>
Final result based on 0 votes
<div
class="mx_Spinner"
class="mx_MPollBody_totalVotes"
data-testid="totalVotes"
>
Final result based on 0 votes
<div
aria-label="Loading…"
class="mx_Spinner_icon"
data-testid="spinner"
role="progressbar"
style="width: 16px; height: 16px;"
/>
class="mx_Spinner"
>
<div
aria-label="Loading…"
class="mx_Spinner_icon"
data-testid="spinner"
role="progressbar"
style="width: 16px; height: 16px;"
/>
</div>
</div>
</div>
</div>

View File

@ -18,9 +18,8 @@ import React from "react";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { Room } from "matrix-js-sdk/src/matrix";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { act } from "react-dom/test-utils";
import { mocked } from "jest-mock";
import { render, screen, fireEvent, RenderResult } from "@testing-library/react";
import { act, render, screen, fireEvent, RenderResult } from "@testing-library/react";
import SpaceStore from "../../../../src/stores/spaces/SpaceStore";
import { MetaSpace } from "../../../../src/stores/spaces";

View File

@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { ReactElement } from "react";
import ReactDOM from "react-dom";
import React from "react";
import { render } from "@testing-library/react";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
@ -37,16 +37,9 @@ describe("CryptographyPanel", () => {
const rendered = render(<CryptographyPanel />);
// Then it displays info about the user's session
const codes = rendered.querySelectorAll("code");
const codes = rendered.container.querySelectorAll("code");
expect(codes.length).toEqual(2);
expect(codes[0].innerHTML).toEqual(sessionId);
expect(codes[1].innerHTML).toEqual(sessionKeyFormatted);
});
});
function render(component: ReactElement<CryptographyPanel>): HTMLDivElement {
const parentDiv = document.createElement("div");
document.body.appendChild(parentDiv);
ReactDOM.render(component, parentDiv);
return parentDiv;
}

View File

@ -14,8 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import { fireEvent, render } from "@testing-library/react";
import { act } from "react-dom/test-utils";
import { act, fireEvent, render } from "@testing-library/react";
import { CrossSigningInfo } from "matrix-js-sdk/src/crypto/CrossSigning";
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
import { sleep } from "matrix-js-sdk/src/utils";

View File

@ -25,8 +25,7 @@ import {
PushRuleActionName,
} from "matrix-js-sdk/src/matrix";
import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids";
import { act } from "react-dom/test-utils";
import { fireEvent, getByTestId, render, screen, waitFor } from "@testing-library/react";
import { act, fireEvent, getByTestId, render, screen, waitFor } from "@testing-library/react";
import Notifications from "../../../../src/components/views/settings/Notifications";
import SettingsStore from "../../../../src/settings/SettingsStore";

View File

@ -12,8 +12,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { render } from "@testing-library/react";
import React from "react";
import { renderIntoDocument } from "react-dom/test-utils";
import SettingsFieldset from "../../../../src/components/views/settings/SettingsFieldset";
@ -21,24 +21,19 @@ describe("<SettingsFieldset />", () => {
const defaultProps = {
"legend": "Who can read history?",
"children": <div>test</div>,
"data-test-id": "test",
"data-testid": "test",
};
const getComponent = (props = {}) => {
const wrapper = renderIntoDocument<HTMLDivElement>(
<div>
<SettingsFieldset {...defaultProps} {...props} />
</div>,
) as HTMLDivElement;
return wrapper.children[0];
return render(<SettingsFieldset {...defaultProps} {...props} />);
};
it("renders fieldset without description", () => {
expect(getComponent()).toMatchSnapshot();
expect(getComponent().asFragment()).toMatchSnapshot();
});
it("renders fieldset with plain text description", () => {
const description = "Changes to who can read history.";
expect(getComponent({ description })).toMatchSnapshot();
expect(getComponent({ description }).asFragment()).toMatchSnapshot();
});
it("renders fieldset with react description", () => {
@ -48,6 +43,6 @@ describe("<SettingsFieldset />", () => {
<a href="#test">a link</a>
</>
);
expect(getComponent({ description })).toMatchSnapshot();
expect(getComponent({ description }).asFragment()).toMatchSnapshot();
});
});

View File

@ -1,66 +1,72 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<SettingsFieldset /> renders fieldset with plain text description 1`] = `
<fieldset
class="mx_SettingsFieldset"
data-test-id="test"
>
<legend
class="mx_SettingsFieldset_legend"
<DocumentFragment>
<fieldset
class="mx_SettingsFieldset"
data-testid="test"
>
Who can read history?
</legend>
<div
class="mx_SettingsFieldset_description"
>
Changes to who can read history.
</div>
<div>
test
</div>
</fieldset>
<legend
class="mx_SettingsFieldset_legend"
>
Who can read history?
</legend>
<div
class="mx_SettingsFieldset_description"
>
Changes to who can read history.
</div>
<div>
test
</div>
</fieldset>
</DocumentFragment>
`;
exports[`<SettingsFieldset /> renders fieldset with react description 1`] = `
<fieldset
class="mx_SettingsFieldset"
data-test-id="test"
>
<legend
class="mx_SettingsFieldset_legend"
<DocumentFragment>
<fieldset
class="mx_SettingsFieldset"
data-testid="test"
>
Who can read history?
</legend>
<div
class="mx_SettingsFieldset_description"
>
<p>
Test
</p>
<a
href="#test"
<legend
class="mx_SettingsFieldset_legend"
>
a link
</a>
</div>
<div>
test
</div>
</fieldset>
Who can read history?
</legend>
<div
class="mx_SettingsFieldset_description"
>
<p>
Test
</p>
<a
href="#test"
>
a link
</a>
</div>
<div>
test
</div>
</fieldset>
</DocumentFragment>
`;
exports[`<SettingsFieldset /> renders fieldset without description 1`] = `
<fieldset
class="mx_SettingsFieldset"
data-test-id="test"
>
<legend
class="mx_SettingsFieldset_legend"
<DocumentFragment>
<fieldset
class="mx_SettingsFieldset"
data-testid="test"
>
Who can read history?
</legend>
<div>
test
</div>
</fieldset>
<legend
class="mx_SettingsFieldset_legend"
>
Who can read history?
</legend>
<div>
test
</div>
</fieldset>
</DocumentFragment>
`;

View File

@ -15,8 +15,7 @@ limitations under the License.
*/
import React from "react";
import { fireEvent, render } from "@testing-library/react";
import { act } from "react-dom/test-utils";
import { act, fireEvent, render } from "@testing-library/react";
import CurrentDeviceSection from "../../../../../src/components/views/settings/devices/CurrentDeviceSection";
import { DeviceType } from "../../../../../src/utils/device/parseUserAgent";

View File

@ -14,9 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { fireEvent, render } from "@testing-library/react";
import { act, fireEvent, render } from "@testing-library/react";
import React from "react";
import { act } from "react-dom/test-utils";
import SelectableDeviceTile from "../../../../../src/components/views/settings/devices/SelectableDeviceTile";
import { DeviceType } from "../../../../../src/utils/device/parseUserAgent";

View File

@ -15,8 +15,7 @@ limitations under the License.
*/
import React from "react";
import { fireEvent, render, RenderResult } from "@testing-library/react";
import { act } from "react-dom/test-utils";
import { act, fireEvent, render, RenderResult } from "@testing-library/react";
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
import { logger } from "matrix-js-sdk/src/logger";
import { DeviceTrustLevel } from "matrix-js-sdk/src/crypto/CrossSigning";

View File

@ -16,8 +16,8 @@ limitations under the License.
import React from "react";
import { mocked } from "jest-mock";
import { renderIntoDocument, Simulate } from "react-dom/test-utils";
import { act } from "react-dom/test-utils";
import { act, Simulate } from "react-dom/test-utils";
import { fireEvent, render, RenderResult } from "@testing-library/react";
import { EventType, MatrixClient, Room } from "matrix-js-sdk/src/matrix";
import { GuestAccess, HistoryVisibility, JoinRule } from "matrix-js-sdk/src/@types/partials";
@ -83,25 +83,18 @@ describe("<SpaceSettingsVisibilityTab />", () => {
};
const getComponent = (props = {}) => {
const wrapper = renderIntoDocument<HTMLSpanElement>(
// wrap in element so renderIntoDocument can render functional component
<span>
<SpaceSettingsVisibilityTab {...defaultProps} {...props} />
</span>,
) as HTMLSpanElement;
return wrapper.children[0];
return render(<SpaceSettingsVisibilityTab {...defaultProps} {...props} />);
};
const getByTestId = (container: Element, id: string) => container.querySelector(`[data-test-id=${id}]`);
const toggleGuestAccessSection = async (component: Element) => {
const toggleButton = getByTestId(component, "toggle-guest-access-btn")!;
await act(async () => {
Simulate.click(toggleButton);
});
const toggleGuestAccessSection = async ({ getByTestId }: RenderResult) => {
const toggleButton = getByTestId("toggle-guest-access-btn")!;
fireEvent.click(toggleButton);
};
const getGuestAccessToggle = (component: Element) => component.querySelector('[aria-label="Enable guest access"]');
const getHistoryVisibilityToggle = (component: Element) => component.querySelector('[aria-label="Preview Space"]');
const getErrorMessage = (component: Element) => getByTestId(component, "space-settings-error")?.textContent;
const getGuestAccessToggle = ({ container }: RenderResult) =>
container.querySelector('[aria-label="Enable guest access"]');
const getHistoryVisibilityToggle = ({ container }: RenderResult) =>
container.querySelector('[aria-label="Preview Space"]');
const getErrorMessage = ({ getByTestId }: RenderResult) => getByTestId("space-settings-error")?.textContent;
beforeEach(() => {
(mockMatrixClient.sendStateEvent as jest.Mock).mockClear().mockResolvedValue({});
@ -113,18 +106,18 @@ describe("<SpaceSettingsVisibilityTab />", () => {
});
it("renders container", () => {
const component = getComponent();
expect(component).toMatchSnapshot();
const { asFragment } = getComponent();
expect(asFragment()).toMatchSnapshot();
});
describe("for a private space", () => {
const joinRule = JoinRule.Invite;
it("does not render addresses section", () => {
const space = makeMockSpace(mockMatrixClient, joinRule);
const component = getComponent({ space });
const { queryByTestId } = getComponent({ space });
expect(getByTestId(component, "published-address-fieldset")).toBeFalsy();
expect(getByTestId(component, "local-address-fieldset")).toBeFalsy();
expect(queryByTestId("published-address-fieldset")).toBeFalsy();
expect(queryByTestId("local-address-fieldset")).toBeFalsy();
});
});
@ -152,10 +145,7 @@ describe("<SpaceSettingsVisibilityTab />", () => {
expect(guestAccessInput?.getAttribute("aria-checked")).toEqual("true");
await act(async () => {
Simulate.click(guestAccessInput!);
});
fireEvent.click(guestAccessInput!);
expect(mockMatrixClient.sendStateEvent).toHaveBeenCalledWith(
mockSpaceId,
EventType.RoomGuestAccess,
@ -200,17 +190,14 @@ describe("<SpaceSettingsVisibilityTab />", () => {
expect(getHistoryVisibilityToggle(component)?.getAttribute("aria-checked")).toEqual("false");
});
it("updates history visibility on toggle", async () => {
it("updates history visibility on toggle", () => {
const space = makeMockSpace(mockMatrixClient, joinRule, guestRule, historyRule);
const component = getComponent({ space });
// toggle off because space settings is != WorldReadable
expect(getHistoryVisibilityToggle(component)?.getAttribute("aria-checked")).toEqual("false");
await act(async () => {
Simulate.click(getHistoryVisibilityToggle(component)!);
});
fireEvent.click(getHistoryVisibilityToggle(component)!);
expect(mockMatrixClient.sendStateEvent).toHaveBeenCalledWith(
mockSpaceId,
EventType.RoomHistoryVisibility,
@ -243,10 +230,10 @@ describe("<SpaceSettingsVisibilityTab />", () => {
it("renders addresses section", () => {
const space = makeMockSpace(mockMatrixClient, joinRule, guestRule);
const component = getComponent({ space });
const { getByTestId } = getComponent({ space });
expect(getByTestId(component, "published-address-fieldset")).toBeTruthy();
expect(getByTestId(component, "local-address-fieldset")).toBeTruthy();
expect(getByTestId("published-address-fieldset")).toBeTruthy();
expect(getByTestId("local-address-fieldset")).toBeTruthy();
});
});
});

View File

@ -16,118 +16,119 @@ exports[`<SpaceSettingsVisibilityTab /> for a public space Access renders guest
`;
exports[`<SpaceSettingsVisibilityTab /> renders container 1`] = `
<div
class="mx_SettingsTab"
>
<DocumentFragment>
<div
class="mx_SettingsTab_heading"
class="mx_SettingsTab"
>
Visibility
</div>
<fieldset
class="mx_SettingsFieldset"
data-test-id="access-fieldset"
>
<legend
class="mx_SettingsFieldset_legend"
>
Access
</legend>
<div
class="mx_SettingsFieldset_description"
class="mx_SettingsTab_heading"
>
Decide who can view and join mock-space.
Visibility
</div>
<label
class="mx_StyledRadioButton mx_JoinRuleSettings_radioButton mx_StyledRadioButton_disabled mx_StyledRadioButton_checked"
<fieldset
class="mx_SettingsFieldset"
data-testid="access-fieldset"
>
<input
aria-describedby="joinRule-invite-description"
checked=""
disabled=""
id="joinRule-invite"
name="joinRule"
type="radio"
value="invite"
/>
<div>
<div />
</div>
<div
class="mx_StyledRadioButton_content"
<legend
class="mx_SettingsFieldset_legend"
>
Private (invite only)
</div>
Access
</legend>
<div
class="mx_StyledRadioButton_spacer"
/>
</label>
<span
id="joinRule-invite-description"
>
Only invited people can join.
</span>
<label
class="mx_StyledRadioButton mx_JoinRuleSettings_radioButton mx_StyledRadioButton_disabled"
>
<input
aria-describedby="joinRule-public-description"
disabled=""
id="joinRule-public"
name="joinRule"
type="radio"
value="public"
/>
<div>
<div />
</div>
<div
class="mx_StyledRadioButton_content"
class="mx_SettingsFieldset_description"
>
Public
Decide who can view and join mock-space.
</div>
<div
class="mx_StyledRadioButton_spacer"
/>
</label>
<span
id="joinRule-public-description"
>
Anyone can find and join.
</span>
<div
class="mx_SettingsTab_toggleWithDescription"
>
<div
class="mx_SettingsFlag"
<label
class="mx_StyledRadioButton mx_JoinRuleSettings_radioButton mx_StyledRadioButton_disabled mx_StyledRadioButton_checked"
>
<span
class="mx_SettingsFlag_label"
>
Preview Space
</span>
<div
aria-checked="true"
aria-disabled="false"
aria-label="Preview Space"
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
role="switch"
tabindex="0"
>
<div
class="mx_ToggleSwitch_ball"
/>
<input
aria-describedby="joinRule-invite-description"
checked=""
disabled=""
id="joinRule-invite"
name="joinRule"
type="radio"
value="invite"
/>
<div>
<div />
</div>
<div
class="mx_StyledRadioButton_content"
>
Private (invite only)
</div>
<div
class="mx_StyledRadioButton_spacer"
/>
</label>
<span
id="joinRule-invite-description"
>
Only invited people can join.
</span>
<label
class="mx_StyledRadioButton mx_JoinRuleSettings_radioButton mx_StyledRadioButton_disabled"
>
<input
aria-describedby="joinRule-public-description"
disabled=""
id="joinRule-public"
name="joinRule"
type="radio"
value="public"
/>
<div>
<div />
</div>
<div
class="mx_StyledRadioButton_content"
>
Public
</div>
<div
class="mx_StyledRadioButton_spacer"
/>
</label>
<span
id="joinRule-public-description"
>
Anyone can find and join.
</span>
<div
class="mx_SettingsTab_toggleWithDescription"
>
<div
class="mx_SettingsFlag"
>
<span
class="mx_SettingsFlag_label"
>
Preview Space
</span>
<div
aria-checked="true"
aria-disabled="false"
aria-label="Preview Space"
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
role="switch"
tabindex="0"
>
<div
class="mx_ToggleSwitch_ball"
/>
</div>
</div>
<p>
Allow people to preview your space before they join.
<br />
<b>
Recommended for public spaces.
</b>
</p>
</div>
<p>
Allow people to preview your space before they join.
<br />
<b>
Recommended for public spaces.
</b>
</p>
</div>
</fieldset>
</div>
</fieldset>
</div>
</DocumentFragment>
`;

View File

@ -14,37 +14,32 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { render } from "@testing-library/react";
import React from "react";
import { renderIntoDocument } from "react-dom/test-utils";
import Heading from "../../../../src/components/views/typography/Heading";
describe("<Heading />", () => {
const defaultProps = {
size: "h1",
children: <div>test</div>,
["data-test-id"]: "test",
className: "test",
"size": "h1",
"children": <div>test</div>,
"data-testid": "test",
"className": "test",
} as any;
const getComponent = (props = {}) => {
const wrapper = renderIntoDocument<HTMLDivElement>(
<div>
<Heading {...defaultProps} {...props} />
</div>,
) as HTMLDivElement;
return wrapper.children[0];
return render(<Heading {...defaultProps} {...props} />);
};
it("renders h1 with correct attributes", () => {
expect(getComponent({ size: "h1" })).toMatchSnapshot();
expect(getComponent({ size: "h1" }).asFragment()).toMatchSnapshot();
});
it("renders h2 with correct attributes", () => {
expect(getComponent({ size: "h2" })).toMatchSnapshot();
expect(getComponent({ size: "h2" }).asFragment()).toMatchSnapshot();
});
it("renders h3 with correct attributes", () => {
expect(getComponent({ size: "h3" })).toMatchSnapshot();
expect(getComponent({ size: "h3" }).asFragment()).toMatchSnapshot();
});
it("renders h4 with correct attributes", () => {
expect(getComponent({ size: "h4" })).toMatchSnapshot();
expect(getComponent({ size: "h4" }).asFragment()).toMatchSnapshot();
});
});

View File

@ -1,45 +1,53 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Heading /> renders h1 with correct attributes 1`] = `
<h1
class="mx_Heading_h1 test"
data-test-id="test"
>
<div>
test
</div>
</h1>
<DocumentFragment>
<h1
class="mx_Heading_h1 test"
data-testid="test"
>
<div>
test
</div>
</h1>
</DocumentFragment>
`;
exports[`<Heading /> renders h2 with correct attributes 1`] = `
<h2
class="mx_Heading_h2 test"
data-test-id="test"
>
<div>
test
</div>
</h2>
<DocumentFragment>
<h2
class="mx_Heading_h2 test"
data-testid="test"
>
<div>
test
</div>
</h2>
</DocumentFragment>
`;
exports[`<Heading /> renders h3 with correct attributes 1`] = `
<h3
class="mx_Heading_h3 test"
data-test-id="test"
>
<div>
test
</div>
</h3>
<DocumentFragment>
<h3
class="mx_Heading_h3 test"
data-testid="test"
>
<div>
test
</div>
</h3>
</DocumentFragment>
`;
exports[`<Heading /> renders h4 with correct attributes 1`] = `
<h4
class="mx_Heading_h4 test"
data-test-id="test"
>
<div>
test
</div>
</h4>
<DocumentFragment>
<h4
class="mx_Heading_h4 test"
data-testid="test"
>
<div>
test
</div>
</h4>
</DocumentFragment>
`;

View File

@ -16,8 +16,7 @@ limitations under the License.
import React from "react";
import { Room } from "matrix-js-sdk/src/models/room";
import { act } from "react-dom/test-utils";
import { render, screen } from "@testing-library/react";
import { act, render, screen } from "@testing-library/react";
import { useTopic } from "../src/hooks/room/useTopic";
import { mkEvent, stubClient } from "./test-utils";

View File

@ -315,4 +315,45 @@ describe("HTMLExport", () => {
expect(fileName).not.toMatch(/^files\/hello/);
}
});
it("should add link to next and previous file", async () => {
const exporter = new HTMLExporter(
room,
ExportType.LastNMessages,
{
attachmentsIncluded: false,
maxSize: 1_024 * 1_024,
},
() => {},
);
// test link to the first page
//@ts-ignore private access
exporter.wrapHTML("", 0, 3).then((res) => {
expect(res).not.toContain("Previous group of messages");
expect(res).toContain(
'<div style="text-align:center;margin:10px"><a href="./messages2.html" style="font-weight:bold">Next group of messages</a></div>',
);
});
// test link for a middle page
//@ts-ignore private access
exporter.wrapHTML("", 1, 3).then((res) => {
expect(res).toContain(
'<div style="text-align:center"><a href="./messages.html" style="font-weight:bold">Previous group of messages</a></div>',
);
expect(res).toContain(
'<div style="text-align:center;margin:10px"><a href="./messages3.html" style="font-weight:bold">Next group of messages</a></div>',
);
});
// test link for last page
//@ts-ignore private access
exporter.wrapHTML("", 2, 3).then((res) => {
expect(res).toContain(
'<div style="text-align:center"><a href="./messages2.html" style="font-weight:bold">Previous group of messages</a></div>',
);
expect(res).not.toContain("Next group of messages");
});
});
});

File diff suppressed because one or more lines are too long

View File

@ -12,7 +12,6 @@ limitations under the License.
*/
import React from "react";
import { Container } from "react-dom";
import { MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix";
import { render, RenderResult } from "@testing-library/react";
@ -33,7 +32,7 @@ describe("VoiceBroadcastHeader", () => {
let client: MatrixClient;
let room: Room;
const sender = new RoomMember(roomId, userId);
let container: Container;
let container: RenderResult["container"];
const renderHeader = (live: VoiceBroadcastLiveness, showBroadcast?: boolean, buffering?: boolean): RenderResult => {
return render(

View File

@ -2336,13 +2336,6 @@
hoist-non-react-statics "^3.3.0"
redux "^4.0.0"
"@types/react-test-renderer@^17.0.1":
version "17.0.2"
resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-17.0.2.tgz#5f800a39b12ac8d2a2149e7e1885215bcf4edbbf"
integrity sha512-+F1KONQTBHDBBhbHuT2GNydeMpPuviduXIVJRB7Y4nma4NR5DrTJfMMZ+jbhEHbpwL+Uqhs1WXh4KHiyrtYTPg==
dependencies:
"@types/react" "^17"
"@types/react-transition-group@^4.4.0":
version "4.4.5"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416"
@ -2364,7 +2357,7 @@
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==
"@types/sanitize-html@^2.3.1":
"@types/sanitize-html@2.8.0":
version "2.8.0"
resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.8.0.tgz#c53d3114d832734fc299568a3458a49f9edc1eef"
integrity sha512-Uih6caOm3DsBYnVGOYn0A9NoTNe1c4aPStmHC/YA2JrpP9kx//jzaRcIklFvSpvVQEcpl/ZCr4DgISSf/YxTvg==
@ -3846,15 +3839,6 @@ dom-helpers@^5.0.1:
"@babel/runtime" "^7.8.7"
csstype "^3.0.2"
dom-serializer@^1.0.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30"
integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==
dependencies:
domelementtype "^2.0.1"
domhandler "^4.2.0"
entities "^2.0.0"
dom-serializer@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
@ -3864,7 +3848,7 @@ dom-serializer@^2.0.0:
domhandler "^5.0.2"
entities "^4.2.0"
domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0:
domelementtype@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
@ -3876,13 +3860,6 @@ domexception@^4.0.0:
dependencies:
webidl-conversions "^7.0.0"
domhandler@^4.0.0, domhandler@^4.2.0:
version "4.3.1"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c"
integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==
dependencies:
domelementtype "^2.2.0"
domhandler@^5.0.1, domhandler@^5.0.2, domhandler@^5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
@ -3890,15 +3867,6 @@ domhandler@^5.0.1, domhandler@^5.0.2, domhandler@^5.0.3:
dependencies:
domelementtype "^2.3.0"
domutils@^2.5.2:
version "2.8.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
dependencies:
dom-serializer "^1.0.1"
domelementtype "^2.2.0"
domhandler "^4.2.0"
domutils@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.0.1.tgz#696b3875238338cb186b6c0612bd4901c89a4f1c"
@ -3980,11 +3948,6 @@ enquirer@^2.3.6:
dependencies:
ansi-colors "^4.1.1"
entities@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
entities@^4.2.0, entities@^4.3.0, entities@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174"
@ -5137,16 +5100,6 @@ html-tags@^3.2.0:
resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.2.0.tgz#dbb3518d20b726524e4dd43de397eb0a95726961"
integrity sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==
htmlparser2@^6.0.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==
dependencies:
domelementtype "^2.0.1"
domhandler "^4.0.0"
domutils "^2.5.2"
entities "^2.0.0"
htmlparser2@^8.0.0, htmlparser2@^8.0.1:
version "8.0.1"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.1.tgz#abaa985474fcefe269bc761a779b544d7196d010"
@ -7535,7 +7488,7 @@ react-shallow-renderer@^16.13.1:
object-assign "^4.1.1"
react-is "^16.12.0 || ^17.0.0 || ^18.0.0"
react-test-renderer@^17.0.0, react-test-renderer@^17.0.2:
react-test-renderer@^17.0.0:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-17.0.2.tgz#4cd4ae5ef1ad5670fc0ef776e8cc7e1231d9866c"
integrity sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ==
@ -7863,14 +7816,14 @@ sanitize-filename@^1.6.3:
dependencies:
truncate-utf8-bytes "^1.0.0"
sanitize-html@^2.3.2:
version "2.7.3"
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.7.3.tgz#166c868444ee4f9fd7352ac8c63fa86c343fc2bd"
integrity sha512-jMaHG29ak4miiJ8wgqA1849iInqORgNv7SLfSw9LtfOhEUQ1C0YHKH73R+hgyufBW9ZFeJrb057k9hjlfBCVlw==
sanitize-html@2.8.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.8.0.tgz#651d1d0e5b2d61b4ec6147cc46f6d6680eef98ce"
integrity sha512-ZsGyc6avnqgvEm3eMKrcy8xa7WM1MrGrfkGsUgQee2CU+vg3PCfNCexXwBDF/6dEPvaQ4k/QqRjnYKHL8xgNjg==
dependencies:
deepmerge "^4.2.2"
escape-string-regexp "^4.0.0"
htmlparser2 "^6.0.0"
htmlparser2 "^8.0.0"
is-plain-object "^5.0.0"
parse-srcset "^1.0.2"
postcss "^8.3.11"