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: jobs:
action: action:
uses: matrix-org/matrix-js-sdk/.github/workflows/pull_request.yaml@develop uses: matrix-org/matrix-js-sdk/.github/workflows/pull_request.yaml@develop
with:
labels: "T-Defect,T-Enhancement,T-Task"
secrets: secrets:
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}

View file

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

View file

@ -38,9 +38,12 @@ limitations under the License.
cursor: pointer; cursor: pointer;
margin-left: 20px; margin-left: 20px;
display: block; 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 */ /* center to first line */
position: relative;
top: 0.15em; top: 0.15em;
background-color: $background;
&::before { &::before {
content: ""; content: "";

View file

@ -28,4 +28,9 @@ limitations under the License.
margin-bottom: $spacing-16; 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 />} {mapDisplayError && <MapError error={mapDisplayError.message as LocationShareError} isMinimised />}
{!centerGeoUri && !mapDisplayError && ( {!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> <span className="mx_BeaconViewDialog_mapFallbackMessage">{_t("No live locations")}</span>
<AccessibleButton <AccessibleButton
kind="primary" kind="primary"
onClick={onFinished} onClick={onFinished}
data-test-id="beacon-view-dialog-fallback-close" data-testid="beacon-view-dialog-fallback-close"
> >
{_t("Close")} {_t("Close")}
</AccessibleButton> </AccessibleButton>
@ -179,7 +179,7 @@ const BeaconViewDialog: React.FC<IProps> = ({ initialFocusedBeacon, roomId, matr
<AccessibleButton <AccessibleButton
kind="primary" kind="primary"
onClick={() => setSidebarOpen(true)} onClick={() => setSidebarOpen(true)}
data-test-id="beacon-view-dialog-open-sidebar" data-testid="beacon-view-dialog-open-sidebar"
className="mx_BeaconViewDialog_viewListButton" className="mx_BeaconViewDialog_viewListButton"
> >
<LiveLocationIcon height={12} /> <LiveLocationIcon height={12} />

View file

@ -14,14 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License. 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 { _t } from "../../../languageHandler";
import { IBodyProps } from "./IBodyProps"; import { IBodyProps } from "./IBodyProps";
// A placeholder element for messages that could not be decrypted function getErrorMessage(mxEvent?: MatrixEvent): string {
export default class DecryptionFailureBody extends React.Component<Partial<IBodyProps>> { return mxEvent?.isEncryptedDisabledForUnverifiedDevices
public render(): ReactNode { ? _t("The sender has blocked you from receiving this message")
return <div className="mx_DecryptionFailureBody mx_EventTile_content">{_t("Unable to decrypt message")}</div>; : _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 { private addListeners(): void {
this.state.poll?.on(PollEvent.Responses, this.onResponsesChange); this.state.poll?.on(PollEvent.Responses, this.onResponsesChange);
this.state.poll?.on(PollEvent.End, this.onRelationsChange); this.state.poll?.on(PollEvent.End, this.onRelationsChange);
this.state.poll?.on(PollEvent.UndecryptableRelations, this.render.bind(this));
} }
private removeListeners(): void { private removeListeners(): void {
if (this.state.poll) { if (this.state.poll) {
this.state.poll.off(PollEvent.Responses, this.onResponsesChange); this.state.poll.off(PollEvent.Responses, this.onResponsesChange);
this.state.poll.off(PollEvent.End, this.onRelationsChange); 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); const showResults = poll.isEnded || (disclosed && myVote !== undefined);
let totalText: string; 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 }); totalText = _t("Final result based on %(count)s votes", { count: totalVotes });
} else if (!disclosed) { } else if (!disclosed) {
totalText = _t("Results will be visible when the poll is ended"); 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 { Icon as PollIcon } from "../../../../res/img/element-icons/room/composer/poll.svg";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { _t } from "../../../languageHandler";
import { textForEvent } from "../../../TextForEvent"; import { textForEvent } from "../../../TextForEvent";
import { Caption } from "../typography/Caption";
import { IBodyProps } from "./IBodyProps"; import { IBodyProps } from "./IBodyProps";
import MPollBody from "./MPollBody"; 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 MLocationBody from "./MLocationBody";
import MjolnirBody from "./MjolnirBody"; import MjolnirBody from "./MjolnirBody";
import MBeaconBody from "./MBeaconBody"; import MBeaconBody from "./MBeaconBody";
import DecryptionFailureBody from "./DecryptionFailureBody"; import { DecryptionFailureBody } from "./DecryptionFailureBody";
import { GetRelationsForEvent, IEventTileOps } from "../rooms/EventTile"; import { GetRelationsForEvent, IEventTileOps } from "../rooms/EventTile";
import { VoiceBroadcastBody, VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "../../../voice-broadcast"; import { VoiceBroadcastBody, VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "../../../voice-broadcast";

View file

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

View file

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

View file

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

View file

@ -93,7 +93,7 @@ const SpaceSettingsVisibilityTab: React.FC<IProps> = ({ matrixClient: cli, space
advancedSection = ( advancedSection = (
<div> <div>
<AccessibleButton <AccessibleButton
data-test-id="toggle-guest-access-btn" data-testid="toggle-guest-access-btn"
onClick={toggleAdvancedSection} onClick={toggleAdvancedSection}
kind="link" kind="link"
className="mx_SettingsTab_showAdvanced" className="mx_SettingsTab_showAdvanced"
@ -141,13 +141,13 @@ const SpaceSettingsVisibilityTab: React.FC<IProps> = ({ matrixClient: cli, space
<div className="mx_SettingsTab_heading">{_t("Visibility")}</div> <div className="mx_SettingsTab_heading">{_t("Visibility")}</div>
{error && ( {error && (
<div data-test-id="space-settings-error" className="mx_SpaceRoomView_errorText"> <div data-testid="space-settings-error" className="mx_SpaceRoomView_errorText">
{error} {error}
</div> </div>
)} )}
<SettingsFieldset <SettingsFieldset
data-test-id="access-fieldset" data-testid="access-fieldset"
legend={_t("Access")} legend={_t("Access")}
description={_t("Decide who can view and join %(spaceName)s.", { spaceName: space.name })} 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 SettingsStore from "../settings/SettingsStore";
import { Protocols } from "../utils/DirectoryUtils"; import { Protocols } from "../utils/DirectoryUtils";
import { useLatestResult } from "./useLatestResult"; import { useLatestResult } from "./useLatestResult";
import { useSettingValue } from "./useSettings";
export const ALL_ROOMS = "ALL_ROOMS"; export const ALL_ROOMS = "ALL_ROOMS";
const LAST_SERVER_KEY = "mx_last_room_directory_server"; const LAST_SERVER_KEY = "mx_last_room_directory_server";
@ -38,6 +39,10 @@ export interface IPublicRoomsOpts {
let thirdParty: Protocols; 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 = (): { export const usePublicRoomDirectory = (): {
ready: boolean; ready: boolean;
loading: boolean; loading: boolean;
@ -58,6 +63,8 @@ export const usePublicRoomDirectory = (): {
const [updateQuery, updateResult] = useLatestResult<IRoomDirectoryOptions, IPublicRoomsChunkRoom[]>(setPublicRooms); const [updateQuery, updateResult] = useLatestResult<IRoomDirectoryOptions, IPublicRoomsChunkRoom[]>(setPublicRooms);
const showNsfwPublicRooms = useSettingValue<boolean>("SpotlightSearch.showNsfwPublicRooms");
async function initProtocols(): Promise<void> { async function initProtocols(): Promise<void> {
if (!MatrixClientPeg.get()) { if (!MatrixClientPeg.get()) {
// We may not have a client yet when invoked from welcome page // We may not have a client yet when invoked from welcome page
@ -108,7 +115,7 @@ export const usePublicRoomDirectory = (): {
try { try {
setLoading(true); setLoading(true);
const { chunk } = await MatrixClientPeg.get().publicRooms(opts); const { chunk } = await MatrixClientPeg.get().publicRooms(opts);
updateResult(opts, chunk); updateResult(opts, showNsfwPublicRooms ? chunk : chunk.filter(cheapNsfwFilter));
return true; return true;
} catch (e) { } catch (e) {
console.error("Could not fetch public rooms for params", opts, e); console.error("Could not fetch public rooms for params", opts, e);
@ -118,7 +125,7 @@ export const usePublicRoomDirectory = (): {
setLoading(false); setLoading(false);
} }
}, },
[config, updateQuery, updateResult], [config, updateQuery, updateResult, showNsfwPublicRooms],
); );
useEffect(() => { useEffect(() => {

View file

@ -1018,6 +1018,7 @@
"Automatic gain control": "Automatic gain control", "Automatic gain control": "Automatic gain control",
"Echo cancellation": "Echo cancellation", "Echo cancellation": "Echo cancellation",
"Noise suppression": "Noise suppression", "Noise suppression": "Noise suppression",
"Show NSFW content": "Show NSFW content",
"Send analytics data": "Send analytics data", "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", "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", "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", "Code blocks": "Code blocks",
"Images, GIFs and videos": "Images, GIFs and videos", "Images, GIFs and videos": "Images, GIFs and videos",
"Timeline": "Timeline", "Timeline": "Timeline",
"Room directory": "Room directory",
"Enable hardware acceleration (restart %(appName)s to take effect)": "Enable hardware acceleration (restart %(appName)s to take effect)", "Enable hardware acceleration (restart %(appName)s to take effect)": "Enable hardware acceleration (restart %(appName)s to take effect)",
"Autocomplete delay (ms)": "Autocomplete delay (ms)", "Autocomplete delay (ms)": "Autocomplete delay (ms)",
"Read Marker lifetime (ms)": "Read Marker lifetime (ms)", "Read Marker lifetime (ms)": "Read Marker lifetime (ms)",
@ -2328,6 +2330,7 @@
"Last month": "Last month", "Last month": "Last month",
"The beginning of the room": "The beginning of the room", "The beginning of the room": "The beginning of the room",
"Jump to date": "Jump to date", "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)", "%(displayName)s (%(matrixId)s)": "%(displayName)s (%(matrixId)s)",
"Downloading": "Downloading", "Downloading": "Downloading",
"Decrypting": "Decrypting", "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.", "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", "Vote not registered": "Vote not registered",
"Sorry, your vote was not registered. Please try again.": "Sorry, your vote was not registered. Please try again.", "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|other": "Final result based on %(count)s votes",
"Final result based on %(count)s votes|one": "Final result based on %(count)s vote", "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", "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|other": "Based on %(count)s votes",
"Based on %(count)s votes|one": "Based on %(count)s vote", "Based on %(count)s votes|one": "Based on %(count)s vote",
"edited": "edited", "edited": "edited",
"Ended a poll": "Ended a poll",
"Error decrypting video": "Error decrypting video", "Error decrypting video": "Error decrypting video",
"Error processing voice message": "Error processing voice message", "Error processing voice message": "Error processing voice message",
"Add reaction": "Add reaction", "Add reaction": "Add reaction",

View file

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

View file

@ -89,7 +89,7 @@ export default class HTMLExporter extends Exporter {
return renderToStaticMarkup(avatar); 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 roomAvatar = await this.getRoomAvatar();
const exportDate = formatFullDateNoDayNoTime(new Date()); const exportDate = formatFullDateNoDayNoTime(new Date());
const creator = this.room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender(); 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 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 ` return `
<!DOCTYPE html> <!DOCTYPE html>
@ -168,6 +191,7 @@ export default class HTMLExporter extends Exporter {
<div class="mx_RoomHeader_topic" dir="auto"> ${topic} </div> <div class="mx_RoomHeader_topic" dir="auto"> ${topic} </div>
</div> </div>
</div> </div>
${previousMessagesLink}
<div class="mx_MainSplit"> <div class="mx_MainSplit">
<div class="mx_RoomView_body"> <div class="mx_RoomView_body">
<div <div
@ -186,13 +210,17 @@ export default class HTMLExporter extends Exporter {
aria-live="polite" aria-live="polite"
role="list" role="list"
> >
<div class="mx_NewRoomIntro"> ${
${roomAvatar} currentPage == 0
<h2> ${this.room.name} </h2> ? `<div class="mx_NewRoomIntro">
<p> ${createdText} <br/><br/> ${exportedText} </p> ${roomAvatar}
<br/> <h2> ${this.room.name} </h2>
<p> ${topicText} </p> <p> ${createdText} <br/><br/> ${exportedText} </p>
</div> <br/>
<p> ${topicText} </p>
</div>`
: ""
}
${content} ${content}
</ol> </ol>
</div> </div>
@ -205,6 +233,7 @@ export default class HTMLExporter extends Exporter {
</div> </div>
</div> </div>
</div> </div>
${nextMessagesLink}
</main> </main>
</div> </div>
</div> </div>
@ -381,7 +410,12 @@ export default class HTMLExporter extends Exporter {
return eventTile; 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 content = "";
let prevEvent: MatrixEvent | null = null; let prevEvent: MatrixEvent | null = null;
for (let i = start; i < Math.min(start + 1000, events.length); i++) { for (let i = start; i < Math.min(start + 1000, events.length); i++) {
@ -405,7 +439,7 @@ export default class HTMLExporter extends Exporter {
content += body; content += body;
prevEvent = event; prevEvent = event;
} }
return this.wrapHTML(content); return this.wrapHTML(content, currentPage, nbPages);
} }
public async export(): Promise<void> { public async export(): Promise<void> {
@ -428,7 +462,7 @@ export default class HTMLExporter extends Exporter {
const usedClasses = new Set<string>(); const usedClasses = new Set<string>();
for (let page = 0; page < res.length / 1000; page++) { 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"); const document = new DOMParser().parseFromString(html, "text/html");
document.querySelectorAll("*").forEach((element) => { document.querySelectorAll("*").forEach((element) => {
element.classList.forEach((c) => usedClasses.add(c)); 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 { 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 { ReactElement } from "react";
import { Mocked, mocked } from "jest-mock"; 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("TextForEvent", () => {
describe("getSenderName()", () => { describe("getSenderName()", () => {
it("Prefers sender.name", () => { it("Prefers sender.name", () => {
@ -105,71 +68,71 @@ describe("TextForEvent", () => {
it("mentions message when a single message was pinned, with no previously pinned messages", () => { it("mentions message when a single message was pinned, with no previously pinned messages", () => {
const event = mockPinnedEvent(["message-1"]); const event = mockPinnedEvent(["message-1"]);
const plainText = textForEvent(event); 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."; const expectedText = "@foo:example.com pinned a message to this room. See all pinned messages.";
expect(plainText).toBe(expectedText); 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", () => { 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 event = mockPinnedEvent(["message-1", "message-2", "message-3"], ["message-1", "message-2"]);
const plainText = textForEvent(event); 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."; const expectedText = "@foo:example.com pinned a message to this room. See all pinned messages.";
expect(plainText).toBe(expectedText); 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", () => { it("mentions message when a single message was unpinned, with a single message previously pinned", () => {
const event = mockPinnedEvent([], ["message-1"]); const event = mockPinnedEvent([], ["message-1"]);
const plainText = textForEvent(event); 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."; const expectedText = "@foo:example.com unpinned a message from this room. See all pinned messages.";
expect(plainText).toBe(expectedText); 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", () => { it("mentions message when a single message was unpinned, with multiple previously pinned messages", () => {
const event = mockPinnedEvent(["message-2"], ["message-1", "message-2"]); const event = mockPinnedEvent(["message-2"], ["message-1", "message-2"]);
const plainText = textForEvent(event); 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."; const expectedText = "@foo:example.com unpinned a message from this room. See all pinned messages.";
expect(plainText).toBe(expectedText); expect(plainText).toBe(expectedText);
expect(renderComponent(component)).toBe(expectedText); expect(component.container).toHaveTextContent(expectedText);
}); });
it("shows generic text when multiple messages were pinned", () => { it("shows generic text when multiple messages were pinned", () => {
const event = mockPinnedEvent(["message-1", "message-2", "message-3"], ["message-1"]); const event = mockPinnedEvent(["message-1", "message-2", "message-3"], ["message-1"]);
const plainText = textForEvent(event); 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."; const expectedText = "@foo:example.com changed the pinned messages for the room.";
expect(plainText).toBe(expectedText); expect(plainText).toBe(expectedText);
expect(renderComponent(component)).toBe(expectedText); expect(component.container).toHaveTextContent(expectedText);
}); });
it("shows generic text when multiple messages were unpinned", () => { it("shows generic text when multiple messages were unpinned", () => {
const event = mockPinnedEvent(["message-3"], ["message-1", "message-2", "message-3"]); const event = mockPinnedEvent(["message-3"], ["message-1", "message-2", "message-3"]);
const plainText = textForEvent(event); 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."; const expectedText = "@foo:example.com changed the pinned messages for the room.";
expect(plainText).toBe(expectedText); 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", () => { it("shows generic text when one message was pinned, and another unpinned", () => {
const event = mockPinnedEvent(["message-2"], ["message-1"]); const event = mockPinnedEvent(["message-2"], ["message-1"]);
const plainText = textForEvent(event); 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."; const expectedText = "@foo:example.com changed the pinned messages for the room.";
expect(plainText).toBe(expectedText); 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 React from "react";
import ReactDOM from "react-dom";
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix"; import { MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
import FakeTimers from "@sinonjs/fake-timers"; import FakeTimers from "@sinonjs/fake-timers";
@ -358,7 +357,7 @@ describe("MessagePanel", function () {
const [rm] = container.getElementsByClassName("mx_RoomView_myReadMarker_container"); const [rm] = container.getElementsByClassName("mx_RoomView_myReadMarker_container");
// it should follow the <li> which wraps the event tile for event 4 // 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); expect(rm.previousSibling).toEqual(eventContainer);
}); });

View file

@ -15,8 +15,7 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import { fireEvent, render } from "@testing-library/react"; import { act, fireEvent, render } from "@testing-library/react";
import { act } from "react-dom/test-utils";
import TabbedView, { Tab, TabLocation } from "../../../src/components/structures/TabbedView"; 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. 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 userEvent from "@testing-library/user-event";
import { mocked } from "jest-mock"; import { mocked } from "jest-mock";
import { MsgType, RelationType } from "matrix-js-sdk/src/@types/event"; 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 { Room } from "matrix-js-sdk/src/models/room";
import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread"; import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread";
import React, { useState } from "react"; import React, { useState } from "react";
import { act } from "react-dom/test-utils";
import ThreadView from "../../../src/components/structures/ThreadView"; import ThreadView from "../../../src/components/structures/ThreadView";
import MatrixClientContext from "../../../src/contexts/MatrixClientContext"; import MatrixClientContext from "../../../src/contexts/MatrixClientContext";

View file

@ -15,10 +15,9 @@ limitations under the License.
*/ */
import React from "react"; 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 { Beacon, RoomMember, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { LocationAssetType } from "matrix-js-sdk/src/@types/location"; 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 BeaconListItem from "../../../../src/components/views/beacon/BeaconListItem";
import MatrixClientContext from "../../../../src/contexts/MatrixClientContext"; import MatrixClientContext from "../../../../src/contexts/MatrixClientContext";

View file

@ -15,9 +15,8 @@ limitations under the License.
*/ */
import React from "react"; 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 * 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 { Beacon, Room, RoomMember, MatrixEvent, getBeaconInfoIdentifier } from "matrix-js-sdk/src/matrix";
import BeaconMarker from "../../../../src/components/views/beacon/BeaconMarker"; import BeaconMarker from "../../../../src/components/views/beacon/BeaconMarker";

View file

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

View file

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

View file

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

View file

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

View file

@ -15,10 +15,9 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import { act } from "react-dom/test-utils";
import { Room, Beacon, BeaconEvent, getBeaconInfoIdentifier, MatrixEvent } from "matrix-js-sdk/src/matrix"; import { Room, Beacon, BeaconEvent, getBeaconInfoIdentifier, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger"; 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 RoomLiveShareWarning from "../../../../src/components/views/beacon/RoomLiveShareWarning";
import { OwnBeaconStore, OwnBeaconStoreEvent } from "../../../../src/stores/OwnBeaconStore"; import { OwnBeaconStore, OwnBeaconStoreEvent } from "../../../../src/stores/OwnBeaconStore";

View file

@ -1,83 +1,83 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<BeaconViewDialog /> renders a fallback when there are no locations 1`] = ` exports[`<BeaconViewDialog /> renders a fallback when there are no locations 1`] = `
[ <div
<MapFallback class="mx_MapFallback mx_BeaconViewDialog_map"
className="mx_BeaconViewDialog_map" data-testid="beacon-view-dialog-map-fallback"
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 <div
className="mx_MapFallback mx_BeaconViewDialog_map" class="mx_MapFallback_bg"
data-test-id="beacon-view-dialog-map-fallback" />
<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 <div
className="mx_MapFallback_bg" class="mx_BeaconStatus_description"
/>
<div
className="mx_MapFallback_icon"
/>
<span
className="mx_BeaconViewDialog_mapFallbackMessage"
> >
No live locations <span
</span> class="mx_BeaconStatus_label"
<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 Live location enabled
</div> </span>
</AccessibleButton> <span
</div>, 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 React from "react";
import { act } from "react-dom/test-utils";
import { MatrixEvent, EventType } from "matrix-js-sdk/src/matrix"; 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 { 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 { 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 userEvent from "@testing-library/user-event";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";

View file

@ -367,4 +367,67 @@ describe("Spotlight Dialog", () => {
expect(screen.queryByText("give feedback")).not.toBeInTheDocument(); 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. limitations under the License.
*/ */
import { render } from "@testing-library/react";
import React from "react"; import React from "react";
import { renderIntoDocument } from "react-dom/test-utils";
import ExternalLink from "../../../../src/components/views/elements/ExternalLink"; import ExternalLink from "../../../../src/components/views/elements/ExternalLink";
@ -22,15 +22,10 @@ describe("<ExternalLink />", () => {
"href": "test.com", "href": "test.com",
"onClick": jest.fn(), "onClick": jest.fn(),
"className": "myCustomClass", "className": "myCustomClass",
"data-test-id": "test", "data-testid": "test",
}; };
const getComponent = (props = {}) => { const getComponent = (props = {}) => {
const wrapper = renderIntoDocument<HTMLDivElement>( return render(<ExternalLink {...defaultProps} {...props} />);
<div>
<ExternalLink {...defaultProps} {...props} />
</div>,
) as HTMLDivElement;
return wrapper.children[0];
}; };
it("renders link correctly", () => { it("renders link correctly", () => {
@ -39,18 +34,19 @@ describe("<ExternalLink />", () => {
react element <b>children</b> react element <b>children</b>
</span> </span>
); );
expect(getComponent({ children, target: "_self", rel: "noopener" })).toMatchSnapshot(); expect(getComponent({ children, target: "_self", rel: "noopener" }).asFragment()).toMatchSnapshot();
}); });
it("defaults target and rel", () => { it("defaults target and rel", () => {
const children = "test"; const children = "test";
const component = getComponent({ children }); const { getByTestId } = getComponent({ children });
expect(component.getAttribute("rel")).toEqual("noreferrer noopener"); const container = getByTestId("test");
expect(component.getAttribute("target")).toEqual("_blank"); expect(container.getAttribute("rel")).toEqual("noreferrer noopener");
expect(container.getAttribute("target")).toEqual("_blank");
}); });
it("renders plain text link correctly", () => { it("renders plain text link correctly", () => {
const children = "test"; 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 React from "react";
import { renderIntoDocument, Simulate } from "react-dom/test-utils"; import { act, renderIntoDocument, Simulate } from "react-dom/test-utils";
import { act } from "react-dom/test-utils";
import { Alignment } from "../../../../src/components/views/elements/Tooltip"; import { Alignment } from "../../../../src/components/views/elements/Tooltip";
import TooltipTarget from "../../../../src/components/views/elements/TooltipTarget"; import TooltipTarget from "../../../../src/components/views/elements/TooltipTarget";

View file

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

View file

@ -15,9 +15,8 @@ limitations under the License.
*/ */
import React from "react"; 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 * as maplibregl from "maplibre-gl";
import { act } from "react-dom/test-utils";
import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { MatrixClient } from "matrix-js-sdk/src/client"; import { MatrixClient } from "matrix-js-sdk/src/client";
import { mocked } from "jest-mock"; 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 { RelationType } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { M_ASSET, LocationAssetType } from "matrix-js-sdk/src/@types/location"; import { M_ASSET, LocationAssetType } from "matrix-js-sdk/src/@types/location";
import { act } from "react-dom/test-utils"; import { act, fireEvent, render, RenderResult } from "@testing-library/react";
import { fireEvent, render, RenderResult } from "@testing-library/react";
import * as maplibregl from "maplibre-gl"; import * as maplibregl from "maplibre-gl";
import LocationShareMenu from "../../../../src/components/views/location/LocationShareMenu"; import LocationShareMenu from "../../../../src/components/views/location/LocationShareMenu";

View file

@ -15,8 +15,7 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import { act } from "react-dom/test-utils"; import { act, fireEvent, getByTestId, render } from "@testing-library/react";
import { fireEvent, getByTestId, render } from "@testing-library/react";
import * as maplibregl from "maplibre-gl"; import * as maplibregl from "maplibre-gl";
import { ClientEvent } from "matrix-js-sdk/src/matrix"; import { ClientEvent } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger"; 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 React from "react";
import TestRenderer from "react-test-renderer"; import { render } from "@testing-library/react";
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { MatrixEvent, EventType } from "matrix-js-sdk/src/matrix"; import { MatrixEvent, EventType } from "matrix-js-sdk/src/matrix";
import { CryptoEvent } from "matrix-js-sdk/src/crypto"; import { CryptoEvent } from "matrix-js-sdk/src/crypto";
@ -72,29 +72,29 @@ describe("MKeyVerificationConclusion", () => {
it("shouldn't render if there's no verificationRequest", () => { it("shouldn't render if there's no verificationRequest", () => {
const event = new MatrixEvent({}); const event = new MatrixEvent({});
const renderer = TestRenderer.create(<MKeyVerificationConclusion mxEvent={event} />); const { container } = render(<MKeyVerificationConclusion mxEvent={event} />);
expect(renderer.toJSON()).toBeNull(); expect(container).toBeEmpty();
}); });
it("shouldn't render if the verificationRequest is pending", () => { it("shouldn't render if the verificationRequest is pending", () => {
const event = new MatrixEvent({}); const event = new MatrixEvent({});
event.verificationRequest = getMockVerificationRequest({ pending: true }); event.verificationRequest = getMockVerificationRequest({ pending: true });
const renderer = TestRenderer.create(<MKeyVerificationConclusion mxEvent={event} />); const { container } = render(<MKeyVerificationConclusion mxEvent={event} />);
expect(renderer.toJSON()).toBeNull(); expect(container).toBeEmpty();
}); });
it("shouldn't render if the event type is cancel but the request type isn't", () => { it("shouldn't render if the event type is cancel but the request type isn't", () => {
const event = new MatrixEvent({ type: EventType.KeyVerificationCancel }); const event = new MatrixEvent({ type: EventType.KeyVerificationCancel });
event.verificationRequest = getMockVerificationRequest({ cancelled: false }); event.verificationRequest = getMockVerificationRequest({ cancelled: false });
const renderer = TestRenderer.create(<MKeyVerificationConclusion mxEvent={event} />); const { container } = render(<MKeyVerificationConclusion mxEvent={event} />);
expect(renderer.toJSON()).toBeNull(); expect(container).toBeEmpty();
}); });
it("shouldn't render if the event type is done but the request type isn't", () => { 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" }); const event = new MatrixEvent({ type: "m.key.verification.done" });
event.verificationRequest = getMockVerificationRequest({ done: false }); event.verificationRequest = getMockVerificationRequest({ done: false });
const renderer = TestRenderer.create(<MKeyVerificationConclusion mxEvent={event} />); const { container } = render(<MKeyVerificationConclusion mxEvent={event} />);
expect(renderer.toJSON()).toBeNull(); expect(container).toBeEmpty();
}); });
it("shouldn't render if the user isn't actually trusted", () => { 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" }); const event = new MatrixEvent({ type: "m.key.verification.done" });
event.verificationRequest = getMockVerificationRequest({ done: true }); event.verificationRequest = getMockVerificationRequest({ done: true });
const renderer = TestRenderer.create(<MKeyVerificationConclusion mxEvent={event} />); const { container } = render(<MKeyVerificationConclusion mxEvent={event} />);
expect(renderer.toJSON()).toBeNull(); expect(container).toBeEmpty();
}); });
it("should rerender appropriately if user trust status changes", () => { it("should rerender appropriately if user trust status changes", () => {
@ -111,8 +111,8 @@ describe("MKeyVerificationConclusion", () => {
const event = new MatrixEvent({ type: "m.key.verification.done" }); const event = new MatrixEvent({ type: "m.key.verification.done" });
event.verificationRequest = getMockVerificationRequest({ done: true, otherUserId: "@someuser:domain" }); event.verificationRequest = getMockVerificationRequest({ done: true, otherUserId: "@someuser:domain" });
const renderer = TestRenderer.create(<MKeyVerificationConclusion mxEvent={event} />); const { container } = render(<MKeyVerificationConclusion mxEvent={event} />);
expect(renderer.toJSON()).toBeNull(); expect(container).toBeEmpty();
mockClient.checkUserTrust.mockReturnValue(trustworthy); mockClient.checkUserTrust.mockReturnValue(trustworthy);
@ -122,7 +122,7 @@ describe("MKeyVerificationConclusion", () => {
"@anotheruser:domain", "@anotheruser:domain",
new UserTrustLevel(true, true, true), new UserTrustLevel(true, true, true),
); );
expect(renderer.toJSON()).toBeNull(); expect(container).toBeEmpty();
/* But when our user changes, we do rerender */ /* But when our user changes, we do rerender */
mockClient.emit( mockClient.emit(
@ -130,6 +130,6 @@ describe("MKeyVerificationConclusion", () => {
event.verificationRequest.otherUserId, event.verificationRequest.otherUserId,
new UserTrustLevel(true, true, true), 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(); 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 () => { it("renders a poll with local, non-local and invalid votes", async () => {
const votes = [ const votes = [
responseEvent("@a:example.com", "pizza", 12), 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`] = ` exports[`<MPollEndBody /> when poll start event exists in current timeline renders an ended poll 1`] = `
<div> <div>
<div <div>
class="mx_MPollBody" <span
> class="mx_Caption"
<h2
data-testid="pollQuestion"
> >
Question? Ended a poll
</h2> </span>
<div <div
class="mx_MPollBody_allOptions" class="mx_MPollBody"
> >
<h2
data-testid="pollQuestion"
>
Question?
</h2>
<div <div
class="mx_PollOption mx_PollOption_ended" class="mx_MPollBody_allOptions"
data-testid="pollOption-socks"
> >
<div <div
class="mx_PollOption_endedOption" class="mx_PollOption mx_PollOption_ended"
data-value="socks" data-testid="pollOption-socks"
> >
<div <div
class="mx_PollOption_content" class="mx_PollOption_endedOption"
data-value="socks"
> >
<div <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>
<div
class="mx_PollOption_popularityBackground"
>
<div <div
class="mx_PollOption_optionVoteCount" class="mx_PollOption_popularityAmount"
> style="width: 0%;"
0 votes />
</div>
</div> </div>
</div> </div>
<div <div
class="mx_PollOption_popularityBackground" class="mx_PollOption mx_PollOption_ended"
data-testid="pollOption-shoes"
> >
<div <div
class="mx_PollOption_popularityAmount" class="mx_PollOption_endedOption"
style="width: 0%;" data-value="shoes"
/>
</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"
> >
<div <div
class="mx_PollOption_optionText" class="mx_PollOption_content"
> >
Shoes <div
</div> class="mx_PollOption_optionText"
<div >
class="mx_PollOption_optionVoteCount" Shoes
> </div>
0 votes <div
class="mx_PollOption_optionVoteCount"
>
0 votes
</div>
</div> </div>
</div> </div>
</div>
<div
class="mx_PollOption_popularityBackground"
>
<div <div
class="mx_PollOption_popularityAmount" class="mx_PollOption_popularityBackground"
style="width: 0%;" >
/> <div
class="mx_PollOption_popularityAmount"
style="width: 0%;"
/>
</div>
</div> </div>
</div> </div>
</div>
<div
class="mx_MPollBody_totalVotes"
data-testid="totalVotes"
>
Final result based on 0 votes
<div <div
class="mx_Spinner" class="mx_MPollBody_totalVotes"
data-testid="totalVotes"
> >
Final result based on 0 votes
<div <div
aria-label="Loading…" class="mx_Spinner"
class="mx_Spinner_icon" >
data-testid="spinner" <div
role="progressbar" aria-label="Loading…"
style="width: 16px; height: 16px;" class="mx_Spinner_icon"
/> data-testid="spinner"
role="progressbar"
style="width: 16px; height: 16px;"
/>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -18,9 +18,8 @@ import React from "react";
import { MatrixClient } from "matrix-js-sdk/src/client"; import { MatrixClient } from "matrix-js-sdk/src/client";
import { Room } from "matrix-js-sdk/src/matrix"; import { Room } from "matrix-js-sdk/src/matrix";
import { EventType } from "matrix-js-sdk/src/@types/event"; import { EventType } from "matrix-js-sdk/src/@types/event";
import { act } from "react-dom/test-utils";
import { mocked } from "jest-mock"; 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 SpaceStore from "../../../../src/stores/spaces/SpaceStore";
import { MetaSpace } from "../../../../src/stores/spaces"; 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. limitations under the License.
*/ */
import React, { ReactElement } from "react"; import React from "react";
import ReactDOM from "react-dom"; import { render } from "@testing-library/react";
import { MatrixClient } from "matrix-js-sdk/src/matrix"; import { MatrixClient } from "matrix-js-sdk/src/matrix";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
@ -37,16 +37,9 @@ describe("CryptographyPanel", () => {
const rendered = render(<CryptographyPanel />); const rendered = render(<CryptographyPanel />);
// Then it displays info about the user's session // 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.length).toEqual(2);
expect(codes[0].innerHTML).toEqual(sessionId); expect(codes[0].innerHTML).toEqual(sessionId);
expect(codes[1].innerHTML).toEqual(sessionKeyFormatted); 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. limitations under the License.
*/ */
import React from "react"; import React from "react";
import { fireEvent, render } from "@testing-library/react"; import { act, fireEvent, render } from "@testing-library/react";
import { act } from "react-dom/test-utils";
import { CrossSigningInfo } from "matrix-js-sdk/src/crypto/CrossSigning"; import { CrossSigningInfo } from "matrix-js-sdk/src/crypto/CrossSigning";
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo"; import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
import { sleep } from "matrix-js-sdk/src/utils"; import { sleep } from "matrix-js-sdk/src/utils";

View file

@ -25,8 +25,7 @@ import {
PushRuleActionName, PushRuleActionName,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids"; import { IThreepid, ThreepidMedium } from "matrix-js-sdk/src/@types/threepids";
import { act } from "react-dom/test-utils"; import { act, fireEvent, getByTestId, render, screen, waitFor } from "@testing-library/react";
import { fireEvent, getByTestId, render, screen, waitFor } from "@testing-library/react";
import Notifications from "../../../../src/components/views/settings/Notifications"; import Notifications from "../../../../src/components/views/settings/Notifications";
import SettingsStore from "../../../../src/settings/SettingsStore"; 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. limitations under the License.
*/ */
import { render } from "@testing-library/react";
import React from "react"; import React from "react";
import { renderIntoDocument } from "react-dom/test-utils";
import SettingsFieldset from "../../../../src/components/views/settings/SettingsFieldset"; import SettingsFieldset from "../../../../src/components/views/settings/SettingsFieldset";
@ -21,24 +21,19 @@ describe("<SettingsFieldset />", () => {
const defaultProps = { const defaultProps = {
"legend": "Who can read history?", "legend": "Who can read history?",
"children": <div>test</div>, "children": <div>test</div>,
"data-test-id": "test", "data-testid": "test",
}; };
const getComponent = (props = {}) => { const getComponent = (props = {}) => {
const wrapper = renderIntoDocument<HTMLDivElement>( return render(<SettingsFieldset {...defaultProps} {...props} />);
<div>
<SettingsFieldset {...defaultProps} {...props} />
</div>,
) as HTMLDivElement;
return wrapper.children[0];
}; };
it("renders fieldset without description", () => { it("renders fieldset without description", () => {
expect(getComponent()).toMatchSnapshot(); expect(getComponent().asFragment()).toMatchSnapshot();
}); });
it("renders fieldset with plain text description", () => { it("renders fieldset with plain text description", () => {
const description = "Changes to who can read history."; const description = "Changes to who can read history.";
expect(getComponent({ description })).toMatchSnapshot(); expect(getComponent({ description }).asFragment()).toMatchSnapshot();
}); });
it("renders fieldset with react description", () => { it("renders fieldset with react description", () => {
@ -48,6 +43,6 @@ describe("<SettingsFieldset />", () => {
<a href="#test">a link</a> <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 // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<SettingsFieldset /> renders fieldset with plain text description 1`] = ` exports[`<SettingsFieldset /> renders fieldset with plain text description 1`] = `
<fieldset <DocumentFragment>
class="mx_SettingsFieldset" <fieldset
data-test-id="test" class="mx_SettingsFieldset"
> data-testid="test"
<legend
class="mx_SettingsFieldset_legend"
> >
Who can read history? <legend
</legend> class="mx_SettingsFieldset_legend"
<div >
class="mx_SettingsFieldset_description" Who can read history?
> </legend>
Changes to who can read history. <div
</div> class="mx_SettingsFieldset_description"
<div> >
test Changes to who can read history.
</div> </div>
</fieldset> <div>
test
</div>
</fieldset>
</DocumentFragment>
`; `;
exports[`<SettingsFieldset /> renders fieldset with react description 1`] = ` exports[`<SettingsFieldset /> renders fieldset with react description 1`] = `
<fieldset <DocumentFragment>
class="mx_SettingsFieldset" <fieldset
data-test-id="test" class="mx_SettingsFieldset"
> data-testid="test"
<legend
class="mx_SettingsFieldset_legend"
> >
Who can read history? <legend
</legend> class="mx_SettingsFieldset_legend"
<div
class="mx_SettingsFieldset_description"
>
<p>
Test
</p>
<a
href="#test"
> >
a link Who can read history?
</a> </legend>
</div> <div
<div> class="mx_SettingsFieldset_description"
test >
</div> <p>
</fieldset> Test
</p>
<a
href="#test"
>
a link
</a>
</div>
<div>
test
</div>
</fieldset>
</DocumentFragment>
`; `;
exports[`<SettingsFieldset /> renders fieldset without description 1`] = ` exports[`<SettingsFieldset /> renders fieldset without description 1`] = `
<fieldset <DocumentFragment>
class="mx_SettingsFieldset" <fieldset
data-test-id="test" class="mx_SettingsFieldset"
> data-testid="test"
<legend
class="mx_SettingsFieldset_legend"
> >
Who can read history? <legend
</legend> class="mx_SettingsFieldset_legend"
<div> >
test Who can read history?
</div> </legend>
</fieldset> <div>
test
</div>
</fieldset>
</DocumentFragment>
`; `;

View file

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

View file

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

View file

@ -16,8 +16,8 @@ limitations under the License.
import React from "react"; import React from "react";
import { mocked } from "jest-mock"; import { mocked } from "jest-mock";
import { renderIntoDocument, Simulate } from "react-dom/test-utils"; import { act, Simulate } from "react-dom/test-utils";
import { act } from "react-dom/test-utils"; import { fireEvent, render, RenderResult } from "@testing-library/react";
import { EventType, MatrixClient, Room } from "matrix-js-sdk/src/matrix"; import { EventType, MatrixClient, Room } from "matrix-js-sdk/src/matrix";
import { GuestAccess, HistoryVisibility, JoinRule } from "matrix-js-sdk/src/@types/partials"; import { GuestAccess, HistoryVisibility, JoinRule } from "matrix-js-sdk/src/@types/partials";
@ -83,25 +83,18 @@ describe("<SpaceSettingsVisibilityTab />", () => {
}; };
const getComponent = (props = {}) => { const getComponent = (props = {}) => {
const wrapper = renderIntoDocument<HTMLSpanElement>( return render(<SpaceSettingsVisibilityTab {...defaultProps} {...props} />);
// wrap in element so renderIntoDocument can render functional component
<span>
<SpaceSettingsVisibilityTab {...defaultProps} {...props} />
</span>,
) as HTMLSpanElement;
return wrapper.children[0];
}; };
const getByTestId = (container: Element, id: string) => container.querySelector(`[data-test-id=${id}]`); const toggleGuestAccessSection = async ({ getByTestId }: RenderResult) => {
const toggleGuestAccessSection = async (component: Element) => { const toggleButton = getByTestId("toggle-guest-access-btn")!;
const toggleButton = getByTestId(component, "toggle-guest-access-btn")!; fireEvent.click(toggleButton);
await act(async () => {
Simulate.click(toggleButton);
});
}; };
const getGuestAccessToggle = (component: Element) => component.querySelector('[aria-label="Enable guest access"]'); const getGuestAccessToggle = ({ container }: RenderResult) =>
const getHistoryVisibilityToggle = (component: Element) => component.querySelector('[aria-label="Preview Space"]'); container.querySelector('[aria-label="Enable guest access"]');
const getErrorMessage = (component: Element) => getByTestId(component, "space-settings-error")?.textContent; const getHistoryVisibilityToggle = ({ container }: RenderResult) =>
container.querySelector('[aria-label="Preview Space"]');
const getErrorMessage = ({ getByTestId }: RenderResult) => getByTestId("space-settings-error")?.textContent;
beforeEach(() => { beforeEach(() => {
(mockMatrixClient.sendStateEvent as jest.Mock).mockClear().mockResolvedValue({}); (mockMatrixClient.sendStateEvent as jest.Mock).mockClear().mockResolvedValue({});
@ -113,18 +106,18 @@ describe("<SpaceSettingsVisibilityTab />", () => {
}); });
it("renders container", () => { it("renders container", () => {
const component = getComponent(); const { asFragment } = getComponent();
expect(component).toMatchSnapshot(); expect(asFragment()).toMatchSnapshot();
}); });
describe("for a private space", () => { describe("for a private space", () => {
const joinRule = JoinRule.Invite; const joinRule = JoinRule.Invite;
it("does not render addresses section", () => { it("does not render addresses section", () => {
const space = makeMockSpace(mockMatrixClient, joinRule); const space = makeMockSpace(mockMatrixClient, joinRule);
const component = getComponent({ space }); const { queryByTestId } = getComponent({ space });
expect(getByTestId(component, "published-address-fieldset")).toBeFalsy(); expect(queryByTestId("published-address-fieldset")).toBeFalsy();
expect(getByTestId(component, "local-address-fieldset")).toBeFalsy(); expect(queryByTestId("local-address-fieldset")).toBeFalsy();
}); });
}); });
@ -152,10 +145,7 @@ describe("<SpaceSettingsVisibilityTab />", () => {
expect(guestAccessInput?.getAttribute("aria-checked")).toEqual("true"); expect(guestAccessInput?.getAttribute("aria-checked")).toEqual("true");
await act(async () => { fireEvent.click(guestAccessInput!);
Simulate.click(guestAccessInput!);
});
expect(mockMatrixClient.sendStateEvent).toHaveBeenCalledWith( expect(mockMatrixClient.sendStateEvent).toHaveBeenCalledWith(
mockSpaceId, mockSpaceId,
EventType.RoomGuestAccess, EventType.RoomGuestAccess,
@ -200,17 +190,14 @@ describe("<SpaceSettingsVisibilityTab />", () => {
expect(getHistoryVisibilityToggle(component)?.getAttribute("aria-checked")).toEqual("false"); 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 space = makeMockSpace(mockMatrixClient, joinRule, guestRule, historyRule);
const component = getComponent({ space }); const component = getComponent({ space });
// toggle off because space settings is != WorldReadable // toggle off because space settings is != WorldReadable
expect(getHistoryVisibilityToggle(component)?.getAttribute("aria-checked")).toEqual("false"); expect(getHistoryVisibilityToggle(component)?.getAttribute("aria-checked")).toEqual("false");
await act(async () => { fireEvent.click(getHistoryVisibilityToggle(component)!);
Simulate.click(getHistoryVisibilityToggle(component)!);
});
expect(mockMatrixClient.sendStateEvent).toHaveBeenCalledWith( expect(mockMatrixClient.sendStateEvent).toHaveBeenCalledWith(
mockSpaceId, mockSpaceId,
EventType.RoomHistoryVisibility, EventType.RoomHistoryVisibility,
@ -243,10 +230,10 @@ describe("<SpaceSettingsVisibilityTab />", () => {
it("renders addresses section", () => { it("renders addresses section", () => {
const space = makeMockSpace(mockMatrixClient, joinRule, guestRule); const space = makeMockSpace(mockMatrixClient, joinRule, guestRule);
const component = getComponent({ space }); const { getByTestId } = getComponent({ space });
expect(getByTestId(component, "published-address-fieldset")).toBeTruthy(); expect(getByTestId("published-address-fieldset")).toBeTruthy();
expect(getByTestId(component, "local-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`] = ` exports[`<SpaceSettingsVisibilityTab /> renders container 1`] = `
<div <DocumentFragment>
class="mx_SettingsTab"
>
<div <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 <div
class="mx_SettingsFieldset_description" class="mx_SettingsTab_heading"
> >
Decide who can view and join mock-space. Visibility
</div> </div>
<label <fieldset
class="mx_StyledRadioButton mx_JoinRuleSettings_radioButton mx_StyledRadioButton_disabled mx_StyledRadioButton_checked" class="mx_SettingsFieldset"
data-testid="access-fieldset"
> >
<input <legend
aria-describedby="joinRule-invite-description" class="mx_SettingsFieldset_legend"
checked=""
disabled=""
id="joinRule-invite"
name="joinRule"
type="radio"
value="invite"
/>
<div>
<div />
</div>
<div
class="mx_StyledRadioButton_content"
> >
Private (invite only) Access
</div> </legend>
<div <div
class="mx_StyledRadioButton_spacer" class="mx_SettingsFieldset_description"
/>
</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 Decide who can view and join mock-space.
</div> </div>
<div <label
class="mx_StyledRadioButton_spacer" class="mx_StyledRadioButton mx_JoinRuleSettings_radioButton mx_StyledRadioButton_disabled mx_StyledRadioButton_checked"
/>
</label>
<span
id="joinRule-public-description"
>
Anyone can find and join.
</span>
<div
class="mx_SettingsTab_toggleWithDescription"
>
<div
class="mx_SettingsFlag"
> >
<span <input
class="mx_SettingsFlag_label" aria-describedby="joinRule-invite-description"
> checked=""
Preview Space disabled=""
</span> id="joinRule-invite"
<div name="joinRule"
aria-checked="true" type="radio"
aria-disabled="false" value="invite"
aria-label="Preview Space" />
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled" <div>
role="switch" <div />
tabindex="0"
>
<div
class="mx_ToggleSwitch_ball"
/>
</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> </div>
<p> </fieldset>
Allow people to preview your space before they join. </div>
<br /> </DocumentFragment>
<b>
Recommended for public spaces.
</b>
</p>
</div>
</fieldset>
</div>
`; `;

View file

@ -14,37 +14,32 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { render } from "@testing-library/react";
import React from "react"; import React from "react";
import { renderIntoDocument } from "react-dom/test-utils";
import Heading from "../../../../src/components/views/typography/Heading"; import Heading from "../../../../src/components/views/typography/Heading";
describe("<Heading />", () => { describe("<Heading />", () => {
const defaultProps = { const defaultProps = {
size: "h1", "size": "h1",
children: <div>test</div>, "children": <div>test</div>,
["data-test-id"]: "test", "data-testid": "test",
className: "test", "className": "test",
} as any; } as any;
const getComponent = (props = {}) => { const getComponent = (props = {}) => {
const wrapper = renderIntoDocument<HTMLDivElement>( return render(<Heading {...defaultProps} {...props} />);
<div>
<Heading {...defaultProps} {...props} />
</div>,
) as HTMLDivElement;
return wrapper.children[0];
}; };
it("renders h1 with correct attributes", () => { it("renders h1 with correct attributes", () => {
expect(getComponent({ size: "h1" })).toMatchSnapshot(); expect(getComponent({ size: "h1" }).asFragment()).toMatchSnapshot();
}); });
it("renders h2 with correct attributes", () => { it("renders h2 with correct attributes", () => {
expect(getComponent({ size: "h2" })).toMatchSnapshot(); expect(getComponent({ size: "h2" }).asFragment()).toMatchSnapshot();
}); });
it("renders h3 with correct attributes", () => { it("renders h3 with correct attributes", () => {
expect(getComponent({ size: "h3" })).toMatchSnapshot(); expect(getComponent({ size: "h3" }).asFragment()).toMatchSnapshot();
}); });
it("renders h4 with correct attributes", () => { 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 // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Heading /> renders h1 with correct attributes 1`] = ` exports[`<Heading /> renders h1 with correct attributes 1`] = `
<h1 <DocumentFragment>
class="mx_Heading_h1 test" <h1
data-test-id="test" class="mx_Heading_h1 test"
> data-testid="test"
<div> >
test <div>
</div> test
</h1> </div>
</h1>
</DocumentFragment>
`; `;
exports[`<Heading /> renders h2 with correct attributes 1`] = ` exports[`<Heading /> renders h2 with correct attributes 1`] = `
<h2 <DocumentFragment>
class="mx_Heading_h2 test" <h2
data-test-id="test" class="mx_Heading_h2 test"
> data-testid="test"
<div> >
test <div>
</div> test
</h2> </div>
</h2>
</DocumentFragment>
`; `;
exports[`<Heading /> renders h3 with correct attributes 1`] = ` exports[`<Heading /> renders h3 with correct attributes 1`] = `
<h3 <DocumentFragment>
class="mx_Heading_h3 test" <h3
data-test-id="test" class="mx_Heading_h3 test"
> data-testid="test"
<div> >
test <div>
</div> test
</h3> </div>
</h3>
</DocumentFragment>
`; `;
exports[`<Heading /> renders h4 with correct attributes 1`] = ` exports[`<Heading /> renders h4 with correct attributes 1`] = `
<h4 <DocumentFragment>
class="mx_Heading_h4 test" <h4
data-test-id="test" class="mx_Heading_h4 test"
> data-testid="test"
<div> >
test <div>
</div> test
</h4> </div>
</h4>
</DocumentFragment>
`; `;

View file

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

View file

@ -315,4 +315,45 @@ describe("HTMLExport", () => {
expect(fileName).not.toMatch(/^files\/hello/); 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 React from "react";
import { Container } from "react-dom";
import { MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix"; import { MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix";
import { render, RenderResult } from "@testing-library/react"; import { render, RenderResult } from "@testing-library/react";
@ -33,7 +32,7 @@ describe("VoiceBroadcastHeader", () => {
let client: MatrixClient; let client: MatrixClient;
let room: Room; let room: Room;
const sender = new RoomMember(roomId, userId); const sender = new RoomMember(roomId, userId);
let container: Container; let container: RenderResult["container"];
const renderHeader = (live: VoiceBroadcastLiveness, showBroadcast?: boolean, buffering?: boolean): RenderResult => { const renderHeader = (live: VoiceBroadcastLiveness, showBroadcast?: boolean, buffering?: boolean): RenderResult => {
return render( return render(

View file

@ -2336,13 +2336,6 @@
hoist-non-react-statics "^3.3.0" hoist-non-react-statics "^3.3.0"
redux "^4.0.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": "@types/react-transition-group@^4.4.0":
version "4.4.5" version "4.4.5"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.5.tgz#aae20dcf773c5aa275d5b9f7cdbca638abc5e416" 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" resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==
"@types/sanitize-html@^2.3.1": "@types/sanitize-html@2.8.0":
version "2.8.0" version "2.8.0"
resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.8.0.tgz#c53d3114d832734fc299568a3458a49f9edc1eef" resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.8.0.tgz#c53d3114d832734fc299568a3458a49f9edc1eef"
integrity sha512-Uih6caOm3DsBYnVGOYn0A9NoTNe1c4aPStmHC/YA2JrpP9kx//jzaRcIklFvSpvVQEcpl/ZCr4DgISSf/YxTvg== integrity sha512-Uih6caOm3DsBYnVGOYn0A9NoTNe1c4aPStmHC/YA2JrpP9kx//jzaRcIklFvSpvVQEcpl/ZCr4DgISSf/YxTvg==
@ -3846,15 +3839,6 @@ dom-helpers@^5.0.1:
"@babel/runtime" "^7.8.7" "@babel/runtime" "^7.8.7"
csstype "^3.0.2" 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: dom-serializer@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" 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" domhandler "^5.0.2"
entities "^4.2.0" entities "^4.2.0"
domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: domelementtype@^2.3.0:
version "2.3.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
@ -3876,13 +3860,6 @@ domexception@^4.0.0:
dependencies: dependencies:
webidl-conversions "^7.0.0" 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: domhandler@^5.0.1, domhandler@^5.0.2, domhandler@^5.0.3:
version "5.0.3" version "5.0.3"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" 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: dependencies:
domelementtype "^2.3.0" 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: domutils@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.0.1.tgz#696b3875238338cb186b6c0612bd4901c89a4f1c" resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.0.1.tgz#696b3875238338cb186b6c0612bd4901c89a4f1c"
@ -3980,11 +3948,6 @@ enquirer@^2.3.6:
dependencies: dependencies:
ansi-colors "^4.1.1" 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: entities@^4.2.0, entities@^4.3.0, entities@^4.4.0:
version "4.4.0" version "4.4.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174" 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" resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.2.0.tgz#dbb3518d20b726524e4dd43de397eb0a95726961"
integrity sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg== 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: htmlparser2@^8.0.0, htmlparser2@^8.0.1:
version "8.0.1" version "8.0.1"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.1.tgz#abaa985474fcefe269bc761a779b544d7196d010" 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" object-assign "^4.1.1"
react-is "^16.12.0 || ^17.0.0 || ^18.0.0" 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" version "17.0.2"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-17.0.2.tgz#4cd4ae5ef1ad5670fc0ef776e8cc7e1231d9866c" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-17.0.2.tgz#4cd4ae5ef1ad5670fc0ef776e8cc7e1231d9866c"
integrity sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ== integrity sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ==
@ -7863,14 +7816,14 @@ sanitize-filename@^1.6.3:
dependencies: dependencies:
truncate-utf8-bytes "^1.0.0" truncate-utf8-bytes "^1.0.0"
sanitize-html@^2.3.2: sanitize-html@2.8.0:
version "2.7.3" version "2.8.0"
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.7.3.tgz#166c868444ee4f9fd7352ac8c63fa86c343fc2bd" resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.8.0.tgz#651d1d0e5b2d61b4ec6147cc46f6d6680eef98ce"
integrity sha512-jMaHG29ak4miiJ8wgqA1849iInqORgNv7SLfSw9LtfOhEUQ1C0YHKH73R+hgyufBW9ZFeJrb057k9hjlfBCVlw== integrity sha512-ZsGyc6avnqgvEm3eMKrcy8xa7WM1MrGrfkGsUgQee2CU+vg3PCfNCexXwBDF/6dEPvaQ4k/QqRjnYKHL8xgNjg==
dependencies: dependencies:
deepmerge "^4.2.2" deepmerge "^4.2.2"
escape-string-regexp "^4.0.0" escape-string-regexp "^4.0.0"
htmlparser2 "^6.0.0" htmlparser2 "^8.0.0"
is-plain-object "^5.0.0" is-plain-object "^5.0.0"
parse-srcset "^1.0.2" parse-srcset "^1.0.2"
postcss "^8.3.11" postcss "^8.3.11"